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

GPU Ui slice WIP #2

Draft
wants to merge 12 commits into
base: feat/ui_texture_slice
Choose a base branch
from
Draft
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
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2422,6 +2422,17 @@ description = "Illustrates how to use TextureAtlases in UI"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "ui_texture_slice"
path = "examples/ui/ui_texture_slice.rs"
doc-scrape-examples = true

[package.metadata.example.ui_texture_slice]
name = "UI Texture Slice"
description = "Illustrates how to use 9 Slicing in UI"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "viewport_debug"
path = "examples/ui/viewport_debug.rs"
Expand Down
30 changes: 30 additions & 0 deletions assets/textures/fantasy_ui_borders/License.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@


Fantasy UI Borders (1.0)

Created/distributed by Kenney (www.kenney.nl)
Creation date: 03-12-2023

For the sample image the font 'Aoboshi One' was used, OPL (Open Font License)

------------------------------

License: (Creative Commons Zero, CC0)
http://creativecommons.org/publicdomain/zero/1.0/

You can use this content for personal, educational, and commercial purposes.

Support by crediting 'Kenney' or 'www.kenney.nl' (this is not a requirement)

------------------------------

• Website : www.kenney.nl
• Donate : www.kenney.nl/donate

• Patreon : patreon.com/kenney

Follow on social media for updates:

