rbgl 0.1.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +20 -2
  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 +150 -40
  20. data/lib/rbgl/engine/clip_space_clipper.rb +163 -0
  21. data/lib/rbgl/engine/context.rb +133 -63
  22. data/lib/rbgl/engine/framebuffer.rb +153 -48
  23. data/lib/rbgl/engine/immutable_color.rb +32 -0
  24. data/lib/rbgl/engine/pipeline.rb +38 -7
  25. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +195 -0
  26. data/lib/rbgl/engine/rasterizer/line_renderer.rb +88 -0
  27. data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
  28. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +136 -0
  29. data/lib/rbgl/engine/rasterizer.rb +65 -171
  30. data/lib/rbgl/engine/shader/base_shader.rb +51 -0
  31. data/lib/rbgl/engine/shader/builtins.rb +257 -0
  32. data/lib/rbgl/engine/shader/dynamic_data.rb +73 -0
  33. data/lib/rbgl/engine/shader.rb +5 -318
  34. data/lib/rbgl/engine/texture.rb +264 -51
  35. data/lib/rbgl/engine.rb +4 -1
  36. data/lib/rbgl/gui/backend.rb +21 -34
  37. data/lib/rbgl/gui/backend_factory.rb +89 -0
  38. data/lib/rbgl/gui/cocoa/backend.rb +59 -46
  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 +63 -0
  42. data/lib/rbgl/gui/file_backend/frame_writer.rb +35 -0
  43. data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
  44. data/lib/rbgl/gui/file_backend/ppm_writer.rb +26 -0
  45. data/lib/rbgl/gui/file_backend.rb +50 -55
  46. data/lib/rbgl/gui/wayland/backend.rb +214 -60
  47. data/lib/rbgl/gui/wayland/codec.rb +50 -0
  48. data/lib/rbgl/gui/wayland/connection.rb +188 -231
  49. data/lib/rbgl/gui/wayland/event_dispatcher.rb +165 -0
  50. data/lib/rbgl/gui/wayland/global_binder.rb +46 -0
  51. data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
  52. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +63 -0
  53. data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
  54. data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
  55. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +157 -0
  56. data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
  57. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +60 -0
  58. data/lib/rbgl/gui/wayland/protocol_objects.rb +8 -0
  59. data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
  60. data/lib/rbgl/gui/window/render_loop.rb +111 -0
  61. data/lib/rbgl/gui/window.rb +74 -84
  62. data/lib/rbgl/gui/x11/atom_cache.rb +29 -0
  63. data/lib/rbgl/gui/x11/backend.rb +73 -49
  64. data/lib/rbgl/gui/x11/connection.rb +284 -219
  65. data/lib/rbgl/gui/x11/event_parser.rb +71 -0
  66. data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
  67. data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
  68. data/lib/rbgl/gui/x11/request_encoder.rb +155 -0
  69. data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
  70. data/lib/rbgl/gui/x11/transport.rb +81 -0
  71. data/lib/rbgl/gui/x11/x_authority.rb +107 -0
  72. data/lib/rbgl/gui.rb +3 -5
  73. data/lib/rbgl/version.rb +1 -1
  74. metadata +63 -24
  75. data/examples/chemical_heartbeat.rb +0 -166
  76. data/examples/dark_transit.rb +0 -166
  77. data/examples/fractured_orb.rb +0 -428
  78. data/examples/fractured_orb_rb.rb +0 -598
  79. data/examples/hexagonal_flow.rb +0 -333
  80. data/examples/teapot.rb +0 -362
  81. data/examples/teapot_mcu.rb +0 -344
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module Engine
5
+ class Rasterizer
6
+ class AttributeInterpolator
7
+ def interpolate(vertices, weights)
8
+ interpolate_prepared(prepare(vertices), weights, result: ShaderIO.new)
9
+ end
10
+
11
+ def prepare(vertices)
12
+ interpolated_keys(vertices).filter_map do |key|
13
+ values = vertices.map { |vertex| vertex[key] }
14
+ [key, *values] unless values.any?(&:nil?)
15
+ end
16
+ end
17
+
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)
22
+ end
23
+ result
24
+ end
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
39
+ result
40
+ end
41
+
42
+ def interpolate_values(values, weights)
43
+ first = values.first
44
+
45
+ case first
46
+ when Larb::Vec2
47
+ vector2(values, weights)
48
+ when Larb::Vec3
49
+ vector3(values, weights)
50
+ when Larb::Vec4
51
+ vector4(values, weights)
52
+ when Larb::Color
53
+ color(values, weights)
54
+ when Array
55
+ numeric_arrays?(*values) ? array(values, weights) : first
56
+ when Numeric
57
+ scalar(values, weights)
58
+ else
59
+ first
60
+ end
61
+ end
62
+
63
+ def interpolate_depth(depths, weights)
64
+ scalar(depths, weights)
65
+ end
66
+
67
+ def perspective_correct_weights(positions, weights)
68
+ corrected = positions.zip(weights).map do |position, weight|
69
+ weight * inverse_clip_w(position)
70
+ end
71
+ total = corrected.sum
72
+ return weights if total.zero?
73
+
74
+ corrected.map { |weight| weight / total }
75
+ end
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
+
84
+ private
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
+
142
+ def interpolated_keys(vertices)
143
+ vertices.flat_map { |vertex| vertex.to_h.keys }.uniq - [:position]
144
+ end
145
+
146
+ def scalar(values, weights)
147
+ values.zip(weights).sum { |value, weight| value * weight }
148
+ end
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
+
159
+ def vector2(values, weights)
160
+ Larb::Vec2.new(
161
+ scalar(values.map(&:x), weights),
162
+ scalar(values.map(&:y), weights)
163
+ )
164
+ end
165
+
166
+ def vector3(values, weights)
167
+ Larb::Vec3.new(
168
+ scalar(values.map(&:x), weights),
169
+ scalar(values.map(&:y), weights),
170
+ scalar(values.map(&:z), weights)
171
+ )
172
+ end
173
+
174
+ def vector4(values, weights)
175
+ Larb::Vec4.new(
176
+ scalar(values.map(&:x), weights),
177
+ scalar(values.map(&:y), weights),
178
+ scalar(values.map(&:z), weights),
179
+ scalar(values.map(&:w), weights)
180
+ )
181
+ end
182
+
183
+ def color(values, weights)
184
+ Larb::Color.new(
185
+ scalar(values.map(&:r), weights),
186
+ scalar(values.map(&:g), weights),
187
+ scalar(values.map(&:b), weights),
188
+ scalar(values.map(&:a), weights)
189
+ )
190
+ end
191
+
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module Engine
5
+ class Rasterizer
6
+ class LineRenderer
7
+ def initialize(framebuffer, interpolator:, viewport_transform:)
8
+ @framebuffer = framebuffer
9
+ @interpolator = interpolator
10
+ @viewport_transform = viewport_transform
11
+ end
12
+
13
+ def rasterize(v0, v1, fragment_shader, uniforms, state)
14
+ p0 = @viewport_transform.call(v0[:position])
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
20
+
21
+ x0 = p0.x.round
22
+ y0 = p0.y.round
23
+ x1 = p1.x.round
24
+ y1 = p1.y.round
25
+
26
+ dx = (x1 - x0).abs
27
+ dy = -(y1 - y0).abs
28
+ sx = x0 < x1 ? 1 : -1
29
+ sy = y0 < y1 ? 1 : -1
30
+ err = dx + dy
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
39
+
40
+ loop do
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)
51
+
52
+ shade_fragment(x0, y0, depth, attributes, fragment_shader, uniforms, state, fragment_output)
53
+ break if x0 == x1 && y0 == y1
54
+
55
+ e2 = 2 * err
56
+ if e2 >= dy
57
+ err += dy
58
+ x0 += sx
59
+ end
60
+ if e2 <= dx
61
+ err += dx
62
+ y0 += sy
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+
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)
74
+ end
75
+
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)
78
+ @framebuffer.write_pixel(
79
+ x, y, frag_output[:color], depth,
80
+ depth_test: state[:depth_test],
81
+ depth_write: state[:depth_write],
82
+ blend_mode: state[:blend_mode]
83
+ )
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module Engine
5
+ class Rasterizer
6
+ class PointRenderer
7
+ def initialize(framebuffer, viewport_transform:, point_pixel_offsets:)
8
+ @framebuffer = framebuffer
9
+ @viewport_transform = viewport_transform
10
+ @point_pixel_offsets = point_pixel_offsets
11
+ end
12
+
13
+ def rasterize(vertex, fragment_shader, uniforms, state)
14
+ point = @viewport_transform.call(vertex[:position])
15
+ x = point.x.floor
16
+ y = point.y.floor
17
+ depth = point.z
18
+ x_offsets, y_offsets = @point_pixel_offsets.call(state[:size])
19
+ frag_output = fragment_shader.process(vertex, uniforms)
20
+
21
+ y_offsets.each do |dy|
22
+ x_offsets.each do |dx|
23
+ @framebuffer.write_pixel(
24
+ x + dx, y + dy, frag_output[:color], depth,
25
+ depth_test: state[:depth_test],
26
+ depth_write: state[:depth_write],
27
+ blend_mode: state[:blend_mode]
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module Engine
5
+ class Rasterizer
6
+ class TriangleRenderer
7
+ def initialize(framebuffer, interpolator:, viewport_transform:, edge_function:)
8
+ @framebuffer = framebuffer
9
+ @interpolator = interpolator
10
+ @viewport_transform = viewport_transform
11
+ @edge_function = edge_function
12
+ end
13
+
14
+ def rasterize(v0, v1, v2, fragment_shader, uniforms, state)
15
+ p0 = @viewport_transform.call(v0[:position])
16
+ p1 = @viewport_transform.call(v1[:position])
17
+ p2 = @viewport_transform.call(v2[:position])
18
+
19
+ area = @edge_function.call(p0, p1, p2)
20
+ return if area.abs < 1e-10
21
+ return if culled?(area, state[:cull_mode])
22
+
23
+ min_x, max_x, min_y, max_y = bounds(p0, p1, p2)
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
31
+
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
+ ]
45
+
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
+ )
68
+
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]
76
+ end
77
+ row_w0 += y_steps[0]
78
+ row_w1 += y_steps[1]
79
+ row_w2 += y_steps[2]
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def bounds(p0, p1, p2)
86
+ min_x = [p0.x, p1.x, p2.x].min.floor.clamp(0, @framebuffer.width - 1)
87
+ max_x = [p0.x, p1.x, p2.x].max.ceil.clamp(0, @framebuffer.width - 1)
88
+ min_y = [p0.y, p1.y, p2.y].min.floor.clamp(0, @framebuffer.height - 1)
89
+ max_y = [p0.y, p1.y, p2.y].max.ceil.clamp(0, @framebuffer.height - 1)
90
+ [min_x, max_x, min_y, max_y]
91
+ end
92
+
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)
106
+ end
107
+
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?)
114
+ end
115
+
116
+ def culled?(area, cull_mode)
117
+ case cull_mode
118
+ when :back then area < 0
119
+ when :front then area > 0
120
+ else false
121
+ end
122
+ end
123
+
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)
126
+ @framebuffer.write_pixel(
127
+ x, y, frag_output[:color], depth,
128
+ depth_test: state[:depth_test],
129
+ depth_write: state[:depth_write],
130
+ blend_mode: state[:blend_mode]
131
+ )
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end