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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +7 -1
- data/README.md +130 -74
- data/Rakefile +7 -0
- data/bench/renderer_bench.rb +283 -0
- data/docs/.nojekyll +1 -0
- data/docs/index.html +38 -0
- data/examples/array_test.rb +1 -1
- data/examples/color_test.rb +1 -1
- data/examples/cube_spinning.rb +1 -1
- data/examples/gradient.rb +1 -1
- data/examples/multi_return_test.rb +1 -1
- data/examples/plasma.rb +1 -1
- data/examples/sphere_raymarch.rb +1 -1
- data/examples/triangle_window.rb +1 -1
- data/exe/rbgl +42 -0
- data/lib/rbgl/cli/doctor.rb +155 -0
- data/lib/rbgl/engine/buffer.rb +49 -8
- data/lib/rbgl/engine/clip_space_clipper.rb +23 -3
- data/lib/rbgl/engine/context.rb +23 -6
- data/lib/rbgl/engine/framebuffer.rb +137 -41
- data/lib/rbgl/engine/immutable_color.rb +32 -0
- data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +100 -10
- data/lib/rbgl/engine/rasterizer/line_renderer.rb +30 -14
- data/lib/rbgl/engine/rasterizer/point_renderer.rb +3 -3
- data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +72 -23
- data/lib/rbgl/engine/rasterizer.rb +3 -20
- data/lib/rbgl/engine/shader/base_shader.rb +3 -7
- data/lib/rbgl/engine/shader/builtins.rb +13 -10
- data/lib/rbgl/engine/shader/dynamic_data.rb +18 -10
- data/lib/rbgl/engine/texture.rb +74 -50
- data/lib/rbgl/engine.rb +3 -1
- data/lib/rbgl/gui/backend.rb +12 -7
- data/lib/rbgl/gui/backend_factory.rb +7 -3
- data/lib/rbgl/gui/cocoa/backend.rb +50 -17
- data/lib/rbgl/gui/cocoa/key_mapper.rb +48 -0
- data/lib/rbgl/gui/event.rb +4 -2
- data/lib/rbgl/gui/file_backend/bmp_writer.rb +2 -2
- data/lib/rbgl/gui/file_backend/frame_writer.rb +7 -0
- data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
- data/lib/rbgl/gui/file_backend/ppm_writer.rb +1 -0
- data/lib/rbgl/gui/file_backend.rb +24 -6
- data/lib/rbgl/gui/wayland/backend.rb +127 -55
- data/lib/rbgl/gui/wayland/codec.rb +7 -11
- data/lib/rbgl/gui/wayland/connection.rb +137 -20
- data/lib/rbgl/gui/wayland/event_dispatcher.rb +103 -12
- data/lib/rbgl/gui/wayland/global_binder.rb +6 -4
- data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
- data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +14 -2
- data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
- data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +23 -12
- data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +15 -0
- data/lib/rbgl/gui/wayland/protocol_objects.rb +1 -0
- data/lib/rbgl/gui/window/render_loop.rb +71 -18
- data/lib/rbgl/gui/window.rb +39 -8
- data/lib/rbgl/gui/x11/atom_cache.rb +4 -1
- data/lib/rbgl/gui/x11/backend.rb +66 -28
- data/lib/rbgl/gui/x11/connection.rb +216 -86
- data/lib/rbgl/gui/x11/event_parser.rb +13 -2
- data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
- data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
- data/lib/rbgl/gui/x11/request_encoder.rb +29 -28
- data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
- data/lib/rbgl/gui/x11/transport.rb +52 -2
- data/lib/rbgl/gui/x11/x_authority.rb +107 -0
- data/lib/rbgl/gui.rb +2 -5
- data/lib/rbgl/version.rb +1 -1
- metadata +36 -24
- data/examples/chemical_heartbeat.rb +0 -166
- data/examples/dark_transit.rb +0 -166
- data/examples/fractured_orb.rb +0 -428
- data/examples/fractured_orb_rb.rb +0 -598
- data/examples/hexagonal_flow.rb +0 -333
- data/examples/teapot.rb +0 -362
- 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, :
|
|
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
|
-
|
|
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
|
-
@
|
|
19
|
-
@depth_buffer =
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
74
|
+
@pixels.fill(pack_color(color))
|
|
59
75
|
@depth_buffer.fill(depth)
|
|
60
76
|
end
|
|
61
77
|
|
|
62
78
|
def clear_color(color)
|
|
63
|
-
@
|
|
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 =
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"
|
|
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
|
|
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(
|
|
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(
|
|
107
|
+
packed_color_bytes(:rgba)
|
|
91
108
|
end
|
|
92
109
|
|
|
93
110
|
def to_bgra_bytes
|
|
94
|
-
packed_color_bytes(
|
|
111
|
+
packed_color_bytes(:bgra)
|
|
95
112
|
end
|
|
96
113
|
|
|
97
114
|
private
|
|
98
115
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
111
|
-
|
|
141
|
+
pixel_index += 1
|
|
142
|
+
byte_index += 4
|
|
112
143
|
end
|
|
113
144
|
end
|
|
114
145
|
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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
|
|
8
|
+
interpolate_prepared(prepare(vertices), weights, result: ShaderIO.new)
|
|
9
|
+
end
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
def prepare(vertices)
|
|
12
|
+
interpolated_keys(vertices).filter_map do |key|
|
|
11
13
|
values = vertices.map { |vertex| vertex[key] }
|
|
12
|
-
|
|
14
|
+
[key, *values] unless values.any?(&:nil?)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
13
17
|
|
|
14
|
-
|
|
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
|
-
|
|
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,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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,
|
|
56
|
-
|
|
57
|
-
|
|
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.
|
|
16
|
-
y = point.y.
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
64
|
-
|
|
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],
|