wgpu_core/command/
transfer.rs

1#[cfg(feature = "trace")]
2use crate::device::trace::Command as TraceCommand;
3use crate::{
4    api_log,
5    command::{clear_texture, CommandBuffer, CommandEncoderError},
6    conv,
7    device::{Device, DeviceError, MissingDownlevelFlags},
8    error::{ErrorFormatter, PrettyError},
9    global::Global,
10    hal_api::HalApi,
11    id::{BufferId, CommandEncoderId, DeviceId, TextureId},
12    init_tracker::{
13        has_copy_partial_init_tracker_coverage, MemoryInitKind, TextureInitRange,
14        TextureInitTrackerAction,
15    },
16    resource::{Resource, Texture, TextureErrorDimension},
17    snatch::SnatchGuard,
18    track::{TextureSelector, Tracker},
19};
20
21use arrayvec::ArrayVec;
22use hal::CommandEncoder as _;
23use thiserror::Error;
24use wgt::{BufferAddress, BufferUsages, Extent3d, TextureUsages};
25
26use std::{iter, sync::Arc};
27
28use super::{memory_init::CommandBufferTextureMemoryActions, ClearError, CommandEncoder};
29
30pub type ImageCopyBuffer = wgt::ImageCopyBuffer<BufferId>;
31pub type ImageCopyTexture = wgt::ImageCopyTexture<TextureId>;
32pub type ImageCopyTextureTagged = wgt::ImageCopyTextureTagged<TextureId>;
33
34#[derive(Clone, Copy, Debug)]
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("Device {0:?} is invalid")]
45    InvalidDevice(DeviceId),
46    #[error("Buffer {0:?} is invalid or destroyed")]
47    InvalidBuffer(BufferId),
48    #[error("Texture {0:?} is invalid or destroyed")]
49    InvalidTexture(TextureId),
50    #[error("Source and destination cannot be the same buffer")]
51    SameSourceDestinationBuffer,
52    #[error("Source buffer/texture is missing the `COPY_SRC` usage flag")]
53    MissingCopySrcUsageFlag,
54    #[error("Destination buffer/texture is missing the `COPY_DST` usage flag")]
55    MissingCopyDstUsageFlag(Option<BufferId>, Option<TextureId>),
56    #[error("Destination texture is missing the `RENDER_ATTACHMENT` usage flag")]
57    MissingRenderAttachmentUsageFlag(TextureId),
58    #[error("Copy of {start_offset}..{end_offset} would end up overrunning the bounds of the {side:?} buffer of size {buffer_size}")]
59    BufferOverrun {
60        start_offset: BufferAddress,
61        end_offset: BufferAddress,
62        buffer_size: BufferAddress,
63        side: CopySide,
64    },
65    #[error("Copy of {dimension:?} {start_offset}..{end_offset} would end up overrunning the bounds of the {side:?} texture of {dimension:?} size {texture_size}")]
66    TextureOverrun {
67        start_offset: u32,
68        end_offset: u32,
69        texture_size: u32,
70        dimension: TextureErrorDimension,
71        side: CopySide,
72    },
73    #[error("Unable to select texture aspect {aspect:?} from format {format:?}")]
74    InvalidTextureAspect {
75        format: wgt::TextureFormat,
76        aspect: wgt::TextureAspect,
77    },
78    #[error("Unable to select texture mip level {level} out of {total}")]
79    InvalidTextureMipLevel { level: u32, total: u32 },
80    #[error("Texture dimension must be 2D when copying from an external texture")]
81    InvalidDimensionExternal(TextureId),
82    #[error("Buffer offset {0} is not aligned to block size or `COPY_BUFFER_ALIGNMENT`")]
83    UnalignedBufferOffset(BufferAddress),
84    #[error("Copy size {0} does not respect `COPY_BUFFER_ALIGNMENT`")]
85    UnalignedCopySize(BufferAddress),
86    #[error("Copy width is not a multiple of block width")]
87    UnalignedCopyWidth,
88    #[error("Copy height is not a multiple of block height")]
89    UnalignedCopyHeight,
90    #[error("Copy origin's x component is not a multiple of block width")]
91    UnalignedCopyOriginX,
92    #[error("Copy origin's y component is not a multiple of block height")]
93    UnalignedCopyOriginY,
94    #[error("Bytes per row does not respect `COPY_BYTES_PER_ROW_ALIGNMENT`")]
95    UnalignedBytesPerRow,
96    #[error("Number of bytes per row needs to be specified since more than one row is copied")]
97    UnspecifiedBytesPerRow,
98    #[error("Number of rows per image needs to be specified since more than one image is copied")]
99    UnspecifiedRowsPerImage,
100    #[error("Number of bytes per row is less than the number of bytes in a complete row")]
101    InvalidBytesPerRow,
102    #[error("Image is 1D and the copy height and depth are not both set to 1")]
103    InvalidCopySize,
104    #[error("Number of rows per image is invalid")]
105    InvalidRowsPerImage,
106    #[error("Copy source aspects must refer to all aspects of the source texture format")]
107    CopySrcMissingAspects,
108    #[error(
109        "Copy destination aspects must refer to all aspects of the destination texture format"
110    )]
111    CopyDstMissingAspects,
112    #[error("Copy aspect must refer to a single aspect of texture format")]
113    CopyAspectNotOne,
114    #[error("Copying from textures with format {format:?} and aspect {aspect:?} is forbidden")]
115    CopyFromForbiddenTextureFormat {
116        format: wgt::TextureFormat,
117        aspect: wgt::TextureAspect,
118    },
119    #[error("Copying to textures with format {format:?} and aspect {aspect:?} is forbidden")]
120    CopyToForbiddenTextureFormat {
121        format: wgt::TextureFormat,
122        aspect: wgt::TextureAspect,
123    },
124    #[error(
125        "Copying to textures with format {0:?} is forbidden when copying from external texture"
126    )]
127    ExternalCopyToForbiddenTextureFormat(wgt::TextureFormat),
128    #[error("The entire texture must be copied when copying from depth texture")]
129    InvalidDepthTextureExtent,
130    #[error(
131        "Source format ({src_format:?}) and destination format ({dst_format:?}) are not copy-compatible (they may only differ in srgb-ness)"
132    )]
133    TextureFormatsNotCopyCompatible {
134        src_format: wgt::TextureFormat,
135        dst_format: wgt::TextureFormat,
136    },
137    #[error(transparent)]
138    MemoryInitFailure(#[from] ClearError),
139    #[error("Cannot encode this copy because of a missing downelevel flag")]
140    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
141    #[error("Source texture sample count must be 1, got {sample_count}")]
142    InvalidSampleCount { sample_count: u32 },
143    #[error("Requested mip level {requested} does no exist (count: {count})")]
144    InvalidMipLevel { requested: u32, count: u32 },
145}
146
147impl PrettyError for TransferError {
148    fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
149        fmt.error(self);
150        match *self {
151            Self::InvalidBuffer(id) => {
152                fmt.buffer_label(&id);
153            }
154            Self::InvalidTexture(id) => {
155                fmt.texture_label(&id);
156            }
157            // Self::MissingCopySrcUsageFlag(buf_opt, tex_opt) => {
158            //     if let Some(buf) = buf_opt {
159            //         let name = crate::gfx_select!(buf => global.buffer_label(buf));
160            //         ret.push_str(&format_label_line("source", &name));
161            //     }
162            //     if let Some(tex) = tex_opt {
163            //         let name = crate::gfx_select!(tex => global.texture_label(tex));
164            //         ret.push_str(&format_label_line("source", &name));
165            //     }
166            // }
167            Self::MissingCopyDstUsageFlag(buf_opt, tex_opt) => {
168                if let Some(buf) = buf_opt {
169                    fmt.buffer_label_with_key(&buf, "destination");
170                }
171                if let Some(tex) = tex_opt {
172                    fmt.texture_label_with_key(&tex, "destination");
173                }
174            }
175            _ => {}
176        };
177    }
178}
179/// Error encountered while attempting to do a copy on a command encoder.
180#[derive(Clone, Debug, Error)]
181#[non_exhaustive]
182pub enum CopyError {
183    #[error(transparent)]
184    Encoder(#[from] CommandEncoderError),
185    #[error("Copy error")]
186    Transfer(#[from] TransferError),
187}
188
189impl From<DeviceError> for CopyError {
190    fn from(err: DeviceError) -> Self {
191        CopyError::Encoder(CommandEncoderError::Device(err))
192    }
193}
194
195pub(crate) fn extract_texture_selector<A: HalApi>(
196    copy_texture: &ImageCopyTexture,
197    copy_size: &Extent3d,
198    texture: &Texture<A>,
199) -> Result<(TextureSelector, hal::TextureCopyBase), TransferError> {
200    let format = texture.desc.format;
201    let copy_aspect = hal::FormatAspects::new(format, copy_texture.aspect);
202    if copy_aspect.is_empty() {
203        return Err(TransferError::InvalidTextureAspect {
204            format,
205            aspect: copy_texture.aspect,
206        });
207    }
208
209    let (layers, origin_z) = match texture.desc.dimension {
210        wgt::TextureDimension::D1 => (0..1, 0),
211        wgt::TextureDimension::D2 => (
212            copy_texture.origin.z..copy_texture.origin.z + copy_size.depth_or_array_layers,
213            0,
214        ),
215        wgt::TextureDimension::D3 => (0..1, copy_texture.origin.z),
216    };
217    let base = hal::TextureCopyBase {
218        origin: wgt::Origin3d {
219            x: copy_texture.origin.x,
220            y: copy_texture.origin.y,
221            z: origin_z,
222        },
223        // this value will be incremented per copied layer
224        array_layer: layers.start,
225        mip_level: copy_texture.mip_level,
226        aspect: copy_aspect,
227    };
228    let selector = TextureSelector {
229        mips: copy_texture.mip_level..copy_texture.mip_level + 1,
230        layers,
231    };
232
233    Ok((selector, base))
234}
235
236/// WebGPU's [validating linear texture data][vltd] algorithm.
237///
238/// Copied with some modifications from WebGPU standard.
239///
240/// If successful, returns a pair `(bytes, stride)`, where:
241/// - `bytes` is the number of buffer bytes required for this copy, and
242/// - `stride` number of bytes between array layers.
243///
244/// [vltd]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-linear-texture-data
245pub(crate) fn validate_linear_texture_data(
246    layout: &wgt::ImageDataLayout,
247    format: wgt::TextureFormat,
248    aspect: wgt::TextureAspect,
249    buffer_size: BufferAddress,
250    buffer_side: CopySide,
251    copy_size: &Extent3d,
252    need_copy_aligned_rows: bool,
253) -> Result<(BufferAddress, BufferAddress), TransferError> {
254    // Convert all inputs to BufferAddress (u64) to avoid some of the overflow issues
255    // Note: u64 is not always enough to prevent overflow, especially when multiplying
256    // something with a potentially large depth value, so it is preferable to validate
257    // the copy size before calling this function (for example via `validate_texture_copy_range`).
258    let copy_width = copy_size.width as BufferAddress;
259    let copy_height = copy_size.height as BufferAddress;
260    let copy_depth = copy_size.depth_or_array_layers as BufferAddress;
261
262    let offset = layout.offset;
263
264    let block_size = format.block_copy_size(Some(aspect)).unwrap() as BufferAddress;
265    let (block_width, block_height) = format.block_dimensions();
266    let block_width = block_width as BufferAddress;
267    let block_height = block_height as BufferAddress;
268
269    if copy_width % block_width != 0 {
270        return Err(TransferError::UnalignedCopyWidth);
271    }
272    if copy_height % block_height != 0 {
273        return Err(TransferError::UnalignedCopyHeight);
274    }
275
276    let width_in_blocks = copy_width / block_width;
277    let height_in_blocks = copy_height / block_height;
278
279    let bytes_in_last_row = width_in_blocks * block_size;
280
281    let bytes_per_row = if let Some(bytes_per_row) = layout.bytes_per_row {
282        let bytes_per_row = bytes_per_row as BufferAddress;
283        if bytes_per_row < bytes_in_last_row {
284            return Err(TransferError::InvalidBytesPerRow);
285        }
286        bytes_per_row
287    } else {
288        if copy_depth > 1 || height_in_blocks > 1 {
289            return Err(TransferError::UnspecifiedBytesPerRow);
290        }
291        0
292    };
293    let block_rows_per_image = if let Some(rows_per_image) = layout.rows_per_image {
294        let rows_per_image = rows_per_image as BufferAddress;
295        if rows_per_image < height_in_blocks {
296            return Err(TransferError::InvalidRowsPerImage);
297        }
298        rows_per_image
299    } else {
300        if copy_depth > 1 {
301            return Err(TransferError::UnspecifiedRowsPerImage);
302        }
303        0
304    };
305
306    if need_copy_aligned_rows {
307        let bytes_per_row_alignment = wgt::COPY_BYTES_PER_ROW_ALIGNMENT as BufferAddress;
308
309        let mut offset_alignment = block_size;
310        if format.is_depth_stencil_format() {
311            offset_alignment = 4
312        }
313        if offset % offset_alignment != 0 {
314            return Err(TransferError::UnalignedBufferOffset(offset));
315        }
316
317        if bytes_per_row % bytes_per_row_alignment != 0 {
318            return Err(TransferError::UnalignedBytesPerRow);
319        }
320    }
321
322    let bytes_per_image = bytes_per_row * block_rows_per_image;
323
324    let required_bytes_in_copy = if copy_depth == 0 {
325        0
326    } else {
327        let mut required_bytes_in_copy = bytes_per_image * (copy_depth - 1);
328        if height_in_blocks > 0 {
329            required_bytes_in_copy += bytes_per_row * (height_in_blocks - 1) + bytes_in_last_row;
330        }
331        required_bytes_in_copy
332    };
333
334    if offset + required_bytes_in_copy > buffer_size {
335        return Err(TransferError::BufferOverrun {
336            start_offset: offset,
337            end_offset: offset + required_bytes_in_copy,
338            buffer_size,
339            side: buffer_side,
340        });
341    }
342
343    Ok((required_bytes_in_copy, bytes_per_image))
344}
345
346/// WebGPU's [validating texture copy range][vtcr] algorithm.
347///
348/// Copied with minor modifications from WebGPU standard.
349///
350/// Returns the HAL copy extent and the layer count.
351///
352/// [vtcr]: https://gpuweb.github.io/gpuweb/#validating-texture-copy-range
353pub(crate) fn validate_texture_copy_range(
354    texture_copy_view: &ImageCopyTexture,
355    desc: &wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>,
356    texture_side: CopySide,
357    copy_size: &Extent3d,
358) -> Result<(hal::CopyExtent, u32), TransferError> {
359    let (block_width, block_height) = desc.format.block_dimensions();
360
361    let extent_virtual = desc.mip_level_size(texture_copy_view.mip_level).ok_or(
362        TransferError::InvalidTextureMipLevel {
363            level: texture_copy_view.mip_level,
364            total: desc.mip_level_count,
365        },
366    )?;
367    // physical size can be larger than the virtual
368    let extent = extent_virtual.physical_size(desc.format);
369
370    if desc.format.is_depth_stencil_format() && *copy_size != extent {
371        return Err(TransferError::InvalidDepthTextureExtent);
372    }
373
374    /// Return `Ok` if a run `size` texels long starting at `start_offset` falls
375    /// entirely within `texture_size`. Otherwise, return an appropriate a`Err`.
376    fn check_dimension(
377        dimension: TextureErrorDimension,
378        side: CopySide,
379        start_offset: u32,
380        size: u32,
381        texture_size: u32,
382    ) -> Result<(), TransferError> {
383        // Avoid underflow in the subtraction by checking start_offset against
384        // texture_size first.
385        if start_offset <= texture_size && size <= texture_size - start_offset {
386            Ok(())
387        } else {
388            Err(TransferError::TextureOverrun {
389                start_offset,
390                end_offset: start_offset.wrapping_add(size),
391                texture_size,
392                dimension,
393                side,
394            })
395        }
396    }
397
398    check_dimension(
399        TextureErrorDimension::X,
400        texture_side,
401        texture_copy_view.origin.x,
402        copy_size.width,
403        extent.width,
404    )?;
405    check_dimension(
406        TextureErrorDimension::Y,
407        texture_side,
408        texture_copy_view.origin.y,
409        copy_size.height,
410        extent.height,
411    )?;
412    check_dimension(
413        TextureErrorDimension::Z,
414        texture_side,
415        texture_copy_view.origin.z,
416        copy_size.depth_or_array_layers,
417        extent.depth_or_array_layers,
418    )?;
419
420    if texture_copy_view.origin.x % block_width != 0 {
421        return Err(TransferError::UnalignedCopyOriginX);
422    }
423    if texture_copy_view.origin.y % block_height != 0 {
424        return Err(TransferError::UnalignedCopyOriginY);
425    }
426    if copy_size.width % block_width != 0 {
427        return Err(TransferError::UnalignedCopyWidth);
428    }
429    if copy_size.height % block_height != 0 {
430        return Err(TransferError::UnalignedCopyHeight);
431    }
432
433    let (depth, array_layer_count) = match desc.dimension {
434        wgt::TextureDimension::D1 => (1, 1),
435        wgt::TextureDimension::D2 => (1, copy_size.depth_or_array_layers),
436        wgt::TextureDimension::D3 => (copy_size.depth_or_array_layers, 1),
437    };
438
439    let copy_extent = hal::CopyExtent {
440        width: copy_size.width,
441        height: copy_size.height,
442        depth,
443    };
444    Ok((copy_extent, array_layer_count))
445}
446
447fn handle_texture_init<A: HalApi>(
448    init_kind: MemoryInitKind,
449    encoder: &mut CommandEncoder<A>,
450    trackers: &mut Tracker<A>,
451    texture_memory_actions: &mut CommandBufferTextureMemoryActions<A>,
452    device: &Device<A>,
453    copy_texture: &ImageCopyTexture,
454    copy_size: &Extent3d,
455    texture: &Arc<Texture<A>>,
456    snatch_guard: &SnatchGuard<'_>,
457) -> Result<(), ClearError> {
458    let init_action = TextureInitTrackerAction {
459        texture: texture.clone(),
460        range: TextureInitRange {
461            mip_range: copy_texture.mip_level..copy_texture.mip_level + 1,
462            layer_range: copy_texture.origin.z
463                ..(copy_texture.origin.z + copy_size.depth_or_array_layers),
464        },
465        kind: init_kind,
466    };
467
468    // Register the init action.
469    let immediate_inits = texture_memory_actions.register_init_action(&{ init_action });
470
471    // In rare cases we may need to insert an init operation immediately onto the command buffer.
472    if !immediate_inits.is_empty() {
473        let cmd_buf_raw = encoder.open()?;
474        for init in immediate_inits {
475            clear_texture(
476                &init.texture,
477                TextureInitRange {
478                    mip_range: init.mip_level..(init.mip_level + 1),
479                    layer_range: init.layer..(init.layer + 1),
480                },
481                cmd_buf_raw,
482                &mut trackers.textures,
483                &device.alignments,
484                device.zero_buffer.as_ref().unwrap(),
485                snatch_guard,
486            )?;
487        }
488    }
489
490    Ok(())
491}
492
493/// Prepare a transfer's source texture.
494///
495/// Ensure the source texture of a transfer is in the right initialization
496/// state, and record the state for after the transfer operation.
497fn handle_src_texture_init<A: HalApi>(
498    encoder: &mut CommandEncoder<A>,
499    trackers: &mut Tracker<A>,
500    texture_memory_actions: &mut CommandBufferTextureMemoryActions<A>,
501    device: &Device<A>,
502    source: &ImageCopyTexture,
503    copy_size: &Extent3d,
504    texture: &Arc<Texture<A>>,
505    snatch_guard: &SnatchGuard<'_>,
506) -> Result<(), TransferError> {
507    handle_texture_init(
508        MemoryInitKind::NeedsInitializedMemory,
509        encoder,
510        trackers,
511        texture_memory_actions,
512        device,
513        source,
514        copy_size,
515        texture,
516        snatch_guard,
517    )?;
518    Ok(())
519}
520
521/// Prepare a transfer's destination texture.
522///
523/// Ensure the destination texture of a transfer is in the right initialization
524/// state, and record the state for after the transfer operation.
525fn handle_dst_texture_init<A: HalApi>(
526    encoder: &mut CommandEncoder<A>,
527    trackers: &mut Tracker<A>,
528    texture_memory_actions: &mut CommandBufferTextureMemoryActions<A>,
529    device: &Device<A>,
530    destination: &ImageCopyTexture,
531    copy_size: &Extent3d,
532    texture: &Arc<Texture<A>>,
533    snatch_guard: &SnatchGuard<'_>,
534) -> Result<(), TransferError> {
535    // Attention: If we don't write full texture subresources, we need to a full
536    // clear first since we don't track subrects. This means that in rare cases
537    // even a *destination* texture of a transfer may need an immediate texture
538    // init.
539    let dst_init_kind = if has_copy_partial_init_tracker_coverage(
540        copy_size,
541        destination.mip_level,
542        &texture.desc,
543    ) {
544        MemoryInitKind::NeedsInitializedMemory
545    } else {
546        MemoryInitKind::ImplicitlyInitialized
547    };
548
549    handle_texture_init(
550        dst_init_kind,
551        encoder,
552        trackers,
553        texture_memory_actions,
554        device,
555        destination,
556        copy_size,
557        texture,
558        snatch_guard,
559    )?;
560    Ok(())
561}
562
563impl Global {
564    pub fn command_encoder_copy_buffer_to_buffer<A: HalApi>(
565        &self,
566        command_encoder_id: CommandEncoderId,
567        source: BufferId,
568        source_offset: BufferAddress,
569        destination: BufferId,
570        destination_offset: BufferAddress,
571        size: BufferAddress,
572    ) -> Result<(), CopyError> {
573        profiling::scope!("CommandEncoder::copy_buffer_to_buffer");
574        api_log!(
575            "CommandEncoder::copy_buffer_to_buffer {source:?} -> {destination:?} {size:?}bytes"
576        );
577
578        if source == destination {
579            return Err(TransferError::SameSourceDestinationBuffer.into());
580        }
581        let hub = A::hub(self);
582
583        let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?;
584        let mut cmd_buf_data = cmd_buf.data.lock();
585        let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
586
587        let device = &cmd_buf.device;
588        if !device.is_valid() {
589            return Err(TransferError::InvalidDevice(cmd_buf.device.as_info().id()).into());
590        }
591
592        #[cfg(feature = "trace")]
593        if let Some(ref mut list) = cmd_buf_data.commands {
594            list.push(TraceCommand::CopyBufferToBuffer {
595                src: source,
596                src_offset: source_offset,
597                dst: destination,
598                dst_offset: destination_offset,
599                size,
600            });
601        }
602
603        let snatch_guard = device.snatchable_lock.read();
604
605        let (src_buffer, src_pending) = {
606            let buffer_guard = hub.buffers.read();
607            let src_buffer = buffer_guard
608                .get(source)
609                .map_err(|_| TransferError::InvalidBuffer(source))?;
610
611            if src_buffer.device.as_info().id() != device.as_info().id() {
612                return Err(DeviceError::WrongDevice.into());
613            }
614
615            cmd_buf_data
616                .trackers
617                .buffers
618                .set_single(src_buffer, hal::BufferUses::COPY_SRC)
619                .ok_or(TransferError::InvalidBuffer(source))?
620        };
621        let src_raw = src_buffer
622            .raw
623            .get(&snatch_guard)
624            .ok_or(TransferError::InvalidBuffer(source))?;
625        if !src_buffer.usage.contains(BufferUsages::COPY_SRC) {
626            return Err(TransferError::MissingCopySrcUsageFlag.into());
627        }
628        // expecting only a single barrier
629        let src_barrier = src_pending.map(|pending| pending.into_hal(&src_buffer, &snatch_guard));
630
631        let (dst_buffer, dst_pending) = {
632            let buffer_guard = hub.buffers.read();
633            let dst_buffer = buffer_guard
634                .get(destination)
635                .map_err(|_| TransferError::InvalidBuffer(destination))?;
636
637            if dst_buffer.device.as_info().id() != device.as_info().id() {
638                return Err(DeviceError::WrongDevice.into());
639            }
640
641            cmd_buf_data
642                .trackers
643                .buffers
644                .set_single(dst_buffer, hal::BufferUses::COPY_DST)
645                .ok_or(TransferError::InvalidBuffer(destination))?
646        };
647        let dst_raw = dst_buffer
648            .raw
649            .get(&snatch_guard)
650            .ok_or(TransferError::InvalidBuffer(destination))?;
651        if !dst_buffer.usage.contains(BufferUsages::COPY_DST) {
652            return Err(TransferError::MissingCopyDstUsageFlag(Some(destination), None).into());
653        }
654        let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard));
655
656        if size % wgt::COPY_BUFFER_ALIGNMENT != 0 {
657            return Err(TransferError::UnalignedCopySize(size).into());
658        }
659        if source_offset % wgt::COPY_BUFFER_ALIGNMENT != 0 {
660            return Err(TransferError::UnalignedBufferOffset(source_offset).into());
661        }
662        if destination_offset % wgt::COPY_BUFFER_ALIGNMENT != 0 {
663            return Err(TransferError::UnalignedBufferOffset(destination_offset).into());
664        }
665        if !device
666            .downlevel
667            .flags
668            .contains(wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER)
669            && (src_buffer.usage.contains(wgt::BufferUsages::INDEX)
670                || dst_buffer.usage.contains(wgt::BufferUsages::INDEX))
671        {
672            let forbidden_usages = wgt::BufferUsages::VERTEX
673                | wgt::BufferUsages::UNIFORM
674                | wgt::BufferUsages::INDIRECT
675                | wgt::BufferUsages::STORAGE;
676            if src_buffer.usage.intersects(forbidden_usages)
677                || dst_buffer.usage.intersects(forbidden_usages)
678            {
679                return Err(TransferError::MissingDownlevelFlags(MissingDownlevelFlags(
680                    wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER,
681                ))
682                .into());
683            }
684        }
685
686        let source_end_offset = source_offset + size;
687        let destination_end_offset = destination_offset + size;
688        if source_end_offset > src_buffer.size {
689            return Err(TransferError::BufferOverrun {
690                start_offset: source_offset,
691                end_offset: source_end_offset,
692                buffer_size: src_buffer.size,
693                side: CopySide::Source,
694            }
695            .into());
696        }
697        if destination_end_offset > dst_buffer.size {
698            return Err(TransferError::BufferOverrun {
699                start_offset: destination_offset,
700                end_offset: destination_end_offset,
701                buffer_size: dst_buffer.size,
702                side: CopySide::Destination,
703            }
704            .into());
705        }
706
707        if size == 0 {
708            log::trace!("Ignoring copy_buffer_to_buffer of size 0");
709            return Ok(());
710        }
711
712        // Make sure source is initialized memory and mark dest as initialized.
713        cmd_buf_data.buffer_memory_init_actions.extend(
714            dst_buffer.initialization_status.read().create_action(
715                &dst_buffer,
716                destination_offset..(destination_offset + size),
717                MemoryInitKind::ImplicitlyInitialized,
718            ),
719        );
720        cmd_buf_data.buffer_memory_init_actions.extend(
721            src_buffer.initialization_status.read().create_action(
722                &src_buffer,
723                source_offset..(source_offset + size),
724                MemoryInitKind::NeedsInitializedMemory,
725            ),
726        );
727
728        let region = hal::BufferCopy {
729            src_offset: source_offset,
730            dst_offset: destination_offset,
731            size: wgt::BufferSize::new(size).unwrap(),
732        };
733        let cmd_buf_raw = cmd_buf_data.encoder.open()?;
734        unsafe {
735            cmd_buf_raw.transition_buffers(src_barrier.into_iter().chain(dst_barrier));
736            cmd_buf_raw.copy_buffer_to_buffer(src_raw, dst_raw, iter::once(region));
737        }
738        Ok(())
739    }
740
741    pub fn command_encoder_copy_buffer_to_texture<A: HalApi>(
742        &self,
743        command_encoder_id: CommandEncoderId,
744        source: &ImageCopyBuffer,
745        destination: &ImageCopyTexture,
746        copy_size: &Extent3d,
747    ) -> Result<(), CopyError> {
748        profiling::scope!("CommandEncoder::copy_buffer_to_texture");
749        api_log!(
750            "CommandEncoder::copy_buffer_to_texture {:?} -> {:?} {copy_size:?}",
751            source.buffer,
752            destination.texture
753        );
754
755        let hub = A::hub(self);
756
757        let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?;
758        let device = &cmd_buf.device;
759        if !device.is_valid() {
760            return Err(TransferError::InvalidDevice(cmd_buf.device.as_info().id()).into());
761        }
762
763        let mut cmd_buf_data = cmd_buf.data.lock();
764        let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
765
766        #[cfg(feature = "trace")]
767        if let Some(ref mut list) = cmd_buf_data.commands {
768            list.push(TraceCommand::CopyBufferToTexture {
769                src: *source,
770                dst: *destination,
771                size: *copy_size,
772            });
773        }
774
775        let encoder = &mut cmd_buf_data.encoder;
776        let tracker = &mut cmd_buf_data.trackers;
777        let buffer_memory_init_actions = &mut cmd_buf_data.buffer_memory_init_actions;
778        let texture_memory_actions = &mut cmd_buf_data.texture_memory_actions;
779
780        if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 {
781            log::trace!("Ignoring copy_buffer_to_texture of size 0");
782            return Ok(());
783        }
784
785        let dst_texture = hub
786            .textures
787            .get(destination.texture)
788            .map_err(|_| TransferError::InvalidTexture(destination.texture))?;
789
790        if dst_texture.device.as_info().id() != device.as_info().id() {
791            return Err(DeviceError::WrongDevice.into());
792        }
793
794        let (hal_copy_size, array_layer_count) = validate_texture_copy_range(
795            destination,
796            &dst_texture.desc,
797            CopySide::Destination,
798            copy_size,
799        )?;
800
801        let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, &dst_texture)?;
802
803        let snatch_guard = device.snatchable_lock.read();
804
805        // Handle texture init *before* dealing with barrier transitions so we
806        // have an easier time inserting "immediate-inits" that may be required
807        // by prior discards in rare cases.
808        handle_dst_texture_init(
809            encoder,
810            tracker,
811            texture_memory_actions,
812            device,
813            destination,
814            copy_size,
815            &dst_texture,
816            &snatch_guard,
817        )?;
818
819        let (src_buffer, src_pending) = {
820            let buffer_guard = hub.buffers.read();
821            let src_buffer = buffer_guard
822                .get(source.buffer)
823                .map_err(|_| TransferError::InvalidBuffer(source.buffer))?;
824
825            if src_buffer.device.as_info().id() != device.as_info().id() {
826                return Err(DeviceError::WrongDevice.into());
827            }
828
829            tracker
830                .buffers
831                .set_single(src_buffer, hal::BufferUses::COPY_SRC)
832                .ok_or(TransferError::InvalidBuffer(source.buffer))?
833        };
834        let src_raw = src_buffer
835            .raw
836            .get(&snatch_guard)
837            .ok_or(TransferError::InvalidBuffer(source.buffer))?;
838        if !src_buffer.usage.contains(BufferUsages::COPY_SRC) {
839            return Err(TransferError::MissingCopySrcUsageFlag.into());
840        }
841        let src_barrier = src_pending.map(|pending| pending.into_hal(&src_buffer, &snatch_guard));
842
843        let dst_pending = tracker
844            .textures
845            .set_single(&dst_texture, dst_range, hal::TextureUses::COPY_DST)
846            .ok_or(TransferError::InvalidTexture(destination.texture))?;
847        let dst_raw = dst_texture
848            .raw(&snatch_guard)
849            .ok_or(TransferError::InvalidTexture(destination.texture))?;
850        if !dst_texture.desc.usage.contains(TextureUsages::COPY_DST) {
851            return Err(
852                TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(),
853            );
854        }
855        let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_raw));
856
857        if !dst_base.aspect.is_one() {
858            return Err(TransferError::CopyAspectNotOne.into());
859        }
860
861        if !conv::is_valid_copy_dst_texture_format(dst_texture.desc.format, destination.aspect) {
862            return Err(TransferError::CopyToForbiddenTextureFormat {
863                format: dst_texture.desc.format,
864                aspect: destination.aspect,
865            }
866            .into());
867        }
868
869        let (required_buffer_bytes_in_copy, bytes_per_array_layer) = validate_linear_texture_data(
870            &source.layout,
871            dst_texture.desc.format,
872            destination.aspect,
873            src_buffer.size,
874            CopySide::Source,
875            copy_size,
876            true,
877        )?;
878
879        if dst_texture.desc.format.is_depth_stencil_format() {
880            device
881                .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES)
882                .map_err(TransferError::from)?;
883        }
884
885        buffer_memory_init_actions.extend(src_buffer.initialization_status.read().create_action(
886            &src_buffer,
887            source.layout.offset..(source.layout.offset + required_buffer_bytes_in_copy),
888            MemoryInitKind::NeedsInitializedMemory,
889        ));
890
891        let regions = (0..array_layer_count).map(|rel_array_layer| {
892            let mut texture_base = dst_base.clone();
893            texture_base.array_layer += rel_array_layer;
894            let mut buffer_layout = source.layout;
895            buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer;
896            hal::BufferTextureCopy {
897                buffer_layout,
898                texture_base,
899                size: hal_copy_size,
900            }
901        });
902
903        let cmd_buf_raw = encoder.open()?;
904        unsafe {
905            cmd_buf_raw.transition_textures(dst_barrier.into_iter());
906            cmd_buf_raw.transition_buffers(src_barrier.into_iter());
907            cmd_buf_raw.copy_buffer_to_texture(src_raw, dst_raw, regions);
908        }
909        Ok(())
910    }
911
912    pub fn command_encoder_copy_texture_to_buffer<A: HalApi>(
913        &self,
914        command_encoder_id: CommandEncoderId,
915        source: &ImageCopyTexture,
916        destination: &ImageCopyBuffer,
917        copy_size: &Extent3d,
918    ) -> Result<(), CopyError> {
919        profiling::scope!("CommandEncoder::copy_texture_to_buffer");
920        api_log!(
921            "CommandEncoder::copy_texture_to_buffer {:?} -> {:?} {copy_size:?}",
922            source.texture,
923            destination.buffer
924        );
925
926        let hub = A::hub(self);
927
928        let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?;
929        let device = &cmd_buf.device;
930        if !device.is_valid() {
931            return Err(TransferError::InvalidDevice(cmd_buf.device.as_info().id()).into());
932        }
933
934        let mut cmd_buf_data = cmd_buf.data.lock();
935        let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
936
937        #[cfg(feature = "trace")]
938        if let Some(ref mut list) = cmd_buf_data.commands {
939            list.push(TraceCommand::CopyTextureToBuffer {
940                src: *source,
941                dst: *destination,
942                size: *copy_size,
943            });
944        }
945        let encoder = &mut cmd_buf_data.encoder;
946        let tracker = &mut cmd_buf_data.trackers;
947        let buffer_memory_init_actions = &mut cmd_buf_data.buffer_memory_init_actions;
948        let texture_memory_actions = &mut cmd_buf_data.texture_memory_actions;
949
950        if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 {
951            log::trace!("Ignoring copy_texture_to_buffer of size 0");
952            return Ok(());
953        }
954
955        let src_texture = hub
956            .textures
957            .get(source.texture)
958            .map_err(|_| TransferError::InvalidTexture(source.texture))?;
959
960        if src_texture.device.as_info().id() != device.as_info().id() {
961            return Err(DeviceError::WrongDevice.into());
962        }
963
964        let (hal_copy_size, array_layer_count) =
965            validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?;
966
967        let (src_range, src_base) = extract_texture_selector(source, copy_size, &src_texture)?;
968
969        let snatch_guard = device.snatchable_lock.read();
970
971        // Handle texture init *before* dealing with barrier transitions so we
972        // have an easier time inserting "immediate-inits" that may be required
973        // by prior discards in rare cases.
974        handle_src_texture_init(
975            encoder,
976            tracker,
977            texture_memory_actions,
978            device,
979            source,
980            copy_size,
981            &src_texture,
982            &snatch_guard,
983        )?;
984
985        let src_pending = tracker
986            .textures
987            .set_single(&src_texture, src_range, hal::TextureUses::COPY_SRC)
988            .ok_or(TransferError::InvalidTexture(source.texture))?;
989        let src_raw = src_texture
990            .raw(&snatch_guard)
991            .ok_or(TransferError::InvalidTexture(source.texture))?;
992        if !src_texture.desc.usage.contains(TextureUsages::COPY_SRC) {
993            return Err(TransferError::MissingCopySrcUsageFlag.into());
994        }
995        if src_texture.desc.sample_count != 1 {
996            return Err(TransferError::InvalidSampleCount {
997                sample_count: src_texture.desc.sample_count,
998            }
999            .into());
1000        }
1001        if source.mip_level >= src_texture.desc.mip_level_count {
1002            return Err(TransferError::InvalidMipLevel {
1003                requested: source.mip_level,
1004                count: src_texture.desc.mip_level_count,
1005            }
1006            .into());
1007        }
1008        let src_barrier = src_pending.map(|pending| pending.into_hal(src_raw));
1009
1010        let (dst_buffer, dst_pending) = {
1011            let buffer_guard = hub.buffers.read();
1012            let dst_buffer = buffer_guard
1013                .get(destination.buffer)
1014                .map_err(|_| TransferError::InvalidBuffer(destination.buffer))?;
1015
1016            if dst_buffer.device.as_info().id() != device.as_info().id() {
1017                return Err(DeviceError::WrongDevice.into());
1018            }
1019
1020            tracker
1021                .buffers
1022                .set_single(dst_buffer, hal::BufferUses::COPY_DST)
1023                .ok_or(TransferError::InvalidBuffer(destination.buffer))?
1024        };
1025        let dst_raw = dst_buffer
1026            .raw
1027            .get(&snatch_guard)
1028            .ok_or(TransferError::InvalidBuffer(destination.buffer))?;
1029        if !dst_buffer.usage.contains(BufferUsages::COPY_DST) {
1030            return Err(
1031                TransferError::MissingCopyDstUsageFlag(Some(destination.buffer), None).into(),
1032            );
1033        }
1034        let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard));
1035
1036        if !src_base.aspect.is_one() {
1037            return Err(TransferError::CopyAspectNotOne.into());
1038        }
1039
1040        if !conv::is_valid_copy_src_texture_format(src_texture.desc.format, source.aspect) {
1041            return Err(TransferError::CopyFromForbiddenTextureFormat {
1042                format: src_texture.desc.format,
1043                aspect: source.aspect,
1044            }
1045            .into());
1046        }
1047
1048        let (required_buffer_bytes_in_copy, bytes_per_array_layer) = validate_linear_texture_data(
1049            &destination.layout,
1050            src_texture.desc.format,
1051            source.aspect,
1052            dst_buffer.size,
1053            CopySide::Destination,
1054            copy_size,
1055            true,
1056        )?;
1057
1058        if src_texture.desc.format.is_depth_stencil_format() {
1059            device
1060                .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES)
1061                .map_err(TransferError::from)?;
1062        }
1063
1064        buffer_memory_init_actions.extend(dst_buffer.initialization_status.read().create_action(
1065            &dst_buffer,
1066            destination.layout.offset..(destination.layout.offset + required_buffer_bytes_in_copy),
1067            MemoryInitKind::ImplicitlyInitialized,
1068        ));
1069
1070        let regions = (0..array_layer_count).map(|rel_array_layer| {
1071            let mut texture_base = src_base.clone();
1072            texture_base.array_layer += rel_array_layer;
1073            let mut buffer_layout = destination.layout;
1074            buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer;
1075            hal::BufferTextureCopy {
1076                buffer_layout,
1077                texture_base,
1078                size: hal_copy_size,
1079            }
1080        });
1081        let cmd_buf_raw = encoder.open()?;
1082        unsafe {
1083            cmd_buf_raw.transition_buffers(dst_barrier.into_iter());
1084            cmd_buf_raw.transition_textures(src_barrier.into_iter());
1085            cmd_buf_raw.copy_texture_to_buffer(
1086                src_raw,
1087                hal::TextureUses::COPY_SRC,
1088                dst_raw,
1089                regions,
1090            );
1091        }
1092        Ok(())
1093    }
1094
1095    pub fn command_encoder_copy_texture_to_texture<A: HalApi>(
1096        &self,
1097        command_encoder_id: CommandEncoderId,
1098        source: &ImageCopyTexture,
1099        destination: &ImageCopyTexture,
1100        copy_size: &Extent3d,
1101    ) -> Result<(), CopyError> {
1102        profiling::scope!("CommandEncoder::copy_texture_to_texture");
1103        api_log!(
1104            "CommandEncoder::copy_texture_to_texture {:?} -> {:?} {copy_size:?}",
1105            source.texture,
1106            destination.texture
1107        );
1108
1109        let hub = A::hub(self);
1110
1111        let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?;
1112        let device = &cmd_buf.device;
1113        if !device.is_valid() {
1114            return Err(TransferError::InvalidDevice(cmd_buf.device.as_info().id()).into());
1115        }
1116
1117        let snatch_guard = device.snatchable_lock.read();
1118
1119        let mut cmd_buf_data = cmd_buf.data.lock();
1120        let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
1121
1122        #[cfg(feature = "trace")]
1123        if let Some(ref mut list) = cmd_buf_data.commands {
1124            list.push(TraceCommand::CopyTextureToTexture {
1125                src: *source,
1126                dst: *destination,
1127                size: *copy_size,
1128            });
1129        }
1130        let encoder = &mut cmd_buf_data.encoder;
1131        let tracker = &mut cmd_buf_data.trackers;
1132        let texture_memory_actions = &mut cmd_buf_data.texture_memory_actions;
1133
1134        if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 {
1135            log::trace!("Ignoring copy_texture_to_texture of size 0");
1136            return Ok(());
1137        }
1138
1139        let src_texture = hub
1140            .textures
1141            .get(source.texture)
1142            .map_err(|_| TransferError::InvalidTexture(source.texture))?;
1143        let dst_texture = hub
1144            .textures
1145            .get(destination.texture)
1146            .map_err(|_| TransferError::InvalidTexture(source.texture))?;
1147
1148        if src_texture.device.as_info().id() != device.as_info().id() {
1149            return Err(DeviceError::WrongDevice.into());
1150        }
1151        if dst_texture.device.as_info().id() != device.as_info().id() {
1152            return Err(DeviceError::WrongDevice.into());
1153        }
1154
1155        // src and dst texture format must be copy-compatible
1156        // https://gpuweb.github.io/gpuweb/#copy-compatible
1157        if src_texture.desc.format.remove_srgb_suffix()
1158            != dst_texture.desc.format.remove_srgb_suffix()
1159        {
1160            return Err(TransferError::TextureFormatsNotCopyCompatible {
1161                src_format: src_texture.desc.format,
1162                dst_format: dst_texture.desc.format,
1163            }
1164            .into());
1165        }
1166
1167        let (src_copy_size, array_layer_count) =
1168            validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?;
1169        let (dst_copy_size, _) = validate_texture_copy_range(
1170            destination,
1171            &dst_texture.desc,
1172            CopySide::Destination,
1173            copy_size,
1174        )?;
1175
1176        let (src_range, src_tex_base) = extract_texture_selector(source, copy_size, &src_texture)?;
1177        let (dst_range, dst_tex_base) =
1178            extract_texture_selector(destination, copy_size, &dst_texture)?;
1179        let src_texture_aspects = hal::FormatAspects::from(src_texture.desc.format);
1180        let dst_texture_aspects = hal::FormatAspects::from(dst_texture.desc.format);
1181        if src_tex_base.aspect != src_texture_aspects {
1182            return Err(TransferError::CopySrcMissingAspects.into());
1183        }
1184        if dst_tex_base.aspect != dst_texture_aspects {
1185            return Err(TransferError::CopyDstMissingAspects.into());
1186        }
1187
1188        // Handle texture init *before* dealing with barrier transitions so we
1189        // have an easier time inserting "immediate-inits" that may be required
1190        // by prior discards in rare cases.
1191        handle_src_texture_init(
1192            encoder,
1193            tracker,
1194            texture_memory_actions,
1195            device,
1196            source,
1197            copy_size,
1198            &src_texture,
1199            &snatch_guard,
1200        )?;
1201        handle_dst_texture_init(
1202            encoder,
1203            tracker,
1204            texture_memory_actions,
1205            device,
1206            destination,
1207            copy_size,
1208            &dst_texture,
1209            &snatch_guard,
1210        )?;
1211
1212        let src_pending = cmd_buf_data
1213            .trackers
1214            .textures
1215            .set_single(&src_texture, src_range, hal::TextureUses::COPY_SRC)
1216            .ok_or(TransferError::InvalidTexture(source.texture))?;
1217        let src_raw = src_texture
1218            .raw(&snatch_guard)
1219            .ok_or(TransferError::InvalidTexture(source.texture))?;
1220        if !src_texture.desc.usage.contains(TextureUsages::COPY_SRC) {
1221            return Err(TransferError::MissingCopySrcUsageFlag.into());
1222        }
1223
1224        //TODO: try to avoid this the collection. It's needed because both
1225        // `src_pending` and `dst_pending` try to hold `trackers.textures` mutably.
1226        let mut barriers: ArrayVec<_, 2> = src_pending
1227            .map(|pending| pending.into_hal(src_raw))
1228            .collect();
1229
1230        let dst_pending = cmd_buf_data
1231            .trackers
1232            .textures
1233            .set_single(&dst_texture, dst_range, hal::TextureUses::COPY_DST)
1234            .ok_or(TransferError::InvalidTexture(destination.texture))?;
1235        let dst_raw = dst_texture
1236            .raw(&snatch_guard)
1237            .ok_or(TransferError::InvalidTexture(destination.texture))?;
1238        if !dst_texture.desc.usage.contains(TextureUsages::COPY_DST) {
1239            return Err(
1240                TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(),
1241            );
1242        }
1243
1244        barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_raw)));
1245
1246        let hal_copy_size = hal::CopyExtent {
1247            width: src_copy_size.width.min(dst_copy_size.width),
1248            height: src_copy_size.height.min(dst_copy_size.height),
1249            depth: src_copy_size.depth.min(dst_copy_size.depth),
1250        };
1251        let regions = (0..array_layer_count).map(|rel_array_layer| {
1252            let mut src_base = src_tex_base.clone();
1253            let mut dst_base = dst_tex_base.clone();
1254            src_base.array_layer += rel_array_layer;
1255            dst_base.array_layer += rel_array_layer;
1256            hal::TextureCopy {
1257                src_base,
1258                dst_base,
1259                size: hal_copy_size,
1260            }
1261        });
1262        let cmd_buf_raw = cmd_buf_data.encoder.open()?;
1263        unsafe {
1264            cmd_buf_raw.transition_textures(barriers.into_iter());
1265            cmd_buf_raw.copy_texture_to_texture(
1266                src_raw,
1267                hal::TextureUses::COPY_SRC,
1268                dst_raw,
1269                regions,
1270            );
1271        }
1272
1273        Ok(())
1274    }
1275}