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,10 @@ module WGPU
4
4
  class CommandEncoder
5
5
  attr_reader :handle
6
6
 
7
+ # Creates an encoder for commands submitted to a device queue.
8
+ # @param device [Device] owning device
9
+ # @param label [String, nil] optional debug label
10
+ # @raise [CommandError] if the native encoder cannot be created
7
11
  def initialize(device, label: nil)
8
12
  @device = device
9
13
  @finished = false
@@ -20,10 +24,15 @@ module WGPU
20
24
  desc[:label][:length] = 0
21
25
  end
22
26
 
23
- @handle = Native.wgpuDeviceCreateCommandEncoder(device.handle, desc)
27
+ @handle = Native.wgpuDeviceCreateCommandEncoder(NativeResource.checked_handle(device, expected_class: Device), desc)
24
28
  raise CommandError, "Failed to create command encoder" if @handle.null?
25
29
  end
26
30
 
31
+ # Begins a compute pass, optionally yielding it for scoped recording.
32
+ # @param label [String, nil] optional debug label
33
+ # @param timestamp_writes [Hash, nil] timestamp query settings
34
+ # @yieldparam pass [ComputePass] newly created pass
35
+ # @return [ComputePass, Object] pass without a block, otherwise the block result
27
36
  def begin_compute_pass(label: nil, timestamp_writes: nil)
28
37
  ensure_can_begin_pass!
29
38
  pass = ComputePass.new(self, label: label, timestamp_writes: timestamp_writes)
@@ -42,6 +51,10 @@ module WGPU
42
51
  end
43
52
  end
44
53
 
54
+ # Begins a render pass, optionally yielding it for scoped recording.
55
+ # @param color_attachments [Array<Hash>] color attachment descriptors
56
+ # @yieldparam pass [RenderPass] newly created pass
57
+ # @return [RenderPass, Object] pass without a block, otherwise the block result
45
58
  def begin_render_pass(color_attachments:, depth_stencil_attachment: nil, occlusion_query_set: nil, timestamp_writes: nil, max_draw_count: nil, label: nil)
46
59
  ensure_can_begin_pass!
