#[deprecated(
since = "24.0.0",
note = "This has been renamed to `TexelCopyBufferInfo`, and will be removed in 25.0.0."
)] pubtype ImageCopyBuffer = wgt::TexelCopyBufferInfo<BufferId>;
#[deprecated(
since = "24.0.0",
note = "This has been renamed to `TexelCopyTextureInfo`, and will be removed in 25.0.0."
)] pubtype ImageCopyTexture = wgt::TexelCopyTextureInfo<TextureId>;
#[deprecated(
since = "24.0.0",
note = "This has been renamed to `TexelCopyTextureSourceInfo`, and will be removed in 25.0.0."
)] pubtype ImageCopyTextureTagged = wgt::CopyExternalImageDestInfo<TextureId>;
/// Error encountered while attempting a data transfer. #[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum TransferError { #[error("Source and destination cannot be the same buffer")]
SameSourceDestinationBuffer, #[error(transparent)]
MissingBufferUsage(#[from] MissingBufferUsageError), #[error(transparent)]
MissingTextureUsage(#[from] MissingTextureUsageError), #[error("Copy of {start_offset}..{end_offset} would end up overrunning the bounds of the {side:?} buffer of size {buffer_size}")]
BufferOverrun {
start_offset: BufferAddress,
end_offset: BufferAddress,
buffer_size: BufferAddress,
side: CopySide,
}, #[error("Copy of {dimension:?} {start_offset}..{end_offset} would end up overrunning the bounds of the {side:?} texture of {dimension:?} size {texture_size}")]
TextureOverrun {
start_offset: u32,
end_offset: u32,
texture_size: u32,
dimension: TextureErrorDimension,
side: CopySide,
}, #[error("Unable to select texture aspect {aspect:?} from format {format:?}")]
InvalidTextureAspect {
format: wgt::TextureFormat,
aspect: wgt::TextureAspect,
}, #[error("Unable to select texture mip level {level} out of {total}")]
InvalidTextureMipLevel { level: u32, total: u32 }, #[error("Texture dimension must be 2D when copying from an external texture")]
InvalidDimensionExternal, #[error("Buffer offset {0} is not aligned to block size or `COPY_BUFFER_ALIGNMENT`")]
UnalignedBufferOffset(BufferAddress), #[error("Copy size {0} does not respect `COPY_BUFFER_ALIGNMENT`")]
UnalignedCopySize(BufferAddress), #[error("Copy width is not a multiple of block width")]
UnalignedCopyWidth, #[error("Copy height is not a multiple of block height")]
UnalignedCopyHeight, #[error("Copy origin's x component is not a multiple of block width")]
UnalignedCopyOriginX, #[error("Copy origin's y component is not a multiple of block height")]
UnalignedCopyOriginY, #[error("Bytes per row does not respect `COPY_BYTES_PER_ROW_ALIGNMENT`")]
UnalignedBytesPerRow, #[error("Number of bytes per row needs to be specified since more than one row is copied")]
UnspecifiedBytesPerRow, #[error("Number of rows per image needs to be specified since more than one image is copied")]
UnspecifiedRowsPerImage, #[error("Number of bytes per row is less than the number of bytes in a complete row")]
InvalidBytesPerRow, #[error("Number of rows per image is invalid")]
InvalidRowsPerImage, #[error("Copy source aspects must refer to all aspects of the source texture format")]
CopySrcMissingAspects, #[error( "Copy destination aspects must refer to all aspects of the destination texture format"
)]
CopyDstMissingAspects, #[error("Copy aspect must refer to a single aspect of texture format")]
CopyAspectNotOne, #[error("Copying from textures with format {format:?} and aspect {aspect:?} is forbidden")]
CopyFromForbiddenTextureFormat {
format: wgt::TextureFormat,
aspect: wgt::TextureAspect,
}, #[error("Copying to textures with format {format:?} and aspect {aspect:?} is forbidden")]
CopyToForbiddenTextureFormat {
format: wgt::TextureFormat,
aspect: wgt::TextureAspect,
}, #[error( "Copying to textures with format {0:?} is forbidden when copying from external texture"
)]
ExternalCopyToForbiddenTextureFormat(wgt::TextureFormat), #[error( "Source format ({src_format:?}) and destination format ({dst_format:?}) are not copy-compatible (they may only differ in srgb-ness)"
)]
TextureFormatsNotCopyCompatible {
src_format: wgt::TextureFormat,
dst_format: wgt::TextureFormat,
}, #[error(transparent)]
MemoryInitFailure(#[from] ClearError), #[error("Cannot encode this copy because of a missing downelevel flag")]
MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error("Source texture sample count must be 1, got {sample_count}")]
InvalidSampleCount { sample_count: u32 }, #[error("Requested mip level {requested} does no exist (count: {count})")]
InvalidMipLevel { requested: u32, count: u32 },
}
/// Error encountered while attempting to do a copy on a command encoder. #[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CopyError { #[error(transparent)]
Encoder(#[from] CommandEncoderError), #[error("Copy error")]
Transfer(#[from] TransferError), #[error(transparent)]
DestroyedResource(#[from] DestroyedResourceError), #[error(transparent)]
InvalidResource(#[from] InvalidResourceError),
}
pub(crate) fn extract_texture_selector<T>(
copy_texture: &wgt::TexelCopyTextureInfo<T>,
copy_size: &Extent3d,
texture: &Texture,
) -> Result<(TextureSelector, hal::TextureCopyBase), TransferError> { let format = texture.desc.format; let copy_aspect = hal::FormatAspects::new(format, copy_texture.aspect); if copy_aspect.is_empty() { return Err(TransferError::InvalidTextureAspect {
format,
aspect: copy_texture.aspect,
});
}
let (layers, origin_z) = match texture.desc.dimension {
wgt::TextureDimension::D1 => (0..1, 0),
wgt::TextureDimension::D2 => (
copy_texture.origin.z..copy_texture.origin.z + copy_size.depth_or_array_layers, 0,
),
wgt::TextureDimension::D3 => (0..1, copy_texture.origin.z),
}; let base = hal::TextureCopyBase {
origin: wgt::Origin3d {
x: copy_texture.origin.x,
y: copy_texture.origin.y,
z: origin_z,
}, // this value will be incremented per copied layer
array_layer: layers.start,
mip_level: copy_texture.mip_level,
aspect: copy_aspect,
}; let selector = TextureSelector {
mips: copy_texture.mip_level..copy_texture.mip_level + 1,
layers,
};
Ok((selector, base))
}
/// WebGPU's [validating linear texture data][vltd] algorithm. /// /// Copied with some modifications from WebGPU standard. /// /// If successful, returns a pair `(bytes, stride)`, where: /// - `bytes` is the number of buffer bytes required for this copy, and /// - `stride` number of bytes between array layers. /// /// [vltd]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-linear-texture-data pub(crate) fn validate_linear_texture_data(
layout: &wgt::TexelCopyBufferLayout,
format: wgt::TextureFormat,
aspect: wgt::TextureAspect,
buffer_size: BufferAddress,
buffer_side: CopySide,
copy_size: &Extent3d,
need_copy_aligned_rows: bool,
) -> Result<(BufferAddress, BufferAddress), TransferError> { // Convert all inputs to BufferAddress (u64) to avoid some of the overflow issues // Note: u64 is not always enough to prevent overflow, especially when multiplying // something with a potentially large depth value, so it is preferable to validate // the copy size before calling this function (for example via `validate_texture_copy_range`). let copy_width = copy_size.width as BufferAddress; let copy_height = copy_size.height as BufferAddress; let depth_or_array_layers = copy_size.depth_or_array_layers as BufferAddress;
let offset = layout.offset;
let block_size = format.block_copy_size(Some(aspect)).unwrap() as BufferAddress; let (block_width, block_height) = format.block_dimensions(); let block_width = block_width as BufferAddress; let block_height = block_height as BufferAddress;
if copy_width % block_width != 0 { return Err(TransferError::UnalignedCopyWidth);
} if copy_height % block_height != 0 { return Err(TransferError::UnalignedCopyHeight);
}
let width_in_blocks = copy_width / block_width; let height_in_blocks = copy_height / block_height;
let bytes_in_last_row = width_in_blocks * block_size;
let bytes_per_row = iflet Some(bytes_per_row) = layout.bytes_per_row { let bytes_per_row = bytes_per_row as BufferAddress; if bytes_per_row < bytes_in_last_row { return Err(TransferError::InvalidBytesPerRow);
}
bytes_per_row
} else { if depth_or_array_layers > 1 || height_in_blocks > 1 { return Err(TransferError::UnspecifiedBytesPerRow);
} 0
}; let rows_per_image = iflet Some(rows_per_image) = layout.rows_per_image { let rows_per_image = rows_per_image as BufferAddress; if rows_per_image < height_in_blocks { return Err(TransferError::InvalidRowsPerImage);
}
rows_per_image
} else { if depth_or_array_layers > 1 { return Err(TransferError::UnspecifiedRowsPerImage);
} 0
};
if need_copy_aligned_rows { let bytes_per_row_alignment = wgt::COPY_BYTES_PER_ROW_ALIGNMENT as BufferAddress;
/// WebGPU's [validating texture copy range][vtcr] algorithm. /// /// Copied with minor modifications from WebGPU standard. /// /// Returns the HAL copy extent and the layer count. /// /// [vtcr]: https://gpuweb.github.io/gpuweb/#validating-texture-copy-range pub(crate) fn validate_texture_copy_range<T>(
texture_copy_view: &wgt::TexelCopyTextureInfo<T>,
desc: &wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>,
texture_side: CopySide,
copy_size: &Extent3d,
) -> Result<(hal::CopyExtent, u32), TransferError> { let (block_width, block_height) = desc.format.block_dimensions();
let extent_virtual = desc.mip_level_size(texture_copy_view.mip_level).ok_or(
TransferError::InvalidTextureMipLevel {
level: texture_copy_view.mip_level,
total: desc.mip_level_count,
},
)?; // physical size can be larger than the virtual let extent = extent_virtual.physical_size(desc.format);
/// Return `Ok` if a run `size` texels long starting at `start_offset` falls /// entirely within `texture_size`. Otherwise, return an appropriate a`Err`. fn check_dimension(
dimension: TextureErrorDimension,
side: CopySide,
start_offset: u32,
size: u32,
texture_size: u32,
) -> Result<(), TransferError> { // Avoid underflow in the subtraction by checking start_offset against // texture_size first. if start_offset <= texture_size && size <= texture_size - start_offset {
Ok(())
} else {
Err(TransferError::TextureOverrun {
start_offset,
end_offset: start_offset.wrapping_add(size),
texture_size,
dimension,
side,
})
}
}
// Register the init action. let immediate_inits = cmd_buf_data
.texture_memory_actions
.register_init_action(&{ init_action });
// In rare cases we may need to insert an init operation immediately onto the command buffer. if !immediate_inits.is_empty() { let cmd_buf_raw = cmd_buf_data.encoder.open()?; for init in immediate_inits {
clear_texture(
&init.texture,
TextureInitRange {
mip_range: init.mip_level..(init.mip_level + 1),
layer_range: init.layer..(init.layer + 1),
},
cmd_buf_raw,
&mut cmd_buf_data.trackers.textures,
&device.alignments,
device.zero_buffer.as_ref(),
snatch_guard,
)?;
}
}
Ok(())
}
/// Prepare a transfer's source texture. /// /// Ensure the source texture of a transfer is in the right initialization /// state, and record the state for after the transfer operation. fn handle_src_texture_init(
cmd_buf_data: &mut CommandBufferMutable,
device: &Device,
source: &TexelCopyTextureInfo,
copy_size: &Extent3d,
texture: &Arc<Texture>,
snatch_guard: &SnatchGuard<'_>,
) -> Result<(), TransferError> {
handle_texture_init(
MemoryInitKind::NeedsInitializedMemory,
cmd_buf_data,
device,
source,
copy_size,
texture,
snatch_guard,
)?;
Ok(())
}
/// Prepare a transfer's destination texture. /// /// Ensure the destination texture of a transfer is in the right initialization /// state, and record the state for after the transfer operation. fn handle_dst_texture_init(
cmd_buf_data: &mut CommandBufferMutable,
device: &Device,
destination: &TexelCopyTextureInfo,
copy_size: &Extent3d,
texture: &Arc<Texture>,
snatch_guard: &SnatchGuard<'_>,
) -> Result<(), TransferError> { // Attention: If we don't write full texture subresources, we need to a full // clear first since we don't track subrects. This means that in rare cases // even a *destination* texture of a transfer may need an immediate texture // init. let dst_init_kind = if has_copy_partial_init_tracker_coverage(
copy_size,
destination.mip_level,
&texture.desc,
) {
MemoryInitKind::NeedsInitializedMemory
} else {
MemoryInitKind::ImplicitlyInitialized
};
let src_pending = cmd_buf_data
.trackers
.buffers
.set_single(&src_buffer, hal::BufferUses::COPY_SRC);
let src_raw = src_buffer.try_raw(&snatch_guard)?;
src_buffer
.check_usage(BufferUsages::COPY_SRC)
.map_err(TransferError::MissingBufferUsage)?; // expecting only a single barrier let src_barrier = src_pending.map(|pending| pending.into_hal(&src_buffer, &snatch_guard));
let dst_buffer = hub.buffers.get(destination).get()?;
dst_buffer.same_device_as(cmd_buf.as_ref())?;
let dst_pending = cmd_buf_data
.trackers
.buffers
.set_single(&dst_buffer, hal::BufferUses::COPY_DST);
let dst_raw = dst_buffer.try_raw(&snatch_guard)?;
dst_buffer
.check_usage(BufferUsages::COPY_DST)
.map_err(TransferError::MissingBufferUsage)?; let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard));
if size == 0 {
log::trace!("Ignoring copy_buffer_to_buffer of size 0");
cmd_buf_data_guard.mark_successful(); return Ok(());
}
// Make sure source is initialized memory and mark dest as initialized.
cmd_buf_data.buffer_memory_init_actions.extend(
dst_buffer.initialization_status.read().create_action(
&dst_buffer,
destination_offset..(destination_offset + size),
MemoryInitKind::ImplicitlyInitialized,
),
);
cmd_buf_data.buffer_memory_init_actions.extend(
src_buffer.initialization_status.read().create_action(
&src_buffer,
source_offset..(source_offset + size),
MemoryInitKind::NeedsInitializedMemory,
),
);
let region = hal::BufferCopy {
src_offset: source_offset,
dst_offset: destination_offset,
size: wgt::BufferSize::new(size).unwrap(),
}; let cmd_buf_raw = cmd_buf_data.encoder.open()?; let barriers = src_barrier
.into_iter()
.chain(dst_barrier)
.collect::<Vec<_>>(); unsafe {
cmd_buf_raw.transition_buffers(&barriers);
cmd_buf_raw.copy_buffer_to_buffer(src_raw, dst_raw, &[region]);
}
let dst_texture = hub.textures.get(destination.texture).get()?;
dst_texture.same_device_as(cmd_buf.as_ref())?;
let (hal_copy_size, array_layer_count) = validate_texture_copy_range(
destination,
&dst_texture.desc,
CopySide::Destination,
copy_size,
)?;
let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, &dst_texture)?;
let snatch_guard = device.snatchable_lock.read();
// Handle texture init *before* dealing with barrier transitions so we // have an easier time inserting "immediate-inits" that may be required // by prior discards in rare cases.
handle_dst_texture_init(
cmd_buf_data,
device,
destination,
copy_size,
&dst_texture,
&snatch_guard,
)?;
let src_buffer = hub.buffers.get(source.buffer).get()?;
src_buffer.same_device_as(cmd_buf.as_ref())?;
let src_pending = cmd_buf_data
.trackers
.buffers
.set_single(&src_buffer, hal::BufferUses::COPY_SRC);
let src_raw = src_buffer.try_raw(&snatch_guard)?;
src_buffer
.check_usage(BufferUsages::COPY_SRC)
.map_err(TransferError::MissingBufferUsage)?; let src_barrier = src_pending.map(|pending| pending.into_hal(&src_buffer, &snatch_guard));
let dst_pending = cmd_buf_data.trackers.textures.set_single(
&dst_texture,
dst_range,
hal::TextureUses::COPY_DST,
); let dst_raw = dst_texture.try_raw(&snatch_guard)?;
dst_texture
.check_usage(TextureUsages::COPY_DST)
.map_err(TransferError::MissingTextureUsage)?; let dst_barrier = dst_pending
.map(|pending| pending.into_hal(dst_raw))
.collect::<Vec<_>>();
if !dst_base.aspect.is_one() { return Err(TransferError::CopyAspectNotOne.into());
}
let src_texture = hub.textures.get(source.texture).get()?;
src_texture.same_device_as(cmd_buf.as_ref())?;
let (hal_copy_size, array_layer_count) =
validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?;
let (src_range, src_base) = extract_texture_selector(source, copy_size, &src_texture)?;
let snatch_guard = device.snatchable_lock.read();
// Handle texture init *before* dealing with barrier transitions so we // have an easier time inserting "immediate-inits" that may be required // by prior discards in rare cases.
handle_src_texture_init(
cmd_buf_data,
device,
source,
copy_size,
&src_texture,
&snatch_guard,
)?;
let src_pending = cmd_buf_data.trackers.textures.set_single(
&src_texture,
src_range,
hal::TextureUses::COPY_SRC,
); let src_raw = src_texture.try_raw(&snatch_guard)?;
src_texture
.check_usage(TextureUsages::COPY_SRC)
.map_err(TransferError::MissingTextureUsage)?; if src_texture.desc.sample_count != 1 { return Err(TransferError::InvalidSampleCount {
sample_count: src_texture.desc.sample_count,
}
.into());
} if source.mip_level >= src_texture.desc.mip_level_count { return Err(TransferError::InvalidMipLevel {
requested: source.mip_level,
count: src_texture.desc.mip_level_count,
}
.into());
} let src_barrier = src_pending
.map(|pending| pending.into_hal(src_raw))
.collect::<Vec<_>>();
let dst_buffer = hub.buffers.get(destination.buffer).get()?;
dst_buffer.same_device_as(cmd_buf.as_ref())?;
let dst_pending = cmd_buf_data
.trackers
.buffers
.set_single(&dst_buffer, hal::BufferUses::COPY_DST);
let dst_raw = dst_buffer.try_raw(&snatch_guard)?;
dst_buffer
.check_usage(BufferUsages::COPY_DST)
.map_err(TransferError::MissingBufferUsage)?; let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard));
if !src_base.aspect.is_one() { return Err(TransferError::CopyAspectNotOne.into());
}
// src and dst texture format must be copy-compatible // https://gpuweb.github.io/gpuweb/#copy-compatible if src_texture.desc.format.remove_srgb_suffix()
!= dst_texture.desc.format.remove_srgb_suffix()
{ return Err(TransferError::TextureFormatsNotCopyCompatible {
src_format: src_texture.desc.format,
dst_format: dst_texture.desc.format,
}
.into());
}
let (src_copy_size, array_layer_count) =
validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?; let (dst_copy_size, _) = validate_texture_copy_range(
destination,
&dst_texture.desc,
CopySide::Destination,
copy_size,
)?;
let (src_range, src_tex_base) = extract_texture_selector(source, copy_size, &src_texture)?; let (dst_range, dst_tex_base) =
extract_texture_selector(destination, copy_size, &dst_texture)?; let src_texture_aspects = hal::FormatAspects::from(src_texture.desc.format); let dst_texture_aspects = hal::FormatAspects::from(dst_texture.desc.format); if src_tex_base.aspect != src_texture_aspects { return Err(TransferError::CopySrcMissingAspects.into());
} if dst_tex_base.aspect != dst_texture_aspects { return Err(TransferError::CopyDstMissingAspects.into());
}
// Handle texture init *before* dealing with barrier transitions so we // have an easier time inserting "immediate-inits" that may be required // by prior discards in rare cases.
handle_src_texture_init(
cmd_buf_data,
device,
source,
copy_size,
&src_texture,
&snatch_guard,
)?;
handle_dst_texture_init(
cmd_buf_data,
device,
destination,
copy_size,
&dst_texture,
&snatch_guard,
)?;
let src_pending = cmd_buf_data.trackers.textures.set_single(
&src_texture,
src_range,
hal::TextureUses::COPY_SRC,
); let src_raw = src_texture.try_raw(&snatch_guard)?;
src_texture
.check_usage(TextureUsages::COPY_SRC)
.map_err(TransferError::MissingTextureUsage)?;
//TODO: try to avoid this the collection. It's needed because both // `src_pending` and `dst_pending` try to hold `trackers.textures` mutably. letmut barriers: ArrayVec<_, 2> = src_pending
.map(|pending| pending.into_hal(src_raw))
.collect();
let dst_pending = cmd_buf_data.trackers.textures.set_single(
&dst_texture,
dst_range,
hal::TextureUses::COPY_DST,
); let dst_raw = dst_texture.try_raw(&snatch_guard)?;
dst_texture
.check_usage(TextureUsages::COPY_DST)
.map_err(TransferError::MissingTextureUsage)?;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.