wgpu 1.0.0 → 1.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +26 -3
- data/docs/README.md +22 -0
- data/docs/api_coverage.md +132 -0
- data/docs/async.md +31 -0
- data/docs/bind_groups.md +25 -0
- data/docs/buffer_data.md +37 -0
- data/docs/command_encoding.md +24 -0
- data/docs/errors.md +40 -0
- data/docs/getting_started_compute.md +61 -0
- data/docs/getting_started_rendering.md +62 -0
- data/docs/installation.md +94 -0
- data/docs/pipeline_descriptors.md +38 -0
- data/docs/releasing.md +22 -0
- data/docs/resource_lifetime.md +94 -0
- data/docs/shaders.md +32 -0
- data/docs/texture_readback.md +19 -0
- data/docs/troubleshooting.md +48 -0
- data/docs/upgrading_wgpu_native.md +26 -0
- data/ext/wgpu/extconf.rb +10 -142
- data/lib/wgpu/commands/command_buffer.rb +11 -0
- data/lib/wgpu/commands/command_encoder.rb +65 -8
- data/lib/wgpu/commands/compute_pass.rb +10 -0
- data/lib/wgpu/commands/render_bundle_encoder.rb +10 -3
- data/lib/wgpu/commands/render_pass.rb +34 -8
- data/lib/wgpu/core/adapter.rb +49 -20
- data/lib/wgpu/core/async_waiter.rb +92 -0
- data/lib/wgpu/core/canvas_context.rb +0 -2
- data/lib/wgpu/core/device.rb +160 -52
- data/lib/wgpu/core/instance.rb +9 -5
- data/lib/wgpu/core/queue.rb +81 -57
- data/lib/wgpu/core/surface.rb +16 -17
- data/lib/wgpu/data_types.rb +67 -0
- data/lib/wgpu/descriptor_helpers.rb +39 -0
- data/lib/wgpu/error.rb +55 -0
- data/lib/wgpu/logging.rb +63 -0
- data/lib/wgpu/native/abi_verifier.rb +109 -0
- data/lib/wgpu/native/callbacks.rb +9 -6
- data/lib/wgpu/native/capabilities.rb +31 -0
- data/lib/wgpu/native/distribution.rb +113 -0
- data/lib/wgpu/native/enum_helper.rb +63 -0
- data/lib/wgpu/native/enums.rb +82 -13
- data/lib/wgpu/native/functions.rb +17 -8
- data/lib/wgpu/native/installer.rb +192 -0
- data/lib/wgpu/native/loader.rb +40 -21
- data/lib/wgpu/native/structs.rb +19 -5
- data/lib/wgpu/native_resource.rb +171 -0
- data/lib/wgpu/pipeline/bind_group.rb +12 -0
- data/lib/wgpu/pipeline/bind_group_layout.rb +91 -37
- data/lib/wgpu/pipeline/compute_pipeline.rb +28 -26
- data/lib/wgpu/pipeline/render_pipeline.rb +180 -68
- data/lib/wgpu/pipeline/shader_module.rb +51 -12
- data/lib/wgpu/resources/buffer.rb +167 -84
- data/lib/wgpu/resources/query_set.rb +14 -12
- data/lib/wgpu/resources/sampler.rb +36 -20
- data/lib/wgpu/resources/texture.rb +44 -42
- data/lib/wgpu/resources/texture_view.rb +11 -3
- data/lib/wgpu/texture_format.rb +82 -0
- data/lib/wgpu/version.rb +1 -1
- data/lib/wgpu/window.rb +8 -1
- data/lib/wgpu.rb +33 -0
- data/sig/wgpu.rbs +381 -0
- metadata +33 -16
data/lib/wgpu/core/device.rb
CHANGED
|
@@ -4,21 +4,26 @@ module WGPU
|
|
|
4
4
|
class Device
|
|
5
5
|
attr_reader :handle, :queue, :adapter
|
|
6
6
|
|
|
7
|
-
CALLBACK_MODE_WAIT_ANY_ONLY = 1
|
|
8
7
|
LIMIT_FIELDS = Native::Limits.members.freeze
|
|
9
8
|
|
|
10
|
-
def self.request(adapter, label: nil, required_features: [], required_limits: nil)
|
|
9
|
+
def self.request(adapter, label: nil, required_features: [], required_limits: nil, timeout: nil)
|
|
11
10
|
device_ptr = FFI::MemoryPointer.new(:pointer)
|
|
12
|
-
status_holder = { value: nil, message: nil }
|
|
11
|
+
status_holder = { done: false, value: nil, message: nil }
|
|
12
|
+
device_callback_state = {
|
|
13
|
+
mutex: Mutex.new,
|
|
14
|
+
uncaptured_error: nil,
|
|
15
|
+
device_lost: nil
|
|
16
|
+
}
|
|
13
17
|
|
|
14
18
|
callback = FFI::Function.new(
|
|
15
|
-
:void, [:uint32, :pointer, Native::StringView.by_value, :pointer]
|
|
16
|
-
) do |status, device, message,
|
|
19
|
+
:void, [:uint32, :pointer, Native::StringView.by_value, :pointer, :pointer]
|
|
20
|
+
) do |status, device, message, _userdata1, _userdata2|
|
|
17
21
|
status_holder[:value] = Native::RequestDeviceStatus[status]
|
|
18
22
|
if message[:data] && !message[:data].null? && message[:length] > 0
|
|
19
23
|
status_holder[:message] = message[:data].read_string(message[:length])
|
|
20
24
|
end
|
|
21
25
|
device_ptr.write_pointer(device)
|
|
26
|
+
status_holder[:done] = true
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
queue_desc = Native::QueueDescriptor.new
|
|
@@ -28,14 +33,18 @@ module WGPU
|
|
|
28
33
|
|
|
29
34
|
device_lost_info = Native::DeviceLostCallbackInfo.new
|
|
30
35
|
device_lost_info[:next_in_chain] = nil
|
|
31
|
-
device_lost_info[:mode] =
|
|
32
|
-
|
|
33
|
-
device_lost_info[:
|
|
36
|
+
device_lost_info[:mode] = AsyncWaiter.callback_mode(instance: adapter.instance)
|
|
37
|
+
device_lost_callback = build_device_lost_callback(device_callback_state)
|
|
38
|
+
device_lost_info[:callback] = device_lost_callback
|
|
39
|
+
device_lost_info[:userdata1] = nil
|
|
40
|
+
device_lost_info[:userdata2] = nil
|
|
34
41
|
|
|
35
42
|
error_info = Native::UncapturedErrorCallbackInfo.new
|
|
36
43
|
error_info[:next_in_chain] = nil
|
|
37
|
-
|
|
38
|
-
error_info[:
|
|
44
|
+
uncaptured_error_callback = build_uncaptured_error_callback(device_callback_state)
|
|
45
|
+
error_info[:callback] = uncaptured_error_callback
|
|
46
|
+
error_info[:userdata1] = nil
|
|
47
|
+
error_info[:userdata2] = nil
|
|
39
48
|
|
|
40
49
|
desc = Native::DeviceDescriptor.new
|
|
41
50
|
desc[:next_in_chain] = nil
|
|
@@ -67,11 +76,23 @@ module WGPU
|
|
|
67
76
|
|
|
68
77
|
callback_info = Native::RequestDeviceCallbackInfo.new
|
|
69
78
|
callback_info[:next_in_chain] = nil
|
|
70
|
-
callback_info[:mode] =
|
|
79
|
+
callback_info[:mode] = AsyncWaiter.callback_mode(instance: adapter.instance)
|
|
71
80
|
callback_info[:callback] = callback
|
|
72
|
-
callback_info[:
|
|
81
|
+
callback_info[:userdata1] = nil
|
|
82
|
+
callback_info[:userdata2] = nil
|
|
73
83
|
|
|
74
|
-
|
|
84
|
+
callback_token = CallbackKeepalive.retain(adapter, callback)
|
|
85
|
+
begin
|
|
86
|
+
future = Native.wgpuAdapterRequestDevice(adapter.handle, desc, callback_info)
|
|
87
|
+
AsyncWaiter.wait(
|
|
88
|
+
status_holder: status_holder,
|
|
89
|
+
instance: adapter.instance,
|
|
90
|
+
future: future,
|
|
91
|
+
timeout: timeout
|
|
92
|
+
)
|
|
93
|
+
ensure
|
|
94
|
+
CallbackKeepalive.release(adapter, callback_token)
|
|
95
|
+
end
|
|
75
96
|
|
|
76
97
|
handle = device_ptr.read_pointer
|
|
77
98
|
if handle.null? || status_holder[:value] != :success
|
|
@@ -79,12 +100,22 @@ module WGPU
|
|
|
79
100
|
raise DeviceError, "Failed to request device: #{msg}"
|
|
80
101
|
end
|
|
81
102
|
|
|
82
|
-
new(handle, adapter: adapter)
|
|
103
|
+
device = new(handle, adapter: adapter, label: label, callback_state: device_callback_state)
|
|
104
|
+
device.send(:retain_device_callback, device_lost_callback)
|
|
105
|
+
device.send(:retain_device_callback, uncaptured_error_callback)
|
|
106
|
+
device
|
|
83
107
|
end
|
|
84
108
|
|
|
85
|
-
def initialize(handle, adapter: nil)
|
|
109
|
+
def initialize(handle, adapter: nil, label: nil, callback_state: nil)
|
|
86
110
|
@handle = handle
|
|
87
111
|
@adapter = adapter
|
|
112
|
+
@label = label
|
|
113
|
+
@device_callback_state = callback_state || {
|
|
114
|
+
mutex: Mutex.new,
|
|
115
|
+
uncaptured_error: nil,
|
|
116
|
+
device_lost: nil
|
|
117
|
+
}
|
|
118
|
+
@device_callback_tokens = []
|
|
88
119
|
@queue = Queue.new(Native.wgpuDeviceGetQueue(@handle), device: self)
|
|
89
120
|
end
|
|
90
121
|
|
|
@@ -96,8 +127,15 @@ module WGPU
|
|
|
96
127
|
Buffer.new(self, label: label, size: size, usage: usage, mapped_at_creation: mapped_at_creation)
|
|
97
128
|
end
|
|
98
129
|
|
|
99
|
-
def create_shader_module(label: nil, code
|
|
100
|
-
ShaderModule.new(
|
|
130
|
+
def create_shader_module(label: nil, code: nil, spirv: nil, compilation_hints: [], validate: false)
|
|
131
|
+
ShaderModule.new(
|
|
132
|
+
self,
|
|
133
|
+
label: label,
|
|
134
|
+
code: code,
|
|
135
|
+
spirv: spirv,
|
|
136
|
+
compilation_hints: compilation_hints,
|
|
137
|
+
validate: validate
|
|
138
|
+
)
|
|
101
139
|
end
|
|
102
140
|
|
|
103
141
|
def create_command_encoder(label: nil)
|
|
@@ -181,8 +219,9 @@ module WGPU
|
|
|
181
219
|
)
|
|
182
220
|
end
|
|
183
221
|
|
|
184
|
-
def create_buffer_with_data(label: nil, data:, usage:)
|
|
185
|
-
data_ptr, byte_size =
|
|
222
|
+
def create_buffer_with_data(label: nil, data:, usage:, type: :f32)
|
|
223
|
+
data_ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
224
|
+
DataTypes.validate_alignment!(byte_size, 4, name: "buffer data size")
|
|
186
225
|
buffer = create_buffer(
|
|
187
226
|
label: label,
|
|
188
227
|
size: byte_size,
|
|
@@ -235,19 +274,27 @@ module WGPU
|
|
|
235
274
|
end
|
|
236
275
|
|
|
237
276
|
def poll(wait: false)
|
|
238
|
-
Native.
|
|
277
|
+
if Native.device_poll_available?
|
|
278
|
+
Native.wgpuDevicePoll(@handle, wait ? 1 : 0, nil)
|
|
279
|
+
else
|
|
280
|
+
@adapter&.instance&.process_events
|
|
281
|
+
0
|
|
282
|
+
end
|
|
239
283
|
end
|
|
240
284
|
|
|
241
285
|
def push_error_scope(filter = :validation)
|
|
242
|
-
Native.
|
|
286
|
+
filter_value = Native::EnumHelper.coerce(Native::ErrorFilter, filter, name: "error filter")
|
|
287
|
+
Native.wgpuDevicePushErrorScope(@handle, filter_value)
|
|
243
288
|
end
|
|
244
289
|
|
|
245
|
-
def pop_error_scope
|
|
246
|
-
error_holder = { type: nil, message: nil }
|
|
290
|
+
def pop_error_scope(timeout: nil)
|
|
291
|
+
error_holder = { done: false, status: nil, type: nil, message: nil }
|
|
247
292
|
|
|
248
293
|
callback = FFI::Function.new(
|
|
249
294
|
:void, [:uint32, :uint32, Native::StringView.by_value, :pointer, :pointer]
|
|
250
|
-
) do |
|
|
295
|
+
) do |status, error_type, message, _userdata1, _userdata2|
|
|
296
|
+
error_holder[:done] = true
|
|
297
|
+
error_holder[:status] = Native::PopErrorScopeStatus[status]
|
|
251
298
|
error_holder[:type] = Native::ErrorType[error_type]
|
|
252
299
|
if message[:data] && !message[:data].null? && message[:length] > 0
|
|
253
300
|
error_holder[:message] = message[:data].read_string(message[:length])
|
|
@@ -256,30 +303,58 @@ module WGPU
|
|
|
256
303
|
|
|
257
304
|
callback_info = Native::PopErrorScopeCallbackInfo.new
|
|
258
305
|
callback_info[:next_in_chain] = nil
|
|
259
|
-
callback_info[:mode] =
|
|
306
|
+
callback_info[:mode] = AsyncWaiter.callback_mode(instance: @adapter&.instance)
|
|
260
307
|
callback_info[:callback] = callback
|
|
261
308
|
callback_info[:userdata1] = nil
|
|
262
309
|
callback_info[:userdata2] = nil
|
|
263
310
|
|
|
264
|
-
|
|
311
|
+
callback_token = CallbackKeepalive.retain(self, callback)
|
|
312
|
+
begin
|
|
313
|
+
future = Native.wgpuDevicePopErrorScope(@handle, callback_info)
|
|
314
|
+
AsyncWaiter.wait(
|
|
315
|
+
status_holder: error_holder,
|
|
316
|
+
instance: @adapter&.instance,
|
|
317
|
+
device: self,
|
|
318
|
+
future: future,
|
|
319
|
+
timeout: timeout
|
|
320
|
+
)
|
|
321
|
+
ensure
|
|
322
|
+
CallbackKeepalive.release(self, callback_token)
|
|
323
|
+
end
|
|
265
324
|
|
|
266
325
|
error_holder
|
|
267
326
|
end
|
|
268
327
|
|
|
269
|
-
def pop_error_scope_async
|
|
270
|
-
AsyncTask.new { pop_error_scope }
|
|
328
|
+
def pop_error_scope_async(timeout: nil)
|
|
329
|
+
AsyncTask.new { pop_error_scope(timeout: timeout) }
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def pop_error_scope_typed(timeout: nil)
|
|
333
|
+
GPUError.from_hash(pop_error_scope(timeout: timeout))
|
|
271
334
|
end
|
|
272
335
|
|
|
273
336
|
def with_error_scope(filter = :validation)
|
|
274
337
|
push_error_scope(filter)
|
|
275
338
|
result = yield
|
|
276
|
-
error = pop_error_scope
|
|
277
|
-
|
|
278
|
-
raise Error, "GPU error (#{error[:type]}): #{error[:message]}"
|
|
279
|
-
end
|
|
339
|
+
error = GPUError.from_hash(pop_error_scope)
|
|
340
|
+
error&.raise!
|
|
280
341
|
result
|
|
281
342
|
end
|
|
282
343
|
|
|
344
|
+
def on_uncaptured_error(&handler)
|
|
345
|
+
raise ArgumentError, "on_uncaptured_error requires a block" unless handler
|
|
346
|
+
|
|
347
|
+
set_device_callback(:uncaptured_error, handler)
|
|
348
|
+
self
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def on_device_lost(&handler)
|
|
352
|
+
raise ArgumentError, "on_device_lost requires a block" unless handler
|
|
353
|
+
|
|
354
|
+
set_device_callback(:device_lost, handler)
|
|
355
|
+
self
|
|
356
|
+
end
|
|
357
|
+
|
|
283
358
|
def destroy
|
|
284
359
|
return if @handle.null?
|
|
285
360
|
Native.wgpuDeviceDestroy(@handle)
|
|
@@ -290,10 +365,10 @@ module WGPU
|
|
|
290
365
|
return if @handle.null?
|
|
291
366
|
Native.wgpuDeviceRelease(@handle)
|
|
292
367
|
@handle = FFI::Pointer::NULL
|
|
368
|
+
Array(@device_callback_tokens).each { |token| CallbackKeepalive.release(self, token) }
|
|
369
|
+
@device_callback_tokens&.clear
|
|
293
370
|
end
|
|
294
371
|
|
|
295
|
-
private
|
|
296
|
-
|
|
297
372
|
def self.normalize_required_features(required_features)
|
|
298
373
|
Array(required_features).map do |feature|
|
|
299
374
|
normalize_feature_name(feature)
|
|
@@ -304,10 +379,7 @@ module WGPU
|
|
|
304
379
|
return feature if feature.is_a?(Integer)
|
|
305
380
|
|
|
306
381
|
key = feature.to_s.strip.tr("-", "_").to_sym
|
|
307
|
-
|
|
308
|
-
raise ArgumentError, "Unknown feature name: #{feature}" if value.nil?
|
|
309
|
-
|
|
310
|
-
value
|
|
382
|
+
Native::EnumHelper.coerce(Native::FeatureName, key, name: "feature name")
|
|
311
383
|
end
|
|
312
384
|
|
|
313
385
|
def self.build_required_limits(adapter, required_limits)
|
|
@@ -340,23 +412,59 @@ module WGPU
|
|
|
340
412
|
raise ArgumentError, "Unknown limit key: #{name}"
|
|
341
413
|
end
|
|
342
414
|
|
|
343
|
-
def
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
[data, data.size]
|
|
355
|
-
else
|
|
356
|
-
raise ArgumentError, "Unsupported data type: #{data.class}"
|
|
415
|
+
def self.build_uncaptured_error_callback(state)
|
|
416
|
+
FFI::Function.new(
|
|
417
|
+
:void, [:pointer, :uint32, Native::StringView.by_value, :pointer, :pointer]
|
|
418
|
+
) do |_device, type, message, _userdata1, _userdata2|
|
|
419
|
+
error = GPUError.new(
|
|
420
|
+
type: Native::ErrorType[type] || :unknown,
|
|
421
|
+
message: string_from_callback(message)
|
|
422
|
+
)
|
|
423
|
+
dispatch_device_callback(state, :uncaptured_error, error) do
|
|
424
|
+
warn "Uncaptured GPU error (#{error.type}): #{error.message}"
|
|
425
|
+
end
|
|
357
426
|
end
|
|
358
427
|
end
|
|
359
428
|
|
|
429
|
+
def self.build_device_lost_callback(state)
|
|
430
|
+
FFI::Function.new(
|
|
431
|
+
:void, [:pointer, :uint32, Native::StringView.by_value, :pointer, :pointer]
|
|
432
|
+
) do |_device, reason, message, _userdata1, _userdata2|
|
|
433
|
+
reason_name = Native::DeviceLostReason[reason] || :unknown
|
|
434
|
+
message_text = string_from_callback(message)
|
|
435
|
+
dispatch_device_callback(state, :device_lost, reason_name, message_text) do
|
|
436
|
+
warn "GPU device lost (#{reason_name}): #{message_text}" unless reason_name == :destroyed
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def self.dispatch_device_callback(state, key, *args)
|
|
442
|
+
handler = state[:mutex].synchronize { state[key] }
|
|
443
|
+
handler ? handler.call(*args) : yield
|
|
444
|
+
rescue StandardError => e
|
|
445
|
+
warn "WGPU #{key} handler failed: #{e.class}: #{e.message}"
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def self.string_from_callback(message)
|
|
449
|
+
return "" if message[:data].nil? || message[:data].null? || message[:length].zero?
|
|
450
|
+
|
|
451
|
+
message[:data].read_string(message[:length])
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
private_class_method :normalize_required_features, :normalize_feature_name,
|
|
455
|
+
:build_required_limits, :canonical_limit_key, :build_uncaptured_error_callback,
|
|
456
|
+
:build_device_lost_callback, :dispatch_device_callback, :string_from_callback
|
|
457
|
+
|
|
458
|
+
private
|
|
459
|
+
|
|
460
|
+
def retain_device_callback(callback)
|
|
461
|
+
@device_callback_tokens << CallbackKeepalive.retain(self, callback)
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def set_device_callback(name, handler)
|
|
465
|
+
@device_callback_state[:mutex].synchronize { @device_callback_state[name] = handler }
|
|
466
|
+
end
|
|
467
|
+
|
|
360
468
|
def limits_to_hash(limits)
|
|
361
469
|
{
|
|
362
470
|
max_texture_dimension_1d: limits[:max_texture_dimension_1d],
|
data/lib/wgpu/core/instance.rb
CHANGED
|
@@ -15,25 +15,29 @@ module WGPU
|
|
|
15
15
|
raise InitializationError, "Failed to create WebGPU instance" if @handle.null?
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def request_adapter(power_preference: :high_performance, backend: nil, feature_level: :core,
|
|
18
|
+
def request_adapter(power_preference: :high_performance, backend: nil, feature_level: :core,
|
|
19
|
+
force_fallback_adapter: false, compatible_surface: nil, timeout: nil)
|
|
19
20
|
Adapter.request(
|
|
20
21
|
self,
|
|
21
22
|
power_preference: power_preference,
|
|
22
23
|
backend: backend,
|
|
23
24
|
feature_level: feature_level,
|
|
24
25
|
force_fallback_adapter: force_fallback_adapter,
|
|
25
|
-
compatible_surface: compatible_surface
|
|
26
|
+
compatible_surface: compatible_surface,
|
|
27
|
+
timeout: timeout
|
|
26
28
|
)
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
def request_adapter_async(power_preference: :high_performance, backend: nil, feature_level: :core,
|
|
31
|
+
def request_adapter_async(power_preference: :high_performance, backend: nil, feature_level: :core,
|
|
32
|
+
force_fallback_adapter: false, compatible_surface: nil, timeout: nil)
|
|
30
33
|
AsyncTask.new do
|
|
31
34
|
request_adapter(
|
|
32
35
|
power_preference: power_preference,
|
|
33
36
|
backend: backend,
|
|
34
37
|
feature_level: feature_level,
|
|
35
38
|
force_fallback_adapter: force_fallback_adapter,
|
|
36
|
-
compatible_surface: compatible_surface
|
|
39
|
+
compatible_surface: compatible_surface,
|
|
40
|
+
timeout: timeout
|
|
37
41
|
)
|
|
38
42
|
end
|
|
39
43
|
end
|
|
@@ -53,7 +57,7 @@ module WGPU
|
|
|
53
57
|
Native.wgpuInstanceEnumerateAdapters(@handle, options, adapters_ptr)
|
|
54
58
|
|
|
55
59
|
adapters_ptr.read_array_of_pointer(count).map do |ptr|
|
|
56
|
-
Adapter.from_handle(ptr)
|
|
60
|
+
Adapter.from_handle(ptr, instance: self)
|
|
57
61
|
end
|
|
58
62
|
end
|
|
59
63
|
|
data/lib/wgpu/core/queue.rb
CHANGED
|
@@ -13,15 +13,24 @@ module WGPU
|
|
|
13
13
|
buffers = Array(command_buffers)
|
|
14
14
|
return if buffers.empty?
|
|
15
15
|
|
|
16
|
+
if buffers.map(&:object_id).uniq.length != buffers.length
|
|
17
|
+
raise CommandError, "The same command buffer cannot appear twice in one submission"
|
|
18
|
+
end
|
|
19
|
+
buffers.each do |buffer|
|
|
20
|
+
raise CommandError, "Command buffer has already been submitted" if buffer.submitted?
|
|
21
|
+
end
|
|
22
|
+
|
|
16
23
|
handles = buffers.map(&:handle)
|
|
17
24
|
ptr = FFI::MemoryPointer.new(:pointer, handles.size)
|
|
18
25
|
ptr.write_array_of_pointer(handles)
|
|
19
26
|
|
|
20
27
|
Native.wgpuQueueSubmit(@handle, handles.size, ptr)
|
|
28
|
+
buffers.each(&:mark_submitted!)
|
|
21
29
|
end
|
|
22
30
|
|
|
23
|
-
def write_buffer(buffer, buffer_offset, data, data_offset: 0, size: nil)
|
|
24
|
-
data_ptr, byte_size =
|
|
31
|
+
def write_buffer(buffer, buffer_offset, data, data_offset: 0, size: nil, type: :f32)
|
|
32
|
+
data_ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
33
|
+
DataTypes.validate_alignment!(buffer_offset, 4, name: "buffer_offset")
|
|
25
34
|
data_offset = Integer(data_offset)
|
|
26
35
|
raise ArgumentError, "data_offset must be non-negative" if data_offset.negative?
|
|
27
36
|
raise ArgumentError, "data_offset out of range" if data_offset > byte_size
|
|
@@ -29,6 +38,7 @@ module WGPU
|
|
|
29
38
|
write_size = size.nil? ? (byte_size - data_offset) : Integer(size)
|
|
30
39
|
raise ArgumentError, "size must be non-negative" if write_size.negative?
|
|
31
40
|
raise ArgumentError, "data_offset + size out of range" if data_offset + write_size > byte_size
|
|
41
|
+
DataTypes.validate_alignment!(write_size, 4, name: "size")
|
|
32
42
|
|
|
33
43
|
Native.wgpuQueueWriteBuffer(
|
|
34
44
|
@handle,
|
|
@@ -39,8 +49,8 @@ module WGPU
|
|
|
39
49
|
)
|
|
40
50
|
end
|
|
41
51
|
|
|
42
|
-
def write_texture(destination:, data:, data_layout:, size:)
|
|
43
|
-
data_ptr, byte_size =
|
|
52
|
+
def write_texture(destination:, data:, data_layout:, size:, type: :f32)
|
|
53
|
+
data_ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
44
54
|
|
|
45
55
|
dst = Native::ImageCopyTexture.new
|
|
46
56
|
dst[:texture] = destination[:texture].handle
|
|
@@ -48,7 +58,11 @@ module WGPU
|
|
|
48
58
|
dst[:origin][:x] = destination.dig(:origin, :x) || 0
|
|
49
59
|
dst[:origin][:y] = destination.dig(:origin, :y) || 0
|
|
50
60
|
dst[:origin][:z] = destination.dig(:origin, :z) || 0
|
|
51
|
-
dst[:aspect] =
|
|
61
|
+
dst[:aspect] = Native::EnumHelper.coerce(
|
|
62
|
+
Native::TextureAspect,
|
|
63
|
+
destination[:aspect] || :all,
|
|
64
|
+
name: "texture aspect"
|
|
65
|
+
)
|
|
52
66
|
|
|
53
67
|
extent = Native::Extent3D.new
|
|
54
68
|
if size.is_a?(Array)
|
|
@@ -69,14 +83,13 @@ module WGPU
|
|
|
69
83
|
Native.wgpuQueueWriteTexture(@handle, dst, data_ptr, byte_size, layout, extent)
|
|
70
84
|
end
|
|
71
85
|
|
|
72
|
-
def read_buffer(buffer, offset: 0, size: nil, device:)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
staging = Buffer.new(device,
|
|
76
|
-
size: size,
|
|
77
|
-
usage: [:map_read, :copy_dst]
|
|
78
|
-
)
|
|
86
|
+
def read_buffer(buffer, offset: 0, size: nil, device: nil, staging: nil)
|
|
87
|
+
device ||= @device
|
|
88
|
+
raise ArgumentError, "device is required when the queue has no owning device" unless device
|
|
79
89
|
|
|
90
|
+
size ||= buffer.size - offset
|
|
91
|
+
owns_staging = staging.nil?
|
|
92
|
+
staging ||= Buffer.new(device, size: size, usage: [:map_read, :copy_dst])
|
|
80
93
|
encoder = CommandEncoder.new(device)
|
|
81
94
|
encoder.copy_buffer_to_buffer(
|
|
82
95
|
source: buffer,
|
|
@@ -89,26 +102,40 @@ module WGPU
|
|
|
89
102
|
submit([command_buffer])
|
|
90
103
|
|
|
91
104
|
staging.map_sync(:read)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
staging.
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
staging.read_mapped_data(size:)
|
|
106
|
+
ensure
|
|
107
|
+
staging&.unmap if staging && staging.map_state == :mapped
|
|
108
|
+
command_buffer&.release
|
|
109
|
+
encoder&.release
|
|
110
|
+
staging&.release if owns_staging
|
|
97
111
|
end
|
|
98
112
|
|
|
99
|
-
def read_texture(source:, data_layout:, size:, device:)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
def read_texture(source:, data_layout:, size:, device: nil, staging: nil)
|
|
114
|
+
device ||= @device
|
|
115
|
+
raise ArgumentError, "device is required when the queue has no owning device" unless device
|
|
116
|
+
|
|
117
|
+
width, height, depth = texture_extent(size)
|
|
103
118
|
bytes_per_row = data_layout[:bytes_per_row]
|
|
104
|
-
|
|
105
|
-
buffer_size = bytes_per_row * rows_per_image * depth
|
|
119
|
+
raise ArgumentError, "data_layout[:bytes_per_row] is required" unless bytes_per_row
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
DataTypes.validate_alignment!(
|
|
122
|
+
bytes_per_row,
|
|
123
|
+
TextureFormat::COPY_ALIGNMENT,
|
|
124
|
+
name: "bytes_per_row"
|
|
110
125
|
)
|
|
126
|
+
format = source[:format] || source[:texture].format
|
|
127
|
+
aspect = source[:aspect] || :all
|
|
128
|
+
minimum_bytes_per_row = TextureFormat.bytes_per_row(width, format, aspect:)
|
|
129
|
+
if bytes_per_row < minimum_bytes_per_row
|
|
130
|
+
raise ArgumentError,
|
|
131
|
+
"bytes_per_row must be at least #{minimum_bytes_per_row} for width #{width} and #{format.inspect}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
rows_per_image = data_layout[:rows_per_image] || height
|
|
135
|
+
buffer_size = bytes_per_row * rows_per_image * depth
|
|
111
136
|
|
|
137
|
+
owns_staging = staging.nil?
|
|
138
|
+
staging ||= Buffer.new(device, size: buffer_size, usage: [:map_read, :copy_dst])
|
|
112
139
|
encoder = CommandEncoder.new(device)
|
|
113
140
|
encoder.copy_texture_to_buffer(
|
|
114
141
|
source: source,
|
|
@@ -124,15 +151,17 @@ module WGPU
|
|
|
124
151
|
submit([command_buffer])
|
|
125
152
|
|
|
126
153
|
staging.map_sync(:read)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
staging.
|
|
130
|
-
|
|
131
|
-
|
|
154
|
+
staging.read_mapped_data(size: buffer_size)
|
|
155
|
+
ensure
|
|
156
|
+
staging&.unmap if staging && staging.map_state == :mapped
|
|
157
|
+
command_buffer&.release
|
|
158
|
+
encoder&.release
|
|
159
|
+
staging&.release if owns_staging
|
|
132
160
|
end
|
|
133
161
|
|
|
134
|
-
def on_submitted_work_done(device: nil)
|
|
162
|
+
def on_submitted_work_done(device: nil, timeout: nil)
|
|
135
163
|
device ||= @device
|
|
164
|
+
instance = device&.adapter&.instance
|
|
136
165
|
status_holder = { done: false, status: nil }
|
|
137
166
|
|
|
138
167
|
callback = FFI::Function.new(
|
|
@@ -144,28 +173,31 @@ module WGPU
|
|
|
144
173
|
|
|
145
174
|
callback_info = Native::QueueWorkDoneCallbackInfo.new
|
|
146
175
|
callback_info[:next_in_chain] = nil
|
|
147
|
-
callback_info[:mode] =
|
|
176
|
+
callback_info[:mode] = AsyncWaiter.callback_mode(instance: instance)
|
|
148
177
|
callback_info[:callback] = callback
|
|
149
178
|
callback_info[:userdata1] = nil
|
|
150
179
|
callback_info[:userdata2] = nil
|
|
151
180
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
181
|
+
callback_token = CallbackKeepalive.retain(self, callback)
|
|
182
|
+
begin
|
|
183
|
+
future = Native.wgpuQueueOnSubmittedWorkDone(@handle, callback_info)
|
|
184
|
+
AsyncWaiter.wait(
|
|
185
|
+
status_holder: status_holder,
|
|
186
|
+
instance: instance,
|
|
187
|
+
device: device,
|
|
188
|
+
future: future,
|
|
189
|
+
timeout: timeout
|
|
190
|
+
)
|
|
191
|
+
ensure
|
|
192
|
+
CallbackKeepalive.release(self, callback_token)
|
|
161
193
|
end
|
|
162
194
|
|
|
163
195
|
status_holder[:status]
|
|
164
196
|
end
|
|
165
197
|
|
|
166
|
-
def on_submitted_work_done_async(device: nil)
|
|
198
|
+
def on_submitted_work_done_async(device: nil, timeout: nil)
|
|
167
199
|
AsyncTask.new do
|
|
168
|
-
on_submitted_work_done(device: device)
|
|
200
|
+
on_submitted_work_done(device: device, timeout: timeout)
|
|
169
201
|
end
|
|
170
202
|
end
|
|
171
203
|
|
|
@@ -177,21 +209,13 @@ module WGPU
|
|
|
177
209
|
|
|
178
210
|
private
|
|
179
211
|
|
|
180
|
-
def
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
ptr = FFI::MemoryPointer.new(:char, data.bytesize)
|
|
184
|
-
ptr.put_bytes(0, data)
|
|
185
|
-
[ptr, data.bytesize]
|
|
186
|
-
when Array
|
|
187
|
-
ptr = FFI::MemoryPointer.new(:float, data.size)
|
|
188
|
-
ptr.write_array_of_float(data)
|
|
189
|
-
[ptr, data.size * 4]
|
|
190
|
-
when FFI::Pointer
|
|
191
|
-
[data, data.size]
|
|
212
|
+
def texture_extent(size)
|
|
213
|
+
if size.is_a?(Array)
|
|
214
|
+
[size.fetch(0), size[1] || 1, size[2] || 1]
|
|
192
215
|
else
|
|
193
|
-
|
|
216
|
+
[size.fetch(:width), size[:height] || 1, size[:depth_or_array_layers] || 1]
|
|
194
217
|
end
|
|
195
218
|
end
|
|
219
|
+
|
|
196
220
|
end
|
|
197
221
|
end
|
data/lib/wgpu/core/surface.rb
CHANGED
|
@@ -86,7 +86,7 @@ module WGPU
|
|
|
86
86
|
config = Native::SurfaceConfiguration.new
|
|
87
87
|
config[:next_in_chain] = nil
|
|
88
88
|
config[:device] = device.handle
|
|
89
|
-
config[:format] = format
|
|
89
|
+
config[:format] = Native::EnumHelper.coerce(Native::TextureFormat, format, name: "surface format")
|
|
90
90
|
config[:usage] = normalize_usage(usage)
|
|
91
91
|
config[:width] = width
|
|
92
92
|
config[:height] = height
|
|
@@ -95,15 +95,23 @@ module WGPU
|
|
|
95
95
|
@view_formats_ptr = nil
|
|
96
96
|
config[:view_formats] = nil
|
|
97
97
|
else
|
|
98
|
-
format_values = view_formats.map do |
|
|
99
|
-
|
|
98
|
+
format_values = view_formats.map do |view_format|
|
|
99
|
+
Native::EnumHelper.coerce(Native::TextureFormat, view_format, name: "view format")
|
|
100
100
|
end
|
|
101
101
|
@view_formats_ptr = FFI::MemoryPointer.new(:uint32, format_values.size)
|
|
102
102
|
@view_formats_ptr.write_array_of_uint32(format_values)
|
|
103
103
|
config[:view_formats] = @view_formats_ptr
|
|
104
104
|
end
|
|
105
|
-
config[:alpha_mode] = Native::
|
|
106
|
-
|
|
105
|
+
config[:alpha_mode] = Native::EnumHelper.coerce(
|
|
106
|
+
Native::CompositeAlphaMode,
|
|
107
|
+
alpha_mode,
|
|
108
|
+
name: "alpha mode"
|
|
109
|
+
)
|
|
110
|
+
config[:present_mode] = Native::EnumHelper.coerce(
|
|
111
|
+
Native::PresentMode,
|
|
112
|
+
present_mode,
|
|
113
|
+
name: "present mode"
|
|
114
|
+
)
|
|
107
115
|
|
|
108
116
|
Native.wgpuSurfaceConfigure(@handle, config)
|
|
109
117
|
@configured = true
|
|
@@ -134,7 +142,7 @@ module WGPU
|
|
|
134
142
|
|
|
135
143
|
status = Native::SurfaceGetCurrentTextureStatus[surface_texture[:status]]
|
|
136
144
|
unless status == :success_optimal || status == :success_suboptimal
|
|
137
|
-
raise
|
|
145
|
+
raise SurfaceAcquisitionError.new(status)
|
|
138
146
|
end
|
|
139
147
|
|
|
140
148
|
texture_ptr = surface_texture[:texture]
|
|
@@ -142,7 +150,7 @@ module WGPU
|
|
|
142
150
|
raise SurfaceError, "Surface returned null texture"
|
|
143
151
|
end
|
|
144
152
|
|
|
145
|
-
Texture.from_handle(texture_ptr)
|
|
153
|
+
Texture.from_handle(texture_ptr, surface_status: status)
|
|
146
154
|
end
|
|
147
155
|
|
|
148
156
|
def get_current_texture
|
|
@@ -206,16 +214,7 @@ module WGPU
|
|
|
206
214
|
private
|
|
207
215
|
|
|
208
216
|
def normalize_usage(usage)
|
|
209
|
-
|
|
210
|
-
when Integer
|
|
211
|
-
usage
|
|
212
|
-
when Symbol
|
|
213
|
-
Native::TextureUsage[usage]
|
|
214
|
-
when Array
|
|
215
|
-
usage.reduce(0) { |acc, u| acc | Native::TextureUsage[u] }
|
|
216
|
-
else
|
|
217
|
-
raise ArgumentError, "Invalid usage: #{usage}"
|
|
218
|
-
end
|
|
217
|
+
Native::EnumHelper.coerce_flags(Native::TextureUsage, usage, name: "surface usage")
|
|
219
218
|
end
|
|
220
219
|
end
|
|
221
220
|
end
|