47
60
  pass = RenderPass.new(self,
@@ -67,32 +80,36 @@ module WGPU
67
80
  end
68
81
  end
69
82
 
83
+ # Copies bytes between buffers.
84
+ # @return [void]
70
85
  def copy_buffer_to_buffer(source:, source_offset: 0, destination:, destination_offset: 0, size:)
71
86
  raise CommandError, "Encoder already finished" if @finished
72
87
  Native.wgpuCommandEncoderCopyBufferToBuffer(
73
88
  @handle,
74
- source.handle, source_offset,
75
- destination.handle, destination_offset,
89
+ NativeResource.checked_handle(source, expected_class: Buffer), source_offset,
90
+ NativeResource.checked_handle(destination, expected_class: Buffer), destination_offset,
76
91
  size
77
92
  )
78
93
  end
79
94
 
95
+ # Copies buffer data into a texture region.
96
+ # @param source [Hash] buffer and layout descriptor
97
+ # @param destination [Hash] texture and origin descriptor
98
+ # @param copy_size [Hash, Array] extent to copy
99
+ # @return [void]
80
100
  def copy_buffer_to_texture(source:, destination:, copy_size:)
81
101
  raise CommandError, "Encoder already finished" if @finished
82
102
 
83
- size = Native::Extent3D.new
84
- size[:width] = copy_size[:width] || copy_size[0]
85
- size[:height] = copy_size[:height] || copy_size[1] || 1
86
- size[:depth_or_array_layers] = copy_size[:depth_or_array_layers] || copy_size[2] || 1
103
+ size = DescriptorHelpers.extent_3d(copy_size)
87
104
 
88
105
  src = Native::ImageCopyBuffer.new
89
106
  src[:layout][:offset] = source[:offset] || 0
90
107
  src[:layout][:bytes_per_row] = source[:bytes_per_row]
91
108
  src[:layout][:rows_per_image] = source[:rows_per_image] || size[:height]
92
- src[:buffer] = source[:buffer].handle
109
+ src[:buffer] = NativeResource.checked_handle(source[:buffer], expected_class: Buffer)
93
110
 
94
111
  dst = Native::ImageCopyTexture.new
95
- dst[:texture] = destination[:texture].handle
112
+ dst[:texture] = NativeResource.checked_handle(destination[:texture], expected_class: Texture)
96
113
  dst[:mip_level] = destination[:mip_level] || 0
97
114
  dst[:origin][:x] = destination.dig(:origin, :x) || 0
98
115
  dst[:origin][:y] = destination.dig(:origin, :y) || 0
@@ -106,16 +123,18 @@ module WGPU
106
123
  Native.wgpuCommandEncoderCopyBufferToTexture(@handle, src, dst, size)
107
124
  end
108
125
 
126
+ # Copies a texture region into a buffer.
127
+ # @param source [Hash] texture and origin descriptor
128
+ # @param destination [Hash] buffer and layout descriptor
129
+ # @param copy_size [Hash, Array] extent to copy
130
+ # @return [void]
109
131
  def copy_texture_to_buffer(source:, destination:, copy_size:)
110
132
  raise CommandError, "Encoder already finished" if @finished
111
133
 
112
- size = Native::Extent3D.new
113
- size[:width] = copy_size[:width] || copy_size[0]
114
- size[:height] = copy_size[:height] || copy_size[1] || 1
115
- size[:depth_or_array_layers] = copy_size[:depth_or_array_layers] || copy_size[2] || 1
134
+ size = DescriptorHelpers.extent_3d(copy_size)
116
135
 
117
136
  src = Native::ImageCopyTexture.new
118
- src[:texture] = source[:texture].handle
137
+ src[:texture] = NativeResource.checked_handle(source[:texture], expected_class: Texture)
119
138
  src[:mip_level] = source[:mip_level] || 0
120
139
  src[:origin][:x] = source.dig(:origin, :x) || 0
121
140
  src[:origin][:y] = source.dig(:origin, :y) || 0
@@ -130,16 +149,21 @@ module WGPU
130
149
  dst[:layout][:offset] = destination[:offset] || 0
131
150
  dst[:layout][:bytes_per_row] = destination[:bytes_per_row]
132
151
  dst[:layout][:rows_per_image] = destination[:rows_per_image] || size[:height]
133
- dst[:buffer] = destination[:buffer].handle
152
+ dst[:buffer] = NativeResource.checked_handle(destination[:buffer], expected_class: Buffer)
134
153
 
135
154
  Native.wgpuCommandEncoderCopyTextureToBuffer(@handle, src, dst, size)
136
155
  end
137
156
 
157
+ # Copies one texture region into another texture.
158
+ # @param source [Hash] source texture and origin descriptor
159
+ # @param destination [Hash] destination texture and origin descriptor
160
+ # @param copy_size [Hash, Array] extent to copy
161
+ # @return [void]
138
162
  def copy_texture_to_texture(source:, destination:, copy_size:)
139
163
  raise CommandError, "Encoder already finished" if @finished
140
164
 
141
165
  src = Native::ImageCopyTexture.new
142
- src[:texture] = source[:texture].handle
166
+ src[:texture] = NativeResource.checked_handle(source[:texture], expected_class: Texture)
143
167
  src[:mip_level] = source[:mip_level] || 0
144
168
  src[:origin][:x] = source.dig(:origin, :x) || 0
145
169
  src[:origin][:y] = source.dig(:origin, :y) || 0
@@ -151,7 +175,7 @@ module WGPU
151
175
  )
152
176
 
153
177
  dst = Native::ImageCopyTexture.new
154
- dst[:texture] = destination[:texture].handle
178
+ dst[:texture] = NativeResource.checked_handle(destination[:texture], expected_class: Texture)
155
179
  dst[:mip_level] = destination[:mip_level] || 0
156
180
  dst[:origin][:x] = destination.dig(:origin, :x) || 0
157
181
  dst[:origin][:y] = destination.dig(:origin, :y) || 0
@@ -162,37 +186,55 @@ module WGPU
162
186
  name: "destination texture aspect"
163
187
  )
164
188
 
165
- size = Native::Extent3D.new
166
- size[:width] = copy_size[:width] || copy_size[0]
167
- size[:height] = copy_size[:height] || copy_size[1] || 1
168
- size[:depth_or_array_layers] = copy_size[:depth_or_array_layers] || copy_size[2] || 1
189
+ size = DescriptorHelpers.extent_3d(copy_size)
169
190
 
170
191
  Native.wgpuCommandEncoderCopyTextureToTexture(@handle, src, dst, size)
171
192
  end
172
193
 
194
+ # Resolves query results into a destination buffer.
195
+ # @return [void]
173
196
  def resolve_query_set(query_set:, first_query:, query_count:, destination:, destination_offset:)
