Skip to content

Add ZIndex, YSort, and SortBias 2d components #19463

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
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 @@ -778,6 +778,17 @@ description = "Renders an animated sprite"
category = "2D Rendering"
wasm = true

[[example]]
name = "sprite_sorting"
path = "examples/2d/sprite_sorting.rs"
doc-scrape-examples = true

[package.metadata.example.sprite_sorting]
name = "Sprite Sorting"
description = "Demonstrates how to sort sprites"
category = "2D Rendering"
wasm = true

[[example]]
name = "sprite_tile"
path = "examples/2d/sprite_tile.rs"
Expand Down
22 changes: 19 additions & 3 deletions crates/bevy_core_pipeline/src/core_2d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,9 +343,25 @@ impl CachedRenderPipelinePhaseItem for AlphaMask2d {
}
}

#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Clone)]
pub struct Transparent2dSortKey {
z_index: i32,
bias: FloatOrd,
}

impl Transparent2dSortKey {
pub fn new(z_index: i32, bias: Option<f32>) -> Transparent2dSortKey {
Transparent2dSortKey {
z_index,
// nans sort after any valid specified y sort
bias: FloatOrd(bias.unwrap_or(f32::NAN)),
}
}
}

/// Transparent 2D [`SortedPhaseItem`]s.
pub struct Transparent2d {
pub sort_key: FloatOrd,
pub sort_key: Transparent2dSortKey,
pub entity: (Entity, MainEntity),
pub pipeline: CachedRenderPipelineId,
pub draw_function: DrawFunctionId,
Expand Down Expand Up @@ -395,7 +411,7 @@ impl PhaseItem for Transparent2d {
}

impl SortedPhaseItem for Transparent2d {
type SortKey = FloatOrd;
type SortKey = Transparent2dSortKey;

#[inline]
fn sort_key(&self) -> Self::SortKey {
Expand All @@ -405,7 +421,7 @@ impl SortedPhaseItem for Transparent2d {
#[inline]
fn sort(items: &mut [Self]) {
// radsort is a stable radix sort that performed better than `slice::sort_by_key` or `slice::sort_unstable_by_key`.
radsort::sort_by_key(items, |item| item.sort_key().0);
radsort::sort_by_key(items, |item| (item.sort_key.z_index, item.sort_key.bias.0));
}

fn indexed(&self) -> bool {
Expand Down
9 changes: 4 additions & 5 deletions crates/bevy_gizmos/src/pipeline_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
};
use bevy_app::{App, Plugin};
use bevy_asset::{load_embedded_asset, Handle};
use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT};
use bevy_core_pipeline::core_2d::{Transparent2d, Transparent2dSortKey, CORE_2D_DEPTH_FORMAT};

use bevy_ecs::{
prelude::Entity,
Expand All @@ -16,7 +16,6 @@ use bevy_ecs::{
world::{FromWorld, World},
};
use bevy_image::BevyDefault as _;
use bevy_math::FloatOrd;
use bevy_render::sync_world::MainEntity;
use bevy_render::{
render_asset::{prepare_assets, RenderAssets},
Expand Down Expand Up @@ -343,7 +342,7 @@ fn queue_line_gizmos_2d(
entity: (entity, *main_entity),
draw_function,
pipeline,
sort_key: FloatOrd(f32::INFINITY),
sort_key: Transparent2dSortKey::new(i32::MAX, None),
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
Expand All @@ -365,7 +364,7 @@ fn queue_line_gizmos_2d(
entity: (entity, *main_entity),
draw_function: draw_function_strip,
pipeline,
sort_key: FloatOrd(f32::INFINITY),
sort_key: Transparent2dSortKey::new(i32::MAX, None),
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
Expand Down Expand Up @@ -425,7 +424,7 @@ fn queue_line_joint_gizmos_2d(
entity: (entity, *main_entity),
draw_function,
pipeline,
sort_key: FloatOrd(f32::INFINITY),
sort_key: Transparent2dSortKey::new(i32::MAX, None),
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
Expand Down
30 changes: 23 additions & 7 deletions crates/bevy_sprite/src/mesh2d/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
use bevy_app::{App, Plugin, PostUpdate};
use bevy_asset::prelude::AssetChanged;
use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEventSystems, AssetId, AssetServer, Handle};
use bevy_core_pipeline::core_2d::Transparent2dSortKey;
use bevy_core_pipeline::{
core_2d::{
AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d,
Expand All @@ -18,7 +19,6 @@ use bevy_ecs::{
prelude::*,
system::{lifetimeless::SRes, SystemParamItem},
};
use bevy_math::FloatOrd;
use bevy_platform::collections::HashMap;
use bevy_reflect::{prelude::ReflectDefault, Reflect};
use bevy_render::camera::extract_cameras;
Expand Down Expand Up @@ -839,7 +839,6 @@ pub fn queue_material2d_meshes<M: Material2d>(
};

mesh_instance.material_bind_group_id = material_2d.get_bind_group_id();
let mesh_z = mesh_instance.transforms.world_from_local.translation.z;

// We don't support multidraw yet for 2D meshes, so we use this
// custom logic to generate the `BinnedRenderPhaseType` instead of
Expand All @@ -852,8 +851,11 @@ pub fn queue_material2d_meshes<M: Material2d>(
BinnedRenderPhaseType::UnbatchableMesh
};

match material_2d.properties.alpha_mode {
AlphaMode2d::Opaque => {
let needs_sort = mesh_instance.z_index.is_some()
|| mesh_instance.y_sort
|| mesh_instance.sort_bias.is_some();
match (material_2d.properties.alpha_mode, needs_sort) {
(AlphaMode2d::Opaque, false) => {
let bin_key = Opaque2dBinKey {
pipeline: pipeline_id,
draw_function: material_2d.properties.draw_function_id,
Expand All @@ -871,7 +873,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
current_change_tick,
);
}
AlphaMode2d::Mask(_) => {
(AlphaMode2d::Mask(_), false) => {
let bin_key = AlphaMask2dBinKey {
pipeline: pipeline_id,
draw_function: material_2d.properties.draw_function_id,
Expand All @@ -889,7 +891,21 @@ pub fn queue_material2d_meshes<M: Material2d>(
current_change_tick,
);
}
AlphaMode2d::Blend => {
(AlphaMode2d::Blend, false) | (_, true) => {
let mesh_y = mesh_instance.transforms.world_from_local.translation.y;
let mesh_z = mesh_instance.transforms.world_from_local.translation.z;
let z_bias = material_2d.properties.depth_bias;
let sort_bias = mesh_instance.sort_bias;
let bias = if mesh_instance.y_sort {
mesh_y + z_bias + sort_bias.unwrap_or_default()
} else {
mesh_z + z_bias + sort_bias.unwrap_or_default()
};

let sort_key = Transparent2dSortKey::new(
mesh_instance.z_index.unwrap_or_default(),
Some(bias),
);
transparent_phase.add(Transparent2d {
entity: (*render_entity, *visible_entity),
draw_function: material_2d.properties.draw_function_id,
Expand All @@ -898,7 +914,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
// lowest sort key and getting closer should increase. As we have
// -z in front of the camera, the largest distance is -far with values increasing toward the
// camera. As such we can just use mesh_z as the distance
sort_key: FloatOrd(mesh_z + material_2d.properties.depth_bias),
sort_key,
// Batching is done in batch_and_prepare_render_phase
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
Expand Down
42 changes: 41 additions & 1 deletion crates/bevy_sprite/src/mesh2d/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@ impl Plugin for Mesh2dRenderPlugin {
}
}

/// Describes the layer in which the sprite or mesh should be rendered. Higher values are rendered
/// on top of lower values, with a default value of 0. This is useful for controlling the rendering
/// order of sprites and always takes precedence over [`YSort`] or [`SortBias`].
#[derive(Component, Deref, DerefMut, Default, Debug, Clone)]
pub struct ZIndex(pub i32);

/// A marker component that enables Y-sorting (depth sorting) for sprites and meshes.
///
/// When attached to an entity, this component indicates that the entity should be rendered
/// in draw order based on its Y position. Entities with higher Y values (higher on screen)
/// are drawn first, creating a depth illusion where objects lower on the screen appear
/// in front of objects higher on the screen.
#[derive(Component, Default, Debug, Clone)]
pub struct YSort;

/// An arbitrary bias value that can be applied to the sorting order of sprites and meshes and is
/// applied after the [`ZIndex`] or added to the Y position of the entity if [`YSort`] is enabled.
#[derive(Component, Deref, DerefMut, Default, Debug, Clone)]
pub struct SortBias(pub f32);

#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
pub struct ViewKeyCache(MainEntityHashMap<Mesh2dPipelineKey>);

Expand Down Expand Up @@ -228,6 +248,9 @@ pub struct RenderMesh2dInstance {
pub material_bind_group_id: Material2dBindGroupId,
pub automatic_batching: bool,
pub tag: u32,
pub z_index: Option<i32>,
pub y_sort: bool,
pub sort_bias: Option<f32>,
}

#[derive(Default, Resource, Deref, DerefMut)]
Expand All @@ -245,13 +268,27 @@ pub fn extract_mesh2d(
&GlobalTransform,
&Mesh2d,
Option<&MeshTag>,
Option<&ZIndex>,
Has<YSort>,
Option<&SortBias>,
Has<NoAutomaticBatching>,
)>,
>,
) {
render_mesh_instances.clear();

for (entity, view_visibility, transform, handle, tag, no_automatic_batching) in &query {
for (
entity,
view_visibility,
transform,
handle,
tag,
z_index,
y_sort,
sort_bias,
no_automatic_batching,
) in &query
{
if !view_visibility.get() {
continue;
}
Expand All @@ -266,6 +303,9 @@ pub fn extract_mesh2d(
material_bind_group_id: Material2dBindGroupId::default(),
automatic_batching: !no_automatic_batching,
tag: tag.map_or(0, |i| **i),
z_index: z_index.cloned().map(|x| x.0),
y_sort,
sort_bias: sort_bias.cloned().map(|sb| sb.0),
},
);
}
Expand Down
44 changes: 37 additions & 7 deletions crates/bevy_sprite/src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use core::ops::Range;

use crate::{Anchor, ComputedTextureSlices, ScalingMode, Sprite};
use crate::{Anchor, ComputedTextureSlices, ScalingMode, SortBias, Sprite, YSort, ZIndex};
use bevy_asset::{load_embedded_asset, AssetEvent, AssetId, Assets, Handle};

use bevy_color::{ColorToComponents, LinearRgba};
use bevy_core_pipeline::core_2d::Transparent2dSortKey;
use bevy_core_pipeline::{
core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
tonemapping::{
Expand All @@ -17,7 +17,7 @@ use bevy_ecs::{
system::{lifetimeless::*, SystemParamItem, SystemState},
};
use bevy_image::{BevyDefault, Image, ImageSampler, TextureAtlasLayout, TextureFormatPixelInfo};
use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4};
use bevy_math::{Affine3A, Quat, Rect, Vec2, Vec4};
use bevy_platform::collections::HashMap;
use bevy_render::view::{RenderVisibleEntities, RetainedViewEntity};
use bevy_render::{
Expand All @@ -41,6 +41,7 @@ use bevy_render::{
};
use bevy_transform::components::GlobalTransform;
use bytemuck::{Pod, Zeroable};
use core::ops::Range;
use fixedbitset::FixedBitSet;

#[derive(Resource)]
Expand Down Expand Up @@ -343,6 +344,9 @@ pub struct ExtractedSprite {
pub flip_x: bool,
pub flip_y: bool,
pub kind: ExtractedSpriteKind,
pub z_index: i32,
pub y_sort: bool,
pub sort_bias: Option<f32>,
}

pub enum ExtractedSpriteKind {
Expand Down Expand Up @@ -398,13 +402,26 @@ pub fn extract_sprites(
&GlobalTransform,
&Anchor,
Option<&ComputedTextureSlices>,
Option<&ZIndex>,
Has<YSort>,
Option<&SortBias>,
)>,
>,
) {
extracted_sprites.sprites.clear();
extracted_slices.slices.clear();
for (main_entity, render_entity, view_visibility, sprite, transform, anchor, slices) in
sprite_query.iter()
for (
main_entity,
render_entity,
view_visibility,
sprite,
transform,
anchor,
slices,
z_index,
y_sort,
sort_bias,
) in sprite_query.iter()
{
if !view_visibility.get() {
continue;
Expand All @@ -427,6 +444,9 @@ pub fn extract_sprites(
kind: ExtractedSpriteKind::Slices {
indices: start..end,
},
z_index: z_index.cloned().map_or(0, |z| z.0),
y_sort,
sort_bias: sort_bias.cloned().map(|sb| sb.0),
});
} else {
let atlas_rect = sprite
Expand Down Expand Up @@ -460,6 +480,9 @@ pub fn extract_sprites(
// Pass the custom size
custom_size: sprite.custom_size,
},
z_index: z_index.cloned().map_or(0, |z| z.0),
y_sort,
sort_bias: sort_bias.cloned().map(|sb| sb.0),
});
}
}
Expand Down Expand Up @@ -595,7 +618,14 @@ pub fn queue_sprites(
}

// These items will be sorted by depth with other phase items
let sort_key = FloatOrd(extracted_sprite.transform.translation().z);
let sort_key = Transparent2dSortKey::new(
extracted_sprite.z_index,
extracted_sprite
.y_sort
.then_some(extracted_sprite.transform.translation().y)
.map(|y| y + extracted_sprite.sort_bias.unwrap_or(0.0))
.or(extracted_sprite.sort_bias),
);

// Add the item to the render phase
transparent_phase.add(Transparent2d {
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ pub fn extract_text2d_sprite(
kind: bevy_sprite::ExtractedSpriteKind::Slices {
indices: start..end,
},
z_index: 0,
y_sort: false,
sort_bias: None,
Comment on lines +241 to +243
Copy link
Contributor

Choose a reason for hiding this comment

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

Text2d needs to support z index too.

});
start = end;
}
Expand Down
3 changes: 2 additions & 1 deletion examples/2d/2d_viewport_to_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ fn setup(
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(50000.0, 50000.0))),
MeshMaterial2d(materials.add(Color::linear_rgb(0.01, 0.01, 0.01))),
Transform::from_translation(Vec3::new(0.0, 0.0, -200.0)),
Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
bevy::sprite::ZIndex(-1),
));
}
Loading