wgpu 1.2.0 → 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 +66 -0
- data/README.md +18 -2
- data/docs/README.md +2 -2
- data/docs/api_coverage.md +23 -15
- data/docs/async.md +13 -1
- data/docs/command_encoding.md +25 -0
- data/docs/errors.md +13 -0
- data/docs/getting_started_compute.md +1 -0
- data/docs/getting_started_rendering.md +5 -0
- data/docs/pipeline_descriptors.md +7 -2
- data/docs/releasing.md +11 -1
- data/docs/resource_lifetime.md +25 -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 +82 -27
- data/lib/wgpu/commands/compute_pass.rb +49 -6
- data/lib/wgpu/commands/render_bundle.rb +9 -1
- data/lib/wgpu/commands/render_bundle_encoder.rb +63 -9
- data/lib/wgpu/commands/render_pass.rb +116 -14
- data/lib/wgpu/core/adapter.rb +95 -13
- data/lib/wgpu/core/async_waiter.rb +30 -4
- data/lib/wgpu/core/canvas_context.rb +37 -2
- data/lib/wgpu/core/device.rb +377 -70
- data/lib/wgpu/core/instance.rb +20 -0
- data/lib/wgpu/core/queue.rb +143 -43
- data/lib/wgpu/core/surface.rb +55 -7
- data/lib/wgpu/data_types.rb +16 -0
- data/lib/wgpu/descriptor_helpers.rb +87 -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 +14 -0
- data/lib/wgpu/native/installer.rb +55 -18
- data/lib/wgpu/native/loader.rb +22 -0
- data/lib/wgpu/native/structs.rb +18 -1
- data/lib/wgpu/native_resource.rb +190 -3
- data/lib/wgpu/pipeline/bind_group.rb +16 -7
- data/lib/wgpu/pipeline/bind_group_layout.rb +20 -7
- data/lib/wgpu/pipeline/compute_pipeline.rb +25 -37
- data/lib/wgpu/pipeline/pipeline_layout.rb +12 -4
- data/lib/wgpu/pipeline/render_pipeline.rb +33 -48
- data/lib/wgpu/pipeline/shader_module.rb +57 -32
- data/lib/wgpu/resources/buffer.rb +330 -59
- data/lib/wgpu/resources/query_set.rb +13 -1
- data/lib/wgpu/resources/sampler.rb +10 -3
- data/lib/wgpu/resources/texture.rb +49 -12
- data/lib/wgpu/resources/texture_view.rb +25 -5
- 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 +85 -5
- metadata +12 -4
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,6 +17,8 @@ module WGPU
|
|
|
15
17
|
raise InitializationError, "Failed to create WebGPU instance" if @handle.null?
|
|
16
18
|
end
|
|
17
19
|
|
|
20
|
+
# Requests an adapter matching the supplied preferences.
|
|
21
|
+
# @return [Adapter]
|
|
18
22
|
def request_adapter(power_preference: :high_performance, backend: nil, feature_level: :core,
|
|
19
23
|
force_fallback_adapter: false, compatible_surface: nil, timeout: nil)
|
|
20
24
|
Adapter.request(
|
|
@@ -28,6 +32,8 @@ module WGPU
|
|
|
28
32
|
)
|
|
29
33
|
end
|
|
30
34
|
|
|
35
|
+
# Requests an adapter on a background task.
|
|
36
|
+
# @return [AsyncTask] task yielding an {Adapter}
|
|
31
37
|
def request_adapter_async(power_preference: :high_performance, backend: nil, feature_level: :core,
|
|
32
38
|
force_fallback_adapter: false, compatible_surface: nil, timeout: nil)
|
|
33
39
|
AsyncTask.new do
|
|
@@ -42,6 +48,9 @@ module WGPU
|
|
|
42
48
|
end
|
|
43
49
|
end
|
|
44
50
|
|
|
51
|
+
# Lists adapters exposed by the instance.
|
|
52
|
+
# @param backends [Integer, nil] backend bit mask
|
|
53
|
+
# @return [Array<Adapter>]
|
|
45
54
|
def enumerate_adapters(backends: nil)
|
|
46
55
|
options = nil
|
|
47
56
|
if backends
|
|
@@ -61,20 +70,31 @@ module WGPU
|
|
|
61
70
|
end
|
|
62
71
|
end
|
|
63
72
|
|
|
73
|
+
# Enumerates adapters on a background task.
|
|
74
|
+
# @return [AsyncTask] task yielding adapter objects
|
|
64
75
|
def enumerate_adapters_async(backends: nil)
|
|
65
76
|
AsyncTask.new do
|
|
66
77
|
enumerate_adapters(backends: backends)
|
|
67
78
|
end
|
|
68
79
|
end
|
|
69
80
|
|
|
81
|
+
# Creates a canvas context from platform presentation information.
|
|
82
|
+
# @param present_info [Hash] platform surface information
|
|
83
|
+
# @return [CanvasContext]
|
|
70
84
|
def get_canvas_context(present_info)
|
|
71
85
|
CanvasContext.new(self, present_info)
|
|
72
86
|
end
|
|
73
87
|
|
|
88
|
+
# Processes pending instance callbacks and events.
|
|
89
|
+
# @return [void]
|
|
74
90
|
def process_events
|
|
75
91
|
Native.wgpuInstanceProcessEvents(@handle)
|
|
76
92
|
end
|
|
77
93
|
|
|
94
|
+
# Releases the native instance handle.
|
|
95
|
+
#
|
|
96
|
+
# Calling this method more than once has no effect.
|
|
97
|
+
# @return [void]
|
|
78
98
|
def release
|
|
79
99
|
return if @handle.null?
|
|
80
100
|
Native.wgpuInstanceRelease(@handle)
|
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?
|
|
@@ -20,7 +27,7 @@ module WGPU
|
|
|
20
27
|
raise CommandError, "Command buffer has already been submitted" if buffer.submitted?
|
|
21
28
|
end
|
|
22
29
|
|
|
23
|
-
handles = buffers.map(
|
|
30
|
+
handles = buffers.map { |buffer| NativeResource.checked_handle(buffer, expected_class: CommandBuffer) }
|
|
24
31
|
ptr = FFI::MemoryPointer.new(:pointer, handles.size)
|
|
25
32
|
ptr.write_array_of_pointer(handles)
|
|
26
33
|
|
|
@@ -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")
|
|
@@ -42,18 +54,24 @@ module WGPU
|
|
|
42
54
|
|
|
43
55
|
Native.wgpuQueueWriteBuffer(
|
|
44
56
|
@handle,
|
|
45
|
-
buffer
|
|
57
|
+
NativeResource.checked_handle(buffer, expected_class: Buffer),
|
|
46
58
|
buffer_offset,
|
|
47
59
|
data_ptr + data_offset,
|
|
48
60
|
write_size
|
|
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
|
|
|
55
73
|
dst = Native::ImageCopyTexture.new
|
|
56
|
-
dst[:texture] = destination[:texture]
|
|
74
|
+
dst[:texture] = NativeResource.checked_handle(destination[:texture], expected_class: Texture)
|
|
57
75
|
dst[:mip_level] = destination[:mip_level] || 0
|
|
58
76
|
dst[:origin][:x] = destination.dig(:origin, :x) || 0
|
|
59
77
|
dst[:origin][:y] = destination.dig(:origin, :y) || 0
|
|
@@ -64,16 +82,7 @@ module WGPU
|
|
|
64
82
|
name: "texture aspect"
|
|
65
83
|
)
|
|
66
84
|
|
|
67
|
-
extent =
|
|
68
|
-
if size.is_a?(Array)
|
|
69
|
-
extent[:width] = size[0]
|
|
70
|
-
extent[:height] = size[1] || 1
|
|
71
|
-
extent[:depth_or_array_layers] = size[2] || 1
|
|
72
|
-
else
|
|
73
|
-
extent[:width] = size[:width]
|
|
74
|
-
extent[:height] = size[:height] || 1
|
|
75
|
-
extent[:depth_or_array_layers] = size[:depth_or_array_layers] || 1
|
|
76
|
-
end
|
|
85
|
+
extent = DescriptorHelpers.extent_3d(size)
|
|
77
86
|
|
|
78
87
|
layout = Native::TextureDataLayout.new
|
|
79
88
|
layout[:offset] = data_layout[:offset] || 0
|
|
@@ -83,6 +92,10 @@ module WGPU
|
|
|
83
92
|
Native.wgpuQueueWriteTexture(@handle, dst, data_ptr, byte_size, layout, extent)
|
|
84
93
|
end
|
|
85
94
|
|
|
95
|
+
# Copies a GPU buffer to mapped staging memory and returns its bytes.
|
|
96
|
+
# @param buffer [Buffer] source buffer
|
|
97
|
+
# @param staging [Buffer, nil] reusable map-read staging buffer
|
|
98
|
+
# @return [String] copied bytes
|
|
86
99
|
def read_buffer(buffer, offset: 0, size: nil, device: nil, staging: nil)
|
|
87
100
|
device ||= @device
|
|
88
101
|
raise ArgumentError, "device is required when the queue has no owning device" unless device
|
|
@@ -90,6 +103,7 @@ module WGPU
|
|
|
90
103
|
size ||= buffer.size - offset
|
|
91
104
|
owns_staging = staging.nil?
|
|
92
105
|
staging ||= Buffer.new(device, size: size, usage: [:map_read, :copy_dst])
|
|
106
|
+
validate_readback_staging!(staging, size)
|
|
93
107
|
encoder = CommandEncoder.new(device)
|
|
94
108
|
encoder.copy_buffer_to_buffer(
|
|
95
109
|
source: buffer,
|
|
@@ -101,20 +115,32 @@ module WGPU
|
|
|
101
115
|
command_buffer = encoder.finish
|
|
102
116
|
submit([command_buffer])
|
|
103
117
|
|
|
118
|
+
map_requested = true
|
|
104
119
|
staging.map_sync(:read)
|
|
105
120
|
staging.read_mapped_data(size:)
|
|
106
121
|
ensure
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
cleanup_readback_resources(
|
|
123
|
+
staging:,
|
|
124
|
+
unmap_staging: map_requested,
|
|
125
|
+
owns_staging:,
|
|
126
|
+
command_buffer:,
|
|
127
|
+
encoder:,
|
|
128
|
+
active_error: $!
|
|
129
|
+
)
|
|
111
130
|
end
|
|
112
131
|
|
|
132
|
+
# Copies a texture region to mapped staging memory and returns its padded bytes.
|
|
133
|
+
# @param source [Hash] source texture, origin, aspect, and optional format
|
|
134
|
+
# @param data_layout [Hash] destination byte layout
|
|
135
|
+
# @param size [Hash, Array] extent to read
|
|
136
|
+
# @param staging [Buffer, nil] reusable map-read staging buffer
|
|
137
|
+
# @return [String] copied bytes
|
|
113
138
|
def read_texture(source:, data_layout:, size:, device: nil, staging: nil)
|
|
114
139
|
device ||= @device
|
|
115
140
|
raise ArgumentError, "device is required when the queue has no owning device" unless device
|
|
116
141
|
|
|
117
|
-
|
|
142
|
+
extent = DescriptorHelpers.extent_3d(size)
|
|
143
|
+
width, height, depth = extent.values
|
|
118
144
|
bytes_per_row = data_layout[:bytes_per_row]
|
|
119
145
|
raise ArgumentError, "data_layout[:bytes_per_row] is required" unless bytes_per_row
|
|
120
146
|
|
|
@@ -125,10 +151,12 @@ module WGPU
|
|
|
125
151
|
)
|
|
126
152
|
format = source[:format] || source[:texture].format
|
|
127
153
|
aspect = source[:aspect] || :all
|
|
128
|
-
|
|
154
|
+
tight_bytes_per_row = TextureFormat.bytes_per_row(width, format, aspect:)
|
|
155
|
+
minimum_bytes_per_row = TextureFormat.aligned_bytes_per_row(width, format, aspect:)
|
|
129
156
|
if bytes_per_row < minimum_bytes_per_row
|
|
130
157
|
raise ArgumentError,
|
|
131
|
-
"bytes_per_row must be at least #{minimum_bytes_per_row} for width #{width} and #{format.inspect}"
|
|
158
|
+
"bytes_per_row must be at least #{minimum_bytes_per_row} for width #{width} and #{format.inspect} " \
|
|
159
|
+
"(tight row is #{tight_bytes_per_row} bytes)"
|
|
132
160
|
end
|
|
133
161
|
|
|
134
162
|
rows_per_image = data_layout[:rows_per_image] || height
|
|
@@ -136,6 +164,7 @@ module WGPU
|
|
|
136
164
|
|
|
137
165
|
owns_staging = staging.nil?
|
|
138
166
|
staging ||= Buffer.new(device, size: buffer_size, usage: [:map_read, :copy_dst])
|
|
167
|
+
validate_readback_staging!(staging, buffer_size)
|
|
139
168
|
encoder = CommandEncoder.new(device)
|
|
140
169
|
encoder.copy_texture_to_buffer(
|
|
141
170
|
source: source,
|
|
@@ -150,25 +179,42 @@ module WGPU
|
|
|
150
179
|
command_buffer = encoder.finish
|
|
151
180
|
submit([command_buffer])
|
|
152
181
|
|
|
182
|
+
map_requested = true
|
|
153
183
|
staging.map_sync(:read)
|
|
154
184
|
staging.read_mapped_data(size: buffer_size)
|
|
155
185
|
ensure
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
186
|
+
cleanup_readback_resources(
|
|
187
|
+
staging:,
|
|
188
|
+
unmap_staging: map_requested,
|
|
189
|
+
owns_staging:,
|
|
190
|
+
command_buffer:,
|
|
191
|
+
encoder:,
|
|
192
|
+
active_error: $!
|
|
193
|
+
)
|
|
160
194
|
end
|
|
161
195
|
|
|
196
|
+
# Waits until all previously submitted queue work completes.
|
|
197
|
+
# @param device [Device, nil] device used to drive callback progress
|
|
198
|
+
# @param timeout [Numeric, nil] maximum wait time in seconds
|
|
199
|
+
# @return [Symbol] native completion status
|
|
162
200
|
def on_submitted_work_done(device: nil, timeout: nil)
|
|
201
|
+
timeout = AsyncWaiter.normalize_timeout(timeout)
|
|
163
202
|
device ||= @device
|
|
164
203
|
instance = device&.adapter&.instance
|
|
165
204
|
status_holder = { done: false, status: nil }
|
|
166
205
|
|
|
206
|
+
callback_lifetime_release = device_callback_lifetime_lease
|
|
207
|
+
callback_token = nil
|
|
167
208
|
callback = FFI::Function.new(
|
|
168
209
|
:void, [:uint32, :pointer, :pointer]
|
|
169
210
|
) do |status, _userdata1, _userdata2|
|
|
170
|
-
|
|
171
|
-
|
|
211
|
+
begin
|
|
212
|
+
status_holder[:status] = Native::QueueWorkDoneStatus[status]
|
|
213
|
+
status_holder[:done] = true
|
|
214
|
+
ensure
|
|
215
|
+
CallbackKeepalive.release(self, callback_token)
|
|
216
|
+
callback_lifetime_release.call
|
|
217
|
+
end
|
|
172
218
|
end
|
|
173
219
|
|
|
174
220
|
callback_info = Native::QueueWorkDoneCallbackInfo.new
|
|
@@ -179,28 +225,37 @@ module WGPU
|
|
|
179
225
|
callback_info[:userdata2] = nil
|
|
180
226
|
|
|
181
227
|
callback_token = CallbackKeepalive.retain(self, callback)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
228
|
+
future =
|
|
229
|
+
begin
|
|
230
|
+
Native.wgpuQueueOnSubmittedWorkDone(@handle, callback_info)
|
|
231
|
+
rescue StandardError
|
|
232
|
+
CallbackKeepalive.release(self, callback_token)
|
|
233
|
+
callback_lifetime_release.call
|
|
234
|
+
raise
|
|
235
|
+
end
|
|
236
|
+
AsyncWaiter.wait(
|
|
237
|
+
status_holder: status_holder,
|
|
238
|
+
instance: instance,
|
|
239
|
+
device: device,
|
|
240
|
+
future: future,
|
|
241
|
+
timeout: timeout
|
|
242
|
+
)
|
|
194
243
|
|
|
195
244
|
status_holder[:status]
|
|
196
245
|
end
|
|
197
246
|
|
|
247
|
+
# Waits for prior queue work on a background task.
|
|
248
|
+
# @return [AsyncTask] task yielding the native completion status
|
|
198
249
|
def on_submitted_work_done_async(device: nil, timeout: nil)
|
|
199
250
|
AsyncTask.new do
|
|
200
251
|
on_submitted_work_done(device: device, timeout: timeout)
|
|
201
252
|
end
|
|
202
253
|
end
|
|
203
254
|
|
|
255
|
+
# Releases the native queue handle.
|
|
256
|
+
#
|
|
257
|
+
# Calling this method more than once has no effect.
|
|
258
|
+
# @return [void]
|
|
204
259
|
def release
|
|
205
260
|
return if @handle.null?
|
|
206
261
|
Native.wgpuQueueRelease(@handle)
|
|
@@ -209,12 +264,57 @@ module WGPU
|
|
|
209
264
|
|
|
210
265
|
private
|
|
211
266
|
|
|
212
|
-
def
|
|
213
|
-
if size
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
267
|
+
def validate_readback_staging!(staging, required_size)
|
|
268
|
+
if staging.size < required_size
|
|
269
|
+
raise ArgumentError,
|
|
270
|
+
"staging buffer size must be at least #{required_size} bytes (got #{staging.size})"
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
required_usage = Native::BufferUsage.fetch(:map_read) | Native::BufferUsage.fetch(:copy_dst)
|
|
274
|
+
unless (staging.usage & required_usage) == required_usage
|
|
275
|
+
raise ArgumentError, "staging buffer usage must include :map_read and :copy_dst"
|
|
217
276
|
end
|
|
277
|
+
|
|
278
|
+
return if staging.map_state == :unmapped
|
|
279
|
+
|
|
280
|
+
raise ArgumentError, "staging buffer must be unmapped before readback"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def cleanup_readback_resources(
|
|
284
|
+
staging:,
|
|
285
|
+
unmap_staging:,
|
|
286
|
+
owns_staging:,
|
|
287
|
+
command_buffer:,
|
|
288
|
+
encoder:,
|
|
289
|
+
active_error:
|
|
290
|
+
)
|
|
291
|
+
cleanup_error = nil
|
|
292
|
+
|
|
293
|
+
begin
|
|
294
|
+
staging&.unmap if unmap_staging && staging && staging.map_state == :mapped
|
|
295
|
+
rescue StandardError => e
|
|
296
|
+
cleanup_error ||= e
|
|
297
|
+
ensure
|
|
298
|
+
begin
|
|
299
|
+
command_buffer&.release
|
|
300
|
+
rescue StandardError => e
|
|
301
|
+
cleanup_error ||= e
|
|
302
|
+
ensure
|
|
303
|
+
begin
|
|
304
|
+
encoder&.release
|
|
305
|
+
rescue StandardError => e
|
|
306
|
+
cleanup_error ||= e
|
|
307
|
+
ensure
|
|
308
|
+
begin
|
|
309
|
+
staging&.release if owns_staging
|
|
310
|
+
rescue StandardError => e
|
|
311
|
+
cleanup_error ||= e
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
raise cleanup_error if cleanup_error && active_error.nil?
|
|
218
318
|
end
|
|
219
319
|
|
|
220
320
|
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
|
|
@@ -15,12 +18,15 @@ module WGPU
|
|
|
15
18
|
desc[:label][:data] = nil
|
|
16
19
|
desc[:label][:length] = 0
|
|
17
20
|
|
|
18
|
-
handle = Native.wgpuInstanceCreateSurface(instance
|
|
21
|
+
handle = Native.wgpuInstanceCreateSurface(NativeResource.checked_handle(instance, expected_class: Instance), desc)
|
|
19
22
|
raise SurfaceError, "Failed to create surface from Metal layer" if handle.null?
|
|
20
23
|
|
|
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
|
|
@@ -33,12 +39,15 @@ module WGPU
|
|
|
33
39
|
desc[:label][:data] = nil
|
|
34
40
|
desc[:label][:length] = 0
|
|
35
41
|
|
|
36
|
-
handle = Native.wgpuInstanceCreateSurface(instance
|
|
42
|
+
handle = Native.wgpuInstanceCreateSurface(NativeResource.checked_handle(instance, expected_class: Instance), desc)
|
|
37
43
|
raise SurfaceError, "Failed to create surface from Windows HWND" if handle.null?
|
|
38
44
|
|
|
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
|
|
@@ -51,12 +60,15 @@ module WGPU
|
|
|
51
60
|
desc[:label][:data] = nil
|
|
52
61
|
desc[:label][:length] = 0
|
|
53
62
|
|
|
54
|
-
handle = Native.wgpuInstanceCreateSurface(instance
|
|
63
|
+
handle = Native.wgpuInstanceCreateSurface(NativeResource.checked_handle(instance, expected_class: Instance), desc)
|
|
55
64
|
raise SurfaceError, "Failed to create surface from Xlib window" if handle.null?
|
|
56
65
|
|
|
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
|
|
@@ -69,12 +81,15 @@ module WGPU
|
|
|
69
81
|
desc[:label][:data] = nil
|
|
70
82
|
desc[:label][:length] = 0
|
|
71
83
|
|
|
72
|
-
handle = Native.wgpuInstanceCreateSurface(instance
|
|
84
|
+
handle = Native.wgpuInstanceCreateSurface(NativeResource.checked_handle(instance, expected_class: Instance), desc)
|
|
73
85
|
raise SurfaceError, "Failed to create surface from Wayland surface" if handle.null?
|
|
74
86
|
|
|
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,10 +97,12 @@ 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
|
-
config[:device] = device
|
|
105
|
+
config[:device] = NativeResource.checked_handle(device, expected_class: Device)
|
|
89
106
|
config[:format] = Native::EnumHelper.coerce(Native::TextureFormat, format, name: "surface format")
|
|
90
107
|
config[:usage] = normalize_usage(usage)
|
|
91
108
|
config[:width] = width
|
|
@@ -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,29 +177,46 @@ 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
|
-
Native.wgpuSurfaceGetCapabilities(@handle, adapter
|
|
219
|
+
Native.wgpuSurfaceGetCapabilities(@handle, NativeResource.checked_handle(adapter, expected_class: Adapter), caps)
|
|
176
220
|
|
|
177
221
|
formats = []
|
|
178
222
|
if caps[:format_count] > 0 && !caps[:formats].null?
|
|
@@ -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?
|