rubygl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/Rakefile +83 -0
- data/RubyGL.gemspec +23 -0
- data/bin/ffi_code_gen.rb +167 -0
- data/bin/gl_code_gen.rb +459 -0
- data/examples/faceted_example.rb +65 -0
- data/examples/instanced_example.rb +128 -0
- data/examples/phong_example.rb +72 -0
- data/ext/windows/RubyGL.so +0 -0
- data/ext/windows/SDL2.dll +0 -0
- data/lib/RubyGL/Native/glcontext.rb +48 -0
- data/lib/RubyGL/Native/include/GLContext.h +37 -0
- data/lib/RubyGL/Native/include/Input.h +16 -0
- data/lib/RubyGL/Native/include/Window.h +28 -0
- data/lib/RubyGL/Native/input.rb +13 -0
- data/lib/RubyGL/Native/opengl.rb +2032 -0
- data/lib/RubyGL/Native/src/GLContext.c +73 -0
- data/lib/RubyGL/Native/src/Input.c +26 -0
- data/lib/RubyGL/Native/src/Window.c +58 -0
- data/lib/RubyGL/Native/window.rb +25 -0
- data/lib/RubyGL/callback.rb +10 -0
- data/lib/RubyGL/geometry.rb +212 -0
- data/lib/RubyGL/math.rb +301 -0
- data/lib/RubyGL/memory.rb +122 -0
- data/lib/RubyGL/setup.rb +51 -0
- data/lib/RubyGL/shader.rb +203 -0
- data/lib/RubyGL/util.rb +77 -0
- data/lib/rubygl.rb +48 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d0db02206c2e8a176817a841bc3ced8cfe60b4af
|
4
|
+
data.tar.gz: cc0aee2bc3df4f86b43b151e666b967ad9153b03
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c37b401ada50218e49983b17a3e7d8f9486fe1a05f1b5bd99ac1ec67fd83cea4a6254c9febf8fa903a21b1f9364bed9bb8c1f8399a862e7eb7e71c3f220d5911
|
7
|
+
data.tar.gz: 7148dd01e893ed00fcfccb884b34d05e24a946ee01d668b637b3defc22a77b0f378784b225512394e6728a2f8210a266e2b247e3c36b3b05204623c3d33d7a86
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Andrew
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'rake/task'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
SDL_INCLUDE = 'C:\Users\GG\Desktop\SDL2-2.0.3\i686-w64-mingw32\include\SDL2'
|
6
|
+
SDL_LIB = 'C:\Users\GG\Desktop\SDL2-2.0.3\i686-w64-mingw32\lib'
|
7
|
+
SDL_BIN = 'C:\Users\GG\Desktop\SDL2-2.0.3\i686-w64-mingw32\bin'
|
8
|
+
|
9
|
+
MINGW_LIB = ''
|
10
|
+
|
11
|
+
PROJ_INCLUDE = 'lib/RubyGL/Native/include'
|
12
|
+
PROJ_SRC = 'lib/RubyGL/Native/src/'
|
13
|
+
|
14
|
+
# OS Detection
|
15
|
+
def os
|
16
|
+
@os ||= (
|
17
|
+
host_os = RbConfig::CONFIG['host_os']
|
18
|
+
|
19
|
+
case host_os
|
20
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
21
|
+
:windows
|
22
|
+
when /darwin|mac os/
|
23
|
+
:macosx
|
24
|
+
when /linux/
|
25
|
+
:linux
|
26
|
+
when /solaris|bsd/
|
27
|
+
:unix
|
28
|
+
else
|
29
|
+
raise Error::WebDriverError, "unknown os: #{host_os.inspect}"
|
30
|
+
end
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
/---------------------------------RAKE JOBS-----------------------------------/
|
35
|
+
|
36
|
+
task :default do
|
37
|
+
files = ['Audio', 'Window', 'GLContext', 'Input', 'OpenGL']
|
38
|
+
|
39
|
+
# Build Object Files
|
40
|
+
obj = "gcc -I#{SDL_INCLUDE} -I#{PROJ_INCLUDE} -c -fPIC REPLACE.c -o REPLACE.o"
|
41
|
+
files.each { |file|
|
42
|
+
sh (obj.gsub(/REPLACE/, PROJ_SRC + file))
|
43
|
+
file = file.prepend PROJ_SRC
|
44
|
+
}
|
45
|
+
|
46
|
+
# Build Shared Object File
|
47
|
+
bin = "gcc -L#{SDL_LIB} -L#{MINGW_LIB} #{files.join('.o ') << '.o'} \
|
48
|
+
-lmingw32 -lSDL2main -lSDL2 -lopengl32 -shared -o \
|
49
|
+
ext/#{os.to_s}/RubyGL.so"
|
50
|
+
sh bin
|
51
|
+
end
|
52
|
+
|
53
|
+
task :clean do
|
54
|
+
# Clean Out Object Files
|
55
|
+
FileUtils.rm Dir.glob(PROJ_SRC + '/*.o')
|
56
|
+
end
|
57
|
+
|
58
|
+
task :bindings do
|
59
|
+
shared_lib = "RubyGL.so"
|
60
|
+
|
61
|
+
Dir.mktmpdir { |dir|
|
62
|
+
# Generates Header And Source C Files
|
63
|
+
sh "ruby bin/gl_code_gen.rb gl 4.5 OpenGL #{dir}"
|
64
|
+
|
65
|
+
# Run Preprocessor On Source File
|
66
|
+
sh "gcc -E -I#{SDL_INCLUDE} -o #{dir}/OpenGL.o #{dir}/OpenGL.c"
|
67
|
+
|
68
|
+
# Pipe Preprocessor(ed) File And Header File To Generate Bindings
|
69
|
+
sh "ruby bin/ffi_code_gen.rb #{dir}/OpenGL.o #{dir}/OpenGL.h \
|
70
|
+
#{dir}/opengl.rb #{shared_lib}"
|
71
|
+
|
72
|
+
# Move Files To Correct Folders
|
73
|
+
mv "#{dir}/OpenGL.h", PROJ_INCLUDE
|
74
|
+
mv "#{dir}/OpenGL.c", PROJ_SRC
|
75
|
+
mv "#{dir}/opengl.rb", "lib/RubyGL/Native/"
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
Rake::TestTask.new do |t|
|
80
|
+
t.libs << "tests"
|
81
|
+
t.test_files = FileList['tests/test*.rb']
|
82
|
+
t.verbose = true
|
83
|
+
end
|
data/RubyGL.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'rubygl'
|
5
|
+
spec.version = '0.1.0'
|
6
|
+
spec.authors = ["Andrew Miller"]
|
7
|
+
spec.email = ["millera9@seattleu.edu"]
|
8
|
+
spec.summary = %q{A Complete Solution For Graphics Programming In Ruby}
|
9
|
+
spec.description = %q{This library provides you with all of the essentials for
|
10
|
+
doing graphics programming in Ruby including windowing, context management,
|
11
|
+
1:1 mapping of OpenGL calls, and input handling. The backend of this library
|
12
|
+
consists of FFI functions with 1:1 mapping to the C code that makes all of this
|
13
|
+
possible. The frontend of this library provides abstractions for interacting with
|
14
|
+
the backend functionality so that you do not have to deal with the manual memory
|
15
|
+
management from within Ruby that the backend requires.}
|
16
|
+
spec.homepage = "https://github.com/GGist/RubyGL"
|
17
|
+
spec.license = "MIT"
|
18
|
+
|
19
|
+
spec.files = `git ls-files`.split($/)
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency 'ffi'
|
23
|
+
end
|
data/bin/ffi_code_gen.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
def resolve_typedef(type, type_defs)
|
4
|
+
while (type_defs[type]) do
|
5
|
+
type = type_defs[type]
|
6
|
+
end
|
7
|
+
|
8
|
+
return type
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_type(type, hash)
|
12
|
+
hash[type]
|
13
|
+
end
|
14
|
+
|
15
|
+
def match_type(type, regex_hash)
|
16
|
+
regex_hash.select { |key,val|
|
17
|
+
type =~ key
|
18
|
+
}.values[0]
|
19
|
+
end
|
20
|
+
|
21
|
+
def write_file(name, contents, mode = 'w+')
|
22
|
+
File.open(name, mode) { |file|
|
23
|
+
file << contents
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
/--------------------------------START SCRIPT---------------------------------/
|
28
|
+
# ARGV: [Object File] [Header File] [Ruby Source Name] [Library Name]
|
29
|
+
|
30
|
+
SOURCE_INDENTS = 4
|
31
|
+
FFI_TYPES = { 'char' => 'char', 'signed char' => 'char',
|
32
|
+
'unsigned char' => 'uchar', 'int8_t' => 'int8', 'uint8_t' => 'uint8',
|
33
|
+
'short' => 'short', 'unsigned short' => 'ushort', 'int16_t' => 'int16',
|
34
|
+
'uint16_t' => 'uint16', 'int' => 'int', 'unsigned int' => 'uint',
|
35
|
+
'int32_t' => 'int32', 'uint32_t' => 'uint32', 'long int' => 'long',
|
36
|
+
'unsigned long int' => 'ulong', 'int64_t' => 'int64', 'uint64_t' => 'uint64',
|
37
|
+
'long long int' => 'long_long', 'unsigned long long int' => 'ulong_long',
|
38
|
+
'float' => 'float', 'double' => 'double' }
|
39
|
+
FFI_PTR_TYPES = { /\Aconst +(?:unsigned +)?char *\*\Z/ => 'string', /\*/ => 'pointer' }
|
40
|
+
|
41
|
+
# Open Pre-Processor(ed) Source File
|
42
|
+
source_code = ''
|
43
|
+
File.open(ARGV[0]) { |file|
|
44
|
+
source_code = file.read
|
45
|
+
}
|
46
|
+
|
47
|
+
# Associate Type Definitions With Their Actual Types
|
48
|
+
typedef_hash = {}
|
49
|
+
source_code.scan(/^typedef (.*?;)$/).flatten.each { |segment|
|
50
|
+
segment.gsub!(/\*/, '* ')
|
51
|
+
|
52
|
+
if (segment =~ /\(.*?\*.*?\)/) then # Function Pointer
|
53
|
+
key = segment[/\* *(.*?) *\)/, 1]
|
54
|
+
|
55
|
+
typedef_hash[key] = segment[0..-2] # Remove Semi-Colon
|
56
|
+
else # Other Type
|
57
|
+
key = segment[/([^ ]+);/, 1]
|
58
|
+
|
59
|
+
typedef_hash[key] = segment[/(.*?) +[^ ]+;/, 1]
|
60
|
+
end
|
61
|
+
}
|
62
|
+
|
63
|
+
# Open Header File
|
64
|
+
source_code = ''
|
65
|
+
File.open(ARGV[1]) { |file|
|
66
|
+
source_code = file.read
|
67
|
+
}
|
68
|
+
|
69
|
+
# Associate Macros With Their Literal Types (OpenGL Enumerations)
|
70
|
+
macro_tuples = []
|
71
|
+
source_code.scan(/^#define GL_.+?$/i).each { |macro|
|
72
|
+
token = macro[/^.+ +(.+) +.+$/, 1]
|
73
|
+
value = macro[/^.+ +.+ +(.+)$/, 1]
|
74
|
+
macro_tuples.push([token, value])
|
75
|
+
}
|
76
|
+
|
77
|
+
# Pull Out OpenGL Function Names From Header
|
78
|
+
gl_functions = source_code.scan(/^.+?gl.+?\(.*?\);$/)
|
79
|
+
|
80
|
+
ffi_template = "require 'ffi'
|
81
|
+
|
82
|
+
module RubyGL::Native
|
83
|
+
FUNCTIONS_GO_HERE
|
84
|
+
|
85
|
+
CONSTANTS_GO_HERE
|
86
|
+
end
|
87
|
+
"
|
88
|
+
|
89
|
+
# Append Functions (Param Type -> Typedefs -> Ruby Type)
|
90
|
+
buffer = ''
|
91
|
+
gl_functions.each { |signature|
|
92
|
+
buffer << ' ' * SOURCE_INDENTS + 'attach_function :' +
|
93
|
+
signature[/(gl.*?)\(.*?\);/, 1] + ', ['
|
94
|
+
|
95
|
+
# Get Type List
|
96
|
+
param_type_list = signature[/\((.*?)\);/, 1].split(', ').map { |full_param|
|
97
|
+
full_param[/(.+) [[:alnum:]]+/, 1]
|
98
|
+
}
|
99
|
+
|
100
|
+
# Type Definition -> Primitive (Actual) Type
|
101
|
+
param_type_list.map! { |param_type|
|
102
|
+
# Parameter Type Could Use Multiple Type Definitions
|
103
|
+
|
104
|
+
simple_param_types = param_type.gsub(/(?:\*|const)/, ' ').split(' ')
|
105
|
+
# Translate Each Type And Put Back Into param_type
|
106
|
+
simple_param_types.each { |simple_param_type|
|
107
|
+
primitive_type = resolve_typedef(simple_param_type, typedef_hash)
|
108
|
+
|
109
|
+
param_type.sub!(/#{simple_param_type}/, primitive_type)
|
110
|
+
}
|
111
|
+
|
112
|
+
param_type
|
113
|
+
}
|
114
|
+
|
115
|
+
# Primitive Type -> Ruby FFI Type
|
116
|
+
param_type_list.each { |param_type|
|
117
|
+
mutable_type = param_type.gsub(/const/, '').strip
|
118
|
+
|
119
|
+
if (get_type(mutable_type, FFI_TYPES)) then
|
120
|
+
param_type = get_type(mutable_type, FFI_TYPES)
|
121
|
+
else
|
122
|
+
param_type = match_type(param_type, FFI_PTR_TYPES)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Write Out FFI Type
|
126
|
+
buffer << ':' + param_type + ', '
|
127
|
+
}
|
128
|
+
if (buffer[-1] == '[') then # No Parameters
|
129
|
+
buffer << '],'
|
130
|
+
else # At Least One Parameter
|
131
|
+
buffer[-2..-1] = '],'
|
132
|
+
end
|
133
|
+
|
134
|
+
# Get Return Type
|
135
|
+
# We Do Not Care About const If We Can Match Our Return Type With A
|
136
|
+
# Primitive Type. Otherwise, We Might Be Able To Match Against A
|
137
|
+
# const char* Or const unsigned char* In Which Case We Prefer To Use A
|
138
|
+
# string Type.
|
139
|
+
return_type = signature[/(.*?) gl[^ ]+\(/, 1]
|
140
|
+
mutable_return_type = return_type.gsub(/ *const */, '')
|
141
|
+
|
142
|
+
mutable_return_type.gsub(/\*/, ' ').split(' ').each { |type|
|
143
|
+
resolved_type = resolve_typedef(type, typedef_hash)
|
144
|
+
|
145
|
+
mutable_return_type.sub!(/#{type}/, resolved_type)
|
146
|
+
return_type.sub!(/#{type}/, resolved_type)
|
147
|
+
}
|
148
|
+
|
149
|
+
if (get_type(mutable_return_type, FFI_TYPES)) then
|
150
|
+
return_type = get_type(mutable_return_type, FFI_TYPES)
|
151
|
+
elsif (match_type(return_type, FFI_PTR_TYPES))
|
152
|
+
return_type = match_type(return_type, FFI_PTR_TYPES)
|
153
|
+
end
|
154
|
+
|
155
|
+
buffer << ' :' + return_type + "\n"
|
156
|
+
}
|
157
|
+
ffi_template.sub!(/FUNCTIONS_GO_HERE/, buffer)
|
158
|
+
|
159
|
+
# Append OpenGL Enumerations (Macros)
|
160
|
+
buffer = ''
|
161
|
+
macro_tuples.each { |name,val|
|
162
|
+
buffer << ' ' * SOURCE_INDENTS + "#{name} = #{val}\n"
|
163
|
+
}
|
164
|
+
ffi_template.sub!(/CONSTANTS_GO_HERE/, buffer)
|
165
|
+
|
166
|
+
write_file(ARGV[2], ffi_template)
|
167
|
+
/---------------------------------END SCRIPT----------------------------------/
|
data/bin/gl_code_gen.rb
ADDED
@@ -0,0 +1,459 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'openssl'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
# Decorate String Class With Convenience Methods For Parsing
|
7
|
+
class String
|
8
|
+
def to_bool
|
9
|
+
return true if self == true || self =~ (/^(?:true|t|yes|y|1)$/i)
|
10
|
+
return false if self == false || self.blank? || self =~ (/^(?:false|f|no|n|0)$/i)
|
11
|
+
raise ArgumentError.new("Invalid Boolean: \"#{self}\"")
|
12
|
+
end
|
13
|
+
def to_bool?
|
14
|
+
begin
|
15
|
+
self.to_bool
|
16
|
+
rescue
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
return true
|
20
|
+
end
|
21
|
+
def is_i?
|
22
|
+
!!(self =~ /\A[-+]?[0-9]+\Z/)
|
23
|
+
end
|
24
|
+
def replace_tags(sub)
|
25
|
+
self.gsub(/\<.*?\>/, sub)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns The Page Source Of The url And Aborts If Anything Other Than A 200
|
30
|
+
# Is Received.
|
31
|
+
def get_page_source(url)
|
32
|
+
uri = URI.parse url
|
33
|
+
|
34
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
35
|
+
if (uri.kind_of? URI::HTTPS) then
|
36
|
+
http.use_ssl = true
|
37
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
38
|
+
end
|
39
|
+
|
40
|
+
response = http.get(uri.path)
|
41
|
+
if (response.code != '200') then
|
42
|
+
abort("\nWas not able to pull xml file from #{uri}" +
|
43
|
+
"Response Code: #{response.code}")
|
44
|
+
end
|
45
|
+
|
46
|
+
return response.body
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns An API List In The Form Of [[API, Version], ...]
|
50
|
+
def get_api_list(root)
|
51
|
+
api_list = []
|
52
|
+
|
53
|
+
root.each_element('feature') { |element|
|
54
|
+
api = element.attributes['api']
|
55
|
+
version = element.attributes['number']
|
56
|
+
|
57
|
+
api_list.push [api, version]
|
58
|
+
}
|
59
|
+
|
60
|
+
return api_list
|
61
|
+
end
|
62
|
+
|
63
|
+
# Builds An ASCII Grid View From The Tuple List Supplied. The Cell Sizes Will
|
64
|
+
# Dynamically Scale Based On The Biggest Cell So That Columns Are Aligned.
|
65
|
+
def get_grid_view(console_width, tuple_list)
|
66
|
+
formatting_chars = 6
|
67
|
+
|
68
|
+
# Find Size Of Longest String Element (With Formatting)
|
69
|
+
max_length = tuple_list.sort { |a,b|
|
70
|
+
string_one = a[0] + a[1]
|
71
|
+
string_two = b[0] + b[1]
|
72
|
+
|
73
|
+
string_one.size <=> string_two.size
|
74
|
+
}.last.join('').size + tuple_list.size.to_s.size + formatting_chars
|
75
|
+
|
76
|
+
options_per_row = console_width / max_length
|
77
|
+
|
78
|
+
# Build 1-D Array Of Concatenated Strings
|
79
|
+
curr_iteraion = -1
|
80
|
+
concat_list = tuple_list.collect { |a,b|
|
81
|
+
curr_iteraion += 1
|
82
|
+
' ' + curr_iteraion.to_s + ': ' + a + ' ' + b + ' '
|
83
|
+
}
|
84
|
+
formatting_chars -= 5
|
85
|
+
|
86
|
+
# Expand Concatenated Strings Into Result
|
87
|
+
result = '-' * console_width
|
88
|
+
curr_column = 1
|
89
|
+
concat_list.each { |concat_item|
|
90
|
+
num_padding = max_length - concat_item.size - formatting_chars
|
91
|
+
|
92
|
+
result << concat_item << ' ' * num_padding
|
93
|
+
|
94
|
+
if (curr_column == options_per_row) then
|
95
|
+
result << "\n" + ('-' * console_width)
|
96
|
+
curr_column = 1
|
97
|
+
else
|
98
|
+
result << '|'
|
99
|
+
curr_column += 1
|
100
|
+
end
|
101
|
+
}
|
102
|
+
|
103
|
+
result << "\n" + ('-' * console_width) if result[-1] != '-'
|
104
|
+
|
105
|
+
return result
|
106
|
+
end
|
107
|
+
|
108
|
+
# Pads The String With pad_char So That It Fills columns Completely
|
109
|
+
def get_pretty(columns, pad_char, string)
|
110
|
+
padding = pad_char * ((columns - string.size) / 2)
|
111
|
+
|
112
|
+
result = (padding + string + padding)
|
113
|
+
result << pad_char if result.size < columns
|
114
|
+
|
115
|
+
return result
|
116
|
+
end
|
117
|
+
|
118
|
+
# Writes contents Out To path/name Using mode
|
119
|
+
def write_file(path, name, contents, mode = 'w+')
|
120
|
+
FileUtils.mkdir_p(path)
|
121
|
+
|
122
|
+
File.open(path + '/' + name, mode) { |file|
|
123
|
+
file << contents
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
# Verify The Supplied api And version Numbers Against The api_list
|
128
|
+
def valid_api_values?(api_list, api, version)
|
129
|
+
api_list.find_index { |api_tuple|
|
130
|
+
api_tuple[0].casecmp(api) && api_tuple[1].to_f == version
|
131
|
+
} != nil
|
132
|
+
end
|
133
|
+
|
134
|
+
# Verify The Supplied "api_index" To Make Sure It Is Valid
|
135
|
+
def valid_api_index?(api_list, api_index)
|
136
|
+
api_index.is_i? && (0...api_list.size).include?(api_index.to_i)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Prompt User With An api_list In A Grid View For An api_index Selection
|
140
|
+
def prompt_user(api_list)
|
141
|
+
# Prompt User
|
142
|
+
print get_pretty(COLUMN_WIDTH, ' ', GRID_TITLE)
|
143
|
+
print get_grid_view(COLUMN_WIDTH, api_list)
|
144
|
+
puts "Note: OpenGL 3.2 introduced the notion of a 'core' profile which
|
145
|
+
essentially means that it is not completely backwards compatible with
|
146
|
+
earlier versions. To retain this backwards compatibility (not recommended)
|
147
|
+
append a 'C' to the index number of the API you want.".gsub(/\s/, ' ')
|
148
|
+
|
149
|
+
# User Input
|
150
|
+
print "\nEnter The Index Number Next To The API You Want: "
|
151
|
+
api_index = gets.chomp
|
152
|
+
compatibility_mode = false
|
153
|
+
|
154
|
+
if (api_index[-1].upcase == 'C') then
|
155
|
+
compatibility_mode = true
|
156
|
+
api_index = api_index[0..-2]
|
157
|
+
end
|
158
|
+
|
159
|
+
while (!valid_api_index?(api_list, api_index)) do
|
160
|
+
print "Please Enter A Valid Input: "
|
161
|
+
api_index = gets.chomp
|
162
|
+
|
163
|
+
if (api_index[-1].upcase == 'C') then
|
164
|
+
compatibility_mode = true
|
165
|
+
api_index = api_index[0..-2]
|
166
|
+
else
|
167
|
+
compatibility_mode = false
|
168
|
+
end
|
169
|
+
end
|
170
|
+
api_index = api_index.to_i
|
171
|
+
|
172
|
+
return [api_list[api_index][0], api_list[api_index][1].to_f,
|
173
|
+
compatibility_mode]
|
174
|
+
end
|
175
|
+
|
176
|
+
/--------------------------------START SCRIPT---------------------------------/
|
177
|
+
|
178
|
+
# ARGV: (GL_API GL_VERSION) NAME FOLDER
|
179
|
+
# All Optional, If GL_API Is Specified Then GL_VERSION Must Be As Well
|
180
|
+
|
181
|
+
# Constants
|
182
|
+
COLUMN_WIDTH = 80
|
183
|
+
GRID_TITLE = "OpenGL API Versions"
|
184
|
+
GL_URL = 'https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api'
|
185
|
+
GL_PATH = '/gl.xml'
|
186
|
+
EGL_URL = 'http://www.khronos.org/registry/egl/api'
|
187
|
+
EGL_KHR_PATH = '/KHR/khrplatform.h'
|
188
|
+
SOURCE_INDENTS = 2
|
189
|
+
|
190
|
+
# Execution Variables
|
191
|
+
gl_header = 'OpenGL.h'
|
192
|
+
gl_source = 'OpenGL.c'
|
193
|
+
directory = './'
|
194
|
+
api, version, compatibility_mode = nil, nil, false
|
195
|
+
|
196
|
+
# Check Command Line Arguments
|
197
|
+
if (ARGV[0]) then
|
198
|
+
api = ARGV[0]
|
199
|
+
version = ARGV[1].to_f
|
200
|
+
compatibility_mode = ARGV[1][-1].casecmp('C') == 0
|
201
|
+
end
|
202
|
+
if (ARGV[2]) then
|
203
|
+
gl_header = ARGV[2] + '.h'
|
204
|
+
gl_source = ARGV[2] + '.c'
|
205
|
+
end
|
206
|
+
if (ARGV[3]) then
|
207
|
+
directory = ARGV[3]
|
208
|
+
end
|
209
|
+
|
210
|
+
print "\n" + get_pretty(COLUMN_WIDTH, '-', "Pulling Data From Web")
|
211
|
+
puts "- #{GL_URL + GL_PATH}"
|
212
|
+
source = get_page_source(GL_URL + GL_PATH)
|
213
|
+
puts get_pretty(COLUMN_WIDTH, '-', "Finished Pulling Data")
|
214
|
+
|
215
|
+
print get_pretty(COLUMN_WIDTH, '-', "Building XML Structure")
|
216
|
+
puts "Bytes: #{source.size}"
|
217
|
+
root = REXML::Document.new(source).root
|
218
|
+
puts get_pretty(COLUMN_WIDTH, '-', "Finished Building XML Structure")
|
219
|
+
|
220
|
+
# Extract API And Version Information
|
221
|
+
api_list = get_api_list(root) # [[API, Version], ...]
|
222
|
+
|
223
|
+
if (!api || !version) then # Prompt User
|
224
|
+
api_tuple = prompt_user(api_list)
|
225
|
+
|
226
|
+
api = api_tuple[0]
|
227
|
+
version = api_tuple[1]
|
228
|
+
compatibility_mode = api_tuple[3]
|
229
|
+
else # Verify User Parameters
|
230
|
+
if (!valid_api_values?(api_list, api, version)) then
|
231
|
+
abort("Invalid API Or Version Parameters Passed To Script")
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
puts get_pretty(COLUMN_WIDTH, '-', "API/Version/Profile - #{api}/#{version}/" +
|
236
|
+
(compatibility_mode ? "Compatibility" : "Core"))
|
237
|
+
|
238
|
+
# Parse All Enumerations And Commands
|
239
|
+
all_enums, all_commands = {}, {}
|
240
|
+
print get_pretty(COLUMN_WIDTH, '-', "Crawling XML Structure")
|
241
|
+
|
242
|
+
puts "Parsing Enumerations..."
|
243
|
+
root.each_element('enums/enum') { |element|
|
244
|
+
all_enums[element.attributes['name']] = element.attributes['value']
|
245
|
+
}
|
246
|
+
puts "Parsed All Enumerations\n\n"
|
247
|
+
|
248
|
+
puts "Parsing Commands..."
|
249
|
+
root.each_element('commands/command') { |element|
|
250
|
+
comm_name = element.elements['proto/name'].text
|
251
|
+
|
252
|
+
command = element.elements['proto'].to_s.replace_tags(' ').squeeze(' ').strip
|
253
|
+
command << '('
|
254
|
+
|
255
|
+
element.each_element('param') { |innerElement|
|
256
|
+
command << innerElement.to_s.replace_tags(' ').squeeze(' ').strip + ', '
|
257
|
+
}
|
258
|
+
command[-2..-1] = '' if command.end_with? ', '
|
259
|
+
|
260
|
+
all_commands[comm_name] = command + ');'
|
261
|
+
}
|
262
|
+
puts "Parsed All Commands"
|
263
|
+
|
264
|
+
puts get_pretty(COLUMN_WIDTH, '-', "Finished Crawling XML Structure")
|
265
|
+
|
266
|
+
#-----------------------------Build Header File-------------------------------
|
267
|
+
used_enums, used_commands = {}, {}
|
268
|
+
|
269
|
+
header_template = "#ifndef #{gl_header.upcase.sub(/\./, '_')}
|
270
|
+
#define #{gl_header.upcase.sub(/\./, '_')}
|
271
|
+
|
272
|
+
#ifdef __cplusplus
|
273
|
+
extern \"C\" {
|
274
|
+
#endif
|
275
|
+
|
276
|
+
#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
|
277
|
+
#ifndef WIN32_LEAN_AND_MEAN
|
278
|
+
#define WIN32_LEAN_AND_MEAN 1
|
279
|
+
#endif
|
280
|
+
#include <windows.h>
|
281
|
+
#undef near // Used As An Identifier In OpenGL
|
282
|
+
#undef far // Used As An Identifier In OpenGL
|
283
|
+
#endif
|
284
|
+
|
285
|
+
#ifndef APIENTRY
|
286
|
+
#define APIENTRY
|
287
|
+
#endif
|
288
|
+
|
289
|
+
COMMENTS_GO_HERE
|
290
|
+
|
291
|
+
TYPEDEFS_GO_HERE
|
292
|
+
|
293
|
+
DEFINES_GO_HERE
|
294
|
+
|
295
|
+
FUNCTIONS_GO_HERE
|
296
|
+
|
297
|
+
#ifdef __cplusplus
|
298
|
+
}
|
299
|
+
#endif
|
300
|
+
|
301
|
+
#endif"
|
302
|
+
|
303
|
+
print get_pretty(COLUMN_WIDTH, '-', "Generating File #{gl_header}")
|
304
|
+
|
305
|
+
puts "Appending Comments To #{gl_header}..."
|
306
|
+
buffer = "/*\n"
|
307
|
+
root.elements['comment'].text.each_line { |line|
|
308
|
+
buffer << '** ' << line
|
309
|
+
}
|
310
|
+
buffer << "\n*/"
|
311
|
+
header_template.sub!(/COMMENTS_GO_HERE/, buffer)
|
312
|
+
puts "Comments Have Been Appended\n\n"
|
313
|
+
|
314
|
+
# Pull Out All Possible Include Dependencies
|
315
|
+
puts "Appending Type Definitions To #{gl_header}..."
|
316
|
+
possible_includes = {}
|
317
|
+
root.each_element('types/type[@name]') { |element|
|
318
|
+
possible_includes[element.attributes['name']] = element.text
|
319
|
+
}
|
320
|
+
|
321
|
+
# Pull Out All Possible Types And Check If They Have Include Dependencies
|
322
|
+
actual_includes, buffer = {}, ''
|
323
|
+
attrib_filter = api == 'gl' ? 'not(@api)' : "@api='#{api}'"
|
324
|
+
root.each_element("types/type[#{attrib_filter} and not(@name)]") { |element|
|
325
|
+
content = element.to_s.replace_tags(' ').squeeze(' ').strip
|
326
|
+
content.gsub!(/ +;/, ';')
|
327
|
+
buffer << content << "\n"
|
328
|
+
|
329
|
+
requires = element.attributes['requires']
|
330
|
+
if (requires && !actual_includes[requires]) then
|
331
|
+
actual_includes[requires] = possible_includes[requires]
|
332
|
+
end
|
333
|
+
}
|
334
|
+
|
335
|
+
# Any Header Dependencies Found
|
336
|
+
actual_includes.each { |require,include|
|
337
|
+
if (require == 'khrplatform') then
|
338
|
+
print get_pretty(COLUMN_WIDTH, '-', "Pulling Data From Web")
|
339
|
+
puts "- #{EGL_URL + EGL_KHR_PATH}"
|
340
|
+
|
341
|
+
source = get_page_source(EGL_URL + EGL_KHR_PATH)
|
342
|
+
write_file("#{directory}/KHR", 'khrplatform.h', source)
|
343
|
+
|
344
|
+
print get_pretty(COLUMN_WIDTH, '-', "Finished Pulling Data")
|
345
|
+
end
|
346
|
+
|
347
|
+
# Ugh, Khronos Re-Used The Require Attribute For Non-Header Dependencies!!!
|
348
|
+
if (include) then
|
349
|
+
buffer.prepend(include + "\n")
|
350
|
+
end
|
351
|
+
}
|
352
|
+
header_template.sub!(/TYPEDEFS_GO_HERE/, buffer)
|
353
|
+
puts "Appended Type Definitions\n\n"
|
354
|
+
|
355
|
+
puts "Filtering Macros And Commands..."
|
356
|
+
root.each_element('feature') { |element|
|
357
|
+
if (element.attributes['api'] == api && element.attributes['number'].to_f <= version) then
|
358
|
+
element.each_element('require/enum') { |inner_element|
|
359
|
+
enum_name = inner_element.attributes['name']
|
360
|
+
used_enums[enum_name] = all_enums[enum_name]
|
361
|
+
}
|
362
|
+
|
363
|
+
element.each_element('require/command') { |inner_element|
|
364
|
+
comm_name = inner_element.attributes['name']
|
365
|
+
used_commands[comm_name] = all_commands[comm_name]
|
366
|
+
}
|
367
|
+
if (!compatibility_mode) then
|
368
|
+
element.each_element('remove/enum') { |inner_element|
|
369
|
+
enum_name = inner_element.attributes['name']
|
370
|
+
used_enums.delete(enum_name)
|
371
|
+
}
|
372
|
+
element.each_element('remove/command') { |inner_element|
|
373
|
+
comm_name = inner_element.attributes['name']
|
374
|
+
used_commands.delete(comm_name)
|
375
|
+
}
|
376
|
+
end
|
377
|
+
end
|
378
|
+
}
|
379
|
+
puts "Filtered Macros And Commands\n\n"
|
380
|
+
|
381
|
+
puts "Appending Macro To #{gl_header}..."
|
382
|
+
used_enums, buffer = used_enums.to_a.sort, ''
|
383
|
+
used_enums.each { |name,value|
|
384
|
+
buffer << '#define ' << name << ' ' << value << "\n"
|
385
|
+
}
|
386
|
+
header_template.sub!(/DEFINES_GO_HERE/, buffer)
|
387
|
+
puts "Appended Macros\n\n"
|
388
|
+
|
389
|
+
puts "Appending Commands To #{gl_header}..."
|
390
|
+
used_commands, buffer = used_commands.to_a.sort, ''
|
391
|
+
used_commands.each { |name,signature|
|
392
|
+
buffer << signature << "\n"
|
393
|
+
}
|
394
|
+
header_template.sub!(/FUNCTIONS_GO_HERE/, buffer)
|
395
|
+
puts "Appended Commands"
|
396
|
+
|
397
|
+
write_file(directory, gl_header, header_template)
|
398
|
+
puts get_pretty(COLUMN_WIDTH, '-', "Finished Generating #{gl_header}")
|
399
|
+
|
400
|
+
#-----------------------------Build Source File-------------------------------
|
401
|
+
print get_pretty(COLUMN_WIDTH, '-', "Generating File #{gl_source}")
|
402
|
+
|
403
|
+
buffer = "#include \"#{gl_header}\"\n#include \"SDL.h\"\n\n"
|
404
|
+
|
405
|
+
puts "Appending Command Definitions To #{gl_source}..."
|
406
|
+
used_commands.each { |name,signature|
|
407
|
+
buffer << signature[0..-2] + " {\n"
|
408
|
+
|
409
|
+
# Break Function Down
|
410
|
+
func_return_type = signature[/\A(.*?)gl/, 1].strip
|
411
|
+
typedef_name = 'GL_' + name[2..-1] + '_Func'
|
412
|
+
func_ptr_name = name + '_ptr'
|
413
|
+
param_type_list = []
|
414
|
+
unless (signature =~ /\(\);/) then # Has At Least One Parameter
|
415
|
+
param_type_list = signature[/\((.*?);/, 1].split(/ *\w+[,\)] */)
|
416
|
+
end
|
417
|
+
param_name_list = signature.scan(/(\w+)[,\)]/).flatten
|
418
|
+
|
419
|
+
# Append Typedef
|
420
|
+
buffer << ' ' * SOURCE_INDENTS + 'typedef ' + func_return_type +
|
421
|
+
" (APIENTRY * #{typedef_name})("
|
422
|
+
param_type_list.each { |param_type|
|
423
|
+
buffer << param_type + ', '
|
424
|
+
}
|
425
|
+
if (buffer[-2..-1] == ', ') then
|
426
|
+
buffer[-2..-1] = ');'
|
427
|
+
else
|
428
|
+
buffer << ');'
|
429
|
+
end
|
430
|
+
buffer << "\n"
|
431
|
+
|
432
|
+
# Append Pointer Initialization
|
433
|
+
buffer << ' ' * SOURCE_INDENTS + 'static ' + typedef_name + ' ' +
|
434
|
+
func_ptr_name + " = NULL;\n\n"
|
435
|
+
|
436
|
+
buffer << ' ' * SOURCE_INDENTS + "if (#{func_ptr_name} == NULL) {\n" +
|
437
|
+
' ' * (SOURCE_INDENTS * 2) + func_ptr_name +
|
438
|
+
" = (#{typedef_name})SDL_GL_GetProcAddress(__func__);\n" +
|
439
|
+
' ' * SOURCE_INDENTS + "}\n\n"
|
440
|
+
|
441
|
+
# Append Function Pointer Call
|
442
|
+
buffer << ' ' * SOURCE_INDENTS + func_ptr_name + '('
|
443
|
+
param_name_list.each { |param_name|
|
444
|
+
buffer << param_name + ', '
|
445
|
+
}
|
446
|
+
if (buffer[-2..-1] == ', ') then
|
447
|
+
buffer[-2..-1] = ');'
|
448
|
+
else
|
449
|
+
buffer << ');'
|
450
|
+
end
|
451
|
+
|
452
|
+
buffer << "\n}\n\n"
|
453
|
+
}
|
454
|
+
write_file(directory, gl_source, buffer)
|
455
|
+
puts "Finished Appending Command Definitions"
|
456
|
+
|
457
|
+
print get_pretty(COLUMN_WIDTH, '-', "Finished Generating #{gl_source}")
|
458
|
+
|
459
|
+
/---------------------------------END SCRIPT----------------------------------/
|