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 does not respect `COPY_BYTES_PER_ROW_ALIGNMENT`")]
121    UnalignedBytesPerRow,
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.
432///
433/// Note that the `bytesPerRow` alignment check is enforced whenever
434/// `bytesPerRow` is specified, even if the transfer is not multiple rows and
435/// `bytesPerRow` could have been omitted.
436///
437/// The following steps in [validating texture buffer copy][vtbc] are implemented elsewhere:
438///  * Invocation of other validation algorithms.
439///  * The texture usage (COPY_DST / COPY_SRC) check.
440///  * The check for non-copyable depth/stencil formats. The caller must perform
441///    this check using `validate_texture_copy_src_format` / `validate_texture_copy_dst_format`
442///    before calling this function. This function will panic if
443///    [`wgt::TextureFormat::block_copy_size`] returns `None` due to a
444///    non-copyable format.
445///
446/// [vtbc]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-texture-buffer-copy
447/// [vtcbi]: https://www.w3.org/TR/webgpu/#abstract-opdef-validating-gputexelcopybufferinfo
448pub(crate) fn validate_texture_buffer_copy<T>(
449    texture_copy_view: &wgt::TexelCopyTextureInfo<T>,
450    aspect: hal::FormatAspects,
451    desc: &wgt::TextureDescriptor<String, Vec<wgt::TextureFormat>>,
452    layout: &wgt::TexelCopyBufferLayout,
453    aligned: bool,
454) -> Result<(), TransferError> {
455    if desc.sample_count != 1 {
456        return Err(TransferError::InvalidSampleCount {
457            sample_count: desc.sample_count,
458        });
459    }
460
461    if !aspect.is_one() {
462        return Err(TransferError::CopyAspectNotOne);
463    }
464
465    let offset_alignment = if desc.format.is_depth_stencil_format() {
466        4
467    } else {
468        // The case where `block_copy_size` returns `None` is currently
469        // unreachable both for the reason in the expect message, and also
470        // because the currently-defined non-copyable formats are depth/stencil
471        // formats so would take the `if` branch.
472        desc.format
473            .block_copy_size(Some(texture_copy_view.aspect))
474            .expect("non-copyable formats should have been rejected previously")
475    };
476
477    if aligned && !layout.offset.is_multiple_of(u64::from(offset_alignment)) {
478        return Err(TransferError::UnalignedBufferOffset(layout.offset));
479    }
480
481    if let Some(bytes_per_row) = layout.bytes_per_row {
482        if aligned && bytes_per_row % wgt::COPY_BYTES_PER_ROW_ALIGNMENT != 0 {
483            return Err(TransferError::UnalignedBytesPerRow);
484        }
485    }
486
487    Ok(())
488}
489
490/// Validate the extent and alignment of a texture copy.
491///
492/// Copied with minor modifications from WebGPU standard. This mostly follows
493/// the [validating GPUTexelCopyTextureInfo][vtcti] and [validating texture copy
494/// range][vtcr] algorithms.
495///
496/// Returns the HAL copy extent and the layer count.
497///
498/// [vtcti]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-gputexelcopytextureinfo
499/// [vtcr]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-texture-copy-range
500pub(crate) fn validate_texture_copy_range<T>(
501    texture_copy_view: &wgt::TexelCopyTextureInfo<T>,
502    desc: &wgt::TextureDescriptor<String, Vec<wgt::TextureFormat>>,
503    texture_side: CopySide,
504    copy_size: &Extent3d,
505) -> Result<(hal::CopyExtent, u32), TransferError> {
506    let (block_width, block_height) = desc.format.block_dimensions();
507
508    let extent_virtual = desc.mip_level_size(texture_copy_view.mip_level).ok_or(
509        TransferError::InvalidTextureMipLevel {
510            level: texture_copy_view.mip_level,
511            total: desc.mip_level_count,
512        },
513    )?;
514    // physical size can be larger than the virtual
515    let extent = extent_virtual.physical_size(desc.format);
516
517    // Multisampled and depth-stencil formats do not support partial copies
518    // on x and y dimensions, but do support copying a subset of layers.
519    let requires_exact_size = desc.format.is_depth_stencil_format() || desc.sample_count > 1;
520
521    // Return `Ok` if a run `size` texels long starting at `start_offset` is
522    // valid for `texture_size`. Otherwise, return an appropriate a`Err`.
523    let check_dimension = |dimension: TextureErrorDimension,
524                           start_offset: u32,
525                           size: u32,
526                           texture_size: u32,
527                           requires_exact_size: bool|
528     -> Result<(), TransferError> {
529        if requires_exact_size && (start_offset != 0 || size != texture_size) {
530            Err(TransferError::UnsupportedPartialTransfer {
531                format: desc.format,
532                sample_count: desc.sample_count,
533                start_offset,
534                end_offset: start_offset.wrapping_add(size),
535                texture_size,
536                dimension,
537                side: texture_side,
538            })
539        // Avoid underflow in the subtraction by checking start_offset against
540        // texture_size first.
541        } else if start_offset > texture_size || texture_size - start_offset < size {
542            Err(TransferError::TextureOverrun {
543                start_offset,
544                end_offset: start_offset.wrapping_add(size),
545                texture_size,
546                dimension,
547                side: texture_side,
548            })
549        } else {
550            Ok(())
551        }
552    };
553
554    check_dimension(
555        TextureErrorDimension::X,
556        texture_copy_view.origin.x,
557        copy_size.width,
558        extent.width,
559        requires_exact_size,
560    )?;
561    check_dimension(
562        TextureErrorDimension::Y,
563        texture_copy_view.origin.y,
564        copy_size.height,
565        extent.height,
566        requires_exact_size,
567    )?;
568    check_dimension(
569        TextureErrorDimension::Z,
570        texture_copy_view.origin.z,
571        copy_size.depth_or_array_layers,
572        extent.depth_or_array_layers,
573        false, // partial copy always allowed on Z/layer dimension
574    )?;
575
576    if !texture_copy_view.origin.x.is_multiple_of(block_width) {
577        return Err(TransferError::UnalignedCopyOriginX);
578    }
579    if !texture_copy_view.origin.y.is_multiple_of(block_height) {
580        return Err(TransferError::UnalignedCopyOriginY);
581    }
582    if !copy_size.width.is_multiple_of(block_width) {
583        return Err(TransferError::UnalignedCopyWidth);
584    }
585    if !copy_size.height.is_multiple_of(block_height) {
586        return Err(TransferError::UnalignedCopyHeight);
587    }
588
589    let (depth, array_layer_count) = match desc.dimension {
590        wgt::TextureDimension::D1 => (1, 1),
591        wgt::TextureDimension::D2 => (1, copy_size.depth_or_array_layers),
592        wgt::TextureDimension::D3 => (copy_size.depth_or_array_layers, 1),
593    };
594
595    let copy_extent = hal::CopyExtent {
596        width: copy_size.width,
597        height: copy_size.height,
598        depth,
599    };
600    Ok((copy_extent, array_layer_count))
601}
602
603/// Validate a copy within the same texture.
604///
605/// This implements the WebGPU requirement that the [sets of subresources for
606/// texture copy][srtc] of the source and destination be disjoint, i.e. that the
607/// source and destination do not overlap.
608///
609/// This function assumes that the copy ranges have already been validated with
610/// `validate_texture_copy_range`.
611///
612/// [srtc]: https://gpuweb.github.io/gpuweb/#abstract-opdef-set-of-subresources-for-texture-copy
613pub(crate) fn validate_copy_within_same_texture<T>(
614    src: &wgt::TexelCopyTextureInfo<T>,
615    dst: &wgt::TexelCopyTextureInfo<T>,
616    format: wgt::TextureFormat,
617    array_layer_count: u32,
618) -> Result<(), TransferError> {
619    let src_aspects = hal::FormatAspects::new(format, src.aspect);
620    let dst_aspects = hal::FormatAspects::new(format, dst.aspect);
621    if (src_aspects & dst_aspects).is_empty() {
622        // Copying between different aspects (if it even makes sense), is okay.
623        return Ok(());
624    }
625
626    if src.origin.z >= dst.origin.z + array_layer_count
627        || dst.origin.z >= src.origin.z + array_layer_count
628    {
629        // Copying between non-overlapping layer ranges is okay.
630        return Ok(());
631    }
632
633    if src.mip_level != dst.mip_level {
634        // Copying between different mip levels is okay.
635        return Ok(());
636    }
637
638    Err(TransferError::InvalidCopyWithinSameTexture {
639        src_aspects: src.aspect,
640        dst_aspects: dst.aspect,
641        src_origin_z: src.origin.z,
642        dst_origin_z: dst.origin.z,
643        array_layer_count,
644    })
645}
646
647fn handle_texture_init(
648    state: &mut EncodingState,
649    init_kind: MemoryInitKind,
650    copy_texture: &TexelCopyTextureInfo,
651    copy_size: &Extent3d,
652    texture: &Arc<Texture>,
653) -> Result<(), ClearError> {
654    let init_layer_range = if texture.desc.dimension == wgt::TextureDimension::D3 {
655        // Init tracking only considers array layers, not depth/volume slices
656        0..1
657    } else {
658        copy_texture.origin.z..copy_texture.origin.z + copy_size.depth_or_array_layers
659    };
660    let init_action = TextureInitTrackerAction {
661        texture: texture.clone(),
662        range: TextureInitRange {
663            mip_range: copy_texture.mip_level..copy_texture.mip_level + 1,
664            layer_range: init_layer_range,
665        },
666        kind: init_kind,
667    };
668
669    // Record the initialization action. Simultaneously, collect a list of any ranges of the
670    // texture that were discarded within the current command buffer, for immediate
671    // initialization. (The analogous case for passes is in `fixup_discarded_surfaces`.)
672    //
673    // Depth slices are only relevant to deciding which pending discards (in a command
674    // buffer with prior render passes) have to be repaired ahead of this copy. Any
675    // initialization that gets generated covers the whole mip level, because that is the
676    // granularity of the init tracker.
677    let accessed_depth_slices = (texture.desc.dimension == wgt::TextureDimension::D3)
678        .then(|| copy_texture.origin.z..copy_texture.origin.z + copy_size.depth_or_array_layers);
679    let immediate_inits = state
680        .texture_memory_actions
681        .register_init_action(&{ init_action }, accessed_depth_slices);
682
683    // In rare cases we may need to insert an init operation immediately onto the command buffer.
684    if !immediate_inits.is_empty() {
685        for init in immediate_inits {
686            let index = init.layer_or_depth_slice;
687            let (layer_range, depth_slice) = if texture.desc.dimension == wgt::TextureDimension::D3
688            {
689                (0..1, Some(index))
690            } else {
691                (index..(index + 1), None)
692            };
693            clear_texture(
694                &init.texture,
695                TextureInitRange {
696                    mip_range: init.mip_level..(init.mip_level + 1),
697                    layer_range,
698                },
699                depth_slice,
700                state.raw_encoder,
701                &mut state.tracker.textures,
702                &state.device.alignments,
703                state.device.zero_buffer.as_ref(),
704                state.snatch_guard,
705                state.device.instance_flags,
706            )?;
707        }
708    }
709
710    Ok(())
711}
712
713/// Prepare a transfer's source texture.
714///
715/// Ensure the source texture of a transfer is in the right initialization
716/// state, and record the state for after the transfer operation.
717fn handle_src_texture_init(
718    state: &mut EncodingState,
719    source: &TexelCopyTextureInfo,
720    copy_size: &Extent3d,
721    texture: &Arc<Texture>,
722) -> Result<(), TransferError> {
723    handle_texture_init(
724        state,
725        MemoryInitKind::NeedsInitializedMemory,
726        source,
727        copy_size,
728        texture,
729    )?;
730    Ok(())
731}
732
733/// Prepare a transfer's destination texture.
734///
735/// Ensure the destination texture of a transfer is in the right initialization
736/// state, and record the state for after the transfer operation.
737fn handle_dst_texture_init(
738    state: &mut EncodingState,
739    destination: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
740    copy_size: &Extent3d,
741    texture: &Arc<Texture>,
742) -> Result<(), TransferError> {
743    // Attention: If we don't write full texture subresources, we need to a full
744    // clear first since we don't track subrects. This means that in rare cases
745    // even a *destination* texture of a transfer may need an immediate texture
746    // init.
747    let dst_init_kind =
748        if has_copy_partial_init_tracker_coverage(copy_size, destination, &texture.desc) {
749            MemoryInitKind::NeedsInitializedMemory
750        } else {
751            MemoryInitKind::ImplicitlyInitialized
752        };
753
754    handle_texture_init(state, dst_init_kind, destination, copy_size, texture)?;
755    Ok(())
756}
757
758/// Handle initialization tracking for a transfer's source or destination buffer.
759///
760/// Ensures that the transfer will not read from uninitialized memory, and updates
761/// the initialization state information to reflect the transfer.
762fn handle_buffer_init(
763    state: &mut EncodingState,
764    info: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
765    direction: CopySide,
766    required_buffer_bytes_in_copy: BufferAddress,
767    is_contiguous: bool,
768) {
769    const ALIGN_SIZE: BufferAddress = wgt::COPY_BUFFER_ALIGNMENT;
770    const ALIGN_MASK: BufferAddress = wgt::COPY_BUFFER_ALIGNMENT - 1;
771
772    let buffer = &info.buffer;
773    let start = info.layout.offset;
774    let end = info.layout.offset + required_buffer_bytes_in_copy;
775    if !is_contiguous || direction == CopySide::Source {
776        // If the transfer will read the buffer, then the whole region needs to
777        // be initialized.
778        //
779        // If the transfer will not write a contiguous region of the buffer,
780        // then we need to make sure the padding areas are initialized. For now,
781        // initialize the whole region, although this could be improved to
782        // initialize only the necessary parts if doing so is likely to be
783        // faster than initializing the whole thing.
784        //
785        // Adjust the start/end outwards to 4B alignment.
786        let aligned_start = start & !ALIGN_MASK;
787        let aligned_end = (end + ALIGN_MASK) & !ALIGN_MASK;
788        state
789            .buffer_memory_init_actions
790            .extend(buffer.initialization_status.read().create_action(
791                buffer,
792                aligned_start..aligned_end,
793                MemoryInitKind::NeedsInitializedMemory,
794            ));
795    } else {
796        // If the transfer will write a contiguous region of the buffer, then we
797        // don't need to initialize that region.
798        //
799        // However, if the start and end are not 4B aligned, we need to make
800        // sure that we don't end up trying to initialize non-4B-aligned regions
801        // later.
802        //
803        // Adjust the start/end inwards to 4B alignment, we will handle the
804        // first/last pieces differently.
805        let aligned_start = (start + ALIGN_MASK) & !ALIGN_MASK;
806        let aligned_end = end & !ALIGN_MASK;
807        if aligned_start != start {
808            state.buffer_memory_init_actions.extend(
809                buffer.initialization_status.read().create_action(
810                    buffer,
811                    aligned_start - ALIGN_SIZE..aligned_start,
812                    MemoryInitKind::NeedsInitializedMemory,
813                ),
814            );
815        }
816        if aligned_start != aligned_end {
817            state.buffer_memory_init_actions.extend(
818                buffer.initialization_status.read().create_action(
819                    buffer,
820                    aligned_start..aligned_end,
821                    MemoryInitKind::ImplicitlyInitialized,
822                ),
823            );
824        }
825        if aligned_end != end {
826            // It is possible that `aligned_end + ALIGN_SIZE > dst_buffer.size`,
827            // because `dst_buffer.size` is the user-requested size, not the
828            // final size of the buffer. The final size of the buffer is not
829            // readily available, but was rounded up to COPY_BUFFER_ALIGNMENT,
830            // so no overrun is possible.
831            state.buffer_memory_init_actions.extend(
832                buffer.initialization_status.read().create_action(
833                    buffer,
834                    aligned_end..aligned_end + ALIGN_SIZE,
835                    MemoryInitKind::NeedsInitializedMemory,
836                ),
837            );
838        }
839    }
840}
841
842impl super::CommandEncoder {
843    fn copy_buffer_to_buffer_inner(
844        self: &Arc<Self>,
845        source: Arc<Buffer>,
846        source_offset: BufferAddress,
847        destination: Arc<Buffer>,
848        destination_offset: BufferAddress,
849        size: Option<BufferAddress>,
850    ) -> Result<(), EncoderStateError> {
851        profiling::scope!("CommandEncoder::copy_buffer_to_buffer");
852        api_log!(
853            "CommandEncoder::copy_buffer_to_buffer {:?} -> {:?} {size:?}bytes",
854            Arc::as_ptr(&source),
855            Arc::as_ptr(&destination)
856        );
857
858        let mut cmd_buf_data = self.data.lock();
859
860        cmd_buf_data.push_with(|| -> Result<_, CommandEncoderError> {
861            source.check_is_valid()?;
862            destination.check_is_valid()?;
863            Ok(ArcCommand::CopyBufferToBuffer {
864                src: source,
865                src_offset: source_offset,
866                dst: destination,
867                dst_offset: destination_offset,
868                size,
869            })
870        })
871    }
872
873    pub fn copy_buffer_to_buffer(
874        self: &Arc<Self>,
875        source: Arc<Buffer>,
876        source_offset: BufferAddress,
877        destination: Arc<Buffer>,
878        destination_offset: BufferAddress,
879        size: Option<BufferAddress>,
880    ) {
881        if let Err(err) = self.copy_buffer_to_buffer_inner(
882            source,
883            source_offset,
884            destination,
885            destination_offset,
886            size,
887        ) {
888            self.device.handle_error(
889                err,
890                Some(self.label()),
891                "CommandEncoder::copy_buffer_to_buffer",
892            );
893        }
894    }
895
896    fn copy_buffer_to_texture_inner(
897        self: &Arc<Self>,
898        source: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
899        destination: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
900        copy_size: &Extent3d,
901    ) -> Result<(), EncoderStateError> {
902        profiling::scope!("CommandEncoder::copy_buffer_to_texture");
903        api_log!(
904            "CommandEncoder::copy_buffer_to_texture {:?} -> {:?} {copy_size:?}",
905            Arc::as_ptr(&source.buffer),
906            Arc::as_ptr(&destination.texture)
907        );
908
909        let mut cmd_buf_data = self.data.lock();
910
911        cmd_buf_data.push_with(|| -> Result<_, CommandEncoderError> {
912            let texture = destination.texture.clone();
913            texture.check_valid()?;
914            let source_buffer = source.buffer.clone();
915            source_buffer.check_is_valid()?;
916            Ok(ArcCommand::CopyBufferToTexture {
917                src: wgt::TexelCopyBufferInfo::<Arc<Buffer>> {
918                    buffer: source_buffer,
919                    layout: source.layout,
920                },
921                dst: wgt::TexelCopyTextureInfo::<Arc<Texture>> {
922                    texture,
923                    mip_level: destination.mip_level,
924                    origin: destination.origin,
925                    aspect: destination.aspect,
926                },
927                size: *copy_size,
928            })
929        })
930    }
931
932    pub fn copy_buffer_to_texture(
933        self: &Arc<Self>,
934        source: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
935        destination: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
936        copy_size: &Extent3d,
937    ) {
938        if let Err(err) = self.copy_buffer_to_texture_inner(source, destination, copy_size) {
939            self.device.handle_error(
940                err,
941                Some(self.label()),
942                "CommandEncoder::copy_buffer_to_texture",
943            );
944        }
945    }
946
947    fn copy_texture_to_buffer_inner(
948        self: &Arc<Self>,
949        source: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
950        destination: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
951        copy_size: &Extent3d,
952    ) -> Result<(), EncoderStateError> {
953        profiling::scope!("CommandEncoder::copy_texture_to_buffer");
954        api_log!(
955            "CommandEncoder::copy_texture_to_buffer {:?} -> {:?} {copy_size:?}",
956            Arc::as_ptr(&source.texture),
957            Arc::as_ptr(&destination.buffer)
958        );
959
960        let mut cmd_buf_data = self.data.lock();
961
962        cmd_buf_data.push_with(|| -> Result<_, CommandEncoderError> {
963            let texture = source.texture.clone();
964            texture.check_valid()?;
965            let destination_buffer = destination.buffer.clone();
966            destination_buffer.check_is_valid()?;
967            Ok(ArcCommand::CopyTextureToBuffer {
968                src: wgt::TexelCopyTextureInfo::<Arc<Texture>> {
969                    texture,
970                    mip_level: source.mip_level,
971                    origin: source.origin,
972                    aspect: source.aspect,
973                },
974                dst: wgt::TexelCopyBufferInfo::<Arc<Buffer>> {
975                    buffer: destination_buffer,
976                    layout: destination.layout,
977                },
978                size: *copy_size,
979            })
980        })
981    }
982
983    pub fn copy_texture_to_buffer(
984        self: &Arc<Self>,
985        source: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
986        destination: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
987        copy_size: &Extent3d,
988    ) {
989        if let Err(err) = self.copy_texture_to_buffer_inner(source, destination, copy_size) {
990            self.device.handle_error(
991                err,
992                Some(self.label()),
993                "CommandEncoder::copy_texture_to_buffer",
994            );
995        }
996    }
997
998    fn copy_texture_to_texture_inner(
999        self: &Arc<Self>,
1000        source: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
1001        destination: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
1002        copy_size: &Extent3d,
1003    ) -> Result<(), EncoderStateError> {
1004        profiling::scope!("CommandEncoder::copy_texture_to_texture");
1005        api_log!(
1006            "CommandEncoder::copy_texture_to_texture {:?} -> {:?} {copy_size:?}",
1007            Arc::as_ptr(&source.texture),
1008            Arc::as_ptr(&destination.texture)
1009        );
1010
1011        let mut cmd_buf_data = self.data.lock();
1012
1013        cmd_buf_data.push_with(|| -> Result<_, CommandEncoderError> {
1014            let src_texture = source.texture.clone();
1015            let dst_texture = destination.texture.clone();
1016            src_texture.check_valid()?;
1017            dst_texture.check_valid()?;
1018            Ok(ArcCommand::CopyTextureToTexture {
1019                src: wgt::TexelCopyTextureInfo {
1020                    texture: src_texture,
1021                    mip_level: source.mip_level,
1022                    origin: source.origin,
1023                    aspect: source.aspect,
1024                },
1025                dst: wgt::TexelCopyTextureInfo {
1026                    texture: dst_texture,
1027                    mip_level: destination.mip_level,
1028                    origin: destination.origin,
1029                    aspect: destination.aspect,
1030                },
1031                size: *copy_size,
1032            })
1033        })
1034    }
1035
1036    pub fn copy_texture_to_texture(
1037        self: &Arc<Self>,
1038        source: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
1039        destination: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
1040        copy_size: &Extent3d,
1041    ) {
1042        if let Err(err) = self.copy_texture_to_texture_inner(source, destination, copy_size) {
1043            self.device.handle_error(
1044                err,
1045                Some(self.label()),
1046                "CommandEncoder::copy_texture_to_texture",
1047            );
1048        }
1049    }
1050}
1051
1052pub(super) fn copy_buffer_to_buffer(
1053    state: &mut EncodingState,
1054    src_buffer: &Arc<Buffer>,
1055    source_offset: BufferAddress,
1056    dst_buffer: &Arc<Buffer>,
1057    destination_offset: BufferAddress,
1058    size: Option<BufferAddress>,
1059) -> Result<(), CommandEncoderError> {
1060    if src_buffer.is_equal(dst_buffer) {
1061        return Err(TransferError::SameSourceDestinationBuffer.into());
1062    }
1063
1064    src_buffer.same_device(state.device)?;
1065
1066    let src_pending = state
1067        .tracker
1068        .buffers
1069        .set_single(src_buffer, wgt::BufferUses::COPY_SRC);
1070
1071    let src_raw = src_buffer.try_raw(state.snatch_guard)?;
1072    src_buffer
1073        .check_usage(BufferUsages::COPY_SRC)
1074        .map_err(TransferError::MissingBufferUsage)?;
1075    // expecting only a single barrier
1076    let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer, state.snatch_guard));
1077
1078    dst_buffer.same_device(state.device)?;
1079
1080    let dst_pending = state
1081        .tracker
1082        .buffers
1083        .set_single(dst_buffer, wgt::BufferUses::COPY_DST);
1084
1085    let dst_raw = dst_buffer.try_raw(state.snatch_guard)?;
1086    dst_buffer
1087        .check_usage(BufferUsages::COPY_DST)
1088        .map_err(TransferError::MissingBufferUsage)?;
1089    let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer, state.snatch_guard));
1090
1091    if source_offset > src_buffer.size {
1092        return Err(TransferError::BufferStartOffsetOverrun {
1093            start_offset: source_offset,
1094            buffer_size: src_buffer.size,
1095            side: CopySide::Source,
1096        }
1097        .into());
1098    }
1099    let size = size.unwrap_or_else(|| {
1100        // NOTE: Should never underflow because of our earlier check.
1101        src_buffer.size - source_offset
1102    });
1103
1104    if !size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
1105        return Err(TransferError::UnalignedCopySize(size).into());
1106    }
1107    if !source_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
1108        return Err(TransferError::UnalignedBufferOffset(source_offset).into());
1109    }
1110    if !destination_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
1111        return Err(TransferError::UnalignedBufferOffset(destination_offset).into());
1112    }
1113    if !state
1114        .device
1115        .downlevel
1116        .flags
1117        .contains(wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER)
1118        && (src_buffer.usage.contains(BufferUsages::INDEX)
1119            || dst_buffer.usage.contains(BufferUsages::INDEX))
1120    {
1121        let forbidden_usages = BufferUsages::VERTEX
1122            | BufferUsages::UNIFORM
1123            | BufferUsages::INDIRECT
1124            | BufferUsages::STORAGE;
1125        if src_buffer.usage.intersects(forbidden_usages)
1126            || dst_buffer.usage.intersects(forbidden_usages)
1127        {
1128            return Err(TransferError::MissingDownlevelFlags(MissingDownlevelFlags(
1129                wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER,
1130            ))
1131            .into());
1132        }
1133    }
1134
1135    if size > src_buffer.size - source_offset {
1136        return Err(TransferError::BufferEndOffsetOverrun {
1137            start_offset: source_offset,
1138            size,
1139            buffer_size: src_buffer.size,
1140            side: CopySide::Source,
1141        }
1142        .into());
1143    }
1144    // NOTE: Should never overflow because of our earlier check.
1145    let source_end_offset = source_offset + size;
1146
1147    if destination_offset > dst_buffer.size {
1148        return Err(TransferError::BufferStartOffsetOverrun {
1149            start_offset: destination_offset,
1150            buffer_size: dst_buffer.size,
1151            side: CopySide::Destination,
1152        }
1153        .into());
1154    }
1155    // NOTE: Should never underflow because of our earlier check.
1156    if size > dst_buffer.size - destination_offset {
1157        return Err(TransferError::BufferEndOffsetOverrun {
1158            start_offset: destination_offset,
1159            size,
1160            buffer_size: dst_buffer.size,
1161            side: CopySide::Destination,
1162        }
1163        .into());
1164    }
1165    // NOTE: Should never overflow because of our earlier check.
1166    let destination_end_offset = destination_offset + size;
1167
1168    // This must happen after parameter validation (so that errors are reported
1169    // as required by the spec), but before any side effects.
1170    if size == 0 {
1171        log::trace!("Ignoring copy_buffer_to_buffer of size 0");
1172        return Ok(());
1173    }
1174
1175    // Make sure source is initialized memory and mark dest as initialized.
1176    state
1177        .buffer_memory_init_actions
1178        .extend(dst_buffer.initialization_status.read().create_action(
1179            dst_buffer,
1180            destination_offset..destination_end_offset,
1181            MemoryInitKind::ImplicitlyInitialized,
1182        ));
1183    state
1184        .buffer_memory_init_actions
1185        .extend(src_buffer.initialization_status.read().create_action(
1186            src_buffer,
1187            source_offset..source_end_offset,
1188            MemoryInitKind::NeedsInitializedMemory,
1189        ));
1190
1191    let region = hal::BufferCopy {
1192        src_offset: source_offset,
1193        dst_offset: destination_offset,
1194        size: wgt::BufferSize::new(size).unwrap(),
1195    };
1196    let barriers = src_barrier
1197        .into_iter()
1198        .chain(dst_barrier)
1199        .collect::<Vec<_>>();
1200    unsafe {
1201        state.raw_encoder.transition_buffers(&barriers);
1202        state
1203            .raw_encoder
1204            .copy_buffer_to_buffer(src_raw, dst_raw, &[region]);
1205    }
1206
1207    Ok(())
1208}
1209
1210pub(super) fn copy_buffer_to_texture(
1211    state: &mut EncodingState,
1212    source: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
1213    destination: &wgt::TexelCopyTextureInfo<Arc<Texture>>,
1214    copy_size: &Extent3d,
1215) -> Result<(), CommandEncoderError> {
1216    let dst_texture = &destination.texture;
1217    let src_buffer = &source.buffer;
1218
1219    dst_texture.same_device(state.device)?;
1220    src_buffer.same_device(state.device)?;
1221
1222    let (hal_copy_size, array_layer_count) = validate_texture_copy_range(
1223        destination,
1224        &dst_texture.desc,
1225        CopySide::Destination,
1226        copy_size,
1227    )?;
1228
1229    let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, dst_texture)?;
1230
1231    let src_raw = src_buffer.try_raw(state.snatch_guard)?;
1232    src_buffer
1233        .check_usage(BufferUsages::COPY_SRC)
1234        .map_err(TransferError::MissingBufferUsage)?;
1235
1236    let dst_raw = dst_texture.try_inner(state.snatch_guard)?.raw();
1237    dst_texture
1238        .check_usage(TextureUsages::COPY_DST)
1239        .map_err(TransferError::MissingTextureUsage)?;
1240
1241    validate_texture_copy_dst_format(dst_texture.desc.format, destination.aspect)?;
1242
1243    validate_texture_buffer_copy(
1244        destination,
1245        dst_base.aspect,
1246        &dst_texture.desc,
1247        &source.layout,
1248        true, // alignment required for buffer offset
1249    )?;
1250
1251    let (required_buffer_bytes_in_copy, bytes_per_array_layer, is_contiguous) =
1252        validate_linear_texture_data(
1253            &source.layout,
1254            dst_texture.desc.format,
1255            destination.aspect,
1256            src_buffer.size,
1257            CopySide::Source,
1258            copy_size,
1259        )?;
1260
1261    if dst_texture.desc.format.is_depth_stencil_format() {
1262        state
1263            .device
1264            .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES)
1265            .map_err(TransferError::from)?;
1266    }
1267
1268    // This must happen after parameter validation (so that errors are reported
1269    // as required by the spec), but before any side effects.
1270    if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 {
1271        log::trace!("Ignoring copy_buffer_to_texture of size 0");
1272        return Ok(());
1273    }
1274
1275    // Handle texture init *before* dealing with barrier transitions so we
1276    // have an easier time inserting "immediate-inits" that may be required
1277    // by prior discards in rare cases.
1278    handle_dst_texture_init(state, destination, copy_size, dst_texture)?;
1279
1280    let src_pending = state
1281        .tracker
1282        .buffers
1283        .set_single(src_buffer, wgt::BufferUses::COPY_SRC);
1284    let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer, state.snatch_guard));
1285
1286    let dst_pending =
1287        state
1288            .tracker
1289            .textures
1290            .set_single(dst_texture, dst_range, wgt::TextureUses::COPY_DST);
1291    let dst_barrier = dst_pending
1292        .map(|pending| pending.into_hal(dst_raw))
1293        .collect::<Vec<_>>();
1294
1295    handle_buffer_init(
1296        state,
1297        source,
1298        CopySide::Source,
1299        required_buffer_bytes_in_copy,
1300        is_contiguous,
1301    );
1302
1303    let regions = (0..array_layer_count)
1304        .map(|rel_array_layer| {
1305            let mut texture_base = dst_base.clone();
1306            texture_base.array_layer += rel_array_layer;
1307            let mut buffer_layout = source.layout;
1308            buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer;
1309            hal::BufferTextureCopy {
1310                buffer_layout,
1311                texture_base,
1312                size: hal_copy_size,
1313            }
1314        })
1315        .collect::<Vec<_>>();
1316
1317    unsafe {
1318        state.raw_encoder.transition_textures(&dst_barrier);
1319        state.raw_encoder.transition_buffers(src_barrier.as_slice());
1320        state
1321            .raw_encoder
1322            .copy_buffer_to_texture(src_raw, dst_raw, &regions);
1323    }
1324
1325    Ok(())
1326}
1327
1328pub(super) fn copy_texture_to_buffer(
1329    state: &mut EncodingState,
1330    source: &TexelCopyTextureInfo,
1331    destination: &wgt::TexelCopyBufferInfo<Arc<Buffer>>,
1332    copy_size: &Extent3d,
1333) -> Result<(), CommandEncoderError> {
1334    let src_texture = &source.texture;
1335    let dst_buffer = &destination.buffer;
1336
1337    src_texture.same_device(state.device)?;
1338    dst_buffer.same_device(state.device)?;
1339
1340    let (hal_copy_size, array_layer_count) =
1341        validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?;
1342
1343    let (src_range, src_base) = extract_texture_selector(source, copy_size, src_texture)?;
1344
1345    let src_raw = src_texture.try_inner(state.snatch_guard)?.raw();
1346    src_texture
1347        .check_usage(TextureUsages::COPY_SRC)
1348        .map_err(TransferError::MissingTextureUsage)?;
1349
1350    if source.mip_level >= src_texture.desc.mip_level_count {
1351        return Err(TransferError::InvalidMipLevel {
1352            requested: source.mip_level,
1353            count: src_texture.desc.mip_level_count,
1354        }
1355        .into());
1356    }
1357
1358    validate_texture_copy_src_format(src_texture.desc.format, source.aspect)?;
1359
1360    validate_texture_buffer_copy(
1361        source,
1362        src_base.aspect,
1363        &src_texture.desc,
1364        &destination.layout,
1365        true, // alignment required for buffer offset
1366    )?;
1367
1368    let (required_buffer_bytes_in_copy, bytes_per_array_layer, is_contiguous) =
1369        validate_linear_texture_data(
1370            &destination.layout,
1371            src_texture.desc.format,
1372            source.aspect,
1373            dst_buffer.size,
1374            CopySide::Destination,
1375            copy_size,
1376        )?;
1377
1378    if src_texture.desc.format.is_depth_stencil_format() {
1379        state
1380            .device
1381            .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES)
1382            .map_err(TransferError::from)?;
1383    }
1384
1385    let dst_raw = dst_buffer.try_raw(state.snatch_guard)?;
1386    dst_buffer
1387        .check_usage(BufferUsages::COPY_DST)
1388        .map_err(TransferError::MissingBufferUsage)?;
1389
1390    // This must happen after parameter validation (so that errors are reported
1391    // as required by the spec), but before any side effects.
1392    if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 {
1393        log::trace!("Ignoring copy_texture_to_buffer of size 0");
1394        return Ok(());
1395    }
1396
1397    // Handle texture init *before* dealing with barrier transitions so we
1398    // have an easier time inserting "immediate-inits" that may be required
1399    // by prior discards in rare cases.
1400    handle_src_texture_init(state, source, copy_size, src_texture)?;
1401
1402    let src_pending =
1403        state
1404            .tracker
1405            .textures
1406            .set_single(src_texture, src_range, wgt::TextureUses::COPY_SRC);
1407    let src_barrier = src_pending
1408        .map(|pending| pending.into_hal(src_raw))
1409        .collect::<Vec<_>>();
1410
1411    let dst_pending = state
1412        .tracker
1413        .buffers
1414        .set_single(dst_buffer, wgt::BufferUses::COPY_DST);
1415
1416    let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer, state.snatch_guard));
1417
1418    handle_buffer_init(
1419        state,
1420        destination,
1421        CopySide::Destination,
1422        required_buffer_bytes_in_copy,
1423        is_contiguous,
1424    );
1425
1426    let regions = (0..array_layer_count)
1427        .map(|rel_array_layer| {
1428            let mut texture_base = src_base.clone();
1429            texture_base.array_layer += rel_array_layer;
1430            let mut buffer_layout = destination.layout;
1431            buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer;
1432            hal::BufferTextureCopy {
1433                buffer_layout,
1434                texture_base,
1435                size: hal_copy_size,
1436            }
1437        })
1438        .collect::<Vec<_>>();
1439    unsafe {
1440        state.raw_encoder.transition_buffers(dst_barrier.as_slice());
1441        state.raw_encoder.transition_textures(&src_barrier);
1442        state.raw_encoder.copy_texture_to_buffer(
1443            src_raw,
1444            wgt::TextureUses::COPY_SRC,
1445            dst_raw,
1446            &regions,
1447        );
1448    }
1449
1450    Ok(())
1451}
1452
1453pub(super) fn copy_texture_to_texture(
1454    state: &mut EncodingState,
1455    source: &TexelCopyTextureInfo,
1456    destination: &TexelCopyTextureInfo,
1457    copy_size: &Extent3d,
1458) -> Result<(), CommandEncoderError> {
1459    let src_texture = &source.texture;
1460    let dst_texture = &destination.texture;
1461
1462    src_texture.same_device(state.device)?;
1463    dst_texture.same_device(state.device)?;
1464
1465    // src and dst texture format must be copy-compatible
1466    // (https://gpuweb.github.io/gpuweb/#copy-compatible), with an
1467    // extension allowing one plane of a planar source to be copied
1468    // into a single-plane destination of the matching format
1469    // (e.g. NV12 Plane0 -> R8Unorm, NV12 Plane1 -> Rg8Unorm).
1470    //
1471    // When taking this path, `copy_size` and `source.origin` are
1472    // interpreted in *plane* texels, not luma texels: copying NV12
1473    // Plane1 into an Rg8Unorm of size (W/2, H/2) requires
1474    // `copy_size = (W/2, H/2)`. The plane-extent check further down
1475    // enforces this against the subsampled plane extent, so a caller
1476    // passing luma-sized values gets a source-side error pointing at
1477    // the actual mistake rather than an opaque destination overrun.
1478    let src_fmt_no_srgb = src_texture.desc.format.remove_srgb_suffix();
1479    let dst_fmt_no_srgb = dst_texture.desc.format.remove_srgb_suffix();
1480    let planar_split_ok = src_fmt_no_srgb.is_multi_planar_format()
1481        && src_fmt_no_srgb.aspect_specific_format(source.aspect) == Some(dst_fmt_no_srgb);
1482    if src_fmt_no_srgb != dst_fmt_no_srgb && !planar_split_ok {
1483        return Err(TransferError::TextureFormatsNotCopyCompatible {
1484            src_format: src_texture.desc.format,
1485            dst_format: dst_texture.desc.format,
1486        }
1487        .into());
1488    }
1489
1490    let (src_copy_size, array_layer_count) =
1491        validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?;
1492    let (dst_copy_size, _) = validate_texture_copy_range(
1493        destination,
1494        &dst_texture.desc,
1495        CopySide::Destination,
1496        copy_size,
1497    )?;
1498
1499    // For planar -> single-plane copies, re-check the source extent
1500    // in plane coordinates. `validate_texture_copy_range` above used
1501    // the full luma extent of the planar source, so it does not
1502    // catch a caller treating `copy_size` / `origin` as luma-sized
1503    // when targeting a subsampled plane (NV12/P010 plane 1).
1504    if planar_split_ok {
1505        // `planar_split_ok` implies `aspect_specific_format(source.aspect)`
1506        // returned `Some`, which is only true for `Plane{0,1,2}`.
1507        let plane = source.aspect.to_plane().expect("planar_split_ok aspect");
1508        let plane_extent = src_texture
1509            .desc
1510            .compute_render_extent(source.mip_level, Some(plane));
1511        let check = |dimension, start: u32, size: u32, plane_size: u32| {
1512            if start > plane_size || plane_size - start < size {
1513                Err(TransferError::TextureOverrun {
1514                    start_offset: start,
1515                    end_offset: start.wrapping_add(size),
1516                    texture_size: plane_size,
1517                    dimension,
1518                    side: CopySide::Source,
1519                })
1520            } else {
1521                Ok(())
1522            }
1523        };
1524        check(
1525            TextureErrorDimension::X,
1526            source.origin.x,
1527            copy_size.width,
1528            plane_extent.width,
1529        )?;
1530        check(
1531            TextureErrorDimension::Y,
1532            source.origin.y,
1533            copy_size.height,
1534            plane_extent.height,
1535        )?;
1536    }
1537
1538    if Arc::as_ptr(src_texture) == Arc::as_ptr(dst_texture) {
1539        validate_copy_within_same_texture(
1540            source,
1541            destination,
1542            src_texture.desc.format,
1543            array_layer_count,
1544        )?;
1545    }
1546
1547    let (src_range, src_tex_base) = extract_texture_selector(source, copy_size, src_texture)?;
1548    let (dst_range, dst_tex_base) = extract_texture_selector(destination, copy_size, dst_texture)?;
1549    let src_texture_aspects = hal::FormatAspects::from(src_texture.desc.format);
1550    let dst_texture_aspects = hal::FormatAspects::from(dst_texture.desc.format);
1551    // `planar_split_ok` already constrains `source.aspect` to a single plane.
1552    if src_tex_base.aspect != src_texture_aspects && !planar_split_ok {
1553        return Err(TransferError::CopySrcMissingAspects.into());
1554    }
1555    if dst_tex_base.aspect != dst_texture_aspects {
1556        return Err(TransferError::CopyDstMissingAspects.into());
1557    }
1558
1559    if src_texture.desc.sample_count != dst_texture.desc.sample_count {
1560        return Err(TransferError::SampleCountNotEqual {
1561            src_sample_count: src_texture.desc.sample_count,
1562            dst_sample_count: dst_texture.desc.sample_count,
1563        }
1564        .into());
1565    }
1566
1567    let src_raw = src_texture.try_inner(state.snatch_guard)?.raw();
1568    src_texture
1569        .check_usage(TextureUsages::COPY_SRC)
1570        .map_err(TransferError::MissingTextureUsage)?;
1571    let dst_raw = dst_texture.try_inner(state.snatch_guard)?.raw();
1572    dst_texture
1573        .check_usage(TextureUsages::COPY_DST)
1574        .map_err(TransferError::MissingTextureUsage)?;
1575
1576    // This must happen after parameter validation (so that errors are reported
1577    // as required by the spec), but before any side effects.
1578    if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 {
1579        log::trace!("Ignoring copy_texture_to_texture of size 0");
1580        return Ok(());
1581    }
1582
1583    // Handle texture init *before* dealing with barrier transitions so we
1584    // have an easier time inserting "immediate-inits" that may be required
1585    // by prior discards in rare cases.
1586    handle_src_texture_init(state, source, copy_size, src_texture)?;
1587    handle_dst_texture_init(state, destination, copy_size, dst_texture)?;
1588
1589    let src_pending =
1590        state
1591            .tracker
1592            .textures
1593            .set_single(src_texture, src_range, wgt::TextureUses::COPY_SRC);
1594
1595    //TODO: try to avoid this the collection. It's needed because both
1596    // `src_pending` and `dst_pending` try to hold `trackers.textures` mutably.
1597    let mut barriers: ArrayVec<_, 2> = src_pending
1598        .map(|pending| pending.into_hal(src_raw))
1599        .collect();
1600
1601    let dst_pending =
1602        state
1603            .tracker
1604            .textures
1605            .set_single(dst_texture, dst_range, wgt::TextureUses::COPY_DST);
1606    barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_raw)));
1607
1608    let hal_copy_size = hal::CopyExtent {
1609        width: src_copy_size.width.min(dst_copy_size.width),
1610        height: src_copy_size.height.min(dst_copy_size.height),
1611        depth: src_copy_size.depth.min(dst_copy_size.depth),
1612    };
1613
1614    let dst_format = dst_texture.desc.format;
1615
1616    let regions = (0..array_layer_count).map(|rel_array_layer| {
1617        let mut src_base = src_tex_base.clone();
1618        let mut dst_base = dst_tex_base.clone();
1619        src_base.array_layer += rel_array_layer;
1620        dst_base.array_layer += rel_array_layer;
1621        hal::TextureCopy {
1622            src_base,
1623            dst_base,
1624            size: hal_copy_size,
1625        }
1626    });
1627
1628    let regions = if dst_tex_base.aspect == hal::FormatAspects::DEPTH_STENCIL {
1629        regions
1630            .flat_map(|region| {
1631                let (mut depth, mut stencil) = (region.clone(), region);
1632                depth.src_base.aspect = hal::FormatAspects::DEPTH;
1633                depth.dst_base.aspect = hal::FormatAspects::DEPTH;
1634                stencil.src_base.aspect = hal::FormatAspects::STENCIL;
1635                stencil.dst_base.aspect = hal::FormatAspects::STENCIL;
1636                [depth, stencil]
1637            })
1638            .collect::<Vec<_>>()
1639    } else if let Some(plane_count) = dst_format.planes() {
1640        regions
1641            .into_iter()
1642            .flat_map(|region| {
1643                (0..plane_count).map(move |plane| {
1644                    let mut plane_region = region.clone();
1645
1646                    let plane_aspect = wgt::TextureAspect::from_plane(plane)
1647                        .expect("expected texture aspect to exist for the plane");
1648                    let plane_aspect = hal::FormatAspects::new(dst_format, plane_aspect);
1649                    plane_region.src_base.aspect = plane_aspect;
1650                    plane_region.dst_base.aspect = plane_aspect;
1651
1652                    let (w_subsampling, h_subsampling) =
1653                        dst_format.subsampling_factors(Some(plane));
1654                    plane_region.src_base.origin.x /= w_subsampling;
1655                    plane_region.src_base.origin.y /= h_subsampling;
1656                    plane_region.dst_base.origin.x /= w_subsampling;
1657                    plane_region.dst_base.origin.y /= h_subsampling;
1658
1659                    plane_region.size.width /= w_subsampling;
1660                    plane_region.size.height /= h_subsampling;
1661
1662                    plane_region
1663                })
1664            })
1665            .collect::<Vec<_>>()
1666    } else {
1667        regions.collect::<Vec<_>>()
1668    };
1669    unsafe {
1670        state.raw_encoder.transition_textures(&barriers);
1671        state.raw_encoder.copy_texture_to_texture(
1672            src_raw,
1673            wgt::TextureUses::COPY_SRC,
1674            dst_raw,
1675            &regions,
1676        );
1677    }
1678
1679    Ok(())
1680}