rbgl 1.0.0 → 1.0.2

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.codespellignore +4 -0
  3. data/.rubocop.yml +3 -0
  4. data/.yamllint +16 -0
  5. data/CHANGELOG.md +11 -1
  6. data/README.md +130 -74
  7. data/Rakefile +7 -0
  8. data/bench/renderer_bench.rb +283 -0
  9. data/docs/.nojekyll +1 -0
  10. data/docs/index.html +38 -0
  11. data/examples/array_test.rb +1 -1
  12. data/examples/color_test.rb +1 -1
  13. data/examples/cube_spinning.rb +1 -1
  14. data/examples/gradient.rb +1 -1
  15. data/examples/multi_return_test.rb +1 -1
  16. data/examples/plasma.rb +1 -1
  17. data/examples/sphere_raymarch.rb +1 -1
  18. data/examples/triangle_window.rb +1 -1
  19. data/exe/rbgl +42 -0
  20. data/lib/rbgl/cli/doctor.rb +155 -0
  21. data/lib/rbgl/engine/buffer.rb +55 -9
  22. data/lib/rbgl/engine/clip_space_clipper.rb +23 -3
  23. data/lib/rbgl/engine/context.rb +23 -6
  24. data/lib/rbgl/engine/framebuffer.rb +146 -41
  25. data/lib/rbgl/engine/immutable_color.rb +32 -0
  26. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +100 -10
  27. data/lib/rbgl/engine/rasterizer/line_renderer.rb +30 -14
  28. data/lib/rbgl/engine/rasterizer/point_renderer.rb +3 -3
  29. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +72 -23
  30. data/lib/rbgl/engine/rasterizer.rb +3 -20
  31. data/lib/rbgl/engine/shader/base_shader.rb +3 -7
  32. data/lib/rbgl/engine/shader/builtins.rb +13 -10
  33. data/lib/rbgl/engine/shader/dynamic_data.rb +18 -10
  34. data/lib/rbgl/engine/texture.rb +74 -50
  35. data/lib/rbgl/engine.rb +3 -1
  36. data/lib/rbgl/gui/backend.rb +12 -7
  37. data/lib/rbgl/gui/backend_factory.rb +7 -3
  38. data/lib/rbgl/gui/cocoa/backend.rb +50 -17
  39. data/lib/rbgl/gui/cocoa/key_mapper.rb +48 -0
  40. data/lib/rbgl/gui/event.rb +4 -2
  41. data/lib/rbgl/gui/file_backend/bmp_writer.rb +2 -2
  42. data/lib/rbgl/gui/file_backend/frame_writer.rb +7 -0
  43. data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
  44. data/lib/rbgl/gui/file_backend/ppm_writer.rb +1 -0
  45. data/lib/rbgl/gui/file_backend.rb +24 -6
  46. data/lib/rbgl/gui/wayland/backend.rb +127 -55
  47. data/lib/rbgl/gui/wayland/codec.rb +7 -11
  48. data/lib/rbgl/gui/wayland/connection.rb +137 -20
  49. data/lib/rbgl/gui/wayland/event_dispatcher.rb +103 -12
  50. data/lib/rbgl/gui/wayland/global_binder.rb +6 -4
  51. data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
  52. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +14 -2
  53. data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
  54. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +23 -12
  55. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +15 -0
  56. data/lib/rbgl/gui/wayland/protocol_objects.rb +1 -0
  57. data/lib/rbgl/gui/window/render_loop.rb +71 -18
  58. data/lib/rbgl/gui/window.rb +39 -8
  59. data/lib/rbgl/gui/x11/atom_cache.rb +4 -1
  60. data/lib/rbgl/gui/x11/backend.rb +66 -28
  61. data/lib/rbgl/gui/x11/connection.rb +216 -86
  62. data/lib/rbgl/gui/x11/event_parser.rb +13 -2
  63. data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
  64. data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
  65. data/lib/rbgl/gui/x11/request_encoder.rb +29 -28
  66. data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
  67. data/lib/rbgl/gui/x11/transport.rb +52 -2
  68. data/lib/rbgl/gui/x11/x_authority.rb +107 -0
  69. data/lib/rbgl/gui.rb +2 -5
  70. data/lib/rbgl/version.rb +1 -1
  71. metadata +38 -24
  72. data/examples/chemical_heartbeat.rb +0 -166
  73. data/examples/dark_transit.rb +0 -166
  74. data/examples/fractured_orb.rb +0 -428
  75. data/examples/fractured_orb_rb.rb +0 -598
  76. data/examples/hexagonal_flow.rb +0 -333
  77. data/examples/teapot.rb +0 -362
  78. data/examples/teapot_mcu.rb +0 -344
