rbgl 1.0.0 → 1.0.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +7 -1
  4. data/README.md +130 -74
  5. data/Rakefile +7 -0
  6. data/bench/renderer_bench.rb +283 -0
  7. data/docs/.nojekyll +1 -0
  8. data/docs/index.html +38 -0
  9. data/examples/array_test.rb +1 -1
  10. data/examples/color_test.rb +1 -1
  11. data/examples/cube_spinning.rb +1 -1
  12. data/examples/gradient.rb +1 -1
  13. data/examples/multi_return_test.rb +1 -1
  14. data/examples/plasma.rb +1 -1
  15. data/examples/sphere_raymarch.rb +1 -1
  16. data/examples/triangle_window.rb +1 -1
  17. data/exe/rbgl +42 -0
  18. data/lib/rbgl/cli/doctor.rb +155 -0
  19. data/lib/rbgl/engine/buffer.rb +49 -8
  20. data/lib/rbgl/engine/clip_space_clipper.rb +23 -3
  21. data/lib/rbgl/engine/context.rb +23 -6
  22. data/lib/rbgl/engine/framebuffer.rb +137 -41
  23. data/lib/rbgl/engine/immutable_color.rb +32 -0
  24. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +100 -10
  25. data/lib/rbgl/engine/rasterizer/line_renderer.rb +30 -14
  26. data/lib/rbgl/engine/rasterizer/point_renderer.rb +3 -3
  27. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +72 -23
  28. data/lib/rbgl/engine/rasterizer.rb +3 -20
  29. data/lib/rbgl/engine/shader/base_shader.rb +3 -7
  30. data/lib/rbgl/engine/shader/builtins.rb +13 -10
  31. data/lib/rbgl/engine/shader/dynamic_data.rb +18 -10
  32. data/lib/rbgl/engine/texture.rb +74 -50
  33. data/lib/rbgl/engine.rb +3 -1
  34. data/lib/rbgl/gui/backend.rb +12 -7
  35. data/lib/rbgl/gui/backend_factory.rb +7 -3
  36. data/lib/rbgl/gui/cocoa/backend.rb +50 -17
  37. data/lib/rbgl/gui/cocoa/key_mapper.rb +48 -0
  38. data/lib/rbgl/gui/event.rb +4 -2
  39. data/lib/rbgl/gui/file_backend/bmp_writer.rb +2 -2
  40. data/lib/rbgl/gui/file_backend/frame_writer.rb +7 -0
  41. data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
  42. data/lib/rbgl/gui/file_backend/ppm_writer.rb +1 -0
  43. data/lib/rbgl/gui/file_backend.rb +24 -6
  44. data/lib/rbgl/gui/wayland/backend.rb +127 -55
  45. data/lib/rbgl/gui/wayland/codec.rb +7 -11
  46. data/lib/rbgl/gui/wayland/connection.rb +137 -20
  47. data/lib/rbgl/gui/wayland/event_dispatcher.rb +103 -12
  48. data/lib/rbgl/gui/wayland/global_binder.rb +6 -4
  49. data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
  50. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +14 -2
  51. data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
  52. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +23 -12
  53. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +15 -0
  54. data/lib/rbgl/gui/wayland/protocol_objects.rb +1 -0
  55. data/lib/rbgl/gui/window/render_loop.rb +71 -18
  56. data/lib/rbgl/gui/window.rb +39 -8
  57. data/lib/rbgl/gui/x11/atom_cache.rb +4 -1
  58. data/lib/rbgl/gui/x11/backend.rb +66 -28
  59. data/lib/rbgl/gui/x11/connection.rb +216 -86
  60. data/lib/rbgl/gui/x11/event_parser.rb +13 -2
  61. data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
  62. data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
  63. data/lib/rbgl/gui/x11/request_encoder.rb +29 -28
  64. data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
  65. data/lib/rbgl/gui/x11/transport.rb +52 -2
  66. data/lib/rbgl/gui/x11/x_authority.rb +107 -0
  67. data/lib/rbgl/gui.rb +2 -5
  68. data/lib/rbgl/version.rb +1 -1
  69. metadata +36 -24
  70. data/examples/chemical_heartbeat.rb +0 -166
  71. data/examples/dark_transit.rb +0 -166
  72. data/examples/fractured_orb.rb +0 -428
  73. data/examples/fractured_orb_rb.rb +0 -598
  74. data/examples/hexagonal_flow.rb +0 -333
  75. data/examples/teapot.rb +0 -362
  76. data/examples/teapot_mcu.rb +0 -344
