Class: WGPU::Queue

Inherits:
Object
  • Object
show all
Includes:
NativeResource
Defined in:
lib/wgpu/core/queue.rb,
sig/wgpu.rbs

Constant Summary

Constants included from NativeResource

NativeResource::GUARDED_METHOD_EXEMPTIONS

Instance Attribute Summary collapse

Attributes included from NativeResource

#label

Instance Method Summary collapse

Methods included from NativeResource

included, #inspect, #released?, #use

Constructor Details

#initialize(handle, device: nil) ⇒ Queue

Wraps a device's native submission queue.

Parameters:

  • handle (FFI::Pointer)

    native queue handle

  • device (Device, nil) (defaults to: nil)

    owning device



10
11
12
13
# File 'lib/wgpu/core/queue.rb', line 10

def initialize(handle, device: nil)
  @handle = handle
  @device = device
end

Instance Attribute Details

#handleObject (readonly)

Returns the value of attribute handle.

Returns:

  • (Object)


5
6
7
# File 'lib/wgpu/core/queue.rb', line 5

def handle
  @handle
end

Instance Method Details

#on_submitted_work_done(device: nil, timeout: nil) ⇒ Symbol

Waits until all previously submitted queue work completes.

Parameters:

  • device (Device, nil) (defaults to: nil)

    device used to drive callback progress

  • timeout (Numeric, nil) (defaults to: nil)

    maximum wait time in seconds

  • device: (Device, nil) (defaults to: nil)
  • timeout: (Numeric, nil) (defaults to: nil)

Returns:

  • (Symbol)

    native completion status



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/wgpu/core/queue.rb', line 208

def (device: nil, timeout: nil)
  device ||= @device
  instance = device&.adapter&.instance
  status_holder = { done: false, status: nil }

  callback_lifetime_release = device_callback_lifetime_lease
  callback_token = nil
  callback = FFI::Function.new(
    :void, [:uint32, :pointer, :pointer]
  ) do |status, _userdata1, _userdata2|
    begin
      status_holder[:status] = Native::QueueWorkDoneStatus[status]
      status_holder[:done] = true
    ensure
      CallbackKeepalive.release(self, callback_token)
      callback_lifetime_release.call
    end
  end

  callback_info = Native::QueueWorkDoneCallbackInfo.new
  callback_info[:next_in_chain] = nil
  callback_info[:mode] = AsyncWaiter.callback_mode(instance: instance)
  callback_info[:callback] = callback
  callback_info[:userdata1] = nil
  callback_info[:userdata2] = nil

  callback_token = CallbackKeepalive.retain(self, callback)
  future =
    begin
      Native.wgpuQueueOnSubmittedWorkDone(@handle, callback_info)
    rescue StandardError
      CallbackKeepalive.release(self, callback_token)
      callback_lifetime_release.call
      raise
    end
  AsyncWaiter.wait(
    status_holder: status_holder,
    instance: instance,
    device: device,
    future: future,
    timeout: timeout
  )

  status_holder[:status]
end

#on_submitted_work_done_async(device: nil, timeout: nil) ⇒ AsyncTask

Waits for prior queue work on a background task.

Parameters:

  • device: (Device, nil) (defaults to: nil)
  • timeout: (Numeric, nil) (defaults to: nil)

Returns:

  • (AsyncTask)

    task yielding the native completion status



256
257
258
259
260
# File 'lib/wgpu/core/queue.rb', line 256

def (device: nil, timeout: nil)
  AsyncTask.new do
    (device: device, timeout: timeout)
  end
end

#read_buffer(buffer, offset: 0, size: nil, device: nil, staging: nil) ⇒ String

Copies a GPU buffer to mapped staging memory and returns its bytes.

Parameters:

  • buffer (Buffer)

    source buffer

  • staging (Buffer, nil) (defaults to: nil)

    reusable map-read staging buffer

  • (Buffer)
  • offset: (Integer) (defaults to: 0)
  • size: (Integer, nil) (defaults to: nil)
  • device: (Device, nil) (defaults to: nil)
  • staging: (Buffer, nil) (defaults to: nil)

Returns:

  • (String)

    copied bytes



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/wgpu/core/queue.rb', line 108

