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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.codespellignore +4 -0
  3. data/.rubocop.yml +3 -0
  4. data/.yamllint +16 -0
  5. data/CHANGELOG.md +11 -1
  6. data/README.md +130 -74
  7. data/Rakefile +7 -0
  8. data/bench/renderer_bench.rb +283 -0
  9. data/docs/.nojekyll +1 -0
  10. data/docs/index.html +38 -0
  11. data/examples/array_test.rb +1 -1
  12. data/examples/color_test.rb +1 -1
  13. data/examples/cube_spinning.rb +1 -1
  14. data/examples/gradient.rb +1 -1
  15. data/examples/multi_return_test.rb +1 -1
  16. data/examples/plasma.rb +1 -1
  17. data/examples/sphere_raymarch.rb +1 -1
  18. data/examples/triangle_window.rb +1 -1
  19. data/exe/rbgl +42 -0
  20. data/lib/rbgl/cli/doctor.rb +155 -0
  21. data/lib/rbgl/engine/buffer.rb +55 -9
  22. data/lib/rbgl/engine/clip_space_clipper.rb +23 -3
  23. data/lib/rbgl/engine/context.rb +23 -6
  24. data/lib/rbgl/engine/framebuffer.rb +146 -41
  25. data/lib/rbgl/engine/immutable_color.rb +32 -0
  26. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +100 -10
  27. data/lib/rbgl/engine/rasterizer/line_renderer.rb +30 -14
  28. data/lib/rbgl/engine/rasterizer/point_renderer.rb +3 -3
  29. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +72 -23
  30. data/lib/rbgl/engine/rasterizer.rb +3 -20
  31. data/lib/rbgl/engine/shader/base_shader.rb +3 -7
  32. data/lib/rbgl/engine/shader/builtins.rb +13 -10
  33. data/lib/rbgl/engine/shader/dynamic_data.rb +18 -10
  34. data/lib/rbgl/engine/texture.rb +74 -50
  35. data/lib/rbgl/engine.rb +3 -1
  36. data/lib/rbgl/gui/backend.rb +12 -7
  37. data/lib/rbgl/gui/backend_factory.rb +7 -3
  38. data/lib/rbgl/gui/cocoa/backend.rb +50 -17
  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 +2 -2
  42. data/lib/rbgl/gui/file_backend/frame_writer.rb +7 -0
  43. data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
  44. data/lib/rbgl/gui/file_backend/ppm_writer.rb +1 -0
  45. data/lib/rbgl/gui/file_backend.rb +24 -6
  46. data/lib/rbgl/gui/wayland/backend.rb +127 -55
  47. data/lib/rbgl/gui/wayland/codec.rb +7 -11
  48. data/lib/rbgl/gui/wayland/connection.rb +137 -20
  49. data/lib/rbgl/gui/wayland/event_dispatcher.rb +103 -12
  50. data/lib/rbgl/gui/wayland/global_binder.rb +6 -4
  51. data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
  52. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +14 -2
  53. data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
  54. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +23 -12
  55. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +15 -0
  56. data/lib/rbgl/gui/wayland/protocol_objects.rb +1 -0
  57. data/lib/rbgl/gui/window/render_loop.rb +71 -18
  58. data/lib/rbgl/gui/window.rb +39 -8
  59. data/lib/rbgl/gui/x11/atom_cache.rb +4 -1
  60. data/lib/rbgl/gui/x11/backend.rb +66 -28
  61. data/lib/rbgl/gui/x11/connection.rb +216 -86
  62. data/lib/rbgl/gui/x11/event_parser.rb +13 -2
  63. data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
  64. data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
  65. data/lib/rbgl/gui/x11/request_encoder.rb +29 -28
  66. data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
  67. data/lib/rbgl/gui/x11/transport.rb +52 -2
  68. data/lib/rbgl/gui/x11/x_authority.rb +107 -0
  69. data/lib/rbgl/gui.rb +2 -5
  70. data/lib/rbgl/version.rb +1 -1
  71. metadata +38 -24
  72. data/examples/chemical_heartbeat.rb +0 -166
  73. data/examples/dark_transit.rb +0 -166
  74. data/examples/fractured_orb.rb +0 -428
  75. data/examples/fractured_orb_rb.rb +0 -598
  76. data/examples/hexagonal_flow.rb +0 -333
  77. data/examples/teapot.rb +0 -362
  78. data/examples/teapot_mcu.rb +0 -344
@@ -8,10 +8,11 @@ module RBGL
8
8
  class Window
9
9
  attr_reader :context, :backend, :width, :height
10
10
 
11
- def initialize(width:, height:, title: "RBGL", backend: :auto, **options)
11
+ def initialize(width:, height:, title: "RBGL", backend: :auto, target_fps: 60, **options)
12
12
  @width = width
13
13
  @height = height
14
14
  @title = title
15
+ @render_loop = RenderLoop.new(target_fps: target_fps)
15
16
  @context = Engine::Context.new(width: width, height: height)
16
17
  @backend = build_backend(backend, width: width, height: height, title: title, **options)
17
18
  @event_handlers = Hash.new { |h, k| h[k] = [] }
@@ -20,7 +21,7 @@ module RBGL
20
21
  event_handlers: @event_handlers,
21
22
  on_resize: method(:apply_resize)
22
23
  )
23
- @render_loop = RenderLoop.new
24
+ @closed = false
24
25
  end
