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

Sprite slicing and tiling #10588

Merged
merged 11 commits into from
Jan 15, 2024
Merged

Conversation

ManevilleF
Copy link
Contributor

@ManevilleF ManevilleF commented Nov 16, 2023

Replaces #5213

Objective

Implement sprite tiling and 9 slice scaling for bevy_sprite.
Allowing slice scaling and texture tiling.

Basic scaling vs 9 slice scaling:

Traditional_scaling_vs_9-slice_scaling

Slicing example:

Screenshot 2022-07-05 at 15 05 49

Tiling example:

Screenshot 2023-11-16 at 13 53 32

Solution

  • SpriteBundlue now has a scale_mode component storing a SpriteScaleMode enum with three variants:
    • Stretched (default)
    • Tiled to have sprites tile horizontally and/or vertically
    • Sliced allowing 9 slicing the texture and optionally tile some sections with a Textureslicer.
  • bevy_sprite has two extra systems to compute a ComputedTextureSlices if necessary,:
    • One system react to changes on Sprite, Handle<Image> or SpriteScaleMode
    • The other listens to AssetEvent<Image> to compute slices on sprites when the texture is ready or changed
  • I updated the bevy_sprite extraction stage to extract potentially multiple textures instead of one, depending on the presence of ComputedTextureSlices
  • I added two examples showcasing the slicing and tiling feature.

The addition of ComputedTextureSlices as a cache is to avoid querying the image data, to retrieve its dimensions, every frame in a extract or prepare stage. Also it reacts to changes so we can have stuff like this (tiling example):

Screen.Recording.2023-11-16.at.13.59.25.mp4

Related

To discuss

There is an other option, to consider slice/tiling as part of the asset, using the new asset preprocessing but I have no clue on how to do it.

Also, instead of retrieving the Image dimensions, we could use the same system as the sprite sheet and have the user give the image dimensions directly (grid). But I think it's less user friendly

@ManevilleF ManevilleF mentioned this pull request Nov 16, 2023
11 tasks
@alice-i-cecile alice-i-cecile added this to the 0.13 milestone Nov 16, 2023
@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-Rendering Drawing game state to the screen labels Nov 16, 2023
@@ -23,6 +25,26 @@ pub struct Sprite {
pub anchor: Anchor,
}

#[derive(Component, Debug, Default, Clone, Reflect)]
#[reflect(Component, Default)]
pub enum SpriteScaleMode {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should call this ImageScaleMode, since we're intending to support non-sprite images eventually.

Copy link
Contributor Author

@ManevilleF ManevilleF Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right ! But then where should it be put ? Still in bevy_sprite ? Is there a common crate for textures shared by bevy_ui as well ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bevy_render feels like the correct home, and is a shared dependency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bevy_sprite is also a dependency of bevy_ui. and I'm not sure bevy_render is the right place, it feels like this abstraction is too high level for this core crate

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah @superdump and @robftm can decide :) Here is fine for now though.

Copy link
Contributor

@pablo-lua pablo-lua left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was reading the changes, and some types here and there don't reflect what they could even if they could, not sure if there is a reason for it. Well, liked this changes very much, hope that goes on :)

@alice-i-cecile
Copy link
Member

@pablo-lua can you expand on that comment? Which types didn't make sense to you?

@pablo-lua
Copy link
Contributor

This was pretty much all I found, I was not sure if this was a problem, but now I'm pointing which types can probably reflect

Copy link
Contributor

@brandon-reinhart brandon-reinhart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: The demo could be improved, but I decided to switch to approve.

I have no technical criticisms. This looks good to me and would offer immediate value to users.

I would mark this "Approve" if the labels on the demo didn't overlap themselves.

Two nits I didn't mention is that I wish Bevy PR authors would put an empty line between blocks more frequently (aids in code readability) and all comments that were sentences ended with a period. This is just personal pedantry.

