Allow TextureAtlasBuilder in AssetLoader (#11548)

# 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
https://github.com/bevyengine/bevy/pull/11474#issuecomment-1904676937
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>
This commit is contained in:
Bude 2024-01-27 17:16:44 +01:00 committed by GitHub
parent b35b9e5005
commit 3851679173
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 31 additions and 30 deletions

View File

@ -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::{
@ -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.
@ -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(),
@ -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;
@ -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.
@ -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
///
@ -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 {
@ -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;
@ -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,
),
);
}
@ -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!(
@ -291,7 +291,7 @@ impl TextureAtlasBuilder {
textures: texture_rects,
texture_handles: Some(texture_ids),
},
textures.add(atlas_texture),
atlas_texture,
))
}
}

View File

@ -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();