• Twitter: twitter.com/KenneyNL
• Instagram: instagram.com/kenney_nl
• Mastodon: mastodon.gamedev.place/@kenney
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions crates/bevy_sprite/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,21 @@ pub struct SpriteBundle {
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
#[derive(Bundle, Clone, Default)]
pub struct SpriteSheetBundle {
/// Specifies the rendering properties of the sprite, such as color tint and flip.
pub sprite: Sprite,
/// Controls how the image is altered when scaled.
pub scale_mode: ImageScaleMode,
/// The local transform of the sprite, relative to its parent.
pub transform: Transform,
/// The absolute transform of the sprite. This should generally not be written to directly.
pub global_transform: GlobalTransform,
/// The sprite sheet base texture
pub texture: Handle<Image>,
/// The sprite sheet texture atlas, allowing to draw a custom section of `texture`.
pub atlas: TextureAtlas,
/// User indication of whether an entity is visible
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub view_visibility: ViewVisibility,
Expand Down
5 changes: 2 additions & 3 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod prelude {
bundle::{SpriteBundle, SpriteSheetBundle},
sprite::{ImageScaleMode, Sprite},
texture_atlas::{TextureAtlas, TextureAtlasLayout},
texture_slice::{BorderRect, SliceScaleMode, TextureSlicer},
texture_slice::{BorderRect, SliceScaleMode, TextureSlice, TextureSlicer},
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
};
}
Expand Down Expand Up @@ -123,7 +123,6 @@ impl Plugin for SpritePlugin {
/// System calculating and inserting an [`Aabb`] component to entities with either:
/// - a `Mesh2dHandle` component,
/// - a `Sprite` and `Handle<Image>` components,
/// - a `TextureAtlasSprite` and `Handle<TextureAtlas>` components,
/// and without a [`NoFrustumCulling`] component.
///
/// Used in system set [`VisibilitySystems::CalculateBounds`].
Expand All @@ -136,7 +135,7 @@ pub fn calculate_bounds_2d(
sprites_to_recalculate_aabb: Query<
(Entity, &Sprite, &Handle<Image>, Option<&TextureAtlas>),
(
Or<(Without<Aabb>, Changed<Sprite>)>,
Or<(Without<Aabb>, Changed<Sprite>, Changed<TextureAtlas>)>,
Without<NoFrustumCulling>,
),
>,
Expand Down
15 changes: 11 additions & 4 deletions crates/bevy_sprite/src/texture_slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ pub(crate) use computed_slices::{
compute_slices_on_asset_event, compute_slices_on_sprite_change, ComputedTextureSlices,
};

/// Single texture slice, representing a texture rect to draw in a given area
#[derive(Debug, Clone)]
pub(crate) struct TextureSlice {
pub struct TextureSlice {
/// texture area to draw
pub texture_rect: Rect,
/// slice draw size
Expand Down Expand Up @@ -39,16 +40,19 @@ impl TextureSlice {
// Each tile expected size
let expected_size = Vec2::new(
if tile_x {
rect_size.x * stretch_value
// No slice should be less than 1 pixel wide
(rect_size.x * stretch_value).max(1.0)
} else {
self.draw_size.x
},
if tile_y {
rect_size.y * stretch_value
// No slice should be less than 1 pixel high
(rect_size.y * stretch_value).max(1.0)
} else {
self.draw_size.y
},
);
)
.min(self.draw_size);
let mut slices = Vec::new();
let base_offset = Vec2::new(
-self.draw_size.x / 2.0,
Expand Down Expand Up @@ -81,6 +85,9 @@ impl TextureSlice {
offset.y -= size_y / 2.0;
remaining_columns -= size_y;
}
if slices.len() > 1_000 {
bevy_log::warn!("One of your tiled textures has generated {} slices. You might want to use higher stretch values to avoid a great performance cost", slices.len());
}
slices
}
}
27 changes: 22 additions & 5 deletions crates/bevy_sprite/src/texture_slice/slicer.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use core::panic;

use super::{BorderRect, TextureSlice};
use bevy_math::{vec2, Rect, Vec2};
use bevy_reflect::Reflect;
Expand Down Expand Up @@ -198,13 +200,28 @@ impl TextureSlicer {
///
/// * `rect` - The section of the texture to slice in 9 parts
/// * `render_size` - The optional draw size of the texture. If not set the `rect` size will be used.
///
/// # Panics
///
/// Panics if any border values are bigger than `rect` half extents
#[must_use]
pub(crate) fn compute_slices(
&self,
rect: Rect,
render_size: Option<Vec2>,
) -> Vec<TextureSlice> {
pub fn compute_slices(&self, rect: Rect, render_size: Option<Vec2>) -> Vec<TextureSlice> {
let render_size = render_size.unwrap_or_else(|| rect.size());
let rect_size = rect.size() / 2.0;
if self.border.left >= rect_size.x
|| self.border.right >= rect_size.x
|| self.border.top >= rect_size.y
|| self.border.bottom >= rect_size.y
{
bevy_log::error!(
"TextureSlicer::border has out of bounds values. No slicing will be applied"
);
return vec![TextureSlice {
texture_rect: rect,
draw_size: render_size,
offset: Vec2::ZERO,
}];
}
let mut slices = Vec::with_capacity(9);
// Corners
let corners = self.corner_slices(rect, render_size);
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub mod prelude {
geometry::*, node_bundles::*, ui_material::*, ui_node::*, widget::Button, widget::Label,
Interaction, UiMaterialPlugin, UiScale,
};
// `bevy_sprite` re-exports for texture slicing
#[doc(hidden)]
pub use bevy_sprite::{BorderRect, ImageScaleMode, SliceScaleMode, TextureSlicer};
}

use bevy_app::prelude::*;
Expand Down
11 changes: 11 additions & 0 deletions crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ use bevy_sprite::TextureAtlas;
use bevy_text::{BreakLineOn, JustifyText, Text, TextLayoutInfo, TextSection, TextStyle};
use bevy_transform::prelude::{GlobalTransform, Transform};

#[derive(bevy_ecs::component::Component, Default, Clone, Debug)]
pub struct ImageScaleOptions {
pub tile_factor: Option<bevy_math::Vec2>,
pub slice_border: Option<bevy_math::Vec4>,
}

/// The basic UI node.
///
/// Contains the [`Node`] component and other components required to make a container.
Expand Down Expand Up @@ -95,6 +101,8 @@ pub struct ImageBundle {
///
/// This component is set automatically
pub image_size: UiImageSize,
/// Controls how the image is altered when scaled.
pub scale_options: ImageScaleOptions,
/// Whether this node should block interaction with lower nodes
pub focus_policy: FocusPolicy,
/// The transform of the node
Expand Down Expand Up @@ -307,6 +315,8 @@ pub struct ButtonBundle {
pub border_color: BorderColor,
/// The image of the node
pub image: UiImage,
/// Controls how the image is altered when scaled.
pub scale_options: ImageScaleOptions,
/// The transform of the node
///
/// This component is automatically managed by the UI layout system.
Expand Down Expand Up @@ -343,6 +353,7 @@ impl Default for ButtonBundle {
inherited_visibility: Default::default(),
view_visibility: Default::default(),
z_index: Default::default(),
scale_options: Default::default(),
}
}
}
Expand Down
63 changes: 57 additions & 6 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ mod ui_material_pipeline;
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
use bevy_hierarchy::Parent;
use bevy_render::{
render_phase::PhaseItem, render_resource::BindGroupEntries, view::ViewVisibility,
render_phase::PhaseItem,
render_resource::{BindGroupEntries, UniformBuffer},
view::ViewVisibility,
ExtractSchedule, Render,
};
use bevy_sprite::{SpriteAssetEvents, TextureAtlas};
Expand All @@ -14,14 +16,14 @@ pub use render_pass::*;
pub use ui_material_pipeline::*;

use crate::{
BackgroundColor, BorderColor, CalculatedClip, ContentSize, DefaultUiCamera, Node, Outline,
Style, TargetCamera, UiImage, UiScale, Val,
prelude::ImageScaleOptions, BackgroundColor, BorderColor, CalculatedClip, ContentSize,
DefaultUiCamera, Node, Outline, Style, TargetCamera, UiImage, UiScale, Val,
};

use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4Swizzles};
use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles};
use bevy_render::{
camera::Camera,
color::Color,
Expand Down Expand Up @@ -164,6 +166,8 @@ pub struct ExtractedUiNode {
// it is defaulted to a single camera if only one exists.
// Nodes with ambiguous camera will be ignored.
pub camera_entity: Entity,
pub slice_border: Option<Vec4>,
pub tiling_factor: Option<Vec2>,
}

#[derive(Resource, Default)]
Expand Down Expand Up @@ -298,6 +302,8 @@ pub fn extract_uinode_borders(
flip_x: false,
flip_y: false,
camera_entity,
slice_border: None,
tiling_factor: None,
},
);
}
Expand Down Expand Up @@ -389,6 +395,8 @@ pub fn extract_uinode_outlines(
flip_x: false,
flip_y: false,
camera_entity,
slice_border: None,
tiling_factor: None,
},
);
}
Expand All @@ -411,11 +419,22 @@ pub fn extract_uinodes(
Option<&CalculatedClip>,
Option<&TextureAtlas>,
Option<&TargetCamera>,
Option<&ImageScaleOptions>,
)>,
>,
) {
for (entity, uinode, transform, color, maybe_image, view_visibility, clip, atlas, camera) in
uinode_query.iter()
for (
entity,
uinode,
transform,
color,
maybe_image,
view_visibility,
clip,
atlas,
camera,
scale,
) in uinode_query.iter()
{
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
else {
Expand Down Expand Up @@ -468,6 +487,8 @@ pub fn extract_uinodes(
flip_x,
flip_y,
camera_entity,
slice_border: scale.and_then(|s| s.slice_border),
tiling_factor: scale.and_then(|s| s.tile_factor),
},
);
}
Expand Down Expand Up @@ -632,6 +653,8 @@ pub fn extract_text_uinodes(
flip_x: false,
flip_y: false,
camera_entity,
slice_border: None,
tiling_factor: None,
},
);
}
Expand Down Expand Up @@ -671,11 +694,20 @@ pub(crate) const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [

pub(crate) const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];

