wgpu 1.1.0 → 1.2.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -0
  3. data/README.md +44 -5
  4. data/docs/README.md +22 -0
  5. data/docs/api_coverage.md +140 -0
  6. data/docs/async.md +41 -0
  7. data/docs/bind_groups.md +25 -0
  8. data/docs/buffer_data.md +37 -0
  9. data/docs/command_encoding.md +49 -0
  10. data/docs/errors.md +40 -0
  11. data/docs/getting_started_compute.md +62 -0
  12. data/docs/getting_started_rendering.md +62 -0
  13. data/docs/installation.md +94 -0
  14. data/docs/pipeline_descriptors.md +43 -0
  15. data/docs/releasing.md +32 -0
  16. data/docs/resource_lifetime.md +108 -0
  17. data/docs/shaders.md +32 -0
  18. data/docs/texture_readback.md +46 -0
  19. data/docs/troubleshooting.md +53 -0
  20. data/docs/upgrading_wgpu_native.md +37 -0
  21. data/ext/wgpu/extconf.rb +10 -142
  22. data/lib/wgpu/async_task.rb +19 -0
  23. data/lib/wgpu/commands/command_buffer.rb +25 -1
  24. data/lib/wgpu/commands/command_encoder.rb +123 -9
  25. data/lib/wgpu/commands/compute_pass.rb +53 -0
  26. data/lib/wgpu/commands/render_bundle.rb +9 -1
  27. data/lib/wgpu/commands/render_bundle_encoder.rb +65 -4
  28. data/lib/wgpu/commands/render_pass.rb +136 -8
  29. data/lib/wgpu/core/adapter.rb +123 -19
  30. data/lib/wgpu/core/async_waiter.rb +47 -4
  31. data/lib/wgpu/core/canvas_context.rb +32 -2
  32. data/lib/wgpu/core/device.rb +399 -53
  33. data/lib/wgpu/core/instance.rb +28 -4
  34. data/lib/wgpu/core/queue.rb +197 -51
  35. data/lib/wgpu/core/surface.rb +64 -17
  36. data/lib/wgpu/data_types.rb +83 -0
  37. data/lib/wgpu/descriptor_helpers.rb +104 -0
  38. data/lib/wgpu/error.rb +70 -0
  39. data/lib/wgpu/logging.rb +63 -0
  40. data/lib/wgpu/native/abi_verifier.rb +143 -0
  41. data/lib/wgpu/native/callbacks.rb +10 -1
  42. data/lib/wgpu/native/capabilities.rb +32 -2
  43. data/lib/wgpu/native/distribution.rb +176 -0
  44. data/lib/wgpu/native/enum_helper.rb +80 -0
  45. data/lib/wgpu/native/enums.rb +68 -8
  46. data/lib/wgpu/native/fixtures/webgpu-v27.0.4.0-enums.h +848 -0
  47. data/lib/wgpu/native/functions.rb +21 -10
  48. data/lib/wgpu/native/installer.rb +223 -0
  49. data/lib/wgpu/native/loader.rb +61 -21
  50. data/lib/wgpu/native/structs.rb +18 -1
  51. data/lib/wgpu/native_resource.rb +342 -0
  52. data/lib/wgpu/pipeline/bind_group.rb +21 -0
  53. data/lib/wgpu/pipeline/bind_group_layout.rb +108 -41
  54. data/lib/wgpu/pipeline/compute_pipeline.rb +40 -50
  55. data/lib/wgpu/pipeline/pipeline_layout.rb +8 -0
  56. data/lib/wgpu/pipeline/render_pipeline.rb +207 -110
  57. data/lib/wgpu/pipeline/shader_module.rb +95 -28
  58. data/lib/wgpu/resources/buffer.rb +387 -85
  59. data/lib/wgpu/resources/query_set.rb +26 -12
  60. data/lib/wgpu/resources/sampler.rb +43 -20
  61. data/lib/wgpu/resources/texture.rb +89 -48
  62. data/lib/wgpu/resources/texture_view.rb +35 -7
  63. data/lib/wgpu/texture_format.rb +96 -0
  64. data/lib/wgpu/version.rb +1 -1
  65. data/lib/wgpu/window.rb +34 -1
  66. data/lib/wgpu.rb +32 -0
  67. data/sig/wgpu.rbs +460 -0
  68. metadata +33 -16
