wgpu 1.2.0 → 1.2.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +18 -2
  4. data/docs/README.md +2 -2
  5. data/docs/api_coverage.md +23 -15
  6. data/docs/async.md +13 -1
  7. data/docs/command_encoding.md +25 -0
  8. data/docs/errors.md +13 -0
  9. data/docs/getting_started_compute.md +1 -0
  10. data/docs/getting_started_rendering.md +5 -0
  11. data/docs/pipeline_descriptors.md +7 -2
  12. data/docs/releasing.md +11 -1
  13. data/docs/resource_lifetime.md +25 -2
  14. data/docs/texture_readback.md +27 -0
  15. data/docs/troubleshooting.md +5 -0
  16. data/docs/upgrading_wgpu_native.md +16 -5
  17. data/lib/wgpu/async_task.rb +19 -0
  18. data/lib/wgpu/commands/command_buffer.rb +14 -1
  19. data/lib/wgpu/commands/command_encoder.rb +82 -27
  20. data/lib/wgpu/commands/compute_pass.rb +49 -6
  21. data/lib/wgpu/commands/render_bundle.rb +9 -1
  22. data/lib/wgpu/commands/render_bundle_encoder.rb +63 -9
  23. data/lib/wgpu/commands/render_pass.rb +116 -14
  24. data/lib/wgpu/core/adapter.rb +95 -13
  25. data/lib/wgpu/core/async_waiter.rb +30 -4
  26. data/lib/wgpu/core/canvas_context.rb +37 -2
  27. data/lib/wgpu/core/device.rb +377 -70
  28. data/lib/wgpu/core/instance.rb +20 -0
  29. data/lib/wgpu/core/queue.rb +143 -43
  30. data/lib/wgpu/core/surface.rb +55 -7
  31. data/lib/wgpu/data_types.rb +16 -0
  32. data/lib/wgpu/descriptor_helpers.rb +87 -0
  33. data/lib/wgpu/error.rb +15 -0
  34. data/lib/wgpu/native/abi_verifier.rb +37 -3
  35. data/lib/wgpu/native/callbacks.rb +6 -0
  36. data/lib/wgpu/native/capabilities.rb +16 -2
  37. data/lib/wgpu/native/distribution.rb +63 -0
  38. data/lib/wgpu/native/enum_helper.rb +17 -0
  39. data/lib/wgpu/native/enums.rb +8 -0
  40. data/lib/wgpu/native/fixtures/webgpu-v27.0.4.0-enums.h +848 -0
  41. data/lib/wgpu/native/functions.rb +14 -0
  42. data/lib/wgpu/native/installer.rb +55 -18
  43. data/lib/wgpu/native/loader.rb +22 -0
  44. data/lib/wgpu/native/structs.rb +18 -1
  45. data/lib/wgpu/native_resource.rb +190 -3
  46. data/lib/wgpu/pipeline/bind_group.rb +16 -7
  47. data/lib/wgpu/pipeline/bind_group_layout.rb +20 -7
  48. data/lib/wgpu/pipeline/compute_pipeline.rb +25 -37
  49. data/lib/wgpu/pipeline/pipeline_layout.rb +12 -4
  50. data/lib/wgpu/pipeline/render_pipeline.rb +33 -48
  51. data/lib/wgpu/pipeline/shader_module.rb +57 -32
  52. data/lib/wgpu/resources/buffer.rb +330 -59
  53. data/lib/wgpu/resources/query_set.rb +13 -1
  54. data/lib/wgpu/resources/sampler.rb +10 -3
  55. data/lib/wgpu/resources/texture.rb +49 -12
  56. data/lib/wgpu/resources/texture_view.rb +25 -5
  57. data/lib/wgpu/texture_format.rb +14 -0
  58. data/lib/wgpu/version.rb +1 -1
  59. data/lib/wgpu/window.rb +26 -0
  60. data/sig/wgpu.rbs +85 -5
  61. metadata +12 -4
@@ -4,6 +4,15 @@ module WGPU
4
4
  class RenderPass
5
5
  attr_reader :handle
6
6
 
7
+ # Begins a render pass owned by the command encoder.
8
+ # @param encoder [CommandEncoder] owning encoder
9
+ # @param label [String, nil] optional debug label
10
+ # @param color_attachments [Array<Hash>] color attachment descriptors
11
+ # @param depth_stencil_attachment [Hash, nil] depth/stencil attachment descriptor
12
+ # @param occlusion_query_set [QuerySet, nil] query set for occlusion queries
13
+ # @param timestamp_writes [Hash, nil] beginning and ending timestamp query settings
14
+ # @param max_draw_count [Integer, nil] implementation hint for the maximum draw count
15
+ # @raise [CommandError] if the native pass cannot be created
7
16
  def initialize(encoder, label: nil, color_attachments:, depth_stencil_attachment: nil, occlusion_query_set: nil, timestamp_writes: nil, max_draw_count: nil)