@@ -3,64 +3,80 @@
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
+ @depth_buffer[(y * @width) + x] = depth
44
59
  end
45
60
 
46
61
  def write_pixel(x, y, color, depth, depth_test: true, depth_write: true, blend_mode: :none)
47
62
  return false if x < 0 || x >= @width || y < 0 || y >= @height
63
+ return false unless depth.is_a?(Numeric) && depth.real? && depth.finite?
48
64
 
49
- idx = y * @width + x
65
+ idx = (y * @width) + x
50
66
  return false if depth_test && depth >= @depth_buffer[idx]
51
67
 
52
- @color_buffer[idx] = blend_color(@color_buffer[idx], color, blend_mode)
68
+ @pixels[idx] = blend_mode == :alpha ? blend_pixel(@pixels[idx], color) : pack_color(color)
53
69
  @depth_buffer[idx] = depth if depth_write
54
70
  true
55
71
  end
56
72
 
57
73
  def clear(color: Larb::Color.black, depth: Float::INFINITY)
58
- @color_buffer.fill(color)
74
+ @pixels.fill(pack_color(color))
59
75
  @depth_buffer.fill(depth)
60
76
  end
61
77
 
62
78
  def clear_color(color)
63
- @color_buffer.fill(color)
79
+ @pixels.fill(pack_color(color))
64
80
  end
65
81
 
66
82
  def clear_depth(depth = Float::INFINITY)
@@ -68,56 +84,136 @@ module RBGL
68
84
  end
69
85
 
70
86
  def to_ppm
71
- ppm = "P3\n#{@width} #{@height}\n255\n"
87
+ ppm = String.new(capacity: @width * @height * 12, encoding: Encoding::BINARY)
88
+ ppm << "P3\n#{@width} #{@height}\n255\n"
72
89
  @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]}"
90
+ @width.times do |x|
91
+ pixel = @pixels[(y * @width) + x]
92
+ ppm << " " unless x.zero?
93
+ ppm << red_byte(pixel).to_s << " " << green_byte(pixel).to_s << " " << blue_byte(pixel).to_s
77
94
  end
78
- ppm += row.join(" ") + "\n"
95
+ ppm << "\n"
79
96
  end
80
97
  ppm
81
98
  end
82
99
 
83
100
  def to_ppm_binary
84
101
  header = "P6\n#{@width} #{@height}\n255\n"
85
- pixels = packed_color_bytes(%i[r g b])
102
+ pixels = packed_color_bytes(:rgb)
86
103
  header + pixels
87
104
  end
88
105
 
89
106
  def to_rgba_bytes
90
- packed_color_bytes(%i[r g b a])
107
+ packed_color_bytes(:rgba)
91
108
  end
92
109
 
93
110
  def to_bgra_bytes
94
- packed_color_bytes(%i[b g r a])
111
+ packed_color_bytes(:bgra)
95
112
  end
96
113
 
97
114
  private