174
197
  raise CommandError, "Encoder already finished" if @finished
175
198
  Native.wgpuCommandEncoderResolveQuerySet(
176
199
  @handle,
177
- query_set.handle,
200
+ NativeResource.checked_handle(query_set, expected_class: QuerySet),
178
201
  first_query,
179
202
  query_count,
180
- destination.handle,
203
+ NativeResource.checked_handle(destination, expected_class: Buffer),
181
204
  destination_offset
182
205
  )
183
206
  end
184
207
 
208
+ # Clears a byte range in a buffer to zero.
209
+ # @param buffer [Buffer] buffer to clear
210
+ # @param offset [Integer] first byte to clear
211
+ # @param size [Integer, nil] number of bytes to clear
212
+ # @return [void]
185
213
  def clear_buffer(buffer, offset: 0, size: nil)
186
214
  raise CommandError, "Encoder already finished" if @finished
187
- size ||= buffer.size - offset
188
- Native.wgpuCommandEncoderClearBuffer(@handle, buffer.handle, offset, size)
215
+ buffer_handle = NativeResource.checked_handle(buffer, expected_class: Buffer)
216
+ offset = DataTypes.validate_alignment!(offset, 4, name: "clear offset")
217
+ size = DataTypes.validate_alignment!(size || (buffer.size - offset), 4, name: "clear size")
218
+ if offset > buffer.size || size > buffer.size - offset
219
+ raise ArgumentError, "clear range exceeds buffer size"
220
+ end
221
+ return if size.zero?
222
+
223
+ Native.wgpuCommandEncoderClearBuffer(@handle, buffer_handle, offset, size)
189
224
  end
190
225
 
226
+ # Writes a GPU timestamp to a query set.
227
+ # @param query_set [QuerySet] timestamp query set
228
+ # @param query_index [Integer] destination query index
229
+ # @return [void]
191
230
  def write_timestamp(query_set, query_index)
192
231
  raise CommandError, "Encoder already finished" if @finished
193
- Native.wgpuCommandEncoderWriteTimestamp(@handle, query_set.handle, query_index)
232
+ Native.wgpuCommandEncoderWriteTimestamp(@handle, NativeResource.checked_handle(query_set, expected_class: QuerySet), query_index)
194
233
  end
195
234
 
235
+ # Starts a labeled group in GPU debugging tools.
236
+ # @param label [String] group label
237
+ # @return [void]
196
238
  def push_debug_group(label)
197
239
  raise CommandError, "Encoder already finished" if @finished
198
240
  label_view = Native::StringView.new
@@ -202,11 +244,16 @@ module WGPU
202
244
  Native.wgpuCommandEncoderPushDebugGroup(@handle, label_view)
203
245
  end
204
246
 
247
+ # Ends the most recently pushed debug group.
248
+ # @return [void]
205
249
  def pop_debug_group
206
250
  raise CommandError, "Encoder already finished" if @finished
207
251
  Native.wgpuCommandEncoderPopDebugGroup(@handle)
208
252
  end
209
253
 
254
+ # Inserts a labeled point in GPU debugging tools.
255
+ # @param label [String] marker label
256
+ # @return [void]
210
257
  def insert_debug_marker(label)
211
258
  raise CommandError, "Encoder already finished" if @finished
212
259
  label_view = Native::StringView.new
@@ -216,6 +263,10 @@ module WGPU
216
263
  Native.wgpuCommandEncoderInsertDebugMarker(@handle, label_view)
217
264
  end
218
265
 
266
+ # Finishes recording and returns a command buffer.
267
+ # @param label [String, nil] optional command buffer label
268
+ # @return [CommandBuffer]
269
+ # @raise [CommandError] if the encoder is finished, has an active pass, or native creation fails
219
270
  def finish(label: nil)
220
271
  raise CommandError, "Encoder already finished" if @finished
221
272
  if @active_pass && !@active_pass.ended?
@@ -235,9 +286,13 @@ module WGPU
235
286
  buffer_handle = Native.wgpuCommandEncoderFinish(@handle, desc)
236
287
  raise CommandError, "Failed to finish command encoder" if buffer_handle.null?
237
288
 
238
- CommandBuffer.new(buffer_handle)
289
+ CommandBuffer.new(buffer_handle, device: @device)
239
290
  end
240
291
 