8
17
  @encoder = encoder
9
18
  @pointers = []
@@ -25,10 +34,10 @@ module WGPU
25
34
  desc[:depth_stencil_attachment] = nil
26
35
  end
27
36
 
28
- desc[:occlusion_query_set] = occlusion_query_set&.handle
37
+ desc[:occlusion_query_set] = (occlusion_query_set && NativeResource.checked_handle(occlusion_query_set, expected_class: QuerySet))
29
38
  if timestamp_writes
30
39
  ts = Native::RenderPassTimestampWrites.new
31
- ts[:query_set] = timestamp_writes.fetch(:query_set).handle
40
+ ts[:query_set] = NativeResource.checked_handle(timestamp_writes.fetch(:query_set), expected_class: QuerySet)
32
41
  ts[:beginning_of_pass_write_index] = timestamp_writes[:beginning_of_pass_write_index] || 0xFFFFFFFF
33
42
  ts[:end_of_pass_write_index] = timestamp_writes[:end_of_pass_write_index] || 0xFFFFFFFF
34
43
  @pointers << ts
@@ -37,51 +46,98 @@ module WGPU
37
46
  desc[:timestamp_writes] = nil
38
47
  end
39
48
 
40
- @handle = Native.wgpuCommandEncoderBeginRenderPass(encoder.handle, desc)
49
+ @handle = Native.wgpuCommandEncoderBeginRenderPass(NativeResource.checked_handle(encoder, expected_class: CommandEncoder), desc)
41
50
  raise CommandError, "Failed to begin render pass" if @handle.null?
42
51
  end
43
52
 
53
+ # Selects the render pipeline used by subsequent draws.
54
+ # @param pipeline [RenderPipeline] pipeline to bind
55
+ # @return [void]
44
56
  def set_pipeline(pipeline)
45
- Native.wgpuRenderPassEncoderSetPipeline(@handle, pipeline.handle)
57
+ Native.wgpuRenderPassEncoderSetPipeline(@handle, NativeResource.checked_handle(pipeline, expected_class: RenderPipeline))
46
58
  end
47
59
 
60
+ # Binds a resource group for subsequent draws.
61
+ # @param index [Integer] bind group index
62
+ # @param bind_group [BindGroup] group to bind
63
+ # @param dynamic_offsets [Array<Integer>] dynamic buffer offsets
64
+ # @return [void]
48
65
  def set_bind_group(index, bind_group, dynamic_offsets: [])
49
66
  if dynamic_offsets.empty?
50
- Native.wgpuRenderPassEncoderSetBindGroup(@handle, index, bind_group.handle, 0, nil)
67
+ Native.wgpuRenderPassEncoderSetBindGroup(@handle, index, NativeResource.checked_handle(bind_group, expected_class: BindGroup), 0, nil)
51
68
  else
52
69
  offsets_ptr = FFI::MemoryPointer.new(:uint32, dynamic_offsets.size)
53
70
  offsets_ptr.write_array_of_uint32(dynamic_offsets)
54
- Native.wgpuRenderPassEncoderSetBindGroup(@handle, index, bind_group.handle, dynamic_offsets.size, offsets_ptr)
71
+ Native.wgpuRenderPassEncoderSetBindGroup(@handle, index, NativeResource.checked_handle(bind_group, expected_class: BindGroup), dynamic_offsets.size, offsets_ptr)
55
72
  end
56
73
  end
57
74
 
75
+ # Binds a vertex buffer to a slot.
76
+ # @param slot [Integer] vertex buffer slot
77
+ # @param buffer [Buffer] vertex data buffer
78
+ # @param offset [Integer] first byte to bind
79
+ # @param size [Integer, nil] number of bytes to bind
80
+ # @return [void]
58
81
  def set_vertex_buffer(slot, buffer, offset: 0, size: nil)
59
82
  size ||= buffer.size - offset
60
- Native.wgpuRenderPassEncoderSetVertexBuffer(@handle, slot, buffer.handle, offset, size)
83
+ Native.wgpuRenderPassEncoderSetVertexBuffer(@handle, slot, NativeResource.checked_handle(buffer, expected_class: Buffer), offset, size)
61
84
  end
62
85
 
86
+ # Binds an index buffer for indexed draws.
87
+ # @param buffer [Buffer] index data buffer
88
+ # @param format [Symbol, Integer] index element format
89
+ # @param offset [Integer] first byte to bind
90
+ # @param size [Integer, nil] number of bytes to bind
91
+ # @return [void]
63
92
  def set_index_buffer(buffer, format, offset: 0, size: nil)
64
93
  size ||= buffer.size - offset
65
94
  format_value = Native::EnumHelper.coerce(Native::IndexFormat, format, name: "index format")
66
- Native.wgpuRenderPassEncoderSetIndexBuffer(@handle, buffer.handle, format_value, offset, size)
95
+ Native.wgpuRenderPassEncoderSetIndexBuffer(@handle, NativeResource.checked_handle(buffer, expected_class: Buffer), format_value, offset, size)
67
96
  end
68
97
 
98
+ # Records a non-indexed draw.
99
+ # @return [void]
69
100
  def draw(vertex_count, instance_count: 1, first_vertex: 0, first_instance: 0)
70
101
  Native.wgpuRenderPassEncoderDraw(@handle, vertex_count, instance_count, first_vertex, first_instance)
71
102
  end
72
103
 
104
+ # Records an indexed draw.
105
+ # @return [void]
73
106
  def draw_indexed(index_count, instance_count: 1, first_index: 0, base_vertex: 0, first_instance: 0)
74
107
  Native.wgpuRenderPassEncoderDrawIndexed(@handle, index_count, instance_count, first_index, base_vertex, first_instance)
75
108
  end
76
109
 
110
+ # Sets the viewport used by subsequent draw calls.
111
+ #
112
+ # @param x [Numeric] left coordinate in pixels
113
+ # @param y [Numeric] top coordinate in pixels
114
+ # @param width [Numeric] viewport width in pixels
115
+ # @param height [Numeric] viewport height in pixels
116
+ # @param min_depth [Numeric] minimum depth value
117
+ # @param max_depth [Numeric] maximum depth value
118
+ # @return [void]
77
119
  def set_viewport(x, y, width, height, min_depth: 0.0, max_depth: 1.0)
78
120
  Native.wgpuRenderPassEncoderSetViewport(@handle, x, y, width, height, min_depth, max_depth)
79
121
  end
80
122
 
123
+ # Restricts rasterization to a rectangular region.
124
+ #
125
+ # @param x [Integer] left coordinate in pixels
126
+ # @param y [Integer] top coordinate in pixels
127
+ # @param width [Integer] rectangle width in pixels
128
+ # @param height [Integer] rectangle height in pixels
129
+ # @return [void]
81
130
  def set_scissor_rect(x, y, width, height)
82
131
  Native.wgpuRenderPassEncoderSetScissorRect(@handle, x, y, width, height)
83
132
  end
84
133
 
134
+ # Sets the constant color used by blend factors.
135
+ #
136
+ # @param r [Numeric] red component
137
+ # @param g [Numeric] green component
138
+ # @param b [Numeric] blue component
139
+ # @param a [Numeric] alpha component
140
+ # @return [void]
85
141
  def set_blend_constant(r: 0.0, g: 0.0, b: 0.0, a: 1.0)
86
142
  color = Native::Color.new
87
143
  color[:r] = r
@@ -91,33 +147,62 @@ module WGPU
91
147
  Native.wgpuRenderPassEncoderSetBlendConstant(@handle, color.to_ptr)
92
148
  end
93
149
 
150
+ # Sets the stencil reference used by subsequent draw calls.
151
+ #
152
+ # @param reference [Integer] unsigned stencil reference
153
+ # @return [void]
94
154
  def set_stencil_reference(reference)
95
155
  Native.wgpuRenderPassEncoderSetStencilReference(@handle, reference)
96
156
  end
97
157
 
158
+ # Draws using non-indexed arguments stored in a buffer.
159
+ #
160
+ # @param buffer [Buffer] buffer containing the indirect arguments
161
+ # @param offset [Integer] byte offset of the arguments
162
+ # @return [void]
98
163
  def draw_indirect(buffer, offset: 0)
99
- Native.wgpuRenderPassEncoderDrawIndirect(@handle, buffer.handle, offset)
164
+ Native.wgpuRenderPassEncoderDrawIndirect(@handle, NativeResource.checked_handle(buffer, expected_class: Buffer), offset)
100
165
  end
101
166
 
167
+ # Draws using indexed arguments stored in a buffer.
168
+ #
169
+ # @param buffer [Buffer] buffer containing the indirect arguments
170
+ # @param offset [Integer] byte offset of the arguments
171
+ # @return [void]
102
172
  def draw_indexed_indirect(buffer, offset: 0)
103
- Native.wgpuRenderPassEncoderDrawIndexedIndirect(@handle, buffer.handle, offset)
173
+ Native.wgpuRenderPassEncoderDrawIndexedIndirect(@handle, NativeResource.checked_handle(buffer, expected_class: Buffer), offset)
104
174
  end
105
175
 
176
+ # Executes pre-recorded render bundles.
177
+ #
178
+ # @param bundles [Array<RenderBundle>] bundles to execute in order
179
+ # @return [void]
106
180
  def execute_bundles(bundles)
107
- bundle_handles = bundles.map(&:handle)
181
+ bundle_handles = bundles.map { |bundle| NativeResource.checked_handle(bundle, expected_class: RenderBundle) }
108
182
  bundles_ptr = FFI::MemoryPointer.new(:pointer, bundle_handles.size)
109
183
  bundles_ptr.write_array_of_pointer(bundle_handles)
110
184
  Native.wgpuRenderPassEncoderExecuteBundles(@handle, bundle_handles.size, bundles_ptr)
111
185
  end
112
186
 
187
+ # Begins an occlusion query for subsequent draw calls.
188
+ #
189
+ # @param query_index [Integer] destination index in the pass query set
190
+ # @return [void]
113
191
  def begin_occlusion_query(query_index)
114
192
  Native.wgpuRenderPassEncoderBeginOcclusionQuery(@handle, query_index)
115
193
  end
116
194
 
195
+ # Ends the active occlusion query.
196
+ #
197
+ # @return [void]
117
198
  def end_occlusion_query
118
199
  Native.wgpuRenderPassEncoderEndOcclusionQuery(@handle)
119
200
  end
120
201
 
202
+ # Starts a labeled group in GPU debugging tools.
203
+ #
204
+ # @param label [String] group label
205
+ # @return [void]
121
206
  def push_debug_group(label)
122
207
  label_view = Native::StringView.new
123
208
  label_ptr = FFI::MemoryPointer.from_string(label)
@@ -126,10 +211,17 @@ module WGPU
126
211
  Native.wgpuRenderPassEncoderPushDebugGroup(@handle, label_view)
127
212
  end
128
213
 
214
+ # Ends the most recently pushed debug group.
215
+ #
216
+ # @return [void]
129
217
  def pop_debug_group
130
218
  Native.wgpuRenderPassEncoderPopDebugGroup(@handle)
131
219
  end
132
220
 
221
+ # Inserts a labeled point in GPU debugging tools.
222
+ #
223
+ # @param label [String] marker label
224
+ # @return [void]
133
225
  def insert_debug_marker(label)
134
226
  label_view = Native::StringView.new
135
227
  label_ptr = FFI::MemoryPointer.from_string(label)
@@ -138,6 +230,8 @@ module WGPU
138
230
  Native.wgpuRenderPassEncoderInsertDebugMarker(@handle, label_view)
139
231
  end
140
232
 
233
+ # Ends the pass if it has not already ended.
234
+ # @return [void]
141
235
  def end_pass
142
236
  return if @ended
143
237
 
@@ -146,14 +240,22 @@ module WGPU
146
240
  @encoder.send(:pass_ended, self)
147
241
  end
148
242
 
243
+ # Ends the pass.
244
+ # @return [void]
149
245
  def end
150
246
  end_pass
151
247
  end
152
248
 
249
+ # Reports whether the pass has ended.
250
+ # @return [Boolean]
153
251
  def ended?
154
252
  @ended
155
253
  end
156
254
 
255
+ # Releases the native render pass encoder handle.
256
+ #
257
+ # Calling this method more than once has no effect.
258
+ # @return [void]
157
259
  def release
158
260
  return if @handle.null?
159
261
  Native.wgpuRenderPassEncoderRelease(@handle)
@@ -181,9 +283,9 @@ module WGPU
181
283
  attachments.each_with_index do |att, i|
182
284
  ca = Native::RenderPassColorAttachment.new(ptr + (i * Native::RenderPassColorAttachment.size))
183
285
  ca[:next_in_chain] = nil
184
- ca[:view] = att[:view].handle
286
+ ca[:view] = NativeResource.checked_handle(att[:view], expected_class: TextureView)
185
287
  ca[:depth_slice] = att[:depth_slice] || 0xFFFFFFFF
186
- ca[:resolve_target] = att[:resolve_target]&.handle
288
+ ca[:resolve_target] = (att[:resolve_target] && NativeResource.checked_handle(att[:resolve_target], expected_class: TextureView))
187
289
  ca[:load_op] = Native::EnumHelper.coerce(Native::LoadOp, att[:load_op] || :clear, name: "load op")
188
290
  ca[:store_op] = Native::EnumHelper.coerce(Native::StoreOp, att[:store_op] || :store, name: "store op")
189
291
 
@@ -201,7 +303,7 @@ module WGPU
201
303
  ds = Native::RenderPassDepthStencilAttachment.new
202
304
  @pointers << ds
203
305
 
204
- ds[:view] = att[:view].handle
306
+ ds[:view] = NativeResource.checked_handle(att[:view], expected_class: TextureView)
205
307
  ds[:depth_load_op] = Native::EnumHelper.coerce(
206
308
  Native::LoadOp,
207
309
  att[:depth_load_op] || :clear,
@@ -4,27 +4,63 @@ module WGPU
4
4
  class Adapter
5
5
  attr_reader :handle, :instance
6
6
 
7
+ # Wraps an existing native adapter handle.
8
+ # @param handle [FFI::Pointer] native adapter handle
9
+ # @param instance [Instance, nil] instance that owns the adapter
10
+ # @return [Adapter]
7
11
  def self.from_handle(handle, instance: nil)
8
- adapter = allocate
9
- adapter.instance_variable_set(:@handle, handle)
12
+ adapter = adopt_native_handle(handle)
10
13
  adapter.instance_variable_set(:@instance, instance)
11
14
  adapter
12
15
  end
13
16
 
17
+ # Requests an adapter and waits for the native callback.
18
+ #
19
+ # @param instance [Instance] instance used for discovery and callback progress
20
+ # @param power_preference [Symbol, Integer] preferred power profile
21
+ # @param backend [Symbol, Integer, nil] backend to restrict discovery to
22
+ # @param feature_level [Symbol, Integer] requested WebGPU feature level
23
+ # @param force_fallback_adapter [Boolean] whether to require a fallback adapter
24
+ # @param compatible_surface [Surface, nil] surface the adapter must support
25
+ # @param timeout [Numeric, nil] maximum wait time in seconds
26
+ # @return [Adapter] requested adapter
27
+ # @raise [AdapterError] if no adapter can be acquired
28
+ # @raise [TimeoutError] if the request exceeds +timeout+
14
29
  def self.request(instance, power_preference: :high_performance, backend: nil, feature_level: :core,
15
30
  force_fallback_adapter: false, compatible_surface: nil, timeout: nil)
31
+ timeout = AsyncWaiter.normalize_timeout(timeout)
16
32
  adapter_ptr = FFI::MemoryPointer.new(:pointer)
17
- status_holder = { value: nil, message: nil }
33
+ status_holder = {
34
+ done: false,
35
+ value: nil,
36
+ message: nil,
37
+ abandoned: false,
38
+ cleanup_claimed: false,
39
+ mutex: Mutex.new
40
+ }
18
41
 
42
+ callback_token = nil
19
43
  callback = FFI::Function.new(
20
44
  :void, [:uint32, :pointer, Native::StringView.by_value, :pointer, :pointer]
21
45
  ) do |status, adapter, message, _userdata1, _userdata2|
22
- status_holder[:value] = Native::RequestAdapterStatus[status]
23
- if message[:data] && !message[:data].null? && message[:length] > 0
24
- status_holder[:message] = message[:data].read_string(message[:length])
46
+ abandoned_adapter = nil
47
+ begin
48
+ status_holder[:mutex].synchronize do
49
+ status_holder[:value] = Native::RequestAdapterStatus[status]
50
+ if message[:data] && !message[:data].null? && message[:length] > 0
51
+ status_holder[:message] = message[:data].read_string(message[:length])
52
+ end
53
+ adapter_ptr.write_pointer(adapter)
54
+ status_holder[:done] = true
55
+ if status_holder[:abandoned] && !status_holder[:cleanup_claimed]
56
+ status_holder[:cleanup_claimed] = true
57
+ abandoned_adapter = adapter
58
+ end
59
+ end
60
+ Native.wgpuAdapterRelease(abandoned_adapter) if abandoned_adapter && !abandoned_adapter.null?
61
+ ensure
62
+ CallbackKeepalive.release(instance, callback_token)
25
63
  end
26
- adapter_ptr.write_pointer(adapter)
27
- status_holder[:done] = true
28
64
  end
29
65
 
30
66
  options = Native::RequestAdapterOptions.new
@@ -45,7 +81,7 @@ module WGPU
45
81
  backend || :undefined,
46
82
  name: "backend type"
47
83
  )
