rbgl 0.1.0 → 1.0.0
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/CHANGELOG.md +13 -1
- data/lib/rbgl/engine/buffer.rb +102 -33
- data/lib/rbgl/engine/clip_space_clipper.rb +143 -0
- data/lib/rbgl/engine/context.rb +117 -64
- data/lib/rbgl/engine/framebuffer.rb +41 -32
- data/lib/rbgl/engine/pipeline.rb +38 -7
- data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +105 -0
- data/lib/rbgl/engine/rasterizer/line_renderer.rb +72 -0
- data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
- data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +87 -0
- data/lib/rbgl/engine/rasterizer.rb +73 -162
- data/lib/rbgl/engine/shader/base_shader.rb +55 -0
- data/lib/rbgl/engine/shader/builtins.rb +254 -0
- data/lib/rbgl/engine/shader/dynamic_data.rb +65 -0
- data/lib/rbgl/engine/shader.rb +5 -318
- data/lib/rbgl/engine/texture.rb +227 -38
- data/lib/rbgl/engine.rb +1 -0
- data/lib/rbgl/gui/backend.rb +12 -30
- data/lib/rbgl/gui/backend_factory.rb +85 -0
- data/lib/rbgl/gui/cocoa/backend.rb +15 -35
- data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
- data/lib/rbgl/gui/file_backend/frame_writer.rb +28 -0
- data/lib/rbgl/gui/file_backend/ppm_writer.rb +25 -0
- data/lib/rbgl/gui/file_backend.rb +25 -48
- data/lib/rbgl/gui/wayland/backend.rb +108 -26
- data/lib/rbgl/gui/wayland/codec.rb +54 -0
- data/lib/rbgl/gui/wayland/connection.rb +80 -240
- data/lib/rbgl/gui/wayland/event_dispatcher.rb +74 -0
- data/lib/rbgl/gui/wayland/global_binder.rb +44 -0
- data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +51 -0
- data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
- data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +146 -0
- data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
- data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +45 -0
- data/lib/rbgl/gui/wayland/protocol_objects.rb +7 -0
- data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
- data/lib/rbgl/gui/window/render_loop.rb +58 -0
- data/lib/rbgl/gui/window.rb +41 -82
- data/lib/rbgl/gui/x11/atom_cache.rb +26 -0
- data/lib/rbgl/gui/x11/backend.rb +14 -28
- data/lib/rbgl/gui/x11/connection.rb +104 -169
- data/lib/rbgl/gui/x11/event_parser.rb +60 -0
- data/lib/rbgl/gui/x11/request_encoder.rb +154 -0
- data/lib/rbgl/gui/x11/transport.rb +31 -0
- data/lib/rbgl/gui.rb +1 -0
- data/lib/rbgl/version.rb +1 -1
- metadata +31 -4
data/lib/rbgl/gui/backend.rb
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module RBGL
|
|
4
4
|
module GUI
|
|
5
|
+
class BackendUnavailable < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class BackendSelectionError < ArgumentError
|
|
9
|
+
end
|
|
10
|
+
|
|
5
11
|
class Backend
|
|
6
12
|
attr_reader :width, :height, :title
|
|
7
13
|
|
|
8
|
-
def initialize(width, height, title = "RBGL")
|
|
14
|
+
def initialize(width, height, title = "RBGL", **_options)
|
|
9
15
|
@width = width
|
|
10
16
|
@height = height
|
|
11
17
|
@title = title
|
|
12
|
-
@key_callback = nil
|
|
13
|
-
@mouse_callback = nil
|
|
14
|
-
@resize_callback = nil
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
def present(_framebuffer)
|
|
@@ -26,6 +29,11 @@ module RBGL
|
|
|
26
29
|
[]
|
|
27
30
|
end
|
|
28
31
|
|
|
32
|
+
def resize(width, height)
|
|
33
|
+
@width = width
|
|
34
|
+
@height = height
|
|
35
|
+
end
|
|
36
|
+
|
|
29
37
|
def should_close?
|
|
30
38
|
raise NotImplementedError
|
|
31
39
|
end
|
|
@@ -45,32 +53,6 @@ module RBGL
|
|
|
45
53
|
def native_handle
|
|
46
54
|
nil
|
|
47
55
|
end
|
|
48
|
-
|
|
49
|
-
def on_key(&block)
|
|
50
|
-
@key_callback = block
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def on_mouse(&block)
|
|
54
|
-
@mouse_callback = block
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def on_resize(&block)
|
|
58
|
-
@resize_callback = block
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
protected
|
|
62
|
-
|
|
63
|
-
def emit_key(key, action)
|
|
64
|
-
@key_callback&.call(key, action)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def emit_mouse(x, y, button, action)
|
|
68
|
-
@mouse_callback&.call(x, y, button, action)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def emit_resize(width, height)
|
|
72
|
-
@resize_callback&.call(width, height)
|
|
73
|
-
end
|
|
74
56
|
end
|
|
75
57
|
end
|
|
76
58
|
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class BackendFactory
|
|
6
|
+
AUTO_BACKEND_ERRORS = [LoadError, SystemCallError, BackendUnavailable].freeze
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def build(backend, width:, height:, title:, platform: RUBY_PLATFORM, env: ENV, **options)
|
|
10
|
+
case backend
|
|
11
|
+
when :auto
|
|
12
|
+
build_auto_backend(
|
|
13
|
+
width: width,
|
|
14
|
+
height: height,
|
|
15
|
+
title: title,
|
|
16
|
+
platform: platform,
|
|
17
|
+
env: env,
|
|
18
|
+
**options
|
|
19
|
+
)
|
|
20
|
+
else
|
|
21
|
+
build_specific_backend(backend, width: width, height: height, title: title, env: env, **options)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def build_auto_backend(width:, height:, title:, platform:, env:, builder: nil, **options)
|
|
28
|
+
builder ||= method(:build_specific_backend)
|
|
29
|
+
errors = []
|
|
30
|
+
|
|
31
|
+
native_backend_candidates(platform: platform, env: env).each do |candidate|
|
|
32
|
+
return builder.call(candidate, width: width, height: height, title: title, env: env, **options)
|
|
33
|
+
rescue *AUTO_BACKEND_ERRORS => error
|
|
34
|
+
errors << [candidate, error]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
raise_auto_backend_error(errors)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build_specific_backend(backend, width:, height:, title:, env: ENV, **options)
|
|
41
|
+
case backend
|
|
42
|
+
when :file
|
|
43
|
+
FileBackend.new(width, height, title, **options)
|
|
44
|
+
when :x11
|
|
45
|
+
require_relative "x11/backend"
|
|
46
|
+
X11::Backend.new(width, height, title, env: env)
|
|
47
|
+
when :wayland
|
|
48
|
+
require_relative "wayland/backend"
|
|
49
|
+
Wayland::Backend.new(width, height, title, env: env)
|
|
50
|
+
when :cocoa
|
|
51
|
+
require_relative "cocoa/backend"
|
|
52
|
+
Cocoa::Backend.new(width, height, title, env: env)
|
|
53
|
+
when Backend
|
|
54
|
+
backend
|
|
55
|
+
else
|
|
56
|
+
raise BackendSelectionError, "Unknown backend: #{backend}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def raise_auto_backend_error(errors)
|
|
61
|
+
return if errors.empty?
|
|
62
|
+
|
|
63
|
+
details = errors.map { |backend, error| "#{backend}: #{error.class}: #{error.message}" }.join(", ")
|
|
64
|
+
raise BackendUnavailable, "Failed to initialize any native backend (#{details})"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def native_backend_candidates(platform:, env:)
|
|
68
|
+
case platform
|
|
69
|
+
when /darwin/
|
|
70
|
+
[:cocoa]
|
|
71
|
+
when /linux/
|
|
72
|
+
candidates = []
|
|
73
|
+
candidates << :wayland if env["WAYLAND_DISPLAY"]
|
|
74
|
+
candidates << :x11 if env["DISPLAY"]
|
|
75
|
+
return candidates unless candidates.empty?
|
|
76
|
+
|
|
77
|
+
raise BackendUnavailable, "No display server found (DISPLAY or WAYLAND_DISPLAY not set)"
|
|
78
|
+
else
|
|
79
|
+
raise BackendUnavailable, "Unsupported platform: #{platform}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -1,48 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
begin
|
|
4
|
-
require "metaco"
|
|
5
|
-
METACO_AVAILABLE = true
|
|
6
|
-
rescue LoadError
|
|
7
|
-
METACO_AVAILABLE = false
|
|
8
|
-
end
|
|
9
|
-
|
|
10
3
|
module RBGL
|
|
11
4
|
module GUI
|
|
12
5
|
module Cocoa
|
|
6
|
+
METACO_AVAILABLE = begin
|
|
7
|
+
require "metaco"
|
|
8
|
+
true
|
|
9
|
+
rescue LoadError
|
|
10
|
+
false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
13
|
class Backend < GUI::Backend
|
|
14
|
-
def initialize(width, height, title = "RBGL")
|
|
14
|
+
def initialize(width, height, title = "RBGL", env: ENV)
|
|
15
|
+
@env = env
|
|
15
16
|
unless METACO_AVAILABLE
|
|
16
17
|
raise LoadError, "metaco gem is required for Cocoa backend. Install it with: gem install metaco"
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
super
|
|
20
|
+
super(width, height, title)
|
|
20
21
|
Metaco.init
|
|
21
22
|
@handle = Metaco.window_create(width, height, title)
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
def present(framebuffer)
|
|
25
|
-
return unless @handle
|
|
26
|
+
return false unless @handle
|
|
26
27
|
|
|
27
28
|
Metaco.set_pixels(@handle, framebuffer.to_rgba_bytes, framebuffer.width, framebuffer.height)
|
|
28
29
|
Metaco.present(@handle)
|
|
30
|
+
true
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def poll_events
|
|
32
34
|
return [] unless @handle
|
|
33
35
|
|
|
34
36
|
raw_events = Metaco.poll_events(@handle)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
raw_events.each do |e|
|
|
38
|
-
event = convert_event(e)
|
|
39
|
-
if event
|
|
40
|
-
events << event
|
|
41
|
-
emit_from_event(event)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
events
|
|
37
|
+
raw_events.filter_map { |event| convert_event(event) }
|
|
46
38
|
end
|
|
47
39
|
|
|
48
40
|
def poll_events_raw
|
|
@@ -97,24 +89,12 @@ module RBGL
|
|
|
97
89
|
Event.new(:mouse_release, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
98
90
|
when :mouse_move
|
|
99
91
|
Event.new(:mouse_move, x: raw[:x], y: raw[:y])
|
|
92
|
+
when :resize
|
|
93
|
+
Event.new(:resize, width: raw[:width], height: raw[:height])
|
|
100
94
|
else
|
|
101
95
|
nil
|
|
102
96
|
end
|
|
103
97
|
end
|
|
104
|
-
|
|
105
|
-
def emit_from_event(event)
|
|
106
|
-
case event.type
|
|
107
|
-
when :key_press, :key_release
|
|
108
|
-
emit_key(event.key, event.type == :key_press ? :press : :release)
|
|
109
|
-
when :mouse_press, :mouse_release, :mouse_move
|
|
110
|
-
action = case event.type
|
|
111
|
-
when :mouse_press then :press
|
|
112
|
-
when :mouse_release then :release
|
|
113
|
-
else :move
|
|
114
|
-
end
|
|
115
|
-
emit_mouse(event.x, event.y, event[:button], action)
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
98
|
end
|
|
119
99
|
end
|
|
120
100
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class FileBackend < Backend
|
|
6
|
+
class BmpWriter < FrameWriter
|
|
7
|
+
def extension
|
|
8
|
+
"bmp"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def write(filename, framebuffer)
|
|
12
|
+
File.binwrite(filename, encode(framebuffer))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def encode(framebuffer)
|
|
18
|
+
width = framebuffer.width
|
|
19
|
+
height = framebuffer.height
|
|
20
|
+
row_size = ((24 * width + 31) / 32) * 4
|
|
21
|
+
pixel_data_size = row_size * height
|
|
22
|
+
file_size = 54 + pixel_data_size
|
|
23
|
+
|
|
24
|
+
header = [
|
|
25
|
+
0x42, 0x4D,
|
|
26
|
+
file_size,
|
|
27
|
+
0, 0,
|
|
28
|
+
54
|
|
29
|
+
].pack("CCVvvV")
|
|
30
|
+
|
|
31
|
+
dib = [
|
|
32
|
+
40,
|
|
33
|
+
width, height,
|
|
34
|
+
1,
|
|
35
|
+
24,
|
|
36
|
+
0,
|
|
37
|
+
pixel_data_size,
|
|
38
|
+
2835, 2835,
|
|
39
|
+
0, 0
|
|
40
|
+
].pack("VVVvvVVVVVV")
|
|
41
|
+
|
|
42
|
+
header + dib + pixel_rows(framebuffer, width, height, row_size)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def pixel_rows(framebuffer, width, height, row_size)
|
|
46
|
+
pixels = +""
|
|
47
|
+
|
|
48
|
+
(height - 1).downto(0) do |y|
|
|
49
|
+
row = +""
|
|
50
|
+
width.times do |x|
|
|
51
|
+
bytes = framebuffer.get_pixel(x, y).to_bytes
|
|
52
|
+
row << [bytes[2], bytes[1], bytes[0]].pack("CCC")
|
|
53
|
+
end
|
|
54
|
+
row << "\x00" * (row_size - width * 3)
|
|
55
|
+
pixels << row
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
pixels
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class FileBackend < Backend
|
|
6
|
+
class FrameWriter
|
|
7
|
+
def self.build(format, ppm_mode: :ascii)
|
|
8
|
+
case format
|
|
9
|
+
when :ppm
|
|
10
|
+
PpmWriter.new(binary: ppm_mode == :binary)
|
|
11
|
+
when :bmp
|
|
12
|
+
BmpWriter.new
|
|
13
|
+
else
|
|
14
|
+
raise ArgumentError, "Unsupported file backend format: #{format}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extension
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def write(_filename, _framebuffer)
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class FileBackend < Backend
|
|
6
|
+
class PpmWriter < FrameWriter
|
|
7
|
+
def initialize(binary:)
|
|
8
|
+
@binary = binary
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def extension
|
|
12
|
+
"ppm"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def write(filename, framebuffer)
|
|
16
|
+
if @binary
|
|
17
|
+
File.binwrite(filename, framebuffer.to_ppm_binary)
|
|
18
|
+
else
|
|
19
|
+
File.write(filename, framebuffer.to_ppm)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "file_backend/frame_writer"
|
|
4
|
+
require_relative "file_backend/ppm_writer"
|
|
5
|
+
require_relative "file_backend/bmp_writer"
|
|
6
|
+
|
|
3
7
|
module RBGL
|
|
4
8
|
module GUI
|
|
5
9
|
class FileBackend < Backend
|
|
6
|
-
|
|
10
|
+
SUPPORTED_FORMATS = %i[ppm bmp].freeze
|
|
11
|
+
PPM_MODES = %i[ascii binary].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(width, height, title = "RBGL", format: :ppm, ppm_mode: :ascii, output_dir: ".")
|
|
7
14
|
super(width, height, title)
|
|
8
|
-
@format = format
|
|
15
|
+
@format = normalize_format(format)
|
|
16
|
+
@ppm_mode = normalize_ppm_mode(ppm_mode, @format)
|
|
17
|
+
@writer = FrameWriter.build(@format, ppm_mode: @ppm_mode)
|
|
9
18
|
@output_dir = output_dir
|
|
10
19
|
@frame_count = 0
|
|
11
20
|
@should_close = false
|
|
@@ -13,23 +22,17 @@ module RBGL
|
|
|
13
22
|
end
|
|
14
23
|
|
|
15
24
|
def present(framebuffer)
|
|
16
|
-
filename = File.join(@output_dir, format("frame_%05d.#{@
|
|
17
|
-
|
|
18
|
-
case @format
|
|
19
|
-
when :ppm
|
|
20
|
-
File.write(filename, framebuffer.to_ppm)
|
|
21
|
-
when :ppm_binary
|
|
22
|
-
File.binwrite(filename, framebuffer.to_ppm_binary)
|
|
23
|
-
when :bmp
|
|
24
|
-
File.binwrite(filename, to_bmp(framebuffer))
|
|
25
|
-
end
|
|
25
|
+
filename = File.join(@output_dir, format("frame_%05d.#{@writer.extension}", @frame_count))
|
|
26
|
+
@writer.write(filename, framebuffer)
|
|
26
27
|
|
|
27
28
|
@frame_count += 1
|
|
28
29
|
|
|
29
30
|
@should_close = true if @max_frames && @frame_count >= @max_frames
|
|
31
|
+
true
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def poll_events
|
|
35
|
+
[]
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def should_close?
|
|
@@ -46,45 +49,19 @@ module RBGL
|
|
|
46
49
|
|
|
47
50
|
private
|
|
48
51
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
row_size = ((24 * w + 31) / 32) * 4
|
|
53
|
-
pixel_data_size = row_size * h
|
|
54
|
-
file_size = 54 + pixel_data_size
|
|
52
|
+
def normalize_format(format)
|
|
53
|
+
normalized = format.to_sym
|
|
54
|
+
return normalized if SUPPORTED_FORMATS.include?(normalized)
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
file_size,
|
|
59
|
-
0, 0,
|
|
60
|
-
54
|
|
61
|
-
].pack("CCVvvV")
|
|
62
|
-
|
|
63
|
-
dib = [
|
|
64
|
-
40,
|
|
65
|
-
w, h,
|
|
66
|
-
1,
|
|
67
|
-
24,
|
|
68
|
-
0,
|
|
69
|
-
pixel_data_size,
|
|
70
|
-
2835, 2835,
|
|
71
|
-
0, 0
|
|
72
|
-
].pack("VVVvvVVVVVV")
|
|
56
|
+
raise ArgumentError, "Unsupported file backend format: #{format}"
|
|
57
|
+
end
|
|
73
58
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
color = framebuffer.get_pixel(x, y)
|
|
79
|
-
bytes = color.to_bytes
|
|
80
|
-
row << [bytes[2], bytes[1], bytes[0]].pack("CCC")
|
|
81
|
-
end
|
|
82
|
-
padding = row_size - w * 3
|
|
83
|
-
row << "\x00" * padding
|
|
84
|
-
pixels << row
|
|
85
|
-
end
|
|
59
|
+
def normalize_ppm_mode(ppm_mode, format)
|
|
60
|
+
normalized = ppm_mode.to_sym
|
|
61
|
+
raise ArgumentError, "Unsupported PPM mode: #{ppm_mode}" unless PPM_MODES.include?(normalized)
|
|
62
|
+
return normalized if format == :ppm || normalized == :ascii
|
|
86
63
|
|
|
87
|
-
|
|
64
|
+
raise ArgumentError, "PPM mode is only supported with :ppm format"
|
|
88
65
|
end
|
|
89
66
|
end
|
|
90
67
|
end
|
|
@@ -6,9 +6,12 @@ module RBGL
|
|
|
6
6
|
module GUI
|
|
7
7
|
module Wayland
|
|
8
8
|
class Backend < GUI::Backend
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
BUFFER_WAIT_TIMEOUT = 0.25
|
|
10
|
+
BUFFER_POLL_INTERVAL = 0.016
|
|
11
|
+
|
|
12
|
+
def initialize(width, height, title = "RBGL", env: ENV, roundtrip_timeout: Connection::DEFAULT_ROUNDTRIP_TIMEOUT)
|
|
13
|
+
super(width, height, title)
|
|
14
|
+
@connection = Connection.new(env: env, roundtrip_timeout: roundtrip_timeout)
|
|
12
15
|
@windows = {}
|
|
13
16
|
setup_window(width, height, title)
|
|
14
17
|
end
|
|
@@ -19,9 +22,11 @@ module RBGL
|
|
|
19
22
|
toplevel = xdg_surface.get_toplevel
|
|
20
23
|
toplevel.set_title(t)
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
buffers = create_shm_buffers(w, h)
|
|
26
|
+
shm_buffer = buffers.first
|
|
23
27
|
|
|
24
28
|
surface.attach(shm_buffer, 0, 0)
|
|
29
|
+
shm_buffer.mark_in_use
|
|
25
30
|
surface.commit
|
|
26
31
|
@connection.flush
|
|
27
32
|
|
|
@@ -30,41 +35,56 @@ module RBGL
|
|
|
30
35
|
surface: surface,
|
|
31
36
|
xdg_surface: xdg_surface,
|
|
32
37
|
toplevel: toplevel,
|
|
38
|
+
buffers: buffers,
|
|
33
39
|
shm_buffer: shm_buffer,
|
|
34
40
|
width: w,
|
|
35
41
|
height: h,
|
|
36
|
-
should_close: false
|
|
37
|
-
pending_events: []
|
|
42
|
+
should_close: false
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
@handle = handle
|
|
41
46
|
end
|
|
42
47
|
|
|
43
48
|
def present(framebuffer)
|
|
44
|
-
return unless @handle
|
|
49
|
+
return false unless @handle
|
|
45
50
|
|
|
46
51
|
window = @windows[@handle]
|
|
47
|
-
return unless window
|
|
52
|
+
return false unless window
|
|
53
|
+
|
|
54
|
+
buffer_object = wait_for_available_buffer(window)
|
|
55
|
+
return false unless buffer_object
|
|
48
56
|
|
|
49
57
|
buffer = convert_to_wayland_format(framebuffer)
|
|
50
|
-
|
|
58
|
+
buffer_object.write(buffer)
|
|
51
59
|
|
|
52
60
|
window[:surface].damage(0, 0, framebuffer.width, framebuffer.height)
|
|
53
|
-
window[:surface].attach(
|
|
61
|
+
window[:surface].attach(buffer_object, 0, 0)
|
|
62
|
+
buffer_object.mark_in_use
|
|
63
|
+
window[:shm_buffer] = buffer_object
|
|
54
64
|
window[:surface].commit
|
|
55
65
|
@connection.flush
|
|
66
|
+
true
|
|
56
67
|
end
|
|
57
68
|
|
|
58
69
|
def poll_events
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
@connection.dispatch_pending.filter_map { |event| convert_event(event) }
|
|
71
|
+
end
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
end
|
|
73
|
+
def resize(width, height)
|
|
74
|
+
super
|
|
75
|
+
return unless @handle
|
|
66
76
|
|
|
67
|
-
|
|
77
|
+
window = @windows[@handle]
|
|
78
|
+
return unless window
|
|
79
|
+
return if window[:width] == width && window[:height] == height
|
|
80
|
+
|
|
81
|
+
old_buffers = window.fetch(:buffers, [window[:shm_buffer]].compact)
|
|
82
|
+
new_buffers = create_shm_buffers(width, height)
|
|
83
|
+
window[:buffers] = new_buffers
|
|
84
|
+
window[:shm_buffer] = new_buffers.first
|
|
85
|
+
window[:width] = width
|
|
86
|
+
window[:height] = height
|
|
87
|
+
old_buffers.each(&:destroy)
|
|
68
88
|
end
|
|
69
89
|
|
|
70
90
|
def should_close?
|
|
@@ -83,8 +103,9 @@ module RBGL
|
|
|
83
103
|
window[:toplevel].destroy
|
|
84
104
|
window[:xdg_surface].destroy
|
|
85
105
|
window[:surface].destroy
|
|
86
|
-
window[:shm_buffer].destroy
|
|
106
|
+
window.fetch(:buffers, [window[:shm_buffer]].compact).each(&:destroy)
|
|
87
107
|
@windows.delete(@handle)
|
|
108
|
+
@handle = nil
|
|
88
109
|
end
|
|
89
110
|
|
|
90
111
|
private
|
|
@@ -93,15 +114,77 @@ module RBGL
|
|
|
93
114
|
framebuffer.to_bgra_bytes
|
|
94
115
|
end
|
|
95
116
|
|
|
117
|
+
def convert_event(raw)
|
|
118
|
+
window = window_for_event(raw[:object_id])
|
|
119
|
+
return nil unless window
|
|
120
|
+
|
|
121
|
+
case raw[:type]
|
|
122
|
+
when :xdg_toplevel_close
|
|
123
|
+
window[:should_close] = true
|
|
124
|
+
Event.new(:close)
|
|
125
|
+
when :xdg_toplevel_configure
|
|
126
|
+
width = raw[:width]
|
|
127
|
+
height = raw[:height]
|
|
128
|
+
return nil unless width.positive? && height.positive?
|
|
129
|
+
|
|
130
|
+
window[:width] = width
|
|
131
|
+
window[:height] = height
|
|
132
|
+
Event.new(:resize, width: width, height: height)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def window_for_event(object_id)
|
|
137
|
+
@windows.values.find { |window| window[:toplevel].id == object_id }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def next_available_buffer(window)
|
|
141
|
+
window.fetch(:buffers, [window[:shm_buffer]].compact).find(&:available?)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def wait_for_available_buffer(window)
|
|
145
|
+
deadline = monotonic_time + BUFFER_WAIT_TIMEOUT
|
|
146
|
+
|
|
147
|
+
loop do
|
|
148
|
+
buffer = next_available_buffer(window)
|
|
149
|
+
return buffer if buffer
|
|
150
|
+
return nil if abort_buffer_wait?(window, deadline)
|
|
151
|
+
|
|
152
|
+
pump_connection(timeout: remaining_buffer_wait(deadline))
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def pump_connection(timeout:)
|
|
157
|
+
if @connection.respond_to?(:pump_events)
|
|
158
|
+
@connection.pump_events(timeout: timeout)
|
|
159
|
+
else
|
|
160
|
+
sleep(timeout)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def abort_buffer_wait?(window, deadline)
|
|
165
|
+
window[:should_close] || monotonic_time >= deadline
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def remaining_buffer_wait(deadline)
|
|
169
|
+
[deadline - monotonic_time, BUFFER_POLL_INTERVAL].min.clamp(0.0, BUFFER_POLL_INTERVAL)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def monotonic_time
|
|
173
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def create_shm_buffers(width, height, count = 2)
|
|
177
|
+
Array.new(count) { create_shm_buffer(width, height) }
|
|
178
|
+
end
|
|
179
|
+
|
|
96
180
|
def create_shm_buffer(width, height)
|
|
97
181
|
size = width * height * 4
|
|
98
182
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
pool = @connection.shm.create_pool(fd, size)
|
|
183
|
+
file = create_anonymous_file(size)
|
|
184
|
+
pool = @connection.shm.create_pool(file.fileno, size)
|
|
102
185
|
buffer = pool.create_buffer(0, width, height, width * 4, :argb8888)
|
|
103
186
|
|
|
104
|
-
ShmBuffer.new(
|
|
187
|
+
ShmBuffer.new(file, pool, buffer)
|
|
105
188
|
end
|
|
106
189
|
|
|
107
190
|
def create_anonymous_file(size)
|
|
@@ -110,16 +193,15 @@ module RBGL
|
|
|
110
193
|
|
|
111
194
|
file = File.open(path, File::RDWR | File::CREAT | File::EXCL, 0o600)
|
|
112
195
|
file.truncate(size)
|
|
113
|
-
fd = file.fileno
|
|
114
196
|
File.unlink(path)
|
|
115
|
-
|
|
116
|
-
fd
|
|
197
|
+
file
|
|
117
198
|
rescue Errno::ENOENT
|
|
118
199
|
require "tempfile"
|
|
119
200
|
tmpfile = Tempfile.new("rbgl")
|
|
120
201
|
tmpfile.truncate(size)
|
|
121
|
-
tmpfile
|
|
202
|
+
tmpfile
|
|
122
203
|
end
|
|
204
|
+
|
|
123
205
|
end
|
|
124
206
|
end
|
|
125
207
|
end
|