@@ -4,6 +4,8 @@ require "timeout"
4
4
 
5
5
  module WGPU
6
6
  class AsyncTask
7
+ # Starts a background task that evaluates the given block.
8
+ # @raise [ArgumentError] if no block is supplied
7
9
  def initialize(&block)
8
10
  raise ArgumentError, "block is required" unless block
9
11
 
@@ -18,12 +20,20 @@ module WGPU
18
20
  end
19
21
  end
20
22
 
23
+ # Waits until the task finishes.
24
+ # @param timeout [Numeric, nil] maximum number of seconds to wait
25
+ # @return [AsyncTask] this task
26
+ # @raise [Timeout::Error] if the task does not finish in time
21
27
  def wait(timeout: nil)
22
28
  joined = timeout ? @thread.join(timeout) : @thread.join
23
29
  raise Timeout::Error, "async task timeout" unless joined
24
30
  self
25
31
  end
26
32
 
33
+ # Waits for and returns the task result.
34
+ # @param timeout [Numeric, nil] maximum number of seconds to wait
35
+ # @return [Object] block result
36
+ # @raise [StandardError] re-raises an exception raised by the block
27
37
  def value(timeout: nil)
28
38
  wait(timeout: timeout)
29
39
  raise @error if @error
@@ -31,20 +41,29 @@ module WGPU
31
41
  @value
32
42
  end
33
43
 
44
+ # Creates a task that transforms this task's result.
45
+ # @yieldparam value [Object] completed task result
46
+ # @return [AsyncTask] chained task
34
47
  def then(&block)
35
48
  AsyncTask.new do
36
49
  block.call(value)
37
50
  end
38
51
  end
39
52
 
53
+ # Reports whether the task has finished.
54
+ # @return [Boolean]
40
55
  def complete?
41
56
  !@thread.alive?
42
57
  end
43
58
 
59
+ # Reports whether the task is still running.
60
+ # @return [Boolean]
44
61
  def pending?
45
62
  @thread.alive?
46
63
  end
47
64
 
65
+ # Returns the exception raised by the task, if any.
66
+ # @return [StandardError, nil]
48
67
  def error
49
68
  value
50
69
  nil
@@ -4,10 +4,34 @@ module WGPU
4
4
  class CommandBuffer
5
5
  attr_reader :handle
6
6
 
7
- def initialize(handle)
7
+ # Wraps an encoded native command buffer.
8
+ # @param handle [FFI::Pointer] native command buffer handle
9
+ # @param device [Device, nil] device whose callbacks the command buffer may use
10
+ def initialize(handle, device: nil)
8
11
  @handle = handle
12
+ @device = device
13
+ @submitted = false
9
14
  end
10
15
 
16
+ # Reports whether this command buffer has been submitted.
17
+ # @return [Boolean]
18
+ def submitted?
19
+ @submitted
20
+ end
21
+
22
+ # Marks this command buffer as submitted.
23
+ # @raise [CommandError] if it was already submitted
24
+ # @return [void]
25
+ def mark_submitted!
26
+ raise CommandError, "Command buffer has already been submitted" if @submitted
27
+
28
+ @submitted = true
29
+ end
30
+
31
+ # Releases the native command buffer handle.
32
+ #
33
+ # Calling this method more than once has no effect.
34
+ # @return [void]
11
35
  def release
12
36
  return if @handle.null?
13
37
  Native.wgpuCommandBufferRelease(@handle)
@@ -4,9 +4,14 @@ 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
14
+ @active_pass = nil
10
15
 
11
16
  desc = Native::CommandEncoderDescriptor.new
12
17
  desc[:next_in_chain] = nil
@@ -23,14 +28,36 @@ module WGPU
23
28
  raise CommandError, "Failed to create command encoder" if @handle.null?
24
29
  end
25
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
26
36
  def begin_compute_pass(label: nil, timestamp_writes: nil)
