diff --git a/cts_runner/test.lst b/cts_runner/test.lst index 5b1f604cd7..f4d3c51c41 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -10,21 +10,7 @@ webgpu:api,validation,encoding,beginRenderPass:* webgpu:api,validation,encoding,cmds,clearBuffer:* webgpu:api,validation,encoding,cmds,compute_pass:set_pipeline:* webgpu:api,validation,encoding,cmds,compute_pass:dispatch_sizes:* -webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_with_invalid_or_destroyed_texture:* -webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture,device_mismatch:* -webgpu:api,validation,encoding,cmds,copyTextureToTexture:mipmap_level:* -webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture_usage:* -webgpu:api,validation,encoding,cmds,copyTextureToTexture:sample_count:* -//FAIL: webgpu:api,validation,encoding,cmds,copyTextureToTexture:multisampled_copy_restrictions:* -// texture_format_compatibility fails on Windows and Mac in CI. -// It passes locally on Mac. It is also a relatively long test. -//FAIL: webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture_format_compatibility:* -//FAIL: webgpu:api,validation,encoding,cmds,copyTextureToTexture:depth_stencil_copy_restrictions:* -//FAIL: webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges:* -//FAIL: webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_within_same_texture:* -//FAIL: webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_aspects:* -// Fails on Windows, https://github.com/gfx-rs/wgpu/issues/7844. -//FAIL: webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges_with_compressed_texture_formats:* +webgpu:api,validation,encoding,cmds,copyTextureToTexture:* webgpu:api,validation,encoding,cmds,index_access:* //FAIL: webgpu:api,validation,encoding,cmds,render,draw:* webgpu:api,validation,encoding,cmds,render,draw:index_buffer_OOB:* diff --git a/wgpu-core/src/command/mod.rs b/wgpu-core/src/command/mod.rs index 9587669c73..8c23e4aef2 100644 --- a/wgpu-core/src/command/mod.rs +++ b/wgpu-core/src/command/mod.rs @@ -289,7 +289,9 @@ impl CommandEncoderStatus { // Since we do not track the state of an invalid encoder, it is not // necessary to unlock an encoder that has been invalidated. fn invalidate>(&mut self, err: E) -> E { - *self = Self::Error(err.clone().into()); + let enc_err = err.clone().into(); + api_log!("Invalidating command encoder: {enc_err:?}"); + *self = Self::Error(enc_err); err } } diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index 698228e4c6..9bb82e88d6 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -1,4 +1,4 @@ -use alloc::{sync::Arc, vec::Vec}; +use alloc::{format, string::String, sync::Arc, vec, vec::Vec}; use arrayvec::ArrayVec; use thiserror::Error; @@ -65,6 +65,33 @@ pub enum TransferError { dimension: TextureErrorDimension, side: CopySide, }, + #[error("Partial copy of {start_offset}..{end_offset} on {dimension:?} dimension with size {texture_size} \ + is not supported for the {side:?} texture format {format:?} with {sample_count} samples")] + UnsupportedPartialTransfer { + format: wgt::TextureFormat, + sample_count: u32, + start_offset: u32, + end_offset: u32, + texture_size: u32, + dimension: TextureErrorDimension, + side: CopySide, + }, + #[error( + "Copying{} layers {}..{} to{} layers {}..{} of the same texture is not allowed", + if *src_aspects == wgt::TextureAspect::All { String::new() } else { format!(" {src_aspects:?}") }, + src_origin_z, + src_origin_z + array_layer_count, + if *dst_aspects == wgt::TextureAspect::All { String::new() } else { format!(" {dst_aspects:?}") }, + dst_origin_z, + dst_origin_z + array_layer_count, + )] + InvalidCopyWithinSameTexture { + src_aspects: wgt::TextureAspect, + dst_aspects: wgt::TextureAspect, + src_origin_z: u32, + dst_origin_z: u32, + array_layer_count: u32, + }, #[error("Unable to select texture aspect {aspect:?} from format {format:?}")] InvalidTextureAspect { format: wgt::TextureFormat, @@ -153,6 +180,8 @@ impl WebGpuError for TransferError { Self::BufferOverrun { .. } | Self::TextureOverrun { .. } + | Self::UnsupportedPartialTransfer { .. } + | Self::InvalidCopyWithinSameTexture { .. } | Self::InvalidTextureAspect { .. } | Self::InvalidTextureMipLevel { .. } | Self::InvalidDimensionExternal @@ -334,13 +363,16 @@ pub(crate) fn validate_linear_texture_data( Ok((bytes_in_copy, image_stride_bytes)) } -/// WebGPU's [validating texture copy range][vtcr] algorithm. +/// Validate the extent and alignment of a texture copy. /// -/// Copied with minor modifications from WebGPU standard. +/// Copied with minor modifications from WebGPU standard. This mostly follows +/// the [validating GPUTexelCopyTextureInfo][vtcti] and [validating texture copy +/// range][vtcr] algorithms. /// /// Returns the HAL copy extent and the layer count. /// -/// [vtcr]: https://gpuweb.github.io/gpuweb/#validating-texture-copy-range +/// [vtcti]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-gputexelcopytextureinfo +/// [vtcr]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-texture-copy-range pub(crate) fn validate_texture_copy_range( texture_copy_view: &wgt::TexelCopyTextureInfo, desc: &wgt::TextureDescriptor<(), Vec>, @@ -358,47 +390,55 @@ pub(crate) fn validate_texture_copy_range( // physical size can be larger than the virtual let extent = extent_virtual.physical_size(desc.format); - /// Return `Ok` if a run `size` texels long starting at `start_offset` falls - /// entirely within `texture_size`. Otherwise, return an appropriate a`Err`. - fn check_dimension( - dimension: TextureErrorDimension, - side: CopySide, - start_offset: u32, - size: u32, - texture_size: u32, - ) -> Result<(), TransferError> { + // Multisampled and depth-stencil formats do not support partial copies. + let requires_exact_size = desc.format.is_depth_stencil_format() || desc.sample_count > 1; + + // Return `Ok` if a run `size` texels long starting at `start_offset` is + // valid for `texture_size`. Otherwise, return an appropriate a`Err`. + let check_dimension = |dimension: TextureErrorDimension, + start_offset: u32, + size: u32, + texture_size: u32| + -> Result<(), TransferError> { + if requires_exact_size && (start_offset != 0 || size != texture_size) { + Err(TransferError::UnsupportedPartialTransfer { + format: desc.format, + sample_count: desc.sample_count, + start_offset, + end_offset: start_offset.wrapping_add(size), + texture_size, + dimension, + side: texture_side, + }) // Avoid underflow in the subtraction by checking start_offset against // texture_size first. - if start_offset <= texture_size && size <= texture_size - start_offset { - Ok(()) - } else { + } else if start_offset > texture_size || texture_size - start_offset < size { Err(TransferError::TextureOverrun { start_offset, end_offset: start_offset.wrapping_add(size), texture_size, dimension, - side, + side: texture_side, }) + } else { + Ok(()) } - } + }; check_dimension( TextureErrorDimension::X, - texture_side, texture_copy_view.origin.x, copy_size.width, extent.width, )?; check_dimension( TextureErrorDimension::Y, - texture_side, texture_copy_view.origin.y, copy_size.height, extent.height, )?; check_dimension( TextureErrorDimension::Z, - texture_side, texture_copy_view.origin.z, copy_size.depth_or_array_layers, extent.depth_or_array_layers, @@ -431,6 +471,46 @@ pub(crate) fn validate_texture_copy_range( Ok((copy_extent, array_layer_count)) } +/// Validate a copy within the same texture. +/// +/// This implements the WebGPU requirement that the [sets of subresources for +/// texture copy][srtc] of the source and destination be disjoint, i.e. that the +/// source and destination do not overlap. +/// +/// This function assumes that the copy ranges have already been validated with +/// `validate_texture_copy_range`. +/// +/// [srtc]: https://gpuweb.github.io/gpuweb/#abstract-opdef-set-of-subresources-for-texture-copy +pub(crate) fn validate_copy_within_same_texture( + src: &wgt::TexelCopyTextureInfo, + src_format: wgt::TextureFormat, + dst: &wgt::TexelCopyTextureInfo, + dst_format: wgt::TextureFormat, + array_layer_count: u32, +) -> Result<(), TransferError> { + let src_aspects = hal::FormatAspects::new(src_format, src.aspect); + let dst_aspects = hal::FormatAspects::new(dst_format, dst.aspect); + if (src_aspects & dst_aspects).is_empty() { + // Copying between different aspects (if it even makes sense), is okay. + return Ok(()); + } + + if src.origin.z >= dst.origin.z + array_layer_count + || dst.origin.z >= src.origin.z + array_layer_count + { + // Copying between non-overlapping layer ranges is okay. + return Ok(()); + } + + Err(TransferError::InvalidCopyWithinSameTexture { + src_aspects: src.aspect, + dst_aspects: dst.aspect, + src_origin_z: src.origin.z, + dst_origin_z: dst.origin.z, + array_layer_count, + }) +} + fn handle_texture_init( init_kind: MemoryInitKind, cmd_buf_data: &mut CommandBufferMutable, @@ -742,15 +822,11 @@ impl Global { }); } - if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 - { - log::trace!("Ignoring copy_buffer_to_texture of size 0"); - return Ok(()); - } - let dst_texture = hub.textures.get(destination.texture).get()?; + let src_buffer = hub.buffers.get(source.buffer).get()?; dst_texture.same_device_as(cmd_buf.as_ref())?; + src_buffer.same_device_as(cmd_buf.as_ref())?; let (hal_copy_size, array_layer_count) = validate_texture_copy_range( destination, @@ -764,6 +840,15 @@ impl Global { let snatch_guard = device.snatchable_lock.read(); + let src_raw = src_buffer.try_raw(&snatch_guard)?; + let dst_raw = dst_texture.try_raw(&snatch_guard)?; + + if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 + { + log::trace!("Ignoring copy_buffer_to_texture of size 0"); + return Ok(()); + } + // Handle texture init *before* dealing with barrier transitions so we // have an easier time inserting "immediate-inits" that may be required // by prior discards in rare cases. @@ -776,16 +861,11 @@ impl Global { &snatch_guard, )?; - let src_buffer = hub.buffers.get(source.buffer).get()?; - - src_buffer.same_device_as(cmd_buf.as_ref())?; - let src_pending = cmd_buf_data .trackers .buffers .set_single(&src_buffer, wgt::BufferUses::COPY_SRC); - let src_raw = src_buffer.try_raw(&snatch_guard)?; src_buffer .check_usage(BufferUsages::COPY_SRC) .map_err(TransferError::MissingBufferUsage)?; @@ -797,7 +877,6 @@ impl Global { dst_range, wgt::TextureUses::COPY_DST, ); - let dst_raw = dst_texture.try_raw(&snatch_guard)?; dst_texture .check_usage(TextureUsages::COPY_DST) .map_err(TransferError::MissingTextureUsage)?; @@ -843,19 +922,40 @@ impl Global { ), ); - let regions = (0..array_layer_count) - .map(|rel_array_layer| { - let mut texture_base = dst_base.clone(); - texture_base.array_layer += rel_array_layer; - let mut buffer_layout = source.layout; - buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer; + let regions = if dst_base.aspect == hal::FormatAspects::DEPTH_STENCIL { + vec![ hal::BufferTextureCopy { - buffer_layout, - texture_base, + texture_base: hal::TextureCopyBase { + aspect: hal::FormatAspects::DEPTH, + ..dst_base + }, + buffer_layout: source.layout, size: hal_copy_size, - } - }) - .collect::>(); + }, + hal::BufferTextureCopy { + texture_base: hal::TextureCopyBase { + aspect: hal::FormatAspects::STENCIL, + ..dst_base + }, + buffer_layout: source.layout, + size: hal_copy_size, + }, + ] + } else { + (0..array_layer_count) + .map(|rel_array_layer| { + let mut texture_base = dst_base.clone(); + texture_base.array_layer += rel_array_layer; + let mut buffer_layout = source.layout; + buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer; + hal::BufferTextureCopy { + buffer_layout, + texture_base, + size: hal_copy_size, + } + }) + .collect() + }; let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { @@ -901,15 +1001,11 @@ impl Global { }); } - if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 - { - log::trace!("Ignoring copy_texture_to_buffer of size 0"); - return Ok(()); - } - let src_texture = hub.textures.get(source.texture).get()?; + let dst_buffer = hub.buffers.get(destination.buffer).get()?; src_texture.same_device_as(cmd_buf.as_ref())?; + dst_buffer.same_device_as(cmd_buf.as_ref())?; let (hal_copy_size, array_layer_count) = validate_texture_copy_range( source, @@ -922,23 +1018,6 @@ impl Global { let snatch_guard = device.snatchable_lock.read(); - // Handle texture init *before* dealing with barrier transitions so we - // have an easier time inserting "immediate-inits" that may be required - // by prior discards in rare cases. - handle_src_texture_init( - cmd_buf_data, - device, - source, - copy_size, - &src_texture, - &snatch_guard, - )?; - - let src_pending = cmd_buf_data.trackers.textures.set_single( - &src_texture, - src_range, - wgt::TextureUses::COPY_SRC, - ); let src_raw = src_texture.try_raw(&snatch_guard)?; src_texture .check_usage(TextureUsages::COPY_SRC) @@ -956,25 +1035,6 @@ impl Global { } .into()); } - let src_barrier = src_pending - .map(|pending| pending.into_hal(src_raw)) - .collect::>(); - - let dst_buffer = hub.buffers.get(destination.buffer).get()?; - - dst_buffer.same_device_as(cmd_buf.as_ref())?; - - let dst_pending = cmd_buf_data - .trackers - .buffers - .set_single(&dst_buffer, wgt::BufferUses::COPY_DST); - - let dst_raw = dst_buffer.try_raw(&snatch_guard)?; - dst_buffer - .check_usage(BufferUsages::COPY_DST) - .map_err(TransferError::MissingBufferUsage)?; - let dst_barrier = - dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard)); if !src_base.aspect.is_one() { return Err(TransferError::CopyAspectNotOne.into()); @@ -1005,6 +1065,46 @@ impl Global { .map_err(TransferError::from)?; } + let dst_raw = dst_buffer.try_raw(&snatch_guard)?; + dst_buffer + .check_usage(BufferUsages::COPY_DST) + .map_err(TransferError::MissingBufferUsage)?; + + if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 + { + log::trace!("Ignoring copy_texture_to_buffer of size 0"); + return Ok(()); + } + + // Handle texture init *before* dealing with barrier transitions so we + // have an easier time inserting "immediate-inits" that may be required + // by prior discards in rare cases. + handle_src_texture_init( + cmd_buf_data, + device, + source, + copy_size, + &src_texture, + &snatch_guard, + )?; + + let src_pending = cmd_buf_data.trackers.textures.set_single( + &src_texture, + src_range, + wgt::TextureUses::COPY_SRC, + ); + let src_barrier = src_pending + .map(|pending| pending.into_hal(src_raw)) + .collect::>(); + + let dst_pending = cmd_buf_data + .trackers + .buffers + .set_single(&dst_buffer, wgt::BufferUses::COPY_DST); + + let dst_barrier = + dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard)); + cmd_buf_data.buffer_memory_init_actions.extend( dst_buffer.initialization_status.read().create_action( &dst_buffer, @@ -1078,12 +1178,6 @@ impl Global { }); } - if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 - { - log::trace!("Ignoring copy_texture_to_texture of size 0"); - return Ok(()); - } - let src_texture = hub.textures.get(source.texture).get()?; let dst_texture = hub.textures.get(destination.texture).get()?; @@ -1115,6 +1209,16 @@ impl Global { copy_size, )?; + if Arc::as_ptr(&src_texture) == Arc::as_ptr(&dst_texture) { + validate_copy_within_same_texture( + source, + src_texture.desc.format, + destination, + dst_texture.desc.format, + array_layer_count, + )?; + } + let (src_range, src_tex_base) = extract_texture_selector(source, copy_size, &src_texture)?; let (dst_range, dst_tex_base) = @@ -1156,15 +1260,26 @@ impl Global { &snatch_guard, )?; + let src_raw = src_texture.try_raw(&snatch_guard)?; + src_texture + .check_usage(TextureUsages::COPY_SRC) + .map_err(TransferError::MissingTextureUsage)?; + let dst_raw = dst_texture.try_raw(&snatch_guard)?; + dst_texture + .check_usage(TextureUsages::COPY_DST) + .map_err(TransferError::MissingTextureUsage)?; + + if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 + { + log::trace!("Ignoring copy_texture_to_texture of size 0"); + return Ok(()); + } + let src_pending = cmd_buf_data.trackers.textures.set_single( &src_texture, src_range, wgt::TextureUses::COPY_SRC, ); - let src_raw = src_texture.try_raw(&snatch_guard)?; - src_texture - .check_usage(TextureUsages::COPY_SRC) - .map_err(TransferError::MissingTextureUsage)?; //TODO: try to avoid this the collection. It's needed because both // `src_pending` and `dst_pending` try to hold `trackers.textures` mutably. @@ -1177,11 +1292,6 @@ impl Global { dst_range, wgt::TextureUses::COPY_DST, ); - let dst_raw = dst_texture.try_raw(&snatch_guard)?; - dst_texture - .check_usage(TextureUsages::COPY_DST) - .map_err(TransferError::MissingTextureUsage)?; - barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_raw))); let hal_copy_size = hal::CopyExtent { @@ -1189,19 +1299,46 @@ impl Global { height: src_copy_size.height.min(dst_copy_size.height), depth: src_copy_size.depth.min(dst_copy_size.depth), }; - let regions = (0..array_layer_count) - .map(|rel_array_layer| { - let mut src_base = src_tex_base.clone(); - let mut dst_base = dst_tex_base.clone(); - src_base.array_layer += rel_array_layer; - dst_base.array_layer += rel_array_layer; + let regions = if dst_tex_base.aspect == hal::FormatAspects::DEPTH_STENCIL { + vec![ hal::TextureCopy { - src_base, - dst_base, + src_base: hal::TextureCopyBase { + aspect: hal::FormatAspects::DEPTH, + ..src_tex_base + }, + dst_base: hal::TextureCopyBase { + aspect: hal::FormatAspects::DEPTH, + ..dst_tex_base + }, size: hal_copy_size, - } - }) - .collect::>(); + }, + hal::TextureCopy { + src_base: hal::TextureCopyBase { + aspect: hal::FormatAspects::STENCIL, + ..src_tex_base + }, + dst_base: hal::TextureCopyBase { + aspect: hal::FormatAspects::STENCIL, + ..dst_tex_base + }, + size: hal_copy_size, + }, + ] + } else { + (0..array_layer_count) + .map(|rel_array_layer| { + let mut src_base = src_tex_base.clone(); + let mut dst_base = dst_tex_base.clone(); + src_base.array_layer += rel_array_layer; + dst_base.array_layer += rel_array_layer; + hal::TextureCopy { + src_base, + dst_base, + size: hal_copy_size, + } + }) + .collect() + }; let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_textures(&barriers); diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 10df7aaf1a..a31f45872b 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -717,11 +717,6 @@ impl Queue { self.device.check_is_valid()?; - if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 { - log::trace!("Ignoring write_texture of size 0"); - return Ok(()); - } - let dst = destination.texture.get()?; let destination = wgt::TexelCopyTextureInfo { texture: (), @@ -774,6 +769,13 @@ impl Queue { let snatch_guard = self.device.snatchable_lock.read(); + let dst_raw = dst.try_raw(&snatch_guard)?; + + if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 { + log::trace!("Ignoring write_texture of size 0"); + return Ok(()); + } + let mut pending_writes = self.pending_writes.lock(); let encoder = pending_writes.activate(); @@ -819,8 +821,6 @@ impl Queue { } } - let dst_raw = dst.try_raw(&snatch_guard)?; - let (block_width, block_height) = dst.desc.format.block_dimensions(); let width_in_blocks = size.width / block_width; let height_in_blocks = size.height / block_height;