wgpu 1.2.1 → 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 +26 -0
- data/docs/async.md +3 -1
- data/docs/errors.md +13 -0
- data/docs/getting_started_rendering.md +5 -0
- data/docs/resource_lifetime.md +9 -0
- data/lib/wgpu/commands/command_encoder.rb +24 -26
- data/lib/wgpu/commands/compute_pass.rb +6 -6
- data/lib/wgpu/commands/render_bundle_encoder.rb +8 -8
- data/lib/wgpu/commands/render_pass.rb +14 -14
- data/lib/wgpu/core/adapter.rb +6 -3
- data/lib/wgpu/core/async_waiter.rb +15 -4
- data/lib/wgpu/core/canvas_context.rb +5 -2
- data/lib/wgpu/core/device.rb +112 -55
- data/lib/wgpu/core/queue.rb +7 -22
- data/lib/wgpu/core/surface.rb +6 -6
- data/lib/wgpu/descriptor_helpers.rb +22 -0
- data/lib/wgpu/native/functions.rb +3 -0
- data/lib/wgpu/native/installer.rb +7 -1
- data/lib/wgpu/native_resource.rb +16 -0
- data/lib/wgpu/pipeline/bind_group.rb +7 -7
- data/lib/wgpu/pipeline/bind_group_layout.rb +3 -3
- data/lib/wgpu/pipeline/compute_pipeline.rb +5 -5
- data/lib/wgpu/pipeline/pipeline_layout.rb +4 -4
- data/lib/wgpu/pipeline/render_pipeline.rb +6 -6
- data/lib/wgpu/pipeline/shader_module.rb +3 -3
- data/lib/wgpu/resources/buffer.rb +83 -39
- data/lib/wgpu/resources/query_set.rb +1 -1
- data/lib/wgpu/resources/sampler.rb +3 -3
- data/lib/wgpu/resources/texture.rb +4 -6
- data/lib/wgpu/resources/texture_view.rb +1 -1
- data/lib/wgpu/version.rb +1 -1
- data/sig/wgpu.rbs +1 -0
- metadata +10 -4
|
@@ -1,5 +1,7 @@
|
|
|
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
|
|
@@ -22,7 +24,9 @@ module WGPU
|
|
|
22
24
|
@mapped = mapped_at_creation
|
|
23
25
|
@map_state = mapped_at_creation ? :mapped : :unmapped
|
|
24
26
|
@map_generation = 0
|
|
25
|
-
@map_state_mutex =
|
|
27
|
+
@map_state_mutex = Monitor.new
|
|
28
|
+
@mapped_offset = 0
|
|
29
|
+
@mapped_size = mapped_at_creation ? size : 0
|
|
26
30
|
|
|
27
31
|
desc, keepalive = build_descriptor(
|
|
28
32
|
label:,
|
|
@@ -32,9 +36,9 @@ module WGPU
|
|
|
32
36
|
)
|
|
33
37
|
@descriptor_keepalive = keepalive
|
|
34
38
|
|
|
35
|
-
device.
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
error = device.send(:capture_error_scope) do
|
|
40
|
+
@handle = Native.wgpuDeviceCreateBuffer(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
41
|
+
end
|
|
38
42
|
@descriptor_keepalive = nil
|
|
39
43
|
|
|
40
44
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
@@ -52,21 +56,23 @@ module WGPU
|
|
|
52
56
|
ptr, byte_size = DataTypes.to_pointer(data, type:)
|
|
53
57
|
DataTypes.validate_alignment!(offset, 4, name: "offset")
|
|
54
58
|
DataTypes.validate_alignment!(byte_size, 4, name: "data size")
|
|
55
|
-
Native.wgpuQueueWriteBuffer(@device.queue
|
|
59
|
+
Native.wgpuQueueWriteBuffer(NativeResource.checked_handle(@device.queue, expected_class: Queue), @handle, offset, ptr, byte_size)
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
# Returns a writable view of a mapped byte range.
|
|
59
63
|
# @return [BufferMappedRange]
|
|
60
64
|
# @raise [BufferError] if the buffer is not mapped or no range is available
|
|
61
65
|
def mapped_range(offset: 0, size: nil)
|
|
62
|
-
|
|
66
|
+
map_state_mutex.synchronize do
|
|
67
|
+
raise BufferError, "Buffer is not mapped" unless @mapped
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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?
|
|
68
73
|
|
|
69
|
-
|
|
74
|
+
BufferMappedRange.new(ptr, size, buffer: self, generation: current_map_generation)
|
|
75
|
+
end
|
|
70
76
|
end
|
|
71
77
|
|
|
72
78
|
# Returns a writable view of a mapped byte range.
|
|
@@ -78,11 +84,9 @@ module WGPU
|
|
|
78
84
|
# Unmaps the buffer and invalidates its mapped ranges.
|
|
79
85
|
# @return [void]
|
|
80
86
|
def unmap
|
|
81
|
-
Native.wgpuBufferUnmap(@handle)
|
|
82
87
|
map_state_mutex.synchronize do
|
|
83
|
-
@
|
|
84
|
-
|
|
85
|
-
@map_state = :unmapped
|
|
88
|
+
Native.wgpuBufferUnmap(@handle)
|
|
89
|
+
invalidate_map_state
|
|
86
90
|
end
|
|
87
91
|
end
|
|
88
92
|
|
|
@@ -92,6 +96,7 @@ module WGPU
|
|
|
92
96
|
# @return [Boolean] true when mapped
|
|
93
97
|
# @raise [BufferError] if mapping fails
|
|
94
98
|
def map_sync(mode, offset: 0, size: nil, timeout: nil)
|
|
99
|
+
timeout = AsyncWaiter.normalize_timeout(timeout)
|
|
95
100
|
status_holder, _callback_token, future, generation =
|
|
96
101
|
begin_map_request(mode, offset: offset, size: size)
|
|
97
102
|
wait_for_map(status_holder, future, timeout:)
|
|
@@ -114,14 +119,16 @@ module WGPU
|
|
|
114
119
|
# @return [String] mapped bytes
|
|
115
120
|
# @raise [BufferError] if the buffer is not mapped
|
|
116
121
|
def read_mapped_data(offset: 0, size: nil)
|
|
117
|
-
|
|
122
|
+
map_state_mutex.synchronize do
|
|
123
|
+
raise BufferError, "Buffer is not mapped" unless @mapped
|
|
118
124
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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?
|
|
123
129
|
|
|
124
|
-
|
|
130
|
+
ptr.read_bytes(size)
|
|
131
|
+
end
|
|
125
132
|
end
|
|
126
133
|
|
|
127
134
|
# Reads mapped bytes, optionally decoding typed values.
|
|
@@ -137,14 +144,16 @@ module WGPU
|
|
|
137
144
|
# @param type [Symbol] source element type
|
|
138
145
|
# @return [void]
|
|
139
146
|
def write_mapped(data, offset: 0, type: :f32)
|
|
140
|
-
|
|
147
|
+
map_state_mutex.synchronize do
|
|
148
|
+
raise BufferError, "Buffer is not mapped" unless @mapped
|
|
141
149
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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?
|
|
146
154
|
|
|
147
|
-
|
|
155
|
+
target.put_bytes(0, ptr.read_bytes(byte_size))
|
|
156
|
+
end
|
|
148
157
|
end
|
|
149
158
|
|
|
150
159
|
# Reads mapped bytes as 32-bit floating-point values.
|
|
@@ -228,8 +237,10 @@ module WGPU
|
|
|
228
237
|
# Destroys the buffer's storage.
|
|
229
238
|
# @return [void]
|
|
230
239
|
def destroy
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
map_state_mutex.synchronize do
|
|
241
|
+
Native.wgpuBufferDestroy(@handle)
|
|
242
|
+
invalidate_map_state
|
|
243
|
+
end
|
|
233
244
|
end
|
|
234
245
|
|
|
235
246
|
# Releases the native buffer handle.
|
|
@@ -237,10 +248,12 @@ module WGPU
|
|
|
237
248
|
# Calling this method more than once has no effect.
|
|
238
249
|
# @return [void]
|
|
239
250
|
def release
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
244
257
|
end
|
|
245
258
|
|
|
246
259
|
private
|
|
@@ -263,7 +276,11 @@ module WGPU
|
|
|
263
276
|
|
|
264
277
|
status_holder = { done: false, status: nil, message: nil }
|
|
265
278
|
generation = map_state_mutex.synchronize do
|
|
279
|
+
raise BufferError, "Buffer is already mapped or mapping" if @mapped || @map_state == :pending
|
|
280
|
+
|
|
266
281
|
@map_generation = current_map_generation + 1
|
|
282
|
+
@mapped_offset = offset
|
|
283
|
+
@mapped_size = size
|
|
267
284
|
@map_state = :pending
|
|
268
285
|
@map_generation
|
|
269
286
|
end
|
|
@@ -351,7 +368,7 @@ module WGPU
|
|
|
351
368
|
end
|
|
352
369
|
|
|
353
370
|
def map_state_mutex
|
|
354
|
-
@map_state_mutex ||=
|
|
371
|
+
@map_state_mutex ||= Monitor.new
|
|
355
372
|
end
|
|
356
373
|
|
|
357
374
|
def normalize_usage(usage)
|
|
@@ -363,6 +380,25 @@ module WGPU
|
|
|
363
380
|
"create buffer#{context}: #{message}"
|
|
364
381
|
end
|
|
365
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
|
+
|
|
366
402
|
def validate_map_range!(offset, size)
|
|
367
403
|
offset = DataTypes.validate_alignment!(offset, 8, name: "map offset")
|
|
368
404
|
size = DataTypes.validate_alignment!(size, 4, name: "map size")
|
|
@@ -379,9 +415,13 @@ module WGPU
|
|
|
379
415
|
# Wraps a native mapped memory range.
|
|
380
416
|
# @param pointer [FFI::Pointer] start of mapped memory
|
|
381
417
|
# @param size [Integer] range size in bytes
|
|
382
|
-
|
|
418
|
+
# @param buffer [Buffer] owner retaining the mapped allocation
|
|
419
|
+
# @param generation [Integer] mapping generation at acquisition
|
|
420
|
+
def initialize(pointer, size, buffer:, generation:)
|
|
383
421
|
@pointer = pointer
|
|
384
422
|
@size = size
|
|
423
|
+
@buffer = buffer
|
|
424
|
+
@generation = generation
|
|
385
425
|
end
|
|
386
426
|
|
|
387
427
|
# Reads 32-bit floating-point values from the mapped range.
|
|
@@ -498,7 +538,7 @@ module WGPU
|
|
|
498
538
|
|
|
499
539
|
bytes_to_read = count * byte_size
|
|
500
540
|
validate_byte_length!(bytes_to_read)
|
|
501
|
-
DataTypes.unpack(@pointer.read_bytes(bytes_to_read), type:)
|
|
541
|
+
DataTypes.unpack(with_valid_mapping { @pointer.read_bytes(bytes_to_read) }, type:)
|
|
502
542
|
end
|
|
503
543
|
|
|
504
544
|
# Encodes typed values into the mapped range.
|
|
@@ -510,13 +550,13 @@ module WGPU
|
|
|
510
550
|
bytes = data.is_a?(String) ? data : DataTypes.pack(data, type:)
|
|
511
551
|
raise ArgumentError, "data exceeds mapped range" if bytes.bytesize > @size
|
|
512
552
|
|
|
513
|
-
@pointer.put_bytes(0, bytes)
|
|
553
|
+
with_valid_mapping { @pointer.put_bytes(0, bytes) }
|
|
514
554
|
end
|
|
515
555
|
|
|
516
556
|
# Returns all bytes in the mapped range.
|
|
517
557
|
# @return [String]
|
|
518
558
|
def read_bytes
|
|
519
|
-
@pointer.read_bytes(@size)
|
|
559
|
+
with_valid_mapping { @pointer.read_bytes(@size) }
|
|
520
560
|
end
|
|
521
561
|
|
|
522
562
|
# Writes bytes at the start of the mapped range.
|
|
@@ -524,11 +564,15 @@ module WGPU
|
|
|
524
564
|
# @return [void]
|
|
525
565
|
def write_bytes(data)
|
|
526
566
|
validate_byte_length!(data.bytesize)
|
|
527
|
-
@pointer.put_bytes(0, data)
|
|
567
|
+
with_valid_mapping { @pointer.put_bytes(0, data) }
|
|
528
568
|
end
|
|
529
569
|
|
|
530
570
|
private
|
|
531
571
|
|
|
572
|
+
def with_valid_mapping(&block)
|
|
573
|
+
@buffer.send(:with_mapped_generation, @generation, &block)
|
|
574
|
+
end
|
|
575
|
+
|
|
532
576
|
def validate_byte_length!(byte_length)
|
|
533
577
|
return if byte_length <= @size
|
|
534
578
|
|
|
@@ -29,7 +29,7 @@ module WGPU
|
|
|
29
29
|
desc[:type] = type_value
|
|
30
30
|
desc[:count] = @count
|
|
31
31
|
|
|
32
|
-
@handle = Native.wgpuDeviceCreateQuerySet(device
|
|
32
|
+
@handle = Native.wgpuDeviceCreateQuerySet(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
33
33
|
raise ResourceError, "Failed to create query set" if @handle.null?
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -25,9 +25,9 @@ module WGPU
|
|
|
25
25
|
)
|
|
26
26
|
@descriptor_keepalive = keepalive
|
|
27
27
|
|
|
28
|
-
device.
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
error = device.send(:capture_error_scope) do
|
|
29
|
+
@handle = Native.wgpuDeviceCreateSampler(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
30
|
+
end
|
|
31
31
|
@descriptor_keepalive = nil
|
|
32
32
|
|
|
33
33
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
@@ -25,9 +25,9 @@ module WGPU
|
|
|
25
25
|
)
|
|
26
26
|
@descriptor_keepalive = keepalive
|
|
27
27
|
|
|
28
|
-
device.
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
error = device.send(:capture_error_scope) do
|
|
29
|
+
@handle = Native.wgpuDeviceCreateTexture(NativeResource.checked_handle(device, expected_class: Device), desc)
|
|
30
|
+
end
|
|
31
31
|
@descriptor_keepalive = nil
|
|
32
32
|
|
|
33
33
|
if @handle.null? || (error[:type] && error[:type] != :no_error)
|
|
@@ -156,9 +156,7 @@ module WGPU
|
|
|
156
156
|
DescriptorHelpers.set_label(desc, label, keepalive:)
|
|
157
157
|
desc[:usage] = normalize_usage(usage)
|
|
158
158
|
desc[:dimension] = Native::EnumHelper.coerce(Native::TextureDimension, dimension, name: "texture dimension")
|
|
159
|
-
desc[:size]
|
|
160
|
-
desc[:size][:height] = size[:height] || size[1] || 1
|
|
161
|
-
desc[:size][:depth_or_array_layers] = size[:depth_or_array_layers] || size[2] || 1
|
|
159
|
+
desc[:size] = DescriptorHelpers.extent_3d(size)
|
|
162
160
|
desc[:format] = Native::EnumHelper.coerce(Native::TextureFormat, format, name: "texture format")
|
|
163
161
|
desc[:mip_level_count] = mip_level_count
|
|
164
162
|
desc[:sample_count] = sample_count
|
|
@@ -41,7 +41,7 @@ module WGPU
|
|
|
41
41
|
name: "texture view usage"
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
@handle = Native.wgpuTextureCreateView(texture
|
|
44
|
+
@handle = Native.wgpuTextureCreateView(NativeResource.checked_handle(texture, expected_class: Texture), desc)
|
|
45
45
|
raise ResourceError, "Failed to create texture view" if @handle.null?
|
|
46
46
|
end
|
|
47
47
|
|
data/lib/wgpu/version.rb
CHANGED
data/sig/wgpu.rbs
CHANGED
|
@@ -248,6 +248,7 @@ module WGPU
|
|
|
248
248
|
end
|
|
249
249
|
|
|
250
250
|
class BufferMappedRange
|
|
251
|
+
def initialize: (untyped pointer, Integer size, buffer: Buffer, generation: Integer) -> void
|
|
251
252
|
def read: (?type: Symbol, ?count: Integer?) -> Array[Numeric]
|
|
252
253
|
def write: (data, ?type: Symbol) -> void
|
|
253
254
|
def read_floats: (?Integer?) -> Array[Float]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wgpu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yudai Takada
|
|
@@ -27,16 +27,22 @@ dependencies:
|
|
|
27
27
|
name: rubyzip
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: '2.3'
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '4.0'
|
|
33
36
|
type: :runtime
|
|
34
37
|
prerelease: false
|
|
35
38
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
39
|
requirements:
|
|
37
|
-
- - "
|
|
40
|
+
- - ">="
|
|
38
41
|
- !ruby/object:Gem::Version
|
|
39
42
|
version: '2.3'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '4.0'
|
|
40
46
|
description: Ruby bindings for the WebGPU API, providing GPU compute and graphics
|
|
41
47
|
capabilities through the wgpu-native library.
|
|
42
48
|
email:
|
|
@@ -138,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
138
144
|
- !ruby/object:Gem::Version
|
|
139
145
|
version: '0'
|
|
140
146
|
requirements: []
|
|
141
|
-
rubygems_version: 4.0.
|
|
147
|
+
rubygems_version: 4.0.19
|
|
142
148
|
specification_version: 4
|
|
143
149
|
summary: Ruby bindings for WebGPU via wgpu-native
|
|
144
150
|
test_files: []
|