48
- options[:compatible_surface] = compatible_surface&.handle
84
+ options[:compatible_surface] = (compatible_surface && NativeResource.checked_handle(compatible_surface, expected_class: Surface))
49
85
 
50
86
  callback_info = Native::RequestAdapterCallbackInfo.new
51
87
  callback_info[:next_in_chain] = nil
@@ -55,12 +91,26 @@ module WGPU
55
91
  callback_info[:userdata2] = nil
56
92
 
57
93
  callback_token = CallbackKeepalive.retain(instance, callback)
94
+ future =
95
+ begin
96
+ Native.wgpuInstanceRequestAdapter(NativeResource.checked_handle(instance, expected_class: Instance), options, callback_info)
97
+ rescue StandardError
98
+ CallbackKeepalive.release(instance, callback_token)
99
+ raise
100
+ end
101
+
58
102
  begin
59
- status_holder[:done] = false
60
- future = Native.wgpuInstanceRequestAdapter(instance.handle, options, callback_info)
61
103
  AsyncWaiter.wait(status_holder: status_holder, instance: instance, future: future, timeout: timeout)
62
- ensure
63
- CallbackKeepalive.release(instance, callback_token)
104
+ rescue StandardError
105
+ abandoned_adapter = status_holder[:mutex].synchronize do
106
+ status_holder[:abandoned] = true
107
+ next unless status_holder[:done] && !status_holder[:cleanup_claimed]
108
+
109
+ status_holder[:cleanup_claimed] = true
110
+ adapter_ptr.read_pointer
111
+ end
112
+ Native.wgpuAdapterRelease(abandoned_adapter) if abandoned_adapter && !abandoned_adapter.null?
113
+ raise
64
114
  end
65
115
 
66
116
  handle = adapter_ptr.read_pointer
@@ -72,11 +122,16 @@ module WGPU
72
122
  new(handle, instance: instance)
73
123
  end
74
124
 
125
+ # Wraps a native adapter handle.
126
+ # @param handle [FFI::Pointer] native adapter handle
127
+ # @param instance [Instance, nil] instance that owns the adapter
75
128
  def initialize(handle, instance: nil)
76
129
  @handle = handle
77
130
  @instance = instance
78
131
  end
79
132
 
133
+ # Requests a logical device from this adapter.
134
+ # @return [Device]
80
135
  def request_device(label: nil, required_features: [], required_limits: nil, timeout: nil)
81
136
  Device.request(
82
137
  self,
@@ -87,6 +142,8 @@ module WGPU
87
142
  )
88
143
  end
89
144
 
145
+ # Requests a logical device on a background task.
146
+ # @return [AsyncTask] task yielding a {Device}
90
147
  def request_device_async(label: nil, required_features: [], required_limits: nil, timeout: nil)
91
148
  AsyncTask.new do
