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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +20 -2
  4. data/README.md +130 -74
  5. data/Rakefile +7 -0
  6. data/bench/renderer_bench.rb +283 -0
  7. data/docs/.nojekyll +1 -0
  8. data/docs/index.html +38 -0
  9. data/examples/array_test.rb +1 -1
  10. data/examples/color_test.rb +1 -1
  11. data/examples/cube_spinning.rb +1 -1
  12. data/examples/gradient.rb +1 -1
  13. data/examples/multi_return_test.rb +1 -1
  14. data/examples/plasma.rb +1 -1
  15. data/examples/sphere_raymarch.rb +1 -1
  16. data/examples/triangle_window.rb +1 -1
  17. data/exe/rbgl +42 -0
  18. data/lib/rbgl/cli/doctor.rb +155 -0
  19. data/lib/rbgl/engine/buffer.rb +150 -40
  20. data/lib/rbgl/engine/clip_space_clipper.rb +163 -0
  21. data/lib/rbgl/engine/context.rb +133 -63
  22. data/lib/rbgl/engine/framebuffer.rb +153 -48
  23. data/lib/rbgl/engine/immutable_color.rb +32 -0
  24. data/lib/rbgl/engine/pipeline.rb +38 -7
  25. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +195 -0
  26. data/lib/rbgl/engine/rasterizer/line_renderer.rb +88 -0
  27. data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
  28. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +136 -0
  29. data/lib/rbgl/engine/rasterizer.rb +65 -171
  30. data/lib/rbgl/engine/shader/base_shader.rb +51 -0
  31. data/lib/rbgl/engine/shader/builtins.rb +257 -0
  32. data/lib/rbgl/engine/shader/dynamic_data.rb +73 -0
  33. data/lib/rbgl/engine/shader.rb +5 -318
  34. data/lib/rbgl/engine/texture.rb +264 -51
  35. data/lib/rbgl/engine.rb +4 -1
  36. data/lib/rbgl/gui/backend.rb +21 -34
  37. data/lib/rbgl/gui/backend_factory.rb +89 -0
  38. data/lib/rbgl/gui/cocoa/backend.rb +59 -46
  39. data/lib/rbgl/gui/cocoa/key_mapper.rb +48 -0
  40. data/lib/rbgl/gui/event.rb +4 -2
  41. data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
  42. data/lib/rbgl/gui/file_backend/frame_writer.rb +35 -0
  43. data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
  44. data/lib/rbgl/gui/file_backend/ppm_writer.rb +26 -0
  45. data/lib/rbgl/gui/file_backend.rb +50 -55
  46. data/lib/rbgl/gui/wayland/backend.rb +214 -60
  47. data/lib/rbgl/gui/wayland/codec.rb +50 -0
  48. data/lib/rbgl/gui/wayland/connection.rb +188 -231
  49. data/lib/rbgl/gui/wayland/event_dispatcher.rb +165 -0
  50. data/lib/rbgl/gui/wayland/global_binder.rb +46 -0
  51. data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
  52. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +63 -0
  53. data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
  54. data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
  55. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +157 -0
  56. data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
  57. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +60 -0
  58. data/lib/rbgl/gui/wayland/protocol_objects.rb +8 -0
  59. data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
  60. data/lib/rbgl/gui/window/render_loop.rb +111 -0
  61. data/lib/rbgl/gui/window.rb +74 -84
  62. data/lib/rbgl/gui/x11/atom_cache.rb +29 -0
  63. data/lib/rbgl/gui/x11/backend.rb +73 -49
  64. data/lib/rbgl/gui/x11/connection.rb +284 -219
  65. data/lib/rbgl/gui/x11/event_parser.rb +71 -0
  66. data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
  67. data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
  68. data/lib/rbgl/gui/x11/request_encoder.rb +155 -0
  69. data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
  70. data/lib/rbgl/gui/x11/transport.rb +81 -0
  71. data/lib/rbgl/gui/x11/x_authority.rb +107 -0
  72. data/lib/rbgl/gui.rb +3 -5
  73. data/lib/rbgl/version.rb +1 -1
  74. metadata +63 -24
  75. data/examples/chemical_heartbeat.rb +0 -166
  76. data/examples/dark_transit.rb +0 -166
  77. data/examples/fractured_orb.rb +0 -428
  78. data/examples/fractured_orb_rb.rb +0 -598
  79. data/examples/hexagonal_flow.rb +0 -333
  80. data/examples/teapot.rb +0 -362
  81. data/examples/teapot_mcu.rb +0 -344
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../backend"
4
+
5
+ module RBGL
6
+ module GUI
7
+ module Wayland
8
+ class GlobalBinder
9
+ BINDINGS = {
10
+ "wl_compositor" => [:compositor, Compositor, 4],
11
+ "wl_shm" => [:shm, Shm, 1],
12
+ "xdg_wm_base" => [:xdg_wm_base, XdgWmBase, 2],
13
+ "wl_seat" => [:seat, Seat, 5]
14
+ }.freeze
15
+ REQUIRED_INTERFACES = %w[wl_compositor wl_shm xdg_wm_base].freeze
16
+
17
+ def initialize(connection)
18
+ @connection = connection
19
+ end
20
+
21
+ def bind!(registry, globals)
22
+ validate_required_globals!(globals)
23
+
24
+ BINDINGS.each_with_object({}) do |(interface, binding), bound|
25
+ slot, klass, max_version = binding
26
+ global = globals[interface]
27
+ next unless global
28
+
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))
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def validate_required_globals!(globals)
38
+ missing = REQUIRED_INTERFACES - globals.keys
39
+ return if missing.empty?
40
+
41
+ raise GUI::BackendUnavailable, "Wayland globals missing: #{missing.join(', ')}"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -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
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module GUI
5
+ module Wayland
6
+ TypedArgument = Struct.new(:type, :value)
7
+
8
+ module Arguments
9
+ module_function
10
+
11
+ def uint(value)
12
+ TypedArgument.new(:uint, Integer(value))
13
+ end
14
+
15
+ def int(value)
16
+ TypedArgument.new(:int, Integer(value))
17
+ end
18
+
19
+ def fixed(value)
20
+ TypedArgument.new(:fixed, Float(value))
21
+ end
22
+
23
+ def string(value)
24
+ TypedArgument.new(:string, value.to_s)
25
+ end
26
+
27
+ def object(value)
28
+ object_id = value.respond_to?(:id) ? value.id : value
29
+ TypedArgument.new(:object, Integer(object_id))
30
+ end
31
+
32
+ def new_id(value)
33
+ TypedArgument.new(:new_id, Integer(value))
34
+ end
35
+ end
36
+
37
+ class WaylandObject
38
+ attr_reader :id, :connection, :version
39
+
40
+ def initialize(connection, id, version: 1)
41
+ @connection = connection
42
+ @id = id
43
+ @version = version
44
+ @destroyed = false
45
+ end
46
+
47
+ def send_request(opcode, *args)
48
+ @connection.send_request(@id, opcode, *args)
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
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module GUI
5
+ module Wayland
6
+ class Display < WaylandObject
7
+ def initialize(connection)
8
+ super(connection, 1)
9
+ end
10
+
11
+ def sync
12
+ callback_id = @connection.allocate_id
13
+ send_request(0, Arguments.new_id(callback_id))
14
+ @connection.register_object(Callback.new(@connection, callback_id))
15
+ end
16
+
17
+ def get_registry
18
+ registry_id = @connection.allocate_id
19
+ send_request(1, Arguments.new_id(registry_id))
20
+ @connection.register_object(Registry.new(@connection, registry_id))
21
+ end
22
+ end
23
+
24
+ class Registry < WaylandObject
25
+ def bind(name, interface, version)
26
+ new_id = @connection.allocate_id
27
+ send_request(
28
+ 0,
29
+ Arguments.uint(name),
30
+ Arguments.string(interface),
31
+ Arguments.uint(version),
32
+ Arguments.new_id(new_id)
33
+ )
34
+ new_id
35
+ end
36
+ end
37
+
38
+ class Callback < WaylandObject
39
+ def initialize(connection, id)
40
+ super
41
+ @done = false
42
+ end
43
+
44
+ def done?
45
+ @done
46
+ end
47
+
48
+ def handle_done
49
+ @done = true
50
+ end
51
+ end
52
+ end
53
+ end
54
+ 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
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module GUI
5
+ module Wayland
6
+ class Shm < WaylandObject
7
+ def create_pool(file, size)
8
+ pool_id = @connection.allocate_id
9
+ @connection.send_request_with_fd(@id, 0, Arguments.new_id(pool_id), Arguments.int(size), file)
10
+ @connection.register_object(ShmPool.new(@connection, pool_id))
11
+ end
12
+ end
13
+
14
+ class ShmPool < WaylandObject
15
+ def create_buffer(offset, width, height, stride, format)
16
+ buffer_id = @connection.allocate_id
17
+ format_val = case format
18
+ when :argb8888 then 0
19
+ when :xrgb8888 then 1
20
+ else
21
+ raise ArgumentError, "Unsupported Wayland SHM format: #{format}"
22
+ end
23
+ send_request(
24
+ 0,
25
+ Arguments.new_id(buffer_id),
26
+ Arguments.int(offset),
27
+ Arguments.int(width),
28
+ Arguments.int(height),
29
+ Arguments.int(stride),
30
+ Arguments.uint(format_val)
31
+ )
32
+ @connection.register_object(WlBuffer.new(@connection, buffer_id))
33
+ end
34
+
35
+ def destroy
36
+ send_request(1)
37
+ end
38
+ end
39
+
40
+ class WlBuffer < WaylandObject
41
+ def initialize(connection, id)
42
+ super
43
+ @busy = false
44
+ @destroy_requested = false
45
+ @destroyed = false
46
+ @release_callbacks = []
47
+ end
48
+
49
+ def on_release(&block)
50
+ @release_callbacks << block if block
51
+ end
52
+
53
+ def mark_in_use
54
+ @busy = true
55
+ end
56
+
57
+ def available?
58
+ !@busy && !@destroy_requested && !@destroyed
59
+ end
60
+
61
+ def busy?
62
+ @busy
63
+ end
64
+
65
+ def handle_release
66
+ @busy = false
67
+ @release_callbacks.each(&:call)
68
+ finalize_destroy if @destroy_requested
69
+ end
70
+
71
+ def destroy(force: false)
72
+ return if @destroyed || (@destroy_requested && !force)
73
+
74
+ if @busy && !force
75
+ @destroy_requested = true
76
+ else
77
+ finalize_destroy
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def finalize_destroy
84
+ return if @destroyed
85
+
86
+ send_request(0)
87
+ @destroyed = true
88
+ end
89
+ end
90
+
91
+ class ShmBuffer
92
+ attr_reader :wl_buffer
93
+
94
+ def initialize(file, pool, wl_buffer)
95
+ @file = file
96
+ @pool = pool
97
+ @wl_buffer = wl_buffer
98
+ @destroy_requested = false
99
+ @destroyed = false
100
+ @file.binmode
101
+ @file.seek(0)
102
+ @wl_buffer.on_release { handle_release }
103
+ end
104
+
105
+ def write(data)
106
+ @file.seek(0)
107
+ @file.write(data)
108
+ @file.flush
109
+ end
110
+
111
+ def available?
112
+ @wl_buffer.available?
113
+ end
114
+
115
+ def mark_in_use
116
+ @wl_buffer.mark_in_use
117
+ end
118
+
119
+ def id
120
+ @wl_buffer.id
121
+ end
122
+
123
+ def destroy(force: false)
124
+ return if @destroyed
125
+
126
+ @destroy_requested = true
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
136
+ end
137
+
138
+ private
139
+
140
+ def handle_release
141
+ finalize_destroy if @destroy_requested
142
+ end
143
+
144
+ def finalize_destroy
145
+ return if @destroyed
146
+
147
+ begin
148
+ @pool.destroy
149
+ ensure
150
+ @file.close unless @file.closed?
151
+ @destroyed = true
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module GUI
5
+ module Wayland
6
+ class Compositor < WaylandObject
7
+ def create_surface
8
+ surface_id = @connection.allocate_id
9
+ send_request(0, Arguments.new_id(surface_id))
10
+ @connection.register_object(Surface.new(@connection, surface_id))
11
+ end
12
+ end
13
+
14
+ class Surface < WaylandObject
15
+ def attach(buffer, x, y)
16
+ send_request(1, Arguments.object(buffer), Arguments.int(x), Arguments.int(y))
17
+ end
18
+
19
+ def damage(x, y, width, height)
20
+ send_request(2, Arguments.int(x), Arguments.int(y), Arguments.int(width), Arguments.int(height))
21
+ end
22
+
23
+ def commit
24
+ send_request(6)
25
+ end
26
+
27
+ def destroy
28
+ send_request(0)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module GUI
5
+ module Wayland
6
+ class XdgWmBase < WaylandObject
7
+ def get_xdg_surface(surface)
8
+ xdg_surface_id = @connection.allocate_id
9
+ send_request(2, Arguments.new_id(xdg_surface_id), Arguments.object(surface))
10
+ @connection.register_object(XdgSurface.new(@connection, xdg_surface_id))
11
+ end
12
+
13
+ def pong(serial)
14
+ send_request(3, Arguments.uint(serial))
15
+ end
16
+ end
17
+
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
+
28
+ def get_toplevel
29
+ toplevel_id = @connection.allocate_id
30
+ send_request(1, Arguments.new_id(toplevel_id))
31
+ @connection.register_object(XdgToplevel.new(@connection, toplevel_id))
32
+ end
33
+
34
+ def ack_configure(serial)
35
+ send_request(4, Arguments.uint(serial))
36
+ end
37
+
38
+ def handle_configure(serial)
39
+ ack_configure(serial)
40
+ @connection.flush
41
+ @configured = true
42
+ end
43
+
44
+ def destroy
45
+ send_request(0)
46
+ end
47
+ end
48
+
49
+ class XdgToplevel < WaylandObject
50
+ def set_title(title)
51
+ send_request(2, Arguments.string(title))
52
+ end
53
+
54
+ def destroy
55
+ send_request(0)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "protocol_objects/arguments"
4
+ require_relative "protocol_objects/display"
5
+ require_relative "protocol_objects/surface"
6
+ require_relative "protocol_objects/shm"
7
+ require_relative "protocol_objects/seat"
8
+ require_relative "protocol_objects/xdg_shell"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module GUI
5
+ class Window
6
+ class EventDispatcher
7
+ def initialize(backend:, event_handlers:, on_resize:)
8
+ @backend = backend
9
+ @event_handlers = event_handlers
10
+ @on_resize = on_resize
11
+ end
12
+
13
+ def process
14
+ Array(poll_events).each do |event|
15
+ next unless event.is_a?(Event)
16
+
17
+ @on_resize.call(event) if event.type == :resize
18
+ @event_handlers[event.type].each { |handler| handler.call(event) }
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def poll_events
25
+ @backend.poll_events
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end