wgpu 1.2.1 → 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 +26 -0
- data/docs/async.md +3 -1
- data/docs/errors.md +13 -0
- data/docs/getting_started_rendering.md +5 -0
- data/docs/resource_lifetime.md +9 -0
- data/lib/wgpu/commands/command_encoder.rb +24 -26
- data/lib/wgpu/commands/compute_pass.rb +6 -6
- data/lib/wgpu/commands/render_bundle_encoder.rb +8 -8
- data/lib/wgpu/commands/render_pass.rb +14 -14
- data/lib/wgpu/core/adapter.rb +6 -3
- data/lib/wgpu/core/async_waiter.rb +15 -4
- data/lib/wgpu/core/canvas_context.rb +5 -2
- data/lib/wgpu/core/device.rb +112 -55
- data/lib/wgpu/core/queue.rb +7 -22
- data/lib/wgpu/core/surface.rb +6 -6
- data/lib/wgpu/descriptor_helpers.rb +22 -0
- data/lib/wgpu/native/functions.rb +3 -0
- data/lib/wgpu/native/installer.rb +7 -1
- data/lib/wgpu/native_resource.rb +16 -0
- data/lib/wgpu/pipeline/bind_group.rb +7 -7
- data/lib/wgpu/pipeline/bind_group_layout.rb +3 -3
- data/lib/wgpu/pipeline/compute_pipeline.rb +5 -5
- data/lib/wgpu/pipeline/pipeline_layout.rb +4 -4
- data/lib/wgpu/pipeline/render_pipeline.rb +6 -6
- data/lib/wgpu/pipeline/shader_module.rb +3 -3
- data/lib/wgpu/resources/buffer.rb +83 -39
- data/lib/wgpu/resources/query_set.rb +1 -1
- data/lib/wgpu/resources/sampler.rb +3 -3
- data/lib/wgpu/resources/texture.rb +4 -6
- data/lib/wgpu/resources/texture_view.rb +1 -1
- data/lib/wgpu/version.rb +1 -1
- data/sig/wgpu.rbs +1 -0
- metadata +10 -4
data/lib/wgpu/core/device.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
3
5
|
module WGPU
|
|
4
6
|
class Device
|
|
5
7
|
attr_reader :handle, :queue, :adapter
|
|
@@ -18,6 +20,7 @@ module WGPU
|
|
|
18
20
|
# @raise [TimeoutError] if the request exceeds +timeout+
|
|
19
21
|
# @raise [ArgumentError] if +timeout+ is used without an instance-backed adapter
|
|
20
22
|
def self.request(adapter, label: nil, required_features: [], required_limits: nil, timeout: nil)
|
|
23
|
+
timeout = AsyncWaiter.normalize_timeout(timeout)
|
|
21
24
|
if timeout && !adapter.instance
|
|
22
25
|
raise ArgumentError,
|
|
23
26
|
"Device.request timeout requires an instance-backed adapter; " \
|
|
@@ -134,7 +137,7 @@ module WGPU
|
|
|
134
137
|
callback_token = CallbackKeepalive.retain(adapter, callback)
|
|
135
138
|
future =
|
|
136
139
|
begin
|
|
137
|
-
Native.wgpuAdapterRequestDevice(adapter
|
|
140
|
+
Native.wgpuAdapterRequestDevice(NativeResource.checked_handle(adapter, expected_class: Adapter), desc, callback_info)
|
|
138
141
|
rescue StandardError
|
|
139
142
|
CallbackKeepalive.release(adapter, callback_token)
|
|
140
143
|
release_device_callback_keepalive(device_callback_state)
|
|
@@ -148,7 +151,7 @@ module WGPU
|
|
|
148
151
|
future: future,
|
|
149
152
|
timeout: timeout
|
|
150
153
|
)
|
|
151
|
-
rescue
|
|
154
|
+
rescue StandardError
|
|
152
155
|
abandoned_device = nil
|
|
153
156
|
cleanup_abandoned = status_holder[:mutex].synchronize do
|
|
154
157
|
status_holder[:abandoned] = true
|
|
@@ -192,7 +195,8 @@ module WGPU
|
|
|
192
195
|
device.send(:adopt_device_callback_keepalive)
|
|
193
196
|
device
|
|
194
197
|
rescue StandardError
|
|
195
|
-
device
|
|
198
|
+
device&.release
|
|
199
|
+
release_device_callback_keepalive(device_callback_state)
|
|
196
200
|
raise
|
|
197
201
|
end
|
|
198
202
|
end
|
|
@@ -394,6 +398,8 @@ module WGPU
|
|
|
394
398
|
end
|
|
395
399
|
end
|
|
396
400
|
result
|
|
401
|
+
ensure
|
|
402
|
+
Native.wgpuSupportedFeaturesFreeMembers(supported) if supported
|
|
397
403
|
end
|
|
398
404
|
|
|
399
405
|
# Reports whether a feature is enabled on the device.
|
|
@@ -425,70 +431,41 @@ module WGPU
|
|
|
425
431
|
end
|
|
426
432
|
|
|
427
433
|
# Pushes a scope that captures matching GPU errors.
|
|
434
|
+
# Scopes belong to the pushing thread and serialize scoped work on this device.
|
|
435
|
+
# Pop on the same thread; do not wait here for another thread's scoped work.
|
|
428
436
|
# @param filter [Symbol, Integer] error filter
|
|
429
437
|
# @return [void]
|
|
430
438
|
def push_error_scope(filter = :validation)
|
|
431
439
|
filter_value = Native::EnumHelper.coerce(Native::ErrorFilter, filter, name: "error filter")
|
|
432
|
-
|
|
440
|
+
error_scope_monitor.enter
|
|
441
|
+
begin
|
|
442
|
+
ensure_not_released!
|
|
443
|
+
Native.wgpuDevicePushErrorScope(@handle, filter_value)
|
|
444
|
+
@error_scope_depth = (@error_scope_depth || 0) + 1
|
|
445
|
+
@error_scope_owner = Thread.current
|
|
446
|
+
rescue StandardError
|
|
447
|
+
error_scope_monitor.exit
|
|
448
|
+
raise
|
|
449
|
+
end
|
|
433
450
|
end
|
|
434
451
|
|
|
435
452
|
# Pops the latest error scope and waits for its result.
|
|
436
453
|
# @param timeout [Numeric, nil] maximum wait time in seconds
|
|
437
454
|
# @return [Hash] native status, error type, and message
|
|
438
455
|
def pop_error_scope(timeout: nil)
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
callback_token = nil
|
|
443
|
-
callback = FFI::Function.new(
|
|
444
|
-
:void, [:uint32, :uint32, Native::StringView.by_value, :pointer, :pointer]
|
|
445
|
-
) do |status, error_type, message, _userdata1, _userdata2|
|
|
446
|
-
begin
|
|
447
|
-
error_holder[:status] = Native::PopErrorScopeStatus[status]
|
|
448
|
-
error_holder[:type] = Native::ErrorType[error_type]
|
|
449
|
-
if message[:data] && !message[:data].null? && message[:length] > 0
|
|
450
|
-
error_holder[:message] = message[:data].read_string(message[:length])
|
|
451
|
-
end
|
|
452
|
-
error_holder[:done] = true
|
|
453
|
-
ensure
|
|
454
|
-
CallbackKeepalive.release(self, callback_token)
|
|
455
|
-
callback_lifetime_release.call
|
|
456
|
-
end
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
callback_info = Native::PopErrorScopeCallbackInfo.new
|
|
460
|
-
callback_info[:next_in_chain] = nil
|
|
461
|
-
callback_info[:mode] = AsyncWaiter.callback_mode(instance: @adapter&.instance)
|
|
462
|
-
callback_info[:callback] = callback
|
|
463
|
-
callback_info[:userdata1] = nil
|
|
464
|
-
callback_info[:userdata2] = nil
|
|
465
|
-
|
|
466
|
-
callback_token = CallbackKeepalive.retain(self, callback)
|
|
467
|
-
future =
|
|
468
|
-
begin
|
|
469
|
-
Native.wgpuDevicePopErrorScope(@handle, callback_info)
|
|
470
|
-
rescue StandardError
|
|
471
|
-
CallbackKeepalive.release(self, callback_token)
|
|
472
|
-
callback_lifetime_release.call
|
|
473
|
-
raise
|
|
474
|
-
end
|
|
475
|
-
AsyncWaiter.wait(
|
|
476
|
-
status_holder: error_holder,
|
|
477
|
-
instance: @adapter&.instance,
|
|
478
|
-
device: self,
|
|
479
|
-
future: future,
|
|
480
|
-
timeout: timeout
|
|
481
|
-
)
|
|
482
|
-
|
|
483
|
-
error_holder
|
|
456
|
+
timeout = AsyncWaiter.normalize_timeout(timeout)
|
|
457
|
+
error_holder, future = begin_pop_error_scope
|
|
458
|
+
wait_for_error_scope(error_holder, future, timeout: timeout)
|
|
484
459
|
end
|
|
485
460
|
|
|
486
|
-
#
|
|
461
|
+
# Starts popping on the calling thread and waits for completion in a background task.
|
|
487
462
|
#
|
|
488
463
|
# @param timeout [Numeric, nil] maximum wait time in seconds
|
|
489
464
|
# @return [AsyncTask] task whose value is the error hash
|
|
490
465
|
def pop_error_scope_async(timeout: nil)
|
|
491
|
-
|
|
466
|
+
timeout = AsyncWaiter.normalize_timeout(timeout)
|
|
467
|
+
error_holder, future = begin_pop_error_scope
|
|
468
|
+
AsyncTask.new { wait_for_error_scope(error_holder, future, timeout: timeout) }
|
|
492
469
|
end
|
|
493
470
|
|
|
494
471
|
# Pops the latest error scope as a typed error.
|
|
@@ -503,10 +480,11 @@ module WGPU
|
|
|
503
480
|
# @yieldreturn [Object] block result
|
|
504
481
|
# @return [Object] block result
|
|
505
482
|
def with_error_scope(filter = :validation)
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
error
|
|
483
|
+
raise ArgumentError, "block is required" unless block_given?
|
|
484
|
+
|
|
485
|
+
result = nil
|
|
486
|
+
error = capture_error_scope(filter) { result = yield }
|
|
487
|
+
GPUError.from_hash(error)&.raise!
|
|
510
488
|
result
|
|
511
489
|
end
|
|
512
490
|
|
|
@@ -706,6 +684,85 @@ module WGPU
|
|
|
706
684
|
|
|
707
685
|
private
|
|
708
686
|
|
|
687
|
+
def begin_pop_error_scope
|
|
688
|
+
unless @error_scope_owner == Thread.current && @error_scope_depth.to_i.positive?
|
|
689
|
+
raise DeviceError, "No error scope belongs to the current thread"
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
error_holder = { done: false, status: nil, type: nil, message: nil }
|
|
693
|
+
|
|
694
|
+
callback_lifetime_release = device_callback_lifetime_lease
|
|
695
|
+
callback_token = nil
|
|
696
|
+
callback = FFI::Function.new(
|
|
697
|
+
:void, [:uint32, :uint32, Native::StringView.by_value, :pointer, :pointer]
|
|
698
|
+
) do |status, error_type, message, _userdata1, _userdata2|
|
|
699
|
+
begin
|
|
700
|
+
error_holder[:status] = Native::PopErrorScopeStatus[status]
|
|
701
|
+
error_holder[:type] = Native::ErrorType[error_type]
|
|
702
|
+
if message[:data] && !message[:data].null? && message[:length] > 0
|
|
703
|
+
error_holder[:message] = message[:data].read_string(message[:length])
|
|
704
|
+
end
|
|
705
|
+
error_holder[:done] = true
|
|
706
|
+
ensure
|
|
707
|
+
CallbackKeepalive.release(self, callback_token)
|
|
708
|
+
callback_lifetime_release.call
|
|
709
|
+
end
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
callback_info = Native::PopErrorScopeCallbackInfo.new
|
|
713
|
+
callback_info[:next_in_chain] = nil
|
|
714
|
+
callback_info[:mode] = AsyncWaiter.callback_mode(instance: @adapter&.instance)
|
|
715
|
+
callback_info[:callback] = callback
|
|
716
|
+
callback_info[:userdata1] = nil
|
|
717
|
+
callback_info[:userdata2] = nil
|
|
718
|
+
|
|
719
|
+
callback_token = CallbackKeepalive.retain(self, callback)
|
|
720
|
+
@error_scope_depth -= 1
|
|
721
|
+
@error_scope_owner = nil if @error_scope_depth.zero?
|
|
722
|
+
future =
|
|
723
|
+
begin
|
|
724
|
+
Native.wgpuDevicePopErrorScope(@handle, callback_info)
|
|
725
|
+
rescue StandardError
|
|
726
|
+
CallbackKeepalive.release(self, callback_token)
|
|
727
|
+
callback_lifetime_release.call
|
|
728
|
+
raise
|
|
729
|
+
ensure
|
|
730
|
+
error_scope_monitor.exit
|
|
731
|
+
end
|
|
732
|
+
[error_holder, future]
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def wait_for_error_scope(error_holder, future, timeout:)
|
|
736
|
+
AsyncWaiter.wait(
|
|
737
|
+
status_holder: error_holder,
|
|
738
|
+
instance: @adapter&.instance,
|
|
739
|
+
device: self,
|
|
740
|
+
future: future,
|
|
741
|
+
timeout: timeout
|
|
742
|
+
)
|
|
743
|
+
error_holder
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
def error_scope_monitor
|
|
747
|
+
@error_scope_monitor ||= Monitor.new
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# The reentrant scope lock spans push, the operation, and native pop.
|
|
751
|
+
def capture_error_scope(filter = :validation)
|
|
752
|
+
push_error_scope(filter)
|
|
753
|
+
begin
|
|
754
|
+
yield
|
|
755
|
+
ensure
|
|
756
|
+
active_error = $!
|
|
757
|
+
begin
|
|
758
|
+
error = pop_error_scope
|
|
759
|
+
rescue StandardError
|
|
760
|
+
raise unless active_error
|
|
761
|
+
end
|
|
762
|
+
end
|
|
763
|
+
error
|
|
764
|
+
end
|
|
765
|
+
|
|
709
766
|
def adopt_device_callback_keepalive
|
|
710
767
|
@device_callback_tokens =
|
|
711
768
|
self.class.send(:transfer_device_callback_keepalive, @device_callback_state, self)
|
data/lib/wgpu/core/queue.rb
CHANGED
|
@@ -27,7 +27,7 @@ module WGPU
|
|
|
27
27
|
raise CommandError, "Command buffer has already been submitted" if buffer.submitted?
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
handles = buffers.map(
|
|
30
|
+
handles = buffers.map { |buffer| NativeResource.checked_handle(buffer, expected_class: CommandBuffer) }
|
|
31
31
|
ptr = FFI::MemoryPointer.new(:pointer, handles.size)
|
|
32
32
|
ptr.write_array_of_pointer(handles)
|
|
33
33
|
|
|
@@ -54,7 +54,7 @@ module WGPU
|
|
|
54
54
|
|
|
55
55
|
Native.wgpuQueueWriteBuffer(
|
|
56
56
|
@handle,
|
|
57
|
-
buffer
|
|
57
|
+
NativeResource.checked_handle(buffer, expected_class: Buffer),
|
|
58
58
|
buffer_offset,
|
|
59
59
|
data_ptr + data_offset,
|
|
60
60
|
write_size
|
|
@@ -71,7 +71,7 @@ module WGPU
|
|
|
71
71
|
data_ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
72
72
|
|
|
73
73
|
dst = Native::ImageCopyTexture.new
|
|
74
|
-
dst[:texture] = destination[:texture]
|
|
74
|
+
dst[:texture] = NativeResource.checked_handle(destination[:texture], expected_class: Texture)
|
|
75
75
|
dst[:mip_level] = destination[:mip_level] || 0
|
|
76
76
|
dst[:origin][:x] = destination.dig(:origin, :x) || 0
|
|
77
77
|
dst[:origin][:y] = destination.dig(:origin, :y) || 0
|
|
@@ -82,16 +82,7 @@ module WGPU
|
|
|
82
82
|
name: "texture aspect"
|
|
83
83
|
)
|
|
84
84
|
|
|
85
|
-
extent =
|
|
86
|
-
if size.is_a?(Array)
|
|
87
|
-
extent[:width] = size[0]
|
|
88
|
-
extent[:height] = size[1] || 1
|
|
89
|
-
extent[:depth_or_array_layers] = size[2] || 1
|
|
90
|
-
else
|
|
91
|
-
extent[:width] = size[:width]
|
|
92
|
-
extent[:height] = size[:height] || 1
|
|
93
|
-
extent[:depth_or_array_layers] = size[:depth_or_array_layers] || 1
|
|
94
|
-
end
|
|
85
|
+
extent = DescriptorHelpers.extent_3d(size)
|
|
95
86
|
|
|
96
87
|
layout = Native::TextureDataLayout.new
|
|
97
88
|
layout[:offset] = data_layout[:offset] || 0
|
|
@@ -148,7 +139,8 @@ module WGPU
|
|
|
148
139
|
device ||= @device
|
|
149
140
|
raise ArgumentError, "device is required when the queue has no owning device" unless device
|
|
150
141
|
|
|
151
|
-
|
|
142
|
+
extent = DescriptorHelpers.extent_3d(size)
|
|
143
|
+
width, height, depth = extent.values
|
|
152
144
|
bytes_per_row = data_layout[:bytes_per_row]
|
|
153
145
|
raise ArgumentError, "data_layout[:bytes_per_row] is required" unless bytes_per_row
|
|
154
146
|
|
|
@@ -206,6 +198,7 @@ module WGPU
|
|
|
206
198
|
# @param timeout [Numeric, nil] maximum wait time in seconds
|
|
207
199
|
# @return [Symbol] native completion status
|
|
208
200
|
def on_submitted_work_done(device: nil, timeout: nil)
|
|
201
|
+
timeout = AsyncWaiter.normalize_timeout(timeout)
|
|
209
202
|
device ||= @device
|
|
210
203
|
instance = device&.adapter&.instance
|
|
211
204
|
status_holder = { done: false, status: nil }
|
|
@@ -271,14 +264,6 @@ module WGPU
|
|
|
271
264
|
|
|
272
265
|
private
|
|
273
266
|
|
|
274
|
-
def texture_extent(size)
|
|
275
|
-
if size.is_a?(Array)
|
|
276
|
-
[size.fetch(0), size[1] || 1, size[2] || 1]
|
|
277
|
-
else
|
|
278
|
-
[size.fetch(:width), size[:height] || 1, size[:depth_or_array_layers] || 1]
|
|
279
|
-
end
|
|
280
|
-
end
|
|
281
|
-
|
|
282
267
|
def validate_readback_staging!(staging, required_size)
|
|
283
268
|
if staging.size < required_size
|
|
284
269
|
raise ArgumentError,
|
data/lib/wgpu/core/surface.rb
CHANGED
|
@@ -18,7 +18,7 @@ module WGPU
|
|
|
18
18
|
desc[:label][:data] = nil
|
|
19
19
|
desc[:label][:length] = 0
|
|
20
20
|
|
|
21
|
-
handle = Native.wgpuInstanceCreateSurface(instance
|
|
21
|
+
handle = Native.wgpuInstanceCreateSurface(NativeResource.checked_handle(instance, expected_class: Instance), desc)
|
|
22
22
|
raise SurfaceError, "Failed to create surface from Metal layer" if handle.null?
|
|
23
23
|
|
|
24
24
|
new(handle, instance)
|
|
@@ -39,7 +39,7 @@ module WGPU
|
|
|
39
39
|
desc[:label][:data] = nil
|
|
40
40
|
desc[:label][:length] = 0
|
|
41
41
|
|
|
42
|
-
handle = Native.wgpuInstanceCreateSurface(instance
|
|
42
|
+
handle = Native.wgpuInstanceCreateSurface(NativeResource.checked_handle(instance, expected_class: Instance), desc)
|
|
43
43
|
raise SurfaceError, "Failed to create surface from Windows HWND" if handle.null?
|
|
44
44
|
|
|
45
45
|
new(handle, instance)
|
|
@@ -60,7 +60,7 @@ module WGPU
|
|
|
60
60
|
desc[:label][:data] = nil
|
|
61
61
|
desc[:label][:length] = 0
|
|
62
62
|
|
|
63
|
-
handle = Native.wgpuInstanceCreateSurface(instance
|
|
63
|
+
handle = Native.wgpuInstanceCreateSurface(NativeResource.checked_handle(instance, expected_class: Instance), desc)
|
|
64
64
|
raise SurfaceError, "Failed to create surface from Xlib window" if handle.null?
|
|
65
65
|
|
|
66
66
|
new(handle, instance)
|
|
@@ -81,7 +81,7 @@ module WGPU
|
|
|
81
81
|
desc[:label][:data] = nil
|
|
82
82
|
desc[:label][:length] = 0
|
|
83
83
|
|
|
84
|
-
handle = Native.wgpuInstanceCreateSurface(instance
|
|
84
|
+
handle = Native.wgpuInstanceCreateSurface(NativeResource.checked_handle(instance, expected_class: Instance), desc)
|
|
85
85
|
raise SurfaceError, "Failed to create surface from Wayland surface" if handle.null?
|
|
86
86
|
|
|
87
87
|
new(handle, instance)
|
|
@@ -102,7 +102,7 @@ module WGPU
|
|
|
102
102
|
def configure(device:, format:, usage: :render_attachment, width:, height:, present_mode: :fifo, alpha_mode: :auto, view_formats: [])
|
|
103
103
|
config = Native::SurfaceConfiguration.new
|
|
104
104
|
config[:next_in_chain] = nil
|
|
105
|
-
config[:device] = device
|
|
105
|
+
config[:device] = NativeResource.checked_handle(device, expected_class: Device)
|
|
106
106
|
config[:format] = Native::EnumHelper.coerce(Native::TextureFormat, format, name: "surface format")
|
|
107
107
|
config[:usage] = normalize_usage(usage)
|
|
108
108
|
config[:width] = width
|
|
@@ -216,7 +216,7 @@ module WGPU
|
|
|
216
216
|
# @return [Hash]
|
|
217
217
|
def capabilities(adapter)
|
|
218
218
|
caps = Native::SurfaceCapabilities.new
|
|
219
|
-
Native.wgpuSurfaceGetCapabilities(@handle, adapter
|
|
219
|
+
Native.wgpuSurfaceGetCapabilities(@handle, NativeResource.checked_handle(adapter, expected_class: Adapter), caps)
|
|
220
220
|
|
|
221
221
|
formats = []
|
|
222
222
|
if caps[:format_count] > 0 && !caps[:formats].null?
|
|
@@ -6,6 +6,28 @@ module WGPU
|
|
|
6
6
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
|
+
# Normalizes an Array or Hash texture extent to a native descriptor.
|
|
10
|
+
# @param size [Array<Integer>, Hash] width, height, and depth/layer count
|
|
11
|
+
# @return [Native::Extent3D]
|
|
12
|
+
def extent_3d(size)
|
|
13
|
+
values = case size
|
|
14
|
+
when Array
|
|
15
|
+
[size.fetch(0), size[1] || 1, size[2] || 1]
|
|
16
|
+
when Hash
|
|
17
|
+
[size.fetch(:width), size[:height] || 1, size[:depth_or_array_layers] || 1]
|
|
18
|
+
else
|
|
19
|
+
raise ArgumentError, "texture size must be an Array or Hash"
|
|
20
|
+
end
|
|
21
|
+
extent = Native::Extent3D.new
|
|
22
|
+
[:width, :height, :depth_or_array_layers].zip(values).each do |field, value|
|
|
23
|
+
value = Integer(value)
|
|
24
|
+
raise ArgumentError, "#{field} must be between 0 and 4294967295" unless (0..0xFFFFFFFF).cover?(value)
|
|
25
|
+
|
|
26
|
+
extent[field] = value
|
|
27
|
+
end
|
|
28
|
+
extent
|
|
29
|
+
end
|
|
30
|
+
|
|
9
31
|
# Writes an optional Ruby label into a native descriptor.
|
|
10
32
|
#
|
|
11
33
|
# @param descriptor [FFI::Struct] descriptor with a +label+ member
|
|
@@ -292,6 +292,9 @@ module WGPU
|
|
|
292
292
|
attach_function :wgpuTextureViewRelease,
|
|
293
293
|
[:pointer], :void
|
|
294
294
|
|
|
295
|
+
attach_function :wgpuSupportedFeaturesFreeMembers,
|
|
296
|
+
[SupportedFeatures.by_value], :void
|
|
297
|
+
|
|
295
298
|
attach_function :wgpuAdapterInfoFreeMembers,
|
|
296
299
|
[AdapterInfo.by_value], :void
|
|
297
300
|
|
|
@@ -186,7 +186,13 @@ module WGPU
|
|
|
186
186
|
end
|
|
187
187
|
|
|
188
188
|
FileUtils.mkdir_p(entry.directory? ? target : File.dirname(target))
|
|
189
|
-
|
|
189
|
+
next if entry.directory?
|
|
190
|
+
|
|
191
|
+
if entry.method(:extract).parameters.include?([:key, :destination_directory])
|
|
192
|
+
entry.extract(entry.name, destination_directory: destination) { true }
|
|
193
|
+
else
|
|
194
|
+
entry.extract(target) { true }
|
|
195
|
+
end
|
|
190
196
|
end
|
|
191
197
|
end
|
|
192
198
|
true
|
data/lib/wgpu/native_resource.rb
CHANGED
|
@@ -182,6 +182,15 @@ module WGPU
|
|
|
182
182
|
module NativeResource
|
|
183
183
|
GUARDED_METHOD_EXEMPTIONS = [:initialize, :release, :released?, :handle, :label, :inspect].freeze
|
|
184
184
|
|
|
185
|
+
# Validates resource arguments before passing their handles to FFI.
|
|
186
|
+
def self.checked_handle(resource, expected_class:)
|
|
187
|
+
unless resource.is_a?(expected_class)
|
|
188
|
+
raise TypeError, "expected #{expected_class}, got #{resource.class}"
|
|
189
|
+
end
|
|
190
|
+
resource.send(:ensure_not_released!)
|
|
191
|
+
resource.handle
|
|
192
|
+
end
|
|
193
|
+
|
|
185
194
|
module ClassMethods
|
|
186
195
|
private
|
|
187
196
|
|
|
@@ -205,6 +214,9 @@ module WGPU
|
|
|
205
214
|
super
|
|
206
215
|
initialize_native_resource(label: kwargs.fetch(:label, UNSET))
|
|
207
216
|
attach_device_callback_lifetime_from_parent
|
|
217
|
+
initialized = true
|
|
218
|
+
ensure
|
|
219
|
+
release if !initialized && @handle && !released?
|
|
208
220
|
end
|
|
209
221
|
|
|
210
222
|
# Releases a resource once and unregisters it from leak tracking.
|
|
@@ -284,6 +296,10 @@ module WGPU
|
|
|
284
296
|
|
|
285
297
|
private
|
|
286
298
|
|
|
299
|
+
def initialize_copy(_source)
|
|
300
|
+
raise TypeError, "#{self.class} owns a native resource and cannot be copied"
|
|
301
|
+
end
|
|
302
|
+
|
|
287
303
|
def attach_device_callback_lifetime(owner)
|
|
288
304
|
lifetime = owner&.instance_variable_get(:@device_callback_lifetime)
|
|
289
305
|
return self unless lifetime
|
|
@@ -29,13 +29,13 @@ module WGPU
|
|
|
29
29
|
desc[:label][:data] = nil
|
|
30
30
|
desc[:label][:length] = 0
|
|
31
31
|
end
|
|
32
|
-
desc[:layout] = layout
|
|
32
|
+
desc[:layout] = NativeResource.checked_handle(layout, expected_class: BindGroupLayout)
|
|
33
33
|
desc[:entry_count] = entries_array.size
|
|
34
34
|
desc[:entries] = entries_ptr
|
|
35
35
|
|
|
36
|
-
device.
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
error = device.send(:capture_error_scope) do
|
|
37
|
+
@handle = Native.wgpuDeviceCreateBindGroup(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
38
|
+
end
|
|
39
39
|
|
|
40
40
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
41
41
|
msg = error[:message] || "Failed to create bind group"
|
|
@@ -74,7 +74,7 @@ module WGPU
|
|
|
74
74
|
|
|
75
75
|
if entry_hash[:buffer]
|
|
76
76
|
buffer = entry_hash[:buffer]
|
|
77
|
-
entry[:buffer] = buffer
|
|
77
|
+
entry[:buffer] = NativeResource.checked_handle(buffer, expected_class: Buffer)
|
|
78
78
|
entry[:offset] = entry_hash[:offset] || 0
|
|
79
79
|
entry[:size] = entry_hash[:size] || buffer.size
|
|
80
80
|
else
|
|
@@ -84,13 +84,13 @@ module WGPU
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
if entry_hash[:sampler]
|
|
87
|
-
entry[:sampler] = entry_hash[:sampler]
|
|
87
|
+
entry[:sampler] = NativeResource.checked_handle(entry_hash[:sampler], expected_class: Sampler)
|
|
88
88
|
else
|
|
89
89
|
entry[:sampler] = nil
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
if entry_hash[:texture_view]
|
|
93
|
-
entry[:texture_view] = entry_hash[:texture_view]
|
|
93
|
+
entry[:texture_view] = NativeResource.checked_handle(entry_hash[:texture_view], expected_class: TextureView)
|
|
94
94
|
else
|
|
95
95
|
entry[:texture_view] = nil
|
|
96
96
|
end
|
|
@@ -24,9 +24,9 @@ module WGPU
|
|
|
24
24
|
@device = device
|
|
25
25
|
desc, @descriptor_keepalive = build_descriptor(label:, entries:)
|
|
26
26
|
|
|
27
|
-
device.
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
error = device.send(:capture_error_scope) do
|
|
28
|
+
@handle = Native.wgpuDeviceCreateBindGroupLayout(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
29
|
+
end
|
|
30
30
|
@descriptor_keepalive = nil
|
|
31
31
|
|
|
32
32
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
@@ -13,9 +13,9 @@ module WGPU
|
|
|
13
13
|
@device = device
|
|
14
14
|
desc, @pointers = build_descriptor(label:, layout:, compute:)
|
|
15
15
|
|
|
16
|
-
device.
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
error = device.send(:capture_error_scope) do
|
|
17
|
+
@handle = Native.wgpuDeviceCreateComputePipeline(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
18
|
+
end
|
|
19
19
|
|
|
20
20
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
21
21
|
msg = error[:message] || "Failed to create compute pipeline"
|
|
@@ -59,7 +59,7 @@ module WGPU
|
|
|
59
59
|
DescriptorHelpers.set_label(desc, label, keepalive: @pointers)
|
|
60
60
|
desc[:layout] = normalize_layout(layout)
|
|
61
61
|
desc[:compute][:next_in_chain] = nil
|
|
62
|
-
desc[:compute][:module] = compute.fetch(:module)
|
|
62
|
+
desc[:compute][:module] = NativeResource.checked_handle(compute.fetch(:module), expected_class: ShaderModule)
|
|
63
63
|
DescriptorHelpers.set_nullable_string_view(
|
|
64
64
|
desc[:compute][:entry_point],
|
|
65
65
|
compute[:entry_point],
|
|
@@ -72,7 +72,7 @@ module WGPU
|
|
|
72
72
|
|
|
73
73
|
def normalize_layout(layout)
|
|
74
74
|
return nil if layout.nil? || layout == :auto || layout == "auto"
|
|
75
|
-
layout
|
|
75
|
+
NativeResource.checked_handle(layout, expected_class: PipelineLayout)
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
end
|
|
@@ -13,7 +13,7 @@ module WGPU
|
|
|
13
13
|
|
|
14
14
|
layouts = Array(bind_group_layouts)
|
|
15
15
|
layouts_ptr = FFI::MemoryPointer.new(:pointer, layouts.size)
|
|
16
|
-
layouts_ptr.write_array_of_pointer(layouts.map(
|
|
16
|
+
layouts_ptr.write_array_of_pointer(layouts.map { |layout| NativeResource.checked_handle(layout, expected_class: BindGroupLayout) })
|
|
17
17
|
|
|
18
18
|
desc = Native::PipelineLayoutDescriptor.new
|
|
19
19
|
desc[:next_in_chain] = nil
|
|
@@ -28,9 +28,9 @@ module WGPU
|
|
|
28
28
|
desc[:bind_group_layout_count] = layouts.size
|
|
29
29
|
desc[:bind_group_layouts] = layouts_ptr
|
|
30
30
|
|
|
31
|
-
device.
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
error = device.send(:capture_error_scope) do
|
|
32
|
+
@handle = Native.wgpuDeviceCreatePipelineLayout(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
33
|
+
end
|
|
34
34
|
|
|
35
35
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
36
36
|
msg = error[:message] || "Failed to create pipeline layout"
|
|
@@ -22,9 +22,9 @@ module WGPU
|
|
|
22
22
|
fragment:
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
device.
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
error = device.send(:capture_error_scope) do
|
|
26
|
+
@handle = Native.wgpuDeviceCreateRenderPipeline(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
27
|
+
end
|
|
28
28
|
|
|
29
29
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
30
30
|
msg = error[:message] || "Failed to create render pipeline"
|
|
@@ -78,7 +78,7 @@ module WGPU
|
|
|
78
78
|
context: "render pipeline vertex descriptor"
|
|
79
79
|
)
|
|
80
80
|
vertex_state[:next_in_chain] = nil
|
|
81
|
-
vertex_state[:module] = vertex[:module]
|
|
81
|
+
vertex_state[:module] = NativeResource.checked_handle(vertex[:module], expected_class: ShaderModule)
|
|
82
82
|
|
|
83
83
|
DescriptorHelpers.set_nullable_string_view(
|
|
84
84
|
vertex_state[:entry_point],
|
|
@@ -265,7 +265,7 @@ module WGPU
|
|
|
265
265
|
frag = Native::FragmentState.new
|
|
266
266
|
@pointers << frag
|
|
267
267
|
frag[:next_in_chain] = nil
|
|
268
|
-
frag[:module] = fragment[:module]
|
|
268
|
+
frag[:module] = NativeResource.checked_handle(fragment[:module], expected_class: ShaderModule)
|
|
269
269
|
|
|
270
270
|
DescriptorHelpers.set_nullable_string_view(
|
|
271
271
|
frag[:entry_point],
|
|
@@ -369,7 +369,7 @@ module WGPU
|
|
|
369
369
|
|
|
370
370
|
def normalize_layout(layout)
|
|
371
371
|
return nil if layout.nil? || layout == :auto || layout == "auto"
|
|
372
|
-
layout
|
|
372
|
+
NativeResource.checked_handle(layout, expected_class: PipelineLayout)
|
|
373
373
|
end
|
|
374
374
|
end
|
|
375
375
|
end
|
|
@@ -30,9 +30,9 @@ module WGPU
|
|
|
30
30
|
desc[:label][:length] = 0
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
device.
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
error = device.send(:capture_error_scope) do
|
|
34
|
+
@handle = Native.wgpuDeviceCreateShaderModule(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
35
|
+
end
|
|
36
36
|
|
|
37
37
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
38
38
|
msg = error[:message] || "Failed to create shader module"
|