diff --git a/CHANGELOG.md b/CHANGELOG.md index 546e16ecf03..8e8142c53b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,34 @@ Bottom level categories: ### Major changes +#### TextureFormat info API + +The `describe` fn was removed in favor of separate functions: `block_dimensions`, `is_compressed`, `is_srgb`, `required_features`, `guaranteed_format_features`, `sample_type` and `block_size`. + +```diff +- let block_dimensions = format.describe().block_dimensions; ++ let block_dimensions = format.block_dimensions(); +- let is_compressed = format.describe().is_compressed(); ++ let is_compressed = format.is_compressed(); +- let is_srgb = format.describe().srgb; ++ let is_srgb = format.is_srgb(); +- let required_features = format.describe().required_features; ++ let required_features = format.required_features(); +- let guaranteed_format_features = format.describe().guaranteed_format_features; ++ let guaranteed_format_features = format.guaranteed_format_features(); +``` + +Additionally `sample_type` and `block_size` now take an optional `TextureAspect` and return `Option`s. + +```diff +- let sample_type = format.describe().sample_type; ++ let sample_type = format.sample_type(None).expect("combined depth-stencil format requires specifying a TextureAspect"); +- let block_size = format.describe().block_size; ++ let block_size = format.block_size(None).expect("combined depth-stencil format requires specifying a TextureAspect"); +``` + +By @teoxoy in [#3436](https://github.com/gfx-rs/wgpu/pull/3436) + #### General - Change type of `mip_level_count` and `array_layer_count` (members of `TextureViewDescriptor` and `ImageSubresourceRange`) from `Option` to `Option`. By @teoxoy in [#3445](https://github.com/gfx-rs/wgpu/pull/3445) @@ -51,6 +79,7 @@ Bottom level categories: #### General - Added `TextureFormatFeatureFlags::MULTISAMPLE_X16`. By @Dinnerbone in [#3454](https://github.com/gfx-rs/wgpu/pull/3454) +- Support stencil-only views and copying to/from combined depth-stencil textures. By @teoxoy in [#3436](https://github.com/gfx-rs/wgpu/pull/3436) #### WebGPU diff --git a/wgpu-core/src/command/clear.rs b/wgpu-core/src/command/clear.rs index ce39adc1357..cf31d6b22a1 100644 --- a/wgpu-core/src/command/clear.rs +++ b/wgpu-core/src/command/clear.rs @@ -178,8 +178,8 @@ impl Global { .map_err(|_| ClearError::InvalidTexture(dst))?; // Check if subresource aspects are valid. - let requested_aspects = hal::FormatAspects::from(subresource_range.aspect); - let clear_aspects = hal::FormatAspects::from(dst_texture.desc.format) & requested_aspects; + let clear_aspects = + hal::FormatAspects::new(dst_texture.desc.format, subresource_range.aspect); if clear_aspects.is_empty() { return Err(ClearError::MissingTextureAspect { texture_format: dst_texture.desc.format, @@ -310,29 +310,33 @@ fn clear_texture_via_buffer_copies( encoder: &mut A::CommandEncoder, dst_raw: &A::Texture, ) { + assert_eq!( + hal::FormatAspects::from(texture_desc.format), + hal::FormatAspects::COLOR + ); + // Gather list of zero_buffer copies and issue a single command then to perform them let mut zero_buffer_copy_regions = Vec::new(); let buffer_copy_pitch = alignments.buffer_copy_pitch.get() as u32; - let format_desc = texture_desc.format.describe(); + let (block_width, block_height) = texture_desc.format.block_dimensions(); + let block_size = texture_desc.format.block_size(None).unwrap(); - let bytes_per_row_alignment = - get_lowest_common_denom(buffer_copy_pitch, format_desc.block_size as u32); + let bytes_per_row_alignment = get_lowest_common_denom(buffer_copy_pitch, block_size); for mip_level in range.mip_range { let mut mip_size = texture_desc.mip_level_size(mip_level).unwrap(); // Round to multiple of block size - mip_size.width = align_to(mip_size.width, format_desc.block_dimensions.0 as u32); - mip_size.height = align_to(mip_size.height, format_desc.block_dimensions.1 as u32); + mip_size.width = align_to(mip_size.width, block_width); + mip_size.height = align_to(mip_size.height, block_height); let bytes_per_row = align_to( - mip_size.width / format_desc.block_dimensions.0 as u32 * format_desc.block_size as u32, + mip_size.width / block_width * block_size, bytes_per_row_alignment, ); let max_rows_per_copy = crate::device::ZERO_BUFFER_SIZE as u32 / bytes_per_row; // round down to a multiple of rows needed by the texture format - let max_rows_per_copy = max_rows_per_copy / format_desc.block_dimensions.1 as u32 - * format_desc.block_dimensions.1 as u32; + let max_rows_per_copy = max_rows_per_copy / block_height * block_height; assert!( max_rows_per_copy > 0, "Zero buffer size is too small to fill a single row \ @@ -370,7 +374,7 @@ fn clear_texture_via_buffer_copies( y: mip_size.height - num_rows_left, z, }, - aspect: hal::FormatAspects::all(), + aspect: hal::FormatAspects::COLOR, }, size: hal::CopyExtent { width: mip_size.width, // full row diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index 9370e7bd4cd..eca45ab70e3 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -101,6 +101,8 @@ pub enum TransferError { "copy destination aspects must refer to all aspects of the destination texture format" )] CopyDstMissingAspects, + #[error("copy aspect must refer to a single aspect of texture format")] + CopyAspectNotOne, #[error("copying from textures with format {format:?} and aspect {aspect:?} is forbidden")] CopyFromForbiddenTextureFormat { format: wgt::TextureFormat, @@ -118,7 +120,7 @@ pub enum TransferError { #[error("the entire texture must be copied when copying from depth texture")] InvalidDepthTextureExtent, #[error( - "source format ({src_format:?}) and destination format ({dst_format:?}) are different" + "source format ({src_format:?}) and destination format ({dst_format:?}) are not copy-compatible" )] MismatchedTextureFormats { src_format: wgt::TextureFormat, @@ -179,10 +181,9 @@ pub(crate) fn extract_texture_selector( copy_texture: &ImageCopyTexture, copy_size: &Extent3d, texture: &Texture, -) -> Result<(TextureSelector, hal::TextureCopyBase, wgt::TextureFormat), TransferError> { +) -> Result<(TextureSelector, hal::TextureCopyBase), TransferError> { let format = texture.desc.format; - let copy_aspect = - hal::FormatAspects::from(format) & hal::FormatAspects::from(copy_texture.aspect); + let copy_aspect = hal::FormatAspects::new(format, copy_texture.aspect); if copy_aspect.is_empty() { return Err(TransferError::InvalidTextureAspect { format, @@ -214,7 +215,7 @@ pub(crate) fn extract_texture_selector( layers, }; - Ok((selector, base, format)) + Ok((selector, base)) } /// WebGPU's [validating linear texture data][vltd] algorithm. @@ -229,9 +230,9 @@ pub(crate) fn extract_texture_selector( pub(crate) fn validate_linear_texture_data( layout: &wgt::ImageDataLayout, format: wgt::TextureFormat, + aspect: wgt::TextureAspect, buffer_size: BufferAddress, buffer_side: CopySide, - bytes_per_block: BufferAddress, copy_size: &Extent3d, need_copy_aligned_rows: bool, ) -> Result<(BufferAddress, BufferAddress), TransferError> { @@ -245,62 +246,76 @@ pub(crate) fn validate_linear_texture_data( let offset = layout.offset; - let (block_width, block_height) = format.describe().block_dimensions; + let block_size = format.block_size(Some(aspect)).unwrap() as BufferAddress; + let (block_width, block_height) = format.block_dimensions(); let block_width = block_width as BufferAddress; let block_height = block_height as BufferAddress; - let block_size = bytes_per_block; + + if copy_width % block_width != 0 { + return Err(TransferError::UnalignedCopyWidth); + } + if copy_height % block_height != 0 { + return Err(TransferError::UnalignedCopyHeight); + } let width_in_blocks = copy_width / block_width; let height_in_blocks = copy_height / block_height; + let bytes_in_last_row = width_in_blocks * block_size; + let bytes_per_row = if let Some(bytes_per_row) = layout.bytes_per_row { - bytes_per_row.get() as BufferAddress + let bytes_per_row = bytes_per_row.get() as BufferAddress; + if bytes_per_row < bytes_in_last_row { + return Err(TransferError::InvalidBytesPerRow); + } + bytes_per_row } else { if copy_depth > 1 || height_in_blocks > 1 { return Err(TransferError::UnspecifiedBytesPerRow); } - bytes_per_block * width_in_blocks + 0 }; let block_rows_per_image = if let Some(rows_per_image) = layout.rows_per_image { - rows_per_image.get() as BufferAddress + let rows_per_image = rows_per_image.get() as BufferAddress; + if rows_per_image < height_in_blocks { + return Err(TransferError::InvalidRowsPerImage); + } + rows_per_image } else { if copy_depth > 1 { return Err(TransferError::UnspecifiedRowsPerImage); } - copy_height / block_height + 0 }; - let rows_per_image = block_rows_per_image * block_height; - - if copy_width % block_width != 0 { - return Err(TransferError::UnalignedCopyWidth); - } - if copy_height % block_height != 0 { - return Err(TransferError::UnalignedCopyHeight); - } if need_copy_aligned_rows { let bytes_per_row_alignment = wgt::COPY_BYTES_PER_ROW_ALIGNMENT as BufferAddress; - if bytes_per_row_alignment % bytes_per_block != 0 { - return Err(TransferError::UnalignedBytesPerRow); + let mut offset_alignment = block_size; + if format.is_depth_stencil_format() { + offset_alignment = 4 } + if offset % offset_alignment != 0 { + return Err(TransferError::UnalignedBufferOffset(offset)); + } + if bytes_per_row % bytes_per_row_alignment != 0 { return Err(TransferError::UnalignedBytesPerRow); } } - let bytes_in_last_row = block_size * width_in_blocks; let bytes_per_image = bytes_per_row * block_rows_per_image; - let required_bytes_in_copy = if copy_width == 0 || copy_height == 0 || copy_depth == 0 { + + let required_bytes_in_copy = if copy_depth == 0 { 0 } else { - let bytes_in_last_slice = bytes_per_row * (height_in_blocks - 1) + bytes_in_last_row; - bytes_per_image * (copy_depth - 1) + bytes_in_last_slice + let mut required_bytes_in_copy = bytes_per_image * (copy_depth - 1); + if height_in_blocks > 0 { + required_bytes_in_copy += bytes_per_row * (height_in_blocks - 1) + bytes_in_last_row; + } + required_bytes_in_copy }; - if rows_per_image < copy_height { - return Err(TransferError::InvalidRowsPerImage); - } if offset + required_bytes_in_copy > buffer_size { return Err(TransferError::BufferOverrun { start_offset: offset, @@ -309,12 +324,7 @@ pub(crate) fn validate_linear_texture_data( side: buffer_side, }); } - if offset % block_size != 0 { - return Err(TransferError::UnalignedBufferOffset(offset)); - } - if copy_height > 1 && bytes_per_row < bytes_in_last_row { - return Err(TransferError::InvalidBytesPerRow); - } + Ok((required_bytes_in_copy, bytes_per_image)) } @@ -331,9 +341,7 @@ pub(crate) fn validate_texture_copy_range( texture_side: CopySide, copy_size: &Extent3d, ) -> Result<(hal::CopyExtent, u32), TransferError> { - let (block_width, block_height) = desc.format.describe().block_dimensions; - let block_width = block_width as u32; - let block_height = block_height as u32; + let (block_width, block_height) = desc.format.block_dimensions(); let extent_virtual = desc.mip_level_size(texture_copy_view.mip_level).ok_or( TransferError::InvalidTextureMipLevel { @@ -344,18 +352,8 @@ pub(crate) fn validate_texture_copy_range( // physical size can be larger than the virtual let extent = extent_virtual.physical_size(desc.format); - match desc.format { - wgt::TextureFormat::Stencil8 - | wgt::TextureFormat::Depth16Unorm - | wgt::TextureFormat::Depth32Float - | wgt::TextureFormat::Depth32FloatStencil8 - | wgt::TextureFormat::Depth24Plus - | wgt::TextureFormat::Depth24PlusStencil8 => { - if *copy_size != extent { - return Err(TransferError::InvalidDepthTextureExtent); - } - } - _ => {} + if desc.format.is_depth_stencil_format() && *copy_size != extent { + return Err(TransferError::InvalidDepthTextureExtent); } /// Return `Ok` if a run `size` texels long starting at `start_offset` falls @@ -736,8 +734,7 @@ impl Global { copy_size, )?; - let (dst_range, dst_base, _) = - extract_texture_selector(destination, copy_size, dst_texture)?; + let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, dst_texture)?; // Handle texture init *before* dealing with barrier transitions so we // have an easier time inserting "immediate-inits" that may be required @@ -779,23 +776,32 @@ impl Global { } let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_texture)); - let format_desc = dst_texture.desc.format.describe(); + if !dst_base.aspect.is_one() { + return Err(TransferError::CopyAspectNotOne.into()); + } + + if !conv::is_valid_copy_dst_texture_format(dst_texture.desc.format, destination.aspect) { + return Err(TransferError::CopyToForbiddenTextureFormat { + format: dst_texture.desc.format, + aspect: destination.aspect, + } + .into()); + } + let (required_buffer_bytes_in_copy, bytes_per_array_layer) = validate_linear_texture_data( &source.layout, dst_texture.desc.format, + destination.aspect, src_buffer.size, CopySide::Source, - format_desc.block_size as BufferAddress, copy_size, true, )?; - if !conv::is_valid_copy_dst_texture_format(dst_texture.desc.format, destination.aspect) { - return Err(TransferError::CopyToForbiddenTextureFormat { - format: dst_texture.desc.format, - aspect: destination.aspect, - } - .into()); + if dst_texture.desc.format.is_depth_stencil_format() { + device + .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES) + .map_err(TransferError::from)?; } cmd_buf @@ -868,7 +874,7 @@ impl Global { let (hal_copy_size, array_layer_count) = validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?; - let (src_range, src_base, _) = extract_texture_selector(source, copy_size, src_texture)?; + let (src_range, src_base) = extract_texture_selector(source, copy_size, src_texture)?; // Handle texture init *before* dealing with barrier transitions so we // have an easier time inserting "immediate-inits" that may be required @@ -927,16 +933,9 @@ impl Global { } let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer)); - let format_desc = src_texture.desc.format.describe(); - let (required_buffer_bytes_in_copy, bytes_per_array_layer) = validate_linear_texture_data( - &destination.layout, - src_texture.desc.format, - dst_buffer.size, - CopySide::Destination, - format_desc.block_size as BufferAddress, - copy_size, - true, - )?; + if !src_base.aspect.is_one() { + return Err(TransferError::CopyAspectNotOne.into()); + } if !conv::is_valid_copy_src_texture_format(src_texture.desc.format, source.aspect) { return Err(TransferError::CopyFromForbiddenTextureFormat { @@ -946,16 +945,20 @@ impl Global { .into()); } - if format_desc.sample_type == wgt::TextureSampleType::Depth - && !device - .downlevel - .flags - .contains(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES) - { - return Err(TransferError::MissingDownlevelFlags(MissingDownlevelFlags( - wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES, - )) - .into()); + let (required_buffer_bytes_in_copy, bytes_per_array_layer) = validate_linear_texture_data( + &destination.layout, + src_texture.desc.format, + source.aspect, + dst_buffer.size, + CopySide::Destination, + copy_size, + true, + )?; + + if src_texture.desc.format.is_depth_stencil_format() { + device + .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES) + .map_err(TransferError::from)?; } cmd_buf @@ -1053,9 +1056,8 @@ impl Global { copy_size, )?; - let (src_range, src_tex_base, _) = - extract_texture_selector(source, copy_size, src_texture)?; - let (dst_range, dst_tex_base, _) = + let (src_range, src_tex_base) = extract_texture_selector(source, copy_size, src_texture)?; + let (dst_range, dst_tex_base) = extract_texture_selector(destination, copy_size, dst_texture)?; let src_texture_aspects = hal::FormatAspects::from(src_texture.desc.format); let dst_texture_aspects = hal::FormatAspects::from(dst_texture.desc.format); diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 49e158c6645..9ff68377476 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -728,11 +728,9 @@ impl Device { &self.limits, )?; - let format_desc = desc.format.describe(); - if desc.dimension != wgt::TextureDimension::D2 { // Depth textures can only be 2D - if format_desc.sample_type == wgt::TextureSampleType::Depth { + if desc.format.is_depth_stencil_format() { return Err(CreateTextureError::InvalidDepthDimension( desc.dimension, desc.format, @@ -747,7 +745,7 @@ impl Device { } // Compressed textures can only be 2D - if format_desc.is_compressed() { + if desc.format.is_compressed() { return Err(CreateTextureError::InvalidCompressedDimension( desc.dimension, desc.format, @@ -755,9 +753,8 @@ impl Device { } } - if format_desc.is_compressed() { - let block_width = format_desc.block_dimensions.0 as u32; - let block_height = format_desc.block_dimensions.1 as u32; + if desc.format.is_compressed() { + let (block_width, block_height) = desc.format.block_dimensions(); if desc.size.width % block_width != 0 { return Err(CreateTextureError::InvalidDimension( @@ -840,11 +837,7 @@ impl Device { let missing_allowed_usages = desc.usage - format_features.allowed_usages; if !missing_allowed_usages.is_empty() { // detect downlevel incompatibilities - let wgpu_allowed_usages = desc - .format - .describe() - .guaranteed_format_features - .allowed_usages; + let wgpu_allowed_usages = desc.format.guaranteed_format_features().allowed_usages; let wgpu_missing_usages = desc.usage - wgpu_allowed_usages; return Err(CreateTextureError::InvalidFormatUsages( missing_allowed_usages, @@ -870,7 +863,7 @@ impl Device { // Enforce having COPY_DST/DEPTH_STENCIL_WRIT/COLOR_TARGET otherwise we // wouldn't be able to initialize the texture. let hal_usage = conv::map_texture_usage(desc.usage, desc.format.into()) - | if format_desc.sample_type == wgt::TextureSampleType::Depth { + | if desc.format.is_depth_stencil_format() { hal::TextureUses::DEPTH_STENCIL_WRITE } else if desc.usage.contains(wgt::TextureUsages::COPY_DST) { hal::TextureUses::COPY_DST // (set already) @@ -909,12 +902,11 @@ impl Device { let clear_mode = if hal_usage .intersects(hal::TextureUses::DEPTH_STENCIL_WRITE | hal::TextureUses::COLOR_TARGET) { - let (is_color, usage) = - if desc.format.describe().sample_type == wgt::TextureSampleType::Depth { - (false, hal::TextureUses::DEPTH_STENCIL_WRITE) - } else { - (true, hal::TextureUses::COLOR_TARGET) - }; + let (is_color, usage) = if desc.format.is_depth_stencil_format() { + (false, hal::TextureUses::DEPTH_STENCIL_WRITE) + } else { + (true, hal::TextureUses::COLOR_TARGET) + }; let dimension = match desc.dimension { wgt::TextureDimension::D1 => wgt::TextureViewDimension::D1, wgt::TextureDimension::D2 => wgt::TextureViewDimension::D2, @@ -977,7 +969,13 @@ impl Device { // resolve TextureViewDescriptor defaults // https://gpuweb.github.io/gpuweb/#abstract-opdef-resolving-gputextureviewdescriptor-defaults - let resolved_format = desc.format.unwrap_or(texture.desc.format); + let resolved_format = desc.format.unwrap_or_else(|| { + texture + .desc + .format + .aspect_specific_format(desc.range.aspect) + .unwrap_or(texture.desc.format) + }); let resolved_dimension = desc .dimension @@ -1018,8 +1016,7 @@ impl Device { // validate TextureViewDescriptor - let aspects = hal::FormatAspects::from(texture.desc.format) - & hal::FormatAspects::from(desc.range.aspect); + let aspects = hal::FormatAspects::new(texture.desc.format, desc.range.aspect); if aspects.is_empty() { return Err(resource::CreateTextureViewError::InvalidAspect { texture_format: texture.desc.format, @@ -1027,9 +1024,17 @@ impl Device { }); } - if resolved_format != texture.desc.format - && !texture.desc.view_formats.contains(&resolved_format) - { + let format_is_good = if desc.range.aspect == wgt::TextureAspect::All { + resolved_format == texture.desc.format + || texture.desc.view_formats.contains(&resolved_format) + } else { + Some(resolved_format) + == texture + .desc + .format + .aspect_specific_format(desc.range.aspect) + }; + if !format_is_good { return Err(resource::CreateTextureViewError::FormatReinterpretation { texture: texture.desc.format, view: resolved_format, @@ -1172,6 +1177,13 @@ impl Device { usage ); + // use the combined depth-stencil format for the view + let format = if resolved_format.is_depth_stencil_component(texture.desc.format) { + texture.desc.format + } else { + resolved_format + }; + let resolved_range = wgt::ImageSubresourceRange { aspect: desc.range.aspect, base_mip_level: desc.range.base_mip_level, @@ -1182,7 +1194,7 @@ impl Device { let hal_desc = hal::TextureViewDescriptor { label: desc.label.borrow_option(), - format: resolved_format, + format, dimension: resolved_dimension, usage, range: resolved_range, @@ -2191,7 +2203,6 @@ impl Device { { return Err(Error::DepthStencilAspect); } - let format_info = view.desc.format.describe(); match decl.ty { wgt::BindingType::Texture { sample_type, @@ -2206,7 +2217,12 @@ impl Device { view_samples: view.samples, }); } - match (sample_type, format_info.sample_type) { + let compat_sample_type = view + .desc + .format + .sample_type(Some(view.desc.range.aspect)) + .unwrap(); + match (sample_type, compat_sample_type) { (Tst::Uint, Tst::Uint) | (Tst::Sint, Tst::Sint) | (Tst::Depth, Tst::Depth) | @@ -3101,8 +3117,7 @@ impl Device { adapter: &Adapter, format: TextureFormat, ) -> Result { - let format_desc = format.describe(); - self.require_features(format_desc.required_features)?; + self.require_features(format.required_features())?; let using_device_features = self .features @@ -3114,7 +3129,7 @@ impl Device { if using_device_features || downlevel { Ok(adapter.get_texture_format_features(format)) } else { - Ok(format_desc.guaranteed_format_features) + Ok(format.guaranteed_format_features()) } } @@ -3330,7 +3345,7 @@ impl Global { self.fetch_adapter_and_surface::(surface_id, adapter_id, |adapter, surface| { let mut hal_caps = surface.get_capabilities(adapter)?; - hal_caps.formats.sort_by_key(|f| !f.describe().srgb); + hal_caps.formats.sort_by_key(|f| !f.is_srgb()); Ok(wgt::SurfaceCapabilities { formats: hal_caps.formats, diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index fe86b0ad1f3..b7c4e359b11 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -602,9 +602,7 @@ impl Global { .get_mut(destination.texture) .map_err(|_| TransferError::InvalidTexture(destination.texture))?; - let (selector, dst_base, texture_format) = - extract_texture_selector(destination, size, dst)?; - let format_desc = texture_format.describe(); + let (selector, dst_base) = extract_texture_selector(destination, size, dst)?; if !dst.desc.usage.contains(wgt::TextureUsages::COPY_DST) { return Err( @@ -617,28 +615,39 @@ impl Global { let (hal_copy_size, array_layer_count) = validate_texture_copy_range(destination, &dst.desc, CopySide::Destination, size)?; + if !dst_base.aspect.is_one() { + return Err(TransferError::CopyAspectNotOne.into()); + } + + if !conv::is_valid_copy_dst_texture_format(dst.desc.format, destination.aspect) { + return Err(TransferError::CopyToForbiddenTextureFormat { + format: dst.desc.format, + aspect: destination.aspect, + } + .into()); + } + // Note: `_source_bytes_per_array_layer` is ignored since we // have a staging copy, and it can have a different value. let (_, _source_bytes_per_array_layer) = validate_linear_texture_data( data_layout, - texture_format, + dst.desc.format, + destination.aspect, data.len() as wgt::BufferAddress, CopySide::Source, - format_desc.block_size as wgt::BufferAddress, size, false, )?; - if !conv::is_valid_copy_dst_texture_format(texture_format, destination.aspect) { - return Err(TransferError::CopyToForbiddenTextureFormat { - format: texture_format, - aspect: destination.aspect, - } - .into()); + if dst.desc.format.is_depth_stencil_format() { + device + .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES) + .map_err(TransferError::from)?; } - let (block_width, block_height) = format_desc.block_dimensions; - let width_blocks = size.width / block_width as u32; - let height_blocks = size.height / block_height as u32; + + let (block_width, block_height) = dst.desc.format.block_dimensions(); + let width_blocks = size.width / block_width; + let height_blocks = size.height / block_height; let block_rows_per_image = match data_layout.rows_per_image { Some(rows_per_image) => rows_per_image.get(), @@ -650,14 +659,15 @@ impl Global { } }; - let bytes_per_row_alignment = get_lowest_common_denom( - device.alignments.buffer_copy_pitch.get() as u32, - format_desc.block_size as u32, - ); - let stage_bytes_per_row = hal::auxil::align_to( - format_desc.block_size as u32 * width_blocks, - bytes_per_row_alignment, - ); + let block_size = dst + .desc + .format + .block_size(Some(destination.aspect)) + .unwrap(); + let bytes_per_row_alignment = + get_lowest_common_denom(device.alignments.buffer_copy_pitch.get() as u32, block_size); + let stage_bytes_per_row = + hal::auxil::align_to(block_size * width_blocks, bytes_per_row_alignment); let block_rows_in_copy = (size.depth_or_array_layers - 1) * block_rows_per_image + height_blocks; @@ -731,7 +741,7 @@ impl Global { let bytes_per_row = if let Some(bytes_per_row) = data_layout.bytes_per_row { bytes_per_row.get() } else { - width_blocks * format_desc.block_size as u32 + width_blocks * block_size }; // Platform validation requires that the staging buffer always be @@ -858,7 +868,7 @@ impl Global { let (mut texture_guard, _) = hub.textures.write(&mut token); // For clear we need write access to the texture. TODO: Can we acquire write lock later? let dst = texture_guard.get_mut(destination.texture).unwrap(); - let (selector, dst_base, _) = + let (selector, dst_base) = extract_texture_selector(&destination.to_untagged(), &size, dst)?; if !conv::is_valid_external_image_copy_dst_texture_format(dst.desc.format) { diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index c50b8240df3..1c3c4a38c09 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -572,7 +572,7 @@ pub(crate) struct HalTextureViewDescriptor { impl HalTextureViewDescriptor { pub fn aspects(&self) -> hal::FormatAspects { - hal::FormatAspects::from(self.format) & hal::FormatAspects::from(self.range.aspect) + hal::FormatAspects::new(self.format, self.range.aspect) } } diff --git a/wgpu-hal/src/auxil/dxgi/conv.rs b/wgpu-hal/src/auxil/dxgi/conv.rs index ed82faaa714..94fbc72ac00 100644 --- a/wgpu-hal/src/auxil/dxgi/conv.rs +++ b/wgpu-hal/src/auxil/dxgi/conv.rs @@ -101,36 +101,51 @@ pub fn map_texture_format_nosrgb(format: wgt::TextureFormat) -> dxgiformat::DXGI } } -// Note: SRV and UAV can't use the depth formats directly -pub fn map_texture_format_nodepth(format: wgt::TextureFormat) -> dxgiformat::DXGI_FORMAT { - match format { - wgt::TextureFormat::Depth16Unorm => dxgiformat::DXGI_FORMAT_R16_UNORM, - wgt::TextureFormat::Depth32Float => dxgiformat::DXGI_FORMAT_R32_FLOAT, - wgt::TextureFormat::Depth32FloatStencil8 => { +// SRV and UAV can't use the depth or typeless formats +// see https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#5.3.6.3%20Pipeline%20Binding:%20Depth/%20Stencil%20Output +pub fn map_aspect_specific_texture_format( + format: wgt::TextureFormat, + aspect: crate::FormatAspects, +) -> Option { + Some(match (format, aspect) { + (wgt::TextureFormat::Depth16Unorm, crate::FormatAspects::DEPTH) => { + dxgiformat::DXGI_FORMAT_R16_UNORM + } + (wgt::TextureFormat::Depth32Float, crate::FormatAspects::DEPTH) => { + dxgiformat::DXGI_FORMAT_R32_FLOAT + } + (wgt::TextureFormat::Depth32FloatStencil8, crate::FormatAspects::DEPTH) => { dxgiformat::DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS } - wgt::TextureFormat::Stencil8 - | wgt::TextureFormat::Depth24Plus - | wgt::TextureFormat::Depth24PlusStencil8 => dxgiformat::DXGI_FORMAT_R24_UNORM_X8_TYPELESS, - _ => { - assert_eq!( - crate::FormatAspects::from(format), - crate::FormatAspects::COLOR - ); - map_texture_format(format) + ( + wgt::TextureFormat::Depth24Plus | wgt::TextureFormat::Depth24PlusStencil8, + crate::FormatAspects::DEPTH, + ) => dxgiformat::DXGI_FORMAT_R24_UNORM_X8_TYPELESS, + + (wgt::TextureFormat::Depth32FloatStencil8, crate::FormatAspects::STENCIL) => { + dxgiformat::DXGI_FORMAT_X32_TYPELESS_G8X24_UINT } - } + ( + wgt::TextureFormat::Stencil8 | wgt::TextureFormat::Depth24PlusStencil8, + crate::FormatAspects::STENCIL, + ) => dxgiformat::DXGI_FORMAT_X24_TYPELESS_G8_UINT, + + (format, crate::FormatAspects::COLOR) => map_texture_format(format), + + _ => return None, + }) } -pub fn map_texture_format_depth_typeless(format: wgt::TextureFormat) -> dxgiformat::DXGI_FORMAT { +pub fn map_texture_format_depth_stencil_typeless( + format: wgt::TextureFormat, +) -> dxgiformat::DXGI_FORMAT { match format { wgt::TextureFormat::Depth16Unorm => dxgiformat::DXGI_FORMAT_R16_TYPELESS, wgt::TextureFormat::Depth32Float => dxgiformat::DXGI_FORMAT_R32_TYPELESS, wgt::TextureFormat::Depth32FloatStencil8 => dxgiformat::DXGI_FORMAT_R32G8X24_TYPELESS, - wgt::TextureFormat::Stencil8 => dxgiformat::DXGI_FORMAT_R24G8_TYPELESS, - wgt::TextureFormat::Depth24Plus | wgt::TextureFormat::Depth24PlusStencil8 => { - dxgiformat::DXGI_FORMAT_R24G8_TYPELESS - } + wgt::TextureFormat::Stencil8 + | wgt::TextureFormat::Depth24Plus + | wgt::TextureFormat::Depth24PlusStencil8 => dxgiformat::DXGI_FORMAT_R24G8_TYPELESS, _ => unreachable!(), } } diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 4f6f75dc7da..ce2d6206a53 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -376,7 +376,19 @@ impl crate::Adapter for super::Adapter { Some(f) => f, None => return Tfc::empty(), }; - let no_depth_format = auxil::dxgi::conv::map_texture_format_nodepth(format); + let srv_uav_format = if format.is_combined_depth_stencil_format() { + auxil::dxgi::conv::map_aspect_specific_texture_format( + format, + // use the depth aspect here as opposed to stencil since it has more capabilities + crate::FormatAspects::DEPTH, + ) + } else { + auxil::dxgi::conv::map_aspect_specific_texture_format( + format, + crate::FormatAspects::from(format), + ) + } + .unwrap(); let mut data = d3d12::D3D12_FEATURE_DATA_FORMAT_SUPPORT { Format: raw_format, @@ -393,24 +405,24 @@ impl crate::Adapter for super::Adapter { // Because we use a different format for SRV and UAV views of depth textures, we need to check // the features that use SRV/UAVs using the no-depth format. - let mut data_no_depth = d3d12::D3D12_FEATURE_DATA_FORMAT_SUPPORT { - Format: no_depth_format, + let mut data_srv_uav = d3d12::D3D12_FEATURE_DATA_FORMAT_SUPPORT { + Format: srv_uav_format, Support1: d3d12::D3D12_FORMAT_SUPPORT1_NONE, Support2: d3d12::D3D12_FORMAT_SUPPORT2_NONE, }; - if raw_format != no_depth_format { + if raw_format != srv_uav_format { // Only-recheck if we're using a different format assert_eq!(winerror::S_OK, unsafe { self.device.CheckFeatureSupport( d3d12::D3D12_FEATURE_FORMAT_SUPPORT, - ptr::addr_of_mut!(data_no_depth).cast(), + ptr::addr_of_mut!(data_srv_uav).cast(), DWORD::try_from(mem::size_of::()) .unwrap(), ) }); } else { // Same format, just copy over. - data_no_depth = data; + data_srv_uav = data; } let mut caps = Tfc::COPY_SRC | Tfc::COPY_DST; @@ -420,14 +432,14 @@ impl crate::Adapter for super::Adapter { | d3d12::D3D12_FORMAT_SUPPORT1_TEXTURE3D | d3d12::D3D12_FORMAT_SUPPORT1_TEXTURECUBE) != 0; - // SRVs use no-depth format + // SRVs use srv_uav_format caps.set( Tfc::SAMPLED, - is_texture && data_no_depth.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_SHADER_LOAD != 0, + is_texture && data_srv_uav.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_SHADER_LOAD != 0, ); caps.set( Tfc::SAMPLED_LINEAR, - data_no_depth.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE != 0, + data_srv_uav.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE != 0, ); caps.set( Tfc::COLOR_ATTACHMENT, @@ -441,19 +453,19 @@ impl crate::Adapter for super::Adapter { Tfc::DEPTH_STENCIL_ATTACHMENT, data.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_DEPTH_STENCIL != 0, ); - // UAVs use no-depth format + // UAVs use srv_uav_format caps.set( Tfc::STORAGE, - data_no_depth.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_TYPED_UNORDERED_ACCESS_VIEW != 0, + data_srv_uav.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_TYPED_UNORDERED_ACCESS_VIEW != 0, ); caps.set( Tfc::STORAGE_READ_WRITE, - data_no_depth.Support2 & d3d12::D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD != 0, + data_srv_uav.Support2 & d3d12::D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD != 0, ); - // We load via UAV/SRV so use no-depth + // We load via UAV/SRV so use srv_uav_format let no_msaa_load = caps.contains(Tfc::SAMPLED) - && data_no_depth.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_MULTISAMPLE_LOAD == 0; + && data_srv_uav.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_MULTISAMPLE_LOAD == 0; let no_msaa_target = data.Support1 & (d3d12::D3D12_FORMAT_SUPPORT1_RENDER_TARGET diff --git a/wgpu-hal/src/dx12/command.rs b/wgpu-hal/src/dx12/command.rs index d678496960f..d1ce2dc3180 100644 --- a/wgpu-hal/src/dx12/command.rs +++ b/wgpu-hal/src/dx12/command.rs @@ -20,26 +20,30 @@ impl crate::BufferTextureCopy { &self, format: wgt::TextureFormat, ) -> d3d12::D3D12_PLACED_SUBRESOURCE_FOOTPRINT { - let desc = format.describe(); + let (block_width, block_height) = format.block_dimensions(); d3d12::D3D12_PLACED_SUBRESOURCE_FOOTPRINT { Offset: self.buffer_layout.offset, Footprint: d3d12::D3D12_SUBRESOURCE_FOOTPRINT { - Format: auxil::dxgi::conv::map_texture_format(format), + Format: auxil::dxgi::conv::map_aspect_specific_texture_format( + format, + self.texture_base.aspect, + ) + .unwrap(), Width: self.size.width, Height: self .buffer_layout .rows_per_image - .map_or(self.size.height, |count| { - count.get() * desc.block_dimensions.1 as u32 - }), + .map_or(self.size.height, |count| count.get() * block_height), Depth: self.size.depth, RowPitch: { let actual = match self.buffer_layout.bytes_per_row { Some(count) => count.get(), // this may happen for single-line updates None => { - (self.size.width / desc.block_dimensions.0 as u32) - * desc.block_size as u32 + let block_size = format + .block_size(Some(self.texture_base.aspect.map())) + .unwrap(); + (self.size.width / block_width) * block_size } }; crate::auxil::align_to(actual, d3d12::D3D12_TEXTURE_DATA_PITCH_ALIGNMENT) @@ -705,7 +709,7 @@ impl crate::CommandEncoder for super::CommandEncoder { if let Some(ref ds) = desc.depth_stencil_attachment { let mut flags = native::ClearFlags::empty(); - let aspects = ds.target.view.format_aspects; + let aspects = ds.target.view.aspects; if !ds.depth_ops.contains(crate::AttachmentOps::LOAD) && aspects.contains(crate::FormatAspects::DEPTH) { diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index c92cd2edf76..cf12716b043 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -1,7 +1,4 @@ -use crate::{ - auxil::{self, dxgi::result::HResult as _}, - FormatAspects, -}; +use crate::auxil::{self, dxgi::result::HResult as _}; use super::{conv, descriptor, view}; use parking_lot::Mutex; @@ -421,7 +418,7 @@ impl crate::Device for super::Device { // because then we'd create a non-depth format view of it. // Note: we can skip this branch if // `D3D12_FEATURE_D3D12_OPTIONS3::CastingFullyTypedFormatSupported` - auxil::dxgi::conv::map_texture_format_depth_typeless(desc.format) + auxil::dxgi::conv::map_texture_format_depth_stencil_typeless(desc.format) }, SampleDesc: dxgitype::DXGI_SAMPLE_DESC { Count: desc.sample_count, @@ -469,23 +466,25 @@ impl crate::Device for super::Device { let view_desc = desc.to_internal(texture); Ok(super::TextureView { - raw_format: view_desc.format, - format_aspects: FormatAspects::from(desc.format), + raw_format: view_desc.rtv_dsv_format, + aspects: view_desc.aspects, target_base: ( texture.resource, texture.calc_subresource(desc.range.base_mip_level, desc.range.base_array_layer, 0), ), handle_srv: if desc.usage.intersects(crate::TextureUses::RESOURCE) { let raw_desc = unsafe { view_desc.to_srv() }; - let handle = self.srv_uav_pool.lock().alloc_handle(); - unsafe { - self.raw.CreateShaderResourceView( - texture.resource.as_mut_ptr(), - &raw_desc, - handle.raw, - ) - }; - Some(handle) + raw_desc.map(|raw_desc| { + let handle = self.srv_uav_pool.lock().alloc_handle(); + unsafe { + self.raw.CreateShaderResourceView( + texture.resource.as_mut_ptr(), + &raw_desc, + handle.raw, + ) + }; + handle + }) } else { None }, @@ -493,16 +492,18 @@ impl crate::Device for super::Device { crate::TextureUses::STORAGE_READ | crate::TextureUses::STORAGE_READ_WRITE, ) { let raw_desc = unsafe { view_desc.to_uav() }; - let handle = self.srv_uav_pool.lock().alloc_handle(); - unsafe { - self.raw.CreateUnorderedAccessView( - texture.resource.as_mut_ptr(), - ptr::null_mut(), - &raw_desc, - handle.raw, - ) - }; - Some(handle) + raw_desc.map(|raw_desc| { + let handle = self.srv_uav_pool.lock().alloc_handle(); + unsafe { + self.raw.CreateUnorderedAccessView( + texture.resource.as_mut_ptr(), + ptr::null_mut(), + &raw_desc, + handle.raw, + ) + }; + handle + }) } else { None }, @@ -524,7 +525,7 @@ impl crate::Device for super::Device { .usage .intersects(crate::TextureUses::DEPTH_STENCIL_READ) { - let raw_desc = unsafe { view_desc.to_dsv(desc.format.into()) }; + let raw_desc = unsafe { view_desc.to_dsv(true) }; let handle = self.dsv_pool.lock().alloc_handle(); unsafe { self.raw.CreateDepthStencilView( @@ -541,7 +542,7 @@ impl crate::Device for super::Device { .usage .intersects(crate::TextureUses::DEPTH_STENCIL_WRITE) { - let raw_desc = unsafe { view_desc.to_dsv(FormatAspects::empty()) }; + let raw_desc = unsafe { view_desc.to_dsv(false) }; let handle = self.dsv_pool.lock().alloc_handle(); unsafe { self.raw.CreateDepthStencilView( diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index 5e4d399e3bf..f31969d32de 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -451,7 +451,7 @@ impl Texture { #[derive(Debug)] pub struct TextureView { raw_format: native::Format, - format_aspects: crate::FormatAspects, // May explicitly ignore stencil aspect of raw_format! + aspects: crate::FormatAspects, target_base: (native::Resource, u32), handle_srv: Option, handle_uav: Option, diff --git a/wgpu-hal/src/dx12/view.rs b/wgpu-hal/src/dx12/view.rs index f52e0ee4b8e..4f55fe47ad1 100644 --- a/wgpu-hal/src/dx12/view.rs +++ b/wgpu-hal/src/dx12/view.rs @@ -6,8 +6,9 @@ pub(crate) const D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING: u32 = 0x1688; pub(super) struct ViewDescriptor { dimension: wgt::TextureViewDimension, - pub format: native::Format, - format_nodepth: native::Format, + pub aspects: crate::FormatAspects, + pub rtv_dsv_format: native::Format, + srv_uav_format: Option, multisampled: bool, array_layer_base: u32, array_layer_count: u32, @@ -17,10 +18,16 @@ pub(super) struct ViewDescriptor { impl crate::TextureViewDescriptor<'_> { pub(super) fn to_internal(&self, texture: &super::Texture) -> ViewDescriptor { + let aspects = crate::FormatAspects::new(self.format, self.range.aspect); + ViewDescriptor { dimension: self.dimension, - format: auxil::dxgi::conv::map_texture_format(self.format), - format_nodepth: auxil::dxgi::conv::map_texture_format_nodepth(self.format), + aspects, + rtv_dsv_format: auxil::dxgi::conv::map_texture_format(self.format), + srv_uav_format: auxil::dxgi::conv::map_aspect_specific_texture_format( + self.format, + aspects, + ), multisampled: texture.sample_count > 1, mip_level_base: self.range.base_mip_level, mip_level_count: self.range.mip_level_count.unwrap_or(!0), @@ -31,9 +38,9 @@ impl crate::TextureViewDescriptor<'_> { } impl ViewDescriptor { - pub(crate) unsafe fn to_srv(&self) -> d3d12::D3D12_SHADER_RESOURCE_VIEW_DESC { + pub(crate) unsafe fn to_srv(&self) -> Option { let mut desc = d3d12::D3D12_SHADER_RESOURCE_VIEW_DESC { - Format: self.format_nodepth, + Format: self.srv_uav_format?, ViewDimension: 0, Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, u: unsafe { mem::zeroed() }, @@ -142,12 +149,12 @@ impl ViewDescriptor { } } - desc + Some(desc) } - pub(crate) unsafe fn to_uav(&self) -> d3d12::D3D12_UNORDERED_ACCESS_VIEW_DESC { + pub(crate) unsafe fn to_uav(&self) -> Option { let mut desc = d3d12::D3D12_UNORDERED_ACCESS_VIEW_DESC { - Format: self.format_nodepth, + Format: self.srv_uav_format?, ViewDimension: 0, u: unsafe { mem::zeroed() }, }; @@ -205,12 +212,12 @@ impl ViewDescriptor { } } - desc + Some(desc) } pub(crate) unsafe fn to_rtv(&self) -> d3d12::D3D12_RENDER_TARGET_VIEW_DESC { let mut desc = d3d12::D3D12_RENDER_TARGET_VIEW_DESC { - Format: self.format, + Format: self.rtv_dsv_format, ViewDimension: 0, u: unsafe { mem::zeroed() }, }; @@ -290,20 +297,19 @@ impl ViewDescriptor { desc } - pub(crate) unsafe fn to_dsv( - &self, - ro_aspects: crate::FormatAspects, - ) -> d3d12::D3D12_DEPTH_STENCIL_VIEW_DESC { + pub(crate) unsafe fn to_dsv(&self, read_only: bool) -> d3d12::D3D12_DEPTH_STENCIL_VIEW_DESC { let mut desc = d3d12::D3D12_DEPTH_STENCIL_VIEW_DESC { - Format: self.format, + Format: self.rtv_dsv_format, ViewDimension: 0, Flags: { let mut flags = d3d12::D3D12_DSV_FLAG_NONE; - if ro_aspects.contains(crate::FormatAspects::DEPTH) { - flags |= d3d12::D3D12_DSV_FLAG_READ_ONLY_DEPTH; - } - if ro_aspects.contains(crate::FormatAspects::STENCIL) { - flags |= d3d12::D3D12_DSV_FLAG_READ_ONLY_STENCIL; + if read_only { + if self.aspects.contains(crate::FormatAspects::DEPTH) { + flags |= d3d12::D3D12_DSV_FLAG_READ_ONLY_DEPTH; + } + if self.aspects.contains(crate::FormatAspects::STENCIL) { + flags |= d3d12::D3D12_DSV_FLAG_READ_ONLY_STENCIL; + } } flags }, diff --git a/wgpu-hal/src/gles/command.rs b/wgpu-hal/src/gles/command.rs index 8076a13aebe..4d3e6fc1209 100644 --- a/wgpu-hal/src/gles/command.rs +++ b/wgpu-hal/src/gles/command.rs @@ -558,15 +558,13 @@ impl crate::CommandEncoder for super::CommandEncoder { { if !cat.ops.contains(crate::AttachmentOps::LOAD) { let c = &cat.clear_value; - self.cmd_buffer - .commands - .push(match cat.target.view.sample_type { + self.cmd_buffer.commands.push( + match cat.target.view.format.sample_type(None).unwrap() { wgt::TextureSampleType::Float { .. } => C::ClearColorF { draw_buffer: i as u32, color: [c.r as f32, c.g as f32, c.b as f32, c.a as f32], - is_srgb: cat.target.view.format.describe().srgb, + is_srgb: cat.target.view.format.is_srgb(), }, - wgt::TextureSampleType::Depth => unreachable!(), wgt::TextureSampleType::Uint => C::ClearColorU( i as u32, [c.r as u32, c.g as u32, c.b as u32, c.a as u32], @@ -575,7 +573,9 @@ impl crate::CommandEncoder for super::CommandEncoder { i as u32, [c.r as i32, c.g as i32, c.b as i32, c.a as i32], ), - }); + wgt::TextureSampleType::Depth => unreachable!(), + }, + ); } } if let Some(ref dsat) = desc.depth_stencil_attachment { @@ -681,13 +681,18 @@ impl crate::CommandEncoder for super::CommandEncoder { dirty_samplers |= 1 << slot; self.state.samplers[slot as usize] = Some(sampler); } - super::RawBinding::Texture { raw, target } => { + super::RawBinding::Texture { + raw, + target, + aspects, + } => { dirty_textures |= 1 << slot; self.state.texture_slots[slot as usize].tex_target = target; self.cmd_buffer.commands.push(C::BindTexture { slot, texture: raw, target, + aspects, }); } super::RawBinding::Image(ref binding) => { diff --git a/wgpu-hal/src/gles/conv.rs b/wgpu-hal/src/gles/conv.rs index 14d30e93089..41f1bfeee69 100644 --- a/wgpu-hal/src/gles/conv.rs +++ b/wgpu-hal/src/gles/conv.rs @@ -58,7 +58,7 @@ impl super::AdapterShared { Tf::Rgba32Float => (glow::RGBA32F, glow::RGBA, glow::FLOAT), Tf::Stencil8 => ( glow::STENCIL_INDEX8, - glow::STENCIL_COMPONENTS, + glow::STENCIL_INDEX, glow::UNSIGNED_BYTE, ), Tf::Depth16Unorm => ( @@ -67,18 +67,20 @@ impl super::AdapterShared { glow::UNSIGNED_SHORT, ), Tf::Depth32Float => (glow::DEPTH_COMPONENT32F, glow::DEPTH_COMPONENT, glow::FLOAT), - Tf::Depth32FloatStencil8 => { - (glow::DEPTH32F_STENCIL8, glow::DEPTH_COMPONENT, glow::FLOAT) - } + Tf::Depth32FloatStencil8 => ( + glow::DEPTH32F_STENCIL8, + glow::DEPTH_STENCIL, + glow::FLOAT_32_UNSIGNED_INT_24_8_REV, + ), Tf::Depth24Plus => ( glow::DEPTH_COMPONENT24, glow::DEPTH_COMPONENT, - glow::UNSIGNED_NORMALIZED, + glow::UNSIGNED_INT, ), Tf::Depth24PlusStencil8 => ( glow::DEPTH24_STENCIL8, - glow::DEPTH_COMPONENT, - glow::UNSIGNED_INT, + glow::DEPTH_STENCIL, + glow::UNSIGNED_INT_24_8, ), Tf::Rgb9e5Ufloat => (glow::RGB9_E5, glow::RGB, glow::UNSIGNED_INT_5_9_9_9_REV), Tf::Bc1RgbaUnorm => (glow::COMPRESSED_RGBA_S3TC_DXT1_EXT, glow::RGBA, 0), diff --git a/wgpu-hal/src/gles/device.rs b/wgpu-hal/src/gles/device.rs index f6921d2b697..0b643a8fc33 100644 --- a/wgpu-hal/src/gles/device.rs +++ b/wgpu-hal/src/gles/device.rs @@ -706,10 +706,12 @@ impl crate::Device for super::Device { unsafe { gl.bind_texture(target, Some(raw)) }; //Note: this has to be done before defining the storage! - match desc.format.describe().sample_type { - wgt::TextureSampleType::Float { filterable: false } - | wgt::TextureSampleType::Uint - | wgt::TextureSampleType::Sint => { + match desc.format.sample_type(None) { + Some( + wgt::TextureSampleType::Float { filterable: false } + | wgt::TextureSampleType::Uint + | wgt::TextureSampleType::Sint, + ) => { // reset default filtering mode unsafe { gl.tex_parameter_i32(target, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32) @@ -718,8 +720,7 @@ impl crate::Device for super::Device { gl.tex_parameter_i32(target, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32) }; } - wgt::TextureSampleType::Float { filterable: true } - | wgt::TextureSampleType::Depth => {} + _ => {} } if is_3d { @@ -808,9 +809,7 @@ impl crate::Device for super::Device { Ok(super::TextureView { //TODO: use `conv::map_view_dimension(desc.dimension)`? inner: texture.inner.clone(), - sample_type: texture.format.describe().sample_type, - aspects: crate::FormatAspects::from(texture.format) - & crate::FormatAspects::from(desc.range.aspect), + aspects: crate::FormatAspects::new(texture.format, desc.range.aspect), mip_levels: desc.range.mip_range(texture.mip_level_count), array_layers: desc.range.layer_range(texture.array_layer_count), format: texture.format, @@ -1037,7 +1036,11 @@ impl crate::Device for super::Device { "This is an implementation problem of wgpu-hal/gles backend.") } let (raw, target) = view.inner.as_native(); - super::RawBinding::Texture { raw, target } + super::RawBinding::Texture { + raw, + target, + aspects: view.aspects, + } } wgt::BindingType::StorageTexture { access, diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index 31fde089d95..df3fc5641de 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -339,7 +339,6 @@ impl Texture { #[derive(Clone, Debug)] pub struct TextureView { inner: TextureInner, - sample_type: wgt::TextureSampleType, aspects: crate::FormatAspects, mip_levels: Range, array_layers: Range, @@ -395,6 +394,7 @@ enum RawBinding { Texture { raw: glow::Texture, target: BindTarget, + aspects: crate::FormatAspects, //TODO: mip levels, array layers }, Image(ImageBinding), @@ -804,6 +804,7 @@ enum Command { slot: u32, texture: glow::Texture, target: BindTarget, + aspects: crate::FormatAspects, }, BindImage { slot: u32, diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index add1daeb749..d91fe036119 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -591,22 +591,23 @@ impl super::Queue { dst_format, ref copy, } => { - let format_info = dst_format.describe(); + let (block_width, block_height) = dst_format.block_dimensions(); + let block_size = dst_format.block_size(None).unwrap(); let format_desc = self.shared.describe_texture_format(dst_format); - let row_texels = copy.buffer_layout.bytes_per_row.map_or(0, |bpr| { - format_info.block_dimensions.0 as u32 * bpr.get() - / format_info.block_size as u32 - }); + let row_texels = copy + .buffer_layout + .bytes_per_row + .map_or(0, |bpr| block_width * bpr.get() / block_size); let column_texels = copy .buffer_layout .rows_per_image - .map_or(0, |rpi| format_info.block_dimensions.1 as u32 * rpi.get()); + .map_or(0, |rpi| block_height * rpi.get()); unsafe { gl.bind_texture(dst_target, Some(dst)) }; unsafe { gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, row_texels as i32) }; unsafe { gl.pixel_store_i32(glow::UNPACK_IMAGE_HEIGHT, column_texels as i32) }; let mut unbind_unpack_buffer = false; - if !format_info.is_compressed() { + if !dst_format.is_compressed() { let buffer_data; let unpack_data = match src.raw { Some(buffer) => { @@ -710,12 +711,9 @@ impl super::Queue { let bytes_per_row = copy .buffer_layout .bytes_per_row - .map_or(copy.size.width * format_info.block_size as u32, |bpr| { - bpr.get() - }); - let block_height = format_info.block_dimensions.1 as u32; - let minimum_rows_per_image = (copy.size.height + block_height - 1) - / format_info.block_dimensions.1 as u32; + .map_or(copy.size.width * block_size, |bpr| bpr.get()); + let minimum_rows_per_image = + (copy.size.height + block_height - 1) / block_height; let rows_per_image = copy .buffer_layout .rows_per_image @@ -806,8 +804,8 @@ impl super::Queue { dst_target: _, ref copy, } => { - let format_info = src_format.describe(); - if format_info.is_compressed() { + let block_size = src_format.block_size(None).unwrap(); + if src_format.is_compressed() { log::error!("Not implemented yet: compressed texture copy to buffer"); return; } @@ -821,9 +819,7 @@ impl super::Queue { let row_texels = copy .buffer_layout .bytes_per_row - .map_or(copy.size.width, |bpr| { - bpr.get() / format_info.block_size as u32 - }); + .map_or(copy.size.width, |bpr| bpr.get() / block_size); unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(self.copy_fbo)) }; //TODO: handle cubemap copies @@ -1354,9 +1350,30 @@ impl super::Queue { slot, texture, target, + aspects, } => { unsafe { gl.active_texture(glow::TEXTURE0 + slot) }; unsafe { gl.bind_texture(target, Some(texture)) }; + + let version = gl.version(); + let is_min_es_3_1 = version.is_embedded && (version.major, version.minor) >= (3, 1); + let is_min_4_3 = !version.is_embedded && (version.major, version.minor) >= (4, 3); + if is_min_es_3_1 || is_min_4_3 { + let mode = match aspects { + crate::FormatAspects::DEPTH => Some(glow::DEPTH_COMPONENT), + crate::FormatAspects::STENCIL => Some(glow::STENCIL_INDEX), + _ => None, + }; + if let Some(mode) = mode { + unsafe { + gl.tex_parameter_i32( + target, + glow::DEPTH_STENCIL_TEXTURE_MODE, + mode as _, + ) + }; + } + } } C::BindImage { slot, ref binding } => { unsafe { diff --git a/wgpu-hal/src/gles/web.rs b/wgpu-hal/src/gles/web.rs index 091c494ddc4..254b6584e0d 100644 --- a/wgpu-hal/src/gles/web.rs +++ b/wgpu-hal/src/gles/web.rs @@ -191,7 +191,7 @@ impl Surface { "need to configure surface before presenting", ))?; - if swapchain.format.describe().srgb { + if swapchain.format.is_srgb() { // Important to set the viewport since we don't know in what state the user left it. unsafe { gl.viewport( @@ -277,7 +277,7 @@ impl crate::Surface for Surface { unsafe { gl.delete_framebuffer(swapchain.framebuffer) }; } - if self.srgb_present_program.is_none() && config.format.describe().srgb { + if self.srgb_present_program.is_none() && config.format.is_srgb() { self.srgb_present_program = Some(unsafe { Self::create_srgb_present_program(gl) }); } diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index 3178e255af3..69b9de853ff 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -644,12 +644,27 @@ bitflags!( } ); -impl From for FormatAspects { - fn from(aspect: wgt::TextureAspect) -> Self { - match aspect { +impl FormatAspects { + pub fn new(format: wgt::TextureFormat, aspect: wgt::TextureAspect) -> Self { + let aspect_mask = match aspect { wgt::TextureAspect::All => Self::all(), wgt::TextureAspect::DepthOnly => Self::DEPTH, wgt::TextureAspect::StencilOnly => Self::STENCIL, + }; + Self::from(format) & aspect_mask + } + + /// Returns `true` if only one flag is set + pub fn is_one(&self) -> bool { + self.bits().count_ones() == 1 + } + + pub fn map(&self) -> wgt::TextureAspect { + match *self { + Self::COLOR => wgt::TextureAspect::All, + Self::DEPTH => wgt::TextureAspect::DepthOnly, + Self::STENCIL => wgt::TextureAspect::StencilOnly, + _ => unreachable!(), } } } @@ -658,8 +673,9 @@ impl From for FormatAspects { fn from(format: wgt::TextureFormat) -> Self { match format { wgt::TextureFormat::Stencil8 => Self::STENCIL, - wgt::TextureFormat::Depth16Unorm => Self::DEPTH, - wgt::TextureFormat::Depth32Float | wgt::TextureFormat::Depth24Plus => Self::DEPTH, + wgt::TextureFormat::Depth16Unorm + | wgt::TextureFormat::Depth32Float + | wgt::TextureFormat::Depth24Plus => Self::DEPTH, wgt::TextureFormat::Depth32FloatStencil8 | wgt::TextureFormat::Depth24PlusStencil8 => { Self::DEPTH | Self::STENCIL } diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 419d9010c6c..e1ac0eae560 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -1003,6 +1003,30 @@ impl super::PrivateCapabilities { }, } } + + pub fn map_view_format( + &self, + format: wgt::TextureFormat, + aspects: crate::FormatAspects, + ) -> mtl::MTLPixelFormat { + use crate::FormatAspects as Fa; + use mtl::MTLPixelFormat::*; + use wgt::TextureFormat as Tf; + match (format, aspects) { + // map combined depth-stencil format to their stencil-only format + // see https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/WhatsNewiniOS10tvOS10andOSX1012/WhatsNewiniOS10tvOS10andOSX1012.html#//apple_ref/doc/uid/TP40014221-CH14-DontLinkElementID_77 + (Tf::Depth24PlusStencil8, Fa::STENCIL) => { + if self.format_depth24_stencil8 { + X24_Stencil8 + } else { + X32_Stencil8 + } + } + (Tf::Depth32FloatStencil8, Fa::STENCIL) => X32_Stencil8, + + _ => self.map_format(format), + } + } } impl super::PrivateDisabilities { diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index 3b94c71ed0f..e94c8dae434 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -247,7 +247,7 @@ impl crate::CommandEncoder for super::CommandEncoder { copy.texture_base.array_layer as u64, copy.texture_base.mip_level as u64, dst_origin, - mtl::MTLBlitOption::empty(), + conv::get_blit_option(dst.format, copy.texture_base.aspect), ); } } @@ -287,7 +287,7 @@ impl crate::CommandEncoder for super::CommandEncoder { copy.buffer_layout.offset, bytes_per_row, bytes_per_image, - mtl::MTLBlitOption::empty(), + conv::get_blit_option(src.format, copy.texture_base.aspect), ); } } diff --git a/wgpu-hal/src/metal/conv.rs b/wgpu-hal/src/metal/conv.rs index 75ed58df248..3ffffc31b88 100644 --- a/wgpu-hal/src/metal/conv.rs +++ b/wgpu-hal/src/metal/conv.rs @@ -1,4 +1,7 @@ -pub fn map_texture_usage(usage: crate::TextureUses) -> mtl::MTLTextureUsage { +pub fn map_texture_usage( + format: wgt::TextureFormat, + usage: crate::TextureUses, +) -> mtl::MTLTextureUsage { use crate::TextureUses as Tu; let mut mtl_usage = mtl::MTLTextureUsage::Unknown; @@ -17,6 +20,12 @@ pub fn map_texture_usage(usage: crate::TextureUses) -> mtl::MTLTextureUsage { mtl::MTLTextureUsage::ShaderWrite, usage.intersects(Tu::STORAGE_READ_WRITE), ); + // needed for combined depth/stencil formats since we might + // create a stencil-only view from them + mtl_usage.set( + mtl::MTLTextureUsage::PixelFormatView, + format.is_combined_depth_stencil_format(), + ); mtl_usage } @@ -298,3 +307,18 @@ pub fn map_clear_color(color: &wgt::Color) -> mtl::MTLClearColor { alpha: color.a, } } + +pub fn get_blit_option( + format: wgt::TextureFormat, + aspect: crate::FormatAspects, +) -> mtl::MTLBlitOption { + if format.is_combined_depth_stencil_format() { + match aspect { + crate::FormatAspects::DEPTH => mtl::MTLBlitOption::DepthFromDepthStencil, + crate::FormatAspects::STENCIL => mtl::MTLBlitOption::StencilFromDepthStencil, + _ => unreachable!(), + } + } else { + mtl::MTLBlitOption::None + } +} diff --git a/wgpu-hal/src/metal/device.rs b/wgpu-hal/src/metal/device.rs index fe9399c48e0..36165ca938f 100644 --- a/wgpu-hal/src/metal/device.rs +++ b/wgpu-hal/src/metal/device.rs @@ -203,7 +203,7 @@ impl super::Device { pub unsafe fn texture_from_raw( raw: mtl::Texture, - raw_format: mtl::MTLPixelFormat, + format: wgt::TextureFormat, raw_type: mtl::MTLTextureType, array_layers: u32, mip_levels: u32, @@ -211,7 +211,7 @@ impl super::Device { ) -> super::Texture { super::Texture { raw, - raw_format, + format, raw_type, array_layers, mip_levels, @@ -317,7 +317,7 @@ impl crate::Device for super::Device { descriptor.set_height(desc.size.height as u64); descriptor.set_mipmap_level_count(desc.mip_level_count as u64); descriptor.set_pixel_format(mtl_format); - descriptor.set_usage(conv::map_texture_usage(desc.usage)); + descriptor.set_usage(conv::map_texture_usage(desc.format, desc.usage)); descriptor.set_storage_mode(mtl::MTLStorageMode::Private); let raw = self.shared.device.lock().new_texture(&descriptor); @@ -327,7 +327,7 @@ impl crate::Device for super::Device { Ok(super::Texture { raw, - raw_format: mtl_format, + format: desc.format, raw_type: mtl_type, mip_levels: desc.mip_level_count, array_layers: desc.array_layer_count(), @@ -343,15 +343,20 @@ impl crate::Device for super::Device { texture: &super::Texture, desc: &crate::TextureViewDescriptor, ) -> DeviceResult { - let raw_format = self.shared.private_caps.map_format(desc.format); - let raw_type = if texture.raw_type == mtl::MTLTextureType::D2Multisample { texture.raw_type } else { conv::map_texture_view_dimension(desc.dimension) }; - let format_equal = raw_format == texture.raw_format; + let aspects = crate::FormatAspects::new(desc.format, desc.range.aspect); + + let raw_format = self + .shared + .private_caps + .map_view_format(desc.format, aspects); + + let format_equal = raw_format == self.shared.private_caps.map_format(texture.format); let type_equal = raw_type == texture.raw_type; let range_full_resource = desc .range @@ -391,7 +396,6 @@ impl crate::Device for super::Device { }) }; - let aspects = crate::FormatAspects::from(desc.format); Ok(super::TextureView { raw, aspects }) } unsafe fn destroy_texture_view(&self, _view: super::TextureView) {} diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index 4c17252bb9e..83aeb02de7a 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -301,7 +301,7 @@ pub struct Device { pub struct Surface { view: Option>, render_layer: Mutex, - raw_swapchain_format: mtl::MTLPixelFormat, + swapchain_format: Option, extent: wgt::Extent3d, main_thread_id: thread::ThreadId, // Useful for UI-intensive applications that are sensitive to @@ -425,7 +425,7 @@ impl Buffer { #[derive(Debug)] pub struct Texture { raw: mtl::Texture, - raw_format: mtl::MTLPixelFormat, + format: wgt::TextureFormat, raw_type: mtl::MTLTextureType, array_layers: u32, mip_levels: u32, diff --git a/wgpu-hal/src/metal/surface.rs b/wgpu-hal/src/metal/surface.rs index fffad30f033..c68e0d952a8 100644 --- a/wgpu-hal/src/metal/surface.rs +++ b/wgpu-hal/src/metal/surface.rs @@ -63,7 +63,7 @@ impl super::Surface { Self { view, render_layer: Mutex::new(layer), - raw_swapchain_format: mtl::MTLPixelFormat::Invalid, + swapchain_format: None, extent: wgt::Extent3d::default(), main_thread_id: thread::current().id(), present_with_transaction: false, @@ -178,7 +178,7 @@ impl crate::Surface for super::Surface { log::info!("build swapchain {:?}", config); let caps = &device.shared.private_caps; - self.raw_swapchain_format = caps.map_format(config.format); + self.swapchain_format = Some(config.format); self.extent = config.extent; let render_layer = self.render_layer.lock(); @@ -210,12 +210,12 @@ impl crate::Surface for super::Surface { } } render_layer.set_device(&device_raw); - render_layer.set_pixel_format(self.raw_swapchain_format); + render_layer.set_pixel_format(caps.map_format(config.format)); render_layer.set_framebuffer_only(framebuffer_only); render_layer.set_presents_with_transaction(self.present_with_transaction); // opt-in to Metal EDR // EDR potentially more power used in display and more bandwidth, memory footprint. - let wants_edr = self.raw_swapchain_format == mtl::MTLPixelFormat::RGBA16Float; + let wants_edr = config.format == wgt::TextureFormat::Rgba16Float; if wants_edr != render_layer.wants_extended_dynamic_range_content() { render_layer.set_wants_extended_dynamic_range_content(wants_edr); } @@ -234,7 +234,7 @@ impl crate::Surface for super::Surface { } unsafe fn unconfigure(&mut self, _device: &super::Device) { - self.raw_swapchain_format = mtl::MTLPixelFormat::Invalid; + self.swapchain_format = None; } unsafe fn acquire_texture( @@ -254,7 +254,7 @@ impl crate::Surface for super::Surface { let suf_texture = super::SurfaceTexture { texture: super::Texture { raw: texture, - raw_format: self.raw_swapchain_format, + format: self.swapchain_format.unwrap(), raw_type: mtl::MTLTextureType::D2, array_layers: 1, mip_levels: 1, diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 197e831ee3a..b835b7d3063 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -1487,7 +1487,7 @@ impl crate::Adapter for super::Adapter { features.intersects(vk::FormatFeatureFlags::TRANSFER_DST), ); // Vulkan is very permissive about MSAA - flags.set(Tfc::MULTISAMPLE_RESOLVE, !format.describe().is_compressed()); + flags.set(Tfc::MULTISAMPLE_RESOLVE, !format.is_compressed()); // get the supported sample counts let format_aspect = crate::FormatAspects::from(format); @@ -1502,7 +1502,7 @@ impl crate::Adapter for super::Adapter { .framebuffer_stencil_sample_counts .min(limits.sampled_image_stencil_sample_counts) } else { - match format.describe().sample_type { + match format.sample_type(None).unwrap() { wgt::TextureSampleType::Float { filterable: _ } => limits .framebuffer_color_sample_counts .min(limits.sampled_image_color_sample_counts), diff --git a/wgpu-hal/src/vulkan/command.rs b/wgpu-hal/src/vulkan/command.rs index d266cd1f47e..29c7d86473a 100644 --- a/wgpu-hal/src/vulkan/command.rs +++ b/wgpu-hal/src/vulkan/command.rs @@ -13,22 +13,24 @@ impl super::Texture { where T: Iterator, { - let aspects = self.aspects; - let fi = self.format_info; + let (block_width, block_height) = self.format.block_dimensions(); + let format = self.format; let copy_size = self.copy_size; regions.map(move |r| { let extent = r.texture_base.max_copy_size(©_size).min(&r.size); - let (image_subresource, image_offset) = - conv::map_subresource_layers(&r.texture_base, aspects); + let (image_subresource, image_offset) = conv::map_subresource_layers(&r.texture_base); vk::BufferImageCopy { buffer_offset: r.buffer_layout.offset, buffer_row_length: r.buffer_layout.bytes_per_row.map_or(0, |bpr| { - fi.block_dimensions.0 as u32 * (bpr.get() / fi.block_size as u32) + let block_size = format + .block_size(Some(r.texture_base.aspect.map())) + .unwrap(); + block_width * (bpr.get() / block_size) }), buffer_image_height: r .buffer_layout .rows_per_image - .map_or(0, |rpi| rpi.get() * fi.block_dimensions.1 as u32), + .map_or(0, |rpi| rpi.get() * block_height), image_subresource, image_offset, image_extent: conv::map_copy_extent(&extent), @@ -155,12 +157,12 @@ impl crate::CommandEncoder for super::CommandEncoder { vk_barriers.clear(); for bar in barriers { - let range = conv::map_subresource_range(&bar.range, bar.texture.aspects); + let range = conv::map_subresource_range(&bar.range, bar.texture.format); let (src_stage, src_access) = conv::map_texture_usage_to_barrier(bar.usage.start); - let src_layout = conv::derive_image_layout(bar.usage.start, bar.texture.aspects); + let src_layout = conv::derive_image_layout(bar.usage.start, bar.texture.format); src_stages |= src_stage; let (dst_stage, dst_access) = conv::map_texture_usage_to_barrier(bar.usage.end); - let dst_layout = conv::derive_image_layout(bar.usage.end, bar.texture.aspects); + let dst_layout = conv::derive_image_layout(bar.usage.end, bar.texture.format); dst_stages |= dst_stage; vk_barriers.push( @@ -235,13 +237,11 @@ impl crate::CommandEncoder for super::CommandEncoder { ) where T: Iterator, { - let src_layout = conv::derive_image_layout(src_usage, src.aspects); + let src_layout = conv::derive_image_layout(src_usage, src.format); let vk_regions_iter = regions.map(|r| { - let (src_subresource, src_offset) = - conv::map_subresource_layers(&r.src_base, src.aspects); - let (dst_subresource, dst_offset) = - conv::map_subresource_layers(&r.dst_base, dst.aspects); + let (src_subresource, src_offset) = conv::map_subresource_layers(&r.src_base); + let (dst_subresource, dst_offset) = conv::map_subresource_layers(&r.dst_base); let extent = r .size .min(&r.src_base.max_copy_size(&src.copy_size)) @@ -297,7 +297,7 @@ impl crate::CommandEncoder for super::CommandEncoder { ) where T: Iterator, { - let src_layout = conv::derive_image_layout(src_usage, src.aspects); + let src_layout = conv::derive_image_layout(src_usage, src.format); let vk_regions_iter = src.map_buffer_copies(regions); unsafe { @@ -820,7 +820,7 @@ impl crate::CommandEncoder for super::CommandEncoder { #[test] fn check_dst_image_layout() { assert_eq!( - conv::derive_image_layout(crate::TextureUses::COPY_DST, crate::FormatAspects::empty()), + conv::derive_image_layout(crate::TextureUses::COPY_DST, wgt::TextureFormat::Rgba8Unorm), DST_IMAGE_LAYOUT ); } diff --git a/wgpu-hal/src/vulkan/conv.rs b/wgpu-hal/src/vulkan/conv.rs index 8f6ea217ce3..a91479a835c 100644 --- a/wgpu-hal/src/vulkan/conv.rs +++ b/wgpu-hal/src/vulkan/conv.rs @@ -182,10 +182,9 @@ impl crate::Attachment<'_, super::Api> { ops: crate::AttachmentOps, caps: &super::PrivateCapabilities, ) -> super::AttachmentKey { - let aspects = self.view.aspects(); super::AttachmentKey { format: caps.map_texture_format(self.view.attachment.view_format), - layout: derive_image_layout(self.usage, aspects), + layout: derive_image_layout(self.usage, self.view.attachment.view_format), ops, } } @@ -199,8 +198,8 @@ impl crate::ColorAttachment<'_, super::Api> { .view .attachment .view_format - .describe() - .sample_type + .sample_type(None) + .unwrap() { wgt::TextureSampleType::Float { .. } => vk::ClearColorValue { float32: [cv.r as f32, cv.g as f32, cv.b as f32, cv.a as f32], @@ -218,10 +217,10 @@ impl crate::ColorAttachment<'_, super::Api> { pub fn derive_image_layout( usage: crate::TextureUses, - aspects: crate::FormatAspects, + format: wgt::TextureFormat, ) -> vk::ImageLayout { - //Note: depth textures are always sampled with RODS layout - let is_color = aspects.contains(crate::FormatAspects::COLOR); + // Note: depth textures are always sampled with RODS layout + let is_color = crate::FormatAspects::from(format).contains(crate::FormatAspects::COLOR); match usage { crate::TextureUses::UNINITIALIZED => vk::ImageLayout::UNDEFINED, crate::TextureUses::COPY_SRC => vk::ImageLayout::TRANSFER_SRC_OPTIMAL, @@ -586,10 +585,10 @@ pub fn map_copy_extent(extent: &crate::CopyExtent) -> vk::Extent3D { pub fn map_subresource_range( range: &wgt::ImageSubresourceRange, - texture_aspect: crate::FormatAspects, + format: wgt::TextureFormat, ) -> vk::ImageSubresourceRange { vk::ImageSubresourceRange { - aspect_mask: map_aspects(crate::FormatAspects::from(range.aspect) & texture_aspect), + aspect_mask: map_aspects(crate::FormatAspects::new(format, range.aspect)), base_mip_level: range.base_mip_level, level_count: range.mip_level_count.unwrap_or(vk::REMAINING_MIP_LEVELS), base_array_layer: range.base_array_layer, @@ -601,7 +600,6 @@ pub fn map_subresource_range( pub fn map_subresource_layers( base: &crate::TextureCopyBase, - texture_aspect: crate::FormatAspects, ) -> (vk::ImageSubresourceLayers, vk::Offset3D) { let offset = vk::Offset3D { x: base.origin.x as i32, @@ -609,7 +607,7 @@ pub fn map_subresource_layers( z: base.origin.z as i32, }; let subresource = vk::ImageSubresourceLayers { - aspect_mask: map_aspects(base.aspect & texture_aspect), + aspect_mask: map_aspects(base.aspect), mip_level: base.mip_level, base_array_layer: base.array_layer, layer_count: 1, diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index e2372b5b49a..1d10d69b0a0 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -673,8 +673,7 @@ impl super::Device { drop_guard, block: None, usage: desc.usage, - aspects: crate::FormatAspects::from(desc.format), - format_info: desc.format.describe(), + format: desc.format, raw_flags: vk::ImageCreateFlags::empty(), copy_size: desc.copy_extent(), view_formats, @@ -1015,8 +1014,7 @@ impl crate::Device for super::Device { drop_guard: None, block: Some(block), usage: desc.usage, - aspects: crate::FormatAspects::from(desc.format), - format_info: desc.format.describe(), + format: desc.format, raw_flags, copy_size, view_formats: wgt_view_formats, @@ -1036,7 +1034,7 @@ impl crate::Device for super::Device { texture: &super::Texture, desc: &crate::TextureViewDescriptor, ) -> Result { - let subresource_range = conv::map_subresource_range(&desc.range, texture.aspects); + let subresource_range = conv::map_subresource_range(&desc.range, desc.format); let mut vk_info = vk::ImageViewCreateInfo::builder() .flags(vk::ImageViewCreateFlags::empty()) .image(texture.raw) @@ -1444,8 +1442,10 @@ impl crate::Device for super::Device { let end = start + entry.count; image_infos.extend(desc.textures[start as usize..end as usize].iter().map( |binding| { - let layout = - conv::derive_image_layout(binding.usage, binding.view.aspects()); + let layout = conv::derive_image_layout( + binding.usage, + binding.view.attachment.view_format, + ); vk::DescriptorImageInfo::builder() .image_view(binding.view.raw) .image_layout(layout) diff --git a/wgpu-hal/src/vulkan/instance.rs b/wgpu-hal/src/vulkan/instance.rs index 4d8bfdd8614..186334e08fb 100644 --- a/wgpu-hal/src/vulkan/instance.rs +++ b/wgpu-hal/src/vulkan/instance.rs @@ -799,8 +799,7 @@ impl crate::Surface for super::Surface { drop_guard: None, block: None, usage: sc.config.usage, - aspects: crate::FormatAspects::COLOR, - format_info: sc.config.format.describe(), + format: sc.config.format, raw_flags, copy_size: crate::CopyExtent { width: sc.config.extent.width, diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index fff9655490a..ee30224b07d 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -293,8 +293,7 @@ pub struct Texture { drop_guard: Option, block: Option>, usage: crate::TextureUses, - aspects: crate::FormatAspects, - format_info: wgt::TextureFormatInfo, + format: wgt::TextureFormat, raw_flags: vk::ImageCreateFlags, copy_size: crate::CopyExtent, view_formats: Vec, @@ -316,12 +315,6 @@ pub struct TextureView { attachment: FramebufferAttachment, } -impl TextureView { - fn aspects(&self) -> crate::FormatAspects { - self.attachment.view_format.into() - } -} - #[derive(Debug)] pub struct Sampler { raw: vk::Sampler, diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index d29de1a71a5..83af7e2ba88 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -1763,32 +1763,6 @@ pub struct TextureFormatFeatures { pub flags: TextureFormatFeatureFlags, } -/// Information about a texture format. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub struct TextureFormatInfo { - /// Features required (if any) to use the texture. - pub required_features: Features, - /// Type of sampling that is valid for the texture. - pub sample_type: TextureSampleType, - /// Dimension of a "block" of texels. This is always (1, 1) on uncompressed textures. - pub block_dimensions: (u8, u8), - /// Size in bytes of a "block" of texels. This is the size per pixel on uncompressed textures. - pub block_size: u8, - /// Count of components in the texture. This determines which components there will be actual data in the shader for. - pub components: u8, - /// Format will have colors be converted from srgb to linear on read and from linear to srgb on write. - pub srgb: bool, - /// Format features guaranteed by the WebGPU spec. Additional features are available if `Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES` is enabled. - pub guaranteed_format_features: TextureFormatFeatures, -} - -impl TextureFormatInfo { - /// Return `true` for compressed formats. - pub fn is_compressed(&self) -> bool { - self.block_dimensions != (1, 1) - } -} - /// ASTC block dimensions #[repr(C)] #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] @@ -2387,36 +2361,286 @@ impl Serialize for TextureFormat { } impl TextureFormat { - /// Get useful information about the texture format. - pub fn describe(&self) -> TextureFormatInfo { - // Features - let native = Features::empty(); - let bc = Features::TEXTURE_COMPRESSION_BC; - let etc2 = Features::TEXTURE_COMPRESSION_ETC2; - let astc_ldr = Features::TEXTURE_COMPRESSION_ASTC_LDR; - let astc_hdr = Features::TEXTURE_COMPRESSION_ASTC_HDR; - let norm16bit = Features::TEXTURE_FORMAT_16BIT_NORM; - let d32_s8 = Features::DEPTH32FLOAT_STENCIL8; - - // Sample Types - let uint = TextureSampleType::Uint; - let sint = TextureSampleType::Sint; - let nearest = TextureSampleType::Float { filterable: false }; - let float = TextureSampleType::Float { filterable: true }; - let depth = TextureSampleType::Depth; + /// Returns the aspect-specific format of the original format + /// + /// see https://gpuweb.github.io/gpuweb/#abstract-opdef-resolving-gputextureaspect + pub fn aspect_specific_format(&self, aspect: TextureAspect) -> Option { + match (*self, aspect) { + (Self::Stencil8, TextureAspect::StencilOnly) => Some(*self), + ( + Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float, + TextureAspect::DepthOnly, + ) => Some(*self), + ( + Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8, + TextureAspect::StencilOnly, + ) => Some(Self::Stencil8), + (Self::Depth24PlusStencil8, TextureAspect::DepthOnly) => Some(Self::Depth24Plus), + (Self::Depth32FloatStencil8, TextureAspect::DepthOnly) => Some(Self::Depth32Float), + (format, TextureAspect::All) => Some(format), + _ => None, + } + } + + /// Returns `true` if `self` is a depth or stencil component of the given + /// combined depth-stencil format + pub fn is_depth_stencil_component(&self, combined_format: Self) -> bool { + match (combined_format, *self) { + (Self::Depth24PlusStencil8, Self::Depth24Plus | Self::Stencil8) + | (Self::Depth32FloatStencil8, Self::Depth32Float | Self::Stencil8) => true, + _ => false, + } + } + + /// Returns `true` if the format is a depth and/or stencil format + /// + /// see https://gpuweb.github.io/gpuweb/#depth-formats + pub fn is_depth_stencil_format(&self) -> bool { + match *self { + Self::Stencil8 + | Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float + | Self::Depth32FloatStencil8 => true, + _ => false, + } + } + + /// Returns `true` if the format is a combined depth-stencil format + /// + /// see https://gpuweb.github.io/gpuweb/#combined-depth-stencil-format + pub fn is_combined_depth_stencil_format(&self) -> bool { + match *self { + Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => true, + _ => false, + } + } + + /// Returns `true` if the format has a color aspect + pub fn has_color_aspect(&self) -> bool { + !self.is_depth_stencil_format() + } + + /// Returns `true` if the format has a depth aspect + pub fn has_depth_aspect(&self) -> bool { + match *self { + Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float + | Self::Depth32FloatStencil8 => true, + _ => false, + } + } + + /// Returns `true` if the format has a stencil aspect + pub fn has_stencil_aspect(&self) -> bool { + match *self { + Self::Stencil8 | Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => true, + _ => false, + } + } + + /// Returns the dimension of a block of texels. + pub fn block_dimensions(&self) -> (u32, u32) { + match *self { + Self::R8Unorm + | Self::R8Snorm + | Self::R8Uint + | Self::R8Sint + | Self::R16Uint + | Self::R16Sint + | Self::R16Unorm + | Self::R16Snorm + | Self::R16Float + | Self::Rg8Unorm + | Self::Rg8Snorm + | Self::Rg8Uint + | Self::Rg8Sint + | Self::R32Uint + | Self::R32Sint + | Self::R32Float + | Self::Rg16Uint + | Self::Rg16Sint + | Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rg16Float + | Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Rgba8Uint + | Self::Rgba8Sint + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb + | Self::Rgb9e5Ufloat + | Self::Rgb10a2Unorm + | Self::Rg11b10Float + | Self::Rg32Uint + | Self::Rg32Sint + | Self::Rg32Float + | Self::Rgba16Uint + | Self::Rgba16Sint + | Self::Rgba16Unorm + | Self::Rgba16Snorm + | Self::Rgba16Float + | Self::Rgba32Uint + | Self::Rgba32Sint + | Self::Rgba32Float + | Self::Stencil8 + | Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float + | Self::Depth32FloatStencil8 => (1, 1), + + Self::Bc1RgbaUnorm + | Self::Bc1RgbaUnormSrgb + | Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc4RUnorm + | Self::Bc4RSnorm + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbSfloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb => (4, 4), + + Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm + | Self::EacRg11Unorm + | Self::EacRg11Snorm => (4, 4), + + Self::Astc { block, .. } => match block { + AstcBlock::B4x4 => (4, 4), + AstcBlock::B5x4 => (5, 4), + AstcBlock::B5x5 => (5, 5), + AstcBlock::B6x5 => (6, 5), + AstcBlock::B6x6 => (6, 6), + AstcBlock::B8x5 => (8, 5), + AstcBlock::B8x6 => (8, 6), + AstcBlock::B8x8 => (8, 8), + AstcBlock::B10x5 => (10, 5), + AstcBlock::B10x6 => (10, 6), + AstcBlock::B10x8 => (10, 8), + AstcBlock::B10x10 => (10, 10), + AstcBlock::B12x10 => (12, 10), + AstcBlock::B12x12 => (12, 12), + }, + } + } + + /// Returns `true` for compressed formats. + pub fn is_compressed(&self) -> bool { + self.block_dimensions() != (1, 1) + } - enum ColorSpace { - Linear, - Corrected, + /// Returns the required features (if any) in order to use the texture. + pub fn required_features(&self) -> Features { + match *self { + Self::R8Unorm + | Self::R8Snorm + | Self::R8Uint + | Self::R8Sint + | Self::R16Uint + | Self::R16Sint + | Self::R16Float + | Self::Rg8Unorm + | Self::Rg8Snorm + | Self::Rg8Uint + | Self::Rg8Sint + | Self::R32Uint + | Self::R32Sint + | Self::R32Float + | Self::Rg16Uint + | Self::Rg16Sint + | Self::Rg16Float + | Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Rgba8Uint + | Self::Rgba8Sint + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb + | Self::Rgb9e5Ufloat + | Self::Rgb10a2Unorm + | Self::Rg11b10Float + | Self::Rg32Uint + | Self::Rg32Sint + | Self::Rg32Float + | Self::Rgba16Uint + | Self::Rgba16Sint + | Self::Rgba16Float + | Self::Rgba32Uint + | Self::Rgba32Sint + | Self::Rgba32Float + | Self::Stencil8 + | Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float => Features::empty(), + + Self::Depth32FloatStencil8 => Features::DEPTH32FLOAT_STENCIL8, + + Self::R16Unorm + | Self::R16Snorm + | Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rgba16Unorm + | Self::Rgba16Snorm => Features::TEXTURE_FORMAT_16BIT_NORM, + + Self::Bc1RgbaUnorm + | Self::Bc1RgbaUnormSrgb + | Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc4RUnorm + | Self::Bc4RSnorm + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbSfloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb => Features::TEXTURE_COMPRESSION_BC, + + Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm + | Self::EacRg11Unorm + | Self::EacRg11Snorm => Features::TEXTURE_COMPRESSION_ETC2, + + Self::Astc { channel, .. } => match channel { + AstcChannel::Hdr => Features::TEXTURE_COMPRESSION_ASTC_HDR, + AstcChannel::Unorm | AstcChannel::UnormSrgb => { + Features::TEXTURE_COMPRESSION_ASTC_LDR + } + }, } - let linear = ColorSpace::Linear; - let corrected = ColorSpace::Corrected; + } + /// Returns the format features guaranteed by the WebGPU spec. + /// + /// Additional features are available if `Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES` is enabled. + pub fn guaranteed_format_features(&self) -> TextureFormatFeatures { // Multisampling let noaa = TextureFormatFeatureFlags::empty(); let msaa = TextureFormatFeatureFlags::MULTISAMPLE_X4; - let msaa_resolve = TextureFormatFeatureFlags::MULTISAMPLE_X4 - | TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE; + let msaa_resolve = msaa | TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE; // Flags let basic = @@ -2425,150 +2649,280 @@ impl TextureFormat { let storage = basic | TextureUsages::STORAGE_BINDING; let all_flags = TextureUsages::all(); - // See for reference #[rustfmt::skip] // lets make a nice table let ( - required_features, - sample_type, - color_space, - msaa_flags, - block_dimensions, - block_size, + mut flags, allowed_usages, - components, - ) = match self { - // Normal 8 bit textures - Self::R8Unorm => ( native, float, linear, msaa_resolve, (1, 1), 1, attachment, 1), - Self::R8Snorm => ( native, float, linear, noaa, (1, 1), 1, basic, 1), - Self::R8Uint => ( native, uint, linear, msaa, (1, 1), 1, attachment, 1), - Self::R8Sint => ( native, sint, linear, msaa, (1, 1), 1, attachment, 1), - // Normal 16 bit textures - Self::R16Uint => ( native, uint, linear, msaa, (1, 1), 2, attachment, 1), - Self::R16Sint => ( native, sint, linear, msaa, (1, 1), 2, attachment, 1), - Self::R16Float => ( native, float, linear, msaa_resolve, (1, 1), 2, attachment, 1), - Self::Rg8Unorm => ( native, float, linear, msaa_resolve, (1, 1), 2, attachment, 2), - Self::Rg8Snorm => ( native, float, linear, noaa, (1, 1), 2, basic, 2), - Self::Rg8Uint => ( native, uint, linear, msaa, (1, 1), 2, attachment, 2), - Self::Rg8Sint => ( native, sint, linear, msaa, (1, 1), 2, attachment, 2), - // Normal 32 bit textures - Self::R32Uint => ( native, uint, linear, noaa, (1, 1), 4, all_flags, 1), - Self::R32Sint => ( native, sint, linear, noaa, (1, 1), 4, all_flags, 1), - Self::R32Float => ( native, nearest, linear, msaa, (1, 1), 4, all_flags, 1), - Self::Rg16Uint => ( native, uint, linear, msaa, (1, 1), 4, attachment, 2), - Self::Rg16Sint => ( native, sint, linear, msaa, (1, 1), 4, attachment, 2), - Self::Rg16Float => ( native, float, linear, msaa_resolve, (1, 1), 4, attachment, 2), - Self::Rgba8Unorm => ( native, float, linear, msaa_resolve, (1, 1), 4, all_flags, 4), - Self::Rgba8UnormSrgb => ( native, float, corrected, msaa_resolve, (1, 1), 4, attachment, 4), - Self::Rgba8Snorm => ( native, float, linear, noaa, (1, 1), 4, storage, 4), - Self::Rgba8Uint => ( native, uint, linear, msaa, (1, 1), 4, all_flags, 4), - Self::Rgba8Sint => ( native, sint, linear, msaa, (1, 1), 4, all_flags, 4), - Self::Bgra8Unorm => ( native, float, linear, msaa_resolve, (1, 1), 4, attachment, 4), - Self::Bgra8UnormSrgb => ( native, float, corrected, msaa_resolve, (1, 1), 4, attachment, 4), - // Packed 32 bit textures - Self::Rgb10a2Unorm => ( native, float, linear, msaa_resolve, (1, 1), 4, attachment, 4), - Self::Rg11b10Float => ( native, float, linear, msaa, (1, 1), 4, basic, 3), - // Normal 64 bit textures - Self::Rg32Uint => ( native, uint, linear, noaa, (1, 1), 8, all_flags, 2), - Self::Rg32Sint => ( native, sint, linear, noaa, (1, 1), 8, all_flags, 2), - Self::Rg32Float => ( native, nearest, linear, noaa, (1, 1), 8, all_flags, 2), - Self::Rgba16Uint => ( native, uint, linear, msaa, (1, 1), 8, all_flags, 4), - Self::Rgba16Sint => ( native, sint, linear, msaa, (1, 1), 8, all_flags, 4), - Self::Rgba16Float => ( native, float, linear, msaa_resolve, (1, 1), 8, all_flags, 4), - // Normal 128 bit textures - Self::Rgba32Uint => ( native, uint, linear, noaa, (1, 1), 16, all_flags, 4), - Self::Rgba32Sint => ( native, sint, linear, noaa, (1, 1), 16, all_flags, 4), - Self::Rgba32Float => ( native, nearest, linear, noaa, (1, 1), 16, all_flags, 4), - // Depth-stencil textures - Self::Stencil8 => ( native, depth, linear, msaa, (1, 1), 2, attachment, 1), - Self::Depth16Unorm => ( native, depth, linear, msaa, (1, 1), 2, attachment, 1), - Self::Depth24Plus => ( native, depth, linear, msaa, (1, 1), 4, attachment, 1), - Self::Depth24PlusStencil8 => ( native, depth, linear, msaa, (1, 1), 4, attachment, 2), - Self::Depth32Float => ( native, depth, linear, msaa, (1, 1), 4, attachment, 1), - Self::Depth32FloatStencil8 =>( d32_s8, depth, linear, msaa, (1, 1), 4, attachment, 2), - // Packed uncompressed - Self::Rgb9e5Ufloat => ( native, float, linear, noaa, (1, 1), 4, basic, 3), - // Optional normalized 16-bit-per-channel formats - Self::R16Unorm => (norm16bit, float, linear, msaa, (1, 1), 2, storage, 1), - Self::R16Snorm => (norm16bit, float, linear, msaa, (1, 1), 2, storage, 1), - Self::Rg16Unorm => (norm16bit, float, linear, msaa, (1, 1), 4, storage, 2), - Self::Rg16Snorm => (norm16bit, float, linear, msaa, (1, 1), 4, storage, 2), - Self::Rgba16Unorm => (norm16bit, float, linear, msaa, (1, 1), 8, storage, 4), - Self::Rgba16Snorm => (norm16bit, float, linear, msaa, (1, 1), 8, storage, 4), - // BCn compressed textures - Self::Bc1RgbaUnorm => ( bc, float, linear, noaa, (4, 4), 8, basic, 4), - Self::Bc1RgbaUnormSrgb => ( bc, float, corrected, noaa, (4, 4), 8, basic, 4), - Self::Bc2RgbaUnorm => ( bc, float, linear, noaa, (4, 4), 16, basic, 4), - Self::Bc2RgbaUnormSrgb => ( bc, float, corrected, noaa, (4, 4), 16, basic, 4), - Self::Bc3RgbaUnorm => ( bc, float, linear, noaa, (4, 4), 16, basic, 4), - Self::Bc3RgbaUnormSrgb => ( bc, float, corrected, noaa, (4, 4), 16, basic, 4), - Self::Bc4RUnorm => ( bc, float, linear, noaa, (4, 4), 8, basic, 1), - Self::Bc4RSnorm => ( bc, float, linear, noaa, (4, 4), 8, basic, 1), - Self::Bc5RgUnorm => ( bc, float, linear, noaa, (4, 4), 16, basic, 2), - Self::Bc5RgSnorm => ( bc, float, linear, noaa, (4, 4), 16, basic, 2), - Self::Bc6hRgbUfloat => ( bc, float, linear, noaa, (4, 4), 16, basic, 3), - Self::Bc6hRgbSfloat => ( bc, float, linear, noaa, (4, 4), 16, basic, 3), - Self::Bc7RgbaUnorm => ( bc, float, linear, noaa, (4, 4), 16, basic, 4), - Self::Bc7RgbaUnormSrgb => ( bc, float, corrected, noaa, (4, 4), 16, basic, 4), - // ETC compressed textures - Self::Etc2Rgb8Unorm => ( etc2, float, linear, noaa, (4, 4), 8, basic, 3), - Self::Etc2Rgb8UnormSrgb => ( etc2, float, corrected, noaa, (4, 4), 8, basic, 3), - Self::Etc2Rgb8A1Unorm => ( etc2, float, linear, noaa, (4, 4), 8, basic, 4), - Self::Etc2Rgb8A1UnormSrgb => ( etc2, float, corrected, noaa, (4, 4), 8, basic, 4), - Self::Etc2Rgba8Unorm => ( etc2, float, linear, noaa, (4, 4), 16, basic, 4), - Self::Etc2Rgba8UnormSrgb => ( etc2, float, corrected, noaa, (4, 4), 16, basic, 4), - Self::EacR11Unorm => ( etc2, float, linear, noaa, (4, 4), 8, basic, 1), - Self::EacR11Snorm => ( etc2, float, linear, noaa, (4, 4), 8, basic, 1), - Self::EacRg11Unorm => ( etc2, float, linear, noaa, (4, 4), 16, basic, 2), - Self::EacRg11Snorm => ( etc2, float, linear, noaa, (4, 4), 16, basic, 2), - // ASTC compressed textures - Self::Astc { block, channel } => { - let (feature, color_space) = match channel { - AstcChannel::Hdr => (astc_hdr, linear), - AstcChannel::Unorm => (astc_ldr, linear), - AstcChannel::UnormSrgb => (astc_ldr, corrected), - }; - let dimensions = match block { - AstcBlock::B4x4 => (4, 4), - AstcBlock::B5x4 => (5, 4), - AstcBlock::B5x5 => (5, 5), - AstcBlock::B6x5 => (6, 5), - AstcBlock::B6x6 => (6, 6), - AstcBlock::B8x5 => (8, 5), - AstcBlock::B8x6 => (8, 6), - AstcBlock::B8x8 => (8, 8), - AstcBlock::B10x5 => (10, 5), - AstcBlock::B10x6 => (10, 6), - AstcBlock::B10x8 => (10, 8), - AstcBlock::B10x10 => (10, 10), - AstcBlock::B12x10 => (12, 10), - AstcBlock::B12x12 => (12, 12), - }; - (feature, float, color_space, noaa, dimensions, 16, basic, 4) - } + ) = match *self { + Self::R8Unorm => (msaa_resolve, attachment), + Self::R8Snorm => ( noaa, basic), + Self::R8Uint => ( msaa, attachment), + Self::R8Sint => ( msaa, attachment), + Self::R16Uint => ( msaa, attachment), + Self::R16Sint => ( msaa, attachment), + Self::R16Float => (msaa_resolve, attachment), + Self::Rg8Unorm => (msaa_resolve, attachment), + Self::Rg8Snorm => ( noaa, basic), + Self::Rg8Uint => ( msaa, attachment), + Self::Rg8Sint => ( msaa, attachment), + Self::R32Uint => ( noaa, all_flags), + Self::R32Sint => ( noaa, all_flags), + Self::R32Float => ( msaa, all_flags), + Self::Rg16Uint => ( msaa, attachment), + Self::Rg16Sint => ( msaa, attachment), + Self::Rg16Float => (msaa_resolve, attachment), + Self::Rgba8Unorm => (msaa_resolve, all_flags), + Self::Rgba8UnormSrgb => (msaa_resolve, attachment), + Self::Rgba8Snorm => ( noaa, storage), + Self::Rgba8Uint => ( msaa, all_flags), + Self::Rgba8Sint => ( msaa, all_flags), + Self::Bgra8Unorm => (msaa_resolve, attachment), + Self::Bgra8UnormSrgb => (msaa_resolve, attachment), + Self::Rgb10a2Unorm => (msaa_resolve, attachment), + Self::Rg11b10Float => ( msaa, basic), + Self::Rg32Uint => ( noaa, all_flags), + Self::Rg32Sint => ( noaa, all_flags), + Self::Rg32Float => ( noaa, all_flags), + Self::Rgba16Uint => ( msaa, all_flags), + Self::Rgba16Sint => ( msaa, all_flags), + Self::Rgba16Float => (msaa_resolve, all_flags), + Self::Rgba32Uint => ( noaa, all_flags), + Self::Rgba32Sint => ( noaa, all_flags), + Self::Rgba32Float => ( noaa, all_flags), + + Self::Stencil8 => ( msaa, attachment), + Self::Depth16Unorm => ( msaa, attachment), + Self::Depth24Plus => ( msaa, attachment), + Self::Depth24PlusStencil8 => ( msaa, attachment), + Self::Depth32Float => ( msaa, attachment), + Self::Depth32FloatStencil8 => ( msaa, attachment), + + Self::R16Unorm => ( msaa, storage), + Self::R16Snorm => ( msaa, storage), + Self::Rg16Unorm => ( msaa, storage), + Self::Rg16Snorm => ( msaa, storage), + Self::Rgba16Unorm => ( msaa, storage), + Self::Rgba16Snorm => ( msaa, storage), + + Self::Rgb9e5Ufloat => ( noaa, basic), + + Self::Bc1RgbaUnorm => ( noaa, basic), + Self::Bc1RgbaUnormSrgb => ( noaa, basic), + Self::Bc2RgbaUnorm => ( noaa, basic), + Self::Bc2RgbaUnormSrgb => ( noaa, basic), + Self::Bc3RgbaUnorm => ( noaa, basic), + Self::Bc3RgbaUnormSrgb => ( noaa, basic), + Self::Bc4RUnorm => ( noaa, basic), + Self::Bc4RSnorm => ( noaa, basic), + Self::Bc5RgUnorm => ( noaa, basic), + Self::Bc5RgSnorm => ( noaa, basic), + Self::Bc6hRgbUfloat => ( noaa, basic), + Self::Bc6hRgbSfloat => ( noaa, basic), + Self::Bc7RgbaUnorm => ( noaa, basic), + Self::Bc7RgbaUnormSrgb => ( noaa, basic), + + Self::Etc2Rgb8Unorm => ( noaa, basic), + Self::Etc2Rgb8UnormSrgb => ( noaa, basic), + Self::Etc2Rgb8A1Unorm => ( noaa, basic), + Self::Etc2Rgb8A1UnormSrgb => ( noaa, basic), + Self::Etc2Rgba8Unorm => ( noaa, basic), + Self::Etc2Rgba8UnormSrgb => ( noaa, basic), + Self::EacR11Unorm => ( noaa, basic), + Self::EacR11Snorm => ( noaa, basic), + Self::EacRg11Unorm => ( noaa, basic), + Self::EacRg11Snorm => ( noaa, basic), + + Self::Astc { .. } => ( noaa, basic), }; - let mut flags = msaa_flags; - let filterable_sample_type = sample_type == TextureSampleType::Float { filterable: true }; - flags.set( - TextureFormatFeatureFlags::FILTERABLE, - filterable_sample_type, - ); - flags.set(TextureFormatFeatureFlags::BLENDABLE, filterable_sample_type); - - TextureFormatInfo { - required_features, - sample_type, - block_dimensions, - block_size, - components, - srgb: match color_space { - ColorSpace::Linear => false, - ColorSpace::Corrected => true, + let is_filterable = + self.sample_type(None) == Some(TextureSampleType::Float { filterable: true }); + flags.set(TextureFormatFeatureFlags::FILTERABLE, is_filterable); + flags.set(TextureFormatFeatureFlags::BLENDABLE, is_filterable); + + TextureFormatFeatures { + allowed_usages, + flags, + } + } + + /// Returns the sample type compatible with this format and aspect + /// + /// Returns `None` only if the format is combined depth-stencil + /// and `TextureAspect::All` or no `aspect` was provided + pub fn sample_type(&self, aspect: Option) -> Option { + let float = TextureSampleType::Float { filterable: true }; + let unfilterable_float = TextureSampleType::Float { filterable: false }; + let depth = TextureSampleType::Depth; + let uint = TextureSampleType::Uint; + let sint = TextureSampleType::Sint; + + match *self { + Self::R8Unorm + | Self::R8Snorm + | Self::Rg8Unorm + | Self::Rg8Snorm + | Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb + | Self::R16Float + | Self::Rg16Float + | Self::Rgba16Float + | Self::Rgb10a2Unorm + | Self::Rg11b10Float => Some(float), + + Self::R32Float | Self::Rg32Float | Self::Rgba32Float => Some(unfilterable_float), + + Self::R8Uint + | Self::Rg8Uint + | Self::Rgba8Uint + | Self::R16Uint + | Self::Rg16Uint + | Self::Rgba16Uint + | Self::R32Uint + | Self::Rg32Uint + | Self::Rgba32Uint => Some(uint), + + Self::R8Sint + | Self::Rg8Sint + | Self::Rgba8Sint + | Self::R16Sint + | Self::Rg16Sint + | Self::Rgba16Sint + | Self::R32Sint + | Self::Rg32Sint + | Self::Rgba32Sint => Some(sint), + + Self::Stencil8 => Some(uint), + Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float => Some(depth), + Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => match aspect { + None | Some(TextureAspect::All) => None, + Some(TextureAspect::DepthOnly) => Some(depth), + Some(TextureAspect::StencilOnly) => Some(uint), }, - guaranteed_format_features: TextureFormatFeatures { - allowed_usages, - flags, + + Self::R16Unorm + | Self::R16Snorm + | Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rgba16Unorm + | Self::Rgba16Snorm => Some(float), + + Self::Rgb9e5Ufloat => Some(float), + + Self::Bc1RgbaUnorm + | Self::Bc1RgbaUnormSrgb + | Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc4RUnorm + | Self::Bc4RSnorm + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbSfloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb => Some(float), + + Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm + | Self::EacRg11Unorm + | Self::EacRg11Snorm => Some(float), + + Self::Astc { .. } => Some(float), + } + } + + /// Returns the [texel block size](https://gpuweb.github.io/gpuweb/#texel-block-size) + /// of this format. + /// + /// Returns `None` if any of the following are true: + /// - the format is combined depth-stencil and no `aspect` was provided + /// - the format is `Depth24Plus` + /// - the format is `Depth24PlusStencil8` and `aspect` is depth. + pub fn block_size(&self, aspect: Option) -> Option { + match *self { + Self::R8Unorm | Self::R8Snorm | Self::R8Uint | Self::R8Sint => Some(1), + + Self::Rg8Unorm | Self::Rg8Snorm | Self::Rg8Uint | Self::Rg8Sint => Some(2), + Self::R16Unorm | Self::R16Snorm | Self::R16Uint | Self::R16Sint | Self::R16Float => { + Some(2) + } + + Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Rgba8Uint + | Self::Rgba8Sint + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb => Some(4), + Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rg16Uint + | Self::Rg16Sint + | Self::Rg16Float => Some(4), + Self::R32Uint | Self::R32Sint | Self::R32Float => Some(4), + Self::Rgb9e5Ufloat | Self::Rgb10a2Unorm | Self::Rg11b10Float => Some(4), + + Self::Rgba16Unorm + | Self::Rgba16Snorm + | Self::Rgba16Uint + | Self::Rgba16Sint + | Self::Rgba16Float => Some(8), + Self::Rg32Uint | Self::Rg32Sint | Self::Rg32Float => Some(8), + + Self::Rgba32Uint | Self::Rgba32Sint | Self::Rgba32Float => Some(16), + + Self::Stencil8 => Some(1), + Self::Depth16Unorm => Some(2), + Self::Depth32Float => Some(4), + Self::Depth24Plus => None, + Self::Depth24PlusStencil8 => match aspect { + None | Some(TextureAspect::All) => None, + Some(TextureAspect::DepthOnly) => None, + Some(TextureAspect::StencilOnly) => Some(1), }, + Self::Depth32FloatStencil8 => match aspect { + None | Some(TextureAspect::All) => None, + Some(TextureAspect::DepthOnly) => Some(4), + Some(TextureAspect::StencilOnly) => Some(1), + }, + + Self::Bc1RgbaUnorm | Self::Bc1RgbaUnormSrgb | Self::Bc4RUnorm | Self::Bc4RSnorm => { + Some(8) + } + Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbSfloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb => Some(16), + + Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm => Some(8), + Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacRg11Unorm + | Self::EacRg11Snorm => Some(16), + + Self::Astc { .. } => Some(16), } } @@ -2617,6 +2971,11 @@ impl TextureFormat { _ => *self, } } + + /// Returns `true` for srgb formats. + pub fn is_srgb(&self) -> bool { + *self != self.remove_srgb_suffix() + } } #[test] @@ -4321,9 +4680,7 @@ impl Extent3d { /// /// [physical size]: https://gpuweb.github.io/gpuweb/#physical-miplevel-specific-texture-extent pub fn physical_size(&self, format: TextureFormat) -> Self { - let (block_width, block_height) = format.describe().block_dimensions; - let block_width = block_width as u32; - let block_height = block_height as u32; + let (block_width, block_height) = format.block_dimensions(); let width = ((self.width + block_width - 1) / block_width) * block_width; let height = ((self.height + block_height - 1) / block_height) * block_height; @@ -4899,12 +5256,17 @@ pub enum TextureSampleType { /// uniform texture2D t; /// ``` Float { - /// If `filterable` is false, the texture can't be sampled with + /// If this is `false`, the texture can't be sampled with /// a filtering sampler. + /// + /// Even if this is `true`, it's possible to sample with + /// a **non-filtering** sampler. filterable: bool, }, /// Sampling does the depth reference comparison. /// + /// This is also compatible with a non-filtering sampler. + /// /// Example WGSL syntax: /// ```rust,ignore /// @group(0) @binding(0) diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index f3b9383610c..416bedb5d93 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -1008,7 +1008,7 @@ impl crate::context::Context for Context { _adapter_data: &Self::AdapterData, format: wgt::TextureFormat, ) -> wgt::TextureFormatFeatures { - format.describe().guaranteed_format_features + format.guaranteed_format_features() } fn adapter_get_presentation_timestamp( diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 9fd36fa2c7f..f747ae25017 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -2769,12 +2769,6 @@ impl CommandEncoder { } /// Copy data from a buffer to a texture. - /// - /// # Panics - /// - /// - Copy would overrun buffer. - /// - Copy would overrun texture. - /// - `source.layout.bytes_per_row` isn't divisible by [`COPY_BYTES_PER_ROW_ALIGNMENT`]. pub fn copy_buffer_to_texture( &mut self, source: ImageCopyBuffer, @@ -2792,12 +2786,6 @@ impl CommandEncoder { } /// Copy data from a texture to a buffer. - /// - /// # Panics - /// - /// - Copy would overrun buffer. - /// - Copy would overrun texture. - /// - `source.layout.bytes_per_row` isn't divisible by [`COPY_BYTES_PER_ROW_ALIGNMENT`]. pub fn copy_texture_to_buffer( &mut self, source: ImageCopyTexture, diff --git a/wgpu/src/util/device.rs b/wgpu/src/util/device.rs index b441041a715..01dcaf17170 100644 --- a/wgpu/src/util/device.rs +++ b/wgpu/src/util/device.rs @@ -87,7 +87,11 @@ impl DeviceExt for crate::Device { desc.usage |= crate::TextureUsages::COPY_DST; let texture = self.create_texture(&desc); - let format_info = desc.format.describe(); + // Will return None only if it's a combined depth-stencil format + // If so, default to 4, validation will fail later anyway since the depth or stencil + // aspect needs to be written to individually + let block_size = desc.format.block_size(None).unwrap_or(4); + let (block_width, block_height) = desc.format.block_dimensions(); let layer_iterations = desc.array_layer_count(); let mut binary_offset = 0; @@ -106,10 +110,10 @@ impl DeviceExt for crate::Device { // All these calculations are performed on the physical size as that's the // data that exists in the buffer. - let width_blocks = mip_physical.width / format_info.block_dimensions.0 as u32; - let height_blocks = mip_physical.height / format_info.block_dimensions.1 as u32; + let width_blocks = mip_physical.width / block_width; + let height_blocks = mip_physical.height / block_height; - let bytes_per_row = width_blocks * format_info.block_size as u32; + let bytes_per_row = width_blocks * block_size; let data_size = bytes_per_row * height_blocks * mip_size.depth_or_array_layers; let end_offset = binary_offset + data_size as usize; diff --git a/wgpu/tests/clear_texture.rs b/wgpu/tests/clear_texture.rs index 597fa48ec68..b2772ce9e53 100644 --- a/wgpu/tests/clear_texture.rs +++ b/wgpu/tests/clear_texture.rs @@ -1,4 +1,4 @@ -use crate::common::{initialize_test, TestParameters, TestingContext}; +use crate::common::{image::ReadbackBuffers, initialize_test, TestParameters, TestingContext}; use wasm_bindgen_test::*; use wgpu::util::align_to; @@ -46,6 +46,7 @@ static TEXTURE_FORMATS_DEPTH: &[wgpu::TextureFormat] = &[ wgpu::TextureFormat::Depth16Unorm, wgpu::TextureFormat::Depth24Plus, wgpu::TextureFormat::Depth24PlusStencil8, + wgpu::TextureFormat::Depth32Float, ]; // needs TEXTURE_COMPRESSION_BC @@ -210,6 +211,13 @@ fn single_texture_clear_test( size ); + let extra_usages = match format { + wgpu::TextureFormat::Depth24Plus | wgpu::TextureFormat::Depth24PlusStencil8 => { + wgpu::TextureUsages::TEXTURE_BINDING + } + _ => wgpu::TextureUsages::empty(), + }; + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: Some(&format!("texture {format:?}")), size, @@ -222,9 +230,7 @@ fn single_texture_clear_test( sample_count: 1, // multisampling is not supported for clear dimension, format, - // Forces internally the required usages to be able to clear it. - // This is not visible on the API level. - usage: wgpu::TextureUsages::TEXTURE_BINDING, + usage: wgpu::TextureUsages::COPY_SRC | extra_usages, view_formats: &[], }); let mut encoder = ctx @@ -240,21 +246,29 @@ fn single_texture_clear_test( array_layer_count: None, }, ); + + let readback_buffers = ReadbackBuffers::new(&ctx.device, &texture); + + readback_buffers.copy_from(&ctx.device, &mut encoder, &texture); + ctx.queue.submit([encoder.finish()]); - // TODO: Read back and check zeroness? + assert!( + readback_buffers.are_zero(&ctx.device), + "texture was not fully cleared" + ); } -fn clear_texture_tests( - ctx: &TestingContext, - formats: &[wgpu::TextureFormat], - supports_1d: bool, - supports_3d: bool, -) { +fn clear_texture_tests(ctx: &TestingContext, formats: &[wgpu::TextureFormat]) { for &format in formats { - let desc = format.describe(); - let rounded_width = align_to(64, desc.block_dimensions.0 as u32); - let rounded_height = align_to(64, desc.block_dimensions.1 as u32); + let (block_width, block_height) = format.block_dimensions(); + let rounded_width = align_to(256, block_width); + let rounded_height = align_to(256, block_height); + + let is_compressed_or_depth_stencil_format = + format.is_compressed() || format.is_depth_stencil_format(); + let supports_1d = !is_compressed_or_depth_stencil_format; + let supports_3d = !is_compressed_or_depth_stencil_format; // 1D texture if supports_1d { @@ -309,14 +323,28 @@ fn clear_texture_tests( #[test] #[wasm_bindgen_test] -fn clear_texture_2d_uncompressed() { +fn clear_texture_uncompressed() { initialize_test( TestParameters::default() .webgl2_failure() .features(wgpu::Features::CLEAR_TEXTURE), |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_UNCOMPRESSED, true, true); - clear_texture_tests(&ctx, TEXTURE_FORMATS_DEPTH, false, false); + clear_texture_tests(&ctx, TEXTURE_FORMATS_UNCOMPRESSED); + }, + ) +} + +#[test] +#[wasm_bindgen_test] +fn clear_texture_depth() { + initialize_test( + TestParameters::default() + .webgl2_failure() + .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) + .limits(wgpu::Limits::downlevel_defaults()) + .features(wgpu::Features::CLEAR_TEXTURE), + |ctx| { + clear_texture_tests(&ctx, TEXTURE_FORMATS_DEPTH); }, ) } @@ -328,48 +356,43 @@ fn clear_texture_d32_s8() { TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::DEPTH32FLOAT_STENCIL8), |ctx| { - clear_texture_tests( - &ctx, - &[wgpu::TextureFormat::Depth32FloatStencil8], - false, - false, - ); + clear_texture_tests(&ctx, &[wgpu::TextureFormat::Depth32FloatStencil8]); }, ) } #[test] -fn clear_texture_2d_bc() { +fn clear_texture_bc() { initialize_test( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_BC) .specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), false), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_BC, false, false); + clear_texture_tests(&ctx, TEXTURE_FORMATS_BC); }, ) } #[test] -fn clear_texture_2d_astc() { +fn clear_texture_astc() { initialize_test( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR) .specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), false), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_ASTC, false, false); + clear_texture_tests(&ctx, TEXTURE_FORMATS_ASTC); }, ) } #[test] -fn clear_texture_2d_etc2() { +fn clear_texture_etc2() { initialize_test( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_ETC2) .specific_failure(Some(wgpu::Backends::GL), None, Some("ANGLE"), false), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_ETC2, false, false); + clear_texture_tests(&ctx, TEXTURE_FORMATS_ETC2); }, ) } diff --git a/wgpu/tests/common/copy_texture_to_buffer.wgsl b/wgpu/tests/common/copy_texture_to_buffer.wgsl new file mode 100644 index 00000000000..2e2c08f9553 --- /dev/null +++ b/wgpu/tests/common/copy_texture_to_buffer.wgsl @@ -0,0 +1,18 @@ +@group(0) @binding(0) +var texture: texture_2d_array<{{type}}>; + +@group(0) @binding(1) +var output: array<{{type}}>; + +@compute @workgroup_size(1) +fn copy_texture_to_buffer() { + let layers = textureNumLayers(texture); + let dim = textureDimensions(texture); + for (var l = 0; l < layers; l++) { + for (var y = 0; y < dim.y; y++) { + for (var x = 0; x < dim.x; x++) { + output[x + y * dim.x] = textureLoad(texture, vec2(x, y), l, 0).x; + } + } + } +} diff --git a/wgpu/tests/common/image.rs b/wgpu/tests/common/image.rs index d98d70acd16..812648d2e8b 100644 --- a/wgpu/tests/common/image.rs +++ b/wgpu/tests/common/image.rs @@ -1,9 +1,13 @@ use std::{ + borrow::Cow, ffi::{OsStr, OsString}, io, + num::NonZeroU32, path::Path, str::FromStr, }; +use wgpu::util::DeviceExt; +use wgpu::*; fn read_png(path: impl AsRef, width: u32, height: u32) -> Option> { let data = match std::fs::read(&path) { @@ -144,3 +148,279 @@ pub fn compare_image_output( write_png(&path, width, height, data, png::Compression::Best); } } + +fn copy_via_compute( + device: &Device, + encoder: &mut CommandEncoder, + texture: &Texture, + buffer: &Buffer, + aspect: TextureAspect, +) { + let bgl = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Texture { + sample_type: match aspect { + TextureAspect::DepthOnly => TextureSampleType::Float { filterable: false }, + TextureAspect::StencilOnly => TextureSampleType::Uint, + _ => unreachable!(), + }, + view_dimension: TextureViewDimension::D2Array, + multisampled: false, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let view = texture.create_view(&TextureViewDescriptor { + aspect, + dimension: Some(TextureViewDimension::D2Array), + ..Default::default() + }); + + let output_buffer = device.create_buffer(&BufferDescriptor { + label: Some("output buffer"), + size: buffer.size(), + usage: BufferUsages::COPY_SRC | BufferUsages::STORAGE, + mapped_at_creation: false, + }); + + let bg = device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Buffer(BufferBinding { + buffer: &output_buffer, + offset: 0, + size: None, + }), + }, + ], + }); + + let pll = device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bgl], + push_constant_ranges: &[], + }); + + let source = String::from(include_str!("copy_texture_to_buffer.wgsl")); + + let processed_source = source.replace( + "{{type}}", + match aspect { + TextureAspect::DepthOnly => "f32", + TextureAspect::StencilOnly => "u32", + _ => unreachable!(), + }, + ); + + let sm = device.create_shader_module(ShaderModuleDescriptor { + label: Some(&format!("shader copy_texture_to_buffer.wgsl")), + source: ShaderSource::Wgsl(Cow::Borrowed(&processed_source)), + }); + + let pipeline_copy = device.create_compute_pipeline(&ComputePipelineDescriptor { + label: Some("pipeline read"), + layout: Some(&pll), + module: &sm, + entry_point: "copy_texture_to_buffer", + }); + + { + let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor::default()); + + pass.set_pipeline(&pipeline_copy); + pass.set_bind_group(0, &bg, &[]); + pass.dispatch_workgroups(1, 1, 1); + } + + encoder.copy_buffer_to_buffer(&output_buffer, 0, buffer, 0, buffer.size()); +} + +fn copy_texture_to_buffer_with_aspect( + encoder: &mut CommandEncoder, + texture: &Texture, + buffer: &Buffer, + buffer_stencil: &Option, + aspect: TextureAspect, +) { + let (block_width, block_height) = texture.format().block_dimensions(); + let block_size = texture.format().block_size(Some(aspect)).unwrap(); + encoder.copy_texture_to_buffer( + ImageCopyTexture { + texture, + mip_level: 0, + origin: Origin3d::ZERO, + aspect, + }, + ImageCopyBuffer { + buffer: match aspect { + TextureAspect::StencilOnly => buffer_stencil.as_ref().unwrap(), + _ => buffer, + }, + layout: ImageDataLayout { + offset: 0, + bytes_per_row: Some( + NonZeroU32::new((texture.width() / block_width) * block_size).unwrap(), + ), + rows_per_image: Some(NonZeroU32::new(texture.height() / block_height).unwrap()), + }, + }, + texture.size(), + ); +} + +fn copy_texture_to_buffer( + device: &Device, + encoder: &mut CommandEncoder, + texture: &Texture, + buffer: &Buffer, + buffer_stencil: &Option, +) { + match texture.format() { + TextureFormat::Depth24Plus => { + copy_via_compute(device, encoder, texture, buffer, TextureAspect::DepthOnly); + } + TextureFormat::Depth24PlusStencil8 => { + copy_via_compute(device, encoder, texture, buffer, TextureAspect::DepthOnly); + // copy_via_compute( + // device, + // encoder, + // texture, + // buffer_stencil.as_ref().unwrap(), + // TextureAspect::StencilOnly, + // ); + copy_texture_to_buffer_with_aspect( + encoder, + texture, + buffer, + buffer_stencil, + TextureAspect::StencilOnly, + ); + } + TextureFormat::Depth32FloatStencil8 => { + copy_texture_to_buffer_with_aspect( + encoder, + texture, + buffer, + buffer_stencil, + TextureAspect::DepthOnly, + ); + copy_texture_to_buffer_with_aspect( + encoder, + texture, + buffer, + buffer_stencil, + TextureAspect::StencilOnly, + ); + } + _ => { + copy_texture_to_buffer_with_aspect( + encoder, + texture, + buffer, + buffer_stencil, + TextureAspect::All, + ); + } + } +} + +pub struct ReadbackBuffers { + /// buffer for color or depth aspects + buffer: Buffer, + /// buffer for stencil aspect + buffer_stencil: Option, +} + +impl ReadbackBuffers { + pub fn new(device: &Device, texture: &Texture) -> Self { + let (block_width, block_height) = texture.format().block_dimensions(); + let base_size = (texture.width() / block_width) + * (texture.height() / block_height) + * texture.depth_or_array_layers(); + if texture.format().is_combined_depth_stencil_format() { + let buffer_size = base_size + * texture + .format() + .block_size(Some(TextureAspect::DepthOnly)) + .unwrap_or(4); + let buffer_stencil_size = base_size + * texture + .format() + .block_size(Some(TextureAspect::StencilOnly)) + .unwrap(); + let buffer = device.create_buffer_init(&util::BufferInitDescriptor { + label: Some("Texture Readback"), + usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, + contents: &vec![255; buffer_size as usize], + }); + let buffer_stencil = device.create_buffer_init(&util::BufferInitDescriptor { + label: Some("Texture Stencil-Aspect Readback"), + usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, + contents: &vec![255; buffer_stencil_size as usize], + }); + ReadbackBuffers { + buffer, + buffer_stencil: Some(buffer_stencil), + } + } else { + let buffer_size = base_size * texture.format().block_size(None).unwrap_or(4); + let buffer = device.create_buffer_init(&util::BufferInitDescriptor { + label: Some("Texture Readback"), + usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, + contents: &vec![255; buffer_size as usize], + }); + ReadbackBuffers { + buffer, + buffer_stencil: None, + } + } + } + + pub fn copy_from(&self, device: &Device, encoder: &mut CommandEncoder, texture: &Texture) { + copy_texture_to_buffer(device, encoder, texture, &self.buffer, &self.buffer_stencil); + } + + pub fn are_zero(&self, device: &Device) -> bool { + fn is_zero(device: &Device, buffer: &Buffer) -> bool { + let is_zero = { + let buffer_slice = buffer.slice(..); + buffer_slice.map_async(MapMode::Read, |_| ()); + device.poll(Maintain::Wait); + let buffer_view = buffer_slice.get_mapped_range(); + buffer_view.iter().all(|b| *b == 0) + }; + buffer.unmap(); + is_zero + } + + is_zero(device, &self.buffer) + && self + .buffer_stencil + .as_ref() + .map(|buffer_stencil| is_zero(device, buffer_stencil)) + .unwrap_or(true) + } +} diff --git a/wgpu/tests/zero_init_texture_after_discard.rs b/wgpu/tests/zero_init_texture_after_discard.rs index 6a9bc0a99cb..54aaad75e79 100644 --- a/wgpu/tests/zero_init_texture_after_discard.rs +++ b/wgpu/tests/zero_init_texture_after_discard.rs @@ -1,41 +1,24 @@ use std::num::NonZeroU32; -use crate::common::{initialize_test, TestParameters}; +use crate::common::{image::ReadbackBuffers, initialize_test, TestParameters, TestingContext}; use wasm_bindgen_test::*; +use wgpu::*; // Checks if discarding a color target resets its init state, causing a zero read of this texture when copied in after submit of the encoder. #[test] #[wasm_bindgen_test] fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_after_submit() { - initialize_test(TestParameters::default().webgl2_failure(), |ctx| { - let (texture, readback_buffer) = - create_white_texture_and_readback_buffer(&ctx, wgpu::TextureFormat::Rgba8UnormSrgb); - { - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Color Discard"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &texture.create_view(&wgpu::TextureViewDescriptor::default()), - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: false, // discard! - }, - })], - depth_stencil_attachment: None, - }); - ctx.queue.submit([encoder.finish()]); - } - { - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - copy_texture_to_buffer(&mut encoder, &texture, &readback_buffer); - ctx.queue.submit([encoder.finish()]); - } - assert_buffer_is_zero(&readback_buffer, &ctx.device); + initialize_test(TestParameters::default().webgl2_failure(), |mut ctx| { + let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); + case.create_command_encoder(); + case.discard(); + case.submit_command_encoder(); + + case.create_command_encoder(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); + + case.assert_buffers_are_zero(); }); } @@ -43,70 +26,41 @@ fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_after #[test] #[wasm_bindgen_test] fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_in_same_encoder() { - initialize_test(TestParameters::default().webgl2_failure(), |ctx| { - let (texture, readback_buffer) = - create_white_texture_and_readback_buffer(&ctx, wgpu::TextureFormat::Rgba8UnormSrgb); - { - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Color Discard"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &texture.create_view(&wgpu::TextureViewDescriptor::default()), - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: false, // discard! - }, - })], - depth_stencil_attachment: None, - }); - copy_texture_to_buffer(&mut encoder, &texture, &readback_buffer); - ctx.queue.submit([encoder.finish()]); - } - assert_buffer_is_zero(&readback_buffer, &ctx.device); + initialize_test(TestParameters::default().webgl2_failure(), |mut ctx| { + let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); + case.create_command_encoder(); + case.discard(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); + + case.assert_buffers_are_zero(); }); } #[test] #[wasm_bindgen_test] -#[allow(clippy::single_element_loop)] fn discarding_depth_target_resets_texture_init_state_check_visible_on_copy_in_same_encoder() { initialize_test( TestParameters::default() - .downlevel_flags(wgpu::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES), - |ctx| { + .downlevel_flags( + DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES | DownlevelFlags::COMPUTE_SHADERS, + ) + .limits(Limits::downlevel_defaults()), + |mut ctx| { for format in [ - wgpu::TextureFormat::Depth32Float, - //wgpu::TextureFormat::Depth24Plus, // Can't copy to or from buffer - //wgpu::TextureFormat::Depth24PlusStencil8, // Can only copy stencil aspect to/from buffer + TextureFormat::Stencil8, + TextureFormat::Depth16Unorm, + TextureFormat::Depth24Plus, + TextureFormat::Depth24PlusStencil8, + TextureFormat::Depth32Float, ] { - let (texture, readback_buffer) = - create_white_texture_and_readback_buffer(&ctx, format); - { - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Depth Discard"), - color_attachments: &[], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &texture.create_view(&wgpu::TextureViewDescriptor::default()), - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: false, // discard! - }), - stencil_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: false, // discard! - }), - }), - }); - copy_texture_to_buffer(&mut encoder, &texture, &readback_buffer); - ctx.queue.submit([encoder.finish()]); - } - assert_buffer_is_zero(&readback_buffer, &ctx.device); + let mut case = TestCase::new(&mut ctx, format); + case.create_command_encoder(); + case.discard(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); + + case.assert_buffers_are_zero(); } }, ); @@ -115,188 +69,237 @@ fn discarding_depth_target_resets_texture_init_state_check_visible_on_copy_in_sa #[test] #[wasm_bindgen_test] fn discarding_either_depth_or_stencil_aspect() { - initialize_test(TestParameters::default(), |ctx| { - let (texture, _) = create_white_texture_and_readback_buffer( - &ctx, - wgpu::TextureFormat::Depth24PlusStencil8, - ); - // TODO: How do we test this other than "doesn't crash"? We can't copy the texture to/from buffers, so we would need to do a copy in a shader - { + initialize_test( + TestParameters::default() + .downlevel_flags( + DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES | DownlevelFlags::COMPUTE_SHADERS, + ) + .limits(Limits::downlevel_defaults()), + |mut ctx| { + let mut case = TestCase::new(&mut ctx, TextureFormat::Depth24PlusStencil8); + case.create_command_encoder(); + case.discard_depth(); + case.submit_command_encoder(); + + case.create_command_encoder(); + case.discard_stencil(); + case.submit_command_encoder(); + + case.create_command_encoder(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); + + case.assert_buffers_are_zero(); + }, + ); +} + +struct TestCase<'ctx> { + ctx: &'ctx mut TestingContext, + format: TextureFormat, + texture: Texture, + readback_buffers: ReadbackBuffers, + encoder: Option, +} + +impl<'ctx> TestCase<'ctx> { + pub fn new(ctx: &'ctx mut TestingContext, format: TextureFormat) -> Self { + let extra_usages = match format { + TextureFormat::Depth24Plus | TextureFormat::Depth24PlusStencil8 => { + TextureUsages::TEXTURE_BINDING + } + _ => TextureUsages::empty(), + }; + + let texture = ctx.device.create_texture(&TextureDescriptor { + label: Some("RenderTarget"), + size: Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format, + usage: TextureUsages::COPY_DST + | TextureUsages::COPY_SRC + | TextureUsages::RENDER_ATTACHMENT + | extra_usages, + view_formats: &[], + }); + + // Clear using a write_texture operation. We could also clear using a render_pass clear. + // However, when making this test intentionally fail (by breaking wgpu impl), it shows that at least on the tested Vulkan driver, + // the later following discard pass in the test (i.e. internally vk::AttachmentStoreOp::DONT_CARE) will yield different depending on the operation we take here: + // * clearing white -> discard will cause it to become black! + // * clearing red -> discard will keep it red + // * write_texture -> discard will keep buffer + // This behavior is curious, but does not violate any spec - it is wgpu's job to pass this test no matter what a render target discard does. + + // ... but that said, for depth/stencil textures we need to do a clear. + if format.is_depth_stencil_format() { let mut encoder = ctx .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Depth Discard, Stencil Load"), + .create_command_encoder(&CommandEncoderDescriptor::default()); + encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("Depth/Stencil setup"), color_attachments: &[], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &texture.create_view(&wgpu::TextureViewDescriptor::default()), - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: false, // discard! - }), - stencil_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(0), + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &texture.create_view(&TextureViewDescriptor::default()), + depth_ops: format.has_depth_aspect().then_some(Operations { + load: LoadOp::Clear(1.0), store: true, }), - }), - }); - ctx.queue.submit([encoder.finish()]); - } - { - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Depth Load, Stencil Discard"), - color_attachments: &[], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &texture.create_view(&wgpu::TextureViewDescriptor::default()), - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(0.0), + stencil_ops: format.has_stencil_aspect().then_some(Operations { + load: LoadOp::Clear(0xFFFFFFFF), store: true, }), - stencil_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: false, // discard! - }), }), }); ctx.queue.submit([encoder.finish()]); - } - }); -} + } else { + let block_size = format.block_size(None).unwrap(); + let bytes_per_row = texture.width() * block_size; -const TEXTURE_SIZE: wgpu::Extent3d = wgpu::Extent3d { - width: 64, - height: 64, - depth_or_array_layers: 1, -}; -const BYTES_PER_PIXEL: u32 = 4; -const BUFFER_COPY_LAYOUT: wgpu::ImageDataLayout = wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new(TEXTURE_SIZE.width * BYTES_PER_PIXEL), - rows_per_image: None, -}; + // Size for tests is chosen so that we don't need to care about buffer alignments. + assert!(!format.is_compressed()); + assert_eq!(bytes_per_row % COPY_BYTES_PER_ROW_ALIGNMENT, 0); -fn create_white_texture_and_readback_buffer( - ctx: &crate::common::TestingContext, - format: wgpu::TextureFormat, -) -> (wgpu::Texture, wgpu::Buffer) { - let format_desc = format.describe(); + let buffer_size = texture.height() * bytes_per_row; + let data = vec![255; buffer_size as usize]; + ctx.queue.write_texture( + ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin: Origin3d { x: 0, y: 0, z: 0 }, + aspect: TextureAspect::All, + }, + &data, + ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(bytes_per_row), + rows_per_image: None, + }, + texture.size(), + ); + } - // Size for tests is chosen so that we don't need to care about buffer alignments. - assert_eq!(format_desc.block_dimensions, (1, 1)); - assert_eq!(format_desc.block_size as u32, BYTES_PER_PIXEL); - assert_eq!( - (TEXTURE_SIZE.width * format_desc.block_size as u32) % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT, - 0 - ); - let buffer_size = TEXTURE_SIZE.width * TEXTURE_SIZE.height * BYTES_PER_PIXEL; + let readback_buffers = ReadbackBuffers::new(&ctx.device, &texture); - let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { - label: Some("RenderTarget"), - size: TEXTURE_SIZE, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format, - usage: if format == wgpu::TextureFormat::Depth24PlusStencil8 { - // not allowed to have copy usages! - wgpu::TextureUsages::RENDER_ATTACHMENT - } else { - wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::COPY_SRC - | wgpu::TextureUsages::RENDER_ATTACHMENT - }, - view_formats: &[], - }); + Self { + ctx, + format, + texture, + readback_buffers, + encoder: None, + } + } - // Clear using a write_texture operation. We could also clear using a render_pass clear. - // However, when making this test intentionally fail (by breaking wgpu impl), it shows that at least on the tested Vulkan driver, - // the later following discard pass in the test (i.e. internally vk::AttachmentStoreOp::DONT_CARE) will yield different depending on the operation we take here: - // * clearing white -> discard will cause it to become black! - // * clearing red -> discard will keep it red - // * write_texture -> discard will keep buffer - // This behavior is curious, but does not violate any spec - it is wgpu's job to pass this test no matter what a render target discard does. + pub fn create_command_encoder(&mut self) { + self.encoder = Some( + self.ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()), + ) + } - // ... but that said, for depth/stencil textures we need to do a clear. - if format_desc.sample_type == wgpu::TextureSampleType::Depth { - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Depth/Stencil setup"), - color_attachments: &[], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &texture.create_view(&wgpu::TextureViewDescriptor::default()), - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(0xFFFFFFFF), - store: true, - }), - }), - }); - ctx.queue.submit([encoder.finish()]); - } else { - let data = vec![255; buffer_size as usize]; - ctx.queue.write_texture( - wgpu::ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, - aspect: wgpu::TextureAspect::All, - }, - &data, - BUFFER_COPY_LAYOUT, - TEXTURE_SIZE, - ); + pub fn submit_command_encoder(&mut self) { + self.ctx + .queue + .submit([self.encoder.take().unwrap().finish()]); } - ( - texture, - ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Texture Readback"), - size: buffer_size as u64, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }), - ) -} + pub fn discard(&mut self) { + self.encoder + .as_mut() + .unwrap() + .begin_render_pass(&RenderPassDescriptor { + label: Some("Discard"), + color_attachments: &[self.format.has_color_aspect().then_some( + RenderPassColorAttachment { + view: &self.texture.create_view(&TextureViewDescriptor::default()), + resolve_target: None, + ops: Operations { + load: LoadOp::Load, + store: false, // discard! + }, + }, + )], + depth_stencil_attachment: self.format.is_depth_stencil_format().then_some( + RenderPassDepthStencilAttachment { + view: &self.texture.create_view(&TextureViewDescriptor::default()), + depth_ops: self.format.has_depth_aspect().then_some(Operations { + load: LoadOp::Load, + store: false, // discard! + }), + stencil_ops: self.format.has_stencil_aspect().then_some(Operations { + load: LoadOp::Load, + store: false, // discard! + }), + }, + ), + }); + } -fn copy_texture_to_buffer( - encoder: &mut wgpu::CommandEncoder, - texture: &wgpu::Texture, - read_back: &wgpu::Buffer, -) { - encoder.copy_texture_to_buffer( - wgpu::ImageCopyTexture { - texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - wgpu::ImageCopyBuffer { - buffer: read_back, - layout: BUFFER_COPY_LAYOUT, - }, - TEXTURE_SIZE, - ); -} + pub fn discard_depth(&mut self) { + self.encoder + .as_mut() + .unwrap() + .begin_render_pass(&RenderPassDescriptor { + label: Some("Discard Depth"), + color_attachments: &[], + depth_stencil_attachment: self.format.is_depth_stencil_format().then_some( + RenderPassDepthStencilAttachment { + view: &self.texture.create_view(&TextureViewDescriptor::default()), + depth_ops: Some(Operations { + load: LoadOp::Load, + store: false, // discard! + }), + stencil_ops: self.format.has_stencil_aspect().then_some(Operations { + load: LoadOp::Clear(0), + store: true, + }), + }, + ), + }); + } -fn assert_buffer_is_zero(readback_buffer: &wgpu::Buffer, device: &wgpu::Device) { - { - let buffer_slice = readback_buffer.slice(..); - buffer_slice.map_async(wgpu::MapMode::Read, |_| ()); - device.poll(wgpu::Maintain::Wait); - let buffer_view = buffer_slice.get_mapped_range(); + pub fn discard_stencil(&mut self) { + self.encoder + .as_mut() + .unwrap() + .begin_render_pass(&RenderPassDescriptor { + label: Some("Discard Stencil"), + color_attachments: &[], + depth_stencil_attachment: self.format.is_depth_stencil_format().then_some( + RenderPassDepthStencilAttachment { + view: &self.texture.create_view(&TextureViewDescriptor::default()), + depth_ops: self.format.has_depth_aspect().then_some(Operations { + load: LoadOp::Clear(0.0), + store: true, + }), + stencil_ops: Some(Operations { + load: LoadOp::Load, + store: false, // discard! + }), + }, + ), + }); + } + + pub fn copy_texture_to_buffer(&mut self) { + self.readback_buffers.copy_from( + &self.ctx.device, + self.encoder.as_mut().unwrap(), + &self.texture, + ); + } + pub fn assert_buffers_are_zero(&mut self) { assert!( - buffer_view.iter().all(|b| *b == 0), + self.readback_buffers.are_zero(&self.ctx.device), "texture was not fully cleared" ); } - readback_buffer.unmap(); }