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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -0
- data/README.md +18 -2
- data/docs/README.md +2 -2
- data/docs/api_coverage.md +23 -15
- data/docs/async.md +13 -1
- data/docs/command_encoding.md +25 -0
- data/docs/errors.md +13 -0
- data/docs/getting_started_compute.md +1 -0
- data/docs/getting_started_rendering.md +5 -0
- data/docs/pipeline_descriptors.md +7 -2
- data/docs/releasing.md +11 -1
- data/docs/resource_lifetime.md +25 -2
- data/docs/texture_readback.md +27 -0
- data/docs/troubleshooting.md +5 -0
- data/docs/upgrading_wgpu_native.md +16 -5
- data/lib/wgpu/async_task.rb +19 -0
- data/lib/wgpu/commands/command_buffer.rb +14 -1
- data/lib/wgpu/commands/command_encoder.rb +82 -27
- data/lib/wgpu/commands/compute_pass.rb +49 -6
- data/lib/wgpu/commands/render_bundle.rb +9 -1
- data/lib/wgpu/commands/render_bundle_encoder.rb +63 -9
- data/lib/wgpu/commands/render_pass.rb +116 -14
- data/lib/wgpu/core/adapter.rb +95 -13
- data/lib/wgpu/core/async_waiter.rb +30 -4
- data/lib/wgpu/core/canvas_context.rb +37 -2
- data/lib/wgpu/core/device.rb +377 -70
- data/lib/wgpu/core/instance.rb +20 -0
- data/lib/wgpu/core/queue.rb +143 -43
- data/lib/wgpu/core/surface.rb +55 -7
- data/lib/wgpu/data_types.rb +16 -0
- data/lib/wgpu/descriptor_helpers.rb +87 -0
- data/lib/wgpu/error.rb +15 -0
- data/lib/wgpu/native/abi_verifier.rb +37 -3
- data/lib/wgpu/native/callbacks.rb +6 -0
- data/lib/wgpu/native/capabilities.rb +16 -2
- data/lib/wgpu/native/distribution.rb +63 -0
- data/lib/wgpu/native/enum_helper.rb +17 -0
- data/lib/wgpu/native/enums.rb +8 -0
- data/lib/wgpu/native/fixtures/webgpu-v27.0.4.0-enums.h +848 -0
- data/lib/wgpu/native/functions.rb +14 -0
- data/lib/wgpu/native/installer.rb +55 -18
- data/lib/wgpu/native/loader.rb +22 -0
- data/lib/wgpu/native/structs.rb +18 -1
- data/lib/wgpu/native_resource.rb +190 -3
- data/lib/wgpu/pipeline/bind_group.rb +16 -7
- data/lib/wgpu/pipeline/bind_group_layout.rb +20 -7
- data/lib/wgpu/pipeline/compute_pipeline.rb +25 -37
- data/lib/wgpu/pipeline/pipeline_layout.rb +12 -4
- data/lib/wgpu/pipeline/render_pipeline.rb +33 -48
- data/lib/wgpu/pipeline/shader_module.rb +57 -32
- data/lib/wgpu/resources/buffer.rb +330 -59
- data/lib/wgpu/resources/query_set.rb +13 -1
- data/lib/wgpu/resources/sampler.rb +10 -3
- data/lib/wgpu/resources/texture.rb +49 -12
- data/lib/wgpu/resources/texture_view.rb +25 -5
- data/lib/wgpu/texture_format.rb +14 -0
- data/lib/wgpu/version.rb +1 -1
- data/lib/wgpu/window.rb +26 -0
- data/sig/wgpu.rbs +85 -5
- 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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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]
|
|
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]
|
|
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]
|
|
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,
|
data/lib/wgpu/core/adapter.rb
CHANGED
|
@@ -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 =
|
|
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 = {
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
status_holder[:
|
|
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
|
|
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
|
-
|
|
63
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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[:
|
|
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
|