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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -1
  3. data/lib/rbgl/engine/buffer.rb +102 -33
  4. data/lib/rbgl/engine/clip_space_clipper.rb +143 -0
  5. data/lib/rbgl/engine/context.rb +117 -64
  6. data/lib/rbgl/engine/framebuffer.rb +41 -32
  7. data/lib/rbgl/engine/pipeline.rb +38 -7
  8. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +105 -0
  9. data/lib/rbgl/engine/rasterizer/line_renderer.rb +72 -0
  10. data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
  11. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +87 -0
  12. data/lib/rbgl/engine/rasterizer.rb +73 -162
  13. data/lib/rbgl/engine/shader/base_shader.rb +55 -0
  14. data/lib/rbgl/engine/shader/builtins.rb +254 -0
  15. data/lib/rbgl/engine/shader/dynamic_data.rb +65 -0
  16. data/lib/rbgl/engine/shader.rb +5 -318
  17. data/lib/rbgl/engine/texture.rb +227 -38
  18. data/lib/rbgl/engine.rb +1 -0
  19. data/lib/rbgl/gui/backend.rb +12 -30
  20. data/lib/rbgl/gui/backend_factory.rb +85 -0
  21. data/lib/rbgl/gui/cocoa/backend.rb +15 -35
  22. data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
  23. data/lib/rbgl/gui/file_backend/frame_writer.rb +28 -0
  24. data/lib/rbgl/gui/file_backend/ppm_writer.rb +25 -0
  25. data/lib/rbgl/gui/file_backend.rb +25 -48
  26. data/lib/rbgl/gui/wayland/backend.rb +108 -26
  27. data/lib/rbgl/gui/wayland/codec.rb +54 -0
  28. data/lib/rbgl/gui/wayland/connection.rb +80 -240
  29. data/lib/rbgl/gui/wayland/event_dispatcher.rb +74 -0
  30. data/lib/rbgl/gui/wayland/global_binder.rb +44 -0
  31. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +51 -0
  32. data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
  33. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +146 -0
  34. data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
  35. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +45 -0
  36. data/lib/rbgl/gui/wayland/protocol_objects.rb +7 -0
  37. data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
  38. data/lib/rbgl/gui/window/render_loop.rb +58 -0
  39. data/lib/rbgl/gui/window.rb +41 -82
  40. data/lib/rbgl/gui/x11/atom_cache.rb +26 -0
  41. data/lib/rbgl/gui/x11/backend.rb +14 -28
  42. data/lib/rbgl/gui/x11/connection.rb +104 -169
  43. data/lib/rbgl/gui/x11/event_parser.rb +60 -0
  44. data/lib/rbgl/gui/x11/request_encoder.rb +154 -0
  45. data/lib/rbgl/gui/x11/transport.rb +31 -0
  46. data/lib/rbgl/gui.rb +1 -0
  47. data/lib/rbgl/version.rb +1 -1
  48. metadata +31 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ff09c0ce9d51c2c28146dbcfdb9eecbe2a9982e45a43fbb81cd8bc1de4b96fb
4
- data.tar.gz: e58db1f718d9e8a8e1fb7cac8e3acffc026eedfc18e64d427f1669e95e7674be
3
+ metadata.gz: c17ca3e225ae4e0c8aff6550263d7c6fa8b741e5711bfe000b847e6630c62fed
4
+ data.tar.gz: 3a227d1b5bca1fd9f1e4ef8cfea35321cbf8f6ecf967a517086597f259574652
5
5
  SHA512:
6
- metadata.gz: b6f662d84ba8b4f44063d9d09e3ce7154bea6547ea3a53ad383b06cf719edb62d0e79ae5b85046b408d1ffa7125100d80a2414dc3213c509d41d9d89cb2e0f2c
7
- data.tar.gz: a04b7ce7b7c20502af090e26fcdf0514cfe37850266bfce5a5ad27885999c3bababe0c6089e8cf2f9f31921cdf5c27c2a1e523e03a07b31f40469e048625c3bd
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.
@@ -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
- value = attributes[name]
72
- raise "Missing attribute: #{name}" unless value
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
@@ -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
- vertices = (first...first + count).map { |i| process_vertex(i) }
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
- (0...vertices.size).step(3) do |i|
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
- (0...vertices.size).step(2) do |i|
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 { |v| draw_point(v) }
96
+ vertices.each { |vertex| yield [vertex] }
58
97
  when :triangle_strip
59
- (0...vertices.size - 2).each do |i|
60
- if i.even?
61
- draw_triangle(vertices[i], vertices[i + 1], vertices[i + 2])
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
- draw_triangle(vertices[i], vertices[i + 2], vertices[i + 1])
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 |i|
68
- draw_triangle(vertices[0], vertices[i], vertices[i + 1])
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 draw_elements(mode, count, offset = 0)
74
- raise "No pipeline bound" unless @pipeline
75
- raise "No vertex buffer bound" unless @vertex_buffer
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
- indices = @index_buffer.indices[offset, count]
79
- vertices = indices.map { |i| process_vertex(i) }
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
- (0...vertices.size).step(2) do |i|
88
- draw_line(vertices[i], vertices[i + 1])
89
- end
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
- @rasterizer.rasterize_triangle(
120
- v0, v1, v2,
121
- @pipeline.fragment_shader,
122
- @uniforms,
123
- cull_mode: @pipeline.cull_mode
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(v0, v1, @pipeline.fragment_shader, @uniforms)
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(v, @pipeline.fragment_shader, @uniforms)
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
- positions = [v0[:position], v1[:position], v2[:position]].map do |p|
141
- if p.is_a?(Larb::Vec4)
142
- p.perspective_divide
143
- else
144
- p
145
- end
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
- positions.all? { |p| p.x < -1 } ||
149
- positions.all? { |p| p.x > 1 } ||
150
- positions.all? { |p| p.y < -1 } ||
151
- positions.all? { |p| p.y > 1 } ||
152
- positions.all? { |p| p.z < -1 } ||
153
- positions.all? { |p| p.z > 1 }
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