rbgl 0.1.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 +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +21 -0
- data/README.md +123 -0
- data/Rakefile +12 -0
- data/examples/array_test.rb +99 -0
- data/examples/chemical_heartbeat.rb +166 -0
- data/examples/color_test.rb +61 -0
- data/examples/cube_spinning.rb +87 -0
- data/examples/dark_transit.rb +166 -0
- data/examples/fractured_orb.rb +428 -0
- data/examples/fractured_orb_rb.rb +598 -0
- data/examples/gradient.rb +84 -0
- data/examples/hexagonal_flow.rb +333 -0
- data/examples/multi_return_test.rb +98 -0
- data/examples/plasma.rb +78 -0
- data/examples/sphere_raymarch.rb +126 -0
- data/examples/teapot.rb +362 -0
- data/examples/teapot_mcu.rb +344 -0
- data/examples/triangle_basic.rb +36 -0
- data/examples/triangle_window.rb +62 -0
- data/examples/window_test.rb +36 -0
- data/lib/rbgl/engine/buffer.rb +160 -0
- data/lib/rbgl/engine/context.rb +157 -0
- data/lib/rbgl/engine/framebuffer.rb +115 -0
- data/lib/rbgl/engine/pipeline.rb +35 -0
- data/lib/rbgl/engine/rasterizer.rb +213 -0
- data/lib/rbgl/engine/shader.rb +324 -0
- data/lib/rbgl/engine/texture.rb +125 -0
- data/lib/rbgl/engine.rb +15 -0
- data/lib/rbgl/gui/backend.rb +76 -0
- data/lib/rbgl/gui/cocoa/backend.rb +121 -0
- data/lib/rbgl/gui/event.rb +34 -0
- data/lib/rbgl/gui/file_backend.rb +91 -0
- data/lib/rbgl/gui/wayland/backend.rb +126 -0
- data/lib/rbgl/gui/wayland/connection.rb +331 -0
- data/lib/rbgl/gui/window.rb +148 -0
- data/lib/rbgl/gui/x11/backend.rb +156 -0
- data/lib/rbgl/gui/x11/connection.rb +344 -0
- data/lib/rbgl/gui.rb +16 -0
- data/lib/rbgl/version.rb +5 -0
- data/lib/rbgl.rb +6 -0
- metadata +114 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class Backend
|
|
6
|
+
attr_reader :width, :height, :title
|
|
7
|
+
|
|
8
|
+
def initialize(width, height, title = "RBGL")
|
|
9
|
+
@width = width
|
|
10
|
+
@height = height
|
|
11
|
+
@title = title
|
|
12
|
+
@key_callback = nil
|
|
13
|
+
@mouse_callback = nil
|
|
14
|
+
@resize_callback = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def present(_framebuffer)
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def poll_events
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def poll_events_raw
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def should_close?
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def close
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def set_pixels(buffer, width, height)
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def metal_available?
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def native_handle
|
|
46
|
+
nil
|
|
47
|
+
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
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "metaco"
|
|
5
|
+
METACO_AVAILABLE = true
|
|
6
|
+
rescue LoadError
|
|
7
|
+
METACO_AVAILABLE = false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module RBGL
|
|
11
|
+
module GUI
|
|
12
|
+
module Cocoa
|
|
13
|
+
class Backend < GUI::Backend
|
|
14
|
+
def initialize(width, height, title = "RBGL")
|
|
15
|
+
unless METACO_AVAILABLE
|
|
16
|
+
raise LoadError, "metaco gem is required for Cocoa backend. Install it with: gem install metaco"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
super
|
|
20
|
+
Metaco.init
|
|
21
|
+
@handle = Metaco.window_create(width, height, title)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def present(framebuffer)
|
|
25
|
+
return unless @handle
|
|
26
|
+
|
|
27
|
+
Metaco.set_pixels(@handle, framebuffer.to_rgba_bytes, framebuffer.width, framebuffer.height)
|
|
28
|
+
Metaco.present(@handle)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def poll_events
|
|
32
|
+
return [] unless @handle
|
|
33
|
+
|
|
34
|
+
raw_events = Metaco.poll_events(@handle)
|
|
35
|
+
events = []
|
|
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
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def poll_events_raw
|
|
49
|
+
return [] unless @handle
|
|
50
|
+
|
|
51
|
+
Metaco.poll_events(@handle)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def should_close?
|
|
55
|
+
return false unless @handle
|
|
56
|
+
|
|
57
|
+
Metaco.should_close?(@handle)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def close
|
|
61
|
+
return unless @handle
|
|
62
|
+
|
|
63
|
+
Metaco.window_destroy(@handle)
|
|
64
|
+
@handle = nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def set_pixels(buffer, width, height)
|
|
68
|
+
return unless @handle
|
|
69
|
+
|
|
70
|
+
Metaco.set_pixels(@handle, buffer, width, height)
|
|
71
|
+
Metaco.present(@handle)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def metal_available?
|
|
75
|
+
return false unless @handle
|
|
76
|
+
|
|
77
|
+
Metaco.metal_compute_available?(@handle)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def native_handle
|
|
81
|
+
@handle
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def convert_event(raw)
|
|
87
|
+
type = raw[:type]
|
|
88
|
+
|
|
89
|
+
case type
|
|
90
|
+
when :key_press
|
|
91
|
+
Event.new(:key_press, key: raw[:key], char: raw[:char])
|
|
92
|
+
when :key_release
|
|
93
|
+
Event.new(:key_release, key: raw[:key])
|
|
94
|
+
when :mouse_press
|
|
95
|
+
Event.new(:mouse_press, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
96
|
+
when :mouse_release
|
|
97
|
+
Event.new(:mouse_release, x: raw[:x], y: raw[:y], button: raw[:button])
|
|
98
|
+
when :mouse_move
|
|
99
|
+
Event.new(:mouse_move, x: raw[:x], y: raw[:y])
|
|
100
|
+
else
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
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
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class Event
|
|
6
|
+
attr_reader :type, :data
|
|
7
|
+
|
|
8
|
+
def initialize(type, **data)
|
|
9
|
+
@type = type
|
|
10
|
+
@data = data
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def [](key)
|
|
14
|
+
@data[key]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def method_missing(name, *args)
|
|
18
|
+
@data.key?(name) ? @data[name] : super
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def respond_to_missing?(name, include_private = false)
|
|
22
|
+
@data.key?(name) || super
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_h
|
|
26
|
+
{ type: @type }.merge(@data)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def inspect
|
|
30
|
+
"Event[#{@type}, #{@data.map { |k, v| "#{k}: #{v}" }.join(', ')}]"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module GUI
|
|
5
|
+
class FileBackend < Backend
|
|
6
|
+
def initialize(width, height, title = "RBGL", format: :ppm, output_dir: ".")
|
|
7
|
+
super(width, height, title)
|
|
8
|
+
@format = format
|
|
9
|
+
@output_dir = output_dir
|
|
10
|
+
@frame_count = 0
|
|
11
|
+
@should_close = false
|
|
12
|
+
@max_frames = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def present(framebuffer)
|
|
16
|
+
filename = File.join(@output_dir, format("frame_%05d.#{@format}", @frame_count))
|
|
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
|
|
26
|
+
|
|
27
|
+
@frame_count += 1
|
|
28
|
+
|
|
29
|
+
@should_close = true if @max_frames && @frame_count >= @max_frames
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def poll_events
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def should_close?
|
|
36
|
+
@should_close
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def close
|
|
40
|
+
@should_close = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def set_max_frames(count)
|
|
44
|
+
@max_frames = count
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def to_bmp(framebuffer)
|
|
50
|
+
w = framebuffer.width
|
|
51
|
+
h = framebuffer.height
|
|
52
|
+
row_size = ((24 * w + 31) / 32) * 4
|
|
53
|
+
pixel_data_size = row_size * h
|
|
54
|
+
file_size = 54 + pixel_data_size
|
|
55
|
+
|
|
56
|
+
header = [
|
|
57
|
+
0x42, 0x4D,
|
|
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")
|
|
73
|
+
|
|
74
|
+
pixels = +""
|
|
75
|
+
(h - 1).downto(0) do |y|
|
|
76
|
+
row = +""
|
|
77
|
+
w.times do |x|
|
|
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
|
|
86
|
+
|
|
87
|
+
header + dib + pixels
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "connection"
|
|
4
|
+
|
|
5
|
+
module RBGL
|
|
6
|
+
module GUI
|
|
7
|
+
module Wayland
|
|
8
|
+
class Backend < GUI::Backend
|
|
9
|
+
def initialize(width, height, title = "RBGL")
|
|
10
|
+
super
|
|
11
|
+
@connection = Connection.new
|
|
12
|
+
@windows = {}
|
|
13
|
+
setup_window(width, height, title)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private def setup_window(w, h, t)
|
|
17
|
+
surface = @connection.compositor.create_surface
|
|
18
|
+
xdg_surface = @connection.xdg_wm_base.get_xdg_surface(surface)
|
|
19
|
+
toplevel = xdg_surface.get_toplevel
|
|
20
|
+
toplevel.set_title(t)
|
|
21
|
+
|
|
22
|
+
shm_buffer = create_shm_buffer(w, h)
|
|
23
|
+
|
|
24
|
+
surface.attach(shm_buffer, 0, 0)
|
|
25
|
+
surface.commit
|
|
26
|
+
@connection.flush
|
|
27
|
+
|
|
28
|
+
handle = surface.id
|
|
29
|
+
@windows[handle] = {
|
|
30
|
+
surface: surface,
|
|
31
|
+
xdg_surface: xdg_surface,
|
|
32
|
+
toplevel: toplevel,
|
|
33
|
+
shm_buffer: shm_buffer,
|
|
34
|
+
width: w,
|
|
35
|
+
height: h,
|
|
36
|
+
should_close: false,
|
|
37
|
+
pending_events: []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@handle = handle
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def present(framebuffer)
|
|
44
|
+
return unless @handle
|
|
45
|
+
|
|
46
|
+
window = @windows[@handle]
|
|
47
|
+
return unless window
|
|
48
|
+
|
|
49
|
+
buffer = convert_to_wayland_format(framebuffer)
|
|
50
|
+
window[:shm_buffer].write(buffer)
|
|
51
|
+
|
|
52
|
+
window[:surface].damage(0, 0, framebuffer.width, framebuffer.height)
|
|
53
|
+
window[:surface].attach(window[:shm_buffer], 0, 0)
|
|
54
|
+
window[:surface].commit
|
|
55
|
+
@connection.flush
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def poll_events
|
|
59
|
+
events = []
|
|
60
|
+
@connection.dispatch_pending
|
|
61
|
+
|
|
62
|
+
if @handle && @windows[@handle]
|
|
63
|
+
events.concat(@windows[@handle][:pending_events])
|
|
64
|
+
@windows[@handle][:pending_events] = []
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
events
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def should_close?
|
|
71
|
+
return false unless @handle
|
|
72
|
+
|
|
73
|
+
@windows[@handle]&.[](:should_close) || false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def close
|
|
77
|
+
return unless @handle
|
|
78
|
+
|
|
79
|
+
window = @windows[@handle]
|
|
80
|
+
return unless window
|
|
81
|
+
|
|
82
|
+
window[:should_close] = true
|
|
83
|
+
window[:toplevel].destroy
|
|
84
|
+
window[:xdg_surface].destroy
|
|
85
|
+
window[:surface].destroy
|
|
86
|
+
window[:shm_buffer].destroy
|
|
87
|
+
@windows.delete(@handle)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def convert_to_wayland_format(framebuffer)
|
|
93
|
+
framebuffer.to_bgra_bytes
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def create_shm_buffer(width, height)
|
|
97
|
+
size = width * height * 4
|
|
98
|
+
|
|
99
|
+
fd = create_anonymous_file(size)
|
|
100
|
+
|
|
101
|
+
pool = @connection.shm.create_pool(fd, size)
|
|
102
|
+
buffer = pool.create_buffer(0, width, height, width * 4, :argb8888)
|
|
103
|
+
|
|
104
|
+
ShmBuffer.new(fd, size, buffer)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def create_anonymous_file(size)
|
|
108
|
+
name = "rbgl-#{Process.pid}-#{rand(10000)}"
|
|
109
|
+
path = "/dev/shm/#{name}"
|
|
110
|
+
|
|
111
|
+
file = File.open(path, File::RDWR | File::CREAT | File::EXCL, 0o600)
|
|
112
|
+
file.truncate(size)
|
|
113
|
+
fd = file.fileno
|
|
114
|
+
File.unlink(path)
|
|
115
|
+
|
|
116
|
+
fd
|
|
117
|
+
rescue Errno::ENOENT
|
|
118
|
+
require "tempfile"
|
|
119
|
+
tmpfile = Tempfile.new("rbgl")
|
|
120
|
+
tmpfile.truncate(size)
|
|
121
|
+
tmpfile.fileno
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|