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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -1
  3. data/lib/rbgl/engine/buffer.rb +102 -33
  4. data/lib/rbgl/engine/clip_space_clipper.rb +143 -0
  5. data/lib/rbgl/engine/context.rb +117 -64
  6. data/lib/rbgl/engine/framebuffer.rb +41 -32
  7. data/lib/rbgl/engine/pipeline.rb +38 -7
  8. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +105 -0
  9. data/lib/rbgl/engine/rasterizer/line_renderer.rb +72 -0
  10. data/lib/rbgl/engine/rasterizer/point_renderer.rb +35 -0
  11. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +87 -0
  12. data/lib/rbgl/engine/rasterizer.rb +73 -162
  13. data/lib/rbgl/engine/shader/base_shader.rb +55 -0
  14. data/lib/rbgl/engine/shader/builtins.rb +254 -0
  15. data/lib/rbgl/engine/shader/dynamic_data.rb +65 -0
  16. data/lib/rbgl/engine/shader.rb +5 -318
  17. data/lib/rbgl/engine/texture.rb +227 -38
  18. data/lib/rbgl/engine.rb +1 -0
  19. data/lib/rbgl/gui/backend.rb +12 -30
  20. data/lib/rbgl/gui/backend_factory.rb +85 -0
  21. data/lib/rbgl/gui/cocoa/backend.rb +15 -35
  22. data/lib/rbgl/gui/file_backend/bmp_writer.rb +63 -0
  23. data/lib/rbgl/gui/file_backend/frame_writer.rb +28 -0
  24. data/lib/rbgl/gui/file_backend/ppm_writer.rb +25 -0
  25. data/lib/rbgl/gui/file_backend.rb +25 -48
  26. data/lib/rbgl/gui/wayland/backend.rb +108 -26
  27. data/lib/rbgl/gui/wayland/codec.rb +54 -0
  28. data/lib/rbgl/gui/wayland/connection.rb +80 -240
  29. data/lib/rbgl/gui/wayland/event_dispatcher.rb +74 -0
  30. data/lib/rbgl/gui/wayland/global_binder.rb +44 -0
  31. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +51 -0
  32. data/lib/rbgl/gui/wayland/protocol_objects/display.rb +54 -0
  33. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +146 -0
  34. data/lib/rbgl/gui/wayland/protocol_objects/surface.rb +33 -0
  35. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +45 -0
  36. data/lib/rbgl/gui/wayland/protocol_objects.rb +7 -0
  37. data/lib/rbgl/gui/window/event_dispatcher.rb +30 -0
  38. data/lib/rbgl/gui/window/render_loop.rb +58 -0
  39. data/lib/rbgl/gui/window.rb +41 -82
  40. data/lib/rbgl/gui/x11/atom_cache.rb +26 -0
  41. data/lib/rbgl/gui/x11/backend.rb +14 -28
  42. data/lib/rbgl/gui/x11/connection.rb +104 -169
  43. data/lib/rbgl/gui/x11/event_parser.rb +60 -0
  44. data/lib/rbgl/gui/x11/request_encoder.rb +154 -0
  45. data/lib/rbgl/gui/x11/transport.rb +31 -0
  46. data/lib/rbgl/gui.rb +1 -0
  47. data/lib/rbgl/version.rb +1 -1
  48. metadata +31 -4
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "socket"
4
+ require_relative "../backend"
5
+ require_relative "atom_cache"
6
+ require_relative "event_parser"
7
+ require_relative "request_encoder"
8
+ require_relative "transport"
4
9
 
5
10
  module RBGL
6
11
  module GUI
@@ -12,7 +17,7 @@ module RBGL
12
17
  def initialize(display_name)
13
18
  host, display_num, _screen_num = parse_display_name(display_name)
14
19
  @socket = connect(host, display_num)
15
- @next_seq = 1
20
+ @transport = Transport.new(@socket)
16
21
  @resource_id_counter = 0
17
22
  @pending_events = []
18
23
 
