rubygl 0.1.0
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 +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
@@ -0,0 +1,122 @@
|
|
1
|
+
require_relative './native/opengl'
|
2
|
+
|
3
|
+
module RubyGL
|
4
|
+
|
5
|
+
class VertexArray
|
6
|
+
# Allocates a GPU buffer for vertex_array and transfers the data as a vertex
|
7
|
+
# attribute array. This allows you to access the given data from within any
|
8
|
+
# shader as long as it is bound beforehand.
|
9
|
+
def initialize(vertex_array)
|
10
|
+
buff_ptr = FFI::MemoryPointer.new(:uint)
|
11
|
+
Native.glGenBuffers(1, buff_ptr)
|
12
|
+
|
13
|
+
@buffer_id = buff_ptr.get_int(0)
|
14
|
+
@buffer_elements = vertex_array.size
|
15
|
+
@buffer_valid = true
|
16
|
+
|
17
|
+
if @buffer_id <= 0 then
|
18
|
+
raise "Could Not Allocate A GPU Buffer For The VertexArray Object"
|
19
|
+
end
|
20
|
+
|
21
|
+
Native.glBindBuffer(Native::GL_ARRAY_BUFFER, @buffer_id)
|
22
|
+
|
23
|
+
vertex_data = FFI::MemoryPointer.new(:float, vertex_array.size)
|
24
|
+
vertex_data.write_array_of_float(vertex_array)
|
25
|
+
|
26
|
+
Native.glBufferData(Native::GL_ARRAY_BUFFER, vertex_data.size, vertex_data, Native::GL_STATIC_DRAW)
|
27
|
+
|
28
|
+
Native.glBindBuffer(Native::GL_ARRAY_BUFFER, 0)
|
29
|
+
end
|
30
|
+
|
31
|
+
def draw(components)
|
32
|
+
raise "Call To VertexArray#draw On Invalid Object" unless @buffer_valid
|
33
|
+
|
34
|
+
Native.glDrawArrays(Native::GL_TRIANGLES, 0, @buffer_elements / components)
|
35
|
+
end
|
36
|
+
|
37
|
+
def draw_instanced(components, iterations)
|
38
|
+
raise "Call To VertexArray#draw_instanced On Invalid Object" unless @buffer_valid
|
39
|
+
|
40
|
+
Native.glDrawArraysInstanced(Native::GL_TRIANGLES, 0, @buffer_elements / components, iterations)
|
41
|
+
end
|
42
|
+
|
43
|
+
def vertex_attrib_div(location, instances_per_change)
|
44
|
+
raise "Call To VertexArray#vertex_attrib_div On Invalid Object" unless @buffer_valid
|
45
|
+
|
46
|
+
Native.glVertexAttribDivisor(location, instances_per_change)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Binds the VertexArray to the attribute at location within a shader.
|
50
|
+
def vertex_attrib_ptr(location, components)
|
51
|
+
raise "Call To VertexArray#vertex_attrib_ptr On Invalid Object" unless @buffer_valid
|
52
|
+
|
53
|
+
# TODO: Move This To Constructor In The Future
|
54
|
+
Native.glEnableVertexAttribArray(location)
|
55
|
+
|
56
|
+
Native.glBindBuffer(Native::GL_ARRAY_BUFFER, @buffer_id)
|
57
|
+
|
58
|
+
Native.glVertexAttribPointer(location, components, Native::GL_FLOAT, Native::GL_FALSE, 0, FFI::MemoryPointer::NULL)
|
59
|
+
|
60
|
+
Native.glBindBuffer(Native::GL_ARRAY_BUFFER, 0)
|
61
|
+
end
|
62
|
+
|
63
|
+
# This frees the currently allocated GPU buffer for this VertexArray and
|
64
|
+
# invalidates this VertexArray object. Any calls to this object after calling
|
65
|
+
# this method will throw a runtime error.
|
66
|
+
def release()
|
67
|
+
buff_ptr = FFI::MemoryPointer.new(:uint)
|
68
|
+
buff_ptr.write_uint(@buffer_id)
|
69
|
+
|
70
|
+
Native.glDeleteBuffers(1, buff_ptr)
|
71
|
+
|
72
|
+
@buffer_valid = false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class IndexArray
|
77
|
+
def initialize(index_array)
|
78
|
+
buff_ptr = FFI::MemoryPointer.new(:pointer)
|
79
|
+
Native.glGenBuffers(1, buff_ptr)
|
80
|
+
|
81
|
+
@buffer_id = buff_ptr.get_int(0)
|
82
|
+
@buffer_elements = index_array.size
|
83
|
+
@buffer_valid = true
|
84
|
+
|
85
|
+
if @buffer_id <= 0 then
|
86
|
+
raise "Could Not Allocate A GPU Buffer For The IndexArray Object"
|
87
|
+
end
|
88
|
+
|
89
|
+
Native.glBindBuffer(Native::GL_ELEMENT_ARRAY_BUFFER, @buffer_id)
|
90
|
+
|
91
|
+
index_data = FFI::MemoryPointer.new(:uint, index_array.size)
|
92
|
+
index_data.write_array_of_uint(index_array)
|
93
|
+
|
94
|
+
Native.glBufferData(Native::GL_ELEMENT_ARRAY_BUFFER, index_data.size, index_data, Native::GL_STATIC_DRAW)
|
95
|
+
|
96
|
+
Native.glBindBuffer(Native::GL_ELEMENT_ARRAY_BUFFER, 0)
|
97
|
+
end
|
98
|
+
|
99
|
+
def draw(components)
|
100
|
+
raise "Call To IndexArray#draw On Frozen Object" unless @buffer_valid
|
101
|
+
|
102
|
+
Native.glBindBuffer(Native::GL_ELEMENT_ARRAY_BUFFER, @buffer_id)
|
103
|
+
|
104
|
+
Native.glDrawElements(Native::GL_TRIANGLES, @buffer_elements, Native::GL_UNSIGNED_INT, FFI::MemoryPointer::NULL)
|
105
|
+
|
106
|
+
Native.glBindBuffer(Native::GL_ELEMENT_ARRAY_BUFFER, 0)
|
107
|
+
end
|
108
|
+
|
109
|
+
# This frees the currently allocated GPU buffer for this IndexArray and
|
110
|
+
# invalidates this IndexArray object. Any calls to this object after calling
|
111
|
+
# this method will throw a runtime error.
|
112
|
+
def release()
|
113
|
+
buff_ptr = FFI::MemoryPointer.new(:uint)
|
114
|
+
buff_ptr.put_int(@buffer_id)
|
115
|
+
|
116
|
+
Native.glDeleteBuffers(1, buff_ptr)
|
117
|
+
|
118
|
+
@buffer_valid = false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
data/lib/RubyGL/setup.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative './native/glcontext'
|
2
|
+
require_relative './native/window'
|
3
|
+
|
4
|
+
module RubyGL
|
5
|
+
|
6
|
+
class DefaultSetup
|
7
|
+
# The args hash accepts position and size values to be applied to the window.
|
8
|
+
# :x, :y, :width, :height
|
9
|
+
def initialize(args = {})
|
10
|
+
RubyGL::Native.initWindow
|
11
|
+
RubyGL::Native.initInput
|
12
|
+
RubyGL::Native.loadLibrary(FFI::Pointer::NULL)
|
13
|
+
|
14
|
+
RubyGL::Native.setAttribute(:context_major_version, 4)
|
15
|
+
RubyGL::Native.setAttribute(:context_minor_version, 2)
|
16
|
+
RubyGL::Native.setAttribute(:context_profile_mask, RubyGL::Native.CONTEXT_PROFILE_COMPATIBILITY)
|
17
|
+
|
18
|
+
RubyGL::Native.setAttribute(:depth_size, 24)
|
19
|
+
RubyGL::Native.setAttribute(:doublebuffer, 1)
|
20
|
+
|
21
|
+
@window = RubyGL::Native.createWindow("RubyGL Window",
|
22
|
+
args[:x] || 50, args[:y] || 50,
|
23
|
+
args[:width] || 500, args[:height] || 500,
|
24
|
+
RubyGL::Native.OPENGL)
|
25
|
+
@context = RubyGL::Native.createContext(@window)
|
26
|
+
|
27
|
+
RubyGL::Native.makeCurrent(@window, @context)
|
28
|
+
RubyGL::Native.setSwapInterval(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
def show_dialog(title, message)
|
32
|
+
RubyGL::Native.showSimpleMessageBox(0, title, message, @window)
|
33
|
+
end
|
34
|
+
|
35
|
+
def end_frame()
|
36
|
+
RubyGL::Native.swapWindow(@window)
|
37
|
+
|
38
|
+
RubyGL::Native.pumpEvents()
|
39
|
+
end
|
40
|
+
|
41
|
+
def teardown()
|
42
|
+
RubyGL::Native.deleteContext(@context)
|
43
|
+
RubyGL::Native.destroyWindow(@window)
|
44
|
+
|
45
|
+
RubyGL::Native.unloadLibrary()
|
46
|
+
RubyGL::Native.quitWindow()
|
47
|
+
RubyGL::Native.quitInput()
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require_relative './native/opengl'
|
2
|
+
|
3
|
+
module RubyGL
|
4
|
+
|
5
|
+
class Shader
|
6
|
+
def initialize(vert_src, frag_src)
|
7
|
+
@v_shader_id = Native.glCreateShader(Native::GL_VERTEX_SHADER)
|
8
|
+
@f_shader_id = Native.glCreateShader(Native::GL_FRAGMENT_SHADER)
|
9
|
+
@attrib_locs, @uniform_locs = {}, {}
|
10
|
+
|
11
|
+
# Pointer Used To Point To The String Of Our Source Code
|
12
|
+
shader_src_ptr = FFI::MemoryPointer.new(:pointer)
|
13
|
+
shader_src_len = FFI::MemoryPointer.new(:int)
|
14
|
+
|
15
|
+
v_shader_ptr = FFI::MemoryPointer.from_string(vert_src)
|
16
|
+
f_shader_ptr = FFI::MemoryPointer.from_string(frag_src)
|
17
|
+
|
18
|
+
shader_src_ptr.write_pointer(v_shader_ptr.address)
|
19
|
+
shader_src_len.write_int(vert_src.size)
|
20
|
+
Native.glShaderSource(@v_shader_id, 1, shader_src_ptr, shader_src_len)
|
21
|
+
|
22
|
+
shader_src_ptr.write_pointer(f_shader_ptr.address)
|
23
|
+
shader_src_len.write_int(frag_src.size)
|
24
|
+
Native.glShaderSource(@f_shader_id, 1, shader_src_ptr, shader_src_len)
|
25
|
+
|
26
|
+
Native.glCompileShader(@v_shader_id)
|
27
|
+
Native.glCompileShader(@f_shader_id)
|
28
|
+
|
29
|
+
# Throw Exception With Compile Error If Compilation Failed
|
30
|
+
Shader.compile_status(@v_shader_id)
|
31
|
+
Shader.compile_status(@f_shader_id)
|
32
|
+
|
33
|
+
@program_id = Native.glCreateProgram()
|
34
|
+
|
35
|
+
Native.glAttachShader(@program_id, @v_shader_id)
|
36
|
+
Native.glAttachShader(@program_id, @f_shader_id)
|
37
|
+
|
38
|
+
Native.glLinkProgram(@program_id)
|
39
|
+
|
40
|
+
Shader.link_status(@program_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def use_program()
|
44
|
+
Native.glUseProgram(@program_id)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Splat Operator Aggregates Incoming Parameters And Expands Outgoing Parameters
|
48
|
+
# If method_sym Does Not Accept A Pointer To The Data, Pass nil For data And
|
49
|
+
# Pass In The Normal Parameters Required For The Specific Method.
|
50
|
+
def send_uniform(method_sym, var_name, data = nil, *args)
|
51
|
+
loc = @uniform_locs[var_name] ||= uniform_location(var_name)
|
52
|
+
|
53
|
+
if data != nil then
|
54
|
+
data_ptr = FFI::MemoryPointer.new(:float, data.size)
|
55
|
+
data_ptr.write_array_of_float(data)
|
56
|
+
|
57
|
+
RubyGL::Native.send(method_sym, loc, *args, data_ptr)
|
58
|
+
else
|
59
|
+
RubyGL::Native.send(method_sym, loc, *args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def attrib_location(var_name)
|
64
|
+
Native.glGetAttribLocation(@program_id, var_name)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def uniform_location(var_name)
|
69
|
+
Native.glGetUniformLocation(@program_id, var_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.compile_status(shader_id)
|
73
|
+
result = FFI::MemoryPointer.new(:int)
|
74
|
+
Native.glGetShaderiv(shader_id, Native::GL_COMPILE_STATUS, result)
|
75
|
+
|
76
|
+
if (result.get_int(0) != Native::GL_TRUE) then
|
77
|
+
Native.glGetShaderiv(shader_id, Native::GL_INFO_LOG_LENGTH, result)
|
78
|
+
|
79
|
+
compile_log = FFI::MemoryPointer.new(:char, result.get_int(0))
|
80
|
+
|
81
|
+
Native.glGetShaderInfoLog(shader_id, compile_log.total, FFI::MemoryPointer::NULL, compile_log)
|
82
|
+
|
83
|
+
raise compile_log.read_string_to_null()
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.link_status(program_id)
|
88
|
+
result = FFI::MemoryPointer.new(:int)
|
89
|
+
Native.glGetProgramiv(program_id, Native::GL_LINK_STATUS, result)
|
90
|
+
|
91
|
+
if (result.get_int(0) != Native::GL_TRUE) then
|
92
|
+
Native.glGetProgramiv(program_id, Native::GL_INFO_LOG_LENGTH, result)
|
93
|
+
|
94
|
+
link_log = FFI::MemoryPointer.new(:char, result.get_int(0))
|
95
|
+
|
96
|
+
Native.glGetShaderInfoLog(program_id, link_log.total, FFI::MemoryPointer::NULL, link_log)
|
97
|
+
|
98
|
+
raise link_log.read_string_to_null()
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class ShaderGenerator
|
104
|
+
def self.faceted_shader()
|
105
|
+
Shader.new('''
|
106
|
+
#version 130
|
107
|
+
in vec3 position;
|
108
|
+
out vec3 vPosition;
|
109
|
+
uniform mat4 modelview;
|
110
|
+
uniform mat4 perspective;
|
111
|
+
|
112
|
+
void main() {
|
113
|
+
vec4 hPosition = modelview * vec4(position, 1);
|
114
|
+
vPosition = hPosition.xyz;
|
115
|
+
|
116
|
+
gl_Position = perspective * hPosition;
|
117
|
+
}
|
118
|
+
''','''
|
119
|
+
#version 130
|
120
|
+
in vec3 vPosition;
|
121
|
+
out vec4 fColor;
|
122
|
+
uniform vec4 color;
|
123
|
+
uniform vec3 light;
|
124
|
+
|
125
|
+
void main() {
|
126
|
+
vec3 dx = dFdy(vPosition);
|
127
|
+
vec3 dy = dFdx(vPosition);
|
128
|
+
vec3 triangle_norm = normalize(cross(dx, dy));
|
129
|
+
float factor = clamp(dot(triangle_norm, light), 0, 1);
|
130
|
+
|
131
|
+
fColor = vec4(color.xyz * factor, color.w);
|
132
|
+
}
|
133
|
+
''')
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.color_shader()
|
137
|
+
Shader.new('''
|
138
|
+
#version 130
|
139
|
+
in vec3 position;
|
140
|
+
uniform mat4 modelview;
|
141
|
+
uniform mat4 perspective;
|
142
|
+
|
143
|
+
void main() {
|
144
|
+
gl_Position = perspective * modelView * vec4(position, 1);
|
145
|
+
}
|
146
|
+
''','''
|
147
|
+
#version 130
|
148
|
+
out vec4 fColor;
|
149
|
+
uniform vec4 color;
|
150
|
+
|
151
|
+
void main() {
|
152
|
+
fColor = color;
|
153
|
+
}
|
154
|
+
''')
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.phong_shader()
|
158
|
+
Shader.new('''
|
159
|
+
#version 130
|
160
|
+
in vec3 position;
|
161
|
+
in vec3 normal;
|
162
|
+
out vec3 vPosition;
|
163
|
+
out vec3 vNormal;
|
164
|
+
uniform mat4 modelview;
|
165
|
+
uniform mat4 perspective;
|
166
|
+
|
167
|
+
void main() {
|
168
|
+
vec4 hPosition = modelview * vec4(position, 1);
|
169
|
+
vPosition = hPosition.xyz;
|
170
|
+
vNormal = (modelview * vec4(normal, 1)).xyz;
|
171
|
+
|
172
|
+
gl_Position = perspective * hPosition;
|
173
|
+
}
|
174
|
+
''','''
|
175
|
+
#version 130
|
176
|
+
in vec3 vPosition;
|
177
|
+
in vec3 vNormal;
|
178
|
+
out vec4 fColor;
|
179
|
+
uniform vec4 color;
|
180
|
+
uniform vec3 light;
|
181
|
+
|
182
|
+
float PhongIntensity(vec3 pos, vec3 norm) {
|
183
|
+
vec3 N = normalize(norm);
|
184
|
+
vec3 L = normalize(light - pos);
|
185
|
+
vec3 E = normalize(pos);
|
186
|
+
vec3 R = reflect(L, N);
|
187
|
+
|
188
|
+
float diffuse = abs(dot(N, L));
|
189
|
+
float specular = abs(dot(R, E));
|
190
|
+
|
191
|
+
return clamp(pow(specular, 10) + diffuse, 0, 1);
|
192
|
+
}
|
193
|
+
|
194
|
+
void main() {
|
195
|
+
float intensity = PhongIntensity(vPosition, vNormal);
|
196
|
+
|
197
|
+
fColor = vec4(intensity * color.xyz, color.w);
|
198
|
+
}
|
199
|
+
''')
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
data/lib/RubyGL/util.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
|
2
|
+
module RubyGL
|
3
|
+
|
4
|
+
class Util
|
5
|
+
# Returns the value of the array at the overflow wrapped index value.
|
6
|
+
def self.overflow_wrap(array, index)
|
7
|
+
array[index % array.size]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns [vertices, indices] where vertices is an array of unique vertices
|
11
|
+
# corresponding to the vertices in vertex_array and indices is the zero based
|
12
|
+
# index array that references the unqiue vertices stored in vertices.
|
13
|
+
def self.gen_index_arrays(vertex_array, components)
|
14
|
+
unique_points, point_indices, index_hash = [], [], {}
|
15
|
+
|
16
|
+
curr_index = 0
|
17
|
+
vertex_array.each_slice(components) { |point|
|
18
|
+
index = index_hash[point]
|
19
|
+
|
20
|
+
if index then
|
21
|
+
point_indices.push(index)
|
22
|
+
else
|
23
|
+
unique_points.push(point)
|
24
|
+
point_indices.push(curr_index)
|
25
|
+
|
26
|
+
index_hash[point] = curr_index
|
27
|
+
curr_index += 1
|
28
|
+
end
|
29
|
+
}
|
30
|
+
unique_points.flatten!
|
31
|
+
|
32
|
+
[unique_points, point_indices]
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Returns an array of vertex normals which is indexable by the indices array.
|
37
|
+
# The components parameter should be the number of components per vertex for
|
38
|
+
# each triangle.
|
39
|
+
#
|
40
|
+
# The vertices parameters must contain 3 component vertices or this function
|
41
|
+
# will not be able to compute appropriate normals.
|
42
|
+
def self.gen_vertex_normals(indices, vertices)
|
43
|
+
normals = Array.new(vertices.size, Vec3.new)
|
44
|
+
|
45
|
+
indices.each_slice(3) { |i1, i2, i3|
|
46
|
+
# 3 Components Per Vertex (Index References 3 Components)
|
47
|
+
adj_i1, adj_i2, adj_i3 = i1 * 3, i2 * 3, i3 * 3
|
48
|
+
|
49
|
+
# Construct Triangle Vertices
|
50
|
+
v1 = Vec3.new(vertices[adj_i1..adj_i1 + 2])
|
51
|
+
v2 = Vec3.new(vertices[adj_i2..adj_i2 + 2])
|
52
|
+
v3 = Vec3.new(vertices[adj_i3..adj_i3 + 2])
|
53
|
+
|
54
|
+
d1 = v2 - v1
|
55
|
+
d2 = v3 - v2
|
56
|
+
|
57
|
+
normal = d1.cross(d2)
|
58
|
+
normal.norm!
|
59
|
+
|
60
|
+
normals[i1] += normal
|
61
|
+
normals[i2] += normal
|
62
|
+
normals[i3] += normal
|
63
|
+
}
|
64
|
+
|
65
|
+
# Normalize All Vectors And Convert From Vec3 To Flat Array
|
66
|
+
normals.map! { |normal_vector|
|
67
|
+
normal_vector.norm!
|
68
|
+
|
69
|
+
normal_vector.to_a
|
70
|
+
}
|
71
|
+
normals.flatten!
|
72
|
+
|
73
|
+
normals
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|