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
|
@@ -4,148 +4,290 @@ module WGPU
|
|
|
4
4
|
class Buffer
|
|
5
5
|
attr_reader :handle, :size, :usage
|
|
6
6
|
|
|
7
|
+
# Creates a GPU buffer with the requested size and usage.
|
|
8
|
+
# @param device [Device] owning device
|
|
9
|
+
# @param size [Integer] buffer size in bytes
|
|
10
|
+
# @param usage [Symbol, Array<Symbol>, Integer] usage flags
|
|
11
|
+
# @param mapped_at_creation [Boolean] whether to expose an initial mapped range
|
|
12
|
+
# @raise [BufferError] if native validation or creation fails
|
|
7
13
|
def initialize(device, label: nil, size:, usage:, mapped_at_creation: false)
|
|
8
14
|
@device = device
|
|
9
15
|
@size = size
|
|
10
|
-
@usage =
|
|
16
|
+
@usage =
|
|
17
|
+
begin
|
|
18
|
+
normalize_usage(usage)
|
|
19
|
+
rescue ArgumentError => e
|
|
20
|
+
raise ArgumentError, buffer_error_message(e.message, label)
|
|
21
|
+
end
|
|
11
22
|
@mapped = mapped_at_creation
|
|
12
|
-
@
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
end
|
|
24
|
-
desc[:usage] = @usage
|
|
25
|
-
desc[:size] = size
|
|
26
|
-
desc[:mapped_at_creation] = mapped_at_creation ? 1 : 0
|
|
23
|
+
@map_state = mapped_at_creation ? :mapped : :unmapped
|
|
24
|
+
@map_generation = 0
|
|
25
|
+
@map_state_mutex = Mutex.new
|
|
26
|
+
|
|
27
|
+
desc, keepalive = build_descriptor(
|
|
28
|
+
label:,
|
|
29
|
+
size:,
|
|
30
|
+
usage: @usage,
|
|
31
|
+
mapped_at_creation:
|
|
32
|
+
)
|
|
33
|
+
@descriptor_keepalive = keepalive
|
|
27
34
|
|
|
28
35
|
device.push_error_scope(:validation)
|
|
29
36
|
@handle = Native.wgpuDeviceCreateBuffer(device.handle, desc)
|
|
30
37
|
error = device.pop_error_scope
|
|
38
|
+
@descriptor_keepalive = nil
|
|
31
39
|
|
|
32
40
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
33
41
|
msg = error[:message] || "Failed to create buffer"
|
|
34
|
-
raise BufferError, msg
|
|
42
|
+
raise BufferError, buffer_error_message(msg, label)
|
|
35
43
|
end
|
|
36
44
|
end
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
# Writes typed data through the device's default queue.
|
|
47
|
+
# @param data [Array, String, FFI::Pointer] source data
|
|
48
|
+
# @param offset [Integer] destination byte offset
|
|
49
|
+
# @param type [Symbol] source element type
|
|
50
|
+
# @return [void]
|
|
51
|
+
def write(data, offset: 0, type: :f32)
|
|
52
|
+
ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
53
|
+
DataTypes.validate_alignment!(offset, 4, name: "offset")
|
|
54
|
+
DataTypes.validate_alignment!(byte_size, 4, name: "data size")
|
|
40
55
|
Native.wgpuQueueWriteBuffer(@device.queue.handle, @handle, offset, ptr, byte_size)
|
|
41
56
|
end
|
|
42
57
|
|
|
58
|
+
# Returns a writable view of a mapped byte range.
|
|
59
|
+
# @return [BufferMappedRange]
|
|
60
|
+
# @raise [BufferError] if the buffer is not mapped or no range is available
|
|
43
61
|
def mapped_range(offset: 0, size: nil)
|
|
44
62
|
raise BufferError, "Buffer is not mapped" unless @mapped
|
|
45
63
|
|
|
46
64
|
size ||= @size - offset
|
|
65
|
+
offset, size = validate_map_range!(offset, size)
|
|
47
66
|
ptr = Native.wgpuBufferGetMappedRange(@handle, offset, size)
|
|
48
67
|
raise BufferError, "Failed to get mapped range" if ptr.null?
|
|
49
68
|
|
|
50
69
|
BufferMappedRange.new(ptr, size)
|
|
51
70
|
end
|
|
52
71
|
|
|
72
|
+
# Returns a writable view of a mapped byte range.
|
|
73
|
+
# @return [BufferMappedRange]
|
|
53
74
|
def get_mapped_range(offset: 0, size: nil)
|
|
54
75
|
mapped_range(offset: offset, size: size)
|
|
55
76
|
end
|
|
56
77
|
|
|
78
|
+
# Unmaps the buffer and invalidates its mapped ranges.
|
|
79
|
+
# @return [void]
|
|
57
80
|
def unmap
|
|
58
81
|
Native.wgpuBufferUnmap(@handle)
|
|
59
|
-
|
|
82
|
+
map_state_mutex.synchronize do
|
|
83
|
+
@map_generation = current_map_generation + 1
|
|
84
|
+
@mapped = false
|
|
85
|
+
@map_state = :unmapped
|
|
86
|
+
end
|
|
60
87
|
end
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
89
|
+
# Maps a range and waits for native completion.
|
|
90
|
+
# @param mode [Symbol, Integer] map access mode
|
|
91
|
+
# @param timeout [Numeric, nil] maximum wait time in seconds
|
|
92
|
+
# @return [Boolean] true when mapped
|
|
93
|
+
# @raise [BufferError] if mapping fails
|
|
94
|
+
def map_sync(mode, offset: 0, size: nil, timeout: nil)
|
|
95
|
+
status_holder, _callback_token, future, generation =
|
|
96
|
+
begin_map_request(mode, offset: offset, size: size)
|
|
97
|
+
wait_for_map(status_holder, future, timeout:)
|
|
98
|
+
finalize_map(status_holder, generation)
|
|
68
99
|
end
|
|
69
100
|
|
|
101
|
+
# Maps a range on a background task.
|
|
102
|
+
# @param mode [Symbol, Integer] map access mode
|
|
103
|
+
# @return [AsyncTask] task yielding true when mapped
|
|
70
104
|
def map_async(mode, offset: 0, size: nil)
|
|
71
|
-
status_holder,
|
|
105
|
+
status_holder, _callback_token, future, generation =
|
|
106
|
+
begin_map_request(mode, offset: offset, size: size)
|
|
72
107
|
AsyncTask.new do
|
|
73
108
|
wait_for_map(status_holder, future)
|
|
74
|
-
finalize_map(status_holder)
|
|
75
|
-
ensure
|
|
76
|
-
@map_callbacks.delete(callback)
|
|
109
|
+
finalize_map(status_holder, generation)
|
|
77
110
|
end
|
|
78
111
|
end
|
|
79
112
|
|
|
113
|
+
# Copies bytes from a const mapped range.
|
|
114
|
+
# @return [String] mapped bytes
|
|
115
|
+
# @raise [BufferError] if the buffer is not mapped
|
|
80
116
|
def read_mapped_data(offset: 0, size: nil)
|
|
81
117
|
raise BufferError, "Buffer is not mapped" unless @mapped
|
|
82
118
|
|
|
83
119
|
size ||= @size - offset
|
|
120
|
+
offset, size = validate_map_range!(offset, size)
|
|
84
121
|
ptr = Native.wgpuBufferGetConstMappedRange(@handle, offset, size)
|
|
85
122
|
raise BufferError, "Failed to get mapped range" if ptr.null?
|
|
86
123
|
|
|
87
124
|
ptr.read_bytes(size)
|
|
88
125
|
end
|
|
89
126
|
|
|
90
|
-
|
|
91
|
-
|
|
127
|
+
# Reads mapped bytes, optionally decoding typed values.
|
|
128
|
+
# @param type [Symbol, nil] element type, or +nil+ for raw bytes
|
|
129
|
+
# @return [String, Array]
|
|
130
|
+
def read_mapped(offset: 0, size: nil, type: nil)
|
|
131
|
+
bytes = read_mapped_data(offset: offset, size: size)
|
|
132
|
+
type ? DataTypes.unpack(bytes, type:) : bytes
|
|
92
133
|
end
|
|
93
134
|
|
|
94
|
-
|
|
135
|
+
# Writes typed data into a mapped range.
|
|
136
|
+
# @param data [Array, String, FFI::Pointer] source data
|
|
137
|
+
# @param type [Symbol] source element type
|
|
138
|
+
# @return [void]
|
|
139
|
+
def write_mapped(data, offset: 0, type: :f32)
|
|
95
140
|
raise BufferError, "Buffer is not mapped" unless @mapped
|
|
96
141
|
|
|
97
|
-
ptr, byte_size =
|
|
142
|
+
ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
143
|
+
offset, byte_size = validate_map_range!(offset, byte_size)
|
|
98
144
|
target = Native.wgpuBufferGetMappedRange(@handle, offset, byte_size)
|
|
99
145
|
raise BufferError, "Failed to get mapped range" if target.null?
|
|
100
146
|
|
|
101
147
|
target.put_bytes(0, ptr.read_bytes(byte_size))
|
|
102
148
|
end
|
|
103
149
|
|
|
150
|
+
# Reads mapped bytes as 32-bit floating-point values.
|
|
151
|
+
#
|
|
152
|
+
# @param offset [Integer] byte offset in the buffer
|
|
153
|
+
# @param count [Integer, nil] values to read; defaults to the remaining range
|
|
154
|
+
# @return [Array<Float>] decoded values
|
|
155
|
+
# @raise [BufferError] if the buffer is not mapped
|
|
104
156
|
def read_mapped_floats(offset: 0, count: nil)
|
|
105
|
-
|
|
157
|
+
read_mapped_values(type: :f32, offset:, count:)
|
|
158
|
+
end
|
|
106
159
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
160
|
+
# Reads mapped bytes as unsigned 32-bit integers.
|
|
161
|
+
#
|
|
162
|
+
# @param offset [Integer] byte offset in the buffer
|
|
163
|
+
# @param count [Integer, nil] values to read; defaults to the remaining range
|
|
164
|
+
# @return [Array<Integer>] decoded values
|
|
165
|
+
# @raise [BufferError] if the buffer is not mapped
|
|
166
|
+
def read_mapped_uint32s(offset: 0, count: nil)
|
|
167
|
+
read_mapped_values(type: :u32, offset:, count:)
|
|
168
|
+
end
|
|
110
169
|
|
|
111
|
-
|
|
170
|
+
# Reads mapped bytes as signed 32-bit integers.
|
|
171
|
+
#
|
|
172
|
+
# @param offset [Integer] byte offset in the buffer
|
|
173
|
+
# @param count [Integer, nil] values to read; defaults to the remaining range
|
|
174
|
+
# @return [Array<Integer>] decoded values
|
|
175
|
+
# @raise [BufferError] if the buffer is not mapped
|
|
176
|
+
def read_mapped_int32s(offset: 0, count: nil)
|
|
177
|
+
read_mapped_values(type: :i32, offset:, count:)
|
|
112
178
|
end
|
|
113
179
|
|
|
180
|
+
# Reads mapped bytes as 64-bit floating-point values.
|
|
181
|
+
#
|
|
182
|
+
# @param offset [Integer] byte offset in the buffer
|
|
183
|
+
# @param count [Integer, nil] values to read; defaults to the remaining range
|
|
184
|
+
# @return [Array<Float>] decoded values
|
|
185
|
+
# @raise [BufferError] if the buffer is not mapped
|
|
186
|
+
def read_mapped_float64s(offset: 0, count: nil)
|
|
187
|
+
read_mapped_values(type: :f64, offset:, count:)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Reads mapped bytes as unsigned 16-bit integers.
|
|
191
|
+
#
|
|
192
|
+
# @param offset [Integer] byte offset in the buffer
|
|
193
|
+
# @param count [Integer, nil] values to read; defaults to the remaining range
|
|
194
|
+
# @return [Array<Integer>] decoded values
|
|
195
|
+
# @raise [BufferError] if the buffer is not mapped
|
|
196
|
+
def read_mapped_uint16s(offset: 0, count: nil)
|
|
197
|
+
read_mapped_values(type: :u16, offset:, count:)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Reads mapped bytes as unsigned 8-bit integers.
|
|
201
|
+
#
|
|
202
|
+
# @param offset [Integer] byte offset in the buffer
|
|
203
|
+
# @param count [Integer, nil] values to read; defaults to the remaining range
|
|
204
|
+
# @return [Array<Integer>] decoded values
|
|
205
|
+
# @raise [BufferError] if the buffer is not mapped
|
|
206
|
+
def read_mapped_uint8s(offset: 0, count: nil)
|
|
207
|
+
read_mapped_values(type: :u8, offset:, count:)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Reads mapped values of the requested element type.
|
|
211
|
+
# @param type [Symbol] element type
|
|
212
|
+
# @param count [Integer, nil] number of values
|
|
213
|
+
# @return [Array]
|
|
214
|
+
def read_mapped_values(type: :f32, offset: 0, count: nil)
|
|
215
|
+
element_size = DataTypes.byte_size(type)
|
|
216
|
+
size = count ? count * element_size : @size - offset
|
|
217
|
+
DataTypes.unpack(read_mapped_data(offset:, size:), type:)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Returns the current mapping state.
|
|
221
|
+
# @return [Symbol]
|
|
114
222
|
def map_state
|
|
115
|
-
|
|
116
|
-
|
|
223
|
+
return map_state_mutex.synchronize { @map_state } unless Native.buffer_map_state_available?
|
|
224
|
+
|
|
225
|
+
Native.wgpuBufferGetMapState(@handle) || map_state_mutex.synchronize { @map_state }
|
|
117
226
|
end
|
|
118
227
|
|
|
228
|
+
# Destroys the buffer's storage.
|
|
229
|
+
# @return [void]
|
|
119
230
|
def destroy
|
|
120
231
|
Native.wgpuBufferDestroy(@handle)
|
|
232
|
+
invalidate_map_state
|
|
121
233
|
end
|
|
122
234
|
|
|
235
|
+
# Releases the native buffer handle.
|
|
236
|
+
#
|
|
237
|
+
# Calling this method more than once has no effect.
|
|
238
|
+
# @return [void]
|
|
123
239
|
def release
|
|
124
240
|
return if @handle.null?
|
|
125
241
|
Native.wgpuBufferRelease(@handle)
|
|
126
242
|
@handle = FFI::Pointer::NULL
|
|
243
|
+
invalidate_map_state
|
|
127
244
|
end
|
|
128
245
|
|
|
129
246
|
private
|
|
130
247
|
|
|
248
|
+
def build_descriptor(label:, size:, usage:, mapped_at_creation:)
|
|
249
|
+
keepalive = []
|
|
250
|
+
desc = Native::BufferDescriptor.new
|
|
251
|
+
desc[:next_in_chain] = nil
|
|
252
|
+
DescriptorHelpers.set_label(desc, label, keepalive:)
|
|
253
|
+
desc[:usage] = usage
|
|
254
|
+
desc[:size] = size
|
|
255
|
+
desc[:mapped_at_creation] = mapped_at_creation ? 1 : 0
|
|
256
|
+
[desc, keepalive]
|
|
257
|
+
end
|
|
258
|
+
|
|
131
259
|
def begin_map_request(mode, offset:, size:)
|
|
132
260
|
size ||= @size - offset
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
when :write then Native::MapMode[:write]
|
|
136
|
-
when Integer then mode
|
|
137
|
-
else raise ArgumentError, "Invalid map mode: #{mode}"
|
|
138
|
-
end
|
|
261
|
+
offset, size = validate_map_range!(offset, size)
|
|
262
|
+
mode_flag = Native::EnumHelper.coerce(Native::MapMode, mode, name: "map mode")
|
|
139
263
|
|
|
140
264
|
status_holder = { done: false, status: nil, message: nil }
|
|
265
|
+
generation = map_state_mutex.synchronize do
|
|
266
|
+
@map_generation = current_map_generation + 1
|
|
267
|
+
@map_state = :pending
|
|
268
|
+
@map_generation
|
|
269
|
+
end
|
|
270
|
+
callback_lifetime_release = device_callback_lifetime_lease
|
|
271
|
+
callback_token = nil
|
|
141
272
|
callback = FFI::Function.new(:void, [:uint32, Native::StringView.by_value, :pointer, :pointer]) do |status, message, _userdata1, _userdata2|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
273
|
+
begin
|
|
274
|
+
status_holder[:status] = Native::MapAsyncStatus[status]
|
|
275
|
+
if message[:data] && !message[:data].null? && message[:length] > 0
|
|
276
|
+
status_holder[:message] = message[:data].read_string(message[:length])
|
|
277
|
+
end
|
|
278
|
+
map_state_mutex.synchronize do
|
|
279
|
+
if generation == current_map_generation && !@handle.null?
|
|
280
|
+
@mapped = status_holder[:status] == :success
|
|
281
|
+
@map_state = @mapped ? :mapped : :unmapped
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
status_holder[:done] = true
|
|
285
|
+
ensure
|
|
286
|
+
CallbackKeepalive.release(self, callback_token)
|
|
287
|
+
callback_lifetime_release.call
|
|
146
288
|
end
|
|
147
289
|
end
|
|
148
|
-
|
|
290
|
+
callback_token = CallbackKeepalive.retain(self, callback)
|
|
149
291
|
|
|
150
292
|
callback_info = Native::BufferMapCallbackInfo.new
|
|
151
293
|
callback_info[:next_in_chain] = nil
|
|
@@ -154,83 +296,243 @@ module WGPU
|
|
|
154
296
|
callback_info[:userdata1] = nil
|
|
155
297
|
callback_info[:userdata2] = nil
|
|
156
298
|
|
|
157
|
-
future =
|
|
299
|
+
future =
|
|
300
|
+
begin
|
|
301
|
+
Native.wgpuBufferMapAsync(@handle, mode_flag, offset, size, callback_info)
|
|
302
|
+
rescue StandardError
|
|
303
|
+
CallbackKeepalive.release(self, callback_token)
|
|
304
|
+
callback_lifetime_release.call
|
|
305
|
+
invalidate_map_state(generation)
|
|
306
|
+
raise
|
|
307
|
+
end
|
|
158
308
|
|
|
159
|
-
[status_holder,
|
|
309
|
+
[status_holder, callback_token, future, generation]
|
|
160
310
|
end
|
|
161
311
|
|
|
162
|
-
def wait_for_map(status_holder, future)
|
|
312
|
+
def wait_for_map(status_holder, future, timeout: nil)
|
|
163
313
|
AsyncWaiter.wait(
|
|
164
314
|
status_holder: status_holder,
|
|
165
315
|
instance: @device.adapter&.instance,
|
|
166
316
|
device: @device,
|
|
167
|
-
future: future
|
|
317
|
+
future: future,
|
|
318
|
+
timeout: timeout
|
|
168
319
|
)
|
|
169
320
|
end
|
|
170
321
|
|
|
171
|
-
def finalize_map(status_holder)
|
|
322
|
+
def finalize_map(status_holder, generation)
|
|
172
323
|
if status_holder[:status] == :success
|
|
173
|
-
|
|
324
|
+
map_state_mutex.synchronize do
|
|
325
|
+
if generation == current_map_generation && !@handle.null?
|
|
326
|
+
@mapped = true
|
|
327
|
+
@map_state = :mapped
|
|
328
|
+
end
|
|
329
|
+
end
|
|
174
330
|
true
|
|
175
331
|
else
|
|
332
|
+
invalidate_map_state(generation)
|
|
176
333
|
detail = status_holder[:message]
|
|
177
334
|
base = "Failed to map buffer: #{status_holder[:status]}"
|
|
178
335
|
raise BufferError, detail && !detail.empty? ? "#{base} (#{detail})" : base
|
|
179
336
|
end
|
|
180
337
|
end
|
|
181
338
|
|
|
182
|
-
def
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
usage.reduce(0) { |acc, u| acc | Native::BufferUsage[u] }
|
|
190
|
-
else
|
|
191
|
-
raise ArgumentError, "Invalid usage type: #{usage.class}"
|
|
339
|
+
def invalidate_map_state(generation = nil)
|
|
340
|
+
map_state_mutex.synchronize do
|
|
341
|
+
return if generation && generation != current_map_generation
|
|
342
|
+
|
|
343
|
+
@map_generation = current_map_generation + 1
|
|
344
|
+
@mapped = false
|
|
345
|
+
@map_state = :unmapped
|
|
192
346
|
end
|
|
193
347
|
end
|
|
194
348
|
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
349
|
+
def current_map_generation
|
|
350
|
+
@map_generation ||= 0
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def map_state_mutex
|
|
354
|
+
@map_state_mutex ||= Mutex.new
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def normalize_usage(usage)
|
|
358
|
+
Native::EnumHelper.coerce_flags(Native::BufferUsage, usage, name: "buffer usage")
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def buffer_error_message(message, label)
|
|
362
|
+
context = label ? " #{label.inspect}" : ""
|
|
363
|
+
"create buffer#{context}: #{message}"
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def validate_map_range!(offset, size)
|
|
367
|
+
offset = DataTypes.validate_alignment!(offset, 8, name: "map offset")
|
|
368
|
+
size = DataTypes.validate_alignment!(size, 4, name: "map size")
|
|
369
|
+
if offset > @size || size > @size - offset
|
|
370
|
+
raise ArgumentError,
|
|
371
|
+
"mapped range (offset #{offset}, size #{size}) exceeds buffer size #{@size}"
|
|
209
372
|
end
|
|
373
|
+
|
|
374
|
+
[offset, size]
|
|
210
375
|
end
|
|
211
376
|
end
|
|
212
377
|
|
|
213
378
|
class BufferMappedRange
|
|
379
|
+
# Wraps a native mapped memory range.
|
|
380
|
+
# @param pointer [FFI::Pointer] start of mapped memory
|
|
381
|
+
# @param size [Integer] range size in bytes
|
|
214
382
|
def initialize(pointer, size)
|
|
215
383
|
@pointer = pointer
|
|
216
384
|
@size = size
|
|
217
385
|
end
|
|
218
386
|
|
|
387
|
+
# Reads 32-bit floating-point values from the mapped range.
|
|
388
|
+
#
|
|
389
|
+
# @param count [Integer, nil] values to read; defaults to the full range
|
|
390
|
+
# @return [Array<Float>] decoded values
|
|
219
391
|
def read_floats(count = nil)
|
|
220
|
-
|
|
221
|
-
@pointer.read_array_of_float(count)
|
|
392
|
+
read(type: :f32, count:)
|
|
222
393
|
end
|
|
223
394
|
|
|
395
|
+
# Writes 32-bit floating-point values into the mapped range.
|
|
396
|
+
#
|
|
397
|
+
# @param data [Array<Numeric>] values to write
|
|
398
|
+
# @return [void]
|
|
399
|
+
# @raise [ArgumentError] if the data exceeds the mapped range
|
|
224
400
|
def write_floats(data)
|
|
225
|
-
|
|
401
|
+
write(data, type: :f32)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Reads unsigned 32-bit integers from the mapped range.
|
|
405
|
+
#
|
|
406
|
+
# @param count [Integer, nil] values to read; defaults to the full range
|
|
407
|
+
# @return [Array<Integer>] decoded values
|
|
408
|
+
def read_uint32s(count = nil)
|
|
409
|
+
read(type: :u32, count:)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Writes unsigned 32-bit integers into the mapped range.
|
|
413
|
+
#
|
|
414
|
+
# @param data [Array<Integer>] values to write
|
|
415
|
+
# @return [void]
|
|
416
|
+
# @raise [ArgumentError] if the data exceeds the mapped range
|
|
417
|
+
def write_uint32s(data)
|
|
418
|
+
write(data, type: :u32)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Reads signed 32-bit integers from the mapped range.
|
|
422
|
+
#
|
|
423
|
+
# @param count [Integer, nil] values to read; defaults to the full range
|
|
424
|
+
# @return [Array<Integer>] decoded values
|
|
425
|
+
def read_int32s(count = nil)
|
|
426
|
+
read(type: :i32, count:)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Writes signed 32-bit integers into the mapped range.
|
|
430
|
+
#
|
|
431
|
+
# @param data [Array<Integer>] values to write
|
|
432
|
+
# @return [void]
|
|
433
|
+
# @raise [ArgumentError] if the data exceeds the mapped range
|
|
434
|
+
def write_int32s(data)
|
|
435
|
+
write(data, type: :i32)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# Reads 64-bit floating-point values from the mapped range.
|
|
439
|
+
#
|
|
440
|
+
# @param count [Integer, nil] values to read; defaults to the full range
|
|
441
|
+
# @return [Array<Float>] decoded values
|
|
442
|
+
def read_float64s(count = nil)
|
|
443
|
+
read(type: :f64, count:)
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Writes 64-bit floating-point values into the mapped range.
|
|
447
|
+
#
|
|
448
|
+
# @param data [Array<Numeric>] values to write
|
|
449
|
+
# @return [void]
|
|
450
|
+
# @raise [ArgumentError] if the data exceeds the mapped range
|
|
451
|
+
def write_float64s(data)
|
|
452
|
+
write(data, type: :f64)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Reads unsigned 16-bit integers from the mapped range.
|
|
456
|
+
#
|
|
457
|
+
# @param count [Integer, nil] values to read; defaults to the full range
|
|
458
|
+
# @return [Array<Integer>] decoded values
|
|
459
|
+
def read_uint16s(count = nil)
|
|
460
|
+
read(type: :u16, count:)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# Writes unsigned 16-bit integers into the mapped range.
|
|
464
|
+
#
|
|
465
|
+
# @param data [Array<Integer>] values to write
|
|
466
|
+
# @return [void]
|
|
467
|
+
# @raise [ArgumentError] if the data exceeds the mapped range
|
|
468
|
+
def write_uint16s(data)
|
|
469
|
+
write(data, type: :u16)
|
|
226
470
|
end
|
|
227
471
|
|
|
472
|
+
# Reads unsigned 8-bit integers from the mapped range.
|
|
473
|
+
#
|
|
474
|
+
# @param count [Integer, nil] values to read; defaults to the full range
|
|
475
|
+
# @return [Array<Integer>] decoded values
|
|
476
|
+
def read_uint8s(count = nil)
|
|
477
|
+
read(type: :u8, count:)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Writes unsigned 8-bit integers into the mapped range.
|
|
481
|
+
#
|
|
482
|
+
# @param data [Array<Integer>] values to write
|
|
483
|
+
# @return [void]
|
|
484
|
+
# @raise [ArgumentError] if the data exceeds the mapped range
|
|
485
|
+
def write_uint8s(data)
|
|
486
|
+
write(data, type: :u8)
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# Decodes typed values from the mapped range.
|
|
490
|
+
# @param type [Symbol] element type
|
|
491
|
+
# @param count [Integer, nil] number of values
|
|
492
|
+
# @return [Array]
|
|
493
|
+
def read(type: :f32, count: nil)
|
|
494
|
+
byte_size = DataTypes.byte_size(type)
|
|
495
|
+
count ||= @size / byte_size
|
|
496
|
+
count = Integer(count)
|
|
497
|
+
raise ArgumentError, "count must be non-negative" if count.negative?
|
|
498
|
+
|
|
499
|
+
bytes_to_read = count * byte_size
|
|
500
|
+
validate_byte_length!(bytes_to_read)
|
|
501
|
+
DataTypes.unpack(@pointer.read_bytes(bytes_to_read), type:)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Encodes typed values into the mapped range.
|
|
505
|
+
# @param data [Array, String] values or bytes to write
|
|
506
|
+
# @param type [Symbol] element type
|
|
507
|
+
# @return [void]
|
|
508
|
+
# @raise [ArgumentError] if the encoded data exceeds the range
|
|
509
|
+
def write(data, type: :f32)
|
|
510
|
+
bytes = data.is_a?(String) ? data : DataTypes.pack(data, type:)
|
|
511
|
+
raise ArgumentError, "data exceeds mapped range" if bytes.bytesize > @size
|
|
512
|
+
|
|
513
|
+
@pointer.put_bytes(0, bytes)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Returns all bytes in the mapped range.
|
|
517
|
+
# @return [String]
|
|
228
518
|
def read_bytes
|
|
229
519
|
@pointer.read_bytes(@size)
|
|
230
520
|
end
|
|
231
521
|
|
|
522
|
+
# Writes bytes at the start of the mapped range.
|
|
523
|
+
# @param data [String] bytes to write
|
|
524
|
+
# @return [void]
|
|
232
525
|
def write_bytes(data)
|
|
526
|
+
validate_byte_length!(data.bytesize)
|
|
233
527
|
@pointer.put_bytes(0, data)
|
|
234
528
|
end
|
|
529
|
+
|
|
530
|
+
private
|
|
531
|
+
|
|
532
|
+
def validate_byte_length!(byte_length)
|
|
533
|
+
return if byte_length <= @size
|
|
534
|
+
|
|
535
|
+
raise ArgumentError, "data exceeds mapped range"
|
|
536
|
+
end
|
|
235
537
|
end
|
|
236
538
|
end
|
|
@@ -2,10 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module WGPU
|
|
4
4
|
class QuerySet
|
|
5
|
-
attr_reader :handle
|
|
5
|
+
attr_reader :handle, :count, :type
|
|
6
6
|
|
|
7
|
+
# Creates a set of GPU queries.
|
|
8
|
+
# @param device [Device] owning device
|
|
9
|
+
# @param type [Symbol, Integer] query type
|
|
10
|
+
# @param count [Integer] number of queries
|
|
11
|
+
# @raise [ResourceError] if native creation fails
|
|
7
12
|
def initialize(device, label: nil, type:, count:)
|
|
8
13
|
@device = device
|
|
14
|
+
@count = Integer(count)
|
|
15
|
+
type_value = Native::EnumHelper.coerce(Native::QueryType, type, name: "query type")
|
|
16
|
+
@type = Native::QueryType[type_value]
|
|
17
|
+
@destroyed = false
|
|
9
18
|
|
|
10
19
|
desc = Native::QuerySetDescriptor.new
|
|
11
20
|
desc[:next_in_chain] = nil
|
|
@@ -17,28 +26,33 @@ module WGPU
|
|
|
17
26
|
desc[:label][:data] = nil
|
|
18
27
|
desc[:label][:length] = 0
|
|
19
28
|
end
|
|
20
|
-
desc[:type] =
|
|
21
|
-
desc[:count] = count
|
|
29
|
+
desc[:type] = type_value
|
|
30
|
+
desc[:count] = @count
|
|
22
31
|
|
|
23
32
|
@handle = Native.wgpuDeviceCreateQuerySet(device.handle, desc)
|
|
24
33
|
raise ResourceError, "Failed to create query set" if @handle.null?
|
|
25
34
|
end
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def type
|
|
32
|
-
Native.wgpuQuerySetGetType(@handle)
|
|
33
|
-
end
|
|
34
|
-
|
|
36
|
+
# Destroys the query storage once.
|
|
37
|
+
# @return [void]
|
|
35
38
|
def destroy
|
|
39
|
+
return if @destroyed
|
|
40
|
+
|
|
36
41
|
Native.wgpuQuerySetDestroy(@handle)
|
|
42
|
+
@destroyed = true
|
|
37
43
|
end
|
|
38
44
|
|
|
45
|
+
# Releases the native query set handle.
|
|
46
|
+
#
|
|
47
|
+
# A query set destroyed through {#destroy} is only marked released because
|
|
48
|
+
# the pinned native implementation cannot safely release it afterward.
|
|
49
|
+
# @return [void]
|
|
39
50
|
def release
|
|
40
51
|
return if @handle.null?
|
|
41
|
-
|
|
52
|
+
|
|
53
|
+
# Pinned wgpu-native v27 removes a destroyed query set from its storage;
|
|
54
|
+
# calling Release afterward aborts inside Rust instead of being harmless.
|
|
55
|
+
Native.wgpuQuerySetRelease(@handle) unless @destroyed
|
|
42
56
|
@handle = FFI::Pointer::NULL
|
|
43
57
|
end
|
|
44
58
|
end
|