wgpu 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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-APACHE +190 -0
  3. data/LICENSE-MIT +21 -0
  4. data/README.md +228 -0
  5. data/ext/wgpu/Makefile +7 -0
  6. data/ext/wgpu/extconf.rb +161 -0
  7. data/lib/wgpu/async_task.rb +55 -0
  8. data/lib/wgpu/commands/command_buffer.rb +17 -0
  9. data/lib/wgpu/commands/command_encoder.rb +201 -0
  10. data/lib/wgpu/commands/compute_pass.rb +89 -0
  11. data/lib/wgpu/commands/render_bundle.rb +18 -0
  12. data/lib/wgpu/commands/render_bundle_encoder.rb +148 -0
  13. data/lib/wgpu/commands/render_pass.rb +207 -0
  14. data/lib/wgpu/core/adapter.rb +186 -0
  15. data/lib/wgpu/core/canvas_context.rb +104 -0
  16. data/lib/wgpu/core/device.rb +397 -0
  17. data/lib/wgpu/core/instance.rb +81 -0
  18. data/lib/wgpu/core/queue.rb +197 -0
  19. data/lib/wgpu/core/surface.rb +221 -0
  20. data/lib/wgpu/error.rb +16 -0
  21. data/lib/wgpu/native/callbacks.rb +26 -0
  22. data/lib/wgpu/native/enums.rb +529 -0
  23. data/lib/wgpu/native/functions.rb +419 -0
  24. data/lib/wgpu/native/loader.rb +61 -0
  25. data/lib/wgpu/native/structs.rb +646 -0
  26. data/lib/wgpu/pipeline/bind_group.rb +80 -0
  27. data/lib/wgpu/pipeline/bind_group_layout.rb +121 -0
  28. data/lib/wgpu/pipeline/compute_pipeline.rb +88 -0
  29. data/lib/wgpu/pipeline/pipeline_layout.rb +43 -0
  30. data/lib/wgpu/pipeline/render_pipeline.rb +278 -0
  31. data/lib/wgpu/pipeline/shader_module.rb +202 -0
  32. data/lib/wgpu/resources/buffer.rb +228 -0
  33. data/lib/wgpu/resources/query_set.rb +45 -0
  34. data/lib/wgpu/resources/sampler.rb +47 -0
  35. data/lib/wgpu/resources/texture.rb +136 -0
  36. data/lib/wgpu/resources/texture_view.rb +49 -0
  37. data/lib/wgpu/version.rb +5 -0
  38. data/lib/wgpu/window.rb +177 -0
  39. data/lib/wgpu.rb +36 -0
  40. metadata +125 -0
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class CommandEncoder
5
+ attr_reader :handle
6
+
7
+ def initialize(device, label: nil)
8
+ @device = device
9
+ @finished = false
10
+
11
+ desc = Native::CommandEncoderDescriptor.new
12
+ desc[:next_in_chain] = nil
13
+ if label
14
+ label_ptr = FFI::MemoryPointer.from_string(label)
15
+ desc[:label][:data] = label_ptr
16
+ desc[:label][:length] = label.bytesize
17
+ else
18
+ desc[:label][:data] = nil
19
+ desc[:label][:length] = 0
20
+ end
21
+
22
+ @handle = Native.wgpuDeviceCreateCommandEncoder(device.handle, desc)
23
+ raise CommandError, "Failed to create command encoder" if @handle.null?
24
+ end
25
+
26
+ 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)
29
+ end
30
+
31
+ 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,
34
+ color_attachments: color_attachments,
35
+ depth_stencil_attachment: depth_stencil_attachment,
36
+ occlusion_query_set: occlusion_query_set,
37
+ timestamp_writes: timestamp_writes,
38
+ max_draw_count: max_draw_count,
39
+ label: label
40
+ )
41
+ end
42
+
43
+ def copy_buffer_to_buffer(source:, source_offset: 0, destination:, destination_offset: 0, size:)
44
+ raise CommandError, "Encoder already finished" if @finished
45
+ Native.wgpuCommandEncoderCopyBufferToBuffer(
46
+ @handle,
47
+ source.handle, source_offset,
48
+ destination.handle, destination_offset,
49
+ size
50
+ )
51
+ end
52
+
53
+ def copy_buffer_to_texture(source:, destination:, copy_size:)
54
+ raise CommandError, "Encoder already finished" if @finished
55
+
56
+ size = Native::Extent3D.new
57
+ size[:width] = copy_size[:width] || copy_size[0]
58
+ size[:height] = copy_size[:height] || copy_size[1] || 1
59
+ size[:depth_or_array_layers] = copy_size[:depth_or_array_layers] || copy_size[2] || 1
60
+
61
+ src = Native::ImageCopyBuffer.new
62
+ src[:layout][:offset] = source[:offset] || 0
63
+ src[:layout][:bytes_per_row] = source[:bytes_per_row]
64
+ src[:layout][:rows_per_image] = source[:rows_per_image] || size[:height]
65
+ src[:buffer] = source[:buffer].handle
66
+
67
+ dst = Native::ImageCopyTexture.new
68
+ dst[:texture] = destination[:texture].handle
69
+ dst[:mip_level] = destination[:mip_level] || 0
70
+ dst[:origin][:x] = destination.dig(:origin, :x) || 0
71
+ dst[:origin][:y] = destination.dig(:origin, :y) || 0
72
+ dst[:origin][:z] = destination.dig(:origin, :z) || 0
73
+ dst[:aspect] = destination[:aspect] || :all
74
+
75
+ Native.wgpuCommandEncoderCopyBufferToTexture(@handle, src, dst, size)
76
+ end
77
+
78
+ def copy_texture_to_buffer(source:, destination:, copy_size:)
79
+ raise CommandError, "Encoder already finished" if @finished
80
+
81
+ size = Native::Extent3D.new
82
+ size[:width] = copy_size[:width] || copy_size[0]
83
+ size[:height] = copy_size[:height] || copy_size[1] || 1
84
+ size[:depth_or_array_layers] = copy_size[:depth_or_array_layers] || copy_size[2] || 1
85
+
86
+ src = Native::ImageCopyTexture.new
87
+ src[:texture] = source[:texture].handle
88
+ src[:mip_level] = source[:mip_level] || 0
89
+ src[:origin][:x] = source.dig(:origin, :x) || 0
90
+ src[:origin][:y] = source.dig(:origin, :y) || 0
91
+ src[:origin][:z] = source.dig(:origin, :z) || 0
92
+ src[:aspect] = source[:aspect] || :all
93
+
94
+ dst = Native::ImageCopyBuffer.new
95
+ dst[:layout][:offset] = destination[:offset] || 0
96
+ dst[:layout][:bytes_per_row] = destination[:bytes_per_row]
97
+ dst[:layout][:rows_per_image] = destination[:rows_per_image] || size[:height]
98
+ dst[:buffer] = destination[:buffer].handle
99
+
100
+ Native.wgpuCommandEncoderCopyTextureToBuffer(@handle, src, dst, size)
101
+ end
102
+
103
+ def copy_texture_to_texture(source:, destination:, copy_size:)
104
+ raise CommandError, "Encoder already finished" if @finished
105
+
106
+ src = Native::ImageCopyTexture.new
107
+ src[:texture] = source[:texture].handle
108
+ src[:mip_level] = source[:mip_level] || 0
109
+ src[:origin][:x] = source.dig(:origin, :x) || 0
110
+ src[:origin][:y] = source.dig(:origin, :y) || 0
111
+ src[:origin][:z] = source.dig(:origin, :z) || 0
112
+ src[:aspect] = source[:aspect] || :all
113
+
114
+ dst = Native::ImageCopyTexture.new
115
+ dst[:texture] = destination[:texture].handle
116
+ dst[:mip_level] = destination[:mip_level] || 0
117
+ dst[:origin][:x] = destination.dig(:origin, :x) || 0
118
+ dst[:origin][:y] = destination.dig(:origin, :y) || 0
119
+ dst[:origin][:z] = destination.dig(:origin, :z) || 0
120
+ dst[:aspect] = destination[:aspect] || :all
121
+
122
+ size = Native::Extent3D.new
123
+ size[:width] = copy_size[:width] || copy_size[0]
124
+ size[:height] = copy_size[:height] || copy_size[1] || 1
125
+ size[:depth_or_array_layers] = copy_size[:depth_or_array_layers] || copy_size[2] || 1
126
+
127
+ Native.wgpuCommandEncoderCopyTextureToTexture(@handle, src, dst, size)
128
+ end
129
+
130
+ def resolve_query_set(query_set:, first_query:, query_count:, destination:, destination_offset:)
131
+ raise CommandError, "Encoder already finished" if @finished
132
+ Native.wgpuCommandEncoderResolveQuerySet(
133
+ @handle,
134
+ query_set.handle,
135
+ first_query,
136
+ query_count,
137
+ destination.handle,
138
+ destination_offset
139
+ )
140
+ end
141
+
142
+ def clear_buffer(buffer, offset: 0, size: nil)
143
+ raise CommandError, "Encoder already finished" if @finished
144
+ size ||= buffer.size - offset
145
+ Native.wgpuCommandEncoderClearBuffer(@handle, buffer.handle, offset, size)
146
+ end
147
+
148
+ def write_timestamp(query_set, query_index)
149
+ raise CommandError, "Encoder already finished" if @finished
150
+ Native.wgpuCommandEncoderWriteTimestamp(@handle, query_set.handle, query_index)
151
+ end
152
+
153
+ def push_debug_group(label)
154
+ raise CommandError, "Encoder already finished" if @finished
155
+ label_view = Native::StringView.new
156
+ label_ptr = FFI::MemoryPointer.from_string(label)
157
+ label_view[:data] = label_ptr
158
+ label_view[:length] = label.bytesize
159
+ Native.wgpuCommandEncoderPushDebugGroup(@handle, label_view)
160
+ end
161
+
162
+ def pop_debug_group
163
+ raise CommandError, "Encoder already finished" if @finished
164
+ Native.wgpuCommandEncoderPopDebugGroup(@handle)
165
+ end
166
+
167
+ def insert_debug_marker(label)
168
+ raise CommandError, "Encoder already finished" if @finished
169
+ label_view = Native::StringView.new
170
+ label_ptr = FFI::MemoryPointer.from_string(label)
171
+ label_view[:data] = label_ptr
172
+ label_view[:length] = label.bytesize
173
+ Native.wgpuCommandEncoderInsertDebugMarker(@handle, label_view)
174
+ end
175
+
176
+ def finish(label: nil)
177
+ raise CommandError, "Encoder already finished" if @finished
178
+ @finished = true
179
+
180
+ desc = nil
181
+ if label
182
+ desc = Native::CommandBufferDescriptor.new
183
+ desc[:next_in_chain] = nil
184
+ label_ptr = FFI::MemoryPointer.from_string(label)
185
+ desc[:label][:data] = label_ptr
186
+ desc[:label][:length] = label.bytesize
187
+ end
188
+
189
+ buffer_handle = Native.wgpuCommandEncoderFinish(@handle, desc)
190
+ raise CommandError, "Failed to finish command encoder" if buffer_handle.null?
191
+
192
+ CommandBuffer.new(buffer_handle)
193
+ end
194
+
195
+ def release
196
+ return if @handle.null?
197
+ Native.wgpuCommandEncoderRelease(@handle)
198
+ @handle = FFI::Pointer::NULL
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class ComputePass
5
+ attr_reader :handle
6
+
7
+ def initialize(encoder, label: nil, timestamp_writes: nil)
8
+ desc = Native::ComputePassDescriptor.new
9
+ desc[:next_in_chain] = nil
10
+ if label
11
+ label_ptr = FFI::MemoryPointer.from_string(label)
12
+ desc[:label][:data] = label_ptr
13
+ desc[:label][:length] = label.bytesize
14
+ else
15
+ desc[:label][:data] = nil
16
+ desc[:label][:length] = 0
17
+ end
18
+ @timestamp_writes = nil
19
+ if timestamp_writes
20
+ @timestamp_writes = Native::ComputePassTimestampWrites.new
21
+ @timestamp_writes[:query_set] = timestamp_writes.fetch(:query_set).handle
22
+ @timestamp_writes[:beginning_of_pass_write_index] = timestamp_writes[:beginning_of_pass_write_index] || 0xFFFFFFFF
23
+ @timestamp_writes[:end_of_pass_write_index] = timestamp_writes[:end_of_pass_write_index] || 0xFFFFFFFF
24
+ desc[:timestamp_writes] = @timestamp_writes.to_ptr
25
+ else
26
+ desc[:timestamp_writes] = nil
27
+ end
28
+
29
+ @handle = Native.wgpuCommandEncoderBeginComputePass(encoder.handle, desc)
30
+ raise CommandError, "Failed to begin compute pass" if @handle.null?
31
+ end
32
+
33
+ def set_pipeline(pipeline)
34
+ Native.wgpuComputePassEncoderSetPipeline(@handle, pipeline.handle)
35
+ end
36
+
37
+ def set_bind_group(index, bind_group, dynamic_offsets: [])
38
+ if dynamic_offsets.empty?
39
+ Native.wgpuComputePassEncoderSetBindGroup(@handle, index, bind_group.handle, 0, nil)
40
+ else
41
+ offsets_ptr = FFI::MemoryPointer.new(:uint32, dynamic_offsets.size)
42
+ offsets_ptr.write_array_of_uint32(dynamic_offsets)
43
+ Native.wgpuComputePassEncoderSetBindGroup(@handle, index, bind_group.handle, dynamic_offsets.size, offsets_ptr)
44
+ end
45
+ end
46
+
47
+ def dispatch_workgroups(x, y = 1, z = 1)
48
+ Native.wgpuComputePassEncoderDispatchWorkgroups(@handle, x, y, z)
49
+ end
50
+
51
+ def dispatch_workgroups_indirect(buffer, offset: 0)
52
+ Native.wgpuComputePassEncoderDispatchWorkgroupsIndirect(@handle, buffer.handle, offset)
53
+ end
54
+
55
+ def push_debug_group(label)
56
+ label_view = Native::StringView.new
57
+ label_ptr = FFI::MemoryPointer.from_string(label)
58
+ label_view[:data] = label_ptr
59
+ label_view[:length] = label.bytesize
60
+ Native.wgpuComputePassEncoderPushDebugGroup(@handle, label_view)
61
+ end
62
+
63
+ def pop_debug_group
64
+ Native.wgpuComputePassEncoderPopDebugGroup(@handle)
65
+ end
66
+
67
+ def insert_debug_marker(label)
68
+ label_view = Native::StringView.new
69
+ label_ptr = FFI::MemoryPointer.from_string(label)
70
+ label_view[:data] = label_ptr
71
+ label_view[:length] = label.bytesize
72
+ Native.wgpuComputePassEncoderInsertDebugMarker(@handle, label_view)
73
+ end
74
+
75
+ def end_pass
76
+ Native.wgpuComputePassEncoderEnd(@handle)
77
+ end
78
+
79
+ def end
80
+ end_pass
81
+ end
82
+
83
+ def release
84
+ return if @handle.null?
85
+ Native.wgpuComputePassEncoderRelease(@handle)
86
+ @handle = FFI::Pointer::NULL
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class RenderBundle
5
+ attr_reader :handle
6
+
7
+ def initialize(handle)
8
+ @handle = handle
9
+ end
10
+
11
+ def release
12
+ return if @handle.null?
13
+
14
+ Native.wgpuRenderBundleRelease(@handle)
15
+ @handle = FFI::Pointer::NULL
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class RenderBundleEncoder
5
+ attr_reader :handle
6
+
7
+ def initialize(device, color_formats:, depth_stencil_format: nil, sample_count: 1,
8
+ depth_read_only: false, stencil_read_only: false, label: nil)
9
+ @device = device
10
+ @finished = false
11
+
12
+ desc = Native::RenderBundleEncoderDescriptor.new
13
+ desc[:next_in_chain] = nil
14
+
15
+ if label
16
+ @label_ptr = FFI::MemoryPointer.from_string(label)
17
+ desc[:label][:data] = @label_ptr
18
+ desc[:label][:length] = label.bytesize
19
+ else
20
+ desc[:label][:data] = nil
21
+ desc[:label][:length] = 0
22
+ end
23
+
24
+ formats = Array(color_formats).map { |f| Native::TextureFormat[f] }
25
+ @formats_ptr = FFI::MemoryPointer.new(:uint32, formats.size)
26
+ @formats_ptr.write_array_of_uint32(formats)
27
+ desc[:color_format_count] = formats.size
28
+ desc[:color_formats] = @formats_ptr
29
+
30
+ desc[:depth_stencil_format] = depth_stencil_format || :undefined
31
+ desc[:sample_count] = sample_count
32
+ desc[:depth_read_only] = depth_read_only ? 1 : 0
33
+ desc[:stencil_read_only] = stencil_read_only ? 1 : 0
34
+
35
+ @handle = Native.wgpuDeviceCreateRenderBundleEncoder(device.handle, desc)
36
+ raise RenderBundleError, "Failed to create render bundle encoder" if @handle.null?
37
+ end
38
+
39
+ def set_pipeline(pipeline)
40
+ raise RenderBundleError, "Encoder already finished" if @finished
41
+
42
+ Native.wgpuRenderBundleEncoderSetPipeline(@handle, pipeline.handle)
43
+ end
44
+
45
+ def set_bind_group(index, bind_group, dynamic_offsets: nil)
46
+ raise RenderBundleError, "Encoder already finished" if @finished
47
+
48
+ if dynamic_offsets && !dynamic_offsets.empty?
49
+ offsets_ptr = FFI::MemoryPointer.new(:uint32, dynamic_offsets.size)
50
+ offsets_ptr.write_array_of_uint32(dynamic_offsets)
51
+ Native.wgpuRenderBundleEncoderSetBindGroup(@handle, index, bind_group.handle, dynamic_offsets.size, offsets_ptr)
52
+ else
53
+ Native.wgpuRenderBundleEncoderSetBindGroup(@handle, index, bind_group.handle, 0, nil)
54
+ end
55
+ end
56
+
57
+ def set_vertex_buffer(slot, buffer, offset: 0, size: nil)
58
+ raise RenderBundleError, "Encoder already finished" if @finished
59
+
60
+ size ||= buffer.size - offset
61
+ Native.wgpuRenderBundleEncoderSetVertexBuffer(@handle, slot, buffer.handle, offset, size)
62
+ end
63
+
64
+ def set_index_buffer(buffer, format: :uint32, offset: 0, size: nil)
65
+ raise RenderBundleError, "Encoder already finished" if @finished
66
+
67
+ size ||= buffer.size - offset
68
+ Native.wgpuRenderBundleEncoderSetIndexBuffer(@handle, buffer.handle, format, offset, size)
69
+ end
70
+
71
+ def draw(vertex_count, instance_count: 1, first_vertex: 0, first_instance: 0)
72
+ raise RenderBundleError, "Encoder already finished" if @finished
73
+
74
+ Native.wgpuRenderBundleEncoderDraw(@handle, vertex_count, instance_count, first_vertex, first_instance)
75
+ end
76
+
77
+ def draw_indexed(index_count, instance_count: 1, first_index: 0, base_vertex: 0, first_instance: 0)
78
+ raise RenderBundleError, "Encoder already finished" if @finished
79
+
80
+ Native.wgpuRenderBundleEncoderDrawIndexed(@handle, index_count, instance_count, first_index, base_vertex, first_instance)
81
+ end
82
+
83
+ def draw_indirect(buffer, offset: 0)
84
+ raise RenderBundleError, "Encoder already finished" if @finished
85
+
86
+ Native.wgpuRenderBundleEncoderDrawIndirect(@handle, buffer.handle, offset)
87
+ end
88
+
89
+ def draw_indexed_indirect(buffer, offset: 0)
90
+ raise RenderBundleError, "Encoder already finished" if @finished
91
+
92
+ Native.wgpuRenderBundleEncoderDrawIndexedIndirect(@handle, buffer.handle, offset)
93
+ end
94
+
95
+ def push_debug_group(label)
96
+ raise RenderBundleError, "Encoder already finished" if @finished
97
+
98
+ label_view = Native::StringView.new
99
+ label_ptr = FFI::MemoryPointer.from_string(label)
100
+ label_view[:data] = label_ptr
101
+ label_view[:length] = label.bytesize
102
+ Native.wgpuRenderBundleEncoderPushDebugGroup(@handle, label_view)
103
+ end
104
+
105
+ def pop_debug_group
106
+ raise RenderBundleError, "Encoder already finished" if @finished
107
+
108
+ Native.wgpuRenderBundleEncoderPopDebugGroup(@handle)
109
+ end
110
+
111
+ def insert_debug_marker(label)
112
+ raise RenderBundleError, "Encoder already finished" if @finished
113
+
114
+ label_view = Native::StringView.new
115
+ label_ptr = FFI::MemoryPointer.from_string(label)
116
+ label_view[:data] = label_ptr
117
+ label_view[:length] = label.bytesize
118
+ Native.wgpuRenderBundleEncoderInsertDebugMarker(@handle, label_view)
119
+ end
120
+
121
+ def finish(label: nil)
122
+ raise RenderBundleError, "Encoder already finished" if @finished
123
+
124
+ @finished = true
125
+
126
+ desc = nil
127
+ if label
128
+ desc = Native::RenderBundleDescriptor.new
129
+ desc[:next_in_chain] = nil
130
+ label_ptr = FFI::MemoryPointer.from_string(label)
131
+ desc[:label][:data] = label_ptr
132
+ desc[:label][:length] = label.bytesize
133
+ end
134
+
135
+ bundle_handle = Native.wgpuRenderBundleEncoderFinish(@handle, desc)
136
+ raise RenderBundleError, "Failed to finish render bundle encoder" if bundle_handle.null?
137
+
138
+ RenderBundle.new(bundle_handle)
139
+ end
140
+
141
+ def release
142
+ return if @handle.null?
143
+
144
+ Native.wgpuRenderBundleEncoderRelease(@handle)
145
+ @handle = FFI::Pointer::NULL
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WGPU
4
+ class RenderPass
5
+ attr_reader :handle
6
+
7
+ def initialize(encoder, label: nil, color_attachments:, depth_stencil_attachment: nil, occlusion_query_set: nil, timestamp_writes: nil, max_draw_count: nil)
8
+ @encoder = encoder
9
+ @pointers = []
10
+ @max_draw_count = max_draw_count
11
+
12
+ desc = Native::RenderPassDescriptor.new
13
+ desc[:next_in_chain] = nil
14
+ setup_label(desc, label)
15
+
16
+ color_attachments_ptr = setup_color_attachments(color_attachments)
17
+ desc[:color_attachment_count] = color_attachments.size
18
+ desc[:color_attachments] = color_attachments_ptr
19
+
20
+ if depth_stencil_attachment
21
+ ds_ptr = setup_depth_stencil_attachment(depth_stencil_attachment)
22
+ desc[:depth_stencil_attachment] = ds_ptr
23
+ else
24
+ desc[:depth_stencil_attachment] = nil
25
+ end
26
+
27
+ desc[:occlusion_query_set] = occlusion_query_set&.handle
28
+ if timestamp_writes
29
+ ts = Native::RenderPassTimestampWrites.new
30
+ ts[:query_set] = timestamp_writes.fetch(:query_set).handle
31
+ ts[:beginning_of_pass_write_index] = timestamp_writes[:beginning_of_pass_write_index] || 0xFFFFFFFF
32
+ ts[:end_of_pass_write_index] = timestamp_writes[:end_of_pass_write_index] || 0xFFFFFFFF
33
+ @pointers << ts
34
+ desc[:timestamp_writes] = ts.to_ptr
35
+ else
36
+ desc[:timestamp_writes] = nil
37
+ end
38
+
39
+ @handle = Native.wgpuCommandEncoderBeginRenderPass(encoder.handle, desc)
40
+ raise CommandError, "Failed to begin render pass" if @handle.null?
41
+ end
42
+
43
+ def set_pipeline(pipeline)
44
+ Native.wgpuRenderPassEncoderSetPipeline(@handle, pipeline.handle)
45
+ end
46
+
47
+ def set_bind_group(index, bind_group, dynamic_offsets: [])
48
+ if dynamic_offsets.empty?
49
+ Native.wgpuRenderPassEncoderSetBindGroup(@handle, index, bind_group.handle, 0, nil)
50
+ else
51
+ offsets_ptr = FFI::MemoryPointer.new(:uint32, dynamic_offsets.size)
52
+ offsets_ptr.write_array_of_uint32(dynamic_offsets)
53
+ Native.wgpuRenderPassEncoderSetBindGroup(@handle, index, bind_group.handle, dynamic_offsets.size, offsets_ptr)
54
+ end
55
+ end
56
+
57
+ def set_vertex_buffer(slot, buffer, offset: 0, size: nil)
58
+ size ||= buffer.size - offset
59
+ Native.wgpuRenderPassEncoderSetVertexBuffer(@handle, slot, buffer.handle, offset, size)
60
+ end
61
+
62
+ def set_index_buffer(buffer, format, offset: 0, size: nil)
63
+ size ||= buffer.size - offset
64
+ Native.wgpuRenderPassEncoderSetIndexBuffer(@handle, buffer.handle, format, offset, size)
65
+ end
66
+
67
+ def draw(vertex_count, instance_count: 1, first_vertex: 0, first_instance: 0)
68
+ Native.wgpuRenderPassEncoderDraw(@handle, vertex_count, instance_count, first_vertex, first_instance)
69
+ end
70
+
71
+ def draw_indexed(index_count, instance_count: 1, first_index: 0, base_vertex: 0, first_instance: 0)
72
+ Native.wgpuRenderPassEncoderDrawIndexed(@handle, index_count, instance_count, first_index, base_vertex, first_instance)
73
+ end
74
+
75
+ def set_viewport(x, y, width, height, min_depth: 0.0, max_depth: 1.0)
76
+ Native.wgpuRenderPassEncoderSetViewport(@handle, x, y, width, height, min_depth, max_depth)
77
+ end
78
+
79
+ def set_scissor_rect(x, y, width, height)
80
+ Native.wgpuRenderPassEncoderSetScissorRect(@handle, x, y, width, height)
81
+ end
82
+
83
+ def set_blend_constant(r: 0.0, g: 0.0, b: 0.0, a: 1.0)
84
+ color = Native::Color.new
85
+ color[:r] = r
86
+ color[:g] = g
87
+ color[:b] = b
88
+ color[:a] = a
89
+ Native.wgpuRenderPassEncoderSetBlendConstant(@handle, color.to_ptr)
90
+ end
91
+
92
+ def set_stencil_reference(reference)
93
+ Native.wgpuRenderPassEncoderSetStencilReference(@handle, reference)
94
+ end
95
+
96
+ def draw_indirect(buffer, offset: 0)
97
+ Native.wgpuRenderPassEncoderDrawIndirect(@handle, buffer.handle, offset)
98
+ end
99
+
100
+ def draw_indexed_indirect(buffer, offset: 0)
101
+ Native.wgpuRenderPassEncoderDrawIndexedIndirect(@handle, buffer.handle, offset)
102
+ end
103
+
104
+ def execute_bundles(bundles)
105
+ bundle_handles = bundles.map(&:handle)
106
+ bundles_ptr = FFI::MemoryPointer.new(:pointer, bundle_handles.size)
107
+ bundles_ptr.write_array_of_pointer(bundle_handles)
108
+ Native.wgpuRenderPassEncoderExecuteBundles(@handle, bundle_handles.size, bundles_ptr)
109
+ end
110
+
111
+ def begin_occlusion_query(query_index)
112
+ Native.wgpuRenderPassEncoderBeginOcclusionQuery(@handle, query_index)
113
+ end
114
+
115
+ def end_occlusion_query
116
+ Native.wgpuRenderPassEncoderEndOcclusionQuery(@handle)
117
+ end
118
+
119
+ def push_debug_group(label)
120
+ label_view = Native::StringView.new
121
+ label_ptr = FFI::MemoryPointer.from_string(label)
122
+ label_view[:data] = label_ptr
123
+ label_view[:length] = label.bytesize
124
+ Native.wgpuRenderPassEncoderPushDebugGroup(@handle, label_view)
125
+ end
126
+
127
+ def pop_debug_group
128
+ Native.wgpuRenderPassEncoderPopDebugGroup(@handle)
129
+ end
130
+
131
+ def insert_debug_marker(label)
132
+ label_view = Native::StringView.new
133
+ label_ptr = FFI::MemoryPointer.from_string(label)
134
+ label_view[:data] = label_ptr
135
+ label_view[:length] = label.bytesize
136
+ Native.wgpuRenderPassEncoderInsertDebugMarker(@handle, label_view)
137
+ end
138
+
139
+ def end_pass
140
+ Native.wgpuRenderPassEncoderEnd(@handle)
141
+ end
142
+
143
+ def end
144
+ end_pass
145
+ end
146
+
147
+ def release
148
+ return if @handle.null?
149
+ Native.wgpuRenderPassEncoderRelease(@handle)
150
+ @handle = FFI::Pointer::NULL
151
+ end
152
+
153
+ private
154
+
155
+ def setup_label(desc, label)
156
+ if label
157
+ ptr = FFI::MemoryPointer.from_string(label)
158
+ @pointers << ptr
159
+ desc[:label][:data] = ptr
160
+ desc[:label][:length] = label.bytesize
161
+ else
162
+ desc[:label][:data] = nil
163
+ desc[:label][:length] = 0
164
+ end
165
+ end
166
+
167
+ def setup_color_attachments(attachments)
168
+ ptr = FFI::MemoryPointer.new(Native::RenderPassColorAttachment, attachments.size)
169
+ @pointers << ptr
170
+
171
+ attachments.each_with_index do |att, i|
172
+ ca = Native::RenderPassColorAttachment.new(ptr + i * Native::RenderPassColorAttachment.size)
173
+ ca[:next_in_chain] = nil
174
+ ca[:view] = att[:view].handle
175
+ ca[:depth_slice] = att[:depth_slice] || 0xFFFFFFFF
176
+ ca[:resolve_target] = att[:resolve_target]&.handle
177
+ ca[:load_op] = att[:load_op] || :clear
178
+ ca[:store_op] = att[:store_op] || :store
179
+
180
+ clear = att[:clear_value] || { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }
181
+ ca[:clear_value][:r] = clear[:r] || 0.0
182
+ ca[:clear_value][:g] = clear[:g] || 0.0
183
+ ca[:clear_value][:b] = clear[:b] || 0.0
184
+ ca[:clear_value][:a] = clear[:a] || 1.0
185
+ end
186
+
187
+ ptr
188
+ end
189
+
190
+ def setup_depth_stencil_attachment(att)
191
+ ds = Native::RenderPassDepthStencilAttachment.new
192
+ @pointers << ds
193
+
194
+ ds[:view] = att[:view].handle
195
+ ds[:depth_load_op] = att[:depth_load_op] || :clear
196
+ ds[:depth_store_op] = att[:depth_store_op] || :store
197
+ ds[:depth_clear_value] = att[:depth_clear_value] || 1.0
198
+ ds[:depth_read_only] = att[:depth_read_only] ? 1 : 0
199
+ ds[:stencil_load_op] = att[:stencil_load_op] || :clear
200
+ ds[:stencil_store_op] = att[:stencil_store_op] || :store
201
+ ds[:stencil_clear_value] = att[:stencil_clear_value] || 0
202
+ ds[:stencil_read_only] = att[:stencil_read_only] ? 1 : 0
203
+
204
+ ds.to_ptr
205
+ end
206
+ end
207
+ end