25
26
 
26
27
  def on(event_type, &block)
@@ -29,6 +30,8 @@ module RBGL
29
30
  end
30
31
 
31
32
  def run(&frame_callback)
33
+ raise RuntimeError, "Window#run is one-shot and cannot be called after the window is closed" if @closed
34
+
32
35
  @render_loop.run(
33
36
  backend: @backend,
34
37
  context: @context,
@@ -36,7 +39,25 @@ module RBGL
36
39
  &frame_callback
37
40
  )
38
41
  ensure
39
- @backend.close if @backend
42
+ original_error = $!
43
+ begin
44
+ close if @backend
45
+ rescue StandardError
46
+ raise unless original_error
47
+ end
48
+ end
49
+
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
+ )
40
61
  end
41
62
 
42
63
  def stop
@@ -53,11 +74,11 @@ module RBGL
53
74
  end
54
75
 
55
76
  def metal_available?
56
- @backend.metal_available?
77
+ @backend.respond_to?(:metal_available?) && @backend.metal_available?
57
78
  end
58
79
 
59
80
  def native_handle
60
- @backend.native_handle
81
+ @backend.native_handle if @backend.respond_to?(:native_handle)
61
82
  end
62
83
 
63
84
  def should_close?
@@ -65,11 +86,21 @@ module RBGL
65
86
  end
66
87
 
67
88
  def poll_events_raw
68
- @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
69
96
  end
70
97
 
71
98
  def close
99
+ return if @closed
100
+
101
+ @render_loop.stop
72
102
  @backend.close
103
+ @closed = true
73
104
  end
74
105
 
75
106
  def fps
@@ -91,10 +122,10 @@ module RBGL
91
122
  end
92
123
 
93
124
  def apply_resize(event)
125
+ @backend.resize(event.width, event.height)
126
+ @context.resize(width: event.width, height: event.height)
94
127
  @width = event.width
95
128
  @height = event.height
96
- @context.resize(width: event.width, height: event.height)
97
- @backend.resize(event.width, event.height)
98
129
  end
99
130
 
100
131
  def validate_event_handler!(event_type, block)
@@ -17,8 +17,11 @@ module RBGL
17
17
 
18
18
  def fetch(name)
19
19
  return name if name.is_a?(Integer)
20
+ return @cache[name] if @cache.key?(name)
20
21
 
21
- @cache[name] ||= @resolver.call(name)
22
+ resolved = @resolver.call(name)
23
+ @cache[name] = resolved
24
+ resolved
22
25
  end
23
26
  end
24
27
  end
@@ -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", env: ENV)
10
+ def initialize(width, height, title = "RBGL", env: ENV, reply_timeout: Connection::DEFAULT_REPLY_TIMEOUT)
10
11
  super(width, height, title)
11
- @display = Connection.new(env["DISPLAY"] || ":0")
12
- @windows = {}
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)
@@ -42,33 +50,43 @@ module RBGL
42
50
  gc_id = @display.generate_id
43
51
  @display.create_gc(gc_id, wid)
44
52
 
45
- @windows[wid] = {
53
+ @window = {
54
+ id: wid,
46
55
  width: w,
47
56
  height: h,
48
57
  gc: gc_id,
49
58
  should_close: false
50
59
  }
51
-
52
- @handle = wid
53
60
  end
54
61
 
55
62
  def present(framebuffer)
56
- return false unless @handle
57
-
58
- window = @windows[@handle]
59
- return false unless window
63
+ return false unless @window
60
64
 
61
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
62
79
 
63
80
  @display.put_image(
64
81
  format: :z_pixmap,
65
- drawable: @handle,
82
+ drawable: window[:id],
66
83
  gc: window[:gc],
67
- width: framebuffer.width,
68
- height: framebuffer.height,
84
+ width: width,
85
+ height: height,
69
86
  dst_x: 0, dst_y: 0,
70
87
  depth: @display.root_depth,
71
- data: buffer
88
+ data: buffer,
89
+ bytes_per_line: pixel_encoder.bytes_per_line(width)
72
90
  )
73
91
 
74
92
  @display.flush
@@ -90,33 +108,53 @@ module RBGL
90
108
  end
91
109
 
92
110
  def should_close?
93
- return false unless @handle
94
-
95
- @windows[@handle]&.[](:should_close) || false
111
+ @window.nil? || @window[:should_close]
96
112
  end
97
113
 
98
114
  def close
99
- return unless @handle
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 }
100
123
 
101
- @windows[@handle][:should_close] = true
102
- @display.destroy_window(@handle)
103
- @windows.delete(@handle)
104
- @handle = nil
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
- # X11 uses BGRX format (blue, green, red, padding)
111
- framebuffer.to_bgra_bytes
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,11 +164,11 @@ module RBGL
126
164
  when :configure_notify
127
165
  Event.new(:resize, width: raw[:width], height: raw[:height])
128
166
  when :client_message
129
- return nil unless raw[:window] == @handle
167
+ return nil unless @window && raw[:window] == @window[:id]
130
168
  return nil unless raw[:message_type] == @display.wm_protocols_atom
131
169
  return nil unless raw[:data32]&.first == @display.wm_delete_window_atom
132
170
 
133
- @windows[@handle][:should_close] = true if @handle && @windows[@handle]
171
+ @window[:should_close] = true
134
172
  Event.new(:close)
135
173
  else
136
174
  nil