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 slice scaling and tiling #5213

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,26 @@ description = "Renders a sprite"
category = "2D Rendering"
wasm = true

[[example]]
name = "sprite_tile"
path = "examples/2d/sprite_tile.rs"

[package.metadata.example.sprite_tile]
name = "Sprite Tile"
description = "Renders a sprite tiled in a grid"
category = "2D Rendering"
wasm = true

[[example]]
name = "sprite_slice"
path = "examples/2d/sprite_slice.rs"

[package.metadata.example.sprite_slice]
name = "Sprite Slice"
description = "Showcases slicing sprites into sections that can be scaled independently via the 9-patch technique"
category = "2D Rendering"
wasm = true

[[example]]
name = "sprite_flipping"
path = "examples/2d/sprite_flipping.rs"
Expand Down
Binary file added assets/textures/slice_sprite.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/textures/slice_square.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/textures/slice_square_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ mod render;
mod sprite;
mod texture_atlas;
mod texture_atlas_builder;
mod texture_slice;

pub mod collide_aabb;

pub mod prelude {
#[doc(hidden)]
pub use crate::{
bundle::{SpriteBundle, SpriteSheetBundle},
sprite::Sprite,
sprite::{Sprite, SpriteDrawMode},
texture_atlas::{TextureAtlas, TextureAtlasSprite},
texture_slice::{BorderRect, SliceScaleMode, TextureSlicer},
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
};
}
Expand All @@ -25,6 +27,7 @@ pub use render::*;
pub use sprite::*;
pub use texture_atlas::*;
pub use texture_atlas_builder::*;
pub use texture_slice::*;

use bevy_app::prelude::*;
use bevy_asset::{AddAsset, Assets, HandleUntyped};
Expand Down
85 changes: 69 additions & 16 deletions crates/bevy_sprite/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::cmp::Ordering;