#[derive(Debug, Clone, ShaderType)]
pub struct TextureScalerUniform {
pub border: Vec4,
pub tiling_factor: Vec2,
}

#[derive(Component)]
pub struct UiBatch {
pub range: Range<u32>,
pub image: AssetId<Image>,
pub camera: Entity,
pub slice_border: Option<Vec4>,
pub scale_factor: Option<Vec2>,
pub scale_bind_group: BindGroup,
}

const TEXTURED_QUAD: u32 = 0;
Expand Down Expand Up @@ -778,15 +810,34 @@ pub fn prepare_uinodes(
&& batch_image_handle != extracted_uinode.image)
|| existing_batch.as_ref().map(|(_, b)| b.camera)
!= Some(extracted_uinode.camera_entity)
|| existing_batch.as_ref().and_then(|(_, b)| b.slice_border)
!= extracted_uinode.slice_border
|| existing_batch.as_ref().and_then(|(_, b)| b.scale_factor)
!= extracted_uinode.tiling_factor
{
if let Some(gpu_image) = gpu_images.get(extracted_uinode.image) {
batch_item_index = item_index;
batch_image_handle = extracted_uinode.image;

let mut buffer: UniformBuffer<_> = TextureScalerUniform {
border: extracted_uinode.slice_border.unwrap_or_default(),
tiling_factor: extracted_uinode.tiling_factor.unwrap_or_default(),
}
.into();
buffer.write_buffer(&render_device, &render_queue);
let scale_bind_group = render_device.create_bind_group(
"ui_scale_bind_group",
&ui_pipeline.scale_layout,
&BindGroupEntries::single(&buffer),
);

let new_batch = UiBatch {
range: index..index,
image: extracted_uinode.image,
camera: extracted_uinode.camera_entity,
scale_factor: extracted_uinode.tiling_factor,
slice_border: extracted_uinode.slice_border,
scale_bind_group,
};

batches.push((item.entity, new_batch));
Expand Down
Loading