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