wgpu_core/command/
transfer.rs

1use alloc::{format, string::String, sync::Arc, vec::Vec};
2
3use arrayvec::ArrayVec;
4use thiserror::Error;
5use wgt::{
6    error::{ErrorType, WebGpuError},
7    BufferAddress, BufferTextureCopyInfoError, BufferUsages, Extent3d, TextureSelector,
8    TextureUsages,
9};
10
11use crate::{
12    api_log,
13    command::{
14        clear_texture, encoder::EncodingState, ArcCommand, CommandEncoderError, EncoderStateError,
15    },
16    device::MissingDownlevelFlags,
17    global::Global,
18    id::{BufferId, CommandEncoderId, TextureId},
19    init_tracker::{
20        has_copy_partial_init_tracker_coverage, MemoryInitKind, TextureInitRange,
21        TextureInitTrackerAction,
22    },
23    resource::{
24        Buffer, MissingBufferUsageError, MissingTextureUsageError, ParentDevice, RawResourceAccess,
25        Texture, TextureErrorDimension,
26    },
27};
28
29use super::ClearError;
30
31type TexelCopyBufferInfo = wgt::TexelCopyBufferInfo<BufferId>;
32type TexelCopyTextureInfo = wgt::TexelCopyTextureInfo<Arc<Texture>>;
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35pub enum CopySide {
36    Source,
37    Destination,
38}
39
40/// Error encountered while attempting a data transfer.
41#[derive(Clone, Debug, Error)]
42#[non_exhaustive]
43pub enum TransferError {
44    #[error("Source and destination cannot be the same buffer")]
45    SameSourceDestinationBuffer,
46    #[error(transparent)]
47    MissingBufferUsage(#[from] MissingBufferUsageError),
48    #[error(transparent)]
49    MissingTextureUsage(#[from] MissingTextureUsageError),
50    #[error(
51        "Copy at offset {start_offset} bytes would end up overrunning the bounds of the {side:?} buffer of size {buffer_size}"
52    )]
53    BufferStartOffsetOverrun {
54        start_offset: BufferAddress,
55        buffer_size: BufferAddress,
56        side: CopySide,
57    },
58    #[error(
59        "Copy at offset {start_offset} for {size} bytes would end up overrunning the bounds of the {side:?} buffer of size {buffer_size}"
60    )]
61    BufferEndOffsetOverrun {
62        start_offset: BufferAddress,
63        size: BufferAddress,
64        buffer_size: BufferAddress,
65        side: CopySide,
66    },
67    #[error("Copy of {dimension:?} {start_offset}..{end_offset} would end up overrunning the bounds of the {side:?} texture of {dimension:?} size {texture_size}")]
68    TextureOverrun {
69        start_offset: u32,
70        end_offset: u32,
71        texture_size: u32,
72        dimension: TextureErrorDimension,
73        side: CopySide,
74    },
75    #[error("Partial copy of {start_offset}..{end_offset} on {dimension:?} dimension with size {texture_size} \
76             is not supported for the {side:?} texture format {format:?} with {sample_count} samples")]
77    UnsupportedPartialTransfer {
78        format: wgt::TextureFormat,
79        sample_count: u32,
80        start_offset: u32,
81        end_offset: u32,
82        texture_size: u32,
83        dimension: TextureErrorDimension,
84        side: CopySide,
85    },
86    #[error(
87        "Copying{} layers {}..{} to{} layers {}..{} of the same texture is not allowed",
88        if *src_aspects == wgt::TextureAspect::All { String::new() } else { format!(" {src_aspects:?}") },
89        src_origin_z,
90        src_origin_z + array_layer_count,
91        if *dst_aspects == wgt::TextureAspect::All { String::new() } else { format!(" {dst_aspects:?}") },
92        dst_origin_z,
93        dst_origin_z + array_layer_count,
94    )]
95    InvalidCopyWithinSameTexture {
96        src_aspects: wgt::TextureAspect,
97        dst_aspects: wgt::TextureAspect,
98        src_origin_z: u32,
99        dst_origin_z: u32,
100        array_layer_count: u32,
101    },
102    #[error("Unable to select texture aspect {aspect:?} from format {format:?}")]
103    InvalidTextureAspect {
104        format: wgt::TextureFormat,
105        aspect: wgt::TextureAspect,
106    },
107    #[error("Unable to select texture mip level {level} out of {total}")]
108    InvalidTextureMipLevel { level: u32, total: u32 },
109    #[error("Texture dimension must be 2D when copying from an external texture")]
110    InvalidDimensionExternal,
111    #[error("Buffer offset {0} is not aligned to block size or `COPY_BUFFER_ALIGNMENT`")]
112    UnalignedBufferOffset(BufferAddress),
113    #[error("Copy size {0} does not respect `COPY_BUFFER_ALIGNMENT`")]
114    UnalignedCopySize(BufferAddress),
115    #[error("Copy width is not a multiple of block width")]
116    UnalignedCopyWidth,
117    #[error("Copy height is not a multiple of block height")]
118    UnalignedCopyHeight,
119    #[error("Copy origin's x component is not a multiple of block width")]
120    UnalignedCopyOriginX,
121    #[error("Copy origin's y component is not a multiple of block height")]
122    UnalignedCopyOriginY,
123    #[error("Bytes per row does not respect `COPY_BYTES_PER_ROW_ALIGNMENT`")]
124    UnalignedBytesPerRow,
125    #[error("Number of bytes per row needs to be specified since more than one row is copied")]
126    UnspecifiedBytesPerRow,
127    #[error("Number of rows per image needs to be specified since more than one image is copied")]
128    UnspecifiedRowsPerImage,
129    #[error("Number of bytes per row is less than the number of bytes in a complete row")]
130    InvalidBytesPerRow,
131    #[error("Number of rows per image is invalid")]
132    InvalidRowsPerImage,
133    #[error("Overflow while computing the size of the copy")]
134    SizeOverflow,
135    #[error("Copy source aspects must refer to all aspects of the source texture format")]
136    CopySrcMissingAspects,
137    #[error(
138        "Copy destination aspects must refer to all aspects of the destination texture format"
139    )]
140    CopyDstMissingAspects,
141    #[error("Copy aspect must refer to a single aspect of texture format")]
142    CopyAspectNotOne,
143    #[error("Copying from textures with format {0:?} is forbidden")]
144    CopyFromForbiddenTextureFormat(wgt::TextureFormat),
145    #[error("Copying from textures with format {format:?} and aspect {aspect:?} is forbidden")]
146    CopyFromForbiddenTextureFormatAspect {
147        format: wgt::TextureFormat,
148        aspect: wgt::TextureAspect,
149    },
150    #[error("Copying to textures with format {0:?} is forbidden")]
151    CopyToForbiddenTextureFormat(wgt::TextureFormat),
152    #[error("Copying to textures with format {format:?} and aspect {aspect:?} is forbidden")]
153    CopyToForbiddenTextureFormatAspect {
154        format: wgt::TextureFormat,
155        aspect: wgt::TextureAspect,
156    },
157    #[error(
158        "Copying to textures with format {0:?} is forbidden when copying from external texture"
159    )]
160    ExternalCopyToForbiddenTextureFormat(wgt::TextureFormat),
161    #[error(
162        "Source format ({src_format:?}) and destination format ({dst_format:?}) are not copy-compatible (they may only differ in srgb-ness)"
163    )]
164    TextureFormatsNotCopyCompatible {
165        src_format: wgt::TextureFormat,
166        dst_format: wgt::TextureFormat,
167    },
168    #[error(transparent)]
169    MemoryInitFailure(#[from] ClearError),
170    #[error("Cannot encode this copy because of a missing downelevel flag")]
171    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
172    #[error("Source texture sample count must be 1, got {sample_count}")]
173    InvalidSampleCount { sample_count: u32 },
174    #[error(
175        "Source sample count ({src_sample_count:?}) and destination sample count ({dst_sample_count:?}) are not equal"
176    )]
177    SampleCountNotEqual {
178        src_sample_count: u32,
179        dst_sample_count: u32,
180    },
181    #[error("Requested mip level {requested} does not exist (count: {count})")]
182    InvalidMipLevel { requested: u32, count: u32 },
183    #[error("Buffer is expected to be unmapped, but was not")]
184    BufferNotAvailable,
185}
186
187impl WebGpuError for TransferError {
188    fn webgpu_error_type(&self) -> ErrorType {
189        let e: &dyn WebGpuError = match self {
190            Self::MissingBufferUsage(e) => e,
191            Self::MissingTextureUsage(e) => e,
192            Self::MemoryInitFailure(e) => e,
193
194            Self::BufferEndOffsetOverrun { .. }
195            | Self::TextureOverrun { .. }
196            | Self::BufferStartOffsetOverrun { .. }
197            | Self::UnsupportedPartialTransfer { .. }
198            | Self::InvalidCopyWithinSameTexture { .. }
199            | Self::InvalidTextureAspect { .. }
200            | Self::InvalidTextureMipLevel { .. }
201            | Self::InvalidDimensionExternal
202            | Self::UnalignedBufferOffset(..)
203            | Self::UnalignedCopySize(..)
204            | Self::UnalignedCopyWidth
205            | Self::UnalignedCopyHeight
206            | Self::UnalignedCopyOriginX
207            | Self::UnalignedCopyOriginY
208            | Self::UnalignedBytesPerRow
209            | Self::UnspecifiedBytesPerRow
210            | Self::UnspecifiedRowsPerImage
211            | Self::InvalidBytesPerRow
212            | Self::InvalidRowsPerImage
213            | Self::SizeOverflow
214            | Self::CopySrcMissingAspects
215            | Self::CopyDstMissingAspects
216            | Self::CopyAspectNotOne
217            | Self::CopyFromForbiddenTextureFormat(..)
218            | Self::CopyFromForbiddenTextureFormatAspect { .. }
219            | Self::CopyToForbiddenTextureFormat(..)
220            | Self::CopyToForbiddenTextureFormatAspect { .. }
221            | Self::ExternalCopyToForbiddenTextureFormat(..)
222            | Self::TextureFormatsNotCopyCompatible { .. }
223            | Self::MissingDownlevelFlags(..)
224            | Self::InvalidSampleCount { .. }
225            | Self::SampleCountNotEqual { .. }
226            | Self::InvalidMipLevel { .. }
227            | Self::SameSourceDestinationBuffer
228            | Self::BufferNotAvailable => return ErrorType::Validation,
229        };
230        e.webgpu_error_type()
231    }
232}
233
234impl From<BufferTextureCopyInfoError> for TransferError {
235    fn from(value: BufferTextureCopyInfoError) -> Self {
236        match value {
237            BufferTextureCopyInfoError::InvalidBytesPerRow => Self::InvalidBytesPerRow,
238            BufferTextureCopyInfoError::InvalidRowsPerImage => Self::InvalidRowsPerImage,
239            BufferTextureCopyInfoError::ImageStrideOverflow
240            | BufferTextureCopyInfoError::ImageBytesOverflow(_)
241            | BufferTextureCopyInfoError::ArraySizeOverflow(_) => Self::SizeOverflow,
242        }
243    }
244}
245
246pub(crate) fn extract_texture_selector<T>(
247    copy_texture: &wgt::TexelCopyTextureInfo<T>,
248    copy_size: &Extent3d,
249    texture: &Texture,
250) -> Result<(TextureSelector, hal::TextureCopyBase), TransferError> {
251    let format = texture.desc.format;
252    let copy_aspect = hal::FormatAspects::new(format, copy_texture.aspect);
253    if copy_aspect.is_empty() {
254        return Err(TransferError::InvalidTextureAspect {
255            format,
256            aspect: copy_texture.aspect,
257        });
258    }
259
260    let (layers, origin_z) = match texture.desc.dimension {
261        wgt::TextureDimension::D1 => (0..1, 0),
262        wgt::TextureDimension::D2 => (
263            copy_texture.origin.z..copy_texture.origin.z + copy_size.depth_or_array_layers,
264            0,
265        ),
266        wgt::TextureDimension::D3 => (0..1, copy_texture.origin.z),
267    };
268    let base = hal::TextureCopyBase {
269        origin: wgt::Origin3d {
270            x: copy_texture.origin.x,
271            y: copy_texture.origin.y,
272            z: origin_z,
273        },
274        // this value will be incremented per copied layer
275        array_layer: layers.start,
276        mip_level: copy_texture.mip_level,
277        aspect: copy_aspect,
278    };
279    let selector = TextureSelector {
280        mips: copy_texture.mip_level..copy_texture.mip_level + 1,
281        layers,
282    };
283
284    Ok((selector, base))
285}
286
287/// WebGPU's [validating linear texture data][vltd] algorithm.
288///
289/// Copied with some modifications from WebGPU standard.
290///
291/// If successful, returns a tuple `(bytes, stride, is_contiguous)`, where:
292/// - `bytes` is the number of buffer bytes required for this copy, and
293/// - `stride` number of bytes between array layers.
294/// - `is_contiguous` is true if the linear texture data does not have padding
295///   between rows or between images.
296///
297/// [vltd]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-linear-texture-data
298pub(crate) fn validate_linear_texture_data(
299    layout: &wgt::TexelCopyBufferLayout,
300    format: wgt::TextureFormat,
301    aspect: wgt::TextureAspect,
302    buffer_size: BufferAddress,
303    buffer_side: CopySide,
304    copy_size: &Extent3d,
305) -> Result<(BufferAddress, BufferAddress, bool), TransferError> {
306    let wgt::BufferTextureCopyInfo {
307        copy_width,
308        copy_height,
309        depth_or_array_layers,
310
311        offset,
312
313        block_size_bytes: _,
314        block_width_texels,
315        block_height_texels,
316
317        width_blocks: _,
318        height_blocks,
319
320        row_bytes_dense,
321        row_stride_bytes,
322
323        image_stride_rows: _,
324        image_stride_bytes,
325
326        image_rows_dense: _,
327        image_bytes_dense,
328
329        bytes_in_copy,
330    } = layout.get_buffer_texture_copy_info(format, aspect, copy_size)?;
331
332    if !copy_width.is_multiple_of(block_width_texels) {
333        return Err(TransferError::UnalignedCopyWidth);
334    }
335    if !copy_height.is_multiple_of(block_height_texels) {
336        return Err(TransferError::UnalignedCopyHeight);
337    }
338
339    let requires_multiple_rows = depth_or_array_layers > 1 || height_blocks > 1;
340    let requires_multiple_images = depth_or_array_layers > 1;
341
342    // `get_buffer_texture_copy_info()` already proceeded with defaults if these
343    // were not specified, and ensured that the values satisfy the minima if
344    // they were, but now we enforce the WebGPU requirement that they be
345    // specified any time they apply.
346    if layout.bytes_per_row.is_none() && requires_multiple_rows {
347        return Err(TransferError::UnspecifiedBytesPerRow);
348    }
349
350    if layout.rows_per_image.is_none() && requires_multiple_images {
351        return Err(TransferError::UnspecifiedRowsPerImage);
352    };
353
354    if offset > buffer_size {
355        return Err(TransferError::BufferStartOffsetOverrun {
356            start_offset: offset,
357            buffer_size,
358            side: buffer_side,
359        });
360    }
361    // NOTE: Should never underflow because of our earlier check.
362    if bytes_in_copy > buffer_size - offset {
363        return Err(TransferError::BufferEndOffsetOverrun {
364            start_offset: offset,
365            size: bytes_in_copy,
366            buffer_size,
367            side: buffer_side,
368        });
369    }
370
371    let is_contiguous = (row_stride_bytes == row_bytes_dense || !requires_multiple_rows)
372        && (image_stride_bytes == image_bytes_dense || !requires_multiple_images);
373
374    Ok((bytes_in_copy, image_stride_bytes, is_contiguous))
375}
376
377/// Validate the source format of a texture copy.
378///
379/// This performs the check from WebGPU's [validating texture buffer copy][vtbc]
380/// algorithm that ensures that the format and aspect form a valid texel copy source
381/// as defined in the [depth-stencil formats][dsf].
382///
383/// [vtbc]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-texture-buffer-copy
384/// [dsf]: https://gpuweb.github.io/gpuweb/#depth-formats
385pub(crate) fn validate_texture_copy_src_format(
386    format: wgt::TextureFormat,
387    aspect: wgt::TextureAspect,
388) -> Result<(), TransferError> {
389    use wgt::TextureAspect as Ta;
390    use wgt::TextureFormat as Tf;
391    match (format, aspect) {
392        (Tf::Depth24Plus, _) => Err(TransferError::CopyFromForbiddenTextureFormat(format)),
393        (Tf::Depth24PlusStencil8, Ta::DepthOnly) => {
394            Err(TransferError::CopyFromForbiddenTextureFormatAspect { format, aspect })
395        }
396        _ => Ok(()),
397    }
398}
399
400/// Validate the destination format of a texture copy.
401///
402/// This performs the check from WebGPU's [validating texture buffer copy][vtbc]
403/// algorithm that ensures that the format and aspect form a valid texel copy destination
404/// as defined in the [depth-stencil formats][dsf].
405///
406/// [vtbc]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-texture-buffer-copy
407/// [dsf]: https://gpuweb.github.io/gpuweb/#depth-formats
408pub(crate) fn validate_texture_copy_dst_format(
409    format: wgt::TextureFormat,
410    aspect: wgt::TextureAspect,
411) -> Result<(), TransferError> {
412    use wgt::TextureAspect as Ta;
413    use wgt::TextureFormat as Tf;
414    match (format, aspect) {
415        (Tf::Depth24Plus | Tf::Depth32Float, _) => {
416            Err(TransferError::CopyToForbiddenTextureFormat(format))
417        }
418        (Tf::Depth24PlusStencil8 | Tf::Depth32FloatStencil8, Ta::DepthOnly) => {
419            Err(TransferError::CopyToForbiddenTextureFormatAspect { format, aspect })
420        }
421        _ => Ok(()),
422    }
423}
424
425/// Validation for texture/buffer copies.
426///
427/// This implements the following checks from WebGPU's [validating texture buffer copy][vtbc]
428/// algorithm:
429///  * The texture must not be multisampled.
430///  * The copy must be from/to a single aspect of the texture.
431///  * If `aligned` is true, the buffer offset must be aligned appropriately.
432///
433/// And implements the following check from WebGPU's [validating GPUTexelCopyBufferInfo][vtcbi]
434/// algorithm:
435///  * If `aligned` is true, `bytesPerRow` must be a multiple of 256.
436///
437/// Note that the `bytesPerRow` alignment check is enforced whenever
438/// `bytesPerRow` is specified, even if the transfer is not multiple rows and
439/// `bytesPerRow` could have been omitted.
440///
441/// The following steps in [validating texture buffer copy][vtbc] are implemented elsewhere:
442///  * Invocation of other validation algorithms.
443///  * The texture usage (COPY_DST / COPY_SRC) check.
444///  * The check for non-copyable depth/stencil formats. The caller must perform
445///    this check using `validate_texture_copy_src_format` / `validate_texture_copy_dst_format`
446///    before calling this function. This function will panic if
447///    [`wgt::TextureFormat::block_copy_size`] returns `None` due to a
448///    non-copyable format.
449///
450/// [vtbc]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-texture-buffer-copy
451/// [vtcbi]: https://www.w3.org/TR/webgpu/#abstract-opdef-validating-gputexelcopybufferinfo
452pub(crate) fn validate_texture_buffer_copy<T>(
453    texture_copy_view: &wgt::TexelCopyTextureInfo<T>,
454    aspect: hal::FormatAspects,
455    desc: &wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>,
456    layout: &wgt::TexelCopyBufferLayout,
457    aligned: bool,
458) -> Result<(), TransferError> {
459    if desc.sample_count != 1 {
460        return Err(TransferError::InvalidSampleCount {
461            sample_count: desc.sample_count,
462        });
463    }
464
465    if !aspect.is_one() {
466        return Err(TransferError::CopyAspectNotOne);
467    }
468
469    let offset_alignment = if desc.format.is_depth_stencil_format() {
470        4
471    } else {
472        // The case where `block_copy_size` returns `None` is currently
473        // unreachable both for the reason in the expect message, and also
474        // because the currently-defined non-copyable formats are depth/stencil
475        // formats so would take the `if` branch.
476        desc.format
477            .block_copy_size(Some(texture_copy_view.aspect))
478            .expect("non-copyable formats should have been rejected previously")
479    };
480
481    if aligned && !layout.offset.is_multiple_of(u64::from(offset_alignment)) {
482        return Err(TransferError::UnalignedBufferOffset(layout.offset));
483    }
484
485    if let Some(bytes_per_row) = layout.bytes_per_row {
486        if aligned && bytes_per_row % wgt::COPY_BYTES_PER_ROW_ALIGNMENT != 0 {
487            return Err(TransferError::UnalignedBytesPerRow);
488        }
489    }
490
491    Ok(())
492}
493
494/// Validate the extent and alignment of a texture copy.
495///
496/// Copied with minor modifications from WebGPU standard. This mostly follows
497/// the [validating GPUTexelCopyTextureInfo][vtcti] and [validating texture copy
498/// range][vtcr] algorithms.
499///
500/// Returns the HAL copy extent and the layer count.
501///
502/// [vtcti]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-gputexelcopytextureinfo
503/// [vtcr]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-texture-copy-range
504pub(crate) fn validate_texture_copy_range<T>(
505    texture_copy_view: &wgt::TexelCopyTextureInfo<T>,
506    desc: &wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>,
507    texture_side: CopySide,
508    copy_size: &Extent3d,
509) -> Result<(hal::CopyExtent, u32), TransferError> {
510    let (block_width, block_height) = desc.format.block_dimensions();
511
512    let extent_virtual = desc.mip_level_size(texture_copy_view.mip_level).ok_or(
513        TransferError::InvalidTextureMipLevel {
514            level: texture_copy_view.mip_level,
515            total: desc.mip_level_count,
516        },
517    )?;
518    // physical size can be larger than the virtual
519    let extent = extent_virtual.physical_size(desc.format);
520
521    // Multisampled and depth-stencil formats do not support partial copies
522    // on x and y dimensions, but do support copying a subset of layers.
523    let requires_exact_size = desc.format.is_depth_stencil_format() || desc.sample_count > 1;
524
525    // Return `Ok` if a run `size` texels long starting at `start_offset` is
526    // valid for `texture_size`. Otherwise, return an appropriate a`Err`.
527    let check_dimension = |dimension: TextureErrorDimension,
528                           start_offset: u32,
529                           size: u32,
530                           texture_size: u32,
531                           requires_exact_size: bool|
532     -> Result<(), TransferError> {
533        if requires_exact_size && (start_offset != 0 || size != texture_size) {
534            Err(TransferError::UnsupportedPartialTransfer {
535                format: desc.format,
536                sample_count: desc.sample_count,
537                start_offset,
538                end_offset: start_offset.wrapping_add(size),
539                texture_size,
540                dimension,
541                side: texture_side,
542            })
543        // Avoid underflow in the subtraction by checking start_offset against
544        // texture_size first.
545        } else if start_offset > texture_size || texture_size - start_offset < size {
546            Err(TransferError::TextureOverrun {
547                start_offset,
548                end_offset: start_offset.wrapping_add(size),
549                texture_size,
550                dimension,
551                side: texture_side,
552            })
553        } else {
554            Ok(())
555        }
556    };
557
558    check_dimension(
559        TextureErrorDimension::X,
560        texture_copy_view.origin.x,
561        copy_size.width,
562        extent.width,
563        requires_exact_size,
564    )?;
565    check_dimension(
566        TextureErrorDimension::Y,
567        texture_copy_view.origin.y,
568        copy_size.height,
569        extent.height,
570        requires_exact_size,
571    )?;
572    check_dimension(
573        TextureErrorDimension::Z,
574        texture_copy_view.origin.z,
575        copy_size.depth_or_array_layers,
576        extent.depth_or_array_layers,
577        false, // partial copy always allowed on Z/layer dimension
578    )?;
579
580    if !texture_copy_view.origin.x.is_multiple_of(block_width) {
581        return Err(TransferError::UnalignedCopyOriginX);
582    }
583    if !texture_copy_view.origin.y.is_multiple_of(block_height) {
584        return Err(TransferError::UnalignedCopyOriginY);
585    }
586    if !copy_size.width.is_multiple_of(block_width) {
587        return Err(TransferError::UnalignedCopyWidth);
588    }
589    if !copy_size.height.is_multiple_of(block_height) {
590        return Err(TransferError::UnalignedCopyHeight);
591    }
592
593    let (depth, array_layer_count) = match desc.dimension {
594        wgt::TextureDimension::D1 => (1, 1),
595        wgt::TextureDimension::D2 => (1, copy_size.depth_or_array_layers),
596        wgt::TextureDimension::D3 => (copy_size.depth_or_array_layers, 1),
597    };
598
599    let copy_extent = hal::CopyExtent {
600        width: copy_size.width,
601        height: copy_size.height,
602        depth,
603    };
604    Ok((copy_extent, array_layer_count))
605}
606
607/// Validate a copy within the same texture.
608///
609/// This implements the WebGPU requirement that the [sets of subresources for
610/// texture copy][srtc] of the source and destination be disjoint, i.e. that the
611/// source and destination do not overlap.
612///
613/// This function assumes that the copy ranges have already been validated with
614/// `validate_texture_copy_range`.
615///
616/// [srtc]: https://gpuweb.github.io/gpuweb/#abstract-opdef-set-of-subresources-for-texture-copy
617pub(crate) fn validate_copy_within_same_texture<T>(
618    src: &wgt::TexelCopyTextureInfo<T>,
619    dst: &wgt::TexelCopyTextureInfo<T>,
620    format: wgt::TextureFormat,
621    array_layer_count: u32,
622) -> Result<(), TransferError> {
623    let src_aspects = hal::FormatAspects::new(format, src.aspect);
624    let dst_aspects = hal::FormatAspects::new(format, dst.aspect);
625    if (src_aspects & dst_aspects).is_empty() {
626        // Copying between different aspects (if it even makes sense), is okay.
627        return Ok(());
628    }
629
630    if src.origin.z >= dst.origin.z + array_layer_count
631        || dst.origin.z >= src.origin.z + array_layer_count
632    {
633        // Copying between non-overlapping layer ranges is okay.
634        return Ok(());
635    }
636
637    if src.mip_level != dst.mip_level {
638        // Copying between different mip levels is okay.
639        return Ok(());
640    }
641
642    Err(TransferError::InvalidCopyWithinSameTexture {
643        src_aspects: src.aspect,
644        dst_aspects: dst.aspect,
645        src_origin_z: src.origin.z,
646        dst_origin_z: dst.origin.z,
647        array_layer_count,
648    })
649}
650
651fn handle_texture_init(
652    state: &mut EncodingState,
653    init_kind: MemoryInitKind,
654    copy_texture: &TexelCopyTextureInfo,
655    copy_size: &Extent3d,
656    texture: &Arc<Texture>,
657) -> Result<(), ClearError> {
658    let init_action = TextureInitTrackerAction {
659        texture: texture.clone(),
660        range: TextureInitRange {
661            mip_range: copy_texture.mip_level..copy_texture.mip_level + 1,
662            layer_range: copy_texture.origin.z
663                ..(copy_texture.origin.z + copy_size.depth_or_array_layers),
664        },
665        kind: init_kind,
666    };
667
668    // Register the init action.
669    let immediate_inits = state
670        .texture_memory_actions
671        .register_init_action(&{ init_action });
672
673    // In rare cases we may need to insert an init operation immediately onto the command buffer.
674    if !immediate_inits.is_empty() {
675        for init in immediate_inits {
676            clear_texture(
677                &init.texture,
678                TextureInitRange {
679                    mip_range: init.mip_level..(init.mip_level + 1),
680                    layer_range: init.layer..(init.layer + 1),
681                },
682                state.raw_encoder,
683                &mut state.tracker.textures,
684                &state.device.alignments,
685                state.device.zero_buffer.as_ref(),
686                state.snatch_guard,
687                state.device.instance_flags,
688            )?;
689        }
690    }
691
692    Ok(())
693}
694
695/// Prepare a transfer's source texture.
696///
697/// Ensure the source texture of a transfer is in the right initialization
698/// state, and record the state for after the transfer operation.
699fn handle_src_texture_init(
700    state: &mut EncodingState,
701    source: &TexelCopyTextureInfo,
702    copy_size: &Extent3d,
703    texture: &Arc<Texture>,
704) -> Result<(), TransferError> {
705    handle_texture_init(
706        state,
707        MemoryInitKind::NeedsInitializedMemory,
708        source,
709        copy_size,
710        texture,
711    )?;
712    Ok(())
713}
714
715/// Prepare a transfer's destination texture.
716///
717/// Ensure the destination texture of a transfer is in the right initialization
718/// state, and record the state for after the transfer operation.
719fn handle_dst_texture_init(
720    state: &mut EncodingState,
721    destination: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
722    copy_size: &Extent3d,
723    texture: &Arc<Texture>,
724) -> Result<(), TransferError> {
725    // Attention: If we don't write full texture subresources, we need to a full
726    // clear first since we don't track subrects. This means that in rare cases
727    // even a *destination* texture of a transfer may need an immediate texture
728    // init.
729    let dst_init_kind = if has_copy_partial_init_tracker_coverage(
730        copy_size,
731        destination.mip_level,
732        &texture.desc,
733    ) {
734        MemoryInitKind::NeedsInitializedMemory
735    } else {
736        MemoryInitKind::ImplicitlyInitialized
737    };
738
739    handle_texture_init(state, dst_init_kind, destination, copy_size, texture)?;
740    Ok(())
741}
742
743/// Handle initialization tracking for a transfer's source or destination buffer.
744///
745/// Ensures that the transfer will not read from uninitialized memory, and updates
746/// the initialization state information to reflect the transfer.
747fn handle_buffer_init(
748    state: &mut EncodingState,
749    info: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
750    direction: CopySide,
751    required_buffer_bytes_in_copy: BufferAddress,
752    is_contiguous: bool,
753) {
754    const ALIGN_SIZE: BufferAddress = wgt::COPY_BUFFER_ALIGNMENT;
755    const ALIGN_MASK: BufferAddress = wgt::COPY_BUFFER_ALIGNMENT - 1;
756
757    let buffer = &info.buffer;
758    let start = info.layout.offset;
759    let end = info.layout.offset + required_buffer_bytes_in_copy;
760    if !is_contiguous || direction == CopySide::Source {
761        // If the transfer will read the buffer, then the whole region needs to
762        // be initialized.
763        //
764        // If the transfer will not write a contiguous region of the buffer,
765        // then we need to make sure the padding areas are initialized. For now,
766        // initialize the whole region, although this could be improved to
767        // initialize only the necessary parts if doing so is likely to be
768        // faster than initializing the whole thing.
769        //
770        // Adjust the start/end outwards to 4B alignment.
771        let aligned_start = start & !ALIGN_MASK;
772        let aligned_end = (end + ALIGN_MASK) & !ALIGN_MASK;
773        state
774            .buffer_memory_init_actions
775            .extend(buffer.initialization_status.read().create_action(
776                buffer,
777                aligned_start..aligned_end,
778                MemoryInitKind::NeedsInitializedMemory,
779            ));
780    } else {
781        // If the transfer will write a contiguous region of the buffer, then we
782        // don't need to initialize that region.
783        //
784        // However, if the start and end are not 4B aligned, we need to make
785        // sure that we don't end up trying to initialize non-4B-aligned regions
786        // later.
787        //
788        // Adjust the start/end inwards to 4B alignment, we will handle the
789        // first/last pieces differently.
790        let aligned_start = (start + ALIGN_MASK) & !ALIGN_MASK;
791        let aligned_end = end & !ALIGN_MASK;
792        if aligned_start != start {
793            state.buffer_memory_init_actions.extend(
794                buffer.initialization_status.read().create_action(
795                    buffer,
796                    aligned_start - ALIGN_SIZE..aligned_start,
797                    MemoryInitKind::NeedsInitializedMemory,
798                ),
799            );
800        }
801        if aligned_start != aligned_end {
802            state.buffer_memory_init_actions.extend(
803                buffer.initialization_status.read().create_action(
804                    buffer,
805                    aligned_start..aligned_end,
806                    MemoryInitKind::ImplicitlyInitialized,
807                ),
808            );
809        }
810        if aligned_end != end {
811            // It is possible that `aligned_end + ALIGN_SIZE > dst_buffer.size`,
812            // because `dst_buffer.size` is the user-requested size, not the
813            // final size of the buffer. The final size of the buffer is not
814            // readily available, but was rounded up to COPY_BUFFER_ALIGNMENT,
815            // so no overrun is possible.
816            state.buffer_memory_init_actions.extend(
817                buffer.initialization_status.read().create_action(
818                    buffer,
819                    aligned_end..aligned_end + ALIGN_SIZE,
820                    MemoryInitKind::NeedsInitializedMemory,
821                ),
822            );
823        }
824    }
825}
826
827impl Global {
828    pub fn command_encoder_copy_buffer_to_buffer(
829        &self,
830        command_encoder_id: CommandEncoderId,
831        source: BufferId,
832        source_offset: BufferAddress,
833        destination: BufferId,
834        destination_offset: BufferAddress,
835        size: Option<BufferAddress>,
836    ) -> Result<(), EncoderStateError> {
837        profiling::scope!("CommandEncoder::copy_buffer_to_buffer");
838        api_log!(
839            "CommandEncoder::copy_buffer_to_buffer {source:?} -> {destination:?} {size:?}bytes"
840        );
841
842        let hub = &self.hub;
843
844        let cmd_enc = hub.command_encoders.get(command_encoder_id);
845        let mut cmd_buf_data = cmd_enc.data.lock();
846
847        cmd_buf_data.push_with(|| -> Result<_, CommandEncoderError> {
848            Ok(ArcCommand::CopyBufferToBuffer {
849                src: self.resolve_buffer_id(source)?,
850                src_offset: source_offset,
851                dst: self.resolve_buffer_id(destination)?,
852                dst_offset: destination_offset,
853                size,
854            })
855        })
856    }
857
858    pub fn command_encoder_copy_buffer_to_texture(
859        &self,
860        command_encoder_id: CommandEncoderId,
861        source: &TexelCopyBufferInfo,
862        destination: &wgt::TexelCopyTextureInfo<TextureId>,
863        copy_size: &Extent3d,
864    ) -> Result<(), EncoderStateError> {
865        profiling::scope!("CommandEncoder::copy_buffer_to_texture");
866        api_log!(
867            "CommandEncoder::copy_buffer_to_texture {:?} -> {:?} {copy_size:?}",
868            source.buffer,
869            destination.texture
870        );
871
872        let cmd_enc = self.hub.command_encoders.get(command_encoder_id);
873        let mut cmd_buf_data = cmd_enc.data.lock();
874
875        cmd_buf_data.push_with(|| -> Result<_, CommandEncoderError> {
876            Ok(ArcCommand::CopyBufferToTexture {
877                src: wgt::TexelCopyBufferInfo::<Arc<Buffer>> {
878                    buffer: self.resolve_buffer_id(source.buffer)?,
879                    layout: source.layout,
880                },
881                dst: wgt::TexelCopyTextureInfo::<Arc<Texture>> {
882                    texture: self.resolve_texture_id(destination.texture)?,
883                    mip_level: destination.mip_level,
884                    origin: destination.origin,
885                    aspect: destination.aspect,
886                },
887                size: *copy_size,
888            })
889        })
890    }
891
892    pub fn command_encoder_copy_texture_to_buffer(
893        &self,
894        command_encoder_id: CommandEncoderId,
895        source: &wgt::TexelCopyTextureInfo<TextureId>,
896        destination: &TexelCopyBufferInfo,
897        copy_size: &Extent3d,
898    ) -> Result<(), EncoderStateError> {
899        profiling::scope!("CommandEncoder::copy_texture_to_buffer");
900        api_log!(
901            "CommandEncoder::copy_texture_to_buffer {:?} -> {:?} {copy_size:?}",
902            source.texture,
903            destination.buffer
904        );
905
906        let cmd_enc = self.hub.command_encoders.get(command_encoder_id);
907        let mut cmd_buf_data = cmd_enc.data.lock();
908
909        cmd_buf_data.push_with(|| -> Result<_, CommandEncoderError> {
910            Ok(ArcCommand::CopyTextureToBuffer {
911                src: wgt::TexelCopyTextureInfo::<Arc<Texture>> {
912                    texture: self.resolve_texture_id(source.texture)?,
913                    mip_level: source.mip_level,
914                    origin: source.origin,
915                    aspect: source.aspect,
916                },
917                dst: wgt::TexelCopyBufferInfo::<Arc<Buffer>> {
918                    buffer: self.resolve_buffer_id(destination.buffer)?,
919                    layout: destination.layout,
920                },
921                size: *copy_size,
922            })
923        })
924    }
925
926    pub fn command_encoder_copy_texture_to_texture(
927        &self,
928        command_encoder_id: CommandEncoderId,
929        source: &wgt::TexelCopyTextureInfo<TextureId>,
930        destination: &wgt::TexelCopyTextureInfo<TextureId>,
931        copy_size: &Extent3d,
932    ) -> Result<(), EncoderStateError> {
933        profiling::scope!("CommandEncoder::copy_texture_to_texture");
934        api_log!(
935            "CommandEncoder::copy_texture_to_texture {:?} -> {:?} {copy_size:?}",
936            source.texture,
937            destination.texture
938        );
939
940        let cmd_enc = self.hub.command_encoders.get(command_encoder_id);
941        let mut cmd_buf_data = cmd_enc.data.lock();
942
943        cmd_buf_data.push_with(|| -> Result<_, CommandEncoderError> {
944            Ok(ArcCommand::CopyTextureToTexture {
945                src: wgt::TexelCopyTextureInfo {
946                    texture: self.resolve_texture_id(source.texture)?,
947                    mip_level: source.mip_level,
948                    origin: source.origin,
949                    aspect: source.aspect,
950                },
951                dst: wgt::TexelCopyTextureInfo {
952                    texture: self.resolve_texture_id(destination.texture)?,
953                    mip_level: destination.mip_level,
954                    origin: destination.origin,
955                    aspect: destination.aspect,
956                },
957                size: *copy_size,
958            })
959        })
960    }
961}
962
963pub(super) fn copy_buffer_to_buffer(
964    state: &mut EncodingState,
965    src_buffer: &Arc<Buffer>,
966    source_offset: BufferAddress,
967    dst_buffer: &Arc<Buffer>,
968    destination_offset: BufferAddress,
969    size: Option<BufferAddress>,
970) -> Result<(), CommandEncoderError> {
971    if src_buffer.is_equal(dst_buffer) {
972        return Err(TransferError::SameSourceDestinationBuffer.into());
973    }
974
975    src_buffer.same_device(state.device)?;
976
977    let src_pending = state
978        .tracker
979        .buffers
980        .set_single(src_buffer, wgt::BufferUses::COPY_SRC);
981
982    let src_raw = src_buffer.try_raw(state.snatch_guard)?;
983    src_buffer
984        .check_usage(BufferUsages::COPY_SRC)
985        .map_err(TransferError::MissingBufferUsage)?;
986    // expecting only a single barrier
987    let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer, state.snatch_guard));
988
989    dst_buffer.same_device(state.device)?;
990
991    let dst_pending = state
992        .tracker
993        .buffers
994        .set_single(dst_buffer, wgt::BufferUses::COPY_DST);
995
996    let dst_raw = dst_buffer.try_raw(state.snatch_guard)?;
997    dst_buffer
998        .check_usage(BufferUsages::COPY_DST)
999        .map_err(TransferError::MissingBufferUsage)?;
1000    let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer, state.snatch_guard));
1001
1002    if source_offset > src_buffer.size {
1003        return Err(TransferError::BufferStartOffsetOverrun {
1004            start_offset: source_offset,
1005            buffer_size: src_buffer.size,
1006            side: CopySide::Source,
1007        }
1008        .into());
1009    }
1010    let size = size.unwrap_or_else(|| {
1011        // NOTE: Should never underflow because of our earlier check.
1012        src_buffer.size - source_offset
1013    });
1014
1015    if !size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
1016        return Err(TransferError::UnalignedCopySize(size).into());
1017    }
1018    if !source_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
1019        return Err(TransferError::UnalignedBufferOffset(source_offset).into());
1020    }
1021    if !destination_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
1022        return Err(TransferError::UnalignedBufferOffset(destination_offset).into());
1023    }
1024    if !state
1025        .device
1026        .downlevel
1027        .flags
1028        .contains(wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER)
1029        && (src_buffer.usage.contains(BufferUsages::INDEX)
1030            || dst_buffer.usage.contains(BufferUsages::INDEX))
1031    {
1032        let forbidden_usages = BufferUsages::VERTEX
1033            | BufferUsages::UNIFORM
1034            | BufferUsages::INDIRECT
1035            | BufferUsages::STORAGE;
1036        if src_buffer.usage.intersects(forbidden_usages)
1037            || dst_buffer.usage.intersects(forbidden_usages)
1038        {
1039            return Err(TransferError::MissingDownlevelFlags(MissingDownlevelFlags(
1040                wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER,
1041            ))
1042            .into());
1043        }
1044    }
1045
1046    if size > src_buffer.size - source_offset {
1047        return Err(TransferError::BufferEndOffsetOverrun {
1048            start_offset: source_offset,
1049            size,
1050            buffer_size: src_buffer.size,
1051            side: CopySide::Source,
1052        }
1053        .into());
1054    }
1055    // NOTE: Should never overflow because of our earlier check.
1056    let source_end_offset = source_offset + size;
1057
1058    if destination_offset > dst_buffer.size {
1059        return Err(TransferError::BufferStartOffsetOverrun {
1060            start_offset: destination_offset,
1061            buffer_size: dst_buffer.size,
1062            side: CopySide::Destination,
1063        }
1064        .into());
1065    }
1066    // NOTE: Should never underflow because of our earlier check.
1067    if size > dst_buffer.size - destination_offset {
1068        return Err(TransferError::BufferEndOffsetOverrun {
1069            start_offset: destination_offset,
1070            size,
1071            buffer_size: dst_buffer.size,
1072            side: CopySide::Destination,
1073        }
1074        .into());
1075    }
1076    // NOTE: Should never overflow because of our earlier check.
1077    let destination_end_offset = destination_offset + size;
1078
1079    // This must happen after parameter validation (so that errors are reported
1080    // as required by the spec), but before any side effects.
1081    if size == 0 {
1082        log::trace!("Ignoring copy_buffer_to_buffer of size 0");
1083        return Ok(());
1084    }
1085
1086    // Make sure source is initialized memory and mark dest as initialized.
1087    state
1088        .buffer_memory_init_actions
1089        .extend(dst_buffer.initialization_status.read().create_action(
1090            dst_buffer,
1091            destination_offset..destination_end_offset,
1092            MemoryInitKind::ImplicitlyInitialized,
1093        ));
1094    state
1095        .buffer_memory_init_actions
1096        .extend(src_buffer.initialization_status.read().create_action(
1097            src_buffer,
1098            source_offset..source_end_offset,
1099            MemoryInitKind::NeedsInitializedMemory,
1100        ));
1101
1102    let region = hal::BufferCopy {
1103        src_offset: source_offset,
1104        dst_offset: destination_offset,
1105        size: wgt::BufferSize::new(size).unwrap(),
1106    };
1107    let barriers = src_barrier
1108        .into_iter()
1109        .chain(dst_barrier)
1110        .collect::<Vec<_>>();
1111    unsafe {
1112        state.raw_encoder.transition_buffers(&barriers);
1113        state
1114            .raw_encoder
1115            .copy_buffer_to_buffer(src_raw, dst_raw, &[region]);
1116    }
1117
1118    Ok(())
1119}
1120
1121pub(super) fn copy_buffer_to_texture(
1122    state: &mut EncodingState,
1123    source: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
1124    destination: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
1125    copy_size: &Extent3d,
1126) -> Result<(), CommandEncoderError> {
1127    let dst_texture = &destination.texture;
1128    let src_buffer = &source.buffer;
1129
1130    dst_texture.same_device(state.device)?;
1131    src_buffer.same_device(state.device)?;
1132
1133    let (hal_copy_size, array_layer_count) = validate_texture_copy_range(
1134        destination,
1135        &dst_texture.desc,
1136        CopySide::Destination,
1137        copy_size,
1138    )?;
1139
1140    let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, dst_texture)?;
1141
1142    let src_raw = src_buffer.try_raw(state.snatch_guard)?;
1143    src_buffer
1144        .check_usage(BufferUsages::COPY_SRC)
1145        .map_err(TransferError::MissingBufferUsage)?;
1146
1147    let dst_raw = dst_texture.try_raw(state.snatch_guard)?;
1148    dst_texture
1149        .check_usage(TextureUsages::COPY_DST)
1150        .map_err(TransferError::MissingTextureUsage)?;
1151
1152    validate_texture_copy_dst_format(dst_texture.desc.format, destination.aspect)?;
1153
1154    validate_texture_buffer_copy(
1155        destination,
1156        dst_base.aspect,
1157        &dst_texture.desc,
1158        &source.layout,
1159        true, // alignment required for buffer offset
1160    )?;
1161
1162    let (required_buffer_bytes_in_copy, bytes_per_array_layer, is_contiguous) =
1163        validate_linear_texture_data(
1164            &source.layout,
1165            dst_texture.desc.format,
1166            destination.aspect,
1167            src_buffer.size,
1168            CopySide::Source,
1169            copy_size,
1170        )?;
1171
1172    if dst_texture.desc.format.is_depth_stencil_format() {
1173        state
1174            .device
1175            .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES)
1176            .map_err(TransferError::from)?;
1177    }
1178
1179    // This must happen after parameter validation (so that errors are reported
1180    // as required by the spec), but before any side effects.
1181    if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 {
1182        log::trace!("Ignoring copy_buffer_to_texture of size 0");
1183        return Ok(());
1184    }
1185
1186    // Handle texture init *before* dealing with barrier transitions so we
1187    // have an easier time inserting "immediate-inits" that may be required
1188    // by prior discards in rare cases.
1189    handle_dst_texture_init(state, destination, copy_size, dst_texture)?;
1190
1191    let src_pending = state
1192        .tracker
1193        .buffers
1194        .set_single(src_buffer, wgt::BufferUses::COPY_SRC);
1195    let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer, state.snatch_guard));
1196
1197    let dst_pending =
1198        state
1199            .tracker
1200            .textures
1201            .set_single(dst_texture, dst_range, wgt::TextureUses::COPY_DST);
1202    let dst_barrier = dst_pending
1203        .map(|pending| pending.into_hal(dst_raw))
1204        .collect::<Vec<_>>();
1205
1206    handle_buffer_init(
1207        state,
1208        source,
1209        CopySide::Source,
1210        required_buffer_bytes_in_copy,
1211        is_contiguous,
1212    );
1213
1214    let regions = (0..array_layer_count)
1215        .map(|rel_array_layer| {
1216            let mut texture_base = dst_base.clone();
1217            texture_base.array_layer += rel_array_layer;
1218            let mut buffer_layout = source.layout;
1219            buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer;
1220            hal::BufferTextureCopy {
1221                buffer_layout,
1222                texture_base,
1223                size: hal_copy_size,
1224            }
1225        })
1226        .collect::<Vec<_>>();
1227
1228    unsafe {
1229        state.raw_encoder.transition_textures(&dst_barrier);
1230        state.raw_encoder.transition_buffers(src_barrier.as_slice());
1231        state
1232            .raw_encoder
1233            .copy_buffer_to_texture(src_raw, dst_raw, &regions);
1234    }
1235
1236    Ok(())
1237}
1238
1239pub(super) fn copy_texture_to_buffer(
1240    state: &mut EncodingState,
1241    source: &TexelCopyTextureInfo,
1242    destination: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
1243    copy_size: &Extent3d,
1244) -> Result<(), CommandEncoderError> {
1245    let src_texture = &source.texture;
1246    let dst_buffer = &destination.buffer;
1247
1248    src_texture.same_device(state.device)?;
1249    dst_buffer.same_device(state.device)?;
1250
1251    let (hal_copy_size, array_layer_count) =
1252        validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?;
1253
1254    let (src_range, src_base) = extract_texture_selector(source, copy_size, src_texture)?;
1255
1256    let src_raw = src_texture.try_raw(state.snatch_guard)?;
1257    src_texture
1258        .check_usage(TextureUsages::COPY_SRC)
1259        .map_err(TransferError::MissingTextureUsage)?;
1260
1261    if source.mip_level >= src_texture.desc.mip_level_count {
1262        return Err(TransferError::InvalidMipLevel {
1263            requested: source.mip_level,
1264            count: src_texture.desc.mip_level_count,
1265        }
1266        .into());
1267    }
1268
1269    validate_texture_copy_src_format(src_texture.desc.format, source.aspect)?;
1270
1271    validate_texture_buffer_copy(
1272        source,
1273        src_base.aspect,
1274        &src_texture.desc,
1275        &destination.layout,
1276        true, // alignment required for buffer offset
1277    )?;
1278
1279    let (required_buffer_bytes_in_copy, bytes_per_array_layer, is_contiguous) =
1280        validate_linear_texture_data(
1281            &destination.layout,
1282            src_texture.desc.format,
1283            source.aspect,
1284            dst_buffer.size,
1285            CopySide::Destination,
1286            copy_size,
1287        )?;
1288
1289    if src_texture.desc.format.is_depth_stencil_format() {
1290        state
1291            .device
1292            .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES)
1293            .map_err(TransferError::from)?;
1294    }
1295
1296    let dst_raw = dst_buffer.try_raw(state.snatch_guard)?;
1297    dst_buffer
1298        .check_usage(BufferUsages::COPY_DST)
1299        .map_err(TransferError::MissingBufferUsage)?;
1300
1301    // This must happen after parameter validation (so that errors are reported
1302    // as required by the spec), but before any side effects.
1303    if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 {
1304        log::trace!("Ignoring copy_texture_to_buffer of size 0");
1305        return Ok(());
1306    }
1307
1308    // Handle texture init *before* dealing with barrier transitions so we
1309    // have an easier time inserting "immediate-inits" that may be required
1310    // by prior discards in rare cases.
1311    handle_src_texture_init(state, source, copy_size, src_texture)?;
1312
1313    let src_pending =
1314        state
1315            .tracker
1316            .textures
1317            .set_single(src_texture, src_range, wgt::TextureUses::COPY_SRC);
1318    let src_barrier = src_pending
1319        .map(|pending| pending.into_hal(src_raw))
1320        .collect::<Vec<_>>();
1321
1322    let dst_pending = state
1323        .tracker
1324        .buffers
1325        .set_single(dst_buffer, wgt::BufferUses::COPY_DST);
1326
1327    let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer, state.snatch_guard));
1328
1329    handle_buffer_init(
1330        state,
1331        destination,
1332        CopySide::Destination,
1333        required_buffer_bytes_in_copy,
1334        is_contiguous,
1335    );
1336
1337    let regions = (0..array_layer_count)
1338        .map(|rel_array_layer| {
1339            let mut texture_base = src_base.clone();
1340            texture_base.array_layer += rel_array_layer;
1341            let mut buffer_layout = destination.layout;
1342            buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer;
1343            hal::BufferTextureCopy {
1344                buffer_layout,
1345                texture_base,
1346                size: hal_copy_size,
1347            }
1348        })
1349        .collect::<Vec<_>>();
1350    unsafe {
1351        state.raw_encoder.transition_buffers(dst_barrier.as_slice());
1352        state.raw_encoder.transition_textures(&src_barrier);
1353        state.raw_encoder.copy_texture_to_buffer(
1354            src_raw,
1355            wgt::TextureUses::COPY_SRC,
1356            dst_raw,
1357            &regions,
1358        );
1359    }
1360
1361    Ok(())
1362}
1363
1364pub(super) fn copy_texture_to_texture(
1365    state: &mut EncodingState,
1366    source: &TexelCopyTextureInfo,
1367    destination: &TexelCopyTextureInfo,
1368    copy_size: &Extent3d,
1369) -> Result<(), CommandEncoderError> {
1370    let src_texture = &source.texture;
1371    let dst_texture = &destination.texture;
1372
1373    src_texture.same_device(state.device)?;
1374    dst_texture.same_device(state.device)?;
1375
1376    // src and dst texture format must be copy-compatible
1377    // https://gpuweb.github.io/gpuweb/#copy-compatible
1378    if src_texture.desc.format.remove_srgb_suffix() != dst_texture.desc.format.remove_srgb_suffix()
1379    {
1380        return Err(TransferError::TextureFormatsNotCopyCompatible {
1381            src_format: src_texture.desc.format,
1382            dst_format: dst_texture.desc.format,
1383        }
1384        .into());
1385    }
1386
1387    let (src_copy_size, array_layer_count) =
1388        validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?;
1389    let (dst_copy_size, _) = validate_texture_copy_range(
1390        destination,
1391        &dst_texture.desc,
1392        CopySide::Destination,
1393        copy_size,
1394    )?;
1395
1396    if Arc::as_ptr(src_texture) == Arc::as_ptr(dst_texture) {
1397        validate_copy_within_same_texture(
1398            source,
1399            destination,
1400            src_texture.desc.format,
1401            array_layer_count,
1402        )?;
1403    }
1404
1405    let (src_range, src_tex_base) = extract_texture_selector(source, copy_size, src_texture)?;
1406    let (dst_range, dst_tex_base) = extract_texture_selector(destination, copy_size, dst_texture)?;
1407    let src_texture_aspects = hal::FormatAspects::from(src_texture.desc.format);
1408    let dst_texture_aspects = hal::FormatAspects::from(dst_texture.desc.format);
1409    if src_tex_base.aspect != src_texture_aspects {
1410        return Err(TransferError::CopySrcMissingAspects.into());
1411    }
1412    if dst_tex_base.aspect != dst_texture_aspects {
1413        return Err(TransferError::CopyDstMissingAspects.into());
1414    }
1415
1416    if src_texture.desc.sample_count != dst_texture.desc.sample_count {
1417        return Err(TransferError::SampleCountNotEqual {
1418            src_sample_count: src_texture.desc.sample_count,
1419            dst_sample_count: dst_texture.desc.sample_count,
1420        }
1421        .into());
1422    }
1423
1424    let src_raw = src_texture.try_raw(state.snatch_guard)?;
1425    src_texture
1426        .check_usage(TextureUsages::COPY_SRC)
1427        .map_err(TransferError::MissingTextureUsage)?;
1428    let dst_raw = dst_texture.try_raw(state.snatch_guard)?;
1429    dst_texture
1430        .check_usage(TextureUsages::COPY_DST)
1431        .map_err(TransferError::MissingTextureUsage)?;
1432
1433    // This must happen after parameter validation (so that errors are reported
1434    // as required by the spec), but before any side effects.
1435    if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 {
1436        log::trace!("Ignoring copy_texture_to_texture of size 0");
1437        return Ok(());
1438    }
1439
1440    // Handle texture init *before* dealing with barrier transitions so we
1441    // have an easier time inserting "immediate-inits" that may be required
1442    // by prior discards in rare cases.
1443    handle_src_texture_init(state, source, copy_size, src_texture)?;
1444    handle_dst_texture_init(state, destination, copy_size, dst_texture)?;
1445
1446    let src_pending =
1447        state
1448            .tracker
1449            .textures
1450            .set_single(src_texture, src_range, wgt::TextureUses::COPY_SRC);
1451
1452    //TODO: try to avoid this the collection. It's needed because both
1453    // `src_pending` and `dst_pending` try to hold `trackers.textures` mutably.
1454    let mut barriers: ArrayVec<_, 2> = src_pending
1455        .map(|pending| pending.into_hal(src_raw))
1456        .collect();
1457
1458    let dst_pending =
1459        state
1460            .tracker
1461            .textures
1462            .set_single(dst_texture, dst_range, wgt::TextureUses::COPY_DST);
1463    barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_raw)));
1464
1465    let hal_copy_size = hal::CopyExtent {
1466        width: src_copy_size.width.min(dst_copy_size.width),
1467        height: src_copy_size.height.min(dst_copy_size.height),
1468        depth: src_copy_size.depth.min(dst_copy_size.depth),
1469    };
1470
1471    let dst_format = dst_texture.desc.format;
1472
1473    let regions = (0..array_layer_count).map(|rel_array_layer| {
1474        let mut src_base = src_tex_base.clone();
1475        let mut dst_base = dst_tex_base.clone();
1476        src_base.array_layer += rel_array_layer;
1477        dst_base.array_layer += rel_array_layer;
1478        hal::TextureCopy {
1479            src_base,
1480            dst_base,
1481            size: hal_copy_size,
1482        }
1483    });
1484
1485    let regions = if dst_tex_base.aspect == hal::FormatAspects::DEPTH_STENCIL {
1486        regions
1487            .flat_map(|region| {
1488                let (mut depth, mut stencil) = (region.clone(), region);
1489                depth.src_base.aspect = hal::FormatAspects::DEPTH;
1490                depth.dst_base.aspect = hal::FormatAspects::DEPTH;
1491                stencil.src_base.aspect = hal::FormatAspects::STENCIL;
1492                stencil.dst_base.aspect = hal::FormatAspects::STENCIL;
1493                [depth, stencil]
1494            })
1495            .collect::<Vec<_>>()
1496    } else if let Some(plane_count) = dst_format.planes() {
1497        regions
1498            .into_iter()
1499            .flat_map(|region| {
1500                (0..plane_count).map(move |plane| {
1501                    let mut plane_region = region.clone();
1502
1503                    let plane_aspect = wgt::TextureAspect::from_plane(plane)
1504                        .expect("expected texture aspect to exist for the plane");
1505                    let plane_aspect = hal::FormatAspects::new(dst_format, plane_aspect);
1506                    plane_region.src_base.aspect = plane_aspect;
1507                    plane_region.dst_base.aspect = plane_aspect;
1508
1509                    let (w_subsampling, h_subsampling) =
1510                        dst_format.subsampling_factors(Some(plane));
1511                    plane_region.src_base.origin.x /= w_subsampling;
1512                    plane_region.src_base.origin.y /= h_subsampling;
1513                    plane_region.dst_base.origin.x /= w_subsampling;
1514                    plane_region.dst_base.origin.y /= h_subsampling;
1515
1516                    plane_region.size.width /= w_subsampling;
1517                    plane_region.size.height /= h_subsampling;
1518
1519                    plane_region
1520                })
1521            })
1522            .collect::<Vec<_>>()
1523    } else {
1524        regions.collect::<Vec<_>>()
1525    };
1526    unsafe {
1527        state.raw_encoder.transition_textures(&barriers);
1528        state.raw_encoder.copy_texture_to_texture(
1529            src_raw,
1530            wgt::TextureUses::COPY_SRC,
1531            dst_raw,
1532            &regions,
1533        );
1534    }
1535
1536    Ok(())
1537}