92
149
  request_device(
@@ -98,6 +155,8 @@ module WGPU
98
155
  end
99
156
  end
100
157
 
158
+ # Returns identifying and backend information for the adapter.
159
+ # @return [Hash]
101
160
  def info
102
161
  info_struct = Native::AdapterInfo.new
103
162
  Native.wgpuAdapterGetInfo(@handle, info_struct)
@@ -117,22 +176,32 @@ module WGPU
117
176
  result
118
177
  end
119
178
 
179
+ # Returns the adapter's device name.
180
+ # @return [String]
120
181
  def name
121
182
  info[:device]
122
183
  end
123
184
 
185
+ # Returns the adapter vendor name.
186
+ # @return [String]
124
187
  def vendor
125
188
  info[:vendor]
126
189
  end
127
190
 
191
+ # Returns the backend used by the adapter.
192
+ # @return [Symbol, Integer]
128
193
  def backend_type
129
194
  info[:backend_type]
130
195
  end
131
196
 
197
+ # Returns the adapter's device classification.
198
+ # @return [Symbol, Integer]
132
199
  def adapter_type
133
200
  info[:adapter_type]
134
201
  end
135
202
 
203
+ # Lists optional features supported by the adapter.
204
+ # @return [Array<Symbol>]
136
205
  def features
137
206
  supported = Native::SupportedFeatures.new
138
207
  Native.wgpuAdapterGetFeatures(@handle, supported)
@@ -144,12 +213,19 @@ module WGPU
144
213
  end
145
214
  end
146
215
  result
216
+ ensure
217
+ Native.wgpuSupportedFeaturesFreeMembers(supported) if supported
147
218
  end
148
219
 
220
+ # Reports whether the adapter supports a feature.
221
+ # @param feature [Symbol] feature name
222
+ # @return [Boolean]
149
223
  def has_feature?(feature)
150
224
  features.include?(feature)
151
225
  end
152
226
 
227
+ # Returns the adapter resource limits.
228
+ # @return [Hash{Symbol => Integer}]
153
229
  def limits
154
230
  supported = Native::SupportedLimits.new
155
231
  supported[:next_in_chain] = nil
@@ -157,11 +233,17 @@ module WGPU
157
233
  limits_to_hash(supported[:limits])
158
234
  end
159
235
 
236
+ # Returns a concise human-readable adapter description.
237
+ # @return [String]
160
238
  def summary
161
239
  info_hash = info
162
240
  "#{info_hash[:device]} (#{info_hash[:adapter_type]}) via #{info_hash[:backend_type]}"
163
241
  end
164
242
 
243
+ # Releases the native adapter handle.
244
+ #
245
+ # Calling this method more than once has no effect.
246
+ # @return [void]
165
247
  def release
166
248
  return if @handle.null?
167
249
  Native.wgpuAdapterRelease(@handle)
@@ -10,10 +10,16 @@ module WGPU
10
10
 
11
11
  module_function
12
12
 
13
+ # Returns the delay between callback progress polls.
14
+ # @return [Float] seconds
13
15
  def poll_interval
14
16
  @poll_interval ||= POLL_INTERVAL_SECONDS
15
17
  end
16
18
 
19
+ # Sets the delay between callback progress polls.
20
+ # @param seconds [Numeric] positive delay in seconds
21
+ # @return [Float]
22
+ # @raise [ArgumentError] if the delay is not positive
17
23
  def poll_interval=(seconds)
18
24
  value = Float(seconds)
19
25
  raise ArgumentError, "poll interval must be positive" unless value.positive?
@@ -21,6 +27,10 @@ module WGPU
21
27
  @poll_interval = value
22
28
  end
23
29
 
30
+ # Selects the native callback delivery mode for an operation.
31
+ #
32
+ # @param instance [Instance, nil] instance capable of processing events
33
+ # @return [Integer] native {Native::CallbackMode} value
24
34
  def callback_mode(instance:)
25
35
  if instance
26
36
  callback_mode_value(:allow_process_events)
@@ -29,9 +39,13 @@ module WGPU
29
39
  end
30
40
  end
31
41
 
42
+ # Drives native event processing until a callback completes.
43
+ # @param status_holder [Hash] state whose +:done+ flag is set by the callback
44
+ # @param timeout [Numeric, nil] maximum wait time in seconds
45
+ # @return [void]
46
+ # @raise [TimeoutError] if completion exceeds the timeout
32
47
  def wait(status_holder:, instance: nil, device: nil, future: nil, timeout: nil)
33
- timeout = Float(timeout) if timeout
34
- raise ArgumentError, "timeout must be non-negative" if timeout&.negative?
48
+ timeout = normalize_timeout(timeout)
35
49
 
36
50
  deadline = monotonic_time + timeout if timeout
37
51
  wait_info = build_wait_info(future) if instance && Native.future_api?
@@ -45,12 +59,24 @@ module WGPU
45
59
  elsif instance
46
60
  instance.process_events
47
61
  elsif device && Native.device_poll_available?
48
- Native.wgpuDevicePoll(device.handle, 0, nil)
62
+ Native.wgpuDevicePoll(NativeResource.checked_handle(device, expected_class: Device), 0, nil)
49
63
  end
50
64
  sleep(poll_interval) unless status_holder[:done] || waited
51
65
  end
52
66
  end
53
67
 
68
+ # Validates a timeout before any native operation is started.
69
+ def normalize_timeout(timeout)
70
+ return nil if timeout.nil?
71
+
72
+ value = Float(timeout)
73
+ raise ArgumentError, "timeout must be finite and non-negative" unless value.finite? && value >= 0
74
+
75
+ value
76
+ rescue TypeError
77
+ raise ArgumentError, "timeout must be a number"
78
+ end
79
+
54
80
  def callback_mode_value(name)
55
81
  Native::CallbackMode[name] || CALLBACK_MODE_FALLBACK.fetch(name)
56
82
  end
@@ -72,7 +98,7 @@ module WGPU
72
98
  private_class_method :build_wait_info
73
99
 
74
100
  def wait_with_wait_any(instance, wait_info)
75
- status = Native.wgpuInstanceWaitAny(instance.handle, 1, wait_info.to_ptr, 0)
101
+ status = Native.wgpuInstanceWaitAny(NativeResource.checked_handle(instance, expected_class: Instance), 1, wait_info.to_ptr, 0)
76
102
  return false if [:success, :timed_out].include?(status)
77
103
 
78
104
  raise Error, "wgpuInstanceWaitAny failed: #{status.inspect}"
@@ -4,29 +4,52 @@ module WGPU
4
4
  class CanvasContext
5
5
  attr_reader :physical_size
6
6
 
7
+ # Creates a canvas context for platform presentation information.
8
+ # @param instance [Instance] owning WebGPU instance
9
+ # @param present_info [Hash] platform handles or an existing surface
7
10
  def initialize(instance, present_info = {})
8
11
  @instance = instance
9
12
  @present_info = present_info || {}
10
- @surface = @present_info[:surface]
13
+ @surface = @present_info[:wgpu_surface]
14
+ if @present_info[:surface].is_a?(Surface) || ![:wayland, :linux_wayland].include?(@present_info[:platform]&.to_sym)
15
+ @surface ||= @present_info[:surface]
16
+ end
11
17
  @physical_size = [0, 0]
12
18
  @config = nil
13
19
  end
14
20
 
21
+ # Updates the drawable's physical pixel size.
22
+ # @param width [Integer] width in pixels
23
+ # @param height [Integer] height in pixels
24
+ # @return [Array<Integer>] stored width and height
25
+ # @raise [ArgumentError] if either dimension is negative
15
26
  def set_physical_size(width, height)
16
27
  raise ArgumentError, "width and height must be non-negative" if width.to_i.negative? || height.to_i.negative?
17
28
 
18
29
  @physical_size = [width.to_i, height.to_i]
19
30
  end
20
31
 
32
+ # Returns the surface format preferred by an adapter.
33
+ #
34
+ # @param adapter [Adapter] adapter used to query surface capabilities
35
+ # @return [Symbol] preferred texture format
21
36
  def get_preferred_format(adapter)
22
37
  ensure_surface
23
38
  @surface.get_preferred_format(adapter)
24
39
  end
25
40
 
41
+ # Returns the most recent canvas configuration.
42
+ #
43
+ # @return [Hash, nil] configuration options, or +nil+ when unconfigured
26
44
  def get_configuration
27
45
  @config
28
46
  end
29
47
 
48
+ # Configures the backing surface for presentation.
49
+ # @param device [Device] device used to render frames
50
+ # @param format [Symbol, nil] surface texture format
51
+ # @return [Hash] resolved canvas configuration
52
+ # @raise [SurfaceError] if no surface exists or dimensions are not positive
30
53
  def configure(device:, format: nil, usage: :render_attachment, view_formats: [], color_space: "srgb", tone_mapping: nil, alpha_mode: :opaque, width: nil, height: nil, present_mode: :fifo)
31
54
  ensure_surface
32
55
 
@@ -59,21 +82,33 @@ module WGPU
59
82
  }
