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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +88 -0
- data/README.md +44 -5
- data/docs/README.md +22 -0
- data/docs/api_coverage.md +140 -0
- data/docs/async.md +41 -0
- data/docs/bind_groups.md +25 -0
- data/docs/buffer_data.md +37 -0
- data/docs/command_encoding.md +49 -0
- data/docs/errors.md +40 -0
- data/docs/getting_started_compute.md +62 -0
- data/docs/getting_started_rendering.md +62 -0
- data/docs/installation.md +94 -0
- data/docs/pipeline_descriptors.md +43 -0
- data/docs/releasing.md +32 -0
- data/docs/resource_lifetime.md +108 -0
- data/docs/shaders.md +32 -0
- data/docs/texture_readback.md +46 -0
- data/docs/troubleshooting.md +53 -0
- data/docs/upgrading_wgpu_native.md +37 -0
- data/ext/wgpu/extconf.rb +10 -142
- data/lib/wgpu/async_task.rb +19 -0
- data/lib/wgpu/commands/command_buffer.rb +25 -1
- data/lib/wgpu/commands/command_encoder.rb +123 -9
- data/lib/wgpu/commands/compute_pass.rb +53 -0
- data/lib/wgpu/commands/render_bundle.rb +9 -1
- data/lib/wgpu/commands/render_bundle_encoder.rb +65 -4
- data/lib/wgpu/commands/render_pass.rb +136 -8
- data/lib/wgpu/core/adapter.rb +123 -19
- data/lib/wgpu/core/async_waiter.rb +47 -4
- data/lib/wgpu/core/canvas_context.rb +32 -2
- data/lib/wgpu/core/device.rb +399 -53
- data/lib/wgpu/core/instance.rb +28 -4
- data/lib/wgpu/core/queue.rb +197 -51
- data/lib/wgpu/core/surface.rb +64 -17
- data/lib/wgpu/data_types.rb +83 -0
- data/lib/wgpu/descriptor_helpers.rb +104 -0
- data/lib/wgpu/error.rb +70 -0
- data/lib/wgpu/logging.rb +63 -0
- data/lib/wgpu/native/abi_verifier.rb +143 -0
- data/lib/wgpu/native/callbacks.rb +10 -1
- data/lib/wgpu/native/capabilities.rb +32 -2
- data/lib/wgpu/native/distribution.rb +176 -0
- data/lib/wgpu/native/enum_helper.rb +80 -0
- data/lib/wgpu/native/enums.rb +68 -8
- data/lib/wgpu/native/fixtures/webgpu-v27.0.4.0-enums.h +848 -0
- data/lib/wgpu/native/functions.rb +21 -10
- data/lib/wgpu/native/installer.rb +223 -0
- data/lib/wgpu/native/loader.rb +61 -21
- data/lib/wgpu/native/structs.rb +18 -1
- data/lib/wgpu/native_resource.rb +342 -0
- data/lib/wgpu/pipeline/bind_group.rb +21 -0
- data/lib/wgpu/pipeline/bind_group_layout.rb +108 -41
- data/lib/wgpu/pipeline/compute_pipeline.rb +40 -50
- data/lib/wgpu/pipeline/pipeline_layout.rb +8 -0
- data/lib/wgpu/pipeline/render_pipeline.rb +207 -110
- data/lib/wgpu/pipeline/shader_module.rb +95 -28
- data/lib/wgpu/resources/buffer.rb +387 -85
- data/lib/wgpu/resources/query_set.rb +26 -12
- data/lib/wgpu/resources/sampler.rb +43 -20
- data/lib/wgpu/resources/texture.rb +89 -48
- data/lib/wgpu/resources/texture_view.rb +35 -7
- data/lib/wgpu/texture_format.rb +96 -0
- data/lib/wgpu/version.rb +1 -1
- data/lib/wgpu/window.rb +34 -1
- data/lib/wgpu.rb +32 -0
- data/sig/wgpu.rbs +460 -0
- metadata +33 -16
|
@@ -4,10 +4,20 @@ 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 = []
|
|
10
19
|
@max_draw_count = max_draw_count
|
|
20
|
+
@ended = false
|
|
11
21
|
|
|
12
22
|
desc = Native::RenderPassDescriptor.new
|
|
13
23
|
desc[:next_in_chain] = nil
|
|
@@ -40,10 +50,18 @@ module WGPU
|
|
|
40
50
|
raise CommandError, "Failed to begin render pass" if @handle.null?
|
|
41
51
|
end
|
|
42
52
|
|
|
53
|
+
# Selects the render pipeline used by subsequent draws.
|
|
54
|
+
# @param pipeline [RenderPipeline] pipeline to bind
|
|
55
|
+
# @return [void]
|
|
43
56
|
def set_pipeline(pipeline)
|
|
44
57
|
Native.wgpuRenderPassEncoderSetPipeline(@handle, pipeline.handle)
|
|
45
58
|
end
|
|
46
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]
|
|
47
65
|
def set_bind_group(index, bind_group, dynamic_offsets: [])
|
|
48
66
|
if dynamic_offsets.empty?
|
|
49
67
|
Native.wgpuRenderPassEncoderSetBindGroup(@handle, index, bind_group.handle, 0, nil)
|
|
@@ -54,32 +72,72 @@ module WGPU
|
|
|
54
72
|
end
|
|
55
73
|
end
|
|
56
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]
|
|
57
81
|
def set_vertex_buffer(slot, buffer, offset: 0, size: nil)
|
|
58
82
|
size ||= buffer.size - offset
|
|
59
83
|
Native.wgpuRenderPassEncoderSetVertexBuffer(@handle, slot, buffer.handle, offset, size)
|
|
60
84
|
end
|
|
61
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]
|
|
62
92
|
def set_index_buffer(buffer, format, offset: 0, size: nil)
|
|
63
93
|
size ||= buffer.size - offset
|
|
64
|
-
Native.
|
|
94
|
+
format_value = Native::EnumHelper.coerce(Native::IndexFormat, format, name: "index format")
|
|
95
|
+
Native.wgpuRenderPassEncoderSetIndexBuffer(@handle, buffer.handle, format_value, offset, size)
|
|
65
96
|
end
|
|
66
97
|
|
|
98
|
+
# Records a non-indexed draw.
|
|
99
|
+
# @return [void]
|
|
67
100
|
def draw(vertex_count, instance_count: 1, first_vertex: 0, first_instance: 0)
|
|
68
101
|
Native.wgpuRenderPassEncoderDraw(@handle, vertex_count, instance_count, first_vertex, first_instance)
|
|
69
102
|
end
|
|
70
103
|
|
|
104
|
+
# Records an indexed draw.
|
|
105
|
+
# @return [void]
|
|
71
106
|
def draw_indexed(index_count, instance_count: 1, first_index: 0, base_vertex: 0, first_instance: 0)
|
|
72
107
|
Native.wgpuRenderPassEncoderDrawIndexed(@handle, index_count, instance_count, first_index, base_vertex, first_instance)
|
|
73
108
|
end
|
|
74
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]
|
|
75
119
|
def set_viewport(x, y, width, height, min_depth: 0.0, max_depth: 1.0)
|
|
76
120
|
Native.wgpuRenderPassEncoderSetViewport(@handle, x, y, width, height, min_depth, max_depth)
|
|
77
121
|
end
|
|
78
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]
|
|
79
130
|
def set_scissor_rect(x, y, width, height)
|
|
80
131
|
Native.wgpuRenderPassEncoderSetScissorRect(@handle, x, y, width, height)
|
|
81
132
|
end
|
|
82
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]
|
|
83
141
|
def set_blend_constant(r: 0.0, g: 0.0, b: 0.0, a: 1.0)
|
|
84
142
|
color = Native::Color.new
|
|
85
143
|
color[:r] = r
|
|
@@ -89,18 +147,36 @@ module WGPU
|
|
|
89
147
|
Native.wgpuRenderPassEncoderSetBlendConstant(@handle, color.to_ptr)
|
|
90
148
|
end
|
|
91
149
|
|
|
150
|
+
# Sets the stencil reference used by subsequent draw calls.
|
|
151
|
+
#
|
|
152
|
+
# @param reference [Integer] unsigned stencil reference
|
|
153
|
+
# @return [void]
|
|
92
154
|
def set_stencil_reference(reference)
|
|
93
155
|
Native.wgpuRenderPassEncoderSetStencilReference(@handle, reference)
|
|
94
156
|
end
|
|
95
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]
|
|
96
163
|
def draw_indirect(buffer, offset: 0)
|
|
97
164
|
Native.wgpuRenderPassEncoderDrawIndirect(@handle, buffer.handle, offset)
|
|
98
165
|
end
|
|
99
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]
|
|
100
172
|
def draw_indexed_indirect(buffer, offset: 0)
|
|
101
173
|
Native.wgpuRenderPassEncoderDrawIndexedIndirect(@handle, buffer.handle, offset)
|
|
102
174
|
end
|
|
103
175
|
|
|
176
|
+
# Executes pre-recorded render bundles.
|
|
177
|
+
#
|
|
178
|
+
# @param bundles [Array<RenderBundle>] bundles to execute in order
|
|
179
|
+
# @return [void]
|
|
104
180
|
def execute_bundles(bundles)
|
|
105
181
|
bundle_handles = bundles.map(&:handle)
|
|
106
182
|
bundles_ptr = FFI::MemoryPointer.new(:pointer, bundle_handles.size)
|
|
@@ -108,14 +184,25 @@ module WGPU
|
|
|
108
184
|
Native.wgpuRenderPassEncoderExecuteBundles(@handle, bundle_handles.size, bundles_ptr)
|
|
109
185
|
end
|
|
110
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]
|
|
111
191
|
def begin_occlusion_query(query_index)
|
|
112
192
|
Native.wgpuRenderPassEncoderBeginOcclusionQuery(@handle, query_index)
|
|
113
193
|
end
|
|
114
194
|
|
|
195
|
+
# Ends the active occlusion query.
|
|
196
|
+
#
|
|
197
|
+
# @return [void]
|
|
115
198
|
def end_occlusion_query
|
|
116
199
|
Native.wgpuRenderPassEncoderEndOcclusionQuery(@handle)
|
|
117
200
|
end
|
|
118
201
|
|
|
202
|
+
# Starts a labeled group in GPU debugging tools.
|
|
203
|
+
#
|
|
204
|
+
# @param label [String] group label
|
|
205
|
+
# @return [void]
|
|
119
206
|
def push_debug_group(label)
|
|
120
207
|
label_view = Native::StringView.new
|
|
121
208
|
label_ptr = FFI::MemoryPointer.from_string(label)
|
|
@@ -124,10 +211,17 @@ module WGPU
|
|
|
124
211
|
Native.wgpuRenderPassEncoderPushDebugGroup(@handle, label_view)
|
|
125
212
|
end
|
|
126
213
|
|
|
214
|
+
# Ends the most recently pushed debug group.
|
|
215
|
+
#
|
|
216
|
+
# @return [void]
|
|
127
217
|
def pop_debug_group
|
|
128
218
|
Native.wgpuRenderPassEncoderPopDebugGroup(@handle)
|
|
129
219
|
end
|
|
130
220
|
|
|
221
|
+
# Inserts a labeled point in GPU debugging tools.
|
|
222
|
+
#
|
|
223
|
+
# @param label [String] marker label
|
|
224
|
+
# @return [void]
|
|
131
225
|
def insert_debug_marker(label)
|
|
132
226
|
label_view = Native::StringView.new
|
|
133
227
|
label_ptr = FFI::MemoryPointer.from_string(label)
|
|
@@ -136,14 +230,32 @@ module WGPU
|
|
|
136
230
|
Native.wgpuRenderPassEncoderInsertDebugMarker(@handle, label_view)
|
|
137
231
|
end
|
|
138
232
|
|
|
233
|
+
# Ends the pass if it has not already ended.
|
|
234
|
+
# @return [void]
|
|
139
235
|
def end_pass
|
|
236
|
+
return if @ended
|
|
237
|
+
|
|
140
238
|
Native.wgpuRenderPassEncoderEnd(@handle)
|
|
239
|
+
@ended = true
|
|
240
|
+
@encoder.send(:pass_ended, self)
|
|
141
241
|
end
|
|
142
242
|
|
|
243
|
+
# Ends the pass.
|
|
244
|
+
# @return [void]
|
|
143
245
|
def end
|
|
144
246
|
end_pass
|
|
145
247
|
end
|
|
146
248
|
|
|
249
|
+
# Reports whether the pass has ended.
|
|
250
|
+
# @return [Boolean]
|
|
251
|
+
def ended?
|
|
252
|
+
@ended
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Releases the native render pass encoder handle.
|
|
256
|
+
#
|
|
257
|
+
# Calling this method more than once has no effect.
|
|
258
|
+
# @return [void]
|
|
147
259
|
def release
|
|
148
260
|
return if @handle.null?
|
|
149
261
|
Native.wgpuRenderPassEncoderRelease(@handle)
|
|
@@ -169,13 +281,13 @@ module WGPU
|
|
|
169
281
|
@pointers << ptr
|
|
170
282
|
|
|
171
283
|
attachments.each_with_index do |att, i|
|
|
172
|
-
ca = Native::RenderPassColorAttachment.new(ptr + i * Native::RenderPassColorAttachment.size)
|
|
284
|
+
ca = Native::RenderPassColorAttachment.new(ptr + (i * Native::RenderPassColorAttachment.size))
|
|
173
285
|
ca[:next_in_chain] = nil
|
|
174
286
|
ca[:view] = att[:view].handle
|
|
175
287
|
ca[:depth_slice] = att[:depth_slice] || 0xFFFFFFFF
|
|
176
288
|
ca[:resolve_target] = att[:resolve_target]&.handle
|
|
177
|
-
ca[:load_op] = att[:load_op] || :clear
|
|
178
|
-
ca[:store_op] = att[:store_op] || :store
|
|
289
|
+
ca[:load_op] = Native::EnumHelper.coerce(Native::LoadOp, att[:load_op] || :clear, name: "load op")
|
|
290
|
+
ca[:store_op] = Native::EnumHelper.coerce(Native::StoreOp, att[:store_op] || :store, name: "store op")
|
|
179
291
|
|
|
180
292
|
clear = att[:clear_value] || { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }
|
|
181
293
|
ca[:clear_value][:r] = clear[:r] || 0.0
|
|
@@ -192,12 +304,28 @@ module WGPU
|
|
|
192
304
|
@pointers << ds
|
|
193
305
|
|
|
194
306
|
ds[:view] = att[:view].handle
|
|
195
|
-
ds[:depth_load_op] =
|
|
196
|
-
|
|
307
|
+
ds[:depth_load_op] = Native::EnumHelper.coerce(
|
|
308
|
+
Native::LoadOp,
|
|
309
|
+
att[:depth_load_op] || :clear,
|
|
310
|
+
name: "depth load op"
|
|
311
|
+
)
|
|
312
|
+
ds[:depth_store_op] = Native::EnumHelper.coerce(
|
|
313
|
+
Native::StoreOp,
|
|
314
|
+
att[:depth_store_op] || :store,
|
|
315
|
+
name: "depth store op"
|
|
316
|
+
)
|
|
197
317
|
ds[:depth_clear_value] = att[:depth_clear_value] || 1.0
|
|
198
318
|
ds[:depth_read_only] = att[:depth_read_only] ? 1 : 0
|
|
199
|
-
ds[:stencil_load_op] =
|
|
200
|
-
|
|
319
|
+
ds[:stencil_load_op] = Native::EnumHelper.coerce(
|
|
320
|
+
Native::LoadOp,
|
|
321
|
+
att[:stencil_load_op] || :clear,
|
|
322
|
+
name: "stencil load op"
|
|
323
|
+
)
|
|
324
|
+
ds[:stencil_store_op] = Native::EnumHelper.coerce(
|
|
325
|
+
Native::StoreOp,
|
|
326
|
+
att[:stencil_store_op] || :store,
|
|
327
|
+
name: "stencil store op"
|
|
328
|
+
)
|
|
201
329
|
ds[:stencil_clear_value] = att[:stencil_clear_value] || 0
|
|
202
330
|
ds[:stencil_read_only] = att[:stencil_read_only] ? 1 : 0
|
|
203
331
|
|
data/lib/wgpu/core/adapter.rb
CHANGED
|
@@ -4,34 +4,82 @@ 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
|
|
|
14
|
-
|
|
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+
|
|
29
|
+
def self.request(instance, power_preference: :high_performance, backend: nil, feature_level: :core,
|
|
30
|
+
force_fallback_adapter: false, compatible_surface: nil, timeout: nil)
|
|
15
31
|
adapter_ptr = FFI::MemoryPointer.new(:pointer)
|
|
16
|
-
status_holder = {
|
|
32
|
+
status_holder = {
|
|
33
|
+
done: false,
|
|
34
|
+
value: nil,
|
|
35
|
+
message: nil,
|
|
36
|
+
abandoned: false,
|
|
37
|
+
cleanup_claimed: false,
|
|
38
|
+
mutex: Mutex.new
|
|
39
|
+
}
|
|
17
40
|
|
|
41
|
+
callback_token = nil
|
|
18
42
|
callback = FFI::Function.new(
|
|
19
43
|
:void, [:uint32, :pointer, Native::StringView.by_value, :pointer, :pointer]
|
|
20
44
|
) do |status, adapter, message, _userdata1, _userdata2|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
status_holder[:
|
|
45
|
+
abandoned_adapter = nil
|
|
46
|
+
begin
|
|
47
|
+
status_holder[:mutex].synchronize do
|
|
48
|
+
status_holder[:value] = Native::RequestAdapterStatus[status]
|
|
49
|
+
if message[:data] && !message[:data].null? && message[:length] > 0
|
|
50
|
+
status_holder[:message] = message[:data].read_string(message[:length])
|
|
51
|
+
end
|
|
52
|
+
adapter_ptr.write_pointer(adapter)
|
|
53
|
+
status_holder[:done] = true
|
|
54
|
+
if status_holder[:abandoned] && !status_holder[:cleanup_claimed]
|
|
55
|
+
status_holder[:cleanup_claimed] = true
|
|
56
|
+
abandoned_adapter = adapter
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
Native.wgpuAdapterRelease(abandoned_adapter) if abandoned_adapter && !abandoned_adapter.null?
|
|
60
|
+
ensure
|
|
61
|
+
CallbackKeepalive.release(instance, callback_token)
|
|
24
62
|
end
|
|
25
|
-
adapter_ptr.write_pointer(adapter)
|
|
26
|
-
status_holder[:done] = true
|
|
27
63
|
end
|
|
28
64
|
|
|
29
65
|
options = Native::RequestAdapterOptions.new
|
|
30
66
|
options[:next_in_chain] = nil
|
|
31
|
-
options[:feature_level] =
|
|
32
|
-
|
|
67
|
+
options[:feature_level] = Native::EnumHelper.coerce(
|
|
68
|
+
Native::FeatureLevel,
|
|
69
|
+
feature_level,
|
|
70
|
+
name: "feature level"
|
|
71
|
+
)
|
|
72
|
+
options[:power_preference] = Native::EnumHelper.coerce(
|
|
73
|
+
Native::PowerPreference,
|
|
74
|
+
power_preference,
|
|
75
|
+
name: "power preference"
|
|
76
|
+
)
|
|
33
77
|
options[:force_fallback_adapter] = force_fallback_adapter ? 1 : 0
|
|
34
|
-
options[:backend_type] =
|
|
78
|
+
options[:backend_type] = Native::EnumHelper.coerce(
|
|
79
|
+
Native::BackendType,
|
|
80
|
+
backend || :undefined,
|
|
81
|
+
name: "backend type"
|
|
82
|
+
)
|
|
35
83
|
options[:compatible_surface] = compatible_surface&.handle
|
|
36
84
|
|
|
37
85
|
callback_info = Native::RequestAdapterCallbackInfo.new
|
|
@@ -41,9 +89,28 @@ module WGPU
|
|
|
41
89
|
callback_info[:userdata1] = nil
|
|
42
90
|
callback_info[:userdata2] = nil
|
|
43
91
|
|
|
44
|
-
|
|
45
|
-
future =
|
|
46
|
-
|
|
92
|
+
callback_token = CallbackKeepalive.retain(instance, callback)
|
|
93
|
+
future =
|
|
94
|
+
begin
|
|
95
|
+
Native.wgpuInstanceRequestAdapter(instance.handle, options, callback_info)
|
|
96
|
+
rescue StandardError
|
|
97
|
+
CallbackKeepalive.release(instance, callback_token)
|
|
98
|
+
raise
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
begin
|
|
102
|
+
AsyncWaiter.wait(status_holder: status_holder, instance: instance, future: future, timeout: timeout)
|
|
103
|
+
rescue TimeoutError
|
|
104
|
+
abandoned_adapter = status_holder[:mutex].synchronize do
|
|
105
|
+
status_holder[:abandoned] = true
|
|
106
|
+
next unless status_holder[:done] && !status_holder[:cleanup_claimed]
|
|
107
|
+
|
|
108
|
+
status_holder[:cleanup_claimed] = true
|
|
109
|
+
adapter_ptr.read_pointer
|
|
110
|
+
end
|
|
111
|
+
Native.wgpuAdapterRelease(abandoned_adapter) if abandoned_adapter && !abandoned_adapter.null?
|
|
112
|
+
raise
|
|
113
|
+
end
|
|
47
114
|
|
|
48
115
|
handle = adapter_ptr.read_pointer
|
|
49
116
|
if handle.null? || status_holder[:value] != :success
|
|
@@ -54,25 +121,41 @@ module WGPU
|
|
|
54
121
|
new(handle, instance: instance)
|
|
55
122
|
end
|
|
56
123
|
|
|
124
|
+
# Wraps a native adapter handle.
|
|
125
|
+
# @param handle [FFI::Pointer] native adapter handle
|
|
126
|
+
# @param instance [Instance, nil] instance that owns the adapter
|
|
57
127
|
def initialize(handle, instance: nil)
|
|
58
128
|
@handle = handle
|
|
59
129
|
@instance = instance
|
|
60
130
|
end
|
|
61
131
|
|
|
62
|
-
|
|
63
|
-
|
|
132
|
+
# Requests a logical device from this adapter.
|
|
133
|
+
# @return [Device]
|
|
134
|
+
def request_device(label: nil, required_features: [], required_limits: nil, timeout: nil)
|
|
135
|
+
Device.request(
|
|
136
|
+
self,
|
|
137
|
+
label: label,
|
|
138
|
+
required_features: required_features,
|
|
139
|
+
required_limits: required_limits,
|
|
140
|
+
timeout: timeout
|
|
141
|
+
)
|
|
64
142
|
end
|
|
65
143
|
|
|
66
|
-
|
|
144
|
+
# Requests a logical device on a background task.
|
|
145
|
+
# @return [AsyncTask] task yielding a {Device}
|
|
146
|
+
def request_device_async(label: nil, required_features: [], required_limits: nil, timeout: nil)
|
|
67
147
|
AsyncTask.new do
|
|
68
148
|
request_device(
|
|
69
149
|
label: label,
|
|
70
150
|
required_features: required_features,
|
|
71
|
-
required_limits: required_limits
|
|
151
|
+
required_limits: required_limits,
|
|
152
|
+
timeout: timeout
|
|
72
153
|
)
|
|
73
154
|
end
|
|
74
155
|
end
|
|
75
156
|
|
|
157
|
+
# Returns identifying and backend information for the adapter.
|
|
158
|
+
# @return [Hash]
|
|
76
159
|
def info
|
|
77
160
|
info_struct = Native::AdapterInfo.new
|
|
78
161
|
Native.wgpuAdapterGetInfo(@handle, info_struct)
|
|
@@ -92,22 +175,32 @@ module WGPU
|
|
|
92
175
|
result
|
|
93
176
|
end
|
|
94
177
|
|
|
178
|
+
# Returns the adapter's device name.
|
|
179
|
+
# @return [String]
|
|
95
180
|
def name
|
|
96
181
|
info[:device]
|
|
97
182
|
end
|
|
98
183
|
|
|
184
|
+
# Returns the adapter vendor name.
|
|
185
|
+
# @return [String]
|
|
99
186
|
def vendor
|
|
100
187
|
info[:vendor]
|
|
101
188
|
end
|
|
102
189
|
|
|
190
|
+
# Returns the backend used by the adapter.
|
|
191
|
+
# @return [Symbol, Integer]
|
|
103
192
|
def backend_type
|
|
104
193
|
info[:backend_type]
|
|
105
194
|
end
|
|
106
195
|
|
|
196
|
+
# Returns the adapter's device classification.
|
|
197
|
+
# @return [Symbol, Integer]
|
|
107
198
|
def adapter_type
|
|
108
199
|
info[:adapter_type]
|
|
109
200
|
end
|
|
110
201
|
|
|
202
|
+
# Lists optional features supported by the adapter.
|
|
203
|
+
# @return [Array<Symbol>]
|
|
111
204
|
def features
|
|
112
205
|
supported = Native::SupportedFeatures.new
|
|
113
206
|
Native.wgpuAdapterGetFeatures(@handle, supported)
|
|
@@ -121,10 +214,15 @@ module WGPU
|
|
|
121
214
|
result
|
|
122
215
|
end
|
|
123
216
|
|
|
217
|
+
# Reports whether the adapter supports a feature.
|
|
218
|
+
# @param feature [Symbol] feature name
|
|
219
|
+
# @return [Boolean]
|
|
124
220
|
def has_feature?(feature)
|
|
125
221
|
features.include?(feature)
|
|
126
222
|
end
|
|
127
223
|
|
|
224
|
+
# Returns the adapter resource limits.
|
|
225
|
+
# @return [Hash{Symbol => Integer}]
|
|
128
226
|
def limits
|
|
129
227
|
supported = Native::SupportedLimits.new
|
|
130
228
|
supported[:next_in_chain] = nil
|
|
@@ -132,11 +230,17 @@ module WGPU
|
|
|
132
230
|
limits_to_hash(supported[:limits])
|
|
133
231
|
end
|
|
134
232
|
|
|
233
|
+
# Returns a concise human-readable adapter description.
|
|
234
|
+
# @return [String]
|
|
135
235
|
def summary
|
|
136
236
|
info_hash = info
|
|
137
237
|
"#{info_hash[:device]} (#{info_hash[:adapter_type]}) via #{info_hash[:backend_type]}"
|
|
138
238
|
end
|
|
139
239
|
|
|
240
|
+
# Releases the native adapter handle.
|
|
241
|
+
#
|
|
242
|
+
# Calling this method more than once has no effect.
|
|
243
|
+
# @return [void]
|
|
140
244
|
def release
|
|
141
245
|
return if @handle.null?
|
|
142
246
|
Native.wgpuAdapterRelease(@handle)
|
|
@@ -10,6 +10,27 @@ module WGPU
|
|
|
10
10
|
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
|
+
# Returns the delay between callback progress polls.
|
|
14
|
+
# @return [Float] seconds
|
|
15
|
+
def poll_interval
|
|
16
|
+
@poll_interval ||= POLL_INTERVAL_SECONDS
|
|
17
|
+
end
|
|
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
|
|
23
|
+
def poll_interval=(seconds)
|
|
24
|
+
value = Float(seconds)
|
|
25
|
+
raise ArgumentError, "poll interval must be positive" unless value.positive?
|
|
26
|
+
|
|
27
|
+
@poll_interval = value
|
|
28
|
+
end
|
|
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
|
|
13
34
|
def callback_mode(instance:)
|
|
14
35
|
if instance
|
|
15
36
|
callback_mode_value(:allow_process_events)
|
|
@@ -18,18 +39,30 @@ module WGPU
|
|
|
18
39
|
end
|
|
19
40
|
end
|
|
20
41
|
|
|
21
|
-
|
|
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
|
|
47
|
+
def wait(status_holder:, instance: nil, device: nil, future: nil, timeout: nil)
|
|
48
|
+
timeout = Float(timeout) if timeout
|
|
49
|
+
raise ArgumentError, "timeout must be non-negative" if timeout&.negative?
|
|
50
|
+
|
|
51
|
+
deadline = monotonic_time + timeout if timeout
|
|
22
52
|
wait_info = build_wait_info(future) if instance && Native.future_api?
|
|
23
53
|
|
|
24
54
|
until status_holder[:done]
|
|
55
|
+
raise_timeout!(timeout) if deadline && monotonic_time >= deadline
|
|
56
|
+
|
|
57
|
+
waited = false
|
|
25
58
|
if instance && wait_info
|
|
26
|
-
wait_with_wait_any(instance, wait_info)
|
|
59
|
+
waited = wait_with_wait_any(instance, wait_info)
|
|
27
60
|
elsif instance
|
|
28
61
|
instance.process_events
|
|
29
62
|
elsif device && Native.device_poll_available?
|
|
30
63
|
Native.wgpuDevicePoll(device.handle, 0, nil)
|
|
31
64
|
end
|
|
32
|
-
sleep(
|
|
65
|
+
sleep(poll_interval) unless status_holder[:done] || waited
|
|
33
66
|
end
|
|
34
67
|
end
|
|
35
68
|
|
|
@@ -55,10 +88,20 @@ module WGPU
|
|
|
55
88
|
|
|
56
89
|
def wait_with_wait_any(instance, wait_info)
|
|
57
90
|
status = Native.wgpuInstanceWaitAny(instance.handle, 1, wait_info.to_ptr, 0)
|
|
58
|
-
return if [:success, :timed_out].include?(status)
|
|
91
|
+
return false if [:success, :timed_out].include?(status)
|
|
59
92
|
|
|
60
93
|
raise Error, "wgpuInstanceWaitAny failed: #{status.inspect}"
|
|
61
94
|
end
|
|
62
95
|
private_class_method :wait_with_wait_any
|
|
96
|
+
|
|
97
|
+
def monotonic_time
|
|
98
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
99
|
+
end
|
|
100
|
+
private_class_method :monotonic_time
|
|
101
|
+
|
|
102
|
+
def raise_timeout!(timeout)
|
|
103
|
+
raise TimeoutError, "GPU operation timed out after #{timeout} seconds"
|
|
104
|
+
end
|
|
105
|
+
private_class_method :raise_timeout!
|
|
63
106
|
end
|
|
64
107
|
end
|
|
@@ -4,6 +4,9 @@ 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 || {}
|
|
@@ -12,25 +15,40 @@ module WGPU
|
|
|
12
15
|
@config = nil
|
|
13
16
|
end
|
|
14
17
|
|
|
18
|
+
# Updates the drawable's physical pixel size.
|
|
19
|
+
# @param width [Integer] width in pixels
|
|
20
|
+
# @param height [Integer] height in pixels
|
|
21
|
+
# @return [Array<Integer>] stored width and height
|
|
22
|
+
# @raise [ArgumentError] if either dimension is negative
|
|
15
23
|
def set_physical_size(width, height)
|
|
16
24
|
raise ArgumentError, "width and height must be non-negative" if width.to_i.negative? || height.to_i.negative?
|
|
17
25
|
|
|
18
26
|
@physical_size = [width.to_i, height.to_i]
|
|
19
27
|
end
|
|
20
28
|
|
|
29
|
+
# Returns the surface format preferred by an adapter.
|
|
30
|
+
#
|
|
31
|
+
# @param adapter [Adapter] adapter used to query surface capabilities
|
|
32
|
+
# @return [Symbol] preferred texture format
|
|
21
33
|
def get_preferred_format(adapter)
|
|
22
34
|
ensure_surface
|
|
23
35
|
@surface.get_preferred_format(adapter)
|
|
24
36
|
end
|
|
25
37
|
|
|
38
|
+
# Returns the most recent canvas configuration.
|
|
39
|
+
#
|
|
40
|
+
# @return [Hash, nil] configuration options, or +nil+ when unconfigured
|
|
26
41
|
def get_configuration
|
|
27
42
|
@config
|
|
28
43
|
end
|
|
29
44
|
|
|
45
|
+
# Configures the backing surface for presentation.
|
|
46
|
+
# @param device [Device] device used to render frames
|
|
47
|
+
# @param format [Symbol, nil] surface texture format
|
|
48
|
+
# @return [Hash] resolved canvas configuration
|
|
49
|
+
# @raise [SurfaceError] if no surface exists or dimensions are not positive
|
|
30
50
|
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
51
|
ensure_surface
|
|
32
|
-
color_space # reserved for API parity
|
|
33
|
-
tone_mapping # reserved for API parity
|
|
34
52
|
|
|
35
53
|
width = width || @physical_size[0]
|
|
36
54
|
height = height || @physical_size[1]
|
|
@@ -61,21 +79,33 @@ module WGPU
|
|
|
61
79
|
}
|
|
62
80
|
end
|
|
63
81
|
|
|
82
|
+
# Removes the current surface configuration.
|
|
83
|
+
# @return [void]
|
|
64
84
|
def unconfigure
|
|
65
85
|
@surface&.unconfigure
|
|
66
86
|
@config = nil
|
|
67
87
|
end
|
|
68
88
|
|
|
89
|
+
# Acquires the texture for the current presentation frame.
|
|
90
|
+
#
|
|
91
|
+
# @return [Texture] acquired surface texture
|
|
92
|
+
# @raise [SurfaceError] if the context has not been configured
|
|
93
|
+
# @raise [SurfaceAcquisitionError] if acquisition fails
|
|
69
94
|
def get_current_texture
|
|
70
95
|
raise SurfaceError, "Canvas context must be configured before get_current_texture" unless @config
|
|
71
96
|
|
|
72
97
|
@surface.current_texture
|
|
73
98
|
end
|
|
74
99
|
|
|
100
|
+
# Presents the current surface texture.
|
|
101
|
+
# @return [void]
|
|
75
102
|
def present
|
|
76
103
|
@surface&.present
|
|
77
104
|
end
|
|
78
105
|
|
|
106
|
+
# Releases the backing surface and clears the configuration.
|
|
107
|
+
#
|
|
108
|
+
# @return [void]
|
|
79
109
|
def release
|
|
80
110
|
@surface&.release
|
|
81
111
|
@surface = nil
|