Skip to content
1 change: 1 addition & 0 deletions crates/bevy_image/src/compressed_image_saver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ impl AssetSaver for CompressedImageSaver {
is_srgb,
sampler: image.sampler.clone(),
asset_usage: image.asset_usage,
view_dimension: None,
})
}
}
100 changes: 77 additions & 23 deletions crates/bevy_image/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use thiserror::Error;
use wgpu_types::{
AddressMode, CompareFunction, Extent3d, Features, FilterMode, SamplerBorderColor,
SamplerDescriptor, TextureDataOrder, TextureDescriptor, TextureDimension, TextureFormat,
TextureUsages, TextureViewDescriptor,
TextureUsages, TextureViewDescriptor, TextureViewDimension,
};

/// Trait used to provide default values for Bevy-external types that
Expand Down Expand Up @@ -717,6 +717,38 @@ impl ImageSamplerDescriptor {
}
}

/// Dimensions of a particular texture view.
///
/// This type mirrors [`TextureViewDimension`].
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ImageTextureViewDimension {
/// A one dimensional texture. `texture_1d` in WGSL and `texture1D` in GLSL.
D1,
/// A two dimensional texture. `texture_2d` in WGSL and `texture2D` in GLSL.
D2,
/// A two dimensional array texture. `texture_2d_array` in WGSL and `texture2DArray` in GLSL.
D2Array(u32),
/// A cubemap texture. `texture_cube` in WGSL and `textureCube` in GLSL.
Cube,
/// A cubemap array texture. `texture_cube_array` in WGSL and `textureCubeArray` in GLSL.
CubeArray(u32),
/// A three dimensional texture. `texture_3d` in WGSL and `texture3D` in GLSL.
D3,
}
Comment on lines +720 to +737
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way we get serde support and can encode the layers in the type.


impl From<ImageTextureViewDimension> for TextureViewDimension {
fn from(value: ImageTextureViewDimension) -> Self {
match value {
ImageTextureViewDimension::D1 => TextureViewDimension::D1,
ImageTextureViewDimension::D2 => TextureViewDimension::D2,
ImageTextureViewDimension::D2Array(_) => TextureViewDimension::D2Array,
ImageTextureViewDimension::Cube => TextureViewDimension::Cube,
ImageTextureViewDimension::CubeArray(_) => TextureViewDimension::CubeArray,
ImageTextureViewDimension::D3 => TextureViewDimension::D3,
}
}
}

impl From<ImageAddressMode> for AddressMode {
fn from(value: ImageAddressMode) -> Self {
match value {
Expand Down Expand Up @@ -1063,21 +1095,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.
Expand Down Expand Up @@ -1128,21 +1159,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::HeightNotDivisibleByLayers {
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
Expand Down Expand Up @@ -1741,6 +1782,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}")]
HeightNotDivisibleByLayers { height: u32, layers: u32 },
}

/// An error that occurs when accessing specific pixels in a texture.
#[derive(Error, Debug)]
pub enum TextureAccessError {
Expand Down
42 changes: 39 additions & 3 deletions crates/bevy_image/src/image_loader.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use crate::image::{Image, ImageFormat, ImageType, TextureError};
use crate::{
image::{Image, ImageFormat, ImageType, TextureError},
ImageTextureViewDimension, TextureReinterpretationError,
};
use bevy_asset::{io::Reader, AssetLoader, LoadContext, RenderAssetUsages};
use bevy_utils::default;
use thiserror::Error;
use wgpu_types::TextureViewDescriptor;

use super::{CompressedImageFormats, ImageSampler};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -111,6 +116,9 @@ pub struct ImageLoaderSettings {
/// Where the asset will be used - see the docs on
/// [`RenderAssetUsages`] for details.
pub asset_usage: RenderAssetUsages,
/// Dimension of this image's texture view.
/// None, lets the loader decide what the dimensions are from the image (if supported).
pub view_dimension: Option<ImageTextureViewDimension>,
}

impl Default for ImageLoaderSettings {
Expand All @@ -120,6 +128,7 @@ impl Default for ImageLoaderSettings {
is_srgb: true,
sampler: ImageSampler::Default,
asset_usage: RenderAssetUsages::default(),
view_dimension: None,
}
}
}
Expand All @@ -134,6 +143,9 @@ pub enum ImageLoaderError {
/// An error occurred while trying to decode the image bytes.
#[error("Could not load texture file: {0}")]
FileTexture(#[from] FileTextureError),
/// An error occurred while trying to reinterpret the image (e.g. loading as a stacked 2d array).
#[error("Could not reinterpret image: {0}")]
ReinterpretationError(#[from] TextureReinterpretationError),
}

impl AssetLoader for ImageLoader {
Expand Down Expand Up @@ -168,7 +180,7 @@ impl AssetLoader for ImageLoader {
)?)
}
};
Ok(Image::from_buffer(
let mut image = Image::from_buffer(
&bytes,
image_type,
self.supported_compressed_formats,
Expand All @@ -179,7 +191,31 @@ impl AssetLoader for ImageLoader {
.map_err(|err| FileTextureError {
error: err,
path: format!("{}", load_context.path().display()),
})?)
})?;

if let Some(view_dimension) = &settings.view_dimension {
match view_dimension {
ImageTextureViewDimension::D2Array(layers) => {
image.reinterpret_stacked_2d_as_array(*layers)?;
}
ImageTextureViewDimension::Cube => {
image.reinterpret_stacked_2d_as_array(image.height() / image.width())?;
}
ImageTextureViewDimension::CubeArray(layers) => {
image.reinterpret_stacked_2d_as_array(
image.height() / image.width() * *layers,
)?;
}
Comment on lines +201 to +207
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea about Cube or CubeArray textures. Not sure if this was something specific to the skybox example or if Cube will always be height/width.

_ => {}
}

image.texture_view_descriptor = Some(TextureViewDescriptor {
dimension: Some(view_dimension.clone().into()),
..default()
});
}

Ok(image)
}

fn extensions(&self) -> &[&str] {
Expand Down
24 changes: 8 additions & 16 deletions examples/2d/tilemap_chunk.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Shows a tilemap chunk rendered with a single draw call.

use bevy::{
image::{ImageLoaderSettings, ImageTextureViewDimension},
prelude::*,
sprite_render::{TileData, TilemapChunk, TilemapChunkTileData},
};
Expand All @@ -11,7 +12,7 @@ fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
.add_systems(Startup, setup)
.add_systems(Update, (update_tileset_image, update_tilemap))
.add_systems(Update, update_tilemap)
.run();
}

Expand Down Expand Up @@ -43,7 +44,12 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
TilemapChunk {
chunk_size,
tile_display_size,
tileset: assets.load("textures/array_texture.png"),
tileset: assets.load_with_settings(
"textures/array_texture.png",
|settings: &mut ImageLoaderSettings| {
settings.view_dimension = Some(ImageTextureViewDimension::D2Array(4));
},
),
..default()
},
TilemapChunkTileData(tile_data),
Expand All @@ -55,20 +61,6 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
commands.insert_resource(SeededRng(rng));
}

fn update_tileset_image(
chunk_query: Single<&TilemapChunk>,
mut events: EventReader<AssetEvent<Image>>,
mut images: ResMut<Assets<Image>>,
) {
let chunk = *chunk_query;
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);
}
}
}

fn update_tilemap(
time: Res<Time>,
mut query: Query<(&mut TilemapChunkTileData, &mut UpdateTimer)>,
Expand Down
Loading
Loading