Skip to content

Commit

Permalink
Allow TextureAtlasBuilder in AssetLoader (#11548)
Browse files Browse the repository at this point in the history
# Objective

Allow TextureAtlasBuilder in AssetLoader.
Fixes #2987

## Solution

- TextureAtlasBuilder no longer hold just AssetIds that are used to
retrieve the actual image data in `finish`, but &Image instead.
- TextureAtlasBuilder now required AssetId only optionally (and it is
only used to retrieve the index from the AssetId in TextureAtlasLayout),

## Issues

- The issue mentioned here
#11474 (comment)
now also extends to the actual atlas texture. In short: Calling
add_texture multiple times for the same texture will lead to duplicate
image data in the atlas texture and additional indices.
If you provide an AssetId we can probably do something to de-duplicate
the entries while keeping insertion order (suggestions welcome on how
exactly). But if you don't then we are out of luck (unless we can and
want to hash the image, which I do not think we want).

---

## Changelog

### Changed
- TextureAtlasBuilder `add_texture` can be called without providing an
AssetId
- TextureAtlasBuilder `finish` no longer takes Assets<Image> and no
longer returns a Handle<Image>

## Migration Guide

- For `add_texture` you need to wrap your AssetId in Some
- `finish` now returns the atlas texture image directly instead of a
handle. Provide the atlas texture to `add` on Assets<Texture> to get a
Handle<Image>
  • Loading branch information
