From 3f2297a357f2ce362770f8d5b90b616e19a4d0bb Mon Sep 17 00:00:00 2001 From: KirmesBude Date: Fri, 22 Aug 2025 19:53:26 +0200 Subject: [PATCH 1/6] Image reinterpretation methods no longer panic --- crates/bevy_image/src/image.rs | 66 +++++++++++++++++++++----------- examples/2d/tilemap_chunk.rs | 2 +- examples/3d/skybox.rs | 4 +- examples/shader/array_texture.rs | 2 +- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index 9a6fcc63b8a7e..55f1bda3bf1e5 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -1063,21 +1063,20 @@ impl Image { } } - /// Changes the `size`, asserting that the total number of data elements (pixels) remains the - /// same. - /// - /// # Panics - /// Panics if the `new_size` does not have the same volume as to old one. - pub fn reinterpret_size(&mut self, new_size: Extent3d) { - assert_eq!( - new_size.volume(), - self.texture_descriptor.size.volume(), - "Incompatible sizes: old = {:?} new = {:?}", - self.texture_descriptor.size, - new_size - ); + /// Changes the `size` if the total number of data elements (pixels) remains the same. + pub fn reinterpret_size( + &mut self, + new_size: Extent3d, + ) -> Result<(), TextureReinterpretationError> { + if new_size.volume() != self.texture_descriptor.size.volume() { + return Err(TextureReinterpretationError::IncompatibleSizes { + old: self.texture_descriptor.size, + new: new_size, + }); + } self.texture_descriptor.size = new_size; + Ok(()) } /// Resizes the image to the new size, keeping the pixel data intact, anchored at the top-left. @@ -1128,21 +1127,31 @@ impl Image { /// Takes a 2D image containing vertically stacked images of the same size, and reinterprets /// it as a 2D array texture, where each of the stacked images becomes one layer of the /// array. This is primarily for use with the `texture2DArray` shader uniform type. - /// - /// # Panics - /// Panics if the texture is not 2D, has more than one layers or is not evenly dividable into - /// the `layers`. - pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) { + pub fn reinterpret_stacked_2d_as_array( + &mut self, + layers: u32, + ) -> Result<(), TextureReinterpretationError> { // Must be a stacked image, and the height must be divisible by layers. - assert_eq!(self.texture_descriptor.dimension, TextureDimension::D2); - assert_eq!(self.texture_descriptor.size.depth_or_array_layers, 1); - assert_eq!(self.height() % layers, 0); + if self.texture_descriptor.dimension != TextureDimension::D2 { + return Err(TextureReinterpretationError::WrongDimension); + } + if self.texture_descriptor.size.depth_or_array_layers != 1 { + return Err(TextureReinterpretationError::InvalidLayerCount); + } + if self.height() % layers != 0 { + return Err(TextureReinterpretationError::HeightNotDivisableByLayers { + height: self.height(), + layers, + }); + } self.reinterpret_size(Extent3d { width: self.width(), height: self.height() / layers, depth_or_array_layers: layers, - }); + })?; + + Ok(()) } /// Convert a texture from a format to another. Only a few formats are @@ -1741,6 +1750,19 @@ pub enum TranscodeFormat { Rgb8, } +/// An error that occurs when reinterpreting the image. +#[derive(Error, Debug)] +pub enum TextureReinterpretationError { + #[error("incompatible sizes: old = {old:?} new = {new:?}")] + IncompatibleSizes { old: Extent3d, new: Extent3d }, + #[error("must be a 2d image")] + WrongDimension, + #[error("must not already be a layered image")] + InvalidLayerCount, + #[error("can not evenly divide height = {height} by layers = {layers}")] + HeightNotDivisableByLayers { height: u32, layers: u32 }, +} + /// An error that occurs when accessing specific pixels in a texture. #[derive(Error, Debug)] pub enum TextureAccessError { diff --git a/examples/2d/tilemap_chunk.rs b/examples/2d/tilemap_chunk.rs index ac2df4e26e577..a6891682886dd 100644 --- a/examples/2d/tilemap_chunk.rs +++ b/examples/2d/tilemap_chunk.rs @@ -64,7 +64,7 @@ fn update_tileset_image( for event in events.read() { if event.is_loaded_with_dependencies(chunk.tileset.id()) { let image = images.get_mut(&chunk.tileset).unwrap(); - image.reinterpret_stacked_2d_as_array(4); + image.reinterpret_stacked_2d_as_array(4).unwrap(); } } } diff --git a/examples/3d/skybox.rs b/examples/3d/skybox.rs index 22be77c4e7369..bba64b1fe7774 100644 --- a/examples/3d/skybox.rs +++ b/examples/3d/skybox.rs @@ -157,7 +157,9 @@ fn asset_loaded( // NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture, // so they appear as one texture. The following code reconfigures the texture as necessary. if image.texture_descriptor.array_layer_count() == 1 { - image.reinterpret_stacked_2d_as_array(image.height() / image.width()); + image + .reinterpret_stacked_2d_as_array(image.height() / image.width()) + .unwrap(); image.texture_view_descriptor = Some(TextureViewDescriptor { dimension: Some(TextureViewDimension::Cube), ..default() diff --git a/examples/shader/array_texture.rs b/examples/shader/array_texture.rs index 3897d2dca7c36..c371d4a20f7e3 100644 --- a/examples/shader/array_texture.rs +++ b/examples/shader/array_texture.rs @@ -65,7 +65,7 @@ fn create_array_texture( // Create a new array texture asset from the loaded texture. let array_layers = 4; - image.reinterpret_stacked_2d_as_array(array_layers); + image.reinterpret_stacked_2d_as_array(array_layers).unwrap(); // Spawn some cubes using the array texture let mesh_handle = meshes.add(Cuboid::default()); From b00a13986e606e5c5e984a62d8479a4c694ed955 Mon Sep 17 00:00:00 2001 From: KirmesBude Date: Sat, 30 Aug 2025 11:39:52 +0200 Subject: [PATCH 2/6] Fix typo --- crates/bevy_image/src/image.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index 55f1bda3bf1e5..040a248aee320 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -1139,7 +1139,7 @@ impl Image { return Err(TextureReinterpretationError::InvalidLayerCount); } if self.height() % layers != 0 { - return Err(TextureReinterpretationError::HeightNotDivisableByLayers { + return Err(TextureReinterpretationError::HeightNotDivisibleByLayers { height: self.height(), layers, }); @@ -1760,7 +1760,7 @@ pub enum TextureReinterpretationError { #[error("must not already be a layered image")] InvalidLayerCount, #[error("can not evenly divide height = {height} by layers = {layers}")] - HeightNotDivisableByLayers { height: u32, layers: u32 }, + HeightNotDivisibleByLayers { height: u32, layers: u32 }, } /// An error that occurs when accessing specific pixels in a texture. From 5357efda114745661bc19b7e8b04a3486c21a382 Mon Sep 17 00:00:00 2001 From: KirmesBude Date: Sat, 30 Aug 2025 22:53:07 +0200 Subject: [PATCH 3/6] Use expect in examples --- examples/2d/tilemap_chunk.rs | 4 +++- examples/3d/skybox.rs | 2 +- examples/shader/array_texture.rs | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/2d/tilemap_chunk.rs b/examples/2d/tilemap_chunk.rs index a6891682886dd..b47f745ce9185 100644 --- a/examples/2d/tilemap_chunk.rs +++ b/examples/2d/tilemap_chunk.rs @@ -64,7 +64,9 @@ fn update_tileset_image( for event in events.read() { if event.is_loaded_with_dependencies(chunk.tileset.id()) { let image = images.get_mut(&chunk.tileset).unwrap(); - image.reinterpret_stacked_2d_as_array(4).unwrap(); + image + .reinterpret_stacked_2d_as_array(4) + .expect("asset should be 2d texture with height evenly divisible by 4"); } } } diff --git a/examples/3d/skybox.rs b/examples/3d/skybox.rs index bba64b1fe7774..ee633fbf7c208 100644 --- a/examples/3d/skybox.rs +++ b/examples/3d/skybox.rs @@ -159,7 +159,7 @@ fn asset_loaded( if image.texture_descriptor.array_layer_count() == 1 { image .reinterpret_stacked_2d_as_array(image.height() / image.width()) - .unwrap(); + .expect("asset should be 2d texture and height will always be evenly divisible with the given layers"); image.texture_view_descriptor = Some(TextureViewDescriptor { dimension: Some(TextureViewDimension::Cube), ..default() diff --git a/examples/shader/array_texture.rs b/examples/shader/array_texture.rs index c371d4a20f7e3..51703ae14837e 100644 --- a/examples/shader/array_texture.rs +++ b/examples/shader/array_texture.rs @@ -65,7 +65,9 @@ fn create_array_texture( // Create a new array texture asset from the loaded texture. let array_layers = 4; - image.reinterpret_stacked_2d_as_array(array_layers).unwrap(); + image + .reinterpret_stacked_2d_as_array(array_layers) + .expect("asset should be 2d texture with height evenly divisible by array_layers"); // Spawn some cubes using the array texture let mesh_handle = meshes.add(Cuboid::default()); From 831c41324a8a1de1c4011456c0be32f30fe3680a Mon Sep 17 00:00:00 2001 From: KirmesBude Date: Sat, 30 Aug 2025 23:10:59 +0200 Subject: [PATCH 4/6] Add migration guide. --- .../image_reinterpret_returns_result.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 release-content/migration-guides/image_reinterpret_returns_result.md diff --git a/release-content/migration-guides/image_reinterpret_returns_result.md b/release-content/migration-guides/image_reinterpret_returns_result.md new file mode 100644 index 0000000000000..29280afa5a5e6 --- /dev/null +++ b/release-content/migration-guides/image_reinterpret_returns_result.md @@ -0,0 +1,10 @@ +--- +title: "Image::reinterpret_size and Image::reinterpret_stacked_2d_as_array now return a Result" +pull_requests: [20797] +--- + +`Image::reinterpret_size` and `Image::reinterpret_stacked_2d_as_array` now return a `Result` instead of panicking. + +Previously, calling this method on image assets that did not conform to certain constraints could lead to runtime panics. The new return type makes the API safer and more explicit about the constraints. + +To migrate your code, you will need to handle the `Result` returned by `Image::reinterpret_size` or `Image::reinterpret_stacked_2d_as_array`. From 9e7312266352575b345319d65de8648ed3231b18 Mon Sep 17 00:00:00 2001 From: KirmesBude Date: Sat, 30 Aug 2025 23:12:24 +0200 Subject: [PATCH 5/6] Add comment describing error variant to reinterpret_size --- crates/bevy_image/src/image.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index 040a248aee320..c3f9029ee79b2 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -1064,6 +1064,7 @@ impl Image { } /// Changes the `size` if the total number of data elements (pixels) remains the same. + /// If not, returns [`TextureReinterpretationError::IncompatibleSizes`]. pub fn reinterpret_size( &mut self, new_size: Extent3d, From 7b4a673b3cae92874a2cb5461682cffd659eca7c Mon Sep 17 00:00:00 2001 From: KirmesBude Date: Sun, 31 Aug 2025 21:40:25 +0200 Subject: [PATCH 6/6] s/the/an --- crates/bevy_image/src/image.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index c3f9029ee79b2..3f52d494a38e2 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -1751,7 +1751,7 @@ pub enum TranscodeFormat { Rgb8, } -/// An error that occurs when reinterpreting the image. +/// An error that occurs when reinterpreting an image. #[derive(Error, Debug)] pub enum TextureReinterpretationError { #[error("incompatible sizes: old = {old:?} new = {new:?}")]