Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Texture Atlas rework #5103

Merged
merged 16 commits into from
Jan 16, 2024
25 changes: 15 additions & 10 deletions crates/bevy_sprite/src/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
texture_atlas::{TextureAtlas, TextureAtlasSprite},
Sprite,
};
use crate::{Sprite, TextureAtlas};
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
use bevy_render::{
Expand Down Expand Up @@ -34,17 +31,25 @@ impl Default for SpriteBundle {
}
}
}

/// A Bundle of components for drawing a single sprite from a sprite sheet (also referred
/// to as a `TextureAtlas`)
/// to as a `TextureAtlas`) or for animated sprites.
///
/// Note:
/// This bundle is identical to [`SpriteBundle`] with an additional [`TextureAtlas`] component.
///
/// Check the following examples for usage:
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
#[derive(Bundle, Clone, Default)]
pub struct SpriteSheetBundle {
/// The specific sprite from the texture atlas to be drawn, defaulting to the sprite at index 0.
pub sprite: TextureAtlasSprite,
/// A handle to the texture atlas that holds the sprite images
pub texture_atlas: Handle<TextureAtlas>,
/// Data pertaining to how the sprite is drawn on the screen
pub sprite: Sprite,
pub transform: Transform,
pub global_transform: GlobalTransform,
/// The sprite sheet base texture
pub texture: Handle<Image>,
/// The sprite sheet texture atlas, allowing to draw a custom section of `texture`.
pub atlas: TextureAtlas,
/// User indication of whether an entity is visible
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
Expand Down
26 changes: 17 additions & 9 deletions crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crate::TextureAtlas;
use bevy_asset::Assets;
use crate::TextureAtlasLayout;
use bevy_asset::{Assets, Handle};
use bevy_math::{IVec2, Rect, Vec2};
use bevy_render::texture::{Image, TextureFormatPixelInfo};
use guillotiere::{size2, Allocation, AtlasAllocator};

/// Helper utility to update [`TextureAtlas`] on the fly.
/// Helper utility to update [`TextureAtlasLayout`] on the fly.
///
/// Helpful in cases when texture is created procedurally,
/// e.g: in a font glyph [`TextureAtlas`], only add the [`Image`] texture for letters to be rendered.
/// e.g: in a font glyph [`TextureAtlasLayout`], only add the [`Image`] texture for letters to be rendered.
pub struct DynamicTextureAtlasBuilder {
atlas_allocator: AtlasAllocator,
padding: i32,
Expand All @@ -27,24 +27,32 @@ impl DynamicTextureAtlasBuilder {
}
}

/// Add a new texture to [`TextureAtlas`].
/// It is user's responsibility to pass in the correct [`TextureAtlas`]
/// Add a new texture to `atlas_layout`
/// It is user's responsibility to pass in the correct [`TextureAtlasLayout`]
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Arguments
///
/// * `altas_layout` - The atlas to add the texture to
/// * `textures` - The texture assets container
/// * `texture` - The new texture to add to the atlas
/// * `atlas_texture_handle` - The atlas texture to edit
pub fn add_texture(
&mut self,
texture_atlas: &mut TextureAtlas,
atlas_layout: &mut TextureAtlasLayout,
textures: &mut Assets<Image>,
texture: &Image,
atlas_texture_handle: &Handle<Image>,
) -> Option<usize> {
let allocation = self.atlas_allocator.allocate(size2(
texture.texture_descriptor.size.width as i32 + self.padding,
texture.texture_descriptor.size.height as i32 + self.padding,
));
if let Some(allocation) = allocation {
let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap();
let atlas_texture = textures.get_mut(atlas_texture_handle).unwrap();
self.place_texture(atlas_texture, allocation, texture);
let mut rect: Rect = to_rect(allocation.rectangle);
rect.max -= self.padding as f32;
Some(texture_atlas.add_texture(rect))
Some(atlas_layout.add_texture(rect))
} else {
None
}
Expand Down
44 changes: 15 additions & 29 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub mod prelude {
pub use crate::{
bundle::{SpriteBundle, SpriteSheetBundle},
sprite::Sprite,
texture_atlas::{TextureAtlas, TextureAtlasSprite},
texture_atlas::{TextureAtlas, TextureAtlasLayout},
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
};
}
Expand Down Expand Up @@ -59,11 +59,11 @@ impl Plugin for SpritePlugin {
let mut shaders = app.world.resource_mut::<Assets<Shader>>();
let sprite_shader = Shader::from_wgsl(include_str!("render/sprite.wgsl"));
shaders.set_untracked(SPRITE_SHADER_HANDLE, sprite_shader);
app.add_asset::<TextureAtlas>()
.register_asset_reflect::<TextureAtlas>()
app.add_asset::<TextureAtlasLayout>()
.register_asset_reflect::<TextureAtlasLayout>()
.register_type::<Sprite>()
.register_type::<TextureAtlasSprite>()
.register_type::<Anchor>()
.register_type::<TextureAtlas>()
.register_type::<Mesh2dHandle>()
.add_plugin(Mesh2dRenderPlugin)
.add_plugin(ColorMaterialPlugin)
Expand Down Expand Up @@ -107,14 +107,10 @@ pub fn calculate_bounds_2d(
mut commands: Commands,
meshes: Res<Assets<Mesh>>,
images: Res<Assets<Image>>,
atlases: Res<Assets<TextureAtlas>>,
atlases: Res<Assets<TextureAtlasLayout>>,
meshes_without_aabb: Query<(Entity, &Mesh2dHandle), (Without<Aabb>, Without<NoFrustumCulling>)>,
sprites_without_aabb: Query<
(Entity, &Sprite, &Handle<Image>),
(Without<Aabb>, Without<NoFrustumCulling>),
>,
atlases_without_aabb: Query<
(Entity, &TextureAtlasSprite, &Handle<TextureAtlas>),
(Entity, &Sprite, &Handle<Image>, Option<&TextureAtlas>),
(Without<Aabb>, Without<NoFrustumCulling>),
>,
) {
Expand All @@ -125,27 +121,17 @@ pub fn calculate_bounds_2d(
}
}
}
for (entity, sprite, texture_handle) in &sprites_without_aabb {
if let Some(size) = sprite
.custom_size
.or_else(|| images.get(texture_handle).map(|image| image.size()))
{
let aabb = Aabb {
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
half_extents: (0.5 * size).extend(0.0).into(),
};
commands.entity(entity).insert(aabb);
}
}
for (entity, atlas_sprite, atlas_handle) in &atlases_without_aabb {
if let Some(size) = atlas_sprite.custom_size.or_else(|| {
atlases
.get(atlas_handle)
.and_then(|atlas| atlas.textures.get(atlas_sprite.index))
.map(|rect| (rect.min - rect.max).abs())
for (entity, sprite, texture_handle, atlas) in &sprites_without_aabb {
if let Some(size) = sprite.custom_size.or_else(|| match atlas {
// We default to the texture size for regular sprites
None => images.get(texture_handle).map(|image| image.size()),
// We default to the drawn rect for atlas sprites
Some(atlas) => atlas
.texture_rect(&atlases)
.map(|rect| (rect.min - rect.max).abs()),
}) {
let aabb = Aabb {
center: (-atlas_sprite.anchor.as_vec() * size).extend(0.0).into(),
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
half_extents: (0.5 * size).extend(0.0).into(),
};
commands.entity(entity).insert(aabb);
Expand Down
55 changes: 7 additions & 48 deletions crates/bevy_sprite/src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use std::cmp::Ordering;

use crate::{
texture_atlas::{TextureAtlas, TextureAtlasSprite},
Sprite, SPRITE_SHADER_HANDLE,
};
use crate::{Sprite, TextureAtlas, TextureAtlasLayout, SPRITE_SHADER_HANDLE};
use bevy_asset::{AssetEvent, Assets, Handle, HandleId};
use bevy_core_pipeline::{
core_2d::Transparent2d,
Expand Down Expand Up @@ -347,37 +344,31 @@ pub fn extract_sprite_events(

pub fn extract_sprites(
mut extracted_sprites: ResMut<ExtractedSprites>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
sprite_query: Extract<
Query<(
Entity,
&ComputedVisibility,
&Sprite,
&GlobalTransform,
&Handle<Image>,
)>,
>,
atlas_query: Extract<
Query<(
Entity,
&ComputedVisibility,
&TextureAtlasSprite,
&GlobalTransform,
&Handle<TextureAtlas>,
Option<&TextureAtlas>,
)>,
>,
) {
extracted_sprites.sprites.clear();
for (entity, visibility, sprite, transform, handle) in sprite_query.iter() {
for (entity, visibility, sprite, transform, handle, sheet) in sprite_query.iter() {
if !visibility.is_visible() {
continue;
}
let rect = sheet.and_then(|s| s.texture_rect(&texture_atlases));

// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive
extracted_sprites.sprites.push(ExtractedSprite {
entity,
color: sprite.color,
transform: *transform,
rect: sprite.rect,
rect,
// Pass the custom size
custom_size: sprite.custom_size,
flip_x: sprite.flip_x,
Expand All @@ -386,38 +377,6 @@ pub fn extract_sprites(
anchor: sprite.anchor.as_vec(),
});
}
for (entity, visibility, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
if !visibility.is_visible() {
continue;
}
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
let rect = Some(
*texture_atlas
.textures
.get(atlas_sprite.index)
.unwrap_or_else(|| {
panic!(
"Sprite index {:?} does not exist for texture atlas handle {:?}.",
atlas_sprite.index,
texture_atlas_handle.id(),
)
}),
);
extracted_sprites.sprites.push(ExtractedSprite {
entity,
color: atlas_sprite.color,
transform: *transform,
// Select the area in the texture atlas
rect,
// Pass the custom size
custom_size: atlas_sprite.custom_size,
flip_x: atlas_sprite.flip_x,
flip_y: atlas_sprite.flip_y,
image_handle_id: texture_atlas.texture.id(),
anchor: atlas_sprite.anchor.as_vec(),
});
}
}
}

#[repr(C)]
Expand Down
Loading