292
+ # Releases the native command encoder handle.
293
+ #
294
+ # Calling this method more than once has no effect.
295
+ # @return [void]
241
296
  def release
242
297
  return if @handle.null?
243
298
  Native.wgpuCommandEncoderRelease(@handle)
@@ -4,6 +4,11 @@ module WGPU
4
4
  class ComputePass
5
5
  attr_reader :handle
6
6
 
7
+ # Begins a compute pass owned by the command encoder.
8
+ # @param encoder [CommandEncoder] owning encoder
9
+ # @param label [String, nil] optional debug label
10
+ # @param timestamp_writes [Hash, nil] beginning and ending timestamp query settings
11
+ # @raise [CommandError] if the native pass cannot be created
7
12
  def initialize(encoder, label: nil, timestamp_writes: nil)
8
13
  @encoder = encoder
9
14
  @ended = false
@@ -20,7 +25,7 @@ module WGPU
20
25
  @timestamp_writes = nil
21
26
  if timestamp_writes
22
27
  @timestamp_writes = Native::ComputePassTimestampWrites.new
23
- @timestamp_writes[:query_set] = timestamp_writes.fetch(:query_set).handle
28
+ @timestamp_writes[:query_set] = NativeResource.checked_handle(timestamp_writes.fetch(:query_set), expected_class: QuerySet)
24
29
  @timestamp_writes[:beginning_of_pass_write_index] = timestamp_writes[:beginning_of_pass_write_index] || 0xFFFFFFFF
25
30
  @timestamp_writes[:end_of_pass_write_index] = timestamp_writes[:end_of_pass_write_index] || 0xFFFFFFFF
26
31
  desc[:timestamp_writes] = @timestamp_writes.to_ptr
@@ -28,32 +33,53 @@ module WGPU
28
33
  desc[:timestamp_writes] = nil
29
34
  end
30
35
 
31
- @handle = Native.wgpuCommandEncoderBeginComputePass(encoder.handle, desc)
36
+ @handle = Native.wgpuCommandEncoderBeginComputePass(NativeResource.checked_handle(encoder, expected_class: CommandEncoder), desc)
32
37
  raise CommandError, "Failed to begin compute pass" if @handle.null?
33
38
  end
34
39
 
40
+ # Selects the compute pipeline used by subsequent dispatches.
41
+ # @param pipeline [ComputePipeline] pipeline to bind
42
+ # @return [void]
35
43
  def set_pipeline(pipeline)
36
- Native.wgpuComputePassEncoderSetPipeline(@handle, pipeline.handle)
44
+ Native.wgpuComputePassEncoderSetPipeline(@handle, NativeResource.checked_handle(pipeline, expected_class: ComputePipeline))
37
45
  end
38
46
 
47
+ # Binds a resource group for subsequent dispatches.
48
+ # @param index [Integer] bind group index
49
+ # @param bind_group [BindGroup] group to bind
50
+ # @param dynamic_offsets [Array<Integer>] dynamic buffer offsets
51
+ # @return [void]
39
52
  def set_bind_group(index, bind_group, dynamic_offsets: [])
40
53
  if dynamic_offsets.empty?
41
- Native.wgpuComputePassEncoderSetBindGroup(@handle, index, bind_group.handle, 0, nil)
54
+ Native.wgpuComputePassEncoderSetBindGroup(@handle, index, NativeResource.checked_handle(bind_group, expected_class: BindGroup), 0, nil)
42
55
  else
43
56
  offsets_ptr = FFI::MemoryPointer.new(:uint32, dynamic_offsets.size)
44
57
  offsets_ptr.write_array_of_uint32(dynamic_offsets)
45
- Native.wgpuComputePassEncoderSetBindGroup(@handle, index, bind_group.handle, dynamic_offsets.size, offsets_ptr)
58
+ Native.wgpuComputePassEncoderSetBindGroup(@handle, index, NativeResource.checked_handle(bind_group, expected_class: BindGroup), dynamic_offsets.size, offsets_ptr)
46
59
  end
47
60
  end
48
61
 
62
+ # Dispatches a three-dimensional compute workgroup grid.
63
+ # @param x [Integer] workgroup count on the x axis
64
+ # @param y [Integer] workgroup count on the y axis
65
+ # @param z [Integer] workgroup count on the z axis
66
+ # @return [void]
49
67
  def dispatch_workgroups(x, y = 1, z = 1)
50
68
  Native.wgpuComputePassEncoderDispatchWorkgroups(@handle, x, y, z)
