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
|
@@ -76,31 +76,14 @@ module RBGL
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
Larb::Vec3.new(
|
|
79
|
-
(ndc.x + 1) * 0.5 * @viewport[:width] + @viewport[:x],
|
|
80
|
-
(1 - ndc.y) * 0.5 * @viewport[:height] + @viewport[:y],
|
|
79
|
+
((ndc.x + 1) * 0.5 * @viewport[:width]) + @viewport[:x],
|
|
80
|
+
((1 - ndc.y) * 0.5 * @viewport[:height]) + @viewport[:y],
|
|
81
81
|
(ndc.z + 1) * 0.5
|
|
82
82
|
)
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def edge_function(a, b, c)
|
|
86
|
-
(c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def interpolate_attributes(v0, v1, v2, w0, w1, w2)
|
|
90
|
-
@attribute_interpolator.interpolate([v0, v1, v2], [w0, w1, w2])
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def interpolate_value(a, b, c, w0, w1, w2)
|
|
94
|
-
@attribute_interpolator.interpolate_values([a, b, c], [w0, w1, w2])
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def interpolate_line_attributes(v0, v1, t)
|
|
98
|
-
corrected_weights = perspective_correct_weights([v0[:position], v1[:position]], [1.0 - t, t])
|
|
99
|
-
@attribute_interpolator.interpolate([v0, v1], corrected_weights)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def perspective_correct_weights(positions, weights)
|
|
103
|
-
@attribute_interpolator.perspective_correct_weights(positions, weights)
|
|
86
|
+
((c.x - a.x) * (b.y - a.y)) - ((c.y - a.y) * (b.x - a.x))
|
|
104
87
|
end
|
|
105
88
|
|
|
106
89
|
def state(cull_mode:, depth_test:, depth_write:, blend_mode:)
|
|
@@ -9,18 +9,14 @@ module RBGL
|
|
|
9
9
|
@process_block = block
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def process(input, uniforms)
|
|
13
|
-
output
|
|
14
|
-
|
|
15
|
-
@uniforms = uniforms
|
|
16
|
-
@output = output
|
|
12
|
+
def process(input, uniforms, output: nil)
|
|
13
|
+
output ||= ShaderIO.new
|
|
14
|
+
output.clear
|
|
17
15
|
|
|
18
16
|
instance_exec(input, uniforms, output, &@process_block)
|
|
19
17
|
finalize_output(output)
|
|
20
18
|
end
|
|
21
19
|
|
|
22
|
-
attr_reader :input, :uniforms, :output
|
|
23
|
-
|
|
24
20
|
def self.create(&block)
|
|
25
21
|
new(&block)
|
|
26
22
|
end
|
|
@@ -53,25 +53,26 @@ module RBGL
|
|
|
53
53
|
|
|
54
54
|
def refract(v, n, eta)
|
|
55
55
|
cos_i = -dot(n, v)
|
|
56
|
-
sin_t2 = eta * eta * (1.0 - cos_i * cos_i)
|
|
57
|
-
return
|
|
56
|
+
sin_t2 = eta * eta * (1.0 - (cos_i * cos_i))
|
|
57
|
+
return v * 0 if sin_t2 > 1.0
|
|
58
58
|
|
|
59
59
|
cos_t = Math.sqrt(1.0 - sin_t2)
|
|
60
|
-
v * eta + n * (eta * cos_i - cos_t)
|
|
60
|
+
(v * eta) + (n * ((eta * cos_i) - cos_t))
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def mix(a, b, t)
|
|
64
64
|
case a
|
|
65
|
-
when Numeric then a + (b - a) * t
|
|
66
|
-
when Larb::Vec2, Larb::Vec3, Larb::Vec4 then a.lerp(b, t)
|
|
67
|
-
|
|
65
|
+
when Numeric then a + ((b - a) * t)
|
|
66
|
+
when Larb::Vec2, Larb::Vec3, Larb::Vec4, Larb::Color then a.lerp(b, t)
|
|
67
|
+
else
|
|
68
|
+
raise ArgumentError, "Unsupported mix operand: #{a.class}"
|
|
68
69
|
end
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def clamp(v, min_val, max_val)
|
|
72
73
|
case v
|
|
73
74
|
when Numeric then v.clamp(min_val, max_val)
|
|
74
|
-
when Larb::Color then v.clamp
|
|
75
|
+
when Larb::Color then Larb::Color.new(*v.to_a.map { |component| component.clamp(min_val, max_val) })
|
|
75
76
|
else
|
|
76
77
|
component_map(v) { |component| component.clamp(min_val, max_val) }
|
|
77
78
|
end
|
|
@@ -83,8 +84,10 @@ module RBGL
|
|
|
83
84
|
|
|
84
85
|
def smoothstep(edge0, edge1, x)
|
|
85
86
|
component_ternary_map(edge0, edge1, x) do |e0, e1, value|
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
next value < e0 ? 0.0 : 1.0 if e0 == e1
|
|
88
|
+
|
|
89
|
+
t = (value - e0).fdiv(e1 - e0).clamp(0.0, 1.0)
|
|
90
|
+
t * t * (3.0 - (2.0 * t))
|
|
88
91
|
end
|
|
89
92
|
end
|
|
90
93
|
|
|
@@ -97,7 +100,7 @@ module RBGL
|
|
|
97
100
|
end
|
|
98
101
|
|
|
99
102
|
def mod(x, y)
|
|
100
|
-
component_binary_map(x, y) { |left, right| left - right * (left / right).floor }
|
|
103
|
+
component_binary_map(x, y) { |left, right| left - (right * (left / right).floor) }
|
|
101
104
|
end
|
|
102
105
|
|
|
103
106
|
def abs(x)
|
|
@@ -5,21 +5,23 @@ module RBGL
|
|
|
5
5
|
class DynamicData
|
|
6
6
|
def initialize(data = {})
|
|
7
7
|
@data = {}
|
|
8
|
+
@writer_keys = {}
|
|
8
9
|
data.to_h.each { |key, value| self[key] = value }
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def method_missing(name, *args)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
key = normalize_key(name)
|
|
14
|
+
if args.empty? && @data.key?(key)
|
|
15
|
+
@data[key]
|
|
16
|
+
elsif (writer = writer_key(name))
|
|
17
|
+
self[writer] = args.first
|
|
16
18
|
else
|
|
17
19
|
super
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def respond_to_missing?(name, include_private = false)
|
|
22
|
-
|
|
24
|
+
@data.key?(normalize_key(name)) || !writer_key(name).nil? || super
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def [](key)
|
|
@@ -38,18 +40,24 @@ module RBGL
|
|
|
38
40
|
@data.keys
|
|
39
41
|
end
|
|
40
42
|
|
|
43
|
+
def clear
|
|
44
|
+
@data.clear
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
41
48
|
private
|
|
42
49
|
|
|
43
50
|
def normalize_key(key)
|
|
44
|
-
key.
|
|
45
|
-
end
|
|
51
|
+
return key if key.is_a?(Symbol)
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
name.to_s.end_with?("=")
|
|
53
|
+
key.to_s.chomp("=").to_sym
|
|
49
54
|
end
|
|
50
55
|
|
|
51
56
|
def writer_key(name)
|
|
52
|
-
name.
|
|
57
|
+
return @writer_keys[name] if @writer_keys.key?(name)
|
|
58
|
+
|
|
59
|
+
text = name.to_s
|
|
60
|
+
@writer_keys[name] = text.end_with?("=") ? text.chomp("=").to_sym : nil
|
|
53
61
|
end
|
|
54
62
|
end
|
|
55
63
|
|
data/lib/rbgl/engine/texture.rb
CHANGED
|
@@ -7,7 +7,7 @@ module RBGL
|
|
|
7
7
|
WRAP_MODES = %i[repeat clamp mirror].freeze
|
|
8
8
|
FILTER_MODES = %i[nearest linear].freeze
|
|
9
9
|
|
|
10
|
-
attr_reader :width, :height, :
|
|
10
|
+
attr_reader :width, :height, :wrap_s, :wrap_t, :filter_min, :filter_mag
|
|
11
11
|
|
|
12
12
|
WRAP_REPEAT = :repeat
|
|
13
13
|
WRAP_CLAMP = :clamp
|
|
@@ -19,7 +19,7 @@ module RBGL
|
|
|
19
19
|
def initialize(width, height, data = nil)
|
|
20
20
|
@width = width
|
|
21
21
|
@height = height
|
|
22
|
-
@data = data ? normalize_data!(data) : Array.new(width * height
|
|
22
|
+
@data = data ? normalize_data!(data) : Array.new(width * height, immutable_color(Larb::Color.black))
|
|
23
23
|
@levels = [@data]
|
|
24
24
|
@mipmaps_dirty = true
|
|
25
25
|
@wrap_s = WRAP_REPEAT
|
|
@@ -29,16 +29,20 @@ module RBGL
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def sample(u, v, lod: 0)
|
|
32
|
-
u = wrap_coord(u, @wrap_s)
|
|
33
|
-
v = wrap_coord(v, @wrap_t)
|
|
34
32
|
level = mip_level_for(lod)
|
|
35
33
|
level_width = width_for_level(level)
|
|
36
34
|
level_height = height_for_level(level)
|
|
35
|
+
filter = filter_for_lod(lod)
|
|
36
|
+
texel_offset = filter == FILTER_LINEAR ? -0.5 : 0.0
|
|
37
37
|
|
|
38
|
-
x = u *
|
|
39
|
-
y = v *
|
|
38
|
+
x = (u.to_f * level_width) + texel_offset
|
|
39
|
+
y = (v.to_f * level_height) + texel_offset
|
|
40
40
|
|
|
41
|
-
sample_with_filter(level, x, y,
|
|
41
|
+
sample_with_filter(level, x, y, filter)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def data
|
|
45
|
+
@data.dup.freeze
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
def get_pixel(x, y)
|
|
@@ -48,7 +52,7 @@ module RBGL
|
|
|
48
52
|
def set_pixel(x, y, color)
|
|
49
53
|
return if x < 0 || x >= @width || y < 0 || y >= @height
|
|
50
54
|
|
|
51
|
-
@data[y.to_i * @width + x.to_i] =
|
|
55
|
+
@data[(y.to_i * @width) + x.to_i] = immutable_color(color)
|
|
52
56
|
@mipmaps_dirty = true
|
|
53
57
|
end
|
|
54
58
|
|
|
@@ -86,14 +90,15 @@ module RBGL
|
|
|
86
90
|
height.times do |y|
|
|
87
91
|
width.times do |x|
|
|
88
92
|
checker = ((x / size) + (y / size)) % 2
|
|
89
|
-
data[y * width + x] = checker == 0 ? color1 : color2
|
|
93
|
+
data[(y * width) + x] = checker == 0 ? color1 : color2
|
|
90
94
|
end
|
|
91
95
|
end
|
|
92
96
|
new(width, height, data)
|
|
93
97
|
end
|
|
94
98
|
|
|
95
99
|
def self.solid(width, height, color)
|
|
96
|
-
|
|
100
|
+
immutable = ImmutableColor.from(color)
|
|
101
|
+
new(width, height, Array.new(width * height, immutable))
|
|
97
102
|
end
|
|
98
103
|
|
|
99
104
|
def self.parse_ppm_header(content)
|
|
@@ -112,7 +117,7 @@ module RBGL
|
|
|
112
117
|
def self.parse_ppm_samples(content, format, pixel_count, max_val, body_offset)
|
|
113
118
|
case format
|
|
114
119
|
when "P3"
|
|
115
|
-
parse_p3_samples(content.byteslice(body_offset..), pixel_count)
|
|
120
|
+
parse_p3_samples(content.byteslice(body_offset..), pixel_count, max_val)
|
|
116
121
|
when "P6"
|
|
117
122
|
parse_p6_samples(content, body_offset, pixel_count, max_val)
|
|
118
123
|
else
|
|
@@ -120,12 +125,19 @@ module RBGL
|
|
|
120
125
|
end
|
|
121
126
|
end
|
|
122
127
|
|
|
123
|
-
def self.parse_p3_samples(body, pixel_count)
|
|
124
|
-
|
|
128
|
+
def self.parse_p3_samples(body, pixel_count, max_val)
|
|
129
|
+
tokens = body.to_s.gsub(/#[^\n]*/, " ").split
|
|
125
130
|
expected_count = pixel_count * 3
|
|
126
|
-
raise ArgumentError, "PPM pixel data is truncated" if
|
|
131
|
+
raise ArgumentError, "PPM pixel data is truncated" if tokens.length < expected_count
|
|
132
|
+
|
|
133
|
+
tokens.first(expected_count).map do |token|
|
|
134
|
+
raise ArgumentError, "Invalid PPM sample: #{token}" unless /\A\d+\z/.match?(token)
|
|
127
135
|
|
|
128
|
-
|
|
136
|
+
value = token.to_i
|
|
137
|
+
raise ArgumentError, "PPM sample exceeds max value: #{value}" if value > max_val
|
|
138
|
+
|
|
139
|
+
value
|
|
140
|
+
end
|
|
129
141
|
end
|
|
130
142
|
|
|
131
143
|
def self.parse_p6_samples(content, body_offset, pixel_count, max_val)
|
|
@@ -162,7 +174,7 @@ module RBGL
|
|
|
162
174
|
raise ArgumentError, "Texture data size mismatch: expected #{expected_size}, got #{normalized.size}"
|
|
163
175
|
end
|
|
164
176
|
|
|
165
|
-
normalized.map { |pixel|
|
|
177
|
+
normalized.map { |pixel| immutable_color(pixel) }
|
|
166
178
|
end
|
|
167
179
|
|
|
168
180
|
def validate_color!(color)
|
|
@@ -171,16 +183,8 @@ module RBGL
|
|
|
171
183
|
raise ArgumentError, "Texture data must contain only Larb::Color values"
|
|
172
184
|
end
|
|
173
185
|
|
|
174
|
-
def
|
|
175
|
-
|
|
176
|
-
when WRAP_REPEAT
|
|
177
|
-
coord - coord.floor
|
|
178
|
-
when WRAP_CLAMP
|
|
179
|
-
coord.clamp(0.0, 1.0)
|
|
180
|
-
when WRAP_MIRROR
|
|
181
|
-
t = coord - coord.floor
|
|
182
|
-
coord.floor.to_i.even? ? t : 1.0 - t
|
|
183
|
-
end
|
|
186
|
+
def immutable_color(color)
|
|
187
|
+
ImmutableColor.from(validate_color!(color))
|
|
184
188
|
end
|
|
185
189
|
|
|
186
190
|
def validate_wrap_mode!(mode)
|
|
@@ -198,7 +202,7 @@ module RBGL
|
|
|
198
202
|
end
|
|
199
203
|
|
|
200
204
|
def sample_nearest(level, x, y)
|
|
201
|
-
pixel_for_level(level, x.
|
|
205
|
+
pixel_for_level(level, x.floor, y.floor, wrap_s: @wrap_s, wrap_t: @wrap_t)
|
|
202
206
|
end
|
|
203
207
|
|
|
204
208
|
def filter_for_lod(lod)
|
|
@@ -224,10 +228,10 @@ module RBGL
|
|
|
224
228
|
fx = x - x0
|
|
225
229
|
fy = y - y0
|
|
226
230
|
|
|
227
|
-
c00 = pixel_for_level(level, x0, y0)
|
|
228
|
-
c10 = pixel_for_level(level, x1, y0)
|
|
229
|
-
c01 = pixel_for_level(level, x0, y1)
|
|
230
|
-
c11 = pixel_for_level(level, x1, y1)
|
|
231
|
+
c00 = pixel_for_level(level, x0, y0, wrap_s: @wrap_s, wrap_t: @wrap_t)
|
|
232
|
+
c10 = pixel_for_level(level, x1, y0, wrap_s: @wrap_s, wrap_t: @wrap_t)
|
|
233
|
+
c01 = pixel_for_level(level, x0, y1, wrap_s: @wrap_s, wrap_t: @wrap_t)
|
|
234
|
+
c11 = pixel_for_level(level, x1, y1, wrap_s: @wrap_s, wrap_t: @wrap_t)
|
|
231
235
|
|
|
232
236
|
c0 = c00.lerp(c10, fx)
|
|
233
237
|
c1 = c01.lerp(c11, fx)
|
|
@@ -241,13 +245,25 @@ module RBGL
|
|
|
241
245
|
[lod.floor, @levels.size - 1].min
|
|
242
246
|
end
|
|
243
247
|
|
|
244
|
-
def pixel_for_level(level, x, y)
|
|
248
|
+
def pixel_for_level(level, x, y, wrap_s: WRAP_CLAMP, wrap_t: WRAP_CLAMP)
|
|
245
249
|
level_width = width_for_level(level)
|
|
246
250
|
level_height = height_for_level(level)
|
|
247
251
|
level_data = level.zero? ? @data : level_data(level)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
level_data[
|
|
252
|
+
sample_x = wrap_index(x.to_i, level_width, wrap_s)
|
|
253
|
+
sample_y = wrap_index(y.to_i, level_height, wrap_t)
|
|
254
|
+
level_data[(sample_y * level_width) + sample_x]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def wrap_index(index, size, mode)
|
|
258
|
+
case mode
|
|
259
|
+
when WRAP_REPEAT
|
|
260
|
+
index % size
|
|
261
|
+
when WRAP_CLAMP
|
|
262
|
+
index.clamp(0, size - 1)
|
|
263
|
+
when WRAP_MIRROR
|
|
264
|
+
mirrored = index % (size * 2)
|
|
265
|
+
mirrored < size ? mirrored : ((size * 2) - mirrored - 1)
|
|
266
|
+
end
|
|
251
267
|
end
|
|
252
268
|
|
|
253
269
|
def level_data(level)
|
|
@@ -286,28 +302,36 @@ module RBGL
|
|
|
286
302
|
Array.new(target_width * target_height) do |index|
|
|
287
303
|
x = index % target_width
|
|
288
304
|
y = index / target_width
|
|
289
|
-
|
|
305
|
+
start_x = x * source_width.fdiv(target_width)
|
|
306
|
+
end_x = x == target_width - 1 ? source_width.to_f : (x + 1) * source_width.fdiv(target_width)
|
|
307
|
+
start_y = y * source_height.fdiv(target_height)
|
|
308
|
+
end_y = y == target_height - 1 ? source_height.to_f : (y + 1) * source_height.fdiv(target_height)
|
|
309
|
+
average_texel_area(source_data, source_width, start_x, end_x, start_y, end_y)
|
|
290
310
|
end
|
|
291
311
|
end
|
|
292
312
|
|
|
293
|
-
def
|
|
294
|
-
|
|
313
|
+
def average_texel_area(source_data, source_width, start_x, end_x, start_y, end_y)
|
|
314
|
+
red = green = blue = alpha = total_weight = 0.0
|
|
295
315
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
316
|
+
(start_y.floor...end_y.ceil).each do |y|
|
|
317
|
+
y_weight = [end_y, y + 1].min - [start_y, y].max
|
|
318
|
+
(start_x.floor...end_x.ceil).each do |x|
|
|
319
|
+
weight = ([end_x, x + 1].min - [start_x, x].max) * y_weight
|
|
320
|
+
color = source_data[(y * source_width) + x]
|
|
321
|
+
red += color.r * weight
|
|
322
|
+
green += color.g * weight
|
|
323
|
+
blue += color.b * weight
|
|
324
|
+
alpha += color.a * weight
|
|
325
|
+
total_weight += weight
|
|
301
326
|
end
|
|
302
327
|
end
|
|
303
328
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
)
|
|
329
|
+
immutable_color(Larb::Color.new(
|
|
330
|
+
red / total_weight,
|
|
331
|
+
green / total_weight,
|
|
332
|
+
blue / total_weight,
|
|
333
|
+
alpha / total_weight
|
|
334
|
+
))
|
|
311
335
|
end
|
|
312
336
|
end
|
|
313
337
|
end
|
data/lib/rbgl/engine.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "version"
|
|
4
|
+
require_relative "engine/immutable_color"
|
|
3
5
|
require_relative "engine/framebuffer"
|
|
4
6
|
require_relative "engine/buffer"
|
|
5
7
|
require_relative "engine/shader"
|
|
@@ -11,6 +13,6 @@ require_relative "engine/context"
|
|
|
11
13
|
|
|
12
14
|
module RBGL
|
|
13
15
|
module Engine
|
|
14
|
-
VERSION =
|
|
16
|
+
VERSION = RBGL::VERSION
|
|
15
17
|
end
|
|
16
18
|
end
|
data/lib/rbgl/gui/backend.rb
CHANGED
|
@@ -26,7 +26,7 @@ module RBGL
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def poll_events_raw
|
|
29
|
-
|
|
29
|
+
poll_events.filter_map { |event| event.to_h if event.respond_to?(:to_h) }
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def resize(width, height)
|
|
@@ -43,15 +43,20 @@ module RBGL
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def set_pixels(buffer, width, height)
|
|
46
|
-
|
|
46
|
+
bytes = validate_rgba_buffer(buffer, width, height)
|
|
47
|
+
present(Engine::Framebuffer.from_rgba_bytes(width, height, bytes))
|
|
47
48
|
end
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
def validate_rgba_buffer(buffer, width, height)
|
|
53
|
+
bytes = String.try_convert(buffer)
|
|
54
|
+
raise ArgumentError, "Pixel buffer must be a String" unless bytes
|
|
55
|
+
|
|
56
|
+
expected_size = width * height * 4
|
|
57
|
+
return bytes if bytes.bytesize == expected_size
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
nil
|
|
59
|
+
raise ArgumentError, "Pixel buffer size mismatch: expected #{expected_size}, got #{bytes.bytesize}"
|
|
55
60
|
end
|
|
56
61
|
end
|
|
57
62
|
end
|
|
@@ -43,14 +43,18 @@ module RBGL
|
|
|
43
43
|
FileBackend.new(width, height, title, **options)
|
|
44
44
|
when :x11
|
|
45
45
|
require_relative "x11/backend"
|
|
46
|
-
X11::Backend.new(width, height, title, env: env)
|
|
46
|
+
X11::Backend.new(width, height, title, env: env, **options)
|
|
47
47
|
when :wayland
|
|
48
48
|
require_relative "wayland/backend"
|
|
49
|
-
Wayland::Backend.new(width, height, title, env: env)
|
|
49
|
+
Wayland::Backend.new(width, height, title, env: env, **options)
|
|
50
50
|
when :cocoa
|
|
51
51
|
require_relative "cocoa/backend"
|
|
52
|
-
Cocoa::Backend.new(width, height, title, env: env)
|
|
52
|
+
Cocoa::Backend.new(width, height, title, env: env, **options)
|
|
53
53
|
when Backend
|
|
54
|
+
unless options.empty?
|
|
55
|
+
raise ArgumentError, "Backend instances do not accept construction options: #{options.keys.join(', ')}"
|
|
56
|
+
end
|
|
57
|
+
|
|
54
58
|
backend
|
|
55
59
|
else
|
|
56
60
|
raise BackendSelectionError, "Unknown backend: #{backend}"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "key_mapper"
|
|
4
|
+
|
|
3
5
|
module RBGL
|
|
4
6
|
module GUI
|
|
5
7
|
module Cocoa
|
|
@@ -11,7 +13,7 @@ module RBGL
|
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
class Backend < GUI::Backend
|
|
14
|
-
def initialize(width, height, title = "RBGL", env: ENV)
|
|
16
|
+
def initialize(width, height, title = "RBGL", env: ENV, resizable: false, high_dpi: false, **_options)
|
|
15
17
|
@env = env
|
|
16
18
|
unless METACO_AVAILABLE
|
|
17
19
|
raise LoadError, "metaco gem is required for Cocoa backend. Install it with: gem install metaco"
|
|
@@ -19,13 +21,18 @@ module RBGL
|
|
|
19
21
|
|
|
20
22
|
super(width, height, title)
|
|
21
23
|
Metaco.init
|
|
22
|
-
@handle = Metaco.window_create(width, height, title)
|
|
24
|
+
@handle = Metaco.window_create(width, height, title, resizable: resizable, high_dpi: high_dpi)
|
|
25
|
+
@framebuffer_width, @framebuffer_height = Metaco.framebuffer_size(@handle)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def present(framebuffer)
|
|
26
29
|
return false unless @handle
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
bytes = framebuffer.to_rgba_bytes
|
|
32
|
+
width = @framebuffer_width
|
|
33
|
+
height = @framebuffer_height
|
|
34
|
+
bytes = scale_pixels(bytes, framebuffer.width, framebuffer.height, width, height) if [width, height] != [framebuffer.width, framebuffer.height]
|
|
35
|
+
Metaco.set_pixels(@handle, bytes, width, height)
|
|
29
36
|
Metaco.present(@handle)
|
|
30
37
|
true
|
|
31
38
|
end
|
|
@@ -37,14 +44,8 @@ module RBGL
|
|
|
37
44
|
raw_events.filter_map { |event| convert_event(event) }
|
|
38
45
|
end
|
|
39
46
|
|
|
40
|
-
def poll_events_raw
|
|
41
|
-
return [] unless @handle
|
|
42
|
-
|
|
43
|
-
Metaco.poll_events(@handle)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
47
|
def should_close?
|
|
47
|
-
return
|
|
48
|
+
return true unless @handle
|
|
48
49
|
|
|
49
50
|
Metaco.should_close?(@handle)
|
|
50
51
|
end
|
|
@@ -59,10 +60,17 @@ module RBGL
|
|
|
59
60
|
def set_pixels(buffer, width, height)
|
|
60
61
|
return unless @handle
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
bytes = validate_rgba_buffer(buffer, width, height)
|
|
64
|
+
bytes = scale_pixels(bytes, width, height, @framebuffer_width, @framebuffer_height) if [width, height] != [@framebuffer_width, @framebuffer_height]
|
|
65
|
+
Metaco.set_pixels(@handle, bytes, @framebuffer_width, @framebuffer_height)
|
|
63
66
|
Metaco.present(@handle)
|
|
64
67
|
end
|
|
65
68
|
|
|
69
|
+
def resize(width, height)
|
|
70
|
+
super
|
|
71
|
+
@framebuffer_width, @framebuffer_height = Metaco.framebuffer_size(@handle)
|
|
72
|
+
end
|
|
73
|
+
|
|
66
74
|
def metal_available?
|
|
67
75
|
return false unless @handle
|
|
68
76
|
|
|
@@ -80,21 +88,46 @@ module RBGL
|
|
|
80
88
|
|
|
81
89
|
case type
|
|
82
90
|
when :key_press
|
|
83
|
-
Event.new(:key_press, key: raw[:key], char: raw[:char])
|
|
91
|
+
Event.new(:key_press, key: normalize_key(raw[:key], raw[:char]), keycode: raw[:key], char: raw[:char], modifiers: raw[:modifiers] || [])
|
|
84
92
|
when :key_release
|
|
85
|
-
Event.new(:key_release, key: raw[:key])
|
|
93
|
+
Event.new(:key_release, key: normalize_key(raw[:key]), keycode: raw[:key], modifiers: raw[:modifiers] || [])
|
|
86
94
|
when :mouse_press
|
|
87
|
-
Event.new(:mouse_press, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
95
|
+
Event.new(:mouse_press, x: raw[:x], y: mouse_y(raw[:y]), button: raw[:button], modifiers: raw[:modifiers] || [])
|
|
88
96
|
when :mouse_release
|
|
89
|
-
Event.new(:mouse_release, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
97
|
+
Event.new(:mouse_release, x: raw[:x], y: mouse_y(raw[:y]), button: raw[:button], modifiers: raw[:modifiers] || [])
|
|
90
98
|
when :mouse_move
|
|
91
|
-
Event.new(:mouse_move, x: raw[:x], y: raw[:y])
|
|
99
|
+
Event.new(:mouse_move, x: raw[:x], y: mouse_y(raw[:y]), modifiers: raw[:modifiers] || [])
|
|
100
|
+
when :scroll
|
|
101
|
+
Event.new(:scroll, dx: raw[:dx], dy: raw[:dy], modifiers: raw[:modifiers] || [])
|
|
102
|
+
when :focus, :blur
|
|
103
|
+
Event.new(type)
|
|
92
104
|
when :resize
|
|
93
|
-
Event.new(:resize, width: raw[:width], height: raw[:height]
|
|
105
|
+
Event.new(:resize, width: raw[:width], height: raw[:height],
|
|
106
|
+
framebuffer_width: raw[:framebuffer_width], framebuffer_height: raw[:framebuffer_height])
|
|
94
107
|
else
|
|
95
108
|
nil
|
|
96
109
|
end
|
|
97
110
|
end
|
|
111
|
+
|
|
112
|
+
def normalize_key(key, char = nil)
|
|
113
|
+
KeyMapper.key(key, char)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def mouse_y(y)
|
|
117
|
+
@height ? @height - 1 - y : y
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def scale_pixels(bytes, source_width, source_height, target_width, target_height)
|
|
121
|
+
scaled = String.new(capacity: target_width * target_height * 4, encoding: Encoding::BINARY)
|
|
122
|
+
target_height.times do |y|
|
|
123
|
+
source_y = y * source_height / target_height
|
|
124
|
+
target_width.times do |x|
|
|
125
|
+
source_x = x * source_width / target_width
|
|
126
|
+
scaled << bytes.byteslice(((source_y * source_width) + source_x) * 4, 4)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
scaled
|
|
130
|
+
end
|
|
98
131
|
end
|
|
99
132
|
end
|
|
100
133
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
module Cocoa
|
|
6
|
+
module KeyMapper
|
|
7
|
+
KEY_CODES = {
|
|
8
|
+
0 => :a, 1 => :s, 2 => :d, 3 => :f, 4 => :h, 5 => :g, 6 => :z, 7 => :x,
|
|
9
|
+
8 => :c, 9 => :v, 11 => :b, 12 => :q, 13 => :w, 14 => :e, 15 => :r,
|
|
10
|
+
16 => :y, 17 => :t, 18 => :one, 19 => :two, 20 => :three, 21 => :four,
|
|
11
|
+
22 => :six, 23 => :five, 24 => :equal, 25 => :nine, 26 => :seven,
|
|
12
|
+
27 => :minus, 28 => :eight, 29 => :zero, 30 => :right_bracket, 31 => :o,
|
|
13
|
+
32 => :u, 33 => :left_bracket, 34 => :i, 35 => :p, 36 => :enter, 37 => :l,
|
|
14
|
+
38 => :j, 39 => :quote, 40 => :k, 41 => :semicolon, 42 => :backslash,
|
|
15
|
+
43 => :comma, 44 => :slash, 45 => :n, 46 => :m, 47 => :period, 48 => :tab,
|
|
16
|
+
49 => :space, 50 => :grave, 51 => :backspace, 53 => :escape, 54 => :right_meta,
|
|
17
|
+
55 => :left_meta, 56 => :left_shift, 57 => :caps_lock, 58 => :left_alt,
|
|
18
|
+
59 => :left_control, 60 => :right_shift, 61 => :right_alt, 62 => :right_control,
|
|
19
|
+
63 => :function, 64 => :f17, 65 => :keypad_decimal, 67 => :keypad_multiply,
|
|
20
|
+
69 => :keypad_plus, 71 => :keypad_clear, 72 => :volume_up, 73 => :volume_down,
|
|
21
|
+
74 => :mute, 75 => :keypad_divide, 76 => :keypad_enter, 78 => :keypad_minus,
|
|
22
|
+
79 => :f18, 80 => :f19, 81 => :keypad_equal, 82 => :keypad_zero,
|
|
23
|
+
83 => :keypad_one, 84 => :keypad_two, 85 => :keypad_three, 86 => :keypad_four,
|
|
24
|
+
87 => :keypad_five, 88 => :keypad_six, 89 => :keypad_seven,
|
|
25
|
+
91 => :keypad_eight, 92 => :keypad_nine, 96 => :f5, 97 => :f6, 98 => :f7,
|
|
26
|
+
99 => :f3, 100 => :f8, 101 => :f9, 103 => :f11, 105 => :f13,
|
|
27
|
+
106 => :f16, 107 => :f14, 109 => :f10, 111 => :f12, 113 => :f15,
|
|
28
|
+
114 => :help, 115 => :home, 116 => :page_up, 117 => :delete,
|
|
29
|
+
118 => :f4, 119 => :end, 120 => :f2, 121 => :page_down, 122 => :f1,
|
|
30
|
+
123 => :left, 124 => :right, 125 => :down, 126 => :up
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
def key(code, char = nil)
|
|
36
|
+
return code if code.is_a?(Symbol)
|
|
37
|
+
return KEY_CODES.fetch(code, code) if code.is_a?(Integer)
|
|
38
|
+
|
|
39
|
+
text = char.to_s.empty? ? code.to_s : char.to_s
|
|
40
|
+
return :escape if text == "\e" || text.casecmp?("escape")
|
|
41
|
+
return text.downcase.to_sym if text.length == 1
|
|
42
|
+
|
|
43
|
+
code
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/rbgl/gui/event.rb
CHANGED
|
@@ -14,8 +14,10 @@ module RBGL
|
|
|
14
14
|
@data[key]
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def method_missing(name, *args)
|
|
18
|
-
@data.
|
|
17
|
+
def method_missing(name, *args, &block)
|
|
18
|
+
return @data[name] if args.empty? && block.nil? && @data.key?(name)
|
|
19
|
+
|
|
20
|
+
super
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def respond_to_missing?(name, include_private = false)
|