27
- raise CommandError, "Encoder already finished" if @finished
28
- ComputePass.new(self, label: label, timestamp_writes: timestamp_writes)
37
+ ensure_can_begin_pass!
38
+ pass = ComputePass.new(self, label: label, timestamp_writes: timestamp_writes)
39
+ @active_pass = pass
40
+ return pass unless block_given?
41
+
42
+ begin
43
+ yield pass
44
+ ensure
45
+ begin
46
+ pass.end_pass unless pass.ended?
47
+ ensure
48
+ pass.release
49
+ pass_ended(pass)
50
+ end
51
+ end
29
52
  end
30
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
31
58
  def begin_render_pass(color_attachments:, depth_stencil_attachment: nil, occlusion_query_set: nil, timestamp_writes: nil, max_draw_count: nil, label: nil)
32
- raise CommandError, "Encoder already finished" if @finished
33
- RenderPass.new(self,
59
+ ensure_can_begin_pass!
60
+ pass = RenderPass.new(self,
34
61
  color_attachments: color_attachments,
35
62
  depth_stencil_attachment: depth_stencil_attachment,
36
63
  occlusion_query_set: occlusion_query_set,
@@ -38,8 +65,23 @@ module WGPU
38
65
  max_draw_count: max_draw_count,
39
66
  label: label
40
67
  )
68
+ @active_pass = pass
69
+ return pass unless block_given?
70
+
71
+ begin
72
+ yield pass
73
+ ensure
74
+ begin
75
+ pass.end_pass unless pass.ended?
76
+ ensure
77
+ pass.release
78
+ pass_ended(pass)
79
+ end
80
+ end
41
81
  end
42
82
 
83
+ # Copies bytes between buffers.
84
+ # @return [void]
43
85
  def copy_buffer_to_buffer(source:, source_offset: 0, destination:, destination_offset: 0, size:)
44
86
  raise CommandError, "Encoder already finished" if @finished
45
87
  Native.wgpuCommandEncoderCopyBufferToBuffer(
@@ -50,6 +92,11 @@ module WGPU
50
92
  )
51
93
  end
52
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]
53
100
  def copy_buffer_to_texture(source:, destination:, copy_size:)
54
101
  raise CommandError, "Encoder already finished" if @finished
55
102
 
@@ -70,11 +117,20 @@ module WGPU
70
117
  dst[:origin][:x] = destination.dig(:origin, :x) || 0
71
118
  dst[:origin][:y] = destination.dig(:origin, :y) || 0
72
119
  dst[:origin][:z] = destination.dig(:origin, :z) || 0
73
- dst[:aspect] = destination[:aspect] || :all
120
+ dst[:aspect] = Native::EnumHelper.coerce(
121
+ Native::TextureAspect,
122
+ destination[:aspect] || :all,
123
+ name: "texture aspect"
124
+ )
74
125
 
75
126
  Native.wgpuCommandEncoderCopyBufferToTexture(@handle, src, dst, size)
76
127
  end
77
128
 
129
+ # Copies a texture region into a buffer.
130
+ # @param source [Hash] texture and origin descriptor
131
+ # @param destination [Hash] buffer and layout descriptor
132
+ # @param copy_size [Hash, Array] extent to copy
133
+ # @return [void]
78
134
  def copy_texture_to_buffer(source:, destination:, copy_size:)
79
135
  raise CommandError, "Encoder already finished" if @finished
80
136
 
@@ -89,7 +145,11 @@ module WGPU
89
145
  src[:origin][:x] = source.dig(:origin, :x) || 0
90
146
  src[:origin][:y] = source.dig(:origin, :y) || 0
91
147
  src[:origin][:z] = source.dig(:origin, :z) || 0
92
- src[:aspect] = source[:aspect] || :all
148
+ src[:aspect] = Native::EnumHelper.coerce(
149
+ Native::TextureAspect,
150
+ source[:aspect] || :all,
151
+ name: "texture aspect"
152
+ )
93
153
 
94
154
  dst = Native::ImageCopyBuffer.new
95
155
  dst[:layout][:offset] = destination[:offset] || 0
@@ -100,6 +160,11 @@ module WGPU
100
160
  Native.wgpuCommandEncoderCopyTextureToBuffer(@handle, src, dst, size)