def read_buffer(buffer, offset: 0, size: nil, device: nil, staging: nil)
  device ||= @device
  raise ArgumentError, "device is required when the queue has no owning device" unless device

  size ||= buffer.size - offset
  owns_staging = staging.nil?
  staging ||= Buffer.new(device, size: size, usage: [:map_read, :copy_dst])
  validate_readback_staging!(staging, size)
  encoder = CommandEncoder.new(device)
  encoder.copy_buffer_to_buffer(
    source: buffer,
    source_offset: offset,
    destination: staging,
    destination_offset: 0,
    size: size
  )
  command_buffer = encoder.finish
  submit([command_buffer])

  map_requested = true
  staging.map_sync(:read)
  staging.read_mapped_data(size:)
ensure
  cleanup_readback_resources(
    staging:,
    unmap_staging: map_requested,
    owns_staging:,
    command_buffer:,
    encoder:,
    active_error: $!
  )
end

#read_texture(source:, data_layout:, size:, device:, staging:) ⇒ String #read_texture(arg0) ⇒ String

Copies a texture region to mapped staging memory and returns its padded bytes.

Overloads:

  • #read_texture(source:, data_layout:, size:, device:, staging:) ⇒ String

    Parameters:

    • source: (texture_copy)
    • data_layout: (texture_data_layout)
    • size: (extent_3d)
    • device: (Device, nil)
    • staging: (Buffer, nil)

    Returns:

    • (String)
  • #read_texture(arg0) ⇒ String

    Parameters:

    • arg0 (Object)

    Returns:

    • (String)

Parameters:

  • source (Hash)

    source texture, origin, aspect, and optional format

  • data_layout (Hash)

    destination byte layout

  • size (Hash, Array)

    extent to read

  • staging (Buffer, nil) (defaults to: nil)

    reusable map-read staging buffer

Returns:

  • (String)

    copied bytes



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/wgpu/core/queue.rb', line 147

def read_texture(source:, data_layout:, size:, device: nil, staging: nil)
  device ||= @device
  raise ArgumentError, "device is required when the queue has no owning device" unless device

  width, height, depth = texture_extent(size)
  bytes_per_row = data_layout[:bytes_per_row]
  raise ArgumentError, "data_layout[:bytes_per_row] is required" unless bytes_per_row

  DataTypes.validate_alignment!(
    bytes_per_row,
    TextureFormat::COPY_ALIGNMENT,
    name: "bytes_per_row"
  )
  format = source[:format] || source[:texture].format
  aspect = source[:aspect] || :all
  tight_bytes_per_row = TextureFormat.bytes_per_row(width, format, aspect:)
  minimum_bytes_per_row = TextureFormat.aligned_bytes_per_row(width, format, aspect:)
  if bytes_per_row < minimum_bytes_per_row
    raise ArgumentError,
      "bytes_per_row must be at least #{minimum_bytes_per_row} for width #{width} and #{format.inspect} " \
      "(tight row is #{tight_bytes_per_row} bytes)"
  end

  rows_per_image = data_layout[:rows_per_image] || height
  buffer_size = bytes_per_row * rows_per_image * depth

  owns_staging = staging.nil?
  staging ||= Buffer.new(device, size: buffer_size, usage: [:map_read, :copy_dst])
  validate_readback_staging!(staging, buffer_size)
  encoder = CommandEncoder.new(device)
  encoder.copy_texture_to_buffer(
    source: source,
    destination: {
      buffer: staging,
      offset: 0,
      bytes_per_row: bytes_per_row,
      rows_per_image: rows_per_image
    },
    copy_size: size
  )
  command_buffer = encoder.finish
  submit([command_buffer])

  map_requested = true
  staging.map_sync(:read)
  staging.read_mapped_data(size: buffer_size)
ensure
  cleanup_readback_resources(
    staging:,
    unmap_staging: map_requested,
    owns_staging:,
    command_buffer:,
    encoder:,
    active_error: $!
  )
end

#releasevoid

This method returns an undefined value.

Releases the native queue handle.

Calling this method more than once has no effect.



266
267
268
269
270
# File 'lib/wgpu/core/queue.rb', line 266

def release
  return if @handle.null?
  Native.wgpuQueueRelease(@handle)
  @handle = FFI::Pointer::NULL
end

#submit(command_buffers) ⇒ void

This method returns an undefined value.

