wgpu 1.2.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 +40 -0
- data/README.md +18 -2
- data/docs/README.md +2 -2
- data/docs/api_coverage.md +23 -15
- data/docs/async.md +10 -0
- data/docs/command_encoding.md +25 -0
- data/docs/getting_started_compute.md +1 -0
- data/docs/pipeline_descriptors.md +7 -2
- data/docs/releasing.md +11 -1
- data/docs/resource_lifetime.md +16 -2
- data/docs/texture_readback.md +27 -0
- data/docs/troubleshooting.md +5 -0
- data/docs/upgrading_wgpu_native.md +16 -5
- data/lib/wgpu/async_task.rb +19 -0
- data/lib/wgpu/commands/command_buffer.rb +14 -1
- data/lib/wgpu/commands/command_encoder.rb +58 -1
- data/lib/wgpu/commands/compute_pass.rb +43 -0
- data/lib/wgpu/commands/render_bundle.rb +9 -1
- data/lib/wgpu/commands/render_bundle_encoder.rb +55 -1
- data/lib/wgpu/commands/render_pass.rb +102 -0
- data/lib/wgpu/core/adapter.rb +91 -12
- data/lib/wgpu/core/async_waiter.rb +15 -0
- data/lib/wgpu/core/canvas_context.rb +32 -0
- data/lib/wgpu/core/device.rb +296 -46
- data/lib/wgpu/core/instance.rb +20 -0
- data/lib/wgpu/core/queue.rb +139 -24
- data/lib/wgpu/core/surface.rb +49 -1
- data/lib/wgpu/data_types.rb +16 -0
- data/lib/wgpu/descriptor_helpers.rb +65 -0
- data/lib/wgpu/error.rb +15 -0
- data/lib/wgpu/native/abi_verifier.rb +37 -3
- data/lib/wgpu/native/callbacks.rb +6 -0
- data/lib/wgpu/native/capabilities.rb +16 -2
- data/lib/wgpu/native/distribution.rb +63 -0
- data/lib/wgpu/native/enum_helper.rb +17 -0
- data/lib/wgpu/native/enums.rb +8 -0
- data/lib/wgpu/native/fixtures/webgpu-v27.0.4.0-enums.h +848 -0
- data/lib/wgpu/native/functions.rb +11 -0
- data/lib/wgpu/native/installer.rb +48 -17
- data/lib/wgpu/native/loader.rb +22 -0
- data/lib/wgpu/native/structs.rb +18 -1
- data/lib/wgpu/native_resource.rb +174 -3
- data/lib/wgpu/pipeline/bind_group.rb +9 -0
- data/lib/wgpu/pipeline/bind_group_layout.rb +17 -4
- data/lib/wgpu/pipeline/compute_pipeline.rb +20 -32
- data/lib/wgpu/pipeline/pipeline_layout.rb +8 -0
- data/lib/wgpu/pipeline/render_pipeline.rb +27 -42
- data/lib/wgpu/pipeline/shader_module.rb +54 -29
- data/lib/wgpu/resources/buffer.rb +258 -31
- data/lib/wgpu/resources/query_set.rb +12 -0
- data/lib/wgpu/resources/sampler.rb +7 -0
- data/lib/wgpu/resources/texture.rb +45 -6
- data/lib/wgpu/resources/texture_view.rb +24 -4
- data/lib/wgpu/texture_format.rb +14 -0
- data/lib/wgpu/version.rb +1 -1
- data/lib/wgpu/window.rb +26 -0
- data/sig/wgpu.rbs +84 -5
- metadata +3 -1
data/lib/wgpu/core/queue.rb
CHANGED
|
@@ -4,11 +4,18 @@ 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?
|
|
@@ -28,6 +35,11 @@ module WGPU
|
|
|
28
35
|
buffers.each(&:mark_submitted!)
|
|
29
36
|
end
|
|
30
37
|
|
|
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]
|
|
31
43
|
def write_buffer(buffer, buffer_offset, data, data_offset: 0, size: nil, type: :f32)
|
|
32
44
|
data_ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
33
45
|
DataTypes.validate_alignment!(buffer_offset, 4, name: "buffer_offset")
|
|
@@ -49,6 +61,12 @@ module WGPU
|
|
|
49
61
|
)
|
|
50
62
|
end
|
|
51
63
|
|
|
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]
|
|
52
70
|
def write_texture(destination:, data:, data_layout:, size:, type: :f32)
|
|
53
71
|
data_ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
54
72
|
|
|
@@ -83,6 +101,10 @@ module WGPU
|
|
|
83
101
|
Native.wgpuQueueWriteTexture(@handle, dst, data_ptr, byte_size, layout, extent)
|
|
84
102
|
end
|
|
85
103
|
|
|
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
|
|
86
108
|
def read_buffer(buffer, offset: 0, size: nil, device: nil, staging: nil)
|
|
87
109
|
device ||= @device
|
|
88
110
|
raise ArgumentError, "device is required when the queue has no owning device" unless device
|
|
@@ -90,6 +112,7 @@ module WGPU
|
|
|
90
112
|
size ||= buffer.size - offset
|
|
91
113
|
owns_staging = staging.nil?
|
|
92
114
|
staging ||= Buffer.new(device, size: size, usage: [:map_read, :copy_dst])
|
|
115
|
+
validate_readback_staging!(staging, size)
|
|
93
116
|
encoder = CommandEncoder.new(device)
|
|
94
117
|
encoder.copy_buffer_to_buffer(
|
|
95
118
|
source: buffer,
|
|
@@ -101,15 +124,26 @@ module WGPU
|
|
|
101
124
|
command_buffer = encoder.finish
|
|
102
125
|
submit([command_buffer])
|
|
103
126
|
|
|
127
|
+
map_requested = true
|
|
104
128
|
staging.map_sync(:read)
|
|
105
129
|
staging.read_mapped_data(size:)
|
|
106
130
|
ensure
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
131
|
+
cleanup_readback_resources(
|
|
132
|
+
staging:,
|
|
133
|
+
unmap_staging: map_requested,
|
|
134
|
+
owns_staging:,
|
|
135
|
+
command_buffer:,
|
|
136
|
+
encoder:,
|
|
137
|
+
active_error: $!
|
|
138
|
+
)
|
|
111
139
|
end
|
|
112
140
|
|
|
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
|
|
113
147
|
def read_texture(source:, data_layout:, size:, device: nil, staging: nil)
|
|
114
148
|
device ||= @device
|
|
115
149
|
raise ArgumentError, "device is required when the queue has no owning device" unless device
|
|
@@ -125,10 +159,12 @@ module WGPU
|
|
|
125
159
|
)
|
|
126
160
|
format = source[:format] || source[:texture].format
|
|
127
161
|
aspect = source[:aspect] || :all
|
|
128
|
-
|
|
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:)
|
|
129
164
|
if bytes_per_row < minimum_bytes_per_row
|
|
130
165
|
raise ArgumentError,
|
|
131
|
-
"bytes_per_row must be at least #{minimum_bytes_per_row} for width #{width} and #{format.inspect}"
|
|
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)"
|
|
132
168
|
end
|
|
133
169
|
|
|
134
170
|
rows_per_image = data_layout[:rows_per_image] || height
|
|
@@ -136,6 +172,7 @@ module WGPU
|
|
|
136
172
|
|
|
137
173
|
owns_staging = staging.nil?
|
|
138
174
|
staging ||= Buffer.new(device, size: buffer_size, usage: [:map_read, :copy_dst])
|
|
175
|
+
validate_readback_staging!(staging, buffer_size)
|
|
139
176
|
encoder = CommandEncoder.new(device)
|
|
140
177
|
encoder.copy_texture_to_buffer(
|
|
141
178
|
source: source,
|
|
@@ -150,25 +187,41 @@ module WGPU
|
|
|
150
187
|
command_buffer = encoder.finish
|
|
151
188
|
submit([command_buffer])
|
|
152
189
|
|
|
190
|
+
map_requested = true
|
|
153
191
|
staging.map_sync(:read)
|
|
154
192
|
staging.read_mapped_data(size: buffer_size)
|
|
155
193
|
ensure
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
194
|
+
cleanup_readback_resources(
|
|
195
|
+
staging:,
|
|
196
|
+
unmap_staging: map_requested,
|
|
197
|
+
owns_staging:,
|
|
198
|
+
command_buffer:,
|
|
199
|
+
encoder:,
|
|
200
|
+
active_error: $!
|
|
201
|
+
)
|
|
160
202
|
end
|
|
161
203
|
|
|
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
|
|
162
208
|
def on_submitted_work_done(device: nil, timeout: nil)
|
|
163
209
|
device ||= @device
|
|
164
210
|
instance = device&.adapter&.instance
|
|
165
211
|
status_holder = { done: false, status: nil }
|
|
166
212
|
|
|
213
|
+
callback_lifetime_release = device_callback_lifetime_lease
|
|
214
|
+
callback_token = nil
|
|
167
215
|
callback = FFI::Function.new(
|
|
168
216
|
:void, [:uint32, :pointer, :pointer]
|
|
169
217
|
) do |status, _userdata1, _userdata2|
|
|
170
|
-
|
|
171
|
-
|
|
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
|
|
172
225
|
end
|
|
173
226
|
|
|
174
227
|
callback_info = Native::QueueWorkDoneCallbackInfo.new
|
|
@@ -179,28 +232,37 @@ module WGPU
|
|
|
179
232
|
callback_info[:userdata2] = nil
|
|
180
233
|
|
|
181
234
|
callback_token = CallbackKeepalive.retain(self, callback)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
+
)
|
|
194
250
|
|
|
195
251
|
status_holder[:status]
|
|
196
252
|
end
|
|
197
253
|
|
|
254
|
+
# Waits for prior queue work on a background task.
|
|
255
|
+
# @return [AsyncTask] task yielding the native completion status
|
|
198
256
|
def on_submitted_work_done_async(device: nil, timeout: nil)
|
|
199
257
|
AsyncTask.new do
|
|
200
258
|
on_submitted_work_done(device: device, timeout: timeout)
|
|
201
259
|
end
|
|
202
260
|
end
|
|
203
261
|
|
|
262
|
+
# Releases the native queue handle.
|
|
263
|
+
#
|
|
264
|
+
# Calling this method more than once has no effect.
|
|
265
|
+
# @return [void]
|
|
204
266
|
def release
|
|
205
267
|
return if @handle.null?
|
|
206
268
|
Native.wgpuQueueRelease(@handle)
|
|
@@ -217,5 +279,58 @@ module WGPU
|
|
|
217
279
|
end
|
|
218
280
|
end
|
|
219
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
|
+
|
|
220
335
|
end
|
|
221
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,6 +97,8 @@ 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
|
|
@@ -114,8 +131,10 @@ module WGPU
|
|
|
114
131
|
)
|
|
115
132
|
|
|
116
133
|
Native.wgpuSurfaceConfigure(@handle, config)
|
|
134
|
+
release_device_callback_lifetime
|
|
117
135
|
@configured = true
|
|
118
136
|
@device = device
|
|
137
|
+
attach_device_callback_lifetime(device)
|
|
119
138
|
@config = {
|
|
120
139
|
device: device,
|
|
121
140
|
format: format,
|
|
@@ -128,12 +147,20 @@ module WGPU
|
|
|
128
147
|
}
|
|
129
148
|
end
|
|
130
149
|
|
|
150
|
+
# Removes the current surface configuration.
|
|
151
|
+
# @return [void]
|
|
131
152
|
def unconfigure
|
|
132
153
|
Native.wgpuSurfaceUnconfigure(@handle)
|
|
154
|
+
release_device_callback_lifetime
|
|
133
155
|
@configured = false
|
|
156
|
+
@device = nil
|
|
134
157
|
@config = nil
|
|
135
158
|
end
|
|
136
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
|
|
137
164
|
def current_texture
|
|
138
165
|
raise SurfaceError, "Surface is not configured" unless @configured
|
|
139
166
|
|
|
@@ -150,26 +177,43 @@ module WGPU
|
|
|
150
177
|
raise SurfaceError, "Surface returned null texture"
|
|
151
178
|
end
|
|
152
179
|
|
|
153
|
-
Texture.from_handle(texture_ptr, surface_status: status)
|
|
180
|
+
Texture.from_handle(texture_ptr, surface_status: status, device: @device)
|
|
154
181
|
end
|
|
155
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
|
|
156
188
|
def get_current_texture
|
|
157
189
|
current_texture
|
|
158
190
|
end
|
|
159
191
|
|
|
192
|
+
# Presents the current surface texture.
|
|
193
|
+
# @return [void]
|
|
160
194
|
def present
|
|
161
195
|
Native.wgpuSurfacePresent(@handle)
|
|
162
196
|
end
|
|
163
197
|
|
|
198
|
+
# Returns the most recent surface configuration.
|
|
199
|
+
#
|
|
200
|
+
# @return [Hash, nil] configuration options, or +nil+ when unconfigured
|
|
164
201
|
def get_configuration
|
|
165
202
|
@config
|
|
166
203
|
end
|
|
167
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
|
|
168
209
|
def get_preferred_format(adapter)
|
|
169
210
|
caps = capabilities(adapter)
|
|
170
211
|
caps[:formats].first || :bgra8_unorm
|
|
171
212
|
end
|
|
172
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]
|
|
173
217
|
def capabilities(adapter)
|
|
174
218
|
caps = Native::SurfaceCapabilities.new
|
|
175
219
|
Native.wgpuSurfaceGetCapabilities(@handle, adapter.handle, caps)
|
|
@@ -205,6 +249,10 @@ module WGPU
|
|
|
205
249
|
Native.wgpuSurfaceCapabilitiesFreeMembers(caps) if caps
|
|
206
250
|
end
|
|
207
251
|
|
|
252
|
+
# Releases the native surface handle.
|
|
253
|
+
#
|
|
254
|
+
# Calling this method more than once has no effect.
|
|
255
|
+
# @return [void]
|
|
208
256
|
def release
|
|
209
257
|
return if @handle.null?
|
|
210
258
|
Native.wgpuSurfaceRelease(@handle)
|
data/lib/wgpu/data_types.rb
CHANGED
|
@@ -13,6 +13,10 @@ module WGPU
|
|
|
13
13
|
|
|
14
14
|
module_function
|
|
15
15
|
|
|
16
|
+
# Encodes Ruby values in little-endian native transfer format.
|
|
17
|
+
# @param values [Array] values to encode
|
|
18
|
+
# @param type [Symbol] element type
|
|
19
|
+
# @return [String] encoded bytes
|
|
16
20
|
def pack(values, type: :f32)
|
|
17
21
|
format, = format_for(type)
|
|
18
22
|
Array(values).pack(format)
|
|
@@ -20,6 +24,10 @@ module WGPU
|
|
|
20
24
|
raise ArgumentError, "value is out of range for #{type}: #{e.message}"
|
|
21
25
|
end
|
|
22
26
|
|
|
27
|
+
# Decodes little-endian transfer bytes into Ruby values.
|
|
28
|
+
# @param bytes [String] encoded bytes
|
|
29
|
+
# @param type [Symbol] element type
|
|
30
|
+
# @return [Array]
|
|
23
31
|
def unpack(bytes, type: :f32)
|
|
24
32
|
format, byte_size = format_for(type)
|
|
25
33
|
unless (bytes.bytesize % byte_size).zero?
|
|
@@ -29,10 +37,15 @@ module WGPU
|
|
|
29
37
|
bytes.unpack(format)
|
|
30
38
|
end
|
|
31
39
|
|
|
40
|
+
# Returns the byte width of one typed value.
|
|
41
|
+
# @param type [Symbol] element type
|
|
42
|
+
# @return [Integer]
|
|
32
43
|
def byte_size(type)
|
|
33
44
|
format_for(type).last
|
|
34
45
|
end
|
|
35
46
|
|
|
47
|
+
# Converts data to an FFI pointer and reports its byte length.
|
|
48
|
+
# @return [Array(FFI::Pointer, Integer)] pointer and byte length
|
|
36
49
|
def to_pointer(data, type: :f32)
|
|
37
50
|
return [data, pointer_size(data)] if data.is_a?(FFI::Pointer)
|
|
38
51
|
|
|
@@ -42,6 +55,9 @@ module WGPU
|
|
|
42
55
|
[pointer, bytes.bytesize]
|
|
43
56
|
end
|
|
44
57
|
|
|
58
|
+
# Validates a non-negative aligned byte value.
|
|
59
|
+
# @return [Integer] normalized value
|
|
60
|
+
# @raise [ArgumentError] if negative or misaligned
|
|
45
61
|
def validate_alignment!(value, alignment, name:)
|
|
46
62
|
integer = Integer(value)
|
|
47
63
|
raise ArgumentError, "#{name} must be non-negative" if integer.negative?
|
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module WGPU
|
|
4
4
|
module DescriptorHelpers
|
|
5
|
+
SIZE_MAX = (1 << (FFI.type_size(:size_t) * 8)) - 1
|
|
6
|
+
|
|
5
7
|
module_function
|
|
6
8
|
|
|
9
|
+
# Writes an optional Ruby label into a native descriptor.
|
|
10
|
+
#
|
|
11
|
+
# @param descriptor [FFI::Struct] descriptor with a +label+ member
|
|
12
|
+
# @param label [String, nil] label text
|
|
13
|
+
# @param keepalive [Array] receives allocated pointers that must remain alive
|
|
14
|
+
# @return [void]
|
|
7
15
|
def set_label(descriptor, label, keepalive:)
|
|
8
16
|
if label
|
|
9
17
|
pointer = FFI::MemoryPointer.from_string(label)
|
|
@@ -16,6 +24,11 @@ module WGPU
|
|
|
16
24
|
end
|
|
17
25
|
end
|
|
18
26
|
|
|
27
|
+
# Allocates and fills a native uint32 array.
|
|
28
|
+
#
|
|
29
|
+
# @param values [Array<Integer>] values to copy
|
|
30
|
+
# @param keepalive [Array] receives the allocated pointer
|
|
31
|
+
# @return [FFI::MemoryPointer, nil] pointer, or +nil+ for an empty array
|
|
19
32
|
def uint32_array(values, keepalive:)
|
|
20
33
|
return nil if values.empty?
|
|
21
34
|
|
|
@@ -25,6 +38,58 @@ module WGPU
|
|
|
25
38
|
pointer
|
|
26
39
|
end
|
|
27
40
|
|
|
41
|
+
# Writes a nullable string into a native string view.
|
|
42
|
+
#
|
|
43
|
+
# @param string_view [Native::StringView] destination view
|
|
44
|
+
# @param value [String, nil] string value; +nil+ uses the native null sentinel
|
|
45
|
+
# @param keepalive [Array] receives allocated pointers that must remain alive
|
|
46
|
+
# @return [void]
|
|
47
|
+
def set_nullable_string_view(string_view, value, keepalive:)
|
|
48
|
+
if value.nil?
|
|
49
|
+
string_view[:data] = nil
|
|
50
|
+
string_view[:length] = SIZE_MAX
|
|
51
|
+
return
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
string = value
|
|
55
|
+
pointer = FFI::MemoryPointer.from_string(string)
|
|
56
|
+
keepalive << pointer
|
|
57
|
+
string_view[:data] = pointer
|
|
58
|
+
string_view[:length] = string.bytesize
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Writes pipeline-overridable constants into a stage descriptor.
|
|
62
|
+
#
|
|
63
|
+
# @param stage_descriptor [FFI::Struct] programmable stage descriptor
|
|
64
|
+
# @param constants [Hash{#to_s => Numeric}, nil] constant names and values
|
|
65
|
+
# @param keepalive [Array] receives allocated pointers that must remain alive
|
|
66
|
+
# @return [void]
|
|
67
|
+
def set_constants(stage_descriptor, constants, keepalive:)
|
|
68
|
+
stage_descriptor[:constant_count] = 0
|
|
69
|
+
stage_descriptor[:constants] = nil
|
|
70
|
+
return if constants.nil? || constants.empty?
|
|
71
|
+
|
|
72
|
+
constants_pointer = FFI::MemoryPointer.new(Native::ConstantEntry, constants.size)
|
|
73
|
+
keepalive << constants_pointer
|
|
74
|
+
|
|
75
|
+
constants.each_with_index do |(key, value), index|
|
|
76
|
+
entry_pointer = constants_pointer + (index * Native::ConstantEntry.size)
|
|
77
|
+
entry = Native::ConstantEntry.new(entry_pointer)
|
|
78
|
+
entry[:next_in_chain] = nil
|
|
79
|
+
set_nullable_string_view(entry[:key], key.to_s, keepalive:)
|
|
80
|
+
entry[:value] = value.to_f
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
stage_descriptor[:constant_count] = constants.size
|
|
84
|
+
stage_descriptor[:constants] = constants_pointer
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Checks required keys and warns about unsupported descriptor keys.
|
|
88
|
+
# @param options [Hash] descriptor options
|
|
89
|
+
# @param allowed [Array<Symbol>] supported keys
|
|
90
|
+
# @param required [Array<Symbol>] mandatory keys
|
|
91
|
+
# @return [Hash] original options
|
|
92
|
+
# @raise [ArgumentError] when required keys are missing
|
|
28
93
|
def validate_keys!(options, allowed:, required: [], context: "descriptor")
|
|
29
94
|
return options unless options.is_a?(Hash)
|
|
30
95
|
|
data/lib/wgpu/error.rb
CHANGED
|
@@ -20,6 +20,9 @@ module WGPU
|
|
|
20
20
|
class SurfaceAcquisitionError < SurfaceError
|
|
21
21
|
attr_reader :status
|
|
22
22
|
|
|
23
|
+
# Creates an acquisition error for a native surface status.
|
|
24
|
+
# @param status [Symbol] native acquisition status
|
|
25
|
+
# @param message [String, nil] optional override message
|
|
23
26
|
def initialize(status, message = nil)
|
|
24
27
|
@status = status
|
|
25
28
|
super(message || "Failed to get current surface texture: #{status}")
|
|
@@ -28,12 +31,17 @@ module WGPU
|
|
|
28
31
|
class RenderBundleError < Error; end
|
|
29
32
|
|
|
30
33
|
GPUError = Data.define(:type, :message) do
|
|
34
|
+
# Converts a native error-scope result into a typed error.
|
|
35
|
+
# @param error [Hash, nil] native error result
|
|
36
|
+
# @return [GPUError, nil]
|
|
31
37
|
def self.from_hash(error)
|
|
32
38
|
return if error.nil? || error[:type].nil? || error[:type] == :no_error
|
|
33
39
|
|
|
34
40
|
new(type: error[:type], message: error[:message].to_s)
|
|
35
41
|
end
|
|
36
42
|
|
|
43
|
+
# Returns the Ruby exception class matching this GPU error type.
|
|
44
|
+
# @return [Class<Error>]
|
|
37
45
|
def exception_class
|
|
38
46
|
{
|
|
39
47
|
validation: ValidationError,
|
|
@@ -43,10 +51,15 @@ module WGPU
|
|
|
43
51
|
}.fetch(type, Error)
|
|
44
52
|
end
|
|
45
53
|
|
|
54
|
+
# Raises this error as its matching Ruby exception.
|
|
55
|
+
# @return [void]
|
|
56
|
+
# @raise [Error]
|
|
46
57
|
def raise!
|
|
47
58
|
raise exception_class, "GPU error (#{type}): #{message}"
|
|
48
59
|
end
|
|
49
60
|
|
|
61
|
+
# Returns a serializable representation of the error.
|
|
62
|
+
# @return [Hash]
|
|
50
63
|
def to_h
|
|
51
64
|
{ type:, message: }
|
|
52
65
|
end
|
|
@@ -63,6 +76,8 @@ module WGPU
|
|
|
63
76
|
alias line line_num
|
|
64
77
|
alias column line_pos
|
|
65
78
|
|
|
79
|
+
# Formats the diagnostic with source location, severity, and message.
|
|
80
|
+
# @return [String]
|
|
66
81
|
def to_s
|
|
67
82
|
location = line_num&.positive? ? "#{line_num}:#{line_pos}" : "unknown location"
|
|
68
83
|
"#{location}: #{type}: #{message}"
|
|
@@ -22,18 +22,29 @@ module WGPU
|
|
|
22
22
|
]
|
|
23
23
|
}.freeze
|
|
24
24
|
|
|
25
|
+
# Creates a verifier for a header and native binding.
|
|
26
|
+
# @param header_path [String, nil] header fixture to compare
|
|
27
|
+
# @param native [Module] native binding module
|
|
25
28
|
def initialize(header_path: nil, native: Native)
|
|
26
29
|
@header_path = header_path || self.class.default_header_path
|
|
27
30
|
@native = native
|
|
28
31
|
end
|
|
29
32
|
|
|
33
|
+
# Verifies the runtime version and Ruby enum ABI against the header.
|
|
34
|
+
# @return [true]
|
|
35
|
+
# @raise [WGPU::Error] when differences are detected
|
|
30
36
|
def verify!
|
|
31
37
|
differences = enum_differences
|
|
38
|
+
version_difference = native_version_difference
|
|
39
|
+
differences.unshift(version_difference) if version_difference
|
|
32
40
|
return true if differences.empty?
|
|
33
41
|
|
|
34
|
-
raise WGPU::Error, "wgpu-native ABI
|
|
42
|
+
raise WGPU::Error, "wgpu-native ABI differences:\n#{differences.join("\n")}"
|
|
35
43
|
end
|
|
36
44
|
|
|
45
|
+
# Compares Ruby enum values with the pinned native header.
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<String>] human-readable differences
|
|
37
48
|
def enum_differences
|
|
38
49
|
header_enums = parse_header
|
|
39
50
|
ruby_enums.filter_map do |name, mapping|
|
|
@@ -44,10 +55,16 @@ module WGPU
|
|
|
44
55
|
end
|
|
45
56
|
end
|
|
46
57
|
|
|
58
|
+
# Locates the pinned header used for ABI verification.
|
|
59
|
+
# @return [String] absolute header path
|
|
60
|
+
# @raise [WGPU::Error] if no header is available
|
|
47
61
|
def self.default_header_path
|
|
48
62
|
override = ENV["WGPU_HEADER_PATH"]
|
|
49
63
|
return File.expand_path(override) if override && !override.empty?
|
|
50
64
|
|
|
65
|
+
fixture = fixture_header_path
|
|
66
|
+
return fixture if File.file?(fixture)
|
|
67
|
+
|
|
51
68
|
candidates = Distribution.cache_directories.map do |cache_dir|
|
|
52
69
|
File.join(cache_dir, "include", "webgpu", "webgpu.h")
|
|
53
70
|
end
|
|
@@ -56,13 +73,30 @@ module WGPU
|
|
|
56
73
|
|
|
57
74
|
raise WGPU::Error, <<~MSG.chomp
|
|
58
75
|
Pinned #{Distribution::VERSION} webgpu.h was not found.
|
|
59
|
-
|
|
60
|
-
|
|
76
|
+
Restore the repository ABI fixture, run `bundle exec rake wgpu:install`,
|
|
77
|
+
or set WGPU_HEADER_PATH.
|
|
78
|
+
Searched: #{([fixture] + candidates).join(", ")}
|
|
61
79
|
MSG
|
|
62
80
|
end
|
|
63
81
|
|
|
82
|
+
# Returns the repository's pinned enum fixture path.
|
|
83
|
+
#
|
|
84
|
+
# @return [String] absolute header fixture path
|
|
85
|
+
def self.fixture_header_path
|
|
86
|
+
File.expand_path("fixtures/webgpu-#{Distribution::VERSION}-enums.h", __dir__)
|
|
87
|
+
end
|
|
88
|
+
|
|
64
89
|
private
|
|
65
90
|
|
|
91
|
+
def native_version_difference
|
|
92
|
+
actual = @native.wgpuGetVersion
|
|
93
|
+
expected = Distribution::ENCODED_VERSION
|
|
94
|
+
return if actual == expected
|
|
95
|
+
|
|
96
|
+
"version: runtime #{Distribution.version_string(actual)} (0x#{format("%08x", actual)}) " \
|
|
97
|
+
"does not match pinned #{Distribution::VERSION} (0x#{format("%08x", expected)})"
|
|
98
|
+
end
|
|
99
|
+
|
|
66
100
|
def parse_header
|
|
67
101
|
source = File.read(@header_path)
|
|
68
102
|
source.scan(
|
|
@@ -8,6 +8,12 @@ module WGPU
|
|
|
8
8
|
callback :request_device_callback,
|
|
9
9
|
[RequestDeviceStatus, :pointer, StringView.by_value, :pointer, :pointer], :void
|
|
10
10
|
|
|
11
|
+
callback :create_compute_pipeline_async_callback,
|
|
12
|
+
[CreatePipelineAsyncStatus, :pointer, StringView.by_value, :pointer, :pointer], :void
|
|
13
|
+
|
|
14
|
+
callback :create_render_pipeline_async_callback,
|
|
15
|
+
[CreatePipelineAsyncStatus, :pointer, StringView.by_value, :pointer, :pointer], :void
|
|
16
|
+
|
|
11
17
|
callback :buffer_map_callback,
|
|
12
18
|
[MapAsyncStatus, StringView.by_value, :pointer, :pointer], :void
|
|
13
19
|
|