wgpu_core/command/
transfer.rs

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