rbgl 0.1.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +13 -1
- data/lib/rbgl/engine/buffer.rb +102 -33
- data/lib/rbgl/engine/clip_space_clipper.rb +143 -0
- data/lib/rbgl/engine/context.rb +117 -64
- data/lib/rbgl/engine/framebuffer.rb +41 -32
- data/lib/rbgl/engine/pipeline.rb +38 -7
- data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +105 -0
- data/lib/rbgl/engine/rasterizer/line_renderer.rb +72 -0
- data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
- data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +87 -0
- data/lib/rbgl/engine/rasterizer.rb +73 -162
- data/lib/rbgl/engine/shader/base_shader.rb +55 -0
- data/lib/rbgl/engine/shader/builtins.rb +254 -0
- data/lib/rbgl/engine/shader/dynamic_data.rb +65 -0
- data/lib/rbgl/engine/shader.rb +5 -318
- data/lib/rbgl/engine/texture.rb +227 -38
- data/lib/rbgl/engine.rb +1 -0
- data/lib/rbgl/gui/backend.rb +12 -30
- data/lib/rbgl/gui/backend_factory.rb +85 -0
- data/lib/rbgl/gui/cocoa/backend.rb +15 -35
- data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
- data/lib/rbgl/gui/file_backend/frame_writer.rb +28 -0
- data/lib/rbgl/gui/file_backend/ppm_writer.rb +25 -0
- data/lib/rbgl/gui/file_backend.rb +25 -48
- data/lib/rbgl/gui/wayland/backend.rb +108 -26
- data/lib/rbgl/gui/wayland/codec.rb +54 -0
- data/lib/rbgl/gui/wayland/connection.rb +80 -240
- data/lib/rbgl/gui/wayland/event_dispatcher.rb +74 -0
- data/lib/rbgl/gui/wayland/global_binder.rb +44 -0
- data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +51 -0
- data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
- data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +146 -0
- data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
- data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +45 -0
- data/lib/rbgl/gui/wayland/protocol_objects.rb +7 -0
- data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
- data/lib/rbgl/gui/window/render_loop.rb +58 -0
- data/lib/rbgl/gui/window.rb +41 -82
- data/lib/rbgl/gui/x11/atom_cache.rb +26 -0
- data/lib/rbgl/gui/x11/backend.rb +14 -28
- data/lib/rbgl/gui/x11/connection.rb +104 -169
- data/lib/rbgl/gui/x11/event_parser.rb +60 -0
- data/lib/rbgl/gui/x11/request_encoder.rb +154 -0
- data/lib/rbgl/gui/x11/transport.rb +31 -0
- data/lib/rbgl/gui.rb +1 -0
- data/lib/rbgl/version.rb +1 -1
- metadata +31 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c17ca3e225ae4e0c8aff6550263d7c6fa8b741e5711bfe000b847e6630c62fed
|
|
4
|
+
data.tar.gz: 3a227d1b5bca1fd9f1e4ef8cfea35321cbf8f6ecf967a517086597f259574652
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9761c7b0a73c017941be0a4272c994151b2732faaa103b370e39d5d7255801ff0881ea55e50d3425c817e9e2becae43994a661dfb00fdf7414d156c2b1e0acae
|
|
7
|
+
data.tar.gz: 00fa09df593f73df4d2e3bb22e7c2c20f5e38b7a3fa04415e907de167a8a975950bd06febb298dedfa159b606f25efd88c6f50076e5b527a9c61040371c8c5b1
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.0.0 - 2026-04-05
|
|
6
|
+
|
|
7
|
+
- Removed the legacy `Window#on_key`, `Window#on_mouse`, and `Window#on_resize` aliases. Register handlers with `window.on(:key_press)`, `window.on(:mouse_move)`, `window.on(:resize)`, and the other event types directly.
|
|
8
|
+
- Removed the legacy `backend: :native` alias. Use `backend: :auto` for automatic native backend selection.
|
|
9
|
+
- Changed file backend binary PPM output to `format: :ppm, ppm_mode: :binary`. The old `format: :ppm_binary` option is no longer accepted.
|
|
10
|
+
- Added a runtime dependency on `rlsl`, so existing applications need it resolved during install and bundle steps.
|
|
11
|
+
- Added binary (`P6`) and text (`P3`) PPM texture loading via `RBGL::Engine::Texture.from_ppm`, and added ASCII or binary PPM output selection for the file backend.
|
|
12
|
+
- Added `RBGL::GUI::Window#dropped_frames` so applications can detect failed presents, including Wayland backpressure cases.
|
|
13
|
+
- Improved automatic native backend selection on Linux: RBGL now prefers Wayland when available, falls back to X11 when needed, and raises clearer `BackendUnavailable` errors when no display server can be used.
|
|
14
|
+
- Improved rendering correctness with view-frustum clipping for triangles, lines, and points, perspective-correct interpolation, point rasterization fixes, and pipeline render-state handling.
|
|
15
|
+
- Tightened validation across pipelines, shaders, vertex buffers, textures, sampler settings, mipmaps, and texture writes so invalid rendering inputs fail earlier with explicit errors.
|
|
16
|
+
|
|
5
17
|
## 0.1.0 - 2026-01-04
|
|
6
18
|
|
|
7
|
-
- Initial release.
|
|
19
|
+
- Initial release.
|
data/lib/rbgl/engine/buffer.rb
CHANGED
|
@@ -3,12 +3,95 @@
|
|
|
3
3
|
module RBGL
|
|
4
4
|
module Engine
|
|
5
5
|
class VertexAttribute
|
|
6
|
-
attr_reader :name, :size, :offset
|
|
6
|
+
attr_reader :name, :size, :offset, :kind
|
|
7
7
|
|
|
8
|
-
def initialize(name, size, offset = 0)
|
|
8
|
+
def initialize(name, size, offset = 0, kind: nil)
|
|
9
9
|
@name = name.to_sym
|
|
10
10
|
@size = size
|
|
11
11
|
@offset = offset
|
|
12
|
+
@kind = (kind || infer_kind).to_sym
|
|
13
|
+
validate_kind!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def serialize(value)
|
|
17
|
+
values = extract_values(value)
|
|
18
|
+
validate_value_size!(values)
|
|
19
|
+
values.first(@size).map { |component| Float(component) }
|
|
20
|
+
rescue ArgumentError, TypeError => e
|
|
21
|
+
raise ArgumentError, "Invalid value for attribute #{name}: #{e.message}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def deserialize(values)
|
|
25
|
+
case @kind
|
|
26
|
+
when :scalar
|
|
27
|
+
values[0]
|
|
28
|
+
when :vec2
|
|
29
|
+
Larb::Vec2.new(*values)
|
|
30
|
+
when :vec3
|
|
31
|
+
Larb::Vec3.new(*values)
|
|
32
|
+
when :vec4
|
|
33
|
+
Larb::Vec4.new(*values)
|
|
34
|
+
when :color
|
|
35
|
+
Larb::Color.new(*values)
|
|
36
|
+
when :array
|
|
37
|
+
values.dup
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def infer_kind
|
|
44
|
+
return :color if @name == :color && @size == 4
|
|
45
|
+
|
|
46
|
+
case @size
|
|
47
|
+
when 1 then :scalar
|
|
48
|
+
when 2 then :vec2
|
|
49
|
+
when 3 then :vec3
|
|
50
|
+
when 4 then :vec4
|
|
51
|
+
else :array
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate_kind!
|
|
56
|
+
expected_sizes = {
|
|
57
|
+
scalar: [1],
|
|
58
|
+
vec2: [2],
|
|
59
|
+
vec3: [3],
|
|
60
|
+
vec4: [4],
|
|
61
|
+
color: [4],
|
|
62
|
+
array: nil
|
|
63
|
+
}.fetch(@kind) do
|
|
64
|
+
raise ArgumentError, "Unsupported attribute kind: #{@kind}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
return if expected_sizes.nil? || expected_sizes.include?(@size)
|
|
68
|
+
|
|
69
|
+
raise ArgumentError, "Attribute #{name} kind #{@kind} requires size #{expected_sizes.join(' or ')}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def extract_values(value)
|
|
73
|
+
case value
|
|
74
|
+
when Larb::Vec2
|
|
75
|
+
[value.x, value.y]
|
|
76
|
+
when Larb::Vec3
|
|
77
|
+
[value.x, value.y, value.z]
|
|
78
|
+
when Larb::Vec4
|
|
79
|
+
[value.x, value.y, value.z, value.w]
|
|
80
|
+
when Larb::Color
|
|
81
|
+
value.to_a
|
|
82
|
+
when Array
|
|
83
|
+
value
|
|
84
|
+
when Numeric
|
|
85
|
+
[value]
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError, "Unsupported attribute type #{value.class}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def validate_value_size!(values)
|
|
92
|
+
return if values.size >= @size
|
|
93
|
+
|
|
94
|
+
raise ArgumentError, "Expected at least #{@size} components, got #{values.size}"
|
|
12
95
|
end
|
|
13
96
|
end
|
|
14
97
|
|
|
@@ -22,8 +105,8 @@ module RBGL
|
|
|
22
105
|
@stride = @offset
|
|
23
106
|
end
|
|
24
107
|
|
|
25
|
-
def attribute(name, size)
|
|
26
|
-
@attributes[name.to_sym] = VertexAttribute.new(name, size, @offset)
|
|
108
|
+
def attribute(name, size, kind: nil)
|
|
109
|
+
@attributes[name.to_sym] = VertexAttribute.new(name, size, @offset, kind: kind)
|
|
27
110
|
@offset += size
|
|
28
111
|
end
|
|
29
112
|
|
|
@@ -66,25 +149,13 @@ module RBGL
|
|
|
66
149
|
end
|
|
67
150
|
|
|
68
151
|
def add_vertex(**attributes)
|
|
152
|
+
validate_attribute_keys!(attributes)
|
|
153
|
+
|
|
69
154
|
vertex_data = []
|
|
70
155
|
@layout.attributes.each do |name, attr|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
case value
|
|
75
|
-
when Larb::Vec2
|
|
76
|
-
vertex_data.concat([value.x, value.y])
|
|
77
|
-
when Larb::Vec3
|
|
78
|
-
vertex_data.concat([value.x, value.y, value.z])
|
|
79
|
-
when Larb::Vec4
|
|
80
|
-
vertex_data.concat([value.x, value.y, value.z, value.w])
|
|
81
|
-
when Larb::Color
|
|
82
|
-
vertex_data.concat(value.to_a)
|
|
83
|
-
when Array
|
|
84
|
-
vertex_data.concat(value)
|
|
85
|
-
when Numeric
|
|
86
|
-
vertex_data << value.to_f
|
|
87
|
-
end
|
|
156
|
+
raise ArgumentError, "Missing attribute: #{name}" unless attributes.key?(name)
|
|
157
|
+
|
|
158
|
+
vertex_data.concat(attr.serialize(attributes[name]))
|
|
88
159
|
end
|
|
89
160
|
@data.concat(vertex_data)
|
|
90
161
|
@vertex_count += 1
|
|
@@ -105,18 +176,7 @@ module RBGL
|
|
|
105
176
|
@layout.attributes.each do |name, attr|
|
|
106
177
|
offset = start + attr.offset
|
|
107
178
|
values = @data[offset, attr.size]
|
|
108
|
-
|
|
109
|
-
vertex[name] = case attr.size
|
|
110
|
-
when 1 then values[0]
|
|
111
|
-
when 2 then Larb::Vec2.new(*values)
|
|
112
|
-
when 3 then Larb::Vec3.new(*values)
|
|
113
|
-
when 4
|
|
114
|
-
if name == :color
|
|
115
|
-
Larb::Color.new(*values)
|
|
116
|
-
else
|
|
117
|
-
Larb::Vec4.new(*values)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
179
|
+
vertex[name] = attr.deserialize(values)
|
|
120
180
|
end
|
|
121
181
|
|
|
122
182
|
vertex
|
|
@@ -127,6 +187,15 @@ module RBGL
|
|
|
127
187
|
data.each { |vertex| buffer.add_vertex(**vertex) }
|
|
128
188
|
buffer
|
|
129
189
|
end
|
|
190
|
+
|
|
191
|
+
private
|
|
192
|
+
|
|
193
|
+
def validate_attribute_keys!(attributes)
|
|
194
|
+
unknown_attributes = attributes.keys.map(&:to_sym) - @layout.attributes.keys
|
|
195
|
+
return if unknown_attributes.empty?
|
|
196
|
+
|
|
197
|
+
raise ArgumentError, "Unknown attributes: #{unknown_attributes.join(', ')}"
|
|
198
|
+
end
|
|
130
199
|
end
|
|
131
200
|
|
|
132
201
|
class IndexBuffer
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module Engine
|
|
5
|
+
class ClipSpaceClipper
|
|
6
|
+
CLIP_PLANES = [
|
|
7
|
+
->(position) { position.x + position.w },
|
|
8
|
+
->(position) { position.w - position.x },
|
|
9
|
+
->(position) { position.y + position.w },
|
|
10
|
+
->(position) { position.w - position.y },
|
|
11
|
+
->(position) { position.z + position.w },
|
|
12
|
+
->(position) { position.w - position.z }
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
def clip(v0, v1, v2)
|
|
16
|
+
polygon = [v0, v1, v2]
|
|
17
|
+
|
|
18
|
+
CLIP_PLANES.each do |plane|
|
|
19
|
+
polygon = clip_against_plane(polygon, plane)
|
|
20
|
+
return [] if polygon.empty?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
triangulate(polygon)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clip_line(v0, v1)
|
|
27
|
+
segment = [duplicate_vertex(v0), duplicate_vertex(v1)]
|
|
28
|
+
|
|
29
|
+
CLIP_PLANES.each do |plane|
|
|
30
|
+
segment = clip_segment_against_plane(segment, plane)
|
|
31
|
+
return nil unless segment
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
segment
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def visible_point?(vertex)
|
|
38
|
+
CLIP_PLANES.all? { |plane| signed_distance(vertex, plane) >= 0 }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def clip_against_plane(vertices, plane)
|
|
44
|
+
clipped = []
|
|
45
|
+
return clipped if vertices.empty?
|
|
46
|
+
|
|
47
|
+
previous_vertex = vertices.last
|
|
48
|
+
previous_distance = signed_distance(previous_vertex, plane)
|
|
49
|
+
|
|
50
|
+
vertices.each do |current_vertex|
|
|
51
|
+
current_distance = signed_distance(current_vertex, plane)
|
|
52
|
+
current_inside = current_distance >= 0
|
|
53
|
+
previous_inside = previous_distance >= 0
|
|
54
|
+
|
|
55
|
+
if current_inside != previous_inside
|
|
56
|
+
clipped << interpolate_vertex(previous_vertex, current_vertex, previous_distance, current_distance)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
clipped << duplicate_vertex(current_vertex) if current_inside
|
|
60
|
+
|
|
61
|
+
previous_vertex = current_vertex
|
|
62
|
+
previous_distance = current_distance
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
clipped
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def triangulate(polygon)
|
|
69
|
+
return [] if polygon.size < 3
|
|
70
|
+
|
|
71
|
+
polygon[1..].each_cons(2).map do |v1, v2|
|
|
72
|
+
[duplicate_vertex(polygon.first), duplicate_vertex(v1), duplicate_vertex(v2)]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def clip_segment_against_plane(segment, plane)
|
|
77
|
+
start_vertex, finish_vertex = segment
|
|
78
|
+
start_distance = signed_distance(start_vertex, plane)
|
|
79
|
+
finish_distance = signed_distance(finish_vertex, plane)
|
|
80
|
+
start_inside = start_distance >= 0
|
|
81
|
+
finish_inside = finish_distance >= 0
|
|
82
|
+
|
|
83
|
+
return [start_vertex, finish_vertex] if start_inside && finish_inside
|
|
84
|
+
return nil unless start_inside || finish_inside
|
|
85
|
+
|
|
86
|
+
intersection = interpolate_vertex(start_vertex, finish_vertex, start_distance, finish_distance)
|
|
87
|
+
start_inside ? [start_vertex, intersection] : [intersection, finish_vertex]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def interpolate_vertex(start_vertex, finish_vertex, start_distance, finish_distance)
|
|
91
|
+
denominator = start_distance - finish_distance
|
|
92
|
+
t = denominator.zero? ? 0.0 : start_distance / denominator
|
|
93
|
+
result = ShaderIO.new
|
|
94
|
+
|
|
95
|
+
interpolated_keys(start_vertex, finish_vertex).each do |key|
|
|
96
|
+
start_value = start_vertex[key]
|
|
97
|
+
finish_value = finish_vertex[key]
|
|
98
|
+
result[key] = interpolate_value(start_value, finish_value, t)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
result
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def duplicate_vertex(vertex)
|
|
105
|
+
ShaderIO.new(vertex.to_h)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def interpolated_keys(start_vertex, finish_vertex)
|
|
109
|
+
(start_vertex.to_h.keys + finish_vertex.to_h.keys).uniq
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def interpolate_value(start_value, finish_value, t)
|
|
113
|
+
return finish_value if start_value.nil?
|
|
114
|
+
return start_value if finish_value.nil?
|
|
115
|
+
|
|
116
|
+
case start_value
|
|
117
|
+
when Numeric
|
|
118
|
+
start_value + (finish_value - start_value) * t
|
|
119
|
+
when Larb::Vec2, Larb::Vec3, Larb::Vec4, Larb::Color
|
|
120
|
+
start_value.lerp(finish_value, t)
|
|
121
|
+
else
|
|
122
|
+
t < 0.5 ? start_value : finish_value
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def signed_distance(vertex, plane)
|
|
127
|
+
plane.call(clip_position(vertex))
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def clip_position(vertex)
|
|
131
|
+
position = vertex[:position]
|
|
132
|
+
|
|
133
|
+
case position
|
|
134
|
+
when Larb::Vec4 then position
|
|
135
|
+
when Larb::Vec3 then Larb::Vec4.new(position.x, position.y, position.z, 1.0)
|
|
136
|
+
when Larb::Vec2 then Larb::Vec4.new(position.x, position.y, 0.0, 1.0)
|
|
137
|
+
else
|
|
138
|
+
raise ArgumentError, "Unsupported clip position type: #{position.class}"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
data/lib/rbgl/engine/context.rb
CHANGED
|
@@ -8,6 +8,7 @@ module RBGL
|
|
|
8
8
|
def initialize(width:, height:)
|
|
9
9
|
@framebuffer = Framebuffer.new(width, height)
|
|
10
10
|
@rasterizer = Rasterizer.new(@framebuffer)
|
|
11
|
+
@clipper = ClipSpaceClipper.new
|
|
11
12
|
@pipeline = nil
|
|
12
13
|
@uniforms = Uniforms.new
|
|
13
14
|
@vertex_buffer = nil
|
|
@@ -38,119 +39,171 @@ module RBGL
|
|
|
38
39
|
@framebuffer.clear(color: color, depth: depth)
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
def resize(width:, height:)
|
|
43
|
+
@framebuffer.resize(width, height)
|
|
44
|
+
@rasterizer.resize(width, height)
|
|
45
|
+
end
|
|
46
|
+
|
|
41
47
|
def draw_arrays(mode, first, count)
|
|
48
|
+
validate_draw_state!
|
|
49
|
+
draw_vertices(mode, first...first + count)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def draw_elements(mode, count, offset = 0)
|
|
53
|
+
validate_draw_state!(indexed: true)
|
|
54
|
+
indices = @index_buffer.indices[offset, count] || []
|
|
55
|
+
draw_vertices(mode, indices, vertex_cache: {})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def width
|
|
59
|
+
@framebuffer.width
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def height
|
|
63
|
+
@framebuffer.height
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def aspect_ratio
|
|
67
|
+
width.to_f / height
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def validate_draw_state!(indexed: false)
|
|
42
73
|
raise "No pipeline bound" unless @pipeline
|
|
43
74
|
raise "No vertex buffer bound" unless @vertex_buffer
|
|
75
|
+
raise "No index buffer bound" if indexed && !@index_buffer
|
|
76
|
+
end
|
|
44
77
|
|
|
45
|
-
|
|
78
|
+
def draw_vertices(mode, indices, vertex_cache: nil)
|
|
79
|
+
each_primitive(mode, indices) do |primitive_indices|
|
|
80
|
+
primitive = primitive_indices.map { |index| fetch_vertex(index, vertex_cache) }
|
|
81
|
+
draw_primitive(mode, primitive)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def each_primitive(mode, vertices)
|
|
86
|
+
return enum_for(:each_primitive, mode, vertices) unless block_given?
|
|
87
|
+
|
|
88
|
+
vertices = vertices.to_a unless vertices.respond_to?(:[])
|
|
46
89
|
|
|
47
90
|
case mode
|
|
48
91
|
when :triangles
|
|
49
|
-
(
|
|
50
|
-
draw_triangle(vertices[i], vertices[i + 1], vertices[i + 2])
|
|
51
|
-
end
|
|
92
|
+
each_slice(vertices, 3) { |triangle| yield triangle }
|
|
52
93
|
when :lines
|
|
53
|
-
(
|
|
54
|
-
draw_line(vertices[i], vertices[i + 1])
|
|
55
|
-
end
|
|
94
|
+
each_slice(vertices, 2) { |line| yield line }
|
|
56
95
|
when :points
|
|
57
|
-
vertices.each { |
|
|
96
|
+
vertices.each { |vertex| yield [vertex] }
|
|
58
97
|
when :triangle_strip
|
|
59
|
-
(0...vertices.size - 2).each do |
|
|
60
|
-
if
|
|
61
|
-
|
|
98
|
+
(0...vertices.size - 2).each do |index|
|
|
99
|
+
if index.even?
|
|
100
|
+
yield [vertices[index], vertices[index + 1], vertices[index + 2]]
|
|
62
101
|
else
|
|
63
|
-
|
|
102
|
+
yield [vertices[index], vertices[index + 2], vertices[index + 1]]
|
|
64
103
|
end
|
|
65
104
|
end
|
|
66
105
|
when :triangle_fan
|
|
67
|
-
(1...vertices.size - 1).each do |
|
|
68
|
-
|
|
106
|
+
(1...vertices.size - 1).each do |index|
|
|
107
|
+
yield [vertices[0], vertices[index], vertices[index + 1]]
|
|
69
108
|
end
|
|
109
|
+
else
|
|
110
|
+
raise ArgumentError, "Unsupported primitive mode: #{mode}"
|
|
70
111
|
end
|
|
71
112
|
end
|
|
72
113
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
raise "No index buffer bound" unless @index_buffer
|
|
114
|
+
def each_slice(vertices, size)
|
|
115
|
+
vertices.each_slice(size) do |primitive|
|
|
116
|
+
next unless primitive.size == size
|
|
77
117
|
|
|
78
|
-
|
|
79
|
-
|
|
118
|
+
yield primitive
|
|
119
|
+
end
|
|
120
|
+
end
|
|
80
121
|
|
|
122
|
+
def draw_primitive(mode, primitive)
|
|
81
123
|
case mode
|
|
82
|
-
when :triangles
|
|
83
|
-
(0...vertices.size).step(3) do |i|
|
|
84
|
-
draw_triangle(vertices[i], vertices[i + 1], vertices[i + 2])
|
|
85
|
-
end
|
|
86
124
|
when :lines
|
|
87
|
-
(
|
|
88
|
-
|
|
89
|
-
|
|
125
|
+
draw_line(*primitive)
|
|
126
|
+
when :points
|
|
127
|
+
draw_point(primitive.first)
|
|
128
|
+
else
|
|
129
|
+
draw_triangle(*primitive)
|
|
90
130
|
end
|
|
91
131
|
end
|
|
92
132
|
|
|
93
|
-
def width
|
|
94
|
-
@framebuffer.width
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def height
|
|
98
|
-
@framebuffer.height
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def aspect_ratio
|
|
102
|
-
width.to_f / height
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
private
|
|
106
|
-
|
|
107
133
|
def process_vertex(index)
|
|
108
134
|
input = @vertex_buffer.get_vertex(index)
|
|
135
|
+
return nil unless input
|
|
136
|
+
|
|
109
137
|
input_io = ShaderIO.new
|
|
110
138
|
input.each { |k, v| input_io[k] = v }
|
|
111
139
|
|
|
112
140
|
@pipeline.vertex_shader.process(input_io, @uniforms)
|
|
113
141
|
end
|
|
114
142
|
|
|
143
|
+
def fetch_vertex(index, vertex_cache)
|
|
144
|
+
return process_vertex(index) unless vertex_cache
|
|
145
|
+
return vertex_cache[index] if vertex_cache.key?(index)
|
|
146
|
+
|
|
147
|
+
vertex_cache[index] = process_vertex(index)
|
|
148
|
+
end
|
|
149
|
+
|
|
115
150
|
def draw_triangle(v0, v1, v2)
|
|
116
151
|
return unless v0 && v1 && v2
|
|
117
|
-
return if clip_triangle?(v0, v1, v2)
|
|
118
152
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
153
|
+
clip_triangle(v0, v1, v2).each do |triangle|
|
|
154
|
+
@rasterizer.rasterize_triangle(
|
|
155
|
+
*triangle,
|
|
156
|
+
@pipeline.fragment_shader,
|
|
157
|
+
@uniforms,
|
|
158
|
+
**rasterizer_state
|
|
159
|
+
)
|
|
160
|
+
end
|
|
125
161
|
end
|
|
126
162
|
|
|
127
163
|
def draw_line(v0, v1)
|
|
128
164
|
return unless v0 && v1
|
|
165
|
+
clipped = clip_line(v0, v1)
|
|
166
|
+
return unless clipped
|
|
129
167
|
|
|
130
|
-
@rasterizer.rasterize_line(
|
|
168
|
+
@rasterizer.rasterize_line(*clipped, @pipeline.fragment_shader, @uniforms, **rasterizer_state)
|
|
131
169
|
end
|
|
132
170
|
|
|
133
171
|
def draw_point(v)
|
|
134
|
-
return unless v
|
|
172
|
+
return unless v && point_visible?(v)
|
|
135
173
|
|
|
136
|
-
@rasterizer.rasterize_point(
|
|
174
|
+
@rasterizer.rasterize_point(
|
|
175
|
+
v,
|
|
176
|
+
@pipeline.fragment_shader,
|
|
177
|
+
@uniforms,
|
|
178
|
+
depth_test: @pipeline.depth_test,
|
|
179
|
+
depth_write: @pipeline.depth_write,
|
|
180
|
+
blend_mode: @pipeline.blend_mode
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def clip_triangle(v0, v1, v2)
|
|
185
|
+
@clipper.clip(v0, v1, v2)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def clip_line(v0, v1)
|
|
189
|
+
@clipper.clip_line(v0, v1)
|
|
137
190
|
end
|
|
138
191
|
|
|
139
192
|
def clip_triangle?(v0, v1, v2)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
end
|
|
193
|
+
clip_triangle(v0, v1, v2).empty?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def point_visible?(vertex)
|
|
197
|
+
@clipper.visible_point?(vertex)
|
|
198
|
+
end
|
|
147
199
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
200
|
+
def rasterizer_state
|
|
201
|
+
{
|
|
202
|
+
cull_mode: @pipeline.cull_mode,
|
|
203
|
+
depth_test: @pipeline.depth_test,
|
|
204
|
+
depth_write: @pipeline.depth_write,
|
|
205
|
+
blend_mode: @pipeline.blend_mode
|
|
206
|
+
}
|
|
154
207
|
end
|
|
155
208
|
end
|
|
156
209
|
end
|