crates/bevy_sprite/src/bundle.rs Outdated Show resolved Hide resolved
crates/bevy_sprite/src/bundle.rs Outdated Show resolved Hide resolved
Sliced(TextureSlicer),
/// The texture will be repeated if stretched beyond `stretched_value`
Tiled {
/// Should the image repeat horizontally
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to:

/// The image will repeat horizontally.
...
/// The image will repeat vertically.

/// Struct defining a [`Sprite`](crate::Sprite) border with padding values
#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
pub struct BorderRect {
/// Pixel padding to the left
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments probably aren't necessary (like the UiRect fields).

transform: Transform::from_xyz(-400.0, 0.0, 0.0),
..default()
});
commands.spawn(Text2dBundle {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This text overlaps. Drop the font size or stagger the Y of the labels.

@alice-i-cecile alice-i-cecile added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Jan 12, 2024
@alice-i-cecile
Copy link
Member

I suspect #11326 may have fixed up the label arrangement issue: can you merge in main while you're fixing CI?

Copy link
Contributor

@ickshonpe ickshonpe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to work really well, the examples are really slick (apart from the text overlap problems).
I think moving to a shader based implementation would make more sense eventually but this seems good for now.

examples/2d/sprite_slice.rs Outdated Show resolved Hide resolved
examples/2d/sprite_slice.rs Outdated Show resolved Hide resolved
/// Controls how the image is altered when scaled.
#[derive(Component, Debug, Default, Clone, Reflect)]
#[reflect(Component, Default)]
pub enum ImageScaleMode {
Copy link
Contributor

@ickshonpe ickshonpe Jan 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the name ImageScaleMode but it's not that much a problem I guess, I can't think of anything better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ImageScalingMode or ImageScalingBehavior is a marginal improvement I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of scale, because the other variants aren't really scalings, something like ImageExtendMode or ImageExtensionMode

Comment on lines +368 to +369
.extract_sprites(transform, entity, sprite, handle)
.map(|e| (commands.spawn_empty().id(), e)),
Copy link
Contributor

@ickshonpe ickshonpe Jan 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use a custom shader here that draws all the slices in one pass, then only need one entity id is needed.

With a shader based solution, the whole texture_slice module wouldn't be needed even. All it would need is just the ImageScaleMode component, extraction function, some pipeline specialisation stuff, and a fragment shader.

extracted_sprites.sprites.extend(
slices
.extract_sprites(transform, entity, sprite, handle)
.map(|e| (commands.spawn_empty().id(), e)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it isn't very difficult either to modify batching to group sprites together with the same entity id when there isn't any need to sort the group internally. I thought I even wrote a PR somewhere that implemented it for use with Text2d.

Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
use bevy_reflect::Reflect;

/// Struct defining a [`Sprite`](crate::Sprite) border with padding values
#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this type reflect Default and PartialEq?

#[reflect(Default, PartialEq)]


/// Defines how a texture slice scales when resized
#[derive(Debug, Copy, Clone, Default, Reflect)]
pub enum SliceScaleMode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same with this Type, could probably reflect Default.

@ManevilleF
Copy link
Contributor Author

I think moving to a shader based implementation would make more sense eventually but this seems good for now.

Definetely, this should be GPU based, but I don't know how to do that, merging this would provide an API for slicing/tiling but in the future the internals should move from the CPU to a shader

alice-i-cecile and others added 2 commits January 15, 2024 10:20
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jan 15, 2024
Merged via the queue into bevyengine:main with commit 01139b3 Jan 15, 2024
26 checks passed
@ManevilleF ManevilleF mentioned this pull request Jan 29, 2024
3 tasks
github-merge-queue bot pushed a commit that referenced this pull request Feb 7, 2024
> Follow up to #10588 
> Closes #11749 (Supersedes #11756)

Enable Texture slicing for the following UI nodes:
- `ImageBundle`
- `ButtonBundle`

<img width="739" alt="Screenshot 2024-01-29 at 13 57 43"
src="https://github.com/bevyengine/bevy/assets/26703856/37675681-74eb-4689-ab42-024310cf3134">

I also added a collection of `fantazy-ui-borders` from
[Kenney's](www.kenney.nl) assets, with the appropriate license (CC).
If it's a problem I can use the same textures as the `sprite_slice`
example

# Work done

Added the `ImageScaleMode` component to the targetted bundles, most of
the logic is directly reused from `bevy_sprite`.
The only additional internal component is the UI specific
`ComputedSlices`, which does the same thing as its spritee equivalent
but adapted to UI code.

Again the slicing is not compatible with `TextureAtlas`, it's something
I need to tackle more deeply in the future

# Fixes

* [x] I noticed that `TextureSlicer::compute_slices` could infinitely
loop if the border was larger that the image half extents, now an error
is triggered and the texture will fallback to being stretched
* [x] I noticed that when using small textures with very small *tiling*
options we could generate hundred of thousands of slices. Now I set a
minimum size of 1 pixel per slice, which is already ridiculously small,
and a warning will be sent at runtime when slice count goes above 1000
* [x] Sprite slicing with `flip_x` or `flip_y` would give incorrect
results, correct flipping is now supported to both sprites and ui image
nodes thanks to @odecay observation

# GPU Alternative

I create a separate branch attempting to implementing 9 slicing and
tiling directly through the `ui.wgsl` fragment shader. It works but
requires sending more data to the GPU:
- slice border
- tiling factors

And more importantly, the actual quad *scale* which is hard to put in
the shader with the current code, so that would be for a later iteration
github-merge-queue bot pushed a commit that referenced this pull request Feb 9, 2024
> Follow up to #11600 and #10588 

@mockersf expressed some [valid
concerns](#11600 (comment))
about the current system this PR attempts to fix:

The `ComputedTextureSlices` reacts to asset change in both `bevy_sprite`
and `bevy_ui`, meaning that if the `ImageScaleMode` is inserted by
default in the bundles, we will iterate through most 2d items every time
an asset is updated.

# Solution

- `ImageScaleMode` only has two variants: `Sliced` and `Tiled`. I
removed the `Stretched` default
- `ImageScaleMode` is no longer part of any bundle, but the relevant
bundles explain that this additional component can be inserted

This way, the *absence* of `ImageScaleMode` means the image will be
stretched, and its *presence* will include the entity to the various
slicing systems

Optional components in bundles would make this more straigthfoward

# Additional work

Should I add new bundles with the `ImageScaleMode` component ?
github-merge-queue bot pushed a commit that referenced this pull request Mar 5, 2024
# Objective

Follow up to #11600 and #10588 
#11944 made clear that some
people want to use slicing with texture atlases

## Changelog

* Added support for `TextureAtlas` slicing and tiling.
`SpriteSheetBundle` and `AtlasImageBundle` can now use `ImageScaleMode`
* Added new `ui_texture_atlas_slice` example using a texture sheet

<img width="798" alt="Screenshot 2024-02-23 at 11 58 35"
src="https://github.com/bevyengine/bevy/assets/26703856/47a8b764-127c-4a06-893f-181703777501">

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com>
spectria-limina pushed a commit to spectria-limina/bevy that referenced this pull request Mar 9, 2024
# Objective

Follow up to bevyengine#11600 and bevyengine#10588 
bevyengine#11944 made clear that some
people want to use slicing with texture atlases

## Changelog

* Added support for `TextureAtlas` slicing and tiling.
`SpriteSheetBundle` and `AtlasImageBundle` can now use `ImageScaleMode`
* Added new `ui_texture_atlas_slice` example using a texture sheet

<img width="798" alt="Screenshot 2024-02-23 at 11 58 35"
src="https://github.com/bevyengine/bevy/assets/26703856/47a8b764-127c-4a06-893f-181703777501">

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com>
msvbg pushed a commit to msvbg/bevy that referenced this pull request Jun 11, 2024
Follow up to bevyengine#11600 and bevyengine#10588
bevyengine#11944 made clear that some
people want to use slicing with texture atlases

* Added support for `TextureAtlas` slicing and tiling.
`SpriteSheetBundle` and `AtlasImageBundle` can now use `ImageScaleMode`
* Added new `ui_texture_atlas_slice` example using a texture sheet

<img width="798" alt="Screenshot 2024-02-23 at 11 58 35"
src="https://github.com/bevyengine/bevy/assets/26703856/47a8b764-127c-4a06-893f-181703777501">

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants