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
data/lib/wgpu/core/instance.rb
CHANGED
|
@@ -4,6 +4,8 @@ module WGPU
|
|
|
4
4
|
class Instance
|
|
5
5
|
attr_reader :handle
|
|
6
6
|
|
|
7
|
+
# Creates a WebGPU instance.
|
|
8
|
+
# @raise [InitializationError] if native instance creation fails
|
|
7
9
|
def initialize
|
|
8
10
|
desc = Native::InstanceDescriptor.new
|
|
9
11
|
desc[:next_in_chain] = nil
|
|
@@ -15,29 +17,40 @@ module WGPU
|
|
|
15
17
|
raise InitializationError, "Failed to create WebGPU instance" if @handle.null?
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
# Requests an adapter matching the supplied preferences.
|
|
21
|
+
# @return [Adapter]
|
|
22
|
+
def request_adapter(power_preference: :high_performance, backend: nil, feature_level: :core,
|
|
23
|
+
force_fallback_adapter: false, compatible_surface: nil, timeout: nil)
|
|
19
24
|
Adapter.request(
|
|
20
25
|
self,
|
|
21
26
|
power_preference: power_preference,
|
|
22
27
|
backend: backend,
|
|
23
28
|
feature_level: feature_level,
|
|
24
29
|
force_fallback_adapter: force_fallback_adapter,
|
|
25
|
-
compatible_surface: compatible_surface
|
|
30
|
+
compatible_surface: compatible_surface,
|
|
31
|
+
timeout: timeout
|
|
26
32
|
)
|
|
27
33
|
end
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
# Requests an adapter on a background task.
|
|
36
|
+
# @return [AsyncTask] task yielding an {Adapter}
|
|
37
|
+
def request_adapter_async(power_preference: :high_performance, backend: nil, feature_level: :core,
|
|
38
|
+
force_fallback_adapter: false, compatible_surface: nil, timeout: nil)
|
|
30
39
|
AsyncTask.new do
|
|
31
40
|
request_adapter(
|
|
32
41
|
power_preference: power_preference,
|
|
33
42
|
backend: backend,
|
|
34
43
|
feature_level: feature_level,
|
|
35
44
|
force_fallback_adapter: force_fallback_adapter,
|
|
36
|
-
compatible_surface: compatible_surface
|
|
45
|
+
compatible_surface: compatible_surface,
|
|
46
|
+
timeout: timeout
|
|
37
47
|
)
|
|
38
48
|
end
|
|
39
49
|
end
|
|
40
50
|
|
|
51
|
+
# Lists adapters exposed by the instance.
|
|
52
|
+
# @param backends [Integer, nil] backend bit mask
|
|
53
|
+
# @return [Array<Adapter>]
|
|
41
54
|
def enumerate_adapters(backends: nil)
|
|
42
55
|
options = nil
|
|
43
56
|
if backends
|
|
@@ -57,20 +70,31 @@ module WGPU
|
|
|
57
70
|
end
|
|
58
71
|
end
|
|
59
72
|
|
|
73
|
+
# Enumerates adapters on a background task.
|
|
74
|
+
# @return [AsyncTask] task yielding adapter objects
|
|
60
75
|
def enumerate_adapters_async(backends: nil)
|
|
61
76
|
AsyncTask.new do
|
|
62
77
|
enumerate_adapters(backends: backends)
|
|
63
78
|
end
|
|
64
79
|
end
|
|
65
80
|
|
|
81
|
+
# Creates a canvas context from platform presentation information.
|
|
82
|
+
# @param present_info [Hash] platform surface information
|
|
83
|
+
# @return [CanvasContext]
|
|
66
84
|
def get_canvas_context(present_info)
|
|
67
85
|
CanvasContext.new(self, present_info)
|
|
68
86
|
end
|
|
69
87
|
|
|
88
|
+
# Processes pending instance callbacks and events.
|
|
89
|
+
# @return [void]
|
|
70
90
|
def process_events
|
|
71
91
|
Native.wgpuInstanceProcessEvents(@handle)
|
|
72
92
|
end
|
|
73
93
|
|
|
94
|
+
# Releases the native instance handle.
|
|
95
|
+
#
|
|
96
|
+
# Calling this method more than once has no effect.
|
|
97
|
+
# @return [void]
|
|
74
98
|
def release
|
|
75
99
|
return if @handle.null?
|
|
76
100
|
Native.wgpuInstanceRelease(@handle)
|
data/lib/wgpu/core/queue.rb
CHANGED
|
@@ -4,24 +4,45 @@ module WGPU
|
|
|
4
4
|
class Queue
|
|
5
5
|
attr_reader :handle
|
|
6
6
|
|
|
7
|
+
# Wraps a device's native submission queue.
|
|
8
|
+
# @param handle [FFI::Pointer] native queue handle
|
|
9
|
+
# @param device [Device, nil] owning device
|
|
7
10
|
def initialize(handle, device: nil)
|
|
8
11
|
@handle = handle
|
|
9
12
|
@device = device
|
|
10
13
|
end
|
|
11
14
|
|
|
15
|
+
# Submits command buffers exactly once in the supplied order.
|
|
16
|
+
# @param command_buffers [CommandBuffer, Array<CommandBuffer>] buffers to submit
|
|
17
|
+
# @return [void]
|
|
18
|
+
# @raise [CommandError] for duplicate or previously submitted buffers
|
|
12
19
|
def submit(command_buffers)
|
|
13
20
|
buffers = Array(command_buffers)
|
|
14
21
|
return if buffers.empty?
|
|
15
22
|
|
|
23
|
+
if buffers.map(&:object_id).uniq.length != buffers.length
|
|
24
|
+
raise CommandError, "The same command buffer cannot appear twice in one submission"
|
|
25
|
+
end
|
|
26
|
+
buffers.each do |buffer|
|
|
27
|
+
raise CommandError, "Command buffer has already been submitted" if buffer.submitted?
|
|
28
|
+
end
|
|
29
|
+
|
|
16
30
|
handles = buffers.map(&:handle)
|
|
17
31
|
ptr = FFI::MemoryPointer.new(:pointer, handles.size)
|
|
18
32
|
ptr.write_array_of_pointer(handles)
|
|
19
33
|
|
|
20
34
|
Native.wgpuQueueSubmit(@handle, handles.size, ptr)
|
|
35
|
+
buffers.each(&:mark_submitted!)
|
|
21
36
|
end
|
|
22
37
|
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
# Writes typed data into a GPU buffer.
|
|
39
|
+
# @param buffer [Buffer] destination buffer
|
|
40
|
+
# @param buffer_offset [Integer] destination byte offset
|
|
41
|
+
# @param data [Array, String, FFI::Pointer] source data
|
|
42
|
+
# @return [void]
|
|
43
|
+
def write_buffer(buffer, buffer_offset, data, data_offset: 0, size: nil, type: :f32)
|
|
44
|
+
data_ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
45
|
+
DataTypes.validate_alignment!(buffer_offset, 4, name: "buffer_offset")
|
|
25
46
|
data_offset = Integer(data_offset)
|
|
26
47
|
raise ArgumentError, "data_offset must be non-negative" if data_offset.negative?
|
|
27
48
|
raise ArgumentError, "data_offset out of range" if data_offset > byte_size
|
|
@@ -29,6 +50,7 @@ module WGPU
|
|
|
29
50
|
write_size = size.nil? ? (byte_size - data_offset) : Integer(size)
|
|
30
51
|
raise ArgumentError, "size must be non-negative" if write_size.negative?
|
|
31
52
|
raise ArgumentError, "data_offset + size out of range" if data_offset + write_size > byte_size
|
|
53
|
+
DataTypes.validate_alignment!(write_size, 4, name: "size")
|
|
32
54
|
|
|
33
55
|
Native.wgpuQueueWriteBuffer(
|
|
34
56
|
@handle,
|
|
@@ -39,8 +61,14 @@ module WGPU
|
|
|
39
61
|
)
|
|
40
62
|
end
|
|
41
63
|
|
|
42
|
-
|
|
43
|
-
|
|
64
|
+
# Writes typed data into a texture region.
|
|
65
|
+
# @param destination [Hash] destination texture and origin
|
|
66
|
+
# @param data [Array, String, FFI::Pointer] source data
|
|
67
|
+
# @param data_layout [Hash] source byte layout
|
|
68
|
+
# @param size [Hash, Array] extent to write
|
|
69
|
+
# @return [void]
|
|
70
|
+
def write_texture(destination:, data:, data_layout:, size:, type: :f32)
|
|
71
|
+
data_ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
44
72
|
|
|
45
73
|
dst = Native::ImageCopyTexture.new
|
|
46
74
|
dst[:texture] = destination[:texture].handle
|
|
@@ -48,7 +76,11 @@ module WGPU
|
|
|
48
76
|
dst[:origin][:x] = destination.dig(:origin, :x) || 0
|
|
49
77
|
dst[:origin][:y] = destination.dig(:origin, :y) || 0
|
|
50
78
|
dst[:origin][:z] = destination.dig(:origin, :z) || 0
|
|
51
|
-
dst[:aspect] =
|
|
79
|
+
dst[:aspect] = Native::EnumHelper.coerce(
|
|
80
|
+
Native::TextureAspect,
|
|
81
|
+
destination[:aspect] || :all,
|
|
82
|
+
name: "texture aspect"
|
|
83
|
+
)
|
|
52
84
|
|
|
53
85
|
extent = Native::Extent3D.new
|
|
54
86
|
if size.is_a?(Array)
|
|
@@ -69,14 +101,18 @@ module WGPU
|
|
|
69
101
|
Native.wgpuQueueWriteTexture(@handle, dst, data_ptr, byte_size, layout, extent)
|
|
70
102
|
end
|
|
71
103
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
104
|
+
# Copies a GPU buffer to mapped staging memory and returns its bytes.
|
|
105
|
+
# @param buffer [Buffer] source buffer
|
|
106
|
+
# @param staging [Buffer, nil] reusable map-read staging buffer
|
|
107
|
+
# @return [String] copied bytes
|
|
108
|
+
def read_buffer(buffer, offset: 0, size: nil, device: nil, staging: nil)
|
|
109
|
+
device ||= @device
|
|
110
|
+
raise ArgumentError, "device is required when the queue has no owning device" unless device
|
|
79
111
|
|
|
112
|
+
size ||= buffer.size - offset
|
|
113
|
+
owns_staging = staging.nil?
|
|
114
|
+
staging ||= Buffer.new(device, size: size, usage: [:map_read, :copy_dst])
|
|
115
|
+
validate_readback_staging!(staging, size)
|
|
80
116
|
encoder = CommandEncoder.new(device)
|
|
81
117
|
encoder.copy_buffer_to_buffer(
|
|
82
118
|
source: buffer,
|
|
@@ -88,27 +124,55 @@ module WGPU
|
|
|
88
124
|
command_buffer = encoder.finish
|
|
89
125
|
submit([command_buffer])
|
|
90
126
|
|
|
127
|
+
map_requested = true
|
|
91
128
|
staging.map_sync(:read)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
129
|
+
staging.read_mapped_data(size:)
|
|
130
|
+
ensure
|
|
131
|
+
cleanup_readback_resources(
|
|
132
|
+
staging:,
|
|
133
|
+
unmap_staging: map_requested,
|
|
134
|
+
owns_staging:,
|
|
135
|
+
command_buffer:,
|
|
136
|
+
encoder:,
|
|
137
|
+
active_error: $!
|
|
138
|
+
)
|
|
97
139
|
end
|
|
98
140
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
141
|
+
# Copies a texture region to mapped staging memory and returns its padded bytes.
|
|
142
|
+
# @param source [Hash] source texture, origin, aspect, and optional format
|
|
143
|
+
# @param data_layout [Hash] destination byte layout
|
|
144
|
+
# @param size [Hash, Array] extent to read
|
|
145
|
+
# @param staging [Buffer, nil] reusable map-read staging buffer
|
|
146
|
+
# @return [String] copied bytes
|
|
147
|
+
def read_texture(source:, data_layout:, size:, device: nil, staging: nil)
|
|
148
|
+
device ||= @device
|
|
149
|
+
raise ArgumentError, "device is required when the queue has no owning device" unless device
|
|
150
|
+
|
|
151
|
+
width, height, depth = texture_extent(size)
|
|
103
152
|
bytes_per_row = data_layout[:bytes_per_row]
|
|
104
|
-
|
|
105
|
-
buffer_size = bytes_per_row * rows_per_image * depth
|
|
153
|
+
raise ArgumentError, "data_layout[:bytes_per_row] is required" unless bytes_per_row
|
|
106
154
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
155
|
+
DataTypes.validate_alignment!(
|
|
156
|
+
bytes_per_row,
|
|
157
|
+
TextureFormat::COPY_ALIGNMENT,
|
|
158
|
+
name: "bytes_per_row"
|
|
110
159
|
)
|
|
160
|
+
format = source[:format] || source[:texture].format
|
|
161
|
+
aspect = source[:aspect] || :all
|
|
162
|
+
tight_bytes_per_row = TextureFormat.bytes_per_row(width, format, aspect:)
|
|
163
|
+
minimum_bytes_per_row = TextureFormat.aligned_bytes_per_row(width, format, aspect:)
|
|
164
|
+
if bytes_per_row < minimum_bytes_per_row
|
|
165
|
+
raise ArgumentError,
|
|
166
|
+
"bytes_per_row must be at least #{minimum_bytes_per_row} for width #{width} and #{format.inspect} " \
|
|
167
|
+
"(tight row is #{tight_bytes_per_row} bytes)"
|
|
168
|
+
end
|
|
111
169
|
|
|
170
|
+
rows_per_image = data_layout[:rows_per_image] || height
|
|
171
|
+
buffer_size = bytes_per_row * rows_per_image * depth
|
|
172
|
+
|
|
173
|
+
owns_staging = staging.nil?
|
|
174
|
+
staging ||= Buffer.new(device, size: buffer_size, usage: [:map_read, :copy_dst])
|
|
175
|
+
validate_readback_staging!(staging, buffer_size)
|
|
112
176
|
encoder = CommandEncoder.new(device)
|
|
113
177
|
encoder.copy_texture_to_buffer(
|
|
114
178
|
source: source,
|
|
@@ -123,24 +187,41 @@ module WGPU
|
|
|
123
187
|
command_buffer = encoder.finish
|
|
124
188
|
submit([command_buffer])
|
|
125
189
|
|
|
190
|
+
map_requested = true
|
|
126
191
|
staging.map_sync(:read)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
192
|
+
staging.read_mapped_data(size: buffer_size)
|
|
193
|
+
ensure
|
|
194
|
+
cleanup_readback_resources(
|
|
195
|
+
staging:,
|
|
196
|
+
unmap_staging: map_requested,
|
|
197
|
+
owns_staging:,
|
|
198
|
+
command_buffer:,
|
|
199
|
+
encoder:,
|
|
200
|
+
active_error: $!
|
|
201
|
+
)
|
|
132
202
|
end
|
|
133
203
|
|
|
134
|
-
|
|
204
|
+
# Waits until all previously submitted queue work completes.
|
|
205
|
+
# @param device [Device, nil] device used to drive callback progress
|
|
206
|
+
# @param timeout [Numeric, nil] maximum wait time in seconds
|
|
207
|
+
# @return [Symbol] native completion status
|
|
208
|
+
def on_submitted_work_done(device: nil, timeout: nil)
|
|
135
209
|
device ||= @device
|
|
136
210
|
instance = device&.adapter&.instance
|
|
137
211
|
status_holder = { done: false, status: nil }
|
|
138
212
|
|
|
213
|
+
callback_lifetime_release = device_callback_lifetime_lease
|
|
214
|
+
callback_token = nil
|
|
139
215
|
callback = FFI::Function.new(
|
|
140
216
|
:void, [:uint32, :pointer, :pointer]
|
|
141
217
|
) do |status, _userdata1, _userdata2|
|
|
142
|
-
|
|
143
|
-
|
|
218
|
+
begin
|
|
219
|
+
status_holder[:status] = Native::QueueWorkDoneStatus[status]
|
|
220
|
+
status_holder[:done] = true
|
|
221
|
+
ensure
|
|
222
|
+
CallbackKeepalive.release(self, callback_token)
|
|
223
|
+
callback_lifetime_release.call
|
|
224
|
+
end
|
|
144
225
|
end
|
|
145
226
|
|
|
146
227
|
callback_info = Native::QueueWorkDoneCallbackInfo.new
|
|
@@ -150,18 +231,38 @@ module WGPU
|
|
|
150
231
|
callback_info[:userdata1] = nil
|
|
151
232
|
callback_info[:userdata2] = nil
|
|
152
233
|
|
|
153
|
-
|
|
154
|
-
|
|
234
|
+
callback_token = CallbackKeepalive.retain(self, callback)
|
|
235
|
+
future =
|
|
236
|
+
begin
|
|
237
|
+
Native.wgpuQueueOnSubmittedWorkDone(@handle, callback_info)
|
|
238
|
+
rescue StandardError
|
|
239
|
+
CallbackKeepalive.release(self, callback_token)
|
|
240
|
+
callback_lifetime_release.call
|
|
241
|
+
raise
|
|
242
|
+
end
|
|
243
|
+
AsyncWaiter.wait(
|
|
244
|
+
status_holder: status_holder,
|
|
245
|
+
instance: instance,
|
|
246
|
+
device: device,
|
|
247
|
+
future: future,
|
|
248
|
+
timeout: timeout
|
|
249
|
+
)
|
|
155
250
|
|
|
156
251
|
status_holder[:status]
|
|
157
252
|
end
|
|
158
253
|
|
|
159
|
-
|
|
254
|
+
# Waits for prior queue work on a background task.
|
|
255
|
+
# @return [AsyncTask] task yielding the native completion status
|
|
256
|
+
def on_submitted_work_done_async(device: nil, timeout: nil)
|
|
160
257
|
AsyncTask.new do
|
|
161
|
-
on_submitted_work_done(device: device)
|
|
258
|
+
on_submitted_work_done(device: device, timeout: timeout)
|
|
162
259
|
end
|
|
163
260
|
end
|
|
164
261
|
|
|
262
|
+
# Releases the native queue handle.
|
|
263
|
+
#
|
|
264
|
+
# Calling this method more than once has no effect.
|
|
265
|
+
# @return [void]
|
|
165
266
|
def release
|
|
166
267
|
return if @handle.null?
|
|
167
268
|
Native.wgpuQueueRelease(@handle)
|
|
@@ -170,21 +271,66 @@ module WGPU
|
|
|
170
271
|
|
|
171
272
|
private
|
|
172
273
|
|
|
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]
|
|
274
|
+
def texture_extent(size)
|
|
275
|
+
if size.is_a?(Array)
|
|
276
|
+
[size.fetch(0), size[1] || 1, size[2] || 1]
|
|
185
277
|
else
|
|
186
|
-
|
|
278
|
+
[size.fetch(:width), size[:height] || 1, size[:depth_or_array_layers] || 1]
|
|
187
279
|
end
|
|
188
280
|
end
|
|
281
|
+
|
|
282
|
+
def validate_readback_staging!(staging, required_size)
|
|
283
|
+
if staging.size < required_size
|
|
284
|
+
raise ArgumentError,
|
|
285
|
+
"staging buffer size must be at least #{required_size} bytes (got #{staging.size})"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
required_usage = Native::BufferUsage.fetch(:map_read) | Native::BufferUsage.fetch(:copy_dst)
|
|
289
|
+
unless (staging.usage & required_usage) == required_usage
|
|
290
|
+
raise ArgumentError, "staging buffer usage must include :map_read and :copy_dst"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
return if staging.map_state == :unmapped
|
|
294
|
+
|
|
295
|
+
raise ArgumentError, "staging buffer must be unmapped before readback"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def cleanup_readback_resources(
|
|
299
|
+
staging:,
|
|
300
|
+
unmap_staging:,
|
|
301
|
+
owns_staging:,
|
|
302
|
+
command_buffer:,
|
|
303
|
+
encoder:,
|
|
304
|
+
active_error:
|
|
305
|
+
)
|
|
306
|
+
cleanup_error = nil
|
|
307
|
+
|
|
308
|
+
begin
|
|
309
|
+
staging&.unmap if unmap_staging && staging && staging.map_state == :mapped
|
|
310
|
+
rescue StandardError => e
|
|
311
|
+
cleanup_error ||= e
|
|
312
|
+
ensure
|
|
313
|
+
begin
|
|
314
|
+
command_buffer&.release
|
|
315
|
+
rescue StandardError => e
|
|
316
|
+
cleanup_error ||= e
|
|
317
|
+
ensure
|
|
318
|
+
begin
|
|
319
|
+
encoder&.release
|
|
320
|
+
rescue StandardError => e
|
|
321
|
+
cleanup_error ||= e
|
|
322
|
+
ensure
|
|
323
|
+
begin
|
|
324
|
+
staging&.release if owns_staging
|
|
325
|
+
rescue StandardError => e
|
|
326
|
+
cleanup_error ||= e
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
raise cleanup_error if cleanup_error && active_error.nil?
|
|
333
|
+
end
|
|
334
|
+
|
|
189
335
|
end
|
|
190
336
|
end
|
data/lib/wgpu/core/surface.rb
CHANGED
|
@@ -4,6 +4,9 @@ module WGPU
|
|
|
4
4
|
class Surface
|
|
5
5
|
attr_reader :handle
|
|
6
6
|
|
|
7
|
+
# Creates a surface backed by a Core Animation Metal layer.
|
|
8
|
+
# @return [Surface]
|
|
9
|
+
# @raise [SurfaceError] if native surface creation fails
|
|
7
10
|
def self.from_metal_layer(instance, layer)
|
|
8
11
|
source = Native::SurfaceSourceMetalLayer.new
|
|
9
12
|
source[:chain][:next] = nil
|
|
@@ -21,6 +24,9 @@ module WGPU
|
|
|
21
24
|
new(handle, instance)
|
|
22
25
|
end
|
|
23
26
|
|
|
27
|
+
# Creates a surface backed by a Windows window handle.
|
|
28
|
+
# @return [Surface]
|
|
29
|
+
# @raise [SurfaceError] if native surface creation fails
|
|
24
30
|
def self.from_windows_hwnd(instance, hinstance, hwnd)
|
|
25
31
|
source = Native::SurfaceSourceWindowsHWND.new
|
|
26
32
|
source[:chain][:next] = nil
|
|
@@ -39,6 +45,9 @@ module WGPU
|
|
|
39
45
|
new(handle, instance)
|
|
40
46
|
end
|
|
41
47
|
|
|
48
|
+
# Creates a surface backed by an Xlib window.
|
|
49
|
+
# @return [Surface]
|
|
50
|
+
# @raise [SurfaceError] if native surface creation fails
|
|
42
51
|
def self.from_xlib_window(instance, display, window)
|
|
43
52
|
source = Native::SurfaceSourceXlibWindow.new
|
|
44
53
|
source[:chain][:next] = nil
|
|
@@ -57,6 +66,9 @@ module WGPU
|
|
|
57
66
|
new(handle, instance)
|
|
58
67
|
end
|
|
59
68
|
|
|
69
|
+
# Creates a surface backed by a Wayland surface.
|
|
70
|
+
# @return [Surface]
|
|
71
|
+
# @raise [SurfaceError] if native surface creation fails
|
|
60
72
|
def self.from_wayland_surface(instance, display, surface)
|
|
61
73
|
source = Native::SurfaceSourceWaylandSurface.new
|
|
62
74
|
source[:chain][:next] = nil
|
|
@@ -75,6 +87,9 @@ module WGPU
|
|
|
75
87
|
new(handle, instance)
|
|
76
88
|
end
|
|
77
89
|
|
|
90
|
+
# Wraps a native presentation surface.
|
|
91
|
+
# @param handle [FFI::Pointer] native surface handle
|
|
92
|
+
# @param instance [Instance] owning instance
|
|
78
93
|
def initialize(handle, instance)
|
|
79
94
|
@handle = handle
|
|
80
95
|
@instance = instance
|
|
@@ -82,11 +97,13 @@ module WGPU
|
|
|
82
97
|
@config = nil
|
|
83
98
|
end
|
|
84
99
|
|
|
100
|
+
# Configures this surface for presentation by a device.
|
|
101
|
+
# @return [Hash] stored configuration
|
|
85
102
|
def configure(device:, format:, usage: :render_attachment, width:, height:, present_mode: :fifo, alpha_mode: :auto, view_formats: [])
|
|
86
103
|
config = Native::SurfaceConfiguration.new
|
|
87
104
|
config[:next_in_chain] = nil
|
|
88
105
|
config[:device] = device.handle
|
|
89
|
-
config[:format] = format
|
|
106
|
+
config[:format] = Native::EnumHelper.coerce(Native::TextureFormat, format, name: "surface format")
|
|
90
107
|
config[:usage] = normalize_usage(usage)
|
|
91
108
|
config[:width] = width
|
|
92
109
|
config[:height] = height
|
|
@@ -95,19 +112,29 @@ module WGPU
|
|
|
95
112
|
@view_formats_ptr = nil
|
|
96
113
|
config[:view_formats] = nil
|
|
97
114
|
else
|
|
98
|
-
format_values = view_formats.map do |
|
|
99
|
-
|
|
115
|
+
format_values = view_formats.map do |view_format|
|
|
116
|
+
Native::EnumHelper.coerce(Native::TextureFormat, view_format, name: "view format")
|
|
100
117
|
end
|
|
101
118
|
@view_formats_ptr = FFI::MemoryPointer.new(:uint32, format_values.size)
|
|
102
119
|
@view_formats_ptr.write_array_of_uint32(format_values)
|
|
103
120
|
config[:view_formats] = @view_formats_ptr
|
|
104
121
|
end
|
|
105
|
-
config[:alpha_mode] = Native::
|
|
106
|
-
|
|
122
|
+
config[:alpha_mode] = Native::EnumHelper.coerce(
|
|
123
|
+
Native::CompositeAlphaMode,
|
|
124
|
+
alpha_mode,
|
|
125
|
+
name: "alpha mode"
|
|
126
|
+
)
|
|
127
|
+
config[:present_mode] = Native::EnumHelper.coerce(
|
|
128
|
+
Native::PresentMode,
|
|
129
|
+
present_mode,
|
|
130
|
+
name: "present mode"
|
|
131
|
+
)
|
|
107
132
|
|
|
108
133
|
Native.wgpuSurfaceConfigure(@handle, config)
|
|
134
|
+
release_device_callback_lifetime
|
|
109
135
|
@configured = true
|
|
110
136
|
@device = device
|
|
137
|
+
attach_device_callback_lifetime(device)
|
|
111
138
|
@config = {
|
|
112
139
|
device: device,
|
|
113
140
|
format: format,
|
|
@@ -120,12 +147,20 @@ module WGPU
|
|
|
120
147
|
}
|
|
121
148
|
end
|
|
122
149
|
|
|
150
|
+
# Removes the current surface configuration.
|
|
151
|
+
# @return [void]
|
|
123
152
|
def unconfigure
|
|
124
153
|
Native.wgpuSurfaceUnconfigure(@handle)
|
|
154
|
+
release_device_callback_lifetime
|
|
125
155
|
@configured = false
|
|
156
|
+
@device = nil
|
|
126
157
|
@config = nil
|
|
127
158
|
end
|
|
128
159
|
|
|
160
|
+
# Acquires the current surface texture.
|
|
161
|
+
# @return [Texture]
|
|
162
|
+
# @raise [SurfaceError] if unconfigured or no texture is returned
|
|
163
|
+
# @raise [SurfaceAcquisitionError] if the surface status is unsuccessful
|
|
129
164
|
def current_texture
|
|
130
165
|
raise SurfaceError, "Surface is not configured" unless @configured
|
|
131
166
|
|
|
@@ -134,7 +169,7 @@ module WGPU
|
|
|
134
169
|
|
|
135
170
|
status = Native::SurfaceGetCurrentTextureStatus[surface_texture[:status]]
|
|
136
171
|
unless status == :success_optimal || status == :success_suboptimal
|
|
137
|
-
raise
|
|
172
|
+
raise SurfaceAcquisitionError.new(status)
|
|
138
173
|
end
|
|
139
174
|
|
|
140
175
|
texture_ptr = surface_texture[:texture]
|
|
@@ -142,26 +177,43 @@ module WGPU
|
|
|
142
177
|
raise SurfaceError, "Surface returned null texture"
|
|
143
178
|
end
|
|
144
179
|
|
|
145
|
-
Texture.from_handle(texture_ptr)
|
|
180
|
+
Texture.from_handle(texture_ptr, surface_status: status, device: @device)
|
|
146
181
|
end
|
|
147
182
|
|
|
183
|
+
# Acquires the texture for the current presentation frame.
|
|
184
|
+
#
|
|
185
|
+
# @return [Texture] acquired surface texture
|
|
186
|
+
# @raise [SurfaceError] if the surface is not configured
|
|
187
|
+
# @raise [SurfaceAcquisitionError] if acquisition fails
|
|
148
188
|
def get_current_texture
|
|
149
189
|
current_texture
|
|
150
190
|
end
|
|
151
191
|
|
|
192
|
+
# Presents the current surface texture.
|
|
193
|
+
# @return [void]
|
|
152
194
|
def present
|
|
153
195
|
Native.wgpuSurfacePresent(@handle)
|
|
154
196
|
end
|
|
155
197
|
|
|
198
|
+
# Returns the most recent surface configuration.
|
|
199
|
+
#
|
|
200
|
+
# @return [Hash, nil] configuration options, or +nil+ when unconfigured
|
|
156
201
|
def get_configuration
|
|
157
202
|
@config
|
|
158
203
|
end
|
|
159
204
|
|
|
205
|
+
# Returns the first surface format supported by an adapter.
|
|
206
|
+
#
|
|
207
|
+
# @param adapter [Adapter] adapter used to query surface capabilities
|
|
208
|
+
# @return [Symbol] preferred texture format
|
|
160
209
|
def get_preferred_format(adapter)
|
|
161
210
|
caps = capabilities(adapter)
|
|
162
211
|
caps[:formats].first || :bgra8_unorm
|
|
163
212
|
end
|
|
164
213
|
|
|
214
|
+
# Returns the formats, presentation modes, alpha modes, and usages supported by an adapter.
|
|
215
|
+
# @param adapter [Adapter] adapter to query
|
|
216
|
+
# @return [Hash]
|
|
165
217
|
def capabilities(adapter)
|
|
166
218
|
caps = Native::SurfaceCapabilities.new
|
|
167
219
|
Native.wgpuSurfaceGetCapabilities(@handle, adapter.handle, caps)
|
|
@@ -197,6 +249,10 @@ module WGPU
|
|
|
197
249
|
Native.wgpuSurfaceCapabilitiesFreeMembers(caps) if caps
|
|
198
250
|
end
|
|
199
251
|
|
|
252
|
+
# Releases the native surface handle.
|
|
253
|
+
#
|
|
254
|
+
# Calling this method more than once has no effect.
|
|
255
|
+
# @return [void]
|
|
200
256
|
def release
|
|
201
257
|
return if @handle.null?
|
|
202
258
|
Native.wgpuSurfaceRelease(@handle)
|
|
@@ -206,16 +262,7 @@ module WGPU
|
|
|
206
262
|
private
|
|
207
263
|
|
|
208
264
|
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
|
|
265
|
+
Native::EnumHelper.coerce_flags(Native::TextureUsage, usage, name: "surface usage")
|
|
219
266
|
end
|
|
220
267
|
end
|
|
221
268
|
end
|