Skip to main content

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