101
161
  end
102
162
 
163
+ # Copies one texture region into another texture.
164
+ # @param source [Hash] source texture and origin descriptor
165
+ # @param destination [Hash] destination texture and origin descriptor
166
+ # @param copy_size [Hash, Array] extent to copy
167
+ # @return [void]
103
168
  def copy_texture_to_texture(source:, destination:, copy_size:)
104
169
  raise CommandError, "Encoder already finished" if @finished
105
170
 
@@ -109,7 +174,11 @@ module WGPU
109
174
  src[:origin][:x] = source.dig(:origin, :x) || 0
110
175
  src[:origin][:y] = source.dig(:origin, :y) || 0
111
176
  src[:origin][:z] = source.dig(:origin, :z) || 0
112
- src[:aspect] = source[:aspect] || :all
177
+ src[:aspect] = Native::EnumHelper.coerce(
178
+ Native::TextureAspect,
179
+ source[:aspect] || :all,
180
+ name: "source texture aspect"
181
+ )
113
182
 
114
183
  dst = Native::ImageCopyTexture.new
115
184
  dst[:texture] = destination[:texture].handle
@@ -117,7 +186,11 @@ module WGPU
117
186
  dst[:origin][:x] = destination.dig(:origin, :x) || 0
118
187
  dst[:origin][:y] = destination.dig(:origin, :y) || 0
119
188
  dst[:origin][:z] = destination.dig(:origin, :z) || 0
120
- dst[:aspect] = destination[:aspect] || :all
189
+ dst[:aspect] = Native::EnumHelper.coerce(
190
+ Native::TextureAspect,
191
+ destination[:aspect] || :all,
192
+ name: "destination texture aspect"
193
+ )
121
194
 
122
195
  size = Native::Extent3D.new
123
196
  size[:width] = copy_size[:width] || copy_size[0]
@@ -127,6 +200,8 @@ module WGPU
127
200
  Native.wgpuCommandEncoderCopyTextureToTexture(@handle, src, dst, size)
128
201
  end
129
202
 
203
+ # Resolves query results into a destination buffer.
204
+ # @return [void]
130
205
  def resolve_query_set(query_set:, first_query:, query_count:, destination:, destination_offset:)
131
206
  raise CommandError, "Encoder already finished" if @finished
132
207
  Native.wgpuCommandEncoderResolveQuerySet(
@@ -139,17 +214,29 @@ module WGPU
139
214
  )
140
215
  end
141
216
 
217
+ # Clears a byte range in a buffer to zero.
218
+ # @param buffer [Buffer] buffer to clear
219
+ # @param offset [Integer] first byte to clear
220
+ # @param size [Integer, nil] number of bytes to clear
221
+ # @return [void]
142
222
  def clear_buffer(buffer, offset: 0, size: nil)
143
223
  raise CommandError, "Encoder already finished" if @finished
144
224
  size ||= buffer.size - offset
145
225
  Native.wgpuCommandEncoderClearBuffer(@handle, buffer.handle, offset, size)
146
226
  end
147
227
 
228
+ # Writes a GPU timestamp to a query set.
229
+ # @param query_set [QuerySet] timestamp query set
230
+ # @param query_index [Integer] destination query index
231
+ # @return [void]
148
232
  def write_timestamp(query_set, query_index)
149
233
  raise CommandError, "Encoder already finished" if @finished
150
234
  Native.wgpuCommandEncoderWriteTimestamp(@handle, query_set.handle, query_index)
151
235
  end
152
236
 
237
+ # Starts a labeled group in GPU debugging tools.
238
+ # @param label [String] group label
239
+ # @return [void]
153
240
  def push_debug_group(label)
154
241
  raise CommandError, "Encoder already finished" if @finished
155
242
  label_view = Native::StringView.new
@@ -159,11 +246,16 @@ module WGPU
159
246
  Native.wgpuCommandEncoderPushDebugGroup(@handle, label_view)
160
247
  end
161
248
 
249
+ # Ends the most recently pushed debug group.
250
+ # @return [void]
162
251
  def pop_debug_group
