Fix clamping at minimal render extent (#8486)

This was a regression introduced by #8307.

For mipmapped multi-planar textures, requires that the size is a
multiple of a power of two determined by the mip level count, in lieu
of properly aligning the size of each mip level to an even
dimension. See #8491.

(The failure referred to by "fails due to missing validation" was
fixed before the regression, by #8402, but was not identified as fixed
and added to the test list at that time.)
This commit is contained in:
Andy Leiserson 2025-11-06 17:56:18 -08:00 committed by GitHub
parent c0607b53f5
commit a2db631672
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 113 additions and 24 deletions

View File

@ -126,9 +126,7 @@ fails-if(dx12) webgpu:api,validation,image_copy,layout_related:offset_alignment:
webgpu:api,validation,image_copy,texture_related:format:dimension="1d";*
webgpu:api,validation,queue,submit:command_buffer,*
webgpu:api,validation,render_pass,render_pass_descriptor:attachments,*
webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,bound_check:*
// Fails due to missing validation.
// FAIL: webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,overlaps,diff_miplevel:*
webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,*
webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,*
webgpu:api,validation,render_pass,resolve:resolve_attachment:*
webgpu:api,validation,resource_usages,buffer,in_pass_encoder:*
@ -141,6 +139,7 @@ webgpu:api,validation,resource_usages,texture,in_pass_encoder:unused_bindings_in
webgpu:api,validation,resource_usages,texture,in_render_common:subresources,multiple_bind_groups:bg0Levels={"base":0,"count":1};*
webgpu:api,validation,texture,rg11b10ufloat_renderable:*
webgpu:api,operation,render_pipeline,overrides:*
webgpu:api,operation,rendering,3d_texture_slices:*
webgpu:api,operation,rendering,basic:clear:*
webgpu:api,operation,rendering,basic:fullscreen_quad:*
//FAIL: webgpu:api,operation,rendering,basic:large_draw:*

View File

@ -1360,8 +1360,25 @@ impl Device {
}
}
let mips = desc.mip_level_count;
let max_levels_allowed = desc.size.max_mips(desc.dimension).min(hal::MAX_MIP_LEVELS);
if mips == 0 || mips > max_levels_allowed {
return Err(CreateTextureError::InvalidMipLevelCount {
requested: mips,
maximum: max_levels_allowed,
});
}
{
let (width_multiple, height_multiple) = desc.format.size_multiple_requirement();
let (mut width_multiple, mut height_multiple) = desc.format.size_multiple_requirement();
if desc.format.is_multi_planar_format() {
// TODO(https://github.com/gfx-rs/wgpu/issues/8491): fix
// `mip_level_size` calculation for these formats and relax this
// restriction.
width_multiple <<= desc.mip_level_count.saturating_sub(1);
height_multiple <<= desc.mip_level_count.saturating_sub(1);
}
if desc.size.width % width_multiple != 0 {
return Err(CreateTextureError::InvalidDimension(
@ -1456,15 +1473,6 @@ impl Device {
};
}
let mips = desc.mip_level_count;
let max_levels_allowed = desc.size.max_mips(desc.dimension).min(hal::MAX_MIP_LEVELS);
if mips == 0 || mips > max_levels_allowed {
return Err(CreateTextureError::InvalidMipLevelCount {
requested: mips,
maximum: max_levels_allowed,
});
}
let missing_allowed_usages = match desc.format.planes() {
Some(planes) => {
let mut planes_usages = wgt::TextureUsages::all();

View File

@ -158,6 +158,56 @@ fn test_compute_render_extent() {
let _ = desc.compute_render_extent(0, None);
}
}
for format in [wgpu::TextureFormat::NV12, wgpu::TextureFormat::P010] {
let desc = wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: 8,
height: 4,
depth_or_array_layers: 1,
},
mip_level_count: 2,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::empty(),
view_formats: &[],
};
assert_eq!(
desc.compute_render_extent(0, Some(0)),
wgpu::Extent3d {
width: 8,
height: 4,
depth_or_array_layers: 1,
}
);
assert_eq!(
desc.compute_render_extent(0, Some(1)),
wgpu::Extent3d {
width: 4,
height: 2,
depth_or_array_layers: 1,
}
);
assert_eq!(
desc.compute_render_extent(1, Some(0)),
wgpu::Extent3d {
width: 4,
height: 2,
depth_or_array_layers: 1,
}
);
assert_eq!(
desc.compute_render_extent(1, Some(1)),
wgpu::Extent3d {
width: 2,
height: 1,
depth_or_array_layers: 1,
}
);
}
}
pub fn max_texture_format_string_size() -> usize {

View File

@ -2881,6 +2881,20 @@ impl TextureFormat {
}
}
/// Returns the subsampling factor for the indicated plane of a multi-planar format.
#[must_use]
pub fn subsampling_factors(&self, plane: Option<u32>) -> (u32, u32) {
match *self {
Self::NV12 | Self::P010 => match plane {
Some(0) => (1, 1),
Some(1) => (2, 2),
Some(plane) => unreachable!("plane {plane} is not valid for {self:?}"),
None => unreachable!("the plane must be specified for multi-planar formats"),
},
_ => (1, 1),
}
}
/// Returns `true` if the format has a color aspect
#[must_use]
pub fn has_color_aspect(&self) -> bool {
@ -2910,6 +2924,10 @@ impl TextureFormat {
}
/// Returns the size multiple requirement for a texture using this format.
///
/// `create_texture` currently enforces a stricter restriction than this for
/// mipmapped multi-planar formats.
/// TODO(<https://github.com/gfx-rs/wgpu/issues/8491>): Remove this note.
#[must_use]
pub fn size_multiple_requirement(&self) -> (u32, u32) {
match *self {
@ -6186,9 +6204,17 @@ impl Extent3d {
}
/// Calculates the extent at a given mip level.
/// Does *not* account for memory size being a multiple of block size.
///
/// This is a low-level helper for internal use.
///
/// It does *not* account for memory size being a multiple of block size.
///
/// TODO(<https://github.com/gfx-rs/wgpu/issues/8491>): It also does not
/// consider whether an even dimension is required due to chroma
/// subsampling, but it probably should.
///
/// <https://gpuweb.github.io/gpuweb/#logical-miplevel-specific-texture-extent>
#[doc(hidden)]
#[must_use]
pub fn mip_level_size(&self, level: u32, dim: TextureDimension) -> Self {
Self {
@ -6459,20 +6485,26 @@ impl<L, V> TextureDescriptor<L, V> {
/// Computes the render extent of this texture.
///
/// This is a low-level helper exported for use by wgpu-core.
///
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-compute-render-extent>
///
/// # Panics
///
/// If the mip level is out of range.
#[doc(hidden)]
#[must_use]
pub fn compute_render_extent(&self, mip_level: u32, plane: Option<u32>) -> Extent3d {
let width = self.size.width >> mip_level;
let height = self.size.height >> mip_level;
let Extent3d {
width,
height,
depth_or_array_layers: _,
} = self.mip_level_size(mip_level).expect("invalid mip level");
let (width, height) = match (self.format, plane) {
(TextureFormat::NV12 | TextureFormat::P010, Some(0)) => (width, height),
(TextureFormat::NV12 | TextureFormat::P010, Some(1)) => (width / 2, height / 2),
_ => {
debug_assert!(!self.format.is_multi_planar_format());
(width, height)
}
};
let (w_subsampling, h_subsampling) = self.format.subsampling_factors(plane);
let width = width / w_subsampling;
let height = height / h_subsampling;
Extent3d {
width,