From 4f7348d0aedcf102efc67536c857eb214d097c4d Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 8 Jul 2025 09:51:34 +0200 Subject: [PATCH 1/8] Add descriptor to `TextureView` --- wgpu/src/api/device.rs | 2 +- wgpu/src/api/texture.rs | 80 +++++++++++++++++++++++++++++++++++- wgpu/src/api/texture_view.rs | 12 ++++++ 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/wgpu/src/api/device.rs b/wgpu/src/api/device.rs index 99ed5071df9..e5fce1e3e8c 100644 --- a/wgpu/src/api/device.rs +++ b/wgpu/src/api/device.rs @@ -278,7 +278,7 @@ impl Device { Texture { inner: texture, descriptor: TextureDescriptor { - label: None, + label: None, view_formats: &[], ..desc.clone() }, diff --git a/wgpu/src/api/texture.rs b/wgpu/src/api/texture.rs index 6dff7b6a11e..3db32a98483 100644 --- a/wgpu/src/api/texture.rs +++ b/wgpu/src/api/texture.rs @@ -51,7 +51,85 @@ impl Texture { pub fn create_view(&self, desc: &TextureViewDescriptor<'_>) -> TextureView { let view = self.inner.create_view(desc); - TextureView { inner: view } + let &TextureViewDescriptor { + label: _, + format, + dimension, + usage, + aspect, + base_mip_level, + mip_level_count, + base_array_layer, + array_layer_count, + } = desc; + + // WebGPU spec requires us to fill in optional fields for later access. + // We could do this by accessing the underlying implementation, but duplicating this + // logic here is a lot simpler than piping this through from a backend. + // See + // See also wgpu-core's `create_texture_view` + + let resolved_format = format.unwrap_or_else(|| { + self.descriptor + .format + .aspect_specific_format(aspect) + .unwrap_or(self.descriptor.format) + }); + + let resolved_dimension = dimension.unwrap_or_else(|| match self.descriptor.dimension { + TextureDimension::D1 => TextureViewDimension::D1, + TextureDimension::D2 => { + if array_layer_count == Some(1) { + TextureViewDimension::D2 + } else { + TextureViewDimension::D2Array + } + } + TextureDimension::D3 => TextureViewDimension::D3, + }); + + let resolved_mip_level_count = mip_level_count.unwrap_or_else(|| { + self.descriptor + .mip_level_count + .saturating_sub(base_mip_level) + }); + + let resolved_array_layer_count = + array_layer_count.unwrap_or_else(|| match resolved_dimension { + TextureViewDimension::D1 | TextureViewDimension::D2 | TextureViewDimension::D3 => 1, + TextureViewDimension::Cube => 6, + TextureViewDimension::D2Array | TextureViewDimension::CubeArray => self + .descriptor + .array_layer_count() + .saturating_sub(base_array_layer), + }); + + let resolved_usage = { + let usage = usage.unwrap_or(wgt::TextureUsages::empty()); + if usage.is_empty() { + self.descriptor.usage + } else { + usage + // If usage is still empty we have an error, but that's handled by the backend. + } + }; + + let filled_descriptor = TextureViewDescriptor { + label: None, + format: Some(resolved_format), + dimension: Some(resolved_dimension), + usage: Some(resolved_usage), + aspect, + base_mip_level, + mip_level_count: Some(resolved_mip_level_count), + base_array_layer, + array_layer_count: Some(resolved_array_layer_count), + }; + + TextureView { + inner: view, + filled_descriptor, + } } /// Destroy the associated native resources as soon as possible. diff --git a/wgpu/src/api/texture_view.rs b/wgpu/src/api/texture_view.rs index e5af89d62e7..0fcc44b64fa 100644 --- a/wgpu/src/api/texture_view.rs +++ b/wgpu/src/api/texture_view.rs @@ -12,6 +12,7 @@ use crate::*; #[derive(Debug, Clone)] pub struct TextureView { pub(crate) inner: dispatch::DispatchTextureView, + pub(crate) filled_descriptor: TextureViewDescriptor<'static>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(TextureView: Send, Sync); @@ -46,6 +47,17 @@ impl TextureView { pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } + + /// The [`TextureViewDescriptor`] describing this texture view. + /// + /// Fields of [`TextureViewDescriptor`] that were left optional on creation and are + /// derived from the texture are filled out. + /// I.e. this is not the original descriptor used to create this texture view. + /// + /// Does not preserve the original label and leaves it as `None`. + pub fn descriptor(&self) -> &TextureViewDescriptor<'static> { + &self.filled_descriptor + } } /// Describes a [`TextureView`]. From a748bf99b0ced17476be3558f126c9e19a54b611 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 8 Jul 2025 09:53:31 +0200 Subject: [PATCH 2/8] add `texture()` to texture_view --- wgpu/src/api/texture.rs | 1 + wgpu/src/api/texture_view.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/wgpu/src/api/texture.rs b/wgpu/src/api/texture.rs index 3db32a98483..8f4489ccdb7 100644 --- a/wgpu/src/api/texture.rs +++ b/wgpu/src/api/texture.rs @@ -129,6 +129,7 @@ impl Texture { TextureView { inner: view, filled_descriptor, + texture: self.clone(), } } diff --git a/wgpu/src/api/texture_view.rs b/wgpu/src/api/texture_view.rs index 0fcc44b64fa..a573b622b53 100644 --- a/wgpu/src/api/texture_view.rs +++ b/wgpu/src/api/texture_view.rs @@ -13,6 +13,7 @@ use crate::*; pub struct TextureView { pub(crate) inner: dispatch::DispatchTextureView, pub(crate) filled_descriptor: TextureViewDescriptor<'static>, + pub(crate) texture: Texture, } #[cfg(send_sync)] static_assertions::assert_impl_all!(TextureView: Send, Sync); @@ -48,6 +49,11 @@ impl TextureView { self.inner.as_custom() } + /// The [`Texture`] this texture view is a view of. + pub fn texture(&self) -> &Texture { + &self.texture + } + /// The [`TextureViewDescriptor`] describing this texture view. /// /// Fields of [`TextureViewDescriptor`] that were left optional on creation and are From ac877d6059617d27fa2edfec1037db469d24adf3 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 8 Jul 2025 10:00:19 +0200 Subject: [PATCH 3/8] Add render_extent to TextureView --- wgpu/src/api/texture_view.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/wgpu/src/api/texture_view.rs b/wgpu/src/api/texture_view.rs index a573b622b53..c4326ddf236 100644 --- a/wgpu/src/api/texture_view.rs +++ b/wgpu/src/api/texture_view.rs @@ -64,6 +64,26 @@ impl TextureView { pub fn descriptor(&self) -> &TextureViewDescriptor<'static> { &self.filled_descriptor } + + /// The extent of the texture view if this texture view is renderable. + pub fn render_extent(&self) -> Option { + // Computing this on the fly is a lot simpler than piping this through from a backend. + + // The filled_descriptor should have usage always be set. + debug_assert!(self.filled_descriptor.usage.is_some()); + let usage = self + .filled_descriptor + .usage + .unwrap_or(wgt::TextureUsages::empty()); + + usage + .contains(wgt::TextureUsages::RENDER_ATTACHMENT) + .then(|| { + self.texture + .descriptor + .compute_render_extent(self.descriptor().base_mip_level) + }) + } } /// Describes a [`TextureView`]. From 9209a3cdf1a6b5f4bba7ec030d2fa76b86a20a50 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 8 Jul 2025 10:14:10 +0200 Subject: [PATCH 4/8] piggyback on existing tests + bugfix (oops! :)) --- tests/tests/wgpu-gpu/texture_view_creation.rs | 77 +++++++++++++++++-- wgpu/src/api/texture.rs | 2 +- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/tests/tests/wgpu-gpu/texture_view_creation.rs b/tests/tests/wgpu-gpu/texture_view_creation.rs index d6880fb6203..30ffbf03bc3 100644 --- a/tests/tests/wgpu-gpu/texture_view_creation.rs +++ b/tests/tests/wgpu-gpu/texture_view_creation.rs @@ -9,7 +9,10 @@ static STENCIL_ONLY_VIEW_CREATION: GpuTestConfiguration = GpuTestConfiguration:: .limits(wgpu::Limits::downlevel_defaults()), ) .run_async(|ctx| async move { - for format in [TextureFormat::Stencil8, TextureFormat::Depth24PlusStencil8] { + for (format, expected_view_format) in [ + (TextureFormat::Stencil8, TextureFormat::Stencil8), + (TextureFormat::Depth24PlusStencil8, TextureFormat::Stencil8), + ] { let texture = ctx.device.create_texture(&TextureDescriptor { label: None, size: Extent3d { @@ -26,20 +29,39 @@ static STENCIL_ONLY_VIEW_CREATION: GpuTestConfiguration = GpuTestConfiguration:: | TextureUsages::TEXTURE_BINDING, view_formats: &[], }); - let _view = texture.create_view(&TextureViewDescriptor { + let view = texture.create_view(&TextureViewDescriptor { aspect: TextureAspect::StencilOnly, ..Default::default() }); + + assert!(view.render_extent().is_none()); + + let descriptor = view.descriptor(); + assert_eq!(descriptor.format, Some(expected_view_format)); + assert_eq!(descriptor.dimension, Some(TextureViewDimension::D2)); + assert_eq!( + descriptor.usage, + Some( + TextureUsages::COPY_DST + | TextureUsages::COPY_SRC + | TextureUsages::TEXTURE_BINDING, + ) + ); + assert_eq!(descriptor.mip_level_count, Some(1)); + assert_eq!(descriptor.array_layer_count, Some(1)); } }); #[gpu_test] static DEPTH_ONLY_VIEW_CREATION: GpuTestConfiguration = GpuTestConfiguration::new().run_async(|ctx| async move { - for format in [ - TextureFormat::Depth16Unorm, - TextureFormat::Depth24Plus, - TextureFormat::Depth24PlusStencil8, + for (format, expected_view_format) in [ + (TextureFormat::Depth16Unorm, TextureFormat::Depth16Unorm), + (TextureFormat::Depth24Plus, TextureFormat::Depth24Plus), + ( + TextureFormat::Depth24PlusStencil8, + TextureFormat::Depth24Plus, + ), ] { let texture = ctx.device.create_texture(&TextureDescriptor { label: None, @@ -57,10 +79,26 @@ static DEPTH_ONLY_VIEW_CREATION: GpuTestConfiguration = | TextureUsages::TEXTURE_BINDING, view_formats: &[], }); - let _view = texture.create_view(&TextureViewDescriptor { + let view = texture.create_view(&TextureViewDescriptor { aspect: TextureAspect::DepthOnly, ..Default::default() }); + + assert!(view.render_extent().is_none()); + + let descriptor = view.descriptor(); + assert_eq!(descriptor.format, Some(expected_view_format)); + assert_eq!(descriptor.dimension, Some(TextureViewDimension::D2)); + assert_eq!( + descriptor.usage, + Some( + TextureUsages::COPY_DST + | TextureUsages::COPY_SRC + | TextureUsages::TEXTURE_BINDING, + ) + ); + assert_eq!(descriptor.mip_level_count, Some(1)); + assert_eq!(descriptor.array_layer_count, Some(1)); } }); @@ -88,7 +126,7 @@ static SHARED_USAGE_VIEW_CREATION: GpuTestConfiguration = GpuTestConfiguration:: | TextureUsages::RENDER_ATTACHMENT, view_formats: &[TextureFormat::Rgba8UnormSrgb], }); - let _view = texture.create_view(&TextureViewDescriptor { + let view = texture.create_view(&TextureViewDescriptor { aspect: TextureAspect::All, format: Some(view_format), usage: Some( @@ -98,5 +136,28 @@ static SHARED_USAGE_VIEW_CREATION: GpuTestConfiguration = GpuTestConfiguration:: ), ..Default::default() }); + + assert_eq!( + view.render_extent(), + Some(Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }) + ); + + let descriptor = view.descriptor(); + assert_eq!(descriptor.format, Some(view_format)); + assert_eq!(descriptor.dimension, Some(TextureViewDimension::D2)); + assert_eq!( + descriptor.usage, + Some( + TextureUsages::COPY_DST + | TextureUsages::TEXTURE_BINDING + | TextureUsages::RENDER_ATTACHMENT, + ) + ); + assert_eq!(descriptor.mip_level_count, Some(1)); + assert_eq!(descriptor.array_layer_count, Some(1)); } }); diff --git a/wgpu/src/api/texture.rs b/wgpu/src/api/texture.rs index 8f4489ccdb7..4bef0a1cc07 100644 --- a/wgpu/src/api/texture.rs +++ b/wgpu/src/api/texture.rs @@ -79,7 +79,7 @@ impl Texture { let resolved_dimension = dimension.unwrap_or_else(|| match self.descriptor.dimension { TextureDimension::D1 => TextureViewDimension::D1, TextureDimension::D2 => { - if array_layer_count == Some(1) { + if self.descriptor.array_layer_count() == 1 { TextureViewDimension::D2 } else { TextureViewDimension::D2Array From 2a09696c811365078511eb9b8251f18a0b0401ff Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 8 Jul 2025 10:15:34 +0200 Subject: [PATCH 5/8] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 946473f8127..37d795baa67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Bottom level categories: - Add acceleration structure limits. By @Vecvec in [#7845](https://github.com/gfx-rs/wgpu/pull/7845). - Add support for clip-distances feature for Vulkan and GL backends. By @dzamkov in [#7730](https://github.com/gfx-rs/wgpu/pull/7730) - Added `wgpu_types::error::{ErrorType, WebGpuError}` for classification of errors according to WebGPU's [`GPUError`]'s classification scheme, and implement `WebGpuError` for existing errors. This allows users of `wgpu-core` to offload error classification onto the WGPU ecosystem, rather than having to do it themselves without sufficient information. By @ErichDonGubler in [#6547](https://github.com/gfx-rs/wgpu/pull/6547). +- Expose `wgpu::TextureView::descriptor`, `wgpu::TextureView::texture` and `wgpu::TextureView::render_extent`. By @Wumpf in [#????](https://github.com/gfx-rs/wgpu/pull/????) [`GPUError`]: https://www.w3.org/TR/webgpu/#gpuerror From 77e2cd92580cd095a9e38a516148a20eb6227766 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 8 Jul 2025 10:18:16 +0200 Subject: [PATCH 6/8] cargo fmt --- wgpu/src/api/device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/api/device.rs b/wgpu/src/api/device.rs index e5fce1e3e8c..99ed5071df9 100644 --- a/wgpu/src/api/device.rs +++ b/wgpu/src/api/device.rs @@ -278,7 +278,7 @@ impl Device { Texture { inner: texture, descriptor: TextureDescriptor { - label: None, + label: None, view_formats: &[], ..desc.clone() }, From f6131dd20f58057ab2ca05c4aa67fbdd7dd242b6 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 8 Jul 2025 10:19:45 +0200 Subject: [PATCH 7/8] fill changelog number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37d795baa67..a69f551c5e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,7 +59,7 @@ Bottom level categories: - Add acceleration structure limits. By @Vecvec in [#7845](https://github.com/gfx-rs/wgpu/pull/7845). - Add support for clip-distances feature for Vulkan and GL backends. By @dzamkov in [#7730](https://github.com/gfx-rs/wgpu/pull/7730) - Added `wgpu_types::error::{ErrorType, WebGpuError}` for classification of errors according to WebGPU's [`GPUError`]'s classification scheme, and implement `WebGpuError` for existing errors. This allows users of `wgpu-core` to offload error classification onto the WGPU ecosystem, rather than having to do it themselves without sufficient information. By @ErichDonGubler in [#6547](https://github.com/gfx-rs/wgpu/pull/6547). -- Expose `wgpu::TextureView::descriptor`, `wgpu::TextureView::texture` and `wgpu::TextureView::render_extent`. By @Wumpf in [#????](https://github.com/gfx-rs/wgpu/pull/????) +- Expose `wgpu::TextureView::descriptor`, `wgpu::TextureView::texture` and `wgpu::TextureView::render_extent`. By @Wumpf in [#7895](https://github.com/gfx-rs/wgpu/pull/7895) [`GPUError`]: https://www.w3.org/TR/webgpu/#gpuerror From 37a9c3501d24fe6fccf91ddb7dcd60332f8a9343 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 8 Jul 2025 10:51:58 +0200 Subject: [PATCH 8/8] Account for wgpu views keeping texture alive now --- tests/tests/wgpu-gpu/mem_leaks.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/tests/wgpu-gpu/mem_leaks.rs b/tests/tests/wgpu-gpu/mem_leaks.rs index 0d03f9d3749..fcbf5b54276 100644 --- a/tests/tests/wgpu-gpu/mem_leaks.rs +++ b/tests/tests/wgpu-gpu/mem_leaks.rs @@ -169,8 +169,8 @@ async fn draw_test_with_reports( assert_eq!(report.buffers.num_allocated, 1); assert_eq!(report.texture_views.num_allocated, 1); assert_eq!(report.texture_views.num_kept_from_user, 1); - assert_eq!(report.textures.num_allocated, 0); - assert_eq!(report.textures.num_kept_from_user, 0); + assert_eq!(report.textures.num_allocated, 1); // View keeps texture alive. + assert_eq!(report.textures.num_kept_from_user, 1); // View keeps texture alive. let mut encoder = ctx .device @@ -208,7 +208,7 @@ async fn draw_test_with_reports( assert_eq!(report.command_buffers.num_allocated, 1); assert_eq!(report.render_bundles.num_allocated, 0); assert_eq!(report.texture_views.num_allocated, 1); - assert_eq!(report.textures.num_allocated, 0); + assert_eq!(report.textures.num_allocated, 1); // View keeps texture alive. function(&mut rpass);