Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/bevy_image/src/compressed_image_saver.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings};

use bevy_asset::saver::{AssetSaver, SavedAsset};
use core::num::NonZero;
use futures_lite::AsyncWriteExt;
use thiserror::Error;

Expand Down Expand Up @@ -64,11 +65,14 @@ impl AssetSaver for CompressedImageSaver {
};

writer.write_all(&compressed_basis_data).await?;

let layers = NonZero::new(image.texture_descriptor.size.depth_or_array_layers);
Ok(ImageLoaderSettings {
format: ImageFormatSetting::Format(ImageFormat::Basis),
is_srgb,
sampler: image.sampler.clone(),
asset_usage: image.asset_usage,
layers,
})
}
}
66 changes: 44 additions & 22 deletions crates/bevy_image/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1042,21 +1042,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 @@ -1106,21 +1105,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
Expand Down Expand Up @@ -1719,6 +1728,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 {
Expand Down
25 changes: 22 additions & 3 deletions crates/bevy_image/src/image_loader.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use crate::image::{Image, ImageFormat, ImageType, TextureError};
use core::num::NonZero;

use crate::{
image::{Image, ImageFormat, ImageType, TextureError},
TextureReinterpretationError,
};
use bevy_asset::{io::Reader, AssetLoader, LoadContext, RenderAssetUsages};
use thiserror::Error;

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,
/// If the image should be loaded as a stacked 2d array
/// image with the given number of layers
pub layers: Option<NonZero<u32>>,
}

impl Default for ImageLoaderSettings {
Expand All @@ -120,6 +128,7 @@ impl Default for ImageLoaderSettings {
is_srgb: true,
sampler: ImageSampler::Default,
asset_usage: RenderAssetUsages::default(),
layers: Default::default(),
}
}
}
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,8 @@ 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 +192,13 @@ impl AssetLoader for ImageLoader {
.map_err(|err| FileTextureError {
error: err,
path: format!("{}", load_context.path().display()),
})?)
})?;

if let Some(layers) = settings.layers {
image.reinterpret_stacked_2d_as_array(layers.into())?;
}

Ok(image)
}

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

use core::num::NonZero;

use bevy::{
image::ImageLoaderSettings,
prelude::*,
sprite::{TileData, TilemapChunk, TilemapChunkTileData},
};
Expand All @@ -11,7 +14,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 @@ -39,12 +42,18 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
})
.collect();

let layers = 4;
commands.spawn((
TilemapChunk {
chunk_size,
tile_display_size,
tileset: assets.load("textures/array_texture.png"),
..default()
tileset: assets.load_with_settings(
"textures/array_texture.png",
move |settings: &mut ImageLoaderSettings| {
settings.layers = NonZero::new(layers);
},
),
..Default::default()
},
TilemapChunkTileData(tile_data),
UpdateTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
Expand All @@ -55,20 +64,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
4 changes: 3 additions & 1 deletion examples/3d/skybox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,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()
Expand Down
56 changes: 17 additions & 39 deletions examples/shader/array_texture.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
//! This example illustrates how to create a texture for use with a `texture_2d_array<f32>` shader
//! uniform variable.

use core::num::NonZero;

use bevy::{
prelude::*, reflect::TypePath, render::render_resource::AsBindGroup, shader::ShaderRef,
image::ImageLoaderSettings, prelude::*, reflect::TypePath,
render::render_resource::AsBindGroup, shader::ShaderRef,
};

/// This example uses a shader source file from the assets subdirectory
Expand All @@ -15,23 +18,15 @@ fn main() {
MaterialPlugin::<ArrayTextureMaterial>::default(),
))
.add_systems(Startup, setup)
.add_systems(Update, create_array_texture)
.run();
}

#[derive(Resource)]
struct LoadingTexture {
is_loaded: bool,
handle: Handle<Image>,
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Start loading the texture.
commands.insert_resource(LoadingTexture {
is_loaded: false,
handle: asset_server.load("textures/array_texture.png"),
});

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ArrayTextureMaterial>>,
) {
// light
commands.spawn((
DirectionalLight::default(),
Expand All @@ -43,34 +38,17 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
Camera3d::default(),
Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::new(1.5, 0.0, 0.0), Vec3::Y),
));
}

fn create_array_texture(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut loading_texture: ResMut<LoadingTexture>,
mut images: ResMut<Assets<Image>>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ArrayTextureMaterial>>,
) {
if loading_texture.is_loaded
|| !asset_server
.load_state(loading_texture.handle.id())
.is_loaded()
{
return;
}
loading_texture.is_loaded = true;
let image = images.get_mut(&loading_texture.handle).unwrap();

// Create a new array texture asset from the loaded texture.
let array_layers = 4;
image.reinterpret_stacked_2d_as_array(array_layers);

// Spawn some cubes using the array texture
let array_layers = 4;
let mesh_handle = meshes.add(Cuboid::default());
let material_handle = materials.add(ArrayTextureMaterial {
array_texture: loading_texture.handle.clone(),
array_texture: asset_server.load_with_settings(
"textures/array_texture.png",
move |settings: &mut ImageLoaderSettings| {
settings.layers = NonZero::new(array_layers);
},
),
});
for x in -5..=5 {
commands.spawn((
Expand Down
Loading