-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Sprite slicing and tiling #10588
Conversation
crates/bevy_sprite/src/sprite.rs
Outdated
@@ -23,6 +25,26 @@ pub struct Sprite { | |||
pub anchor: Anchor, | |||
} | |||
|
|||
#[derive(Component, Debug, Default, Clone, Reflect)] | |||
#[reflect(Component, Default)] | |||
pub enum SpriteScaleMode { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this 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 :)
@pablo-lua can you expand on that comment? Which types didn't make sense to you? |
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 |
There was a problem hiding this 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.
Sliced(TextureSlicer), | ||
/// The texture will be repeated if stretched beyond `stretched_value` | ||
Tiled { | ||
/// Should the image repeat horizontally |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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).
examples/2d/sprite_slice.rs
Outdated
transform: Transform::from_xyz(-400.0, 0.0, 0.0), | ||
..default() | ||
}); | ||
commands.spawn(Text2dBundle { |
There was a problem hiding this comment.
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.
I suspect #11326 may have fixed up the label arrangement issue: can you merge in main while you're fixing CI? |
There was a problem hiding this 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.
/// Controls how the image is altered when scaled. | ||
#[derive(Component, Debug, Default, Clone, Reflect)] | ||
#[reflect(Component, Default)] | ||
pub enum ImageScaleMode { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.extract_sprites(transform, entity, sprite, handle) | ||
.map(|e| (commands.spawn_empty().id(), e)), |
There was a problem hiding this comment.
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)), |
There was a problem hiding this comment.
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)] |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
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 |
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
> 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
> 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 ?
# 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>
# 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>
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>
Objective
Implement sprite tiling and 9 slice scaling for
bevy_sprite
.Allowing slice scaling and texture tiling.
Basic scaling vs 9 slice scaling:
Slicing example:
Tiling example:
Solution
SpriteBundlue
now has ascale_mode
component storing aSpriteScaleMode
enum with three variants:Stretched
(default)Tiled
to have sprites tile horizontally and/or verticallySliced
allowing 9 slicing the texture and optionally tile some sections with aTextureslicer
.bevy_sprite
has two extra systems to compute aComputedTextureSlices
if necessary,:Sprite
,Handle<Image>
orSpriteScaleMode
AssetEvent<Image>
to compute slices on sprites when the texture is ready or changedbevy_sprite
extraction stage to extract potentially multiple textures instead of one, depending on the presence ofComputedTextureSlices
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