51
69
  end
52
70
 
71
+ # Dispatches workgroups using arguments read from a buffer.
72
+ # @param buffer [Buffer] indirect argument buffer
73
+ # @param offset [Integer] byte offset of the arguments
74
+ # @return [void]
53
75
  def dispatch_workgroups_indirect(buffer, offset: 0)
54
- Native.wgpuComputePassEncoderDispatchWorkgroupsIndirect(@handle, buffer.handle, offset)
76
+ Native.wgpuComputePassEncoderDispatchWorkgroupsIndirect(@handle, NativeResource.checked_handle(buffer, expected_class: Buffer), offset)
55
77
  end
56
78
 
79
+ # Starts a labeled group in GPU debugging tools.
80
+ #
81
+ # @param label [String] group label
82
+ # @return [void]
57
83
  def push_debug_group(label)
58
84
  label_view = Native::StringView.new
59
85
  label_ptr = FFI::MemoryPointer.from_string(label)
@@ -62,10 +88,17 @@ module WGPU
62
88
  Native.wgpuComputePassEncoderPushDebugGroup(@handle, label_view)
63
89
  end
64
90
 
91
+ # Ends the most recently pushed debug group.
92
+ #
93
+ # @return [void]
65
94
  def pop_debug_group
66
95
  Native.wgpuComputePassEncoderPopDebugGroup(@handle)
67
96
  end
68
97
 
98
+ # Inserts a labeled point in GPU debugging tools.
99
+ #
100
+ # @param label [String] marker label
101
+ # @return [void]
69
102
  def insert_debug_marker(label)
70
103
  label_view = Native::StringView.new
71
104
  label_ptr = FFI::MemoryPointer.from_string(label)
@@ -74,6 +107,8 @@ module WGPU
74
107
  Native.wgpuComputePassEncoderInsertDebugMarker(@handle, label_view)
75
108
  end
76
109
 
110
+ # Ends the pass if it has not already ended.
111
+ # @return [void]
77
112
  def end_pass
78
113
  return if @ended
79
114
 
@@ -82,14 +117,22 @@ module WGPU
82
117
  @encoder.send(:pass_ended, self)
83
118
  end
84
119
 
120
+ # Ends the pass.
121
+ # @return [void]
85
122
  def end
86
123
  end_pass
87
124
  end
88
125
 
126
+ # Reports whether the pass has ended.
127
+ # @return [Boolean]
89
128
  def ended?
90
129
  @ended
91
130
  end
92
131
 
132
+ # Releases the native compute pass encoder handle.
133
+ #
134
+ # Calling this method more than once has no effect.
135
+ # @return [void]
93
136
  def release
94
137
  return if @handle.null?
95
138
  Native.wgpuComputePassEncoderRelease(@handle)
@@ -4,10 +4,18 @@ module WGPU
4
4
  class RenderBundle
5
5
  attr_reader :handle
6
6
 
7
- def initialize(handle)
7
+ # Wraps a reusable native render bundle.
8
+ # @param handle [FFI::Pointer] native render bundle handle
9
+ # @param device [Device, nil] device whose callbacks the render bundle may use
10
+ def initialize(handle, device: nil)
8
11
  @handle = handle
12
+ @device = device
9
13
  end
10
14
 
15
+ # Releases the native render bundle handle.
16
+ #
17
+ # Calling this method more than once has no effect.
18
+ # @return [void]
11
19
  def release
12
20
  return if @handle.null?
13
21
 
@@ -4,6 +4,15 @@ module WGPU
4
4
  class RenderBundleEncoder
5
5
  attr_reader :handle
6
6
 
7
+ # Creates an encoder for reusable render commands.
8
+ # @param device [Device] owning device
9
+ # @param color_formats [Array<Symbol, Integer>] color attachment formats
10
+ # @param depth_stencil_format [Symbol, Integer, nil] optional depth/stencil format
11
+ # @param sample_count [Integer] multisample count
12
+ # @param depth_read_only [Boolean] whether depth writes are disabled
13
+ # @param stencil_read_only [Boolean] whether stencil writes are disabled
14
+ # @param label [String, nil] optional debug label
15
+ # @raise [RenderBundleError] if the native encoder cannot be created
7
16
  def initialize(device, color_formats:, depth_stencil_format: nil, sample_count: 1,
8
17
  depth_read_only: false, stencil_read_only: false, label: nil)