Submits command buffers exactly once in the supplied order.

Parameters:

Raises:

  • (CommandError)

    for duplicate or previously submitted buffers



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/wgpu/core/queue.rb', line 19

def submit(command_buffers)
  buffers = Array(command_buffers)
  return if buffers.empty?

  if buffers.map(&:object_id).uniq.length != buffers.length
    raise CommandError, "The same command buffer cannot appear twice in one submission"
  end
  buffers.each do |buffer|
    raise CommandError, "Command buffer has already been submitted" if buffer.
  end

  handles = buffers.map(&:handle)
  ptr = FFI::MemoryPointer.new(:pointer, handles.size)
  ptr.write_array_of_pointer(handles)

  Native.wgpuQueueSubmit(@handle, handles.size, ptr)
  buffers.each(&:mark_submitted!)
end

#write_buffer(buffer, buffer_offset, data, data_offset: 0, size: nil, type: :f32) ⇒ void

This method returns an undefined value.

Writes typed data into a GPU buffer.

Parameters:

  • buffer (Buffer)

    destination buffer

  • buffer_offset (Integer)

    destination byte offset

  • data (Array, String, FFI::Pointer)

    source data

  • (Buffer)
  • (Integer)
  • (data)
  • data_offset: (Integer) (defaults to: 0)
  • size: (Integer, nil) (defaults to: nil)
  • type: (Symbol) (defaults to: :f32)

Raises:

  • (ArgumentError)


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/wgpu/core/queue.rb', line 43

def write_buffer(buffer, buffer_offset, data, data_offset: 0, size: nil, type: :f32)
  data_ptr, byte_size = DataTypes.to_pointer(data, type:)
  DataTypes.validate_alignment!(buffer_offset, 4, name: "buffer_offset")
  data_offset = Integer(data_offset)
  raise ArgumentError, "data_offset must be non-negative" if data_offset.negative?
  raise ArgumentError, "data_offset out of range" if data_offset > byte_size

  write_size = size.nil? ? (byte_size - data_offset) : Integer(size)
  raise ArgumentError, "size must be non-negative" if write_size.negative?
  raise ArgumentError, "data_offset + size out of range" if data_offset + write_size > byte_size
  DataTypes.validate_alignment!(write_size, 4, name: "size")

  Native.wgpuQueueWriteBuffer(
    @handle,
    buffer.handle,
    buffer_offset,
    data_ptr + data_offset,
    write_size
  )
end

#write_texture(destination:, data:, data_layout:, size:, type: :f32) ⇒ void

This method returns an undefined value.

Writes typed data into a texture region.

Parameters:

  • destination (Hash)

    destination texture and origin

  • data (Array, String, FFI::Pointer)

    source data

  • data_layout (Hash)

    source byte layout

  • size (Hash, Array)

    extent to write

  • (Object)


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/wgpu/core/queue.rb', line 70

def write_texture(destination:, data:, data_layout:, size:, type: :f32)
  data_ptr, byte_size = DataTypes.to_pointer(data, type:)

  dst = Native::ImageCopyTexture.new
  dst[:texture] = destination[:texture].handle
  dst[:mip_level] = destination[:mip_level] || 0
  dst[:origin][:x] = destination.dig(:origin, :x) || 0
  dst[:origin][:y] = destination.dig(:origin, :y) || 0
  dst[:origin][:z] = destination.dig(:origin, :z) || 0
  dst[:aspect] = Native::EnumHelper.coerce(
    Native::TextureAspect,
    destination[:aspect] || :all,
    name: "texture aspect"
  )

  extent = Native::Extent3D.new
  if size.is_a?(Array)
    extent[:width] = size[0]
    extent[:height] = size[1] || 1
    extent[:depth_or_array_layers] = size[2] || 1
  else
    extent[:width] = size[:width]
    extent[:height] = size[:height] || 1
    extent[:depth_or_array_layers] = size[:depth_or_array_layers] || 1
  end

  layout = Native::TextureDataLayout.new
  layout[:offset] = data_layout[:offset] || 0
  layout[:bytes_per_row] = data_layout[:bytes_per_row]
  layout[:rows_per_image] = data_layout[:rows_per_image] || extent[:height]

  Native.wgpuQueueWriteTexture(@handle, dst, data_ptr, byte_size, layout, extent)
end