163
252
  raise CommandError, "Encoder already finished" if @finished
164
253
  Native.wgpuCommandEncoderPopDebugGroup(@handle)
165
254
  end
166
255
 
256
+ # Inserts a labeled point in GPU debugging tools.
257
+ # @param label [String] marker label
258
+ # @return [void]
167
259
  def insert_debug_marker(label)
168
260
  raise CommandError, "Encoder already finished" if @finished
169
261
  label_view = Native::StringView.new
@@ -173,8 +265,15 @@ module WGPU
173
265
  Native.wgpuCommandEncoderInsertDebugMarker(@handle, label_view)
174
266
  end
175
267
 
268
+ # Finishes recording and returns a command buffer.
269
+ # @param label [String, nil] optional command buffer label
270
+ # @return [CommandBuffer]
271
+ # @raise [CommandError] if the encoder is finished, has an active pass, or native creation fails
176
272
  def finish(label: nil)
177
273
  raise CommandError, "Encoder already finished" if @finished
274
+ if @active_pass && !@active_pass.ended?
275
+ raise CommandError, "Cannot finish command encoder while a pass is active"
276
+ end
178
277
  @finished = true
179
278
 
180
279
  desc = nil
@@ -189,13 +288,28 @@ module WGPU
189
288
  buffer_handle = Native.wgpuCommandEncoderFinish(@handle, desc)
190
289
  raise CommandError, "Failed to finish command encoder" if buffer_handle.null?
191
290
 
192
- CommandBuffer.new(buffer_handle)
291
+ CommandBuffer.new(buffer_handle, device: @device)
193
292
  end
194
293
 
294
+ # Releases the native command encoder handle.
295
+ #
296
+ # Calling this method more than once has no effect.
297
+ # @return [void]
195
298
  def release
196
299
  return if @handle.null?
197
300
  Native.wgpuCommandEncoderRelease(@handle)
198
301
  @handle = FFI::Pointer::NULL
199
302
  end
303
+
304
+ private
305
+
306
+ def ensure_can_begin_pass!
307
+ raise CommandError, "Encoder already finished" if @finished
308
+ raise CommandError, "A command pass is already active" if @active_pass && !@active_pass.ended?
309
+ end
310
+
311
+ def pass_ended(pass)
312
+ @active_pass = nil if @active_pass.equal?(pass)
313
+ end
200
314
  end
201
315
  end
@@ -4,7 +4,14 @@ 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)
13
+ @encoder = encoder
14
+ @ended = false
8
15
  desc = Native::ComputePassDescriptor.new
9
16
  desc[:next_in_chain] = nil
10
17
  if label
@@ -30,10 +37,18 @@ module WGPU
30
37
  raise CommandError, "Failed to begin compute pass" if @handle.null?
31
38
  end
32
39
 
40
+ # Selects the compute pipeline used by subsequent dispatches.
41
+ # @param pipeline [ComputePipeline] pipeline to bind
42
+ # @return [void]
33
43
  def set_pipeline(pipeline)
34
44
  Native.wgpuComputePassEncoderSetPipeline(@handle, pipeline.handle)
35
45
  end
36
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]
37
52
  def set_bind_group(index, bind_group, dynamic_offsets: [])
38
53
  if dynamic_offsets.empty?
39
54
  Native.wgpuComputePassEncoderSetBindGroup(@handle, index, bind_group.handle, 0, nil)
@@ -44,14 +59,27 @@ module WGPU
44
59
  end
45
60
  end
46
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]
47
67
  def dispatch_workgroups(x, y = 1, z = 1)
48
68
  Native.wgpuComputePassEncoderDispatchWorkgroups(@handle, x, y, z)
49
69
  end
50
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]
51
75
  def dispatch_workgroups_indirect(buffer, offset: 0)
52
76
  Native.wgpuComputePassEncoderDispatchWorkgroupsIndirect(@handle, buffer.handle, offset)
53
77
  end
54
78
 
79
+ # Starts a labeled group in GPU debugging tools.
80
+ #
81
+ # @param label [String] group label
82
+ # @return [void]
55
83
  def push_debug_group(label)
56
84
  label_view = Native::StringView.new
57
85
  label_ptr = FFI::MemoryPointer.from_string(label)
@@ -60,10 +88,17 @@ module WGPU
60
88
  Native.wgpuComputePassEncoderPushDebugGroup(@handle, label_view)
61
89
  end
62
90
 
91
+ # Ends the most recently pushed debug group.
92
+ #
93
+ # @return [void]
63
94
  def pop_debug_group
64
95
  Native.wgpuComputePassEncoderPopDebugGroup(@handle)
65
96
  end
66
97
 
98
+ # Inserts a labeled point in GPU debugging tools.
99
+ #
100
+ # @param label [String] marker label
101
+ # @return [void]
67
102
  def insert_debug_marker(label)
68
103
  label_view = Native::StringView.new
69
104
  label_ptr = FFI::MemoryPointer.from_string(label)
@@ -72,14 +107,32 @@ module WGPU
72
107
  Native.wgpuComputePassEncoderInsertDebugMarker(@handle, label_view)
73
108
  end
74
109
 
110
+ # Ends the pass if it has not already ended.
111
+ # @return [void]
75
112
  def end_pass
113
+ return if @ended
114
+
76
115
  Native.wgpuComputePassEncoderEnd(@handle)
116
+ @ended = true
117
+ @encoder.send(:pass_ended, self)
77
118
  end
78
119
 
120
+ # Ends the pass.
121
+ # @return [void]
79
122
  def end
80
123
  end_pass
81
124
  end
82
125
 
126
+ # Reports whether the pass has ended.
127
+ # @return [Boolean]
128
+ def ended?
129
+ @ended
130
+ end
131
+
132
+ # Releases the native compute pass encoder handle.
133
+ #
134
+ # Calling this method more than once has no effect.
135
+ # @return [void]
83
136
  def release
84
137
  return if @handle.null?
85
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
@@ -21,13 +30,19 @@ module WGPU
21
30
  desc[:label][:length] = 0
22
31
  end
23
32
 
24
- formats = Array(color_formats).map { |f| Native::TextureFormat[f] }
33
+ formats = Array(color_formats).map do |format|
34
+ Native::EnumHelper.coerce(Native::TextureFormat, format, name: "color format")
35
+ end
25
36
  @formats_ptr = FFI::MemoryPointer.new(:uint32, formats.size)
26
37
  @formats_ptr.write_array_of_uint32(formats)
27
38
  desc[:color_format_count] = formats.size
28
39
  desc[:color_formats] = @formats_ptr
29
40
 
30
- desc[:depth_stencil_format] = depth_stencil_format || :undefined
41
+ desc[:depth_stencil_format] = Native::EnumHelper.coerce(
42
+ Native::TextureFormat,
43
+ depth_stencil_format || :undefined,
44
+ name: "depth stencil format"
45
+ )
31
46
  desc[:sample_count] = sample_count
32
47
  desc[:depth_read_only] = depth_read_only ? 1 : 0
33
48
  desc[:stencil_read_only] = stencil_read_only ? 1 : 0
@@ -36,12 +51,21 @@ module WGPU
36
51
  raise RenderBundleError, "Failed to create render bundle encoder" if @handle.null?
37
52
  end
38
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]
39
58
  def set_pipeline(pipeline)
40
59
  raise RenderBundleError, "Encoder already finished" if @finished
41
60
 
42
61
  Native.wgpuRenderBundleEncoderSetPipeline(@handle, pipeline.handle)
43
62
  end
44
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]
45
69
  def set_bind_group(index, bind_group, dynamic_offsets: nil)
46
70
  raise RenderBundleError, "Encoder already finished" if @finished
47
71
 
@@ -54,6 +78,10 @@ module WGPU
54
78
  end
55
79
  end
56
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]
57
85
  def set_vertex_buffer(slot, buffer, offset: 0, size: nil)
58
86
  raise RenderBundleError, "Encoder already finished" if @finished
59
87
 
@@ -61,37 +89,57 @@ module WGPU
61
89
  Native.wgpuRenderBundleEncoderSetVertexBuffer(@handle, slot, buffer.handle, offset, size)