use crate::{
texture_atlas::{TextureAtlas, TextureAtlasSprite},
Sprite, SPRITE_SHADER_HANDLE,
Sprite, SpriteDrawMode, TextureSlice, SPRITE_SHADER_HANDLE,
};
use bevy_asset::{AssetEvent, Assets, Handle, HandleId};
use bevy_core_pipeline::{core_2d::Transparent2d, tonemapping::Tonemapping};
Expand Down Expand Up @@ -30,7 +30,7 @@ use bevy_render::{
},
Extract,
};
use bevy_transform::components::GlobalTransform;
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::FloatOrd;
use bevy_utils::HashMap;
use bytemuck::{Pod, Zeroable};
Expand Down Expand Up @@ -316,6 +316,7 @@ pub fn extract_sprite_events(
pub fn extract_sprites(
mut extracted_sprites: ResMut<ExtractedSprites>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
images: Extract<Res<Assets<Image>>>,
sprite_query: Extract<
Query<(
Entity,
Expand All @@ -340,19 +341,71 @@ pub fn extract_sprites(
if !visibility.is_visible() {
continue;
}
// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive
extracted_sprites.sprites.push(ExtractedSprite {
entity,
color: sprite.color,
transform: *transform,
rect: sprite.rect,
// Pass the custom size
custom_size: sprite.custom_size,
flip_x: sprite.flip_x,
flip_y: sprite.flip_y,
image_handle_id: handle.id(),
anchor: sprite.anchor.as_vec(),
});
if let SpriteDrawMode::Simple = sprite.draw_mode {
// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive
extracted_sprites.sprites.push(ExtractedSprite {
entity,
color: sprite.color,
transform: *transform,
rect: sprite.rect,
// Pass the custom size
custom_size: sprite.custom_size,
flip_x: sprite.flip_x,
flip_y: sprite.flip_y,
image_handle_id: handle.id(),
anchor: sprite.anchor.as_vec(),
});
continue;
}
let image_size = match images.get(handle) {
None => continue,
Some(i) => Vec2::new(
i.texture_descriptor.size.width as f32,
i.texture_descriptor.size.height as f32,
),
};

let slices = match &sprite.draw_mode {
SpriteDrawMode::Sliced(slicer) => slicer.compute_slices(
Copy link
Contributor

Choose a reason for hiding this comment

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

'compute' suggests something that should be done in prepare as it is work that takes time to do and extract is meant to be as fast as possible.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note though that if it doesn't take any significant amount of time for a lot of such entities (i.e. doesn't add more than say 0.1ms compared to as many entities that just use simple sprites) but it would take more time in a separate prepare stage system due to having to iterate over them all again, then it can be OK to do it here. So basically profile and present results.

Copy link
Contributor

Choose a reason for hiding this comment

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

Having now seen the slicer code, I suggest moving that to the prepare stage.

Copy link
Contributor Author

@ManevilleF ManevilleF Jul 7, 2022

Choose a reason for hiding this comment

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

I'll move every computation in prepare once #5247 is merged

sprite.rect.unwrap_or(Rect {
min: Vec2::ZERO,
max: image_size,
}),
sprite.custom_size,
),
SpriteDrawMode::Tiled {
tile_x,
tile_y,
stretch_value,
} => {
let slice = TextureSlice {
texture_rect: sprite.rect.unwrap_or(Rect {
min: Vec2::ZERO,
max: image_size,
}),
draw_size: sprite.custom_size.unwrap_or(image_size),
offset: Vec2::ZERO,
};
slice.tiled(*stretch_value, (*tile_x, *tile_y))
Copy link
Contributor

Choose a reason for hiding this comment

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

This also looks like something that should be done in prepare.

}
SpriteDrawMode::Simple => unreachable!(),
};
for slice in slices {
let mut transform: GlobalTransform = *transform;
transform =
transform.mul_transform(Transform::from_translation(slice.offset.extend(0.0)));
extracted_sprites.sprites.push(ExtractedSprite {
entity,
color: sprite.color,
transform,
rect: Some(slice.texture_rect),
custom_size: Some(slice.draw_size),
flip_x: sprite.flip_x,
flip_y: sprite.flip_y,
image_handle_id: handle.id(),
anchor: sprite.anchor.as_vec(),
});
}
}
for (entity, visibility, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
if !visibility.is_visible() {
Expand Down Expand Up @@ -545,7 +598,7 @@ pub fn queue_sprites(
};
let mut current_batch_entity = Entity::from_raw(u32::MAX);
let mut current_image_size = Vec2::ZERO;
// Add a phase item for each sprite, and detect when succesive items can be batched.
// Add a phase item for each sprite, and detect when successive items can be batched.
// Spawn an entity with a `SpriteBatch` component for each possible batch.
// Compatible items share the same entity.
// Batches are merged later (in `batch_phase_system()`), so that they can be interrupted
Expand Down
24 changes: 24 additions & 0 deletions crates/bevy_sprite/src/sprite.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::TextureSlicer;
use bevy_ecs::component::Component;
use bevy_math::{Rect, Vec2};
use bevy_reflect::Reflect;
Expand All @@ -20,6 +21,27 @@ pub struct Sprite {
pub rect: Option<Rect>,
/// [`Anchor`] point of the sprite in the world
pub anchor: Anchor,
/// Define how the Sprite scales when its dimensions change
pub draw_mode: SpriteDrawMode,
}

#[derive(Debug, Default, Clone, Reflect)]
pub enum SpriteDrawMode {
/// The entire texture scales when its dimensions change. This is the default option.
#[default]
Simple,
/// The texture will be cut in 9 slices, keeping the texture in proportions on resize
Sliced(TextureSlicer),
/// The texture will be repeated if stretched beyond `stretched_value`
Tiled {
/// Should the image repeat horizontally
tile_x: bool,
/// Should the image repeat vertically
tile_y: bool,
/// The texture will repeat when the ratio between the *drawing dimensions* of texture and the
/// *original texture size* are above this value
ManevilleF marked this conversation as resolved.
Show resolved Hide resolved
stretch_value: f32,
},
}

/// How a sprite is positioned relative to its [`Transform`](bevy_transform::components::Transform).
Expand All @@ -43,6 +65,8 @@ pub enum Anchor {
}

impl Anchor {
#[inline]
#[must_use]
pub fn as_vec(&self) -> Vec2 {
match self {
Anchor::Center => Vec2::ZERO,
Expand Down
Loading