9
18
  @device = device
@@ -38,67 +47,99 @@ module WGPU
38
47
  desc[:depth_read_only] = depth_read_only ? 1 : 0
39
48
  desc[:stencil_read_only] = stencil_read_only ? 1 : 0
40
49
 
41
- @handle = Native.wgpuDeviceCreateRenderBundleEncoder(device.handle, desc)
50
+ @handle = Native.wgpuDeviceCreateRenderBundleEncoder(NativeResource.checked_handle(device, expected_class: Device), desc)
42
51
  raise RenderBundleError, "Failed to create render bundle encoder" if @handle.null?
43
52
  end
44
53
 
54
+ # Selects the pipeline used by subsequent bundle draws.
55
+ # @param pipeline [RenderPipeline] pipeline to bind
56
+ # @raise [RenderBundleError] if the encoder is finished
57
+ # @return [void]
45
58
  def set_pipeline(pipeline)
46
59
  raise RenderBundleError, "Encoder already finished" if @finished
47
60
 
48
- Native.wgpuRenderBundleEncoderSetPipeline(@handle, pipeline.handle)
61
+ Native.wgpuRenderBundleEncoderSetPipeline(@handle, NativeResource.checked_handle(pipeline, expected_class: RenderPipeline))
49
62
  end
50
63
 
64
+ # Binds a resource group for subsequent bundle draws.
65
+ # @param index [Integer] bind group index
66
+ # @param bind_group [BindGroup] group to bind
67
+ # @param dynamic_offsets [Array<Integer>, nil] dynamic buffer offsets
68
+ # @return [void]
51
69
  def set_bind_group(index, bind_group, dynamic_offsets: nil)
52
70
  raise RenderBundleError, "Encoder already finished" if @finished
53
71
 
54
72
  if dynamic_offsets && !dynamic_offsets.empty?
55
73
  offsets_ptr = FFI::MemoryPointer.new(:uint32, dynamic_offsets.size)
56
74
  offsets_ptr.write_array_of_uint32(dynamic_offsets)
57
- Native.wgpuRenderBundleEncoderSetBindGroup(@handle, index, bind_group.handle, dynamic_offsets.size, offsets_ptr)
75
+ Native.wgpuRenderBundleEncoderSetBindGroup(@handle, index, NativeResource.checked_handle(bind_group, expected_class: BindGroup), dynamic_offsets.size, offsets_ptr)
58
76
  else
59
- Native.wgpuRenderBundleEncoderSetBindGroup(@handle, index, bind_group.handle, 0, nil)
77
+ Native.wgpuRenderBundleEncoderSetBindGroup(@handle, index, NativeResource.checked_handle(bind_group, expected_class: BindGroup), 0, nil)
60
78
  end
61
79
  end
62
80
 
81
+ # Binds a vertex buffer to a slot.
82
+ # @param slot [Integer] vertex buffer slot
83
+ # @param buffer [Buffer] vertex data buffer
84
+ # @return [void]
63
85
  def set_vertex_buffer(slot, buffer, offset: 0, size: nil)
64
86
  raise RenderBundleError, "Encoder already finished" if @finished
65
87
 
66
88
  size ||= buffer.size - offset
67
- Native.wgpuRenderBundleEncoderSetVertexBuffer(@handle, slot, buffer.handle, offset, size)
89
+ Native.wgpuRenderBundleEncoderSetVertexBuffer(@handle, slot, NativeResource.checked_handle(buffer, expected_class: Buffer), offset, size)
68
90
  end
69
91
 
92
+ # Binds an index buffer for indexed bundle draws.
93
+ # @param buffer [Buffer] index data buffer
94
+ # @param format [Symbol, Integer] index element format
95
+ # @return [void]
70
96
  def set_index_buffer(buffer, format: :uint32, offset: 0, size: nil)
71
97
  raise RenderBundleError, "Encoder already finished" if @finished
72
98
 
73
99
  size ||= buffer.size - offset
74
100
  format_value = Native::EnumHelper.coerce(Native::IndexFormat, format, name: "index format")
75
- Native.wgpuRenderBundleEncoderSetIndexBuffer(@handle, buffer.handle, format_value, offset, size)
101
+ Native.wgpuRenderBundleEncoderSetIndexBuffer(@handle, NativeResource.checked_handle(buffer, expected_class: Buffer), format_value, offset, size)
76
102
  end
77
103
 
