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.
@@ -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 = Mutex.new
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.push_error_scope(:validation)
36
- @handle = Native.wgpuDeviceCreateBuffer(device.handle, desc)
37
- error = device.pop_error_scope
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.handle, @handle, offset, ptr, byte_size)
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
- raise BufferError, "Buffer is not mapped" unless @mapped
66
+ map_state_mutex.synchronize do
67
+ raise BufferError, "Buffer is not mapped" unless @mapped
63
68
 
64
- size ||= @size - offset
65
- offset, size = validate_map_range!(offset, size)
66
- ptr = Native.wgpuBufferGetMappedRange(@handle, offset, size)
67
- raise BufferError, "Failed to get mapped range" if ptr.null?
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
- BufferMappedRange.new(ptr, size)
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
- @map_generation = current_map_generation + 1
84
- @mapped = false
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
- raise BufferError, "Buffer is not mapped" unless @mapped
122
+ map_state_mutex.synchronize do
123
+ raise BufferError, "Buffer is not mapped" unless @mapped
118
124
 
119
- size ||= @size - offset
120
- offset, size = validate_map_range!(offset, size)
121
- ptr = Native.wgpuBufferGetConstMappedRange(@handle, offset, size)
122
- raise BufferError, "Failed to get mapped range" if ptr.null?
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
- ptr.read_bytes(size)
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
- raise BufferError, "Buffer is not mapped" unless @mapped
147
+ map_state_mutex.synchronize do
148
+ raise BufferError, "Buffer is not mapped" unless @mapped
141
149
 
142
- ptr, byte_size = DataTypes.to_pointer(data, type:)
143
- offset, byte_size = validate_map_range!(offset, byte_size)
144
- target = Native.wgpuBufferGetMappedRange(@handle, offset, byte_size)
145
- raise BufferError, "Failed to get mapped range" if target.null?
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
- target.put_bytes(0, ptr.read_bytes(byte_size))
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
- Native.wgpuBufferDestroy(@handle)
232
- invalidate_map_state
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
- return if @handle.null?
241
- Native.wgpuBufferRelease(@handle)
242
- @handle = FFI::Pointer::NULL
243
- invalidate_map_state
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 ||= Mutex.new
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
- def initialize(pointer, size)
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.handle, desc)
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.push_error_scope(:validation)
29
- @handle = Native.wgpuDeviceCreateSampler(device.handle, desc)
30
- error = device.pop_error_scope
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.push_error_scope(:validation)
29
- @handle = Native.wgpuDeviceCreateTexture(device.handle, desc)
30
- error = device.pop_error_scope
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][:width] = size[:width] || size[0]
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.handle, desc)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WGPU
4
- VERSION = "1.2.1"
4
+ VERSION = "1.2.2"
5
5
  end
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.1
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.6
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: []