rbgl 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codespellignore +4 -0
- data/.rubocop.yml +3 -0
- data/.yamllint +16 -0
- data/CHANGELOG.md +11 -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 +55 -9
- data/lib/rbgl/engine/clip_space_clipper.rb +23 -3
- data/lib/rbgl/engine/context.rb +23 -6
- data/lib/rbgl/engine/framebuffer.rb +146 -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 +38 -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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "input_mapper"
|
|
4
|
+
|
|
3
5
|
module RBGL
|
|
4
6
|
module GUI
|
|
5
7
|
module Wayland
|
|
@@ -13,16 +15,24 @@ module RBGL
|
|
|
13
15
|
object = @connection.object_for(object_id)
|
|
14
16
|
|
|
15
17
|
case object
|
|
18
|
+
when Display
|
|
19
|
+
handle_display_event(opcode, payload)
|
|
16
20
|
when Registry
|
|
17
21
|
handle_registry_event(opcode, payload)
|
|
18
22
|
when Callback
|
|
19
23
|
object.handle_done if opcode == 0
|
|
20
24
|
when WlBuffer
|
|
21
25
|
object.handle_release if opcode == 0
|
|
26
|
+
when Seat
|
|
27
|
+
object.handle_capabilities(payload.unpack1("V")) if opcode == 0
|
|
28
|
+
when Keyboard
|
|
29
|
+
handle_keyboard_event(object, opcode, payload)
|
|
30
|
+
when Pointer
|
|
31
|
+
handle_pointer_event(object, opcode, payload)
|
|
22
32
|
when XdgWmBase
|
|
23
33
|
handle_xdg_wm_base_event(object, opcode, payload)
|
|
24
34
|
when XdgSurface
|
|
25
|
-
|
|
35
|
+
object.handle_configure(payload.unpack1("V")) if opcode == 0
|
|
26
36
|
when XdgToplevel
|
|
27
37
|
handle_xdg_toplevel_event(object_id, opcode, payload)
|
|
28
38
|
end
|
|
@@ -30,14 +40,29 @@ module RBGL
|
|
|
30
40
|
|
|
31
41
|
private
|
|
32
42
|
|
|
33
|
-
def
|
|
34
|
-
|
|
43
|
+
def handle_display_event(opcode, payload)
|
|
44
|
+
case opcode
|
|
45
|
+
when 0
|
|
46
|
+
failed_object, code, length = payload.byteslice(0, 12).unpack("VVV")
|
|
47
|
+
message = payload.byteslice(12, length - 1)
|
|
48
|
+
raise GUI::BackendUnavailable,
|
|
49
|
+
"Wayland protocol error #{code} on object #{failed_object}: #{message}"
|
|
50
|
+
when 1
|
|
51
|
+
@connection.unregister_object(payload.unpack1("V"))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
35
54
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
def handle_registry_event(opcode, payload)
|
|
56
|
+
case opcode
|
|
57
|
+
when 0
|
|
58
|
+
name = payload[0, 4].unpack1("V")
|
|
59
|
+
interface_length = payload[4, 4].unpack1("V")
|
|
60
|
+
interface = payload[8, interface_length - 1]
|
|
61
|
+
version = payload[8 + @codec.pad_length(interface_length), 4].unpack1("V")
|
|
62
|
+
@connection.store_global(interface, name: name, version: version)
|
|
63
|
+
when 1
|
|
64
|
+
@connection.remove_global(payload.unpack1("V"))
|
|
65
|
+
end
|
|
41
66
|
end
|
|
42
67
|
|
|
43
68
|
def handle_xdg_wm_base_event(object, opcode, payload)
|
|
@@ -47,11 +72,77 @@ module RBGL
|
|
|
47
72
|
@connection.flush
|
|
48
73
|
end
|
|
49
74
|
|
|
50
|
-
def
|
|
51
|
-
|
|
75
|
+
def handle_keyboard_event(keyboard, opcode, payload)
|
|
76
|
+
case opcode
|
|
77
|
+
when 0
|
|
78
|
+
format, size = payload.unpack("V2")
|
|
79
|
+
keyboard.handle_keymap(format, size, @connection.consume_received_fd)
|
|
80
|
+
when 1
|
|
81
|
+
keyboard.focus(payload.byteslice(4, 4).unpack1("V"))
|
|
82
|
+
when 2
|
|
83
|
+
keyboard.blur
|
|
84
|
+
when 3
|
|
85
|
+
_serial, _time, keycode, state = payload.unpack("V4")
|
|
86
|
+
event = {
|
|
87
|
+
type: state == 1 ? :key_press : :key_release,
|
|
88
|
+
surface_id: keyboard.focused_surface_id,
|
|
89
|
+
key: InputMapper.key(keycode),
|
|
90
|
+
keycode: keycode
|
|
91
|
+
}
|
|
92
|
+
event[:modifiers] = keyboard.modifiers.dup unless keyboard.modifiers.empty?
|
|
93
|
+
@connection.queue_event(event)
|
|
94
|
+
when 4
|
|
95
|
+
_serial, depressed, latched, locked, group = payload.unpack("V5")
|
|
96
|
+
keyboard.handle_modifiers(
|
|
97
|
+
depressed: depressed,
|
|
98
|
+
latched: latched,
|
|
99
|
+
locked: locked,
|
|
100
|
+
group: group
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
52
104
|
|
|
53
|
-
|
|
54
|
-
|
|
105
|
+
def handle_pointer_event(pointer, opcode, payload)
|
|
106
|
+
case opcode
|
|
107
|
+
when 0
|
|
108
|
+
_serial, surface_id, x, y = payload.unpack("VVl<l<")
|
|
109
|
+
pointer.focus(surface_id)
|
|
110
|
+
queue_pointer_motion(pointer, x, y)
|
|
111
|
+
when 1
|
|
112
|
+
pointer.blur
|
|
113
|
+
when 2
|
|
114
|
+
_time, x, y = payload.unpack("Vl<l<")
|
|
115
|
+
queue_pointer_motion(pointer, x, y)
|
|
116
|
+
when 3
|
|
117
|
+
_serial, _time, button, state = payload.unpack("V4")
|
|
118
|
+
@connection.queue_event(
|
|
119
|
+
type: state == 1 ? :pointer_button_press : :pointer_button_release,
|
|
120
|
+
surface_id: pointer.focused_surface_id,
|
|
121
|
+
button: InputMapper.button(button),
|
|
122
|
+
x: pointer.x,
|
|
123
|
+
y: pointer.y
|
|
124
|
+
)
|
|
125
|
+
when 4
|
|
126
|
+
_time, axis, value = payload.unpack("VVl<")
|
|
127
|
+
@connection.queue_event(
|
|
128
|
+
type: :pointer_axis,
|
|
129
|
+
surface_id: pointer.focused_surface_id,
|
|
130
|
+
axis: { 0 => :vertical, 1 => :horizontal }.fetch(axis, axis),
|
|
131
|
+
value: value / 256.0,
|
|
132
|
+
x: pointer.x,
|
|
133
|
+
y: pointer.y
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def queue_pointer_motion(pointer, fixed_x, fixed_y)
|
|
139
|
+
pointer.move(fixed_x / 256.0, fixed_y / 256.0)
|
|
140
|
+
@connection.queue_event(
|
|
141
|
+
type: :pointer_motion,
|
|
142
|
+
surface_id: pointer.focused_surface_id,
|
|
143
|
+
x: pointer.x,
|
|
144
|
+
y: pointer.y
|
|
145
|
+
)
|
|
55
146
|
end
|
|
56
147
|
|
|
57
148
|
def handle_xdg_toplevel_event(object_id, opcode, payload)
|
|
@@ -9,9 +9,10 @@ module RBGL
|
|
|
9
9
|
BINDINGS = {
|
|
10
10
|
"wl_compositor" => [:compositor, Compositor, 4],
|
|
11
11
|
"wl_shm" => [:shm, Shm, 1],
|
|
12
|
-
"xdg_wm_base" => [:xdg_wm_base, XdgWmBase, 2]
|
|
12
|
+
"xdg_wm_base" => [:xdg_wm_base, XdgWmBase, 2],
|
|
13
|
+
"wl_seat" => [:seat, Seat, 5]
|
|
13
14
|
}.freeze
|
|
14
|
-
REQUIRED_INTERFACES =
|
|
15
|
+
REQUIRED_INTERFACES = %w[wl_compositor wl_shm xdg_wm_base].freeze
|
|
15
16
|
|
|
16
17
|
def initialize(connection)
|
|
17
18
|
@connection = connection
|
|
@@ -25,8 +26,9 @@ module RBGL
|
|
|
25
26
|
global = globals[interface]
|
|
26
27
|
next unless global
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
version = [global[:version], max_version].min
|
|
30
|
+
object_id = registry.bind(global[:name], interface, version)
|
|
31
|
+
bound[slot] = @connection.register_object(klass.new(@connection, object_id, version: version))
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
module Wayland
|
|
6
|
+
module InputMapper
|
|
7
|
+
KEY_NAMES = {
|
|
8
|
+
1 => :escape,
|
|
9
|
+
14 => :backspace,
|
|
10
|
+
15 => :tab,
|
|
11
|
+
28 => :enter,
|
|
12
|
+
29 => :left_control,
|
|
13
|
+
42 => :left_shift,
|
|
14
|
+
54 => :right_shift,
|
|
15
|
+
56 => :left_alt,
|
|
16
|
+
57 => :space,
|
|
17
|
+
58 => :caps_lock,
|
|
18
|
+
87 => :f11,
|
|
19
|
+
88 => :f12,
|
|
20
|
+
97 => :right_control,
|
|
21
|
+
100 => :right_alt,
|
|
22
|
+
102 => :home,
|
|
23
|
+
103 => :up,
|
|
24
|
+
104 => :page_up,
|
|
25
|
+
105 => :left,
|
|
26
|
+
106 => :right,
|
|
27
|
+
107 => :end,
|
|
28
|
+
108 => :down,
|
|
29
|
+
109 => :page_down,
|
|
30
|
+
110 => :insert,
|
|
31
|
+
111 => :delete,
|
|
32
|
+
125 => :left_meta,
|
|
33
|
+
126 => :right_meta
|
|
34
|
+
}.merge(
|
|
35
|
+
(2..10).zip(%i[one two three four five six seven eight nine]).to_h,
|
|
36
|
+
{ 11 => :zero },
|
|
37
|
+
(16..25).zip(%i[q w e r t y u i o p]).to_h,
|
|
38
|
+
(30..38).zip(%i[a s d f g h j k l]).to_h,
|
|
39
|
+
(44..50).zip(%i[z x c v b n m]).to_h,
|
|
40
|
+
(59..68).zip(%i[f1 f2 f3 f4 f5 f6 f7 f8 f9 f10]).to_h
|
|
41
|
+
).freeze
|
|
42
|
+
|
|
43
|
+
POINTER_BUTTONS = {
|
|
44
|
+
0x110 => 1,
|
|
45
|
+
0x111 => 3,
|
|
46
|
+
0x112 => 2
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
module_function
|
|
50
|
+
|
|
51
|
+
def key(code)
|
|
52
|
+
KEY_NAMES.fetch(code, code)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def button(code)
|
|
56
|
+
POINTER_BUTTONS.fetch(code, code)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -35,16 +35,28 @@ module RBGL
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
class WaylandObject
|
|
38
|
-
attr_reader :id, :connection
|
|
38
|
+
attr_reader :id, :connection, :version
|
|
39
39
|
|
|
40
|
-
def initialize(connection, id)
|
|
40
|
+
def initialize(connection, id, version: 1)
|
|
41
41
|
@connection = connection
|
|
42
42
|
@id = id
|
|
43
|
+
@version = version
|
|
44
|
+
@destroyed = false
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def send_request(opcode, *args)
|
|
46
48
|
@connection.send_request(@id, opcode, *args)
|
|
47
49
|
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def release(opcode, since: 1)
|
|
54
|
+
return if @destroyed
|
|
55
|
+
|
|
56
|
+
send_request(opcode) if @version >= since
|
|
57
|
+
@connection.unregister_object(@id) if @connection.respond_to?(:unregister_object)
|
|
58
|
+
@destroyed = true
|
|
59
|
+
end
|
|
48
60
|
end
|
|
49
61
|
end
|
|
50
62
|
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
module Wayland
|
|
6
|
+
class Seat < WaylandObject
|
|
7
|
+
POINTER_CAPABILITY = 1
|
|
8
|
+
KEYBOARD_CAPABILITY = 2
|
|
9
|
+
|
|
10
|
+
attr_reader :keyboard, :pointer
|
|
11
|
+
|
|
12
|
+
def handle_capabilities(capabilities)
|
|
13
|
+
update_pointer(capabilities & POINTER_CAPABILITY != 0)
|
|
14
|
+
update_keyboard(capabilities & KEYBOARD_CAPABILITY != 0)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def destroy
|
|
18
|
+
@pointer&.destroy
|
|
19
|
+
@keyboard&.destroy
|
|
20
|
+
release(3, since: 5)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def update_pointer(available)
|
|
26
|
+
@pointer ||= create_pointer if available
|
|
27
|
+
return if available || !@pointer
|
|
28
|
+
|
|
29
|
+
@pointer.destroy
|
|
30
|
+
@pointer = nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def update_keyboard(available)
|
|
34
|
+
@keyboard ||= create_keyboard if available
|
|
35
|
+
return if available || !@keyboard
|
|
36
|
+
|
|
37
|
+
@keyboard.destroy
|
|
38
|
+
@keyboard = nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create_pointer
|
|
42
|
+
object_id = @connection.allocate_id
|
|
43
|
+
send_request(0, Arguments.new_id(object_id))
|
|
44
|
+
@connection.register_object(Pointer.new(@connection, object_id, version: @version))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def create_keyboard
|
|
48
|
+
object_id = @connection.allocate_id
|
|
49
|
+
send_request(1, Arguments.new_id(object_id))
|
|
50
|
+
@connection.register_object(Keyboard.new(@connection, object_id, version: @version))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class Keyboard < WaylandObject
|
|
55
|
+
attr_reader :focused_surface_id, :keymap, :keymap_format, :modifiers
|
|
56
|
+
|
|
57
|
+
def initialize(...)
|
|
58
|
+
super
|
|
59
|
+
@modifiers = {}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def focus(surface_id)
|
|
63
|
+
@focused_surface_id = surface_id
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def blur
|
|
67
|
+
@focused_surface_id = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def handle_keymap(format, size, io)
|
|
71
|
+
raise GUI::BackendUnavailable, "Wayland keymap event did not include a file descriptor" unless io
|
|
72
|
+
|
|
73
|
+
@keymap_format = { 0 => :none, 1 => :xkb_v1 }.fetch(format, format)
|
|
74
|
+
@keymap = io.read(size)&.delete_suffix("\x00")
|
|
75
|
+
ensure
|
|
76
|
+
io&.close
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def handle_modifiers(depressed:, latched:, locked:, group:)
|
|
80
|
+
@modifiers = {
|
|
81
|
+
depressed: depressed,
|
|
82
|
+
latched: latched,
|
|
83
|
+
locked: locked,
|
|
84
|
+
group: group
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def destroy
|
|
89
|
+
release(0, since: 3)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class Pointer < WaylandObject
|
|
94
|
+
attr_reader :focused_surface_id, :x, :y
|
|
95
|
+
|
|
96
|
+
def focus(surface_id)
|
|
97
|
+
@focused_surface_id = surface_id
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def blur
|
|
101
|
+
@focused_surface_id = nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def move(x, y)
|
|
105
|
+
@x = x
|
|
106
|
+
@y = y
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def destroy
|
|
110
|
+
release(1, since: 3)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -4,9 +4,9 @@ module RBGL
|
|
|
4
4
|
module GUI
|
|
5
5
|
module Wayland
|
|
6
6
|
class Shm < WaylandObject
|
|
7
|
-
def create_pool(
|
|
7
|
+
def create_pool(file, size)
|
|
8
8
|
pool_id = @connection.allocate_id
|
|
9
|
-
@connection.send_request_with_fd(@id, 0, Arguments.new_id(pool_id), Arguments.int(size),
|
|
9
|
+
@connection.send_request_with_fd(@id, 0, Arguments.new_id(pool_id), Arguments.int(size), file)
|
|
10
10
|
@connection.register_object(ShmPool.new(@connection, pool_id))
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -17,7 +17,8 @@ module RBGL
|
|
|
17
17
|
format_val = case format
|
|
18
18
|
when :argb8888 then 0
|
|
19
19
|
when :xrgb8888 then 1
|
|
20
|
-
else
|
|
20
|
+
else
|
|
21
|
+
raise ArgumentError, "Unsupported Wayland SHM format: #{format}"
|
|
21
22
|
end
|
|
22
23
|
send_request(
|
|
23
24
|
0,
|
|
@@ -67,10 +68,10 @@ module RBGL
|
|
|
67
68
|
finalize_destroy if @destroy_requested
|
|
68
69
|
end
|
|
69
70
|
|
|
70
|
-
def destroy
|
|
71
|
-
return if @destroyed || @destroy_requested
|
|
71
|
+
def destroy(force: false)
|
|
72
|
+
return if @destroyed || (@destroy_requested && !force)
|
|
72
73
|
|
|
73
|
-
if @busy
|
|
74
|
+
if @busy && !force
|
|
74
75
|
@destroy_requested = true
|
|
75
76
|
else
|
|
76
77
|
finalize_destroy
|
|
@@ -119,12 +120,19 @@ module RBGL
|
|
|
119
120
|
@wl_buffer.id
|
|
120
121
|
end
|
|
121
122
|
|
|
122
|
-
def destroy
|
|
123
|
+
def destroy(force: false)
|
|
123
124
|
return if @destroyed
|
|
124
125
|
|
|
125
126
|
@destroy_requested = true
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
begin
|
|
128
|
+
force ? @wl_buffer.destroy(force: true) : @wl_buffer.destroy
|
|
129
|
+
ensure
|
|
130
|
+
finalize_destroy if force || !@wl_buffer.busy?
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def destroyed?
|
|
135
|
+
@destroyed
|
|
128
136
|
end
|
|
129
137
|
|
|
130
138
|
private
|
|
@@ -136,9 +144,12 @@ module RBGL
|
|
|
136
144
|
def finalize_destroy
|
|
137
145
|
return if @destroyed
|
|
138
146
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
147
|
+
begin
|
|
148
|
+
@pool.destroy
|
|
149
|
+
ensure
|
|
150
|
+
@file.close unless @file.closed?
|
|
151
|
+
@destroyed = true
|
|
152
|
+
end
|
|
142
153
|
end
|
|
143
154
|
end
|
|
144
155
|
end
|
|
@@ -16,6 +16,15 @@ module RBGL
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
class XdgSurface < WaylandObject
|
|
19
|
+
def initialize(connection, id)
|
|
20
|
+
super
|
|
21
|
+
@configured = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def configured?
|
|
25
|
+
@configured
|
|
26
|
+
end
|
|
27
|
+
|
|
19
28
|
def get_toplevel
|
|
20
29
|
toplevel_id = @connection.allocate_id
|
|
21
30
|
send_request(1, Arguments.new_id(toplevel_id))
|
|
@@ -26,6 +35,12 @@ module RBGL
|
|
|
26
35
|
send_request(4, Arguments.uint(serial))
|
|
27
36
|
end
|
|
28
37
|
|
|
38
|
+
def handle_configure(serial)
|
|
39
|
+
ack_configure(serial)
|
|
40
|
+
@connection.flush
|
|
41
|
+
@configured = true
|
|
42
|
+
end
|
|
43
|
+
|
|
29
44
|
def destroy
|
|
30
45
|
send_request(0)
|
|
31
46
|
end
|
|
@@ -5,39 +5,60 @@ module RBGL
|
|
|
5
5
|
class Window
|
|
6
6
|
class RenderLoop
|
|
7
7
|
DEFAULT_TIME_SOURCE = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
8
|
+
DEFAULT_SLEEPER = ->(duration) { sleep(duration) }
|
|
9
|
+
FPS_WINDOW_SECONDS = 1.0
|
|
8
10
|
|
|
9
11
|
attr_reader :fps, :dropped_frames
|
|
10
12
|
|
|
11
|
-
def initialize(time_source: DEFAULT_TIME_SOURCE)
|
|
13
|
+
def initialize(time_source: DEFAULT_TIME_SOURCE, sleeper: DEFAULT_SLEEPER, target_fps: 60)
|
|
12
14
|
@time_source = time_source
|
|
15
|
+
@sleeper = sleeper
|
|
16
|
+
@frame_interval = target_fps && (1.0 / validate_target_fps(target_fps))
|
|
13
17
|
@fps = 0
|
|
14
18
|
@dropped_frames = 0
|
|
15
19
|
@running = false
|
|
20
|
+
@frame_times = []
|
|
16
21
|
end
|
|
17
22
|
|
|
18
23
|
def run(backend:, context:, process_events:, &frame_callback)
|
|
19
|
-
|
|
20
|
-
start_time = @time_source.call
|
|
21
|
-
last_time = start_time
|
|
22
|
-
frame_count = 0
|
|
24
|
+
start
|
|
23
25
|
|
|
24
26
|
while @running && !backend.should_close?
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
break if stop_requested?(backend)
|
|
31
|
-
frame_callback&.call(context, delta_time)
|
|
32
|
-
break if stop_requested?(backend)
|
|
33
|
-
record_present_result(backend.present(context.framebuffer))
|
|
34
|
-
|
|
35
|
-
frame_count += 1
|
|
36
|
-
elapsed = current_time - start_time
|
|
37
|
-
@fps = frame_count / elapsed if elapsed.positive?
|
|
27
|
+
frame_finished_at = step(backend:, context:, process_events:, &frame_callback)
|
|
28
|
+
break unless frame_finished_at
|
|
29
|
+
|
|
30
|
+
throttle(@next_frame_deadline, frame_finished_at)
|
|
31
|
+
@next_frame_deadline = advance_deadline(@next_frame_deadline, frame_finished_at)
|
|
38
32
|
end
|
|
39
33
|
end
|
|
40
34
|
|
|
35
|
+
def start(now: nil)
|
|
36
|
+
@running = true
|
|
37
|
+
@last_time = now.nil? ? @time_source.call : Float(now)
|
|
38
|
+
@next_frame_deadline = @frame_interval && (@last_time + @frame_interval)
|
|
39
|
+
@frame_times.clear
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def step(backend:, context:, process_events:, now: nil, &frame_callback)
|
|
43
|
+
start(now:) if @last_time.nil?
|
|
44
|
+
return false unless @running
|
|
45
|
+
return false if backend.should_close?
|
|
46
|
+
|
|
47
|
+
frame_started_at = now.nil? ? @time_source.call : Float(now)
|
|
48
|
+
delta_time = frame_started_at - @last_time
|
|
49
|
+
@last_time = frame_started_at
|
|
50
|
+
|
|
51
|
+
process_events.call
|
|
52
|
+
return false if stop_requested?(backend)
|
|
53
|
+
frame_callback&.call(context, delta_time)
|
|
54
|
+
return false if stop_requested?(backend)
|
|
55
|
+
|
|
56
|
+
record_present_result(backend.present(context.framebuffer))
|
|
57
|
+
@last_frame_finished_at = now.nil? ? @time_source.call : frame_started_at
|
|
58
|
+
record_frame(@last_frame_finished_at)
|
|
59
|
+
@last_frame_finished_at
|
|
60
|
+
end
|
|
61
|
+
|
|
41
62
|
def record_present_result(result)
|
|
42
63
|
@dropped_frames += 1 if result == false
|
|
43
64
|
result
|
|
@@ -49,6 +70,38 @@ module RBGL
|
|
|
49
70
|
|
|
50
71
|
private
|
|
51
72
|
|
|
73
|
+
def record_frame(timestamp)
|
|
74
|
+
@frame_times << timestamp
|
|
75
|
+
cutoff = timestamp - FPS_WINDOW_SECONDS
|
|
76
|
+
@frame_times.shift while @frame_times.length > 2 && @frame_times.first < cutoff
|
|
77
|
+
return @fps = 0 if @frame_times.length < 2
|
|
78
|
+
|
|
79
|
+
elapsed = @frame_times.last - @frame_times.first
|
|
80
|
+
@fps = elapsed.positive? ? (@frame_times.length - 1) / elapsed : 0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def throttle(deadline, frame_finished_at)
|
|
84
|
+
return unless deadline
|
|
85
|
+
|
|
86
|
+
remaining = deadline - frame_finished_at
|
|
87
|
+
@sleeper.call(remaining) if remaining.positive?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def advance_deadline(deadline, frame_finished_at)
|
|
91
|
+
return unless deadline
|
|
92
|
+
|
|
93
|
+
[deadline, frame_finished_at].max + @frame_interval
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def validate_target_fps(target_fps)
|
|
97
|
+
fps = Float(target_fps)
|
|
98
|
+
return fps if fps.positive?
|
|
99
|
+
|
|
100
|
+
raise ArgumentError, "target_fps must be positive or nil"
|
|
101
|
+
rescue ArgumentError, TypeError
|
|
102
|
+
raise ArgumentError, "target_fps must be positive or nil"
|
|
103
|
+
end
|
|
104
|
+
|
|
52
105
|
def stop_requested?(backend)
|
|
53
106
|
!@running || backend.should_close?
|
|
54
107
|
end
|