KirmesBude authored Jan 27, 2024
1 parent b35b9e5 commit 3851679
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 30 deletions.
56 changes: 28 additions & 28 deletions crates/bevy_sprite/src/texture_atlas_builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use bevy_asset::Handle;
use bevy_asset::{AssetId, Assets};
use bevy_asset::AssetId;
use bevy_log::{debug, error, warn};
use bevy_math::{Rect, UVec2, Vec2};
use bevy_render::{
Expand Down Expand Up @@ -28,9 +27,9 @@ pub enum TextureAtlasBuilderError {
#[must_use]
/// A builder which is used to create a texture atlas from many individual
/// sprites.
pub struct TextureAtlasBuilder {
/// Collection of textures and their size to be packed into an atlas
textures_to_place: Vec<(AssetId<Image>, Extent3d)>,
pub struct TextureAtlasBuilder<'a> {
/// Collection of texture's asset id (optional) and image data to be packed into an atlas
textures_to_place: Vec<(Option<AssetId<Image>>, &'a Image)>,
/// The initial atlas size in pixels.
initial_size: Vec2,
/// The absolute maximum size of the texture atlas in pixels.
Expand All @@ -43,7 +42,7 @@ pub struct TextureAtlasBuilder {
padding: UVec2,
}

impl Default for TextureAtlasBuilder {
impl Default for TextureAtlasBuilder<'_> {
fn default() -> Self {
Self {
textures_to_place: Vec::new(),
Expand All @@ -58,7 +57,7 @@ impl Default for TextureAtlasBuilder {

pub type TextureAtlasBuilderResult<T> = Result<T, TextureAtlasBuilderError>;

impl TextureAtlasBuilder {
impl<'a> TextureAtlasBuilder<'a> {
/// Sets the initial size of the atlas in pixels.
pub fn initial_size(mut self, size: Vec2) -> Self {
self.initial_size = size;
Expand All @@ -85,10 +84,10 @@ impl TextureAtlasBuilder {

/// Adds a texture to be copied to the texture atlas.
///
/// Optionally an asset id can be passed that can later be used with the texture layout to retrieve the index of this texture.
/// The insertion order will reflect the index of the added texture in the finished texture atlas.
pub fn add_texture(&mut self, image_id: AssetId<Image>, texture: &Image) {
self.textures_to_place
.push((image_id, texture.texture_descriptor.size));
pub fn add_texture(&mut self, image_id: Option<AssetId<Image>>, texture: &'a Image) {
self.textures_to_place.push((image_id, texture));
}

/// Sets the amount of padding in pixels to add between the textures in the texture atlas.
Expand Down Expand Up @@ -149,14 +148,12 @@ impl TextureAtlasBuilder {
}
}

/// Consumes the builder, and returns the newly created texture handle and
/// the assciated atlas layout.
/// Consumes the builder, and returns the newly created texture atlas and
/// the associated atlas layout.
///
/// Assigns indices to the textures based on the insertion order.
/// Internally it copies all rectangles from the textures and copies them
/// into a new texture.
/// It is not useful to hold a strong handle to the texture afterwards else
/// it will exist twice in memory.
///
/// # Usage
///
Expand All @@ -172,7 +169,8 @@ impl TextureAtlasBuilder {
/// // Customize it
/// // ...
/// // Build your texture and the atlas layout
/// let (atlas_layout, texture) = builder.finish(&mut textures).unwrap();
/// let (atlas_layout, texture) = builder.finish().unwrap();
/// let texture = textures.add(texture);
/// let layout = layouts.add(atlas_layout);
/// // Spawn your sprite
/// commands.spawn(SpriteSheetBundle {
Expand All @@ -190,10 +188,7 @@ impl TextureAtlasBuilder {
///
/// If there is not enough space in the atlas texture, an error will
/// be returned. It is then recommended to make a larger sprite sheet.
pub fn finish(
self,
textures: &mut Assets<Image>,
) -> Result<(TextureAtlasLayout, Handle<Image>), TextureAtlasBuilderError> {
pub fn finish(self) -> Result<(TextureAtlasLayout, Image), TextureAtlasBuilderError> {
let initial_width = self.initial_size.x as u32;
let initial_height = self.initial_size.y as u32;
let max_width = self.max_size.x as u32;
Expand All @@ -203,14 +198,18 @@ impl TextureAtlasBuilder {
let mut current_height = initial_height;
let mut rect_placements = None;
let mut atlas_texture = Image::default();
let mut rects_to_place = GroupedRectsToPlace::<AssetId<Image>>::new();
let mut rects_to_place = GroupedRectsToPlace::<usize>::new();

// Adds textures to rectangle group packer
for (image_id, size) in &self.textures_to_place {
for (index, (_, texture)) in self.textures_to_place.iter().enumerate() {
rects_to_place.push_rect(
*image_id,
index,
None,
RectToInsert::new(size.width + self.padding.x, size.height + self.padding.y, 1),
RectToInsert::new(
texture.width() + self.padding.x,
texture.height() + self.padding.y,
1,
),
);
}

Expand Down Expand Up @@ -263,17 +262,18 @@ impl TextureAtlasBuilder {
let mut texture_rects = Vec::with_capacity(rect_placements.packed_locations().len());
let mut texture_ids = HashMap::default();
// We iterate through the textures to place to respect the insertion order for the texture indices
for (image_id, _) in &self.textures_to_place {
let (_, packed_location) = rect_placements.packed_locations().get(image_id).unwrap();
for (index, (image_id, texture)) in self.textures_to_place.iter().enumerate() {
let (_, packed_location) = rect_placements.packed_locations().get(&index).unwrap();

let texture = textures.get(*image_id).unwrap();
let min = Vec2::new(packed_location.x() as f32, packed_location.y() as f32);
let max = min
+ Vec2::new(
(packed_location.width() - self.padding.x) as f32,
(packed_location.height() - self.padding.y) as f32,
);
texture_ids.insert(*image_id, texture_rects.len());
if let Some(image_id) = image_id {
texture_ids.insert(*image_id, index);
}
texture_rects.push(Rect { min, max });
if texture.texture_descriptor.format != self.format && !self.auto_format_conversion {
warn!(
Expand All @@ -291,7 +291,7 @@ impl TextureAtlasBuilder {
textures: texture_rects,
texture_handles: Some(texture_ids),
},
textures.add(atlas_texture),
atlas_texture,
))
}
}
5 changes: 3 additions & 2 deletions examples/2d/texture_atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,11 @@ fn create_texture_atlas(
continue;
};

texture_atlas_builder.add_texture(id, texture);
texture_atlas_builder.add_texture(Some(id), texture);
}

let (texture_atlas, texture) = texture_atlas_builder.finish(textures).unwrap();
let (texture_atlas, texture) = texture_atlas_builder.finish().unwrap();
let texture = textures.add(texture);

// Update the sampling settings of the texture atlas
let image = textures.get_mut(&texture).unwrap();
Expand Down

0 comments on commit 3851679

Please sign in to comment.