bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Texture atlases with offset don't work properly in UI

Open rparrett opened this issue 1 year ago • 3 comments

Bevy version

0.12.1 recent main (0275508 is the last recent commit where texture atlases in UI aren't broken by a different bug)

Relevant system information

AdapterInfo { name: "Apple M1 Max", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }
SystemInfo { os: "MacOS 13.6.2 ", kernel: "22.6.0", cpu: "", core_count: "10", memory: "64.0 GiB" }

scale_factor of 1.0 and 2.0 were tested.

What you did

I first noticed this during Bevy Jam 4 as a slight offset in some of my UI images. Below you can see an example, with adjacent sprites showing at the right and bottom edges of the E and the right edge of the F. This is 103x50 texture atlas with 1px of offset and padding. This seemed to get worse at the sprites get further right/down in the sheet.

bleed

Here's an example that shows the same exact TextureAtlas both as a sprite and in the UI.

use bevy::{prelude::*, winit::WinitSettings};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
        .add_systems(Startup, setup)
        .add_systems(Update, increment_atlas_index)
        .run();
}

fn setup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut texture_atlases: ResMut<Assets<TextureAtlas>>,
) {
    commands.spawn(Camera2dBundle::default());

    let texture_handle = asset_server.load("sheettest.png");
    let texture_atlas = TextureAtlas::from_grid(
        texture_handle,
        Vec2::new(16.0, 16.0),
        3,
        4,
        Some(Vec2::splat(16.)),
        Some(Vec2::splat(40.)),
    );
    let texture_atlas_handle = texture_atlases.add(texture_atlas);

    commands.spawn(AtlasImageBundle {
        style: Style {
            width: Val::Px(256.),
            height: Val::Px(256.),
            ..default()
        },
        texture_atlas: texture_atlas_handle.clone(),
        ..default()
    });

    commands.spawn(SpriteSheetBundle {
        texture_atlas: texture_atlas_handle,
        transform: Transform::from_scale(Vec3::splat(16.0)),
        ..default()
    });
}

fn increment_atlas_index(
    mut atlas_images: Query<&mut UiTextureAtlasImage>,
    mut atlas_sprites: Query<&mut TextureAtlasSprite>,
    keyboard: Res<ButtonInput<KeyCode>>,
) {
    let frames = 12;
    if keyboard.just_pressed(KeyCode::Space) {
        for mut atlas_image in &mut atlas_images {
            atlas_image.index = (atlas_image.index + 1) % frames;
        }

        for mut atlas_sprite in &mut atlas_sprites {
            atlas_sprite.index = (atlas_sprite.index + 1) % frames;
        }
    }
}

And sheettest.png. It has exaggerated offset/padding with those areas highlighted for debugging purposes.

sheettest

What went wrong

Both images should look the same. Instead, we see this:

Screenshot 2024-01-04 at 2 05 26 PM

The image in the middle is the sprite and looks correct.

Additional information

This works as expected with a texture atlas without offset/padding such as the one used in the ui_texture_atlas example.

rparrett avatar Jan 04 '24 21:01 rparrett

I believe the issues is either in

  1. TextureAtlas::from_grid not calculating size as expected.
  2. extract_atlas_uinodes using TextureAtlas::size instead of the size of the actual texture.

I'm not really sure what a "correct" fix would be.

1 seems slightly problematic if TextureAtlas::size is supposed to be exactly the texture size, because the offset param is ambiguous about whether the texture has the "offset area" on its right and bottom sides as well. In practice, I would expect that it's far more common for users to have atlases with the "offset" on all sides. It would probably be worth investigating whether from_grid can be used to load a "sub-sheet" from a larger atlas, and how that behaves in a non-ui TextureAtlasSprite. If that needs to work, then fixing 1 feels pointless.

2 seems problematic, because after #10520, we can no longer access the actual texture size where it is needed.

So I think the right call is probably to fix 1 by adding 2. * offset to size in TextureAtlas::from_grid. But I am very unfamiliar with UI rendering so there may be some better solution.

There is a workaround for end users, which is to set the TextureAtlas's size to the correct value after it is created:

let mut atlas = TextureAtlasLayout::from_grid(
    UVec2::new(12, 12),
    103,
    50,
    Some(UVec2::splat(1)),
    Some(UVec2::splat(1)),
);
// Workaround for https://github.com/bevyengine/bevy/issues/11219
atlas.size = UVec2::new(1340, 651);

rparrett avatar Jan 05 '24 00:01 rparrett

After #11212, fixing 2 is on the table. But it would be good to understand what TextureAtlas::size is expected to be and if that's also something that needs fixing.

rparrett avatar Jan 05 '24 17:01 rparrett

I've run into the same problem. I checked the Rects stored in TextureAtlas::textures by from_grid, the calculated areas look correct based on padding and offset - which makes sense, given that SpriteSheetBundle renders the correct sprite for each tested index.

I did notice however that in case of AtlasImageBundle, the original texture looks scaled to match the calculated height in TextureAtlas::size, in other words, unwanted scaling is applied before obtaining the sprites from the texture, which explains padding and offsets not matching. If I set the tile_size to match the texture height in TextureAtlas::from_grid, the rendering result from AtlasImageBundle looks identical.

outergod avatar Feb 09 '24 10:02 outergod

Updated the repro for Bevy 0.13 / confirmed this is still an issue.

rparrett avatar Mar 18 '24 19:03 rparrett

I believe the issues is either in

1. `TextureAtlas::from_grid` not calculating size as expected.

2. `extract_atlas_uinodes` using `TextureAtlas::size` instead of the size of the actual texture.

It is 2. I worked around it by adding a fake texture_size to TextureAtlasLayout and used it here, surely enough works as expected.

I will look around to properly fix it or how bevy_sprites gets around this issue.

s-puig avatar Mar 29 '24 19:03 s-puig