104
+ # Records a non-indexed draw in the bundle.
105
+ # @return [void]
78
106
  def draw(vertex_count, instance_count: 1, first_vertex: 0, first_instance: 0)
79
107
  raise RenderBundleError, "Encoder already finished" if @finished
80
108
 
81
109
  Native.wgpuRenderBundleEncoderDraw(@handle, vertex_count, instance_count, first_vertex, first_instance)
82
110
  end
83
111
 
112
+ # Records an indexed draw in the bundle.
113
+ # @return [void]
84
114
  def draw_indexed(index_count, instance_count: 1, first_index: 0, base_vertex: 0, first_instance: 0)
85
115
  raise RenderBundleError, "Encoder already finished" if @finished
86
116
 
87
117
  Native.wgpuRenderBundleEncoderDrawIndexed(@handle, index_count, instance_count, first_index, base_vertex, first_instance)
88
118
  end
89
119
 
120
+ # Records a non-indexed draw using buffer arguments.
121
+ # @param buffer [Buffer] indirect argument buffer
122
+ # @param offset [Integer] byte offset of the arguments
123
+ # @return [void]
90
124
  def draw_indirect(buffer, offset: 0)
91
125
  raise RenderBundleError, "Encoder already finished" if @finished
92
126
 
93
- Native.wgpuRenderBundleEncoderDrawIndirect(@handle, buffer.handle, offset)
127
+ Native.wgpuRenderBundleEncoderDrawIndirect(@handle, NativeResource.checked_handle(buffer, expected_class: Buffer), offset)
94
128
  end
95
129
 
130
+ # Records an indexed draw using buffer arguments.
131
+ # @param buffer [Buffer] indirect argument buffer
132
+ # @param offset [Integer] byte offset of the arguments
133
+ # @return [void]
96
134
  def draw_indexed_indirect(buffer, offset: 0)
97
135
  raise RenderBundleError, "Encoder already finished" if @finished
98
136
 
99
- Native.wgpuRenderBundleEncoderDrawIndexedIndirect(@handle, buffer.handle, offset)
137
+ Native.wgpuRenderBundleEncoderDrawIndexedIndirect(@handle, NativeResource.checked_handle(buffer, expected_class: Buffer), offset)
100
138
  end
101
139
 
140
+ # Starts a labeled group in GPU debugging tools.
141
+ # @param label [String] group label
142
+ # @return [void]
102
143
  def push_debug_group(label)
103
144
  raise RenderBundleError, "Encoder already finished" if @finished
104
145
 
@@ -109,12 +150,17 @@ module WGPU
109
150
  Native.wgpuRenderBundleEncoderPushDebugGroup(@handle, label_view)
110
151
  end
111
152
 
153
+ # Ends the most recently pushed debug group.
154
+ # @return [void]
112
155
  def pop_debug_group
113
156
  raise RenderBundleError, "Encoder already finished" if @finished
114
157
 
115
158
  Native.wgpuRenderBundleEncoderPopDebugGroup(@handle)
116
159
  end
117
160
 
161
+ # Inserts a labeled point in GPU debugging tools.
162
+ # @param label [String] marker label
163
+ # @return [void]
118
164
  def insert_debug_marker(label)
119
165
  raise RenderBundleError, "Encoder already finished" if @finished
120
166
 
@@ -125,6 +171,10 @@ module WGPU
125
171
  Native.wgpuRenderBundleEncoderInsertDebugMarker(@handle, label_view)
126
172
  end
127
173
 
174
+ # Finishes recording and creates an immutable render bundle.
175
+ # @param label [String, nil] optional bundle label
176
+ # @return [RenderBundle]
177
+ # @raise [RenderBundleError] if already finished or native creation fails
128
178
  def finish(label: nil)
129
179
  raise RenderBundleError, "Encoder already finished" if @finished
130
180
 
@@ -142,9 +192,13 @@ module WGPU
142
192
  bundle_handle = Native.wgpuRenderBundleEncoderFinish(@handle, desc)
143
193
  raise RenderBundleError, "Failed to finish render bundle encoder" if bundle_handle.null?
144
194
 
145
- RenderBundle.new(bundle_handle)
195
+ RenderBundle.new(bundle_handle, device: @device)
146
196
  end
147
197
 
198
+ # Releases the native render bundle encoder handle.
199
+ #
200
+ # Calling this method more than once has no effect.
201
+ # @return [void]
148
202
  def release
149
203
  return if @handle.null?
150
204