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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +20 -2
- 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 +150 -40
- data/lib/rbgl/engine/clip_space_clipper.rb +163 -0
- data/lib/rbgl/engine/context.rb +133 -63
- data/lib/rbgl/engine/framebuffer.rb +153 -48
- data/lib/rbgl/engine/immutable_color.rb +32 -0
- data/lib/rbgl/engine/pipeline.rb +38 -7
- data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +195 -0
- data/lib/rbgl/engine/rasterizer/line_renderer.rb +88 -0
- data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
- data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +136 -0
- data/lib/rbgl/engine/rasterizer.rb +65 -171
- data/lib/rbgl/engine/shader/base_shader.rb +51 -0
- data/lib/rbgl/engine/shader/builtins.rb +257 -0
- data/lib/rbgl/engine/shader/dynamic_data.rb +73 -0
- data/lib/rbgl/engine/shader.rb +5 -318
- data/lib/rbgl/engine/texture.rb +264 -51
- data/lib/rbgl/engine.rb +4 -1
- data/lib/rbgl/gui/backend.rb +21 -34
- data/lib/rbgl/gui/backend_factory.rb +89 -0
- data/lib/rbgl/gui/cocoa/backend.rb +59 -46
- 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 +63 -0
- data/lib/rbgl/gui/file_backend/frame_writer.rb +35 -0
- data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
- data/lib/rbgl/gui/file_backend/ppm_writer.rb +26 -0
- data/lib/rbgl/gui/file_backend.rb +50 -55
- data/lib/rbgl/gui/wayland/backend.rb +214 -60
- data/lib/rbgl/gui/wayland/codec.rb +50 -0
- data/lib/rbgl/gui/wayland/connection.rb +188 -231
- data/lib/rbgl/gui/wayland/event_dispatcher.rb +165 -0
- data/lib/rbgl/gui/wayland/global_binder.rb +46 -0
- data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
- data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +63 -0
- data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
- data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
- data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +157 -0
- data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
- data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +60 -0
- data/lib/rbgl/gui/wayland/protocol_objects.rb +8 -0
- data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
- data/lib/rbgl/gui/window/render_loop.rb +111 -0
- data/lib/rbgl/gui/window.rb +74 -84
- data/lib/rbgl/gui/x11/atom_cache.rb +29 -0
- data/lib/rbgl/gui/x11/backend.rb +73 -49
- data/lib/rbgl/gui/x11/connection.rb +284 -219
- data/lib/rbgl/gui/x11/event_parser.rb +71 -0
- 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 +155 -0
- data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
- data/lib/rbgl/gui/x11/transport.rb +81 -0
- data/lib/rbgl/gui/x11/x_authority.rb +107 -0
- data/lib/rbgl/gui.rb +3 -5
- data/lib/rbgl/version.rb +1 -1
- metadata +63 -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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class Window
|
|
6
|
+
class RenderLoop
|
|
7
|
+
DEFAULT_TIME_SOURCE = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
8
|
+
DEFAULT_SLEEPER = ->(duration) { sleep(duration) }
|
|
9
|
+
FPS_WINDOW_SECONDS = 1.0
|
|
10
|
+
|
|
11
|
+
attr_reader :fps, :dropped_frames
|
|
12
|
+
|
|
13
|
+
def initialize(time_source: DEFAULT_TIME_SOURCE, sleeper: DEFAULT_SLEEPER, target_fps: 60)
|
|
14
|
+
@time_source = time_source
|
|
15
|
+
@sleeper = sleeper
|
|
16
|
+
@frame_interval = target_fps && (1.0 / validate_target_fps(target_fps))
|
|
17
|
+
@fps = 0
|
|
18
|
+
@dropped_frames = 0
|
|
19
|
+
@running = false
|
|
20
|
+
@frame_times = []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def run(backend:, context:, process_events:, &frame_callback)
|
|
24
|
+
start
|
|
25
|
+
|
|
26
|
+
while @running && !backend.should_close?
|
|
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)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
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
|
+
|
|
62
|
+
def record_present_result(result)
|
|
63
|
+
@dropped_frames += 1 if result == false
|
|
64
|
+
result
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def stop
|
|
68
|
+
@running = false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
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
|
+
|
|
105
|
+
def stop_requested?(backend)
|
|
106
|
+
!@running || backend.should_close?
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/rbgl/gui/window.rb
CHANGED
|
@@ -1,87 +1,72 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "window/render_loop"
|
|
4
|
+
require_relative "window/event_dispatcher"
|
|
5
|
+
|
|
3
6
|
module RBGL
|
|
4
7
|
module GUI
|
|
5
8
|
class Window
|
|
6
9
|
attr_reader :context, :backend, :width, :height
|
|
7
10
|
|
|
8
|
-
def initialize(width:, height:, title: "RBGL", backend: :auto, **options)
|
|
11
|
+
def initialize(width:, height:, title: "RBGL", backend: :auto, target_fps: 60, **options)
|
|
9
12
|
@width = width
|
|
10
13
|
@height = height
|
|
11
14
|
@title = title
|
|
15
|
+
@render_loop = RenderLoop.new(target_fps: target_fps)
|
|
12
16
|
@context = Engine::Context.new(width: width, height: height)
|
|
13
|
-
|
|
14
|
-
@backend = case backend
|
|
15
|
-
when :auto, :native
|
|
16
|
-
detect_backend(width, height, title)
|
|
17
|
-
when :file
|
|
18
|
-
FileBackend.new(width, height, title, **options)
|
|
19
|
-
when :x11
|
|
20
|
-
require_relative "x11/backend"
|
|
21
|
-
X11::Backend.new(width, height, title)
|
|
22
|
-
when :wayland
|
|
23
|
-
require_relative "wayland/backend"
|
|
24
|
-
Wayland::Backend.new(width, height, title)
|
|
25
|
-
when :cocoa
|
|
26
|
-
require_relative "cocoa/backend"
|
|
27
|
-
Cocoa::Backend.new(width, height, title)
|
|
28
|
-
when Backend
|
|
29
|
-
backend
|
|
30
|
-
else
|
|
31
|
-
raise "Unknown backend: #{backend}"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
@running = false
|
|
35
|
-
@frame_callback = nil
|
|
36
|
-
@last_time = Time.now
|
|
37
|
-
@fps = 0
|
|
38
|
-
@frame_count = 0
|
|
17
|
+
@backend = build_backend(backend, width: width, height: height, title: title, **options)
|
|
39
18
|
@event_handlers = Hash.new { |h, k| h[k] = [] }
|
|
19
|
+
@event_dispatcher = EventDispatcher.new(
|
|
20
|
+
backend: @backend,
|
|
21
|
+
event_handlers: @event_handlers,
|
|
22
|
+
on_resize: method(:apply_resize)
|
|
23
|
+
)
|
|
24
|
+
@closed = false
|
|
40
25
|
end
|
|
41
26
|
|
|
42
27
|
def on(event_type, &block)
|
|
28
|
+
validate_event_handler!(event_type, block)
|
|
43
29
|
@event_handlers[event_type] << block
|
|
44
30
|
end
|
|
45
31
|
|
|
46
|
-
def on_key(&block)
|
|
47
|
-
@backend.on_key(&block)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def on_mouse(&block)
|
|
51
|
-
@backend.on_mouse(&block)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
32
|
def run(&frame_callback)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@frame_count += 1
|
|
71
|
-
elapsed = current_time - @start_time
|
|
72
|
-
@fps = @frame_count / elapsed if elapsed > 0
|
|
33
|
+
raise RuntimeError, "Window#run is one-shot and cannot be called after the window is closed" if @closed
|
|
34
|
+
|
|
35
|
+
@render_loop.run(
|
|
36
|
+
backend: @backend,
|
|
37
|
+
context: @context,
|
|
38
|
+
process_events: method(:process_events),
|
|
39
|
+
&frame_callback
|
|
40
|
+
)
|
|
41
|
+
ensure
|
|
42
|
+
original_error = $!
|
|
43
|
+
begin
|
|
44
|
+
close if @backend
|
|
45
|
+
rescue StandardError
|
|
46
|
+
raise unless original_error
|
|
73
47
|
end
|
|
48
|
+
end
|
|
74
49
|
|
|
75
|
-
|
|
50
|
+
# Advances one frame. `now` is seconds on a monotonic timeline.
|
|
51
|
+
def step(now = nil, &frame_callback)
|
|
52
|
+
return false if @closed
|
|
53
|
+
|
|
54
|
+
!!@render_loop.step(
|
|
55
|
+
backend: @backend,
|
|
56
|
+
context: @context,
|
|
57
|
+
process_events: method(:process_events),
|
|
58
|
+
now:,
|
|
59
|
+
&frame_callback
|
|
60
|
+
)
|
|
76
61
|
end
|
|
77
62
|
|
|
78
63
|
def stop
|
|
79
|
-
@
|
|
64
|
+
@render_loop.stop
|
|
80
65
|
end
|
|
81
66
|
|
|
82
67
|
def present_framebuffer(framebuffer = nil)
|
|
83
68
|
fb = framebuffer || @context.framebuffer
|
|
84
|
-
@backend.present(fb)
|
|
69
|
+
@render_loop.record_present_result(@backend.present(fb))
|
|
85
70
|
end
|
|
86
71
|
|
|
87
72
|
def set_pixels(buffer)
|
|
@@ -89,11 +74,11 @@ module RBGL
|
|
|
89
74
|
end
|
|
90
75
|
|
|
91
76
|
def metal_available?
|
|
92
|
-
@backend.metal_available?
|
|
77
|
+
@backend.respond_to?(:metal_available?) && @backend.metal_available?
|
|
93
78
|
end
|
|
94
79
|
|
|
95
80
|
def native_handle
|
|
96
|
-
@backend.native_handle
|
|
81
|
+
@backend.native_handle if @backend.respond_to?(:native_handle)
|
|
97
82
|
end
|
|
98
83
|
|
|
99
84
|
def should_close?
|
|
@@ -101,47 +86,52 @@ module RBGL
|
|
|
101
86
|
end
|
|
102
87
|
|
|
103
88
|
def poll_events_raw
|
|
104
|
-
@backend.poll_events_raw
|
|
89
|
+
events = @backend.poll_events_raw
|
|
90
|
+
Array(events).each do |event|
|
|
91
|
+
next unless event.respond_to?(:[]) && event[:type] == :resize
|
|
92
|
+
|
|
93
|
+
apply_resize(Event.new(:resize, width: event[:width], height: event[:height]))
|
|
94
|
+
end
|
|
95
|
+
events
|
|
105
96
|
end
|
|
106
97
|
|
|
107
98
|
def close
|
|
99
|
+
return if @closed
|
|
100
|
+
|
|
101
|
+
@render_loop.stop
|
|
108
102
|
@backend.close
|
|
103
|
+
@closed = true
|
|
109
104
|
end
|
|
110
105
|
|
|
111
|
-
|
|
106
|
+
def fps
|
|
107
|
+
@render_loop.fps
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def dropped_frames
|
|
111
|
+
@render_loop.dropped_frames
|
|
112
|
+
end
|
|
112
113
|
|
|
113
114
|
private
|
|
114
115
|
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
when /darwin/
|
|
118
|
-
require_relative "cocoa/backend"
|
|
119
|
-
Cocoa::Backend.new(width, height, title)
|
|
120
|
-
when /linux/
|
|
121
|
-
if ENV["WAYLAND_DISPLAY"]
|
|
122
|
-
require_relative "wayland/backend"
|
|
123
|
-
Wayland::Backend.new(width, height, title)
|
|
124
|
-
elsif ENV["DISPLAY"]
|
|
125
|
-
require_relative "x11/backend"
|
|
126
|
-
X11::Backend.new(width, height, title)
|
|
127
|
-
else
|
|
128
|
-
raise "No display server found (DISPLAY or WAYLAND_DISPLAY not set)"
|
|
129
|
-
end
|
|
130
|
-
else
|
|
131
|
-
raise "Unsupported platform: #{RUBY_PLATFORM}"
|
|
132
|
-
end
|
|
116
|
+
def build_backend(backend, width:, height:, title:, **options)
|
|
117
|
+
BackendFactory.build(backend, width: width, height: height, title: title, **options)
|
|
133
118
|
end
|
|
134
119
|
|
|
135
120
|
def process_events
|
|
136
|
-
|
|
121
|
+
@event_dispatcher.process
|
|
122
|
+
end
|
|
137
123
|
|
|
138
|
-
|
|
124
|
+
def apply_resize(event)
|
|
125
|
+
@backend.resize(event.width, event.height)
|
|
126
|
+
@context.resize(width: event.width, height: event.height)
|
|
127
|
+
@width = event.width
|
|
128
|
+
@height = event.height
|
|
129
|
+
end
|
|
139
130
|
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
def validate_event_handler!(event_type, block)
|
|
132
|
+
return if block
|
|
142
133
|
|
|
143
|
-
|
|
144
|
-
end
|
|
134
|
+
raise ArgumentError, "A block is required for #{event_type} handlers"
|
|
145
135
|
end
|
|
146
136
|
end
|
|
147
137
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
module X11
|
|
6
|
+
class AtomCache
|
|
7
|
+
BUILTIN_ATOMS = {
|
|
8
|
+
wm_name: 39,
|
|
9
|
+
string: 31,
|
|
10
|
+
atom: 4
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def initialize(&resolver)
|
|
14
|
+
@resolver = resolver
|
|
15
|
+
@cache = BUILTIN_ATOMS.dup
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fetch(name)
|
|
19
|
+
return name if name.is_a?(Integer)
|
|
20
|
+
return @cache[name] if @cache.key?(name)
|
|
21
|
+
|
|
22
|
+
resolved = @resolver.call(name)
|
|
23
|
+
@cache[name] = resolved
|
|
24
|
+
resolved
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/rbgl/gui/x11/backend.rb
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "connection"
|
|
4
|
+
require_relative "pixel_encoder"
|
|
4
5
|
|
|
5
6
|
module RBGL
|
|
6
7
|
module GUI
|
|
7
8
|
module X11
|
|
8
9
|
class Backend < GUI::Backend
|
|
9
|
-
def initialize(width, height, title = "RBGL")
|
|
10
|
-
super
|
|
11
|
-
@display = Connection.new(
|
|
12
|
-
@
|
|
10
|
+
def initialize(width, height, title = "RBGL", env: ENV, reply_timeout: Connection::DEFAULT_REPLY_TIMEOUT)
|
|
11
|
+
super(width, height, title)
|
|
12
|
+
@display = Connection.new(env["DISPLAY"] || ":0", env: env, reply_timeout: reply_timeout)
|
|
13
|
+
@window = nil
|
|
13
14
|
setup_window(width, height, title)
|
|
15
|
+
rescue StandardError => error
|
|
16
|
+
begin
|
|
17
|
+
close
|
|
18
|
+
rescue StandardError
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
raise error
|
|
14
22
|
end
|
|
15
23
|
|
|
16
24
|
private def setup_window(w, h, t)
|
|
@@ -35,42 +43,54 @@ module RBGL
|
|
|
35
43
|
)
|
|
36
44
|
|
|
37
45
|
@display.change_property(wid, :wm_name, :string, t)
|
|
46
|
+
@display.enable_wm_delete_window(wid)
|
|
38
47
|
@display.map_window(wid)
|
|
39
48
|
@display.flush
|
|
40
49
|
|
|
41
50
|
gc_id = @display.generate_id
|
|
42
51
|
@display.create_gc(gc_id, wid)
|
|
43
52
|
|
|
44
|
-
@
|
|
53
|
+
@window = {
|
|
54
|
+
id: wid,
|
|
45
55
|
width: w,
|
|
46
56
|
height: h,
|
|
47
57
|
gc: gc_id,
|
|
48
58
|
should_close: false
|
|
49
59
|
}
|
|
50
|
-
|
|
51
|
-
@handle = wid
|
|
52
60
|
end
|
|
53
61
|
|
|
54
62
|
def present(framebuffer)
|
|
55
|
-
return unless @
|
|
56
|
-
|
|
57
|
-
window = @windows[@handle]
|
|
58
|
-
return unless window
|
|
63
|
+
return false unless @window
|
|
59
64
|
|
|
60
65
|
buffer = convert_to_x11_format(framebuffer)
|
|
66
|
+
present_bytes(buffer, framebuffer.width, framebuffer.height)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def set_pixels(buffer, width, height)
|
|
70
|
+
return false unless @window
|
|
71
|
+
|
|
72
|
+
bytes = validate_rgba_buffer(buffer, width, height)
|
|
73
|
+
present_bytes(pixel_encoder.encode_rgba(bytes, width, height), width, height)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private def present_bytes(buffer, width, height)
|
|
77
|
+
window = @window
|
|
78
|
+
return false unless window
|
|
61
79
|
|
|
62
80
|
@display.put_image(
|
|
63
81
|
format: :z_pixmap,
|
|
64
|
-
drawable:
|
|
82
|
+
drawable: window[:id],
|
|
65
83
|
gc: window[:gc],
|
|
66
|
-
width:
|
|
67
|
-
height:
|
|
84
|
+
width: width,
|
|
85
|
+
height: height,
|
|
68
86
|
dst_x: 0, dst_y: 0,
|
|
69
87
|
depth: @display.root_depth,
|
|
70
|
-
data: buffer
|
|
88
|
+
data: buffer,
|
|
89
|
+
bytes_per_line: pixel_encoder.bytes_per_line(width)
|
|
71
90
|
)
|
|
72
91
|
|
|
73
92
|
@display.flush
|
|
93
|
+
true
|
|
74
94
|
end
|
|
75
95
|
|
|
76
96
|
def poll_events
|
|
@@ -81,42 +101,60 @@ module RBGL
|
|
|
81
101
|
next unless raw_event
|
|
82
102
|
|
|
83
103
|
event = convert_event(raw_event)
|
|
84
|
-
if event
|
|
85
|
-
events << event
|
|
86
|
-
emit_from_event(event)
|
|
87
|
-
end
|
|
104
|
+
events << event if event
|
|
88
105
|
end
|
|
89
106
|
|
|
90
107
|
events
|
|
91
108
|
end
|
|
92
109
|
|
|
93
110
|
def should_close?
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@windows[@handle]&.[](:should_close) || false
|
|
111
|
+
@window.nil? || @window[:should_close]
|
|
97
112
|
end
|
|
98
113
|
|
|
99
114
|
def close
|
|
100
|
-
|
|
115
|
+
window = @window
|
|
116
|
+
operations = []
|
|
117
|
+
if window
|
|
118
|
+
window[:should_close] = true
|
|
119
|
+
operations << -> { @display.destroy_window(window[:id]) }
|
|
120
|
+
operations << -> { @display.flush }
|
|
121
|
+
end
|
|
122
|
+
operations << -> { @display&.close }
|
|
101
123
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
124
|
+
error = nil
|
|
125
|
+
operations.each do |operation|
|
|
126
|
+
operation.call
|
|
127
|
+
rescue StandardError => cleanup_error
|
|
128
|
+
error ||= cleanup_error
|
|
129
|
+
end
|
|
130
|
+
@window = nil
|
|
131
|
+
raise error if error
|
|
105
132
|
end
|
|
106
133
|
|
|
107
134
|
private
|
|
108
135
|
|
|
109
136
|
def convert_to_x11_format(framebuffer)
|
|
110
|
-
|
|
111
|
-
|
|
137
|
+
pixel_encoder.encode(framebuffer)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def pixel_encoder
|
|
141
|
+
@pixel_encoder ||= PixelEncoder.new(
|
|
142
|
+
bits_per_pixel: @display.bits_per_pixel,
|
|
143
|
+
scanline_pad: @display.scanline_pad,
|
|
144
|
+
visual_class: @display.visual_class,
|
|
145
|
+
red_mask: @display.red_mask,
|
|
146
|
+
green_mask: @display.green_mask,
|
|
147
|
+
blue_mask: @display.blue_mask,
|
|
148
|
+
image_byte_order: @display.image_byte_order
|
|
149
|
+
)
|
|
112
150
|
end
|
|
113
151
|
|
|
114
152
|
def convert_event(raw)
|
|
115
153
|
case raw[:type]
|
|
116
154
|
when :key_press
|
|
117
|
-
Event.new(:key_press, key: raw[:keycode])
|
|
155
|
+
Event.new(:key_press, key: @display.key_for_keycode(raw[:keycode]), keycode: raw[:keycode])
|
|
118
156
|
when :key_release
|
|
119
|
-
Event.new(:key_release, key: raw[:keycode])
|
|
157
|
+
Event.new(:key_release, key: @display.key_for_keycode(raw[:keycode]), keycode: raw[:keycode])
|
|
120
158
|
when :button_press
|
|
121
159
|
Event.new(:mouse_press, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
122
160
|
when :button_release
|
|
@@ -126,30 +164,16 @@ module RBGL
|
|
|
126
164
|
when :configure_notify
|
|
127
165
|
Event.new(:resize, width: raw[:width], height: raw[:height])
|
|
128
166
|
when :client_message
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
return nil unless @window && raw[:window] == @window[:id]
|
|
168
|
+
return nil unless raw[:message_type] == @display.wm_protocols_atom
|
|
169
|
+
return nil unless raw[:data32]&.first == @display.wm_delete_window_atom
|
|
170
|
+
|
|
171
|
+
@window[:should_close] = true
|
|
132
172
|
Event.new(:close)
|
|
133
173
|
else
|
|
134
174
|
nil
|
|
135
175
|
end
|
|
136
176
|
end
|
|
137
|
-
|
|
138
|
-
def emit_from_event(event)
|
|
139
|
-
case event.type
|
|
140
|
-
when :key_press, :key_release
|
|
141
|
-
emit_key(event.key, event.type == :key_press ? :press : :release)
|
|
142
|
-
when :mouse_press, :mouse_release, :mouse_move
|
|
143
|
-
action = case event.type
|
|
144
|
-
when :mouse_press then :press
|
|
145
|
-
when :mouse_release then :release
|
|
146
|
-
else :move
|
|
147
|
-
end
|
|
148
|
-
emit_mouse(event.x, event.y, event[:button], action)
|
|
149
|
-
when :resize
|
|
150
|
-
emit_resize(event.width, event.height)
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
177
|
end
|
|
154
178
|
end
|
|
155
179
|
end
|