rubygl 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +13 -5
- data/.travis/push-rdoc-to-gh-pages.sh +22 -0
- data/.travis.yml +18 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +12 -0
- data/LICENSE +21 -21
- data/README.md +40 -0
- data/Rakefile +98 -83
- data/bin/ffi_code_gen.rb +166 -166
- data/bin/gl_code_gen.rb +458 -458
- data/examples/faceted_example.rb +71 -64
- data/examples/instanced_example.rb +135 -127
- data/examples/phong_example.rb +80 -71
- data/ext/windows/RubyGL.so +0 -0
- data/lib/rubygl/event.rb +64 -0
- data/lib/{RubyGL → rubygl}/geometry.rb +216 -211
- data/lib/{RubyGL → rubygl}/math.rb +300 -300
- data/lib/{RubyGL → rubygl}/memory.rb +125 -121
- data/lib/rubygl/native/all_enums.rb +641 -0
- data/lib/{RubyGL/Native → rubygl/native}/glcontext.rb +23 -47
- data/lib/{RubyGL/Native → rubygl/native}/include/GLContext.h +36 -36
- data/lib/{RubyGL/Native → rubygl/native}/include/Input.h +15 -15
- data/lib/{RubyGL/Native → rubygl/native}/include/Window.h +27 -27
- data/lib/rubygl/native/input.rb +247 -0
- data/lib/{RubyGL/Native → rubygl/native}/opengl.rb +2808 -2032
- data/lib/{RubyGL/Native → rubygl/native}/src/GLContext.c +72 -72
- data/lib/{RubyGL/Native → rubygl/native}/src/Input.c +25 -25
- data/lib/{RubyGL/Native → rubygl/native}/src/Window.c +57 -57
- data/lib/{RubyGL/Native → rubygl/native}/window.rb +24 -24
- data/lib/{RubyGL → rubygl}/setup.rb +50 -50
- data/lib/{RubyGL → rubygl}/shader.rb +203 -203
- data/lib/{RubyGL → rubygl}/util.rb +77 -77
- data/lib/rubygl.rb +49 -48
- data/{RubyGL.gemspec → rubygl.gemspec} +20 -23
- data/test/test_util.rb +19 -0
- metadata +36 -33
- data/lib/RubyGL/Native/input.rb +0 -13
- data/lib/RubyGL/callback.rb +0 -10
data/bin/gl_code_gen.rb
CHANGED
@@ -1,459 +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 = '
|
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[
|
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
|
-
|
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 = 'https://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[2]
|
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
459
|
/---------------------------------END SCRIPT----------------------------------/
|