98
115
 
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
116
+ def validate_dimensions!(width, height)
117
+ return if width.is_a?(Integer) && width.positive? && height.is_a?(Integer) && height.positive?
118
+
119
+ raise ArgumentError, "Framebuffer dimensions must be positive integers"
120
+ end
121
+
122
+ def load_rgba_bytes(buffer)
123
+ bytes = String.try_convert(buffer)
124
+ raise ArgumentError, "Pixel buffer must be a String" unless bytes
125
+
126
+ expected_size = @width * @height * 4
127
+ unless bytes.bytesize == expected_size
128
+ raise ArgumentError, "Pixel buffer size mismatch: expected #{expected_size}, got #{bytes.bytesize}"
129
+ end
130
+
131
+ @pixels = Array.new(@width * @height)
132
+ pixel_index = 0
133
+ byte_index = 0
134
+ while pixel_index < @pixels.length
135
+ @pixels[pixel_index] = pack_bytes(
136
+ bytes.getbyte(byte_index),
137
+ bytes.getbyte(byte_index + 1),
138
+ bytes.getbyte(byte_index + 2),
139
+ bytes.getbyte(byte_index + 3)
109
140
  )
110
- else
111
- source
141
+ pixel_index += 1
142
+ byte_index += 4
112
143
  end
113
144
  end
114
145
 
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)
146
+ def blend_pixel(destination, source)
147
+ validate_color!(source)
148
+ alpha = source.a
149
+ inv_alpha = 1.0 - alpha
150
+ pack_bytes(
151
+ color_byte((source.r * alpha) + ((red_byte(destination) / 255.0) * inv_alpha)),
152
+ color_byte((source.g * alpha) + ((green_byte(destination) / 255.0) * inv_alpha)),
153
+ color_byte((source.b * alpha) + ((blue_byte(destination) / 255.0) * inv_alpha)),
154
+ color_byte(alpha + ((alpha_byte(destination) / 255.0) * inv_alpha))
155
+ )
156
+ end
157
+
158
+ def pack_color(color)
159
+ validate_color!(color)
160
+ pack_bytes(color_byte(color.r), color_byte(color.g), color_byte(color.b), color_byte(color.a))
161
+ end
162
+
163
+ def validate_color!(color)
164
+ return if color.is_a?(Larb::Color)
165
+
166
+ raise ArgumentError, "Colors must be Larb::Color values"
167
+ end
168
+
169
+ def packed_color_bytes(format)
170
+ channel_count = format == :rgb ? 3 : 4
171
+ bytes = String.new(capacity: @pixels.length * channel_count, encoding: Encoding::BINARY)
172
+
173
+ @pixels.each do |pixel|
174
+ case format
175
+ when :rgb
176
+ bytes << red_byte(pixel) << green_byte(pixel) << blue_byte(pixel)
177
+ when :rgba
178
+ bytes << red_byte(pixel) << green_byte(pixel) << blue_byte(pixel) << alpha_byte(pixel)
179
+ when :bgra
180
+ bytes << blue_byte(pixel) << green_byte(pixel) << red_byte(pixel) << alpha_byte(pixel)
119
181
  end
120
- end.pack("C*")
182
+ end
183
+ bytes
184
+ end
185
+
186
+ def unpack_color(pixel)
187
+ ImmutableColor.new(
188
+ red_byte(pixel) / 255.0,
189
+ green_byte(pixel) / 255.0,
190
+ blue_byte(pixel) / 255.0,
191
+ alpha_byte(pixel) / 255.0
192
+ ).freeze
193
+ end
194
+
195
+ def pack_bytes(red, green, blue, alpha)
196
+ (alpha << 24) | (red << 16) | (green << 8) | blue
197
+ end
198
+
199
+ def alpha_byte(pixel)
200
+ (pixel >> 24) & 0xFF
201
+ end
202
+
203
+ def red_byte(pixel)
204
+ (pixel >> 16) & 0xFF
205
+ end
206
+
207
+ def green_byte(pixel)
208
+ (pixel >> 8) & 0xFF
209
+ end
210
+
211
+ def blue_byte(pixel)
212
+ pixel & 0xFF
213
+ end
214
+
215
+ def color_byte(value)
216
+ (value * 255).round.clamp(0, 255)
121
217
  end
122
218
  end
123
219
  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],