@@ -3,121 +3,226 @@
3
3
  module RBGL
4
4
  module Engine
5
5
  class Framebuffer
6
- attr_reader :width, :height, :color_buffer, :depth_buffer
6
+ attr_reader :width, :height, :depth_buffer
7
+
8
+ def self.from_rgba_bytes(width, height, buffer)
9
+ new(width, height).tap { |framebuffer| framebuffer.send(:load_rgba_bytes, buffer) }
10
+ end
7
11
 
8
12
  def initialize(width, height)
9
- @width = width
10
- @height = height
11
- @color_buffer = Array.new(width * height) { Larb::Color.black }
12
- @depth_buffer = Array.new(width * height) { Float::INFINITY }
13
+ resize(width, height)
13
14
  end
14
15
 
15
16
  def resize(width, height)
17
+ validate_dimensions!(width, height)
18
+ pixels = Array.new(width * height, pack_color(Larb::Color.black))
19
+ depth_buffer = Array.new(width * height) { Float::INFINITY }
20
+
16
21
  @width = width
17
22
  @height = height
18
- @color_buffer = Array.new(width * height) { Larb::Color.black }
19
- @depth_buffer = Array.new(width * height) { Float::INFINITY }
23
+ @pixels = pixels
24
+ @depth_buffer = depth_buffer
25
+ end
26
+
27
+ def color_buffer
28
+ @pixels.map { |pixel| unpack_color(pixel) }
29
+ end
30
+
31
+ def each_packed_pixel(&block)
32
+ return @pixels.each unless block
33
+
34
+ @pixels.each(&block)
20
35
  end
21
36
 
22
37
  def get_pixel(x, y)
23
38
  return nil if x < 0 || x >= @width || y < 0 || y >= @height
24
39
 
25
- @color_buffer[y * @width + x]
40
+ unpack_color(@pixels[(y * @width) + x])
26
41
  end
27
42
 
28
43
  def set_pixel(x, y, color)
29
44
  return if x < 0 || x >= @width || y < 0 || y >= @height
30
45
 
31
- @color_buffer[y * @width + x] = color
46
+ @pixels[(y * @width) + x] = pack_color(color)
32
47
  end
33
48
 
34
49
  def get_depth(x, y)
35
50
  return Float::INFINITY if x < 0 || x >= @width || y < 0 || y >= @height
36
51
 
37
- @depth_buffer[y * @width + x]
52
+ @depth_buffer[(y * @width) + x]
38
53
  end
39
54
 
40
55
  def set_depth(x, y, depth)
41
56
  return if x < 0 || x >= @width || y < 0 || y >= @height
42
57
 
43
- @depth_buffer[y * @width + x] = depth
58
+ validate_depth!(depth)
59
+ @depth_buffer[(y * @width) + x] = depth
44
60
  end
45
61
 
46
62
  def write_pixel(x, y, color, depth, depth_test: true, depth_write: true, blend_mode: :none)
47
63
  return false if x < 0 || x >= @width || y < 0 || y >= @height
64
+ return false unless depth.is_a?(Numeric) && depth.real? && depth.finite?
48
65
 
49
- idx = y * @width + x
66
+ idx = (y * @width) + x
50
67
  return false if depth_test && depth >= @depth_buffer[idx]
51
68
 
52
- @color_buffer[idx] = blend_color(@color_buffer[idx], color, blend_mode)
69
+ @pixels[idx] = blend_mode == :alpha ? blend_pixel(@pixels[idx], color) : pack_color(color)
53
70
  @depth_buffer[idx] = depth if depth_write
54
71
  true
55
72
  end
56
73
 
57
74
  def clear(color: Larb::Color.black, depth: Float::INFINITY)
58
- @color_buffer.fill(color)
75
+ validate_depth!(depth)
76
+ @pixels.fill(pack_color(color))
59
77
  @depth_buffer.fill(depth)