60
83
  end
61
84
 
85
+ # Removes the current surface configuration.
86
+ # @return [void]
62
87
  def unconfigure
63
88
  @surface&.unconfigure
64
89
  @config = nil
65
90
  end
66
91
 
92
+ # Acquires the texture for the current presentation frame.
93
+ #
94
+ # @return [Texture] acquired surface texture
95
+ # @raise [SurfaceError] if the context has not been configured
96
+ # @raise [SurfaceAcquisitionError] if acquisition fails
67
97
  def get_current_texture
68
98
  raise SurfaceError, "Canvas context must be configured before get_current_texture" unless @config
69
99
 
70
100
  @surface.current_texture
71
101
  end
72
102
 
103
+ # Presents the current surface texture.
104
+ # @return [void]
73
105
  def present
74
106
  @surface&.present
75
107
  end
76
108
 
109
+ # Releases the backing surface and clears the configuration.
110
+ #
111
+ # @return [void]
77
112
  def release
78
113
  @surface&.release
79
114
  @surface = nil
@@ -93,7 +128,7 @@ module WGPU
93
128
  when :x11, :linux_x11
94
129
  Surface.from_xlib_window(@instance, @present_info.fetch(:display), @present_info.fetch(:window))
95
130
  when :wayland, :linux_wayland
96
- Surface.from_wayland_surface(@instance, @present_info.fetch(:display), @present_info.fetch(:surface))
131
+ Surface.from_wayland_surface(@instance, @present_info.fetch(:display), @present_info.fetch(:wl_surface) { @present_info.fetch(:surface) })
97
132
  else
98
133
  raise SurfaceError, "Cannot build surface from present_info: #{@present_info.inspect}"
99
134
  end