Sprite slice scaling and tiling
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
Spritenow has adraw_modefield storing aSpriteDrawModeenum with three variants:SimpleTiledto have sprites tile horizontally and/or verticallySlicedallowing 9 slicing the texture and optionally tile some sections with aTextureslicer.
- I updated the
bevy_spriteextraction stage to extract potentially multiple textures instead of one, depending on thedraw_mode - I added two examples showcasing the slicing and tiling feature.
TODO list
- [x] slicing
- [x] scale factor limit
- [x] Slice Tiling:
- [x] Basic stretch mode
- [x] Center tiling
- [x] Sides tiling
- [x] Sprite tiling without slices
Blocking
- [ ] ~~Requires https://github.com/bevyengine/bevy/pull/5247 to be merged to move the tile and slice computations in the
preparestage of the sprite render pipeline~~ - [ ] https://github.com/bevyengine/bevy/pull/6621
- [ ] Once #5103 is merged I can enable tiling and slicing for texture sheets.
- [ ] Once #5070 is merged I can add this to
bevy_ui
To discuss
I want to move the slicer to be an Asset and avoid to compute the slices every frame, like for TextureAtlas but the slicing depends on the Sprite::custom_size and therefore needs to be updated every frame or on Changed<Sprite> detection
Now I'm not sure if I can put the slicer in an Asset, I can compute the Rect values from a given image size and store it but the slices must be computed every frame since the sprite can be resized. Or maybe I can compute some change detection..
@mockersf how did you do it in https://github.com/vleue/bevy_ninepatch ? I looked at the code after you gave me the link but I don't understand the logic
Slicing and tiling work, I think this is ready to be reviewed @alice-i-cecile
@alice-i-cecile I changed the logic to enable sprite tiling even without slicing, using an enum in Sprite::draw_mode and removing TextureSlicer from being a component.
I think this is better.
But now that I changed a lot of stuff in the extraction stage of the sprite render pipeline, benchmarking is definetely required
many_sprites stress test:
On this MR:
2022-07-07T09:27:50.191875Z INFO many_sprites: Sprites: 102400
2022-07-07T09:27:50.191926Z INFO bevy diagnostic: frame_time : 0.058695s (avg 0.060466s)
2022-07-07T09:27:50.191937Z INFO bevy diagnostic: fps : 17.037371 (avg 16.569809)
2022-07-07T09:27:50.191941Z INFO bevy diagnostic: frame_count : 403.000000
On Main:
2022-07-07T09:29:16.821865Z INFO many_sprites: Sprites: 102400
2022-07-07T09:29:16.821931Z INFO bevy diagnostic: frame_time : 0.052531s (avg 0.050298s)
2022-07-07T09:29:16.821944Z INFO bevy diagnostic: fps : 19.036325 (avg 19.903592)
2022-07-07T09:29:16.821949Z INFO bevy diagnostic: frame_count : 415.000000
So there is some performance regression for regular sprites. I'm going to investigate this
The big issue is accessing the Image from the handle in the extraction stage. I removed this step for SpriteDrawMode::Simple sprites, meaning no performance regressions.
Now this is still required for Sliced and Tiled draw modes. A more optimized solution would be to retrieve the image size lazily on the Added<Sprite> event or to ask the user to give the image dimensions.
I'm trying to think about a more ergonomic way to handle this, since the image dimensions are accessed again in the queue stage of the pipeline.
Closing in favor of #10588