60
78
  end
61
79
 
62
80
  def clear_color(color)
63
- @color_buffer.fill(color)
81
+ @pixels.fill(pack_color(color))
64
82
  end
65
83
 
66
84
  def clear_depth(depth = Float::INFINITY)
85
+ validate_depth!(depth)
67
86
  @depth_buffer.fill(depth)
68
87
  end
69
88
 
70
89
  def to_ppm
71
- ppm = "P3\n#{@width} #{@height}\n255\n"
90
+ ppm = String.new(capacity: @width * @height * 12, encoding: Encoding::BINARY)
91
+ ppm << "P3\n#{@width} #{@height}\n255\n"
72
92
  @height.times do |y|
73
- row = @width.times.map do |x|
74
- c = @color_buffer[y * @width + x]
75
- bytes = c.to_bytes
76
- "#{bytes[0]} #{bytes[1]} #{bytes[2]}"
93
+ @width.times do |x|
94
+ pixel = @pixels[(y * @width) + x]
95
+ ppm << " " unless x.zero?
96
+ ppm << red_byte(pixel).to_s << " " << green_byte(pixel).to_s << " " << blue_byte(pixel).to_s
77
97
  end
78
- ppm += row.join(" ") + "\n"
98
+ ppm << "\n"
79
99
  end
80
100
  ppm
81
101
  end
82
102
 
83
103
  def to_ppm_binary
84
104
  header = "P6\n#{@width} #{@height}\n255\n"
85
- pixels = packed_color_bytes(%i[r g b])
105
+ pixels = packed_color_bytes(:rgb)
86
106
  header + pixels
87
107
  end
88
108
 
89
109
  def to_rgba_bytes
90
- packed_color_bytes(%i[r g b a])
110
+ packed_color_bytes(:rgba)
91
111
  end
92
112
 
93
113
  def to_bgra_bytes
94
- packed_color_bytes(%i[b g r a])
114
+ packed_color_bytes(:bgra)
95
115
  end
96
116
 
97
117
  private
98
118
 
