li_wgpu_core/command/
transfer.rs

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