@@ -20,66 +25,54 @@ module RBGL
20
25
  end
21
26
 
22
27
  def generate_id
23
- id = @resource_id_base | @resource_id_counter
28
+ masked_counter = @resource_id_counter & @resource_id_mask
29
+ raise GUI::BackendUnavailable, "X11 resource ID space exhausted" if masked_counter != @resource_id_counter
30
+
31
+ id = @resource_id_base | masked_counter
24
32
  @resource_id_counter += 1
25
33
  id
26
34
  end
27
35
 
28
36
  def flush
29
- @socket.flush
37
+ transport.flush
30
38
  end
31
39
 
32
- def pending
33
- ready = IO.select([@socket], nil, nil, 0)
34
- ready ? 1 : 0
40
+ def atom(name)
41
+ atom_cache.fetch(name)
35
42
  end
36
43
 
37
- def create_window(depth:, wid:, parent:, x:, y:, width:, height:,
38
- border_width:, window_class:, visual:, value_mask:, values:)
39
- class_val = case window_class
40
- when :input_output then 1
41
- when :input_only then 2
42
- else 0
43
- end
44
-
45
- mask = 0
46
- value_list = []
47
-
48
- if value_mask.include?(:back_pixel)
49
- mask |= 0x0002
50
- value_list << values[:back_pixel]
51
- end
44
+ def wm_delete_window_atom
45
+ atom(:wm_delete_window)
46
+ end
52
47
 
53
- if value_mask.include?(:event_mask)
54
- mask |= 0x0800
55
- event_mask = 0
56
- values[:event_mask].each do |ev|
57
- event_mask |= case ev
58
- when :exposure then 0x8000
59
- when :key_press then 0x0001
60
- when :key_release then 0x0002
61
- when :button_press then 0x0004
62
- when :button_release then 0x0008
63
- when :pointer_motion then 0x0040
64
- when :structure_notify then 0x020000
65
- else 0
66
- end
67
- end
68
- value_list << event_mask
69
- end
48
+ def wm_protocols_atom
49
+ atom(:wm_protocols)
50
+ end
70
51
 
71
- request = [
72
- depth,
73
- wid,
74
- parent,
75
- x, y,
76
- width, height,
77
- border_width,
78
- class_val,
79
- visual,
80
- mask
81
- ].pack("CVVSSSSSVV") + value_list.pack("V*")
52
+ def enable_wm_delete_window(window)
53
+ change_property(window, :wm_protocols, :atom, [wm_delete_window_atom], format: 32)
54
+ end
82
55
 
56
+ def pending
57
+ transport.pending
58
+ end
59
+
60
+ def create_window(depth:, wid:, parent:, x:, y:, width:, height:,
61
+ border_width:, window_class:, visual:, value_mask:, values:)
62
+ request = request_encoder.create_window_data(
63
+ depth: depth,
64
+ wid: wid,
65
+ parent: parent,
66
+ x: x,
67
+ y: y,
68
+ width: width,
69
+ height: height,
70
+ border_width: border_width,
71
+ window_class: window_class,
72
+ visual: visual,
73
+ value_mask: value_mask,
74
+ values: values
75
+ )
83
76
  send_request(1, request)
84
77
  end
85
78
 
@@ -92,76 +85,41 @@ module RBGL
92
85
  end
93
86
 
94
87
  def create_gc(gc_id, drawable, values = {})
95
- mask = 0
96
- value_list = []
97
-
98
- if values[:foreground]
99
- mask |= 0x0004
100
- value_list << values[:foreground]
101
- end
102
-
103
- if values[:background]
104
- mask |= 0x0008
105
- value_list << values[:background]
106
- end
107
-
108
- request = [gc_id, drawable, mask].pack("VVV") + value_list.pack("V*")
88
+ request = request_encoder.create_gc_data(gc_id, drawable, values)
109
89
  send_request(55, request)
110
90
  end
111
91
 
