wgpu 1.1.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 +36 -11
- data/lib/wgpu/core/async_waiter.rb +32 -4
- data/lib/wgpu/core/canvas_context.rb +0 -2
- data/lib/wgpu/core/device.rb +138 -42
- data/lib/wgpu/core/instance.rb +8 -4
- data/lib/wgpu/core/queue.rb +80 -49
- 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 +4 -1
- data/lib/wgpu/native/capabilities.rb +18 -2
- data/lib/wgpu/native/distribution.rb +113 -0
- data/lib/wgpu/native/enum_helper.rb +63 -0
- data/lib/wgpu/native/enums.rb +60 -8
- data/lib/wgpu/native/functions.rb +10 -10
- data/lib/wgpu/native/installer.rb +192 -0
- data/lib/wgpu/native/loader.rb +39 -21
- 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 +50 -8
- data/lib/wgpu/resources/buffer.rb +148 -73
- 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 +32 -0
- data/sig/wgpu.rbs +381 -0
- metadata +31 -16
data/lib/wgpu/core/device.rb
CHANGED
|
@@ -6,9 +6,14 @@ module WGPU
|
|
|
6
6
|
|
|
7
7
|
LIMIT_FIELDS = Native::Limits.members.freeze
|
|
8
8
|
|
|
9
|
-
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)
|
|
10
10
|
device_ptr = FFI::MemoryPointer.new(:pointer)
|
|
11
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
|
+
}
|
|
12
17
|
|
|
13
18
|
callback = FFI::Function.new(
|
|
14
19
|
:void, [:uint32, :pointer, Native::StringView.by_value, :pointer, :pointer]
|
|
@@ -28,14 +33,16 @@ 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
|
-
|
|
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
|
|
33
39
|
device_lost_info[:userdata1] = nil
|
|
34
40
|
device_lost_info[:userdata2] = nil
|
|
35
41
|
|
|
36
42
|
error_info = Native::UncapturedErrorCallbackInfo.new
|
|
37
43
|
error_info[:next_in_chain] = nil
|
|
38
|
-
|
|
44
|
+
uncaptured_error_callback = build_uncaptured_error_callback(device_callback_state)
|
|
45
|
+
error_info[:callback] = uncaptured_error_callback
|
|
39
46
|
error_info[:userdata1] = nil
|
|
40
47
|
error_info[:userdata2] = nil
|
|
41
48
|
|
|
@@ -74,8 +81,18 @@ module WGPU
|
|
|
74
81
|
callback_info[:userdata1] = nil
|
|
75
82
|
callback_info[:userdata2] = nil
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
79
96
|
|
|
80
97
|
handle = device_ptr.read_pointer
|
|
81
98
|
if handle.null? || status_holder[:value] != :success
|
|
@@ -83,12 +100,22 @@ module WGPU
|
|
|
83
100
|
raise DeviceError, "Failed to request device: #{msg}"
|
|
84
101
|
end
|
|
85
102
|
|
|
86
|
-
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
|
|
87
107
|
end
|
|
88
108
|
|
|
89
|
-
def initialize(handle, adapter: nil)
|
|
109
|
+
def initialize(handle, adapter: nil, label: nil, callback_state: nil)
|
|
90
110
|
@handle = handle
|
|
91
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 = []
|
|
92
119
|
@queue = Queue.new(Native.wgpuDeviceGetQueue(@handle), device: self)
|
|
93
120
|
end
|
|
94
121
|
|
|
@@ -100,8 +127,15 @@ module WGPU
|
|
|
100
127
|
Buffer.new(self, label: label, size: size, usage: usage, mapped_at_creation: mapped_at_creation)
|
|
101
128
|
end
|
|
102
129
|
|
|
103
|
-
def create_shader_module(label: nil, code
|
|
104
|
-
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
|
+
)
|
|
105
139
|
end
|
|
106
140
|
|
|
107
141
|
def create_command_encoder(label: nil)
|
|
@@ -185,8 +219,9 @@ module WGPU
|
|
|
185
219
|
)
|
|
186
220
|
end
|
|
187
221
|
|
|
188
|
-
def create_buffer_with_data(label: nil, data:, usage:)
|
|
189
|
-
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")
|
|
190
225
|
buffer = create_buffer(
|
|
191
226
|
label: label,
|
|
192
227
|
size: byte_size,
|
|
@@ -248,10 +283,11 @@ module WGPU
|
|
|
248
283
|
end
|
|
249
284
|
|
|
250
285
|
def push_error_scope(filter = :validation)
|
|
251
|
-
Native.
|
|
286
|
+
filter_value = Native::EnumHelper.coerce(Native::ErrorFilter, filter, name: "error filter")
|
|
287
|
+
Native.wgpuDevicePushErrorScope(@handle, filter_value)
|
|
252
288
|
end
|
|
253
289
|
|
|
254
|
-
def pop_error_scope
|
|
290
|
+
def pop_error_scope(timeout: nil)
|
|
255
291
|
error_holder = { done: false, status: nil, type: nil, message: nil }
|
|
256
292
|
|
|
257
293
|
callback = FFI::Function.new(
|
|
@@ -272,26 +308,53 @@ module WGPU
|
|
|
272
308
|
callback_info[:userdata1] = nil
|
|
273
309
|
callback_info[:userdata2] = nil
|
|
274
310
|
|
|
275
|
-
|
|
276
|
-
|
|
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
|
|
277
324
|
|
|
278
325
|
error_holder
|
|
279
326
|
end
|
|
280
327
|
|
|
281
|
-
def pop_error_scope_async
|
|
282
|
-
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))
|
|
283
334
|
end
|
|
284
335
|
|
|
285
336
|
def with_error_scope(filter = :validation)
|
|
286
337
|
push_error_scope(filter)
|
|
287
338
|
result = yield
|
|
288
|
-
error = pop_error_scope
|
|
289
|
-
|
|
290
|
-
raise Error, "GPU error (#{error[:type]}): #{error[:message]}"
|
|
291
|
-
end
|
|
339
|
+
error = GPUError.from_hash(pop_error_scope)
|
|
340
|
+
error&.raise!
|
|
292
341
|
result
|
|
293
342
|
end
|
|
294
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
|
+
|
|
295
358
|
def destroy
|
|
296
359
|
return if @handle.null?
|
|
297
360
|
Native.wgpuDeviceDestroy(@handle)
|
|
@@ -302,10 +365,10 @@ module WGPU
|
|
|
302
365
|
return if @handle.null?
|
|
303
366
|
Native.wgpuDeviceRelease(@handle)
|
|
304
367
|
@handle = FFI::Pointer::NULL
|
|
368
|
+
Array(@device_callback_tokens).each { |token| CallbackKeepalive.release(self, token) }
|
|
369
|
+
@device_callback_tokens&.clear
|
|
305
370
|
end
|
|
306
371
|
|
|
307
|
-
private
|
|
308
|
-
|
|
309
372
|
def self.normalize_required_features(required_features)
|
|
310
373
|
Array(required_features).map do |feature|
|
|
311
374
|
normalize_feature_name(feature)
|
|
@@ -316,10 +379,7 @@ module WGPU
|
|
|
316
379
|
return feature if feature.is_a?(Integer)
|
|
317
380
|
|
|
318
381
|
key = feature.to_s.strip.tr("-", "_").to_sym
|
|
319
|
-
|
|
320
|
-
raise ArgumentError, "Unknown feature name: #{feature}" if value.nil?
|
|
321
|
-
|
|
322
|
-
value
|
|
382
|
+
Native::EnumHelper.coerce(Native::FeatureName, key, name: "feature name")
|
|
323
383
|
end
|
|
324
384
|
|
|
325
385
|
def self.build_required_limits(adapter, required_limits)
|
|
@@ -352,23 +412,59 @@ module WGPU
|
|
|
352
412
|
raise ArgumentError, "Unknown limit key: #{name}"
|
|
353
413
|
end
|
|
354
414
|
|
|
355
|
-
def
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
426
|
+
end
|
|
427
|
+
end
|
|
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
|
|
369
438
|
end
|
|
370
439
|
end
|
|
371
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
|
+
|
|
372
468
|
def limits_to_hash(limits)
|
|
373
469
|
{
|
|
374
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
|
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
|
|
111
133
|
|
|
134
|
+
rows_per_image = data_layout[:rows_per_image] || height
|
|
135
|
+
buffer_size = bytes_per_row * rows_per_image * depth
|
|
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,14 +151,15 @@ 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
|
|
136
164
|
instance = device&.adapter&.instance
|
|
137
165
|
status_holder = { done: false, status: nil }
|
|
@@ -150,15 +178,26 @@ module WGPU
|
|
|
150
178
|
callback_info[:userdata1] = nil
|
|
151
179
|
callback_info[:userdata2] = nil
|
|
152
180
|
|
|
153
|
-
|
|
154
|
-
|
|
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)
|
|
193
|
+
end
|
|
155
194
|
|
|
156
195
|
status_holder[:status]
|
|
157
196
|
end
|
|
158
197
|
|
|
159
|
-
def on_submitted_work_done_async(device: nil)
|
|
198
|
+
def on_submitted_work_done_async(device: nil, timeout: nil)
|
|
160
199
|
AsyncTask.new do
|
|
161
|
-
on_submitted_work_done(device: device)
|
|
200
|
+
on_submitted_work_done(device: device, timeout: timeout)
|
|
162
201
|
end
|
|
163
202
|
end
|
|
164
203
|
|
|
@@ -170,21 +209,13 @@ module WGPU
|
|
|
170
209
|
|
|
171
210
|
private
|
|
172
211
|
|
|
173
|
-
def
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
ptr = FFI::MemoryPointer.new(:char, data.bytesize)
|
|
177
|
-
ptr.put_bytes(0, data)
|
|
178
|
-
[ptr, data.bytesize]
|
|
179
|
-
when Array
|
|
180
|
-
ptr = FFI::MemoryPointer.new(:float, data.size)
|
|
181
|
-
ptr.write_array_of_float(data)
|
|
182
|
-
[ptr, data.size * 4]
|
|
183
|
-
when FFI::Pointer
|
|
184
|
-
[data, data.size]
|
|
212
|
+
def texture_extent(size)
|
|
213
|
+
if size.is_a?(Array)
|
|
214
|
+
[size.fetch(0), size[1] || 1, size[2] || 1]
|
|
185
215
|
else
|
|
186
|
-
|
|
216
|
+
[size.fetch(:width), size[:height] || 1, size[:depth_or_array_layers] || 1]
|
|
187
217
|
end
|
|
188
218
|
end
|
|
219
|
+
|
|
189
220
|
end
|
|
190
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
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WGPU
|
|
4
|
+
module DataTypes
|
|
5
|
+
FORMATS = {
|
|
6
|
+
f32: ["e*", 4],
|
|
7
|
+
f64: ["E*", 8],
|
|
8
|
+
u32: ["L<*", 4],
|
|
9
|
+
i32: ["l<*", 4],
|
|
10
|
+
u16: ["S<*", 2],
|
|
11
|
+
u8: ["C*", 1]
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def pack(values, type: :f32)
|
|
17
|
+
format, = format_for(type)
|
|
18
|
+
Array(values).pack(format)
|
|
19
|
+
rescue RangeError => e
|
|
20
|
+
raise ArgumentError, "value is out of range for #{type}: #{e.message}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def unpack(bytes, type: :f32)
|
|
24
|
+
format, byte_size = format_for(type)
|
|
25
|
+
unless (bytes.bytesize % byte_size).zero?
|
|
26
|
+
raise ArgumentError, "#{type} data size must be a multiple of #{byte_size} bytes"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
bytes.unpack(format)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def byte_size(type)
|
|
33
|
+
format_for(type).last
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_pointer(data, type: :f32)
|
|
37
|
+
return [data, pointer_size(data)] if data.is_a?(FFI::Pointer)
|
|
38
|
+
|
|
39
|
+
bytes = data.is_a?(String) ? data : pack(data, type:)
|
|
40
|
+
pointer = FFI::MemoryPointer.new(:char, bytes.bytesize)
|
|
41
|
+
pointer.put_bytes(0, bytes)
|
|
42
|
+
[pointer, bytes.bytesize]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def validate_alignment!(value, alignment, name:)
|
|
46
|
+
integer = Integer(value)
|
|
47
|
+
raise ArgumentError, "#{name} must be non-negative" if integer.negative?
|
|
48
|
+
return integer if (integer % alignment).zero?
|
|
49
|
+
|
|
50
|
+
raise ArgumentError, "#{name} must be aligned to #{alignment} bytes (got #{integer})"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def format_for(type)
|
|
54
|
+
FORMATS.fetch(type.to_sym)
|
|
55
|
+
rescue NoMethodError, KeyError
|
|
56
|
+
raise ArgumentError, "Unknown data type #{type.inspect}. Valid values: #{FORMATS.keys.map(&:inspect).join(", ")}"
|
|
57
|
+
end
|
|
58
|
+
private_class_method :format_for
|
|
59
|
+
|
|
60
|
+
def pointer_size(pointer)
|
|
61
|
+
pointer.size
|
|
62
|
+
rescue NoMethodError
|
|
63
|
+
raise ArgumentError, "FFI pointer must have a known size; pass size explicitly"
|
|
64
|
+
end
|
|
65
|
+
private_class_method :pointer_size
|
|
66
|
+
end
|
|
67
|
+
end
|