62
90
  end
63
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]
64
96
  def set_index_buffer(buffer, format: :uint32, offset: 0, size: nil)
65
97
  raise RenderBundleError, "Encoder already finished" if @finished
66
98
 
67
99
  size ||= buffer.size - offset
68
- Native.wgpuRenderBundleEncoderSetIndexBuffer(@handle, buffer.handle, format, offset, size)
100
+ format_value = Native::EnumHelper.coerce(Native::IndexFormat, format, name: "index format")
101
+ Native.wgpuRenderBundleEncoderSetIndexBuffer(@handle, buffer.handle, format_value, offset, size)
69
102
  end
70
103
 
104
+ # Records a non-indexed draw in the bundle.
105
+ # @return [void]
71
106
  def draw(vertex_count, instance_count: 1, first_vertex: 0, first_instance: 0)
72
107
  raise RenderBundleError, "Encoder already finished" if @finished
73
108
 
74
109
  Native.wgpuRenderBundleEncoderDraw(@handle, vertex_count, instance_count, first_vertex, first_instance)
75
110
  end
76
111
 
112
+ # Records an indexed draw in the bundle.
113
+ # @return [void]
77
114
  def draw_indexed(index_count, instance_count: 1, first_index: 0, base_vertex: 0, first_instance: 0)
78
115
  raise RenderBundleError, "Encoder already finished" if @finished
79
116
 
80
117
  Native.wgpuRenderBundleEncoderDrawIndexed(@handle, index_count, instance_count, first_index, base_vertex, first_instance)
81
118
  end
82
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]
83
124
  def draw_indirect(buffer, offset: 0)
84
125
  raise RenderBundleError, "Encoder already finished" if @finished
85
126
 
86
127
  Native.wgpuRenderBundleEncoderDrawIndirect(@handle, buffer.handle, offset)
87
128
  end
88
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]
89
134
  def draw_indexed_indirect(buffer, offset: 0)
90
135
  raise RenderBundleError, "Encoder already finished" if @finished
91
136
 
92
137
  Native.wgpuRenderBundleEncoderDrawIndexedIndirect(@handle, buffer.handle, offset)
93
138
  end
94
139
 
140
+ # Starts a labeled group in GPU debugging tools.
141
+ # @param label [String] group label
142
+ # @return [void]
95
143
  def push_debug_group(label)
96
144
  raise RenderBundleError, "Encoder already finished" if @finished
97
145
 
@@ -102,12 +150,17 @@ module WGPU
102
150
  Native.wgpuRenderBundleEncoderPushDebugGroup(@handle, label_view)
103
151
  end
104
152
 
153
+ # Ends the most recently pushed debug group.
154
+ # @return [void]
105
155
  def pop_debug_group
106
156
  raise RenderBundleError, "Encoder already finished" if @finished
107
157
 
108
158
  Native.wgpuRenderBundleEncoderPopDebugGroup(@handle)
109
159
  end
110
160
 
161
+ # Inserts a labeled point in GPU debugging tools.
162
+ # @param label [String] marker label
163
+ # @return [void]
111
164
  def insert_debug_marker(label)
112
165
  raise RenderBundleError, "Encoder already finished" if @finished
113
166
 
@@ -118,6 +171,10 @@ module WGPU
118
171
  Native.wgpuRenderBundleEncoderInsertDebugMarker(@handle, label_view)
119
172
  end
120
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
121
178
  def finish(label: nil)
122
179
  raise RenderBundleError, "Encoder already finished" if @finished
123
180
 
@@ -135,9 +192,13 @@ module WGPU
135
192
  bundle_handle = Native.wgpuRenderBundleEncoderFinish(@handle, desc)
136
193
  raise RenderBundleError, "Failed to finish render bundle encoder" if bundle_handle.null?
137
194
 
138
- RenderBundle.new(bundle_handle)
195
+ RenderBundle.new(bundle_handle, device: @device)
139
196
  end
140
197
 
198
+ # Releases the native render bundle encoder handle.
199
+ #
200
+ # Calling this method more than once has no effect.
201
+ # @return [void]
141
202
  def release
142
203
  return if @handle.null?
143
204