99
- def blend_color(destination, source, blend_mode)
100
- case blend_mode
101
- when :alpha
102
- alpha = source.a
103
- inv_alpha = 1.0 - alpha
104
- Larb::Color.new(
105
- source.r * alpha + destination.r * inv_alpha,
106
- source.g * alpha + destination.g * inv_alpha,
107
- source.b * alpha + destination.b * inv_alpha,
108
- alpha + destination.a * inv_alpha
119
+ def validate_depth!(depth)
120
+ return if depth.is_a?(Numeric) && depth.real? && (depth.finite? || depth == Float::INFINITY)
121
+
122
+ raise ArgumentError, "Depth must be finite or positive infinity"
123
+ end
124
+
125
+ def validate_dimensions!(width, height)
126
+ return if width.is_a?(Integer) && width.positive? && height.is_a?(Integer) && height.positive?
127
+
128
+ raise ArgumentError, "Framebuffer dimensions must be positive integers"
129
+ end
130
+
131
+ def load_rgba_bytes(buffer)
132
+ bytes = String.try_convert(buffer)
133
+ raise ArgumentError, "Pixel buffer must be a String" unless bytes
134
+
135
+ expected_size = @width * @height * 4
136
+ unless bytes.bytesize == expected_size
137
+ raise ArgumentError, "Pixel buffer size mismatch: expected #{expected_size}, got #{bytes.bytesize}"
138
+ end
139
+
140
+ @pixels = Array.new(@width * @height)
141
+ pixel_index = 0
142
+ byte_index = 0
143
+ while pixel_index < @pixels.length
144
+ @pixels[pixel_index] = pack_bytes(
145
+ bytes.getbyte(byte_index),
146
+ bytes.getbyte(byte_index + 1),
147
+ bytes.getbyte(byte_index + 2),
148
+ bytes.getbyte(byte_index + 3)
109
149
  )
110
- else
111
- source
150
+ pixel_index += 1
151
+ byte_index += 4
112
152
  end
113
153
  end
114
154
 
115
- def packed_color_bytes(channels)
116
- @color_buffer.flat_map do |color|
117
- channels.map do |channel|
118
- (color.public_send(channel) * 255).round.clamp(0, 255)
155
+ def blend_pixel(destination, source)
156
+ validate_color!(source)
157
+ alpha = source.a
158
+ inv_alpha = 1.0 - alpha
159
+ pack_bytes(
160
+ color_byte((source.r * alpha) + ((red_byte(destination) / 255.0) * inv_alpha)),
161
+ color_byte((source.g * alpha) + ((green_byte(destination) / 255.0) * inv_alpha)),
162
+ color_byte((source.b * alpha) + ((blue_byte(destination) / 255.0) * inv_alpha)),
163
+ color_byte(alpha + ((alpha_byte(destination) / 255.0) * inv_alpha))
164
+ )
165
+ end
166
+
167
+ def pack_color(color)
168
+ validate_color!(color)
169
+ pack_bytes(color_byte(color.r), color_byte(color.g), color_byte(color.b), color_byte(color.a))
170
+ end
171
+
172
+ def validate_color!(color)
173
+ return if color.is_a?(Larb::Color)
174
+
175
+ raise ArgumentError, "Colors must be Larb::Color values"
176
+ end
177
+
178
+ def packed_color_bytes(format)
179
+ channel_count = format == :rgb ? 3 : 4
180
+ bytes = String.new(capacity: @pixels.length * channel_count, encoding: Encoding::BINARY)
181
+
182
+ @pixels.each do |pixel|
183
+ case format
184
+ when :rgb
185
+ bytes << red_byte(pixel) << green_byte(pixel) << blue_byte(pixel)
186
+ when :rgba
187
+ bytes << red_byte(pixel) << green_byte(pixel) << blue_byte(pixel) << alpha_byte(pixel)
188
+ when :bgra
189
+ bytes << blue_byte(pixel) << green_byte(pixel) << red_byte(pixel) << alpha_byte(pixel)
119
190
  end
120
- end.pack("C*")
191
+ end
192
+ bytes
193
+ end
194
+
195
+ def unpack_color(pixel)
196
+ ImmutableColor.new(
197
+ red_byte(pixel) / 255.0,
198
+ green_byte(pixel) / 255.0,
199
+ blue_byte(pixel) / 255.0,
200
+ alpha_byte(pixel) / 255.0
201
+ ).freeze
202
+ end
203
+
204
+ def pack_bytes(red, green, blue, alpha)
205
+ (alpha << 24) | (red << 16) | (green << 8) | blue
206
+ end
207
+
208
+ def alpha_byte(pixel)
209
+ (pixel >> 24) & 0xFF
210
+ end
211
+
212
+ def red_byte(pixel)
213
+ (pixel >> 16) & 0xFF
214
+ end
215
+
216
+ def green_byte(pixel)
217
+ (pixel >> 8) & 0xFF
218
+ end
219
+
220
+ def blue_byte(pixel)
221
+ pixel & 0xFF
222
+ end
223
+
224
+ def color_byte(value)
225
+ (value * 255).round.clamp(0, 255)
121
226
  end
122
227
  end
123
228
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module Engine
5
+ class ImmutableColor < Larb::Color
6
+ def self.from(color)
7
+ return color if color.is_a?(self)
8
+ unless color.is_a?(Larb::Color)
9
+ raise ArgumentError, "Colors must be Larb::Color values"
10
+ end
11
+
12
+ new(color.r, color.g, color.b, color.a).freeze
13
+ end
14
+
15
+ def r=(_value)
16
+ raise FrozenError, "stored colors are immutable"
17
+ end
18
+
19
+ def g=(_value)
20
+ raise FrozenError, "stored colors are immutable"
21
+ end
22
+
23
+ def b=(_value)
24
+ raise FrozenError, "stored colors are immutable"
25
+ end
26
+
27
+ def a=(_value)
28
+ raise FrozenError, "stored colors are immutable"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -5,15 +5,37 @@ module RBGL
5
5
  class Rasterizer
6
6
  class AttributeInterpolator
7
7
  def interpolate(vertices, weights)
8
- result = ShaderIO.new
8
+ interpolate_prepared(prepare(vertices), weights, result: ShaderIO.new)
9
+ end
9
10
 
10
- interpolated_keys(vertices).each do |key|
11
+ def prepare(vertices)
12
+ interpolated_keys(vertices).filter_map do |key|
11
13
  values = vertices.map { |vertex| vertex[key] }
12
- next if values.any?(&:nil?)
14
+ [key, *values] unless values.any?(&:nil?)
15
+ end
16
+ end
13
17
 
14
- result[key] = interpolate_values(values, weights)
18
+ def interpolate_prepared(plan, weights, result:)
19
+ result.clear
20
+ plan.each do |entry|
21
+ result[entry[0]] = interpolate_values(entry.drop(1), weights)
15
22
  end
23
+ result
24
+ end
16
25
 
26
+ def interpolate_triangle(plan, w0, w1, w2, result:)
27
+ result.clear
28
+ plan.each do |key, a, b, c|
29
+ result[key] = interpolate_three(a, b, c, w0, w1, w2)
30
+ end
31
+ result
32
+ end
33
+
34
+ def interpolate_line(plan, w0, w1, result:)
35
+ result.clear
36
+ plan.each do |key, a, b|
37
+ result[key] = interpolate_two(a, b, w0, w1)
38
+ end
17
39
  result
18
40
  end
19
41
 
@@ -29,6 +51,8 @@ module RBGL
29
51
  vector4(values, weights)
30
52
  when Larb::Color
31
53
  color(values, weights)
54
+ when Array
55
+ numeric_arrays?(*values) ? array(values, weights) : first
32
56
  when Numeric
33
57
  scalar(values, weights)
34
58
  else
@@ -50,8 +74,71 @@ module RBGL
50
74
  corrected.map { |weight| weight / total }
51
75
  end
52
76
 
77
+ def inverse_clip_w(position)
78
+ return 1.0 unless position.is_a?(Larb::Vec4)
79
+ return 1.0 if position.w.zero?
80
+
81
+ 1.0 / position.w
82
+ end
83
+
53
84
  private
54
85
 
86
+ def interpolate_three(a, b, c, w0, w1, w2)
87
+ case a
88
+ when Larb::Vec2
89
+ Larb::Vec2.new((a.x * w0) + (b.x * w1) + (c.x * w2), (a.y * w0) + (b.y * w1) + (c.y * w2))
90
+ when Larb::Vec3
91
+ Larb::Vec3.new(
92
+ (a.x * w0) + (b.x * w1) + (c.x * w2),
93
+ (a.y * w0) + (b.y * w1) + (c.y * w2),
94
+ (a.z * w0) + (b.z * w1) + (c.z * w2)
95
+ )
96
+ when Larb::Vec4
97
+ Larb::Vec4.new(
98
+ (a.x * w0) + (b.x * w1) + (c.x * w2),
99
+ (a.y * w0) + (b.y * w1) + (c.y * w2),
100
+ (a.z * w0) + (b.z * w1) + (c.z * w2),
101
+ (a.w * w0) + (b.w * w1) + (c.w * w2)
102
+ )
103
+ when Larb::Color
104
+ Larb::Color.new(
105
+ (a.r * w0) + (b.r * w1) + (c.r * w2),
106
+ (a.g * w0) + (b.g * w1) + (c.g * w2),
107
+ (a.b * w0) + (b.b * w1) + (c.b * w2),
108
+ (a.a * w0) + (b.a * w1) + (c.a * w2)
109
+ )
110
+ when Array then numeric_arrays?(a, b, c) ? array([a, b, c], [w0, w1, w2]) : a
111
+ when Numeric then (a * w0) + (b * w1) + (c * w2)
112
+ else a
113
+ end
114
+ end
115
+
116
+ def interpolate_two(a, b, w0, w1)
117
+ case a
118
+ when Larb::Vec2
119
+ Larb::Vec2.new((a.x * w0) + (b.x * w1), (a.y * w0) + (b.y * w1))
120
+ when Larb::Vec3
121
+ Larb::Vec3.new((a.x * w0) + (b.x * w1), (a.y * w0) + (b.y * w1), (a.z * w0) + (b.z * w1))
122
+ when Larb::Vec4
123
+ Larb::Vec4.new(
124
+ (a.x * w0) + (b.x * w1),
125
+ (a.y * w0) + (b.y * w1),
126
+ (a.z * w0) + (b.z * w1),
127
+ (a.w * w0) + (b.w * w1)
128
+ )
129
+ when Larb::Color
130
+ Larb::Color.new(
131
+ (a.r * w0) + (b.r * w1),
132
+ (a.g * w0) + (b.g * w1),
133
+ (a.b * w0) + (b.b * w1),
134
+ (a.a * w0) + (b.a * w1)
135
+ )
136
+ when Array then numeric_arrays?(a, b) ? array([a, b], [w0, w1]) : a
137
+ when Numeric then (a * w0) + (b * w1)
138
+ else a
139
+ end
140
+ end
141
+
55
142
  def interpolated_keys(vertices)
56
143
  vertices.flat_map { |vertex| vertex.to_h.keys }.uniq - [:position]
57
144
  end
@@ -60,6 +147,15 @@ module RBGL
60
147
  values.zip(weights).sum { |value, weight| value * weight }
61
148
  end
62
149
 
150
+ def array(values, weights)
151
+ values.transpose.map { |components| scalar(components, weights) }
152
+ end
153
+
154
+ def numeric_arrays?(*values)
155
+ size = values.first.size
156
+ values.all? { |value| value.is_a?(Array) && value.size == size && value.all?(Numeric) }
157
+ end
158
+
63
159
  def vector2(values, weights)
64
160
  Larb::Vec2.new(
65
161
  scalar(values.map(&:x), weights),
@@ -93,12 +189,6 @@ module RBGL
93
189
  )
94
190
  end
95
191
 
96
- def inverse_clip_w(position)
97
- return 1.0 unless position.is_a?(Larb::Vec4)
98
- return 1.0 if position.w.zero?
99
-
100
- 1.0 / position.w
101
- end
102
192
  end
103
193
  end
104
194
  end
@@ -13,6 +13,10 @@ module RBGL
13
13
  def rasterize(v0, v1, fragment_shader, uniforms, state)
14
14
  p0 = @viewport_transform.call(v0[:position])
15
15
  p1 = @viewport_transform.call(v1[:position])
16
+ if ([p0.x, p0.y, p0.z] <=> [p1.x, p1.y, p1.z]).positive?
17
+ p0, p1 = p1, p0
18
+ v0, v1 = v1, v0
19
+ end
16
20
 
17
21
  x0 = p0.x.round
18
22
  y0 = p0.y.round
@@ -24,18 +28,28 @@ module RBGL
24
28
  sx = x0 < x1 ? 1 : -1
25
29
  sy = y0 < y1 ? 1 : -1
26
30
  err = dx + dy
27
- total_dist = Math.sqrt((x1 - x0)**2 + (y1 - y0)**2)
31
+ line_dx = p1.x - p0.x
32
+ line_dy = p1.y - p0.y
33
+ line_length_squared = (line_dx * line_dx) + (line_dy * line_dy)
34
+ inverse_w0 = @interpolator.inverse_clip_w(v0[:position])
35
+ inverse_w1 = @interpolator.inverse_clip_w(v1[:position])
36
+ interpolation_plan = @interpolator.prepare([v0, v1])
37
+ attributes = ShaderIO.new
38
+ fragment_output = ShaderIO.new
28
39
 
29
40
  loop do
30
- t = interpolation_factor(x0, y0, p0, total_dist)
31
- corrected_weights = @interpolator.perspective_correct_weights(
32
- [v0[:position], v1[:position]],
33
- [1.0 - t, t]
34
- )
35
- depth = @interpolator.interpolate_depth([p0.z, p1.z], corrected_weights)
36
- attributes = @interpolator.interpolate([v0, v1], corrected_weights)
41
+ t = interpolation_factor(x0, y0, p0, line_dx, line_dy, line_length_squared)
42
+ corrected_w0 = (1.0 - t) * inverse_w0
43
+ corrected_w1 = t * inverse_w1
44
+ total = corrected_w0 + corrected_w1
45
+ unless total.zero?
46
+ corrected_w0 /= total
47
+ corrected_w1 /= total
48
+ end
49
+ depth = (p0.z * (1.0 - t)) + (p1.z * t)
50
+ @interpolator.interpolate_line(interpolation_plan, corrected_w0, corrected_w1, result: attributes)
37
51
 
38
- shade_fragment(x0, y0, depth, attributes, fragment_shader, uniforms, state)
52
+ shade_fragment(x0, y0, depth, attributes, fragment_shader, uniforms, state, fragment_output)
39
53
  break if x0 == x1 && y0 == y1
40
54
 
41
55
  e2 = 2 * err
@@ -52,13 +66,15 @@ module RBGL
52
66
 
53
67
  private
54
68
 
55
- def interpolation_factor(x, y, start_point, total_dist)
56
- current_dist = Math.sqrt((x - start_point.x.round)**2 + (y - start_point.y.round)**2)
57
- total_dist.positive? ? current_dist / total_dist : 0.0
69
+ def interpolation_factor(x, y, start_point, line_dx, line_dy, line_length_squared)
70
+ return 0.0 unless line_length_squared.positive?
71
+
72
+ (((x - start_point.x) * line_dx) + ((y - start_point.y) * line_dy))
73
+ .fdiv(line_length_squared).clamp(0.0, 1.0)
58
74
  end
59
75
 
60
- def shade_fragment(x, y, depth, attributes, fragment_shader, uniforms, state)
61
- frag_output = fragment_shader.process(attributes, uniforms)
76
+ def shade_fragment(x, y, depth, attributes, fragment_shader, uniforms, state, fragment_output)
77
+ frag_output = fragment_shader.process(attributes, uniforms, output: fragment_output)
62
78
  @framebuffer.write_pixel(
63
79
  x, y, frag_output[:color], depth,
64
80
  depth_test: state[:depth_test],
@@ -12,14 +12,14 @@ module RBGL
12
12
 
13
13
  def rasterize(vertex, fragment_shader, uniforms, state)
14
14
  point = @viewport_transform.call(vertex[:position])
15
- x = point.x.round
16
- y = point.y.round
15
+ x = point.x.floor
16
+ y = point.y.floor
17
17
  depth = point.z
18
18
  x_offsets, y_offsets = @point_pixel_offsets.call(state[:size])
19
+ frag_output = fragment_shader.process(vertex, uniforms)
19
20
 
20
21
  y_offsets.each do |dy|
21
22
  x_offsets.each do |dx|
22
- frag_output = fragment_shader.process(vertex, uniforms)
23
23
  @framebuffer.write_pixel(
24
24
  x + dx, y + dy, frag_output[:color], depth,
25
25
  depth_test: state[:depth_test],
@@ -22,22 +22,61 @@ module RBGL
22
22
 
23
23
  min_x, max_x, min_y, max_y = bounds(p0, p1, p2)
24
24
  inv_area = 1.0 / area
25
+ inverse_w0 = @interpolator.inverse_clip_w(v0[:position])
26
+ inverse_w1 = @interpolator.inverse_clip_w(v1[:position])
27
+ inverse_w2 = @interpolator.inverse_clip_w(v2[:position])
28
+ interpolation_plan = @interpolator.prepare([v0, v1, v2])
29
+ attributes = ShaderIO.new
30
+ fragment_output = ShaderIO.new
25
31
 
26
- (min_y..max_y).each do |y|
27
- (min_x..max_x).each do |x|
28
- weights = barycentric_weights(p0, p1, p2, x + 0.5, y + 0.5)
29
- next unless inside_triangle?(weights)
32
+ x = min_x + 0.5
33
+ y = min_y + 0.5
34
+ row_w0 = edge_value(p1, p2, x, y)
35
+ row_w1 = edge_value(p2, p0, x, y)
36
+ row_w2 = edge_value(p0, p1, x, y)
37
+ x_steps = [p2.y - p1.y, p0.y - p2.y, p1.y - p0.y]
38
+ y_steps = [p1.x - p2.x, p2.x - p0.x, p0.x - p1.x]
39
+ positive_area = area.positive?
40
+ inclusive_edges = [
41
+ inclusive_edge?(p1, p2, positive_area),
42
+ inclusive_edge?(p2, p0, positive_area),
43
+ inclusive_edge?(p0, p1, positive_area)
44
+ ]
30
45
 
31
- screen_weights = weights.map { |weight| weight * inv_area }
32
- corrected_weights = @interpolator.perspective_correct_weights(
33
- [v0[:position], v1[:position], v2[:position]],
34
- screen_weights
35
- )
36
- depth = @interpolator.interpolate_depth([p0.z, p1.z, p2.z], corrected_weights)
37
- attributes = @interpolator.interpolate([v0, v1, v2], corrected_weights)
46
+ (min_y..max_y).each do |pixel_y|
47
+ w0 = row_w0
48
+ w1 = row_w1
49
+ w2 = row_w2
50
+ (min_x..max_x).each do |pixel_x|
51
+ if inside_triangle?(w0, w1, w2, positive_area, inclusive_edges)
52
+ screen_w0 = w0 * inv_area
53
+ screen_w1 = w1 * inv_area
54
+ screen_w2 = w2 * inv_area
55
+ corrected_w0 = screen_w0 * inverse_w0
56
+ corrected_w1 = screen_w1 * inverse_w1
57
+ corrected_w2 = screen_w2 * inverse_w2
58
+ total = corrected_w0 + corrected_w1 + corrected_w2
59
+ unless total.zero?
60
+ corrected_w0 /= total
61
+ corrected_w1 /= total
62
+ corrected_w2 /= total
63
+ end
64
+ depth = (p0.z * screen_w0) + (p1.z * screen_w1) + (p2.z * screen_w2)
65
+ @interpolator.interpolate_triangle(
66
+ interpolation_plan, corrected_w0, corrected_w1, corrected_w2, result: attributes
67
+ )
38
68
 
39
- shade_fragment(x, y, depth, attributes, fragment_shader, uniforms, state)
69
+ shade_fragment(
70
+ pixel_x, pixel_y, depth, attributes, fragment_shader, uniforms, state, fragment_output
71
+ )
72
+ end
73
+ w0 += x_steps[0]
74
+ w1 += x_steps[1]
75
+ w2 += x_steps[2]
40
76
  end
77
+ row_w0 += y_steps[0]
78
+ row_w1 += y_steps[1]
79
+ row_w2 += y_steps[2]
41
80
  end
42
81
  end
43
82
 
@@ -51,17 +90,27 @@ module RBGL
51
90
  [min_x, max_x, min_y, max_y]
52
91
  end
53
92
 
54
- def barycentric_weights(p0, p1, p2, x, y)
55
- point = Larb::Vec2.new(x, y)
56
- [
57
- @edge_function.call(p1, p2, point),
58
- @edge_function.call(p2, p0, point),
59
- @edge_function.call(p0, p1, point)
60
- ]
93
+ def edge_value(a, b, x, y)
94
+ ((x - a.x) * (b.y - a.y)) - ((y - a.y) * (b.x - a.x))
95
+ end
96
+
97
+ def inside_triangle?(w0, w1, w2, positive_area, inclusive_edges)
98
+ inside_edge?(w0, positive_area, inclusive_edges[0]) &&
99
+ inside_edge?(w1, positive_area, inclusive_edges[1]) &&
100
+ inside_edge?(w2, positive_area, inclusive_edges[2])
101
+ end
102
+
103
+ def inside_edge?(weight, positive_area, inclusive)
104
+ positive_area ? weight.positive? || (weight.zero? && inclusive) :
105
+ weight.negative? || (weight.zero? && inclusive)
61
106
  end
62
107
 
63
- def inside_triangle?(weights)
64
- weights.all? { |weight| weight >= 0 } || weights.all? { |weight| weight <= 0 }
108
+ def inclusive_edge?(start_point, end_point, positive_area)
109
+ dx = end_point.x - start_point.x
110
+ dy = end_point.y - start_point.y
111
+ dx = -dx if positive_area
112
+ dy = -dy if positive_area
113
+ dy.positive? || (dy.zero? && dx.positive?)
65
114
  end
66
115
 
67
116
  def culled?(area, cull_mode)
@@ -72,8 +121,8 @@ module RBGL
72
121
  end
73
122
  end
74
123
 
75
- def shade_fragment(x, y, depth, attributes, fragment_shader, uniforms, state)
76
- frag_output = fragment_shader.process(attributes, uniforms)
124
+ def shade_fragment(x, y, depth, attributes, fragment_shader, uniforms, state, fragment_output)
125
+ frag_output = fragment_shader.process(attributes, uniforms, output: fragment_output)
77
126
  @framebuffer.write_pixel(
78
127
  x, y, frag_output[:color], depth,
79
128
  depth_test: state[:depth_test],