112
92
  def put_image(format:, drawable:, gc:, width:, height:, dst_x:, dst_y:, depth:, data:)
113
- format_byte = case format
114
- when :bitmap then 0
115
- when :xy_pixmap then 1
116
- when :z_pixmap then 2
117
- else 2
118
- end
119
-
120
- left_pad = 0
121
-
122
- header = [
123
- drawable,
124
- gc,
125
- width, height,
126
- dst_x, dst_y,
127
- left_pad,
128
- depth
129
- ].pack("VVvvvvCC") + "\x00\x00"
130
-
131
- send_request_with_data(72, format_byte, header, data)
132
- end
133
-
134
- def change_property(window, property, type, data, mode: :replace)
135
- mode_val = case mode
136
- when :replace then 0
137
- when :prepend then 1
138
- when :append then 2
139
- else 0
140
- end
141
-
142
- property_atom = get_atom(property)
143
- type_atom = get_atom(type)
144
-
145
- format = 8
146
- data_bytes = data.to_s
147
-
148
- request = [
93
+ format_byte, header, image_data = request_encoder.put_image_data(
94
+ format: format,
95
+ drawable: drawable,
96
+ gc: gc,
97
+ width: width,
98
+ height: height,
99
+ dst_x: dst_x,
100
+ dst_y: dst_y,
101
+ depth: depth,
102
+ data: data
103
+ )
104
+ send_request_with_data(72, format_byte, header, image_data)
105
+ end
106
+
107
+ def change_property(window, property, type, data, mode: :replace, format: 8)
108
+ property_atom = atom(property)
109
+ type_atom = atom(type)
110
+ mode_val, request = request_encoder.change_property_data(
149
111
  window,
150
112
  property_atom,
151
113
  type_atom,
152
- format,
153
- data_bytes.bytesize
154
- ].pack("VVVCV") + "\x00\x00\x00" + pad_to_4(data_bytes)
155
-
114
+ data,
115
+ mode: mode,
116
+ format: format
117
+ )
156
118
  send_request(18, request, mode_val)
157
119
  end
158
120
 
159
121
  def intern_atom(name, only_if_exists: false)
160
- request = [
161
- name.bytesize,
162
- 0
163
- ].pack("vv") + pad_to_4(name)
164
-
122
+ request = request_encoder.intern_atom_data(name)
165
123
  send_request(16, request, only_if_exists ? 1 : 0)
166
124
  flush
167
125
 
@@ -188,10 +146,12 @@ module RBGL
188
146
  private
189
147
 
190
148
  def parse_display_name(name)
191
- if name =~ /^(?:(.+):)?(\d+)(?:\.(\d+))?$/
192
- [::Regexp.last_match(1), ::Regexp.last_match(2).to_i, (::Regexp.last_match(3) || 0).to_i]
149
+ if name =~ /^(?:(.*):)?(\d+)(?:\.(\d+))?$/
150
+ host = ::Regexp.last_match(1)
151
+ host = nil if host&.empty?
152
+ [host, ::Regexp.last_match(2).to_i, (::Regexp.last_match(3) || 0).to_i]
193
153
  else
194
- raise "Invalid display name: #{name}"
154
+ raise GUI::BackendUnavailable, "Invalid X11 display name: #{name}"
195
155
  end
196
156
  end
197
157
 
@@ -217,9 +177,11 @@ module RBGL
217
177
  @socket.flush
218
178
 
219
179
  header = @socket.read(8)
180
+ raise GUI::BackendUnavailable, "X11 connection closed during handshake" unless header&.bytesize == 8
181
+
220
182
  status = header.unpack1("C")
221
183
 
222
- raise "X11 connection failed" unless status == 1
184
+ raise GUI::BackendUnavailable, "X11 connection failed" unless status == 1
223
185
 
224
186
  additional_length = header[6, 2].unpack1("v")
225
187
  data = @socket.read(additional_length * 4)
@@ -247,82 +209,34 @@ module RBGL
247
209
  end
248
210
 
249
211
  def send_request(opcode, data, extra = 0)
250
- length = (4 + data.bytesize + 3) / 4
251
- header = [opcode, extra, length].pack("CCv")
252
-
253
- padding_size = length * 4 - 4 - data.bytesize
254
- padded_data = data + ("\x00" * padding_size)
255
-
256
- @socket.write(header + padded_data)
257
- @next_seq += 1
212
+ transport.write(request_encoder.request_packet(opcode, data, extra))
258
213
  end
259
214
 
260
215
  def send_request_with_data(opcode, extra, header_data, bulk_data)
261
- total_data = header_data + bulk_data
262
- length = (4 + total_data.bytesize + 3) / 4
263
-
264
- header = [opcode, extra, length].pack("CCv")
265
- padding_size = length * 4 - 4 - total_data.bytesize
266
- padded = total_data + ("\x00" * padding_size)
267
-
268
- @socket.write(header + padded)
269
- @next_seq += 1
216
+ transport.write(request_encoder.request_packet_with_data(opcode, extra, header_data, bulk_data))
270
217
  end
271
218
 
272
219
  def read_reply
273
- header = @socket.read(32)
220
+ header = transport.read(32)
274
221
  return nil unless header && header.bytesize == 32
275
222
 
276
223
  additional = header[4, 4].unpack1("V")
277
224
  if additional > 0
278
- header + @socket.read(additional * 4)
225
+ header + transport.read(additional * 4)
279
226
  else
280
227
  header
281
228
  end
282
229
  end
283
230
 
284
231
  def read_event
285
- data = @socket.read(32)
286
- return nil unless data && data.bytesize == 32
287
-
288
- event_type = data.unpack1("C") & 0x7F
289
-
290
- case event_type
291
- when 2
292
- keycode = data[1, 1].unpack1("C")
293
- { type: :key_press, keycode: keycode }
294
- when 3
295
- keycode = data[1, 1].unpack1("C")
296
- { type: :key_release, keycode: keycode }
297
- when 4
298
- x, y = data[24, 4].unpack("ss")
299
- button = data[1, 1].unpack1("C")
300
- { type: :button_press, x: x, y: y, button: button }
301
- when 5
302
- x, y = data[24, 4].unpack("ss")
303
- button = data[1, 1].unpack1("C")
304
- { type: :button_release, x: x, y: y, button: button }
305
- when 6
306
- x, y = data[24, 4].unpack("ss")
307
- { type: :motion_notify, x: x, y: y }
308
- when 12
309
- { type: :exposure }
310
- when 22
311
- width, height = data[20, 4].unpack("vv")
312
- { type: :configure_notify, width: width, height: height }
313
- when 33
314
- window = data[4, 4].unpack1("V")
315
- atom = data[8, 4].unpack1("V")
316
- { type: :client_message, window: window, data: atom }
317
- else
318
- { type: :unknown, code: event_type }
319
- end
232
+ event_parser.parse(transport.read(32))
320
233
  end
321
234
 
322
- def get_atom(name)
235
+ def resolve_atom(name)
323
236
  case name
324
237
  when :wm_name then 39
325
238
  when :string then 31
239
+ when :atom then 4
326
240
  when :wm_protocols then intern_atom("WM_PROTOCOLS")
327
241
  when :wm_delete_window then intern_atom("WM_DELETE_WINDOW")
328
242
  else
@@ -330,14 +244,35 @@ module RBGL
330
244
  end
331
245
  end
332
246
 
247
+ def pack_property_data(data, format)
248
+ request_encoder.pack_property_data(data, format)
249
+ end
250
+
333
251
  def pad_to_4(str)
334
- padding = (4 - str.bytesize % 4) % 4
335
- str + ("\x00" * padding)
252
+ request_encoder.pad_to_4(str)
336
253
  end
337
254
 
338
255
  def pad_length(len)
339
256
  ((len + 3) / 4) * 4
340
257
  end
258
+
259
+ def transport
260
+ return @transport if @transport&.socket.equal?(@socket)
261
+
262
+ @transport = Transport.new(@socket)
263
+ end
264
+
265
+ def request_encoder
266
+ @request_encoder ||= RequestEncoder.new
267
+ end
268
+
269
+ def event_parser
270
+ @event_parser ||= EventParser.new
271
+ end
272
+
273
+ def atom_cache
274
+ @atom_cache ||= AtomCache.new { |name| resolve_atom(name) }
275
+ end
341
276
  end
342
277
  end
343
278
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module GUI
5
+ module X11
6
+ class EventParser
7
+ def parse(data)
8
+ return nil unless data && data.bytesize == 32
9
+
10
+ event_type = data.unpack1("C") & 0x7F
11
+
12
+ case event_type
13
+ when 2
14
+ key_event(:key_press, data)
15
+ when 3
16
+ key_event(:key_release, data)
17
+ when 4
18
+ button_event(:button_press, data)
19
+ when 5
20
+ button_event(:button_release, data)
21
+ when 6
22
+ x, y = data[24, 4].unpack("ss")
23
+ { type: :motion_notify, x: x, y: y }
24
+ when 12
25
+ { type: :exposure }
26
+ when 22
27
+ width, height = data[20, 4].unpack("vv")
28
+ { type: :configure_notify, width: width, height: height }
29
+ when 33
30
+ {
31
+ type: :client_message,
32
+ format: data[1, 1].unpack1("C"),
33
+ window: data[4, 4].unpack1("V"),
34
+ message_type: data[8, 4].unpack1("V"),
35
+ data32: data[12, 20].unpack("V5")
36
+ }
37
+ else
38
+ { type: :unknown, code: event_type }
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def key_event(type, data)
45
+ { type: type, keycode: data[1, 1].unpack1("C") }
46
+ end
47
+
48
+ def button_event(type, data)
49
+ x, y = data[24, 4].unpack("ss")
50
+ {
51
+ type: type,
52
+ x: x,
53
+ y: y,
54
+ button: data[1, 1].unpack1("C")
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module GUI
5
+ module X11
6
+ class RequestEncoder
7
+ EVENT_MASKS = {
8
+ exposure: 0x8000,
9
+ key_press: 0x0001,
10
+ key_release: 0x0002,
11
+ button_press: 0x0004,
12
+ button_release: 0x0008,
13
+ pointer_motion: 0x0040,
14
+ structure_notify: 0x020000
15
+ }.freeze
16
+
17
+ WINDOW_CLASSES = {
18
+ input_output: 1,
19
+ input_only: 2
20
+ }.freeze
21
+
22
+ PROPERTY_MODES = {
23
+ replace: 0,
24
+ prepend: 1,
25
+ append: 2
26
+ }.freeze
27
+
28
+ IMAGE_FORMATS = {
29
+ bitmap: 0,
30
+ xy_pixmap: 1,
31
+ z_pixmap: 2
32
+ }.freeze
33
+
34
+ def create_window_data(depth:, wid:, parent:, x:, y:, width:, height:,
35
+ border_width:, window_class:, visual:, value_mask:, values:)
36
+ mask = 0
37
+ value_list = []
38
+
39
+ if value_mask.include?(:back_pixel)
40
+ mask |= 0x0002
41
+ value_list << values[:back_pixel]
42
+ end
43
+
44
+ if value_mask.include?(:event_mask)
45
+ mask |= 0x0800
46
+ value_list << encode_event_mask(values[:event_mask])
47
+ end
48
+
49
+ [
50
+ depth,
51
+ wid,
52
+ parent,
53
+ x, y,
54
+ width, height,
55
+ border_width,
56
+ WINDOW_CLASSES.fetch(window_class, 0),
57
+ visual,
58
+ mask
59
+ ].pack("CVVSSSSSVV") + value_list.pack("V*")
60
+ end
61
+
62
+ def create_gc_data(gc_id, drawable, values = {})
63
+ mask = 0
64
+ value_list = []
65
+
66
+ if values[:foreground]
67
+ mask |= 0x0004
68
+ value_list << values[:foreground]
69
+ end
70
+
71
+ if values[:background]
72
+ mask |= 0x0008
73
+ value_list << values[:background]
74
+ end
75
+
76
+ [gc_id, drawable, mask].pack("VVV") + value_list.pack("V*")
77
+ end
78
+
79
+ def put_image_data(format:, drawable:, gc:, width:, height:, dst_x:, dst_y:, depth:, data:)
80
+ format_byte = IMAGE_FORMATS.fetch(format, IMAGE_FORMATS[:z_pixmap])
81
+ header = [
82
+ drawable,
83
+ gc,
84
+ width, height,
85
+ dst_x, dst_y,
86
+ 0,
87
+ depth
88
+ ].pack("VVvvvvCC") + "\x00\x00"
89
+
90
+ [format_byte, header, data]
91
+ end
92
+
93
+ def change_property_data(window, property_atom, type_atom, data, mode: :replace, format: 8)
94
+ data_bytes, value_count = pack_property_data(data, format)
95
+ request = [
96
+ window,
97
+ property_atom,
98
+ type_atom,
99
+ format,
100
+ value_count
101
+ ].pack("VVVCV") + "\x00\x00\x00" + pad_to_4(data_bytes)
102
+
103
+ [PROPERTY_MODES.fetch(mode, 0), request]
104
+ end
105
+
106
+ def intern_atom_data(name)
107
+ [name.bytesize, 0].pack("vv") + pad_to_4(name)
108
+ end
109
+
110
+ def request_packet(opcode, data, extra = 0)
111
+ length = (4 + data.bytesize + 3) / 4
112
+ header = [opcode, extra, length].pack("CCv")
113
+ padding = "\x00" * (length * 4 - 4 - data.bytesize)
114
+ header + data + padding
115
+ end
116
+
117
+ def request_packet_with_data(opcode, extra, header_data, bulk_data)
118
+ total_data = header_data + bulk_data
119
+ length = (4 + total_data.bytesize + 3) / 4
120
+ header = [opcode, extra, length].pack("CCv")
121
+ padding = "\x00" * (length * 4 - 4 - total_data.bytesize)
122
+ header + total_data + padding
123
+ end
124
+
125
+ def pack_property_data(data, format)
126
+ case format
127
+ when 8
128
+ bytes = data.to_s
129
+ [bytes, bytes.bytesize]
130
+ when 16
131
+ values = Array(data)
132
+ [values.pack("v*"), values.size]
133
+ when 32
134
+ values = Array(data)
135
+ [values.pack("V*"), values.size]
136
+ else
137
+ raise ArgumentError, "Unsupported property format: #{format}"
138
+ end
139
+ end
140
+
141
+ def pad_to_4(str)
142
+ padding = (4 - str.bytesize % 4) % 4
143
+ str + ("\x00" * padding)
144
+ end
145
+
146
+ private
147
+
148
+ def encode_event_mask(events)
149
+ Array(events).sum { |event| EVENT_MASKS.fetch(event, 0) }
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBGL
4
+ module GUI
5
+ module X11
6
+ class Transport
7
+ attr_reader :socket
8
+
9
+ def initialize(socket)
10
+ @socket = socket
11
+ end
12
+
13
+ def write(data)
14
+ @socket.write(data)
15
+ end
16
+
17
+ def read(length)
18
+ @socket.read(length)
19
+ end
20
+
21
+ def flush
22
+ @socket.flush
23
+ end
24
+
25
+ def pending
26
+ IO.select([@socket], nil, nil, 0) ? 1 : 0
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/rbgl/gui.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "gui/event"
4
4
  require_relative "gui/backend"
5
+ require_relative "gui/backend_factory"
5
6
  require_relative "gui/file_backend"
6
7
  require_relative "gui/window"
7
8
 
data/lib/rbgl/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RBGL
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.0"
5
5
  end