mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Reorder render sets, refactor bevy_sprite to take advantage (#9236)
This is a continuation of this PR: #8062 # Objective - Reorder render schedule sets to allow data preparation when phase item order is known to support improved batching - Part of the batching/instancing etc plan from here: https://github.com/bevyengine/bevy/issues/89#issuecomment-1379249074 - The original idea came from @inodentry and proved to be a good one. Thanks! - Refactor `bevy_sprite` and `bevy_ui` to take advantage of the new ordering ## Solution - Move `Prepare` and `PrepareFlush` after `PhaseSortFlush` - Add a `PrepareAssets` set that runs in parallel with other systems and sets in the render schedule. - Put prepare_assets systems in the `PrepareAssets` set - If explicit dependencies are needed on Mesh or Material RenderAssets then depend on the appropriate system. - Add `ManageViews` and `ManageViewsFlush` sets between `ExtractCommands` and Queue - Move `queue_mesh*_bind_group` to the Prepare stage - Rename them to `prepare_` - Put systems that prepare resources (buffers, textures, etc.) into a `PrepareResources` set inside `Prepare` - Put the `prepare_..._bind_group` systems into a `PrepareBindGroup` set after `PrepareResources` - Move `prepare_lights` to the `ManageViews` set - `prepare_lights` creates views and this must happen before `Queue` - This system needs refactoring to stop handling all responsibilities - Gather lights, sort, and create shadow map views. Store sorted light entities in a resource - Remove `BatchedPhaseItem` - Replace `batch_range` with `batch_size` representing how many items to skip after rendering the item or to skip the item entirely if `batch_size` is 0. - `queue_sprites` has been split into `queue_sprites` for queueing phase items and `prepare_sprites` for batching after the `PhaseSort` - `PhaseItem`s are still inserted in `queue_sprites` - After sorting adjacent compatible sprite phase items are accumulated into `SpriteBatch` components on the first entity of each batch, containing a range of vertex indices. The associated `PhaseItem`'s `batch_size` is updated appropriately. - `SpriteBatch` items are then drawn skipping over the other items in the batch based on the value in `batch_size` - A very similar refactor was performed on `bevy_ui` --- ## Changelog Changed: - Reordered and reworked render app schedule sets. The main change is that data is extracted, queued, sorted, and then prepared when the order of data is known. - Refactor `bevy_sprite` and `bevy_ui` to take advantage of the reordering. ## Migration Guide - Assets such as materials and meshes should now be created in `PrepareAssets` e.g. `prepare_assets<Mesh>` - Queueing entities to `RenderPhase`s continues to be done in `Queue` e.g. `queue_sprites` - Preparing resources (textures, buffers, etc.) should now be done in `PrepareResources`, e.g. `prepare_prepass_textures`, `prepare_mesh_uniforms` - Prepare bind groups should now be done in `PrepareBindGroups` e.g. `prepare_mesh_bind_group` - Any batching or instancing can now be done in `Prepare` where the order of the phase items is known e.g. `prepare_sprites` ## Next Steps - Introduce some generic mechanism to ensure items that can be batched are grouped in the phase item order, currently you could easily have `[sprite at z 0, mesh at z 0, sprite at z 0]` preventing batching. - Investigate improved orderings for building the MeshUniform buffer - Implementing batching across the rest of bevy --------- Co-authored-by: Robert Swain <robert.swain@gmail.com> Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com>
This commit is contained in:
parent
e8b3892517
commit
4f1d9a6315
42 changed files with 993 additions and 1140 deletions
|
@ -68,10 +68,10 @@ impl Plugin for BloomPlugin {
|
|||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_bloom_textures.in_set(RenderSet::Prepare),
|
||||
prepare_downsampling_pipeline.in_set(RenderSet::Prepare),
|
||||
prepare_upsampling_pipeline.in_set(RenderSet::Prepare),
|
||||
queue_bloom_bind_groups.in_set(RenderSet::Queue),
|
||||
prepare_bloom_textures.in_set(RenderSet::PrepareResources),
|
||||
prepare_bloom_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
||||
),
|
||||
)
|
||||
// Add bloom to the 3d render graph
|
||||
|
@ -403,7 +403,7 @@ struct BloomBindGroups {
|
|||
sampler: Sampler,
|
||||
}
|
||||
|
||||
fn queue_bloom_bind_groups(
|
||||
fn prepare_bloom_bind_groups(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
downsampling_pipeline: Res<BloomDownsamplingPipeline>,
|
||||
|
|
|
@ -29,14 +29,13 @@ use bevy_render::{
|
|||
extract_component::ExtractComponentPlugin,
|
||||
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
|
||||
render_phase::{
|
||||
batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem,
|
||||
DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase,
|
||||
sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
|
||||
RenderPhase,
|
||||
},
|
||||
render_resource::CachedRenderPipelineId,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::FloatOrd;
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode};
|
||||
|
||||
|
@ -57,12 +56,7 @@ impl Plugin for Core2dPlugin {
|
|||
.add_systems(ExtractSchedule, extract_core_2d_camera_phases)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort),
|
||||
batch_phase_system::<Transparent2d>
|
||||
.after(sort_phase_system::<Transparent2d>)
|
||||
.in_set(RenderSet::PhaseSort),
|
||||
),
|
||||
sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort),
|
||||
);
|
||||
|
||||
use graph::node::*;
|
||||
|
@ -89,8 +83,7 @@ pub struct Transparent2d {
|
|||
pub entity: Entity,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
/// Range in the vertex buffer of this item
|
||||
pub batch_range: Option<Range<u32>>,
|
||||
pub batch_size: usize,
|
||||
}
|
||||
|
||||
impl PhaseItem for Transparent2d {
|
||||
|
@ -115,6 +108,11 @@ impl PhaseItem for Transparent2d {
|
|||
fn sort(items: &mut [Self]) {
|
||||
items.sort_by_key(|item| item.sort_key());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn batch_size(&self) -> usize {
|
||||
self.batch_size
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedRenderPipelinePhaseItem for Transparent2d {
|
||||
|
@ -124,16 +122,6 @@ impl CachedRenderPipelinePhaseItem for Transparent2d {
|
|||
}
|
||||
}
|
||||
|
||||
impl BatchedPhaseItem for Transparent2d {
|
||||
fn batch_range(&self) -> &Option<Range<u32>> {
|
||||
&self.batch_range
|
||||
}
|
||||
|
||||
fn batch_range_mut(&mut self) -> &mut Option<Range<u32>> {
|
||||
&mut self.batch_range
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_core_2d_camera_phases(
|
||||
mut commands: Commands,
|
||||
cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
|
||||
|
|
|
@ -87,17 +87,13 @@ impl Plugin for Core3dPlugin {
|
|||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_core_3d_depth_textures
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(bevy_render::view::prepare_windows),
|
||||
prepare_prepass_textures
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(bevy_render::view::prepare_windows),
|
||||
sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<Opaque3dPrepass>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<AlphaMask3dPrepass>.in_set(RenderSet::PhaseSort),
|
||||
prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources),
|
||||
prepare_prepass_textures.in_set(RenderSet::PrepareResources),
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -136,18 +132,15 @@ impl Plugin for Core3dPlugin {
|
|||
|
||||
pub struct Opaque3d {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub entity: Entity,
|
||||
pub draw_function: DrawFunctionId,
|
||||
pub batch_size: usize,
|
||||
}
|
||||
|
||||
impl PhaseItem for Opaque3d {
|
||||
// NOTE: (dynamic offset, -distance)
|
||||
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
|
||||
type SortKey = (u32, Reverse<FloatOrd>);
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -156,10 +149,7 @@ impl PhaseItem for Opaque3d {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
(
|
||||
self.per_object_binding_dynamic_offset,
|
||||
Reverse(FloatOrd(self.distance)),
|
||||
)
|
||||
Reverse(FloatOrd(self.distance))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -170,9 +160,12 @@ impl PhaseItem for Opaque3d {
|
|||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
// Key negated to match reversed SortKey ordering
|
||||
radsort::sort_by_key(items, |item| {
|
||||
(item.per_object_binding_dynamic_offset, -item.distance)
|
||||
});
|
||||
radsort::sort_by_key(items, |item| -item.distance);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn batch_size(&self) -> usize {
|
||||
self.batch_size
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,18 +178,15 @@ impl CachedRenderPipelinePhaseItem for Opaque3d {
|
|||
|
||||
pub struct AlphaMask3d {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub entity: Entity,
|
||||
pub draw_function: DrawFunctionId,
|
||||
pub batch_size: usize,
|
||||
}
|
||||
|
||||
impl PhaseItem for AlphaMask3d {
|
||||
// NOTE: (dynamic offset, -distance)
|
||||
// NOTE: Values increase towards the camera. Front-to-back ordering for alpha mask means we need a descending sort.
|
||||
type SortKey = (u32, Reverse<FloatOrd>);
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -205,10 +195,7 @@ impl PhaseItem for AlphaMask3d {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
(
|
||||
self.per_object_binding_dynamic_offset,
|
||||
Reverse(FloatOrd(self.distance)),
|
||||
)
|
||||
Reverse(FloatOrd(self.distance))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -219,9 +206,12 @@ impl PhaseItem for AlphaMask3d {
|
|||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
// Key negated to match reversed SortKey ordering
|
||||
radsort::sort_by_key(items, |item| {
|
||||
(item.per_object_binding_dynamic_offset, -item.distance)
|
||||
});
|
||||
radsort::sort_by_key(items, |item| -item.distance);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn batch_size(&self) -> usize {
|
||||
self.batch_size
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,6 +227,7 @@ pub struct Transparent3d {
|
|||
pub pipeline: CachedRenderPipelineId,
|
||||
pub entity: Entity,
|
||||
pub draw_function: DrawFunctionId,
|
||||
pub batch_size: usize,
|
||||
}
|
||||
|
||||
impl PhaseItem for Transparent3d {
|
||||
|
@ -262,6 +253,11 @@ impl PhaseItem for Transparent3d {
|
|||
fn sort(items: &mut [Self]) {
|
||||
radsort::sort_by_key(items, |item| item.distance);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn batch_size(&self) -> usize {
|
||||
self.batch_size
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedRenderPipelinePhaseItem for Transparent3d {
|
||||
|
|
|
@ -26,7 +26,7 @@ impl Plugin for MsaaWritebackPlugin {
|
|||
|
||||
render_app.add_systems(
|
||||
Render,
|
||||
queue_msaa_writeback_pipelines.in_set(RenderSet::Queue),
|
||||
prepare_msaa_writeback_pipelines.in_set(RenderSet::Prepare),
|
||||
);
|
||||
{
|
||||
use core_2d::graph::node::*;
|
||||
|
@ -123,7 +123,7 @@ impl Node for MsaaWritebackNode {
|
|||
#[derive(Component)]
|
||||
pub struct MsaaWritebackBlitPipeline(CachedRenderPipelineId);
|
||||
|
||||
fn queue_msaa_writeback_pipelines(
|
||||
fn prepare_msaa_writeback_pipelines(
|
||||
mut commands: Commands,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<BlitPipeline>>,
|
||||
|
|
|
@ -80,18 +80,14 @@ pub struct ViewPrepassTextures {
|
|||
/// Used to render all 3D meshes with materials that have no transparency.
|
||||
pub struct Opaque3dPrepass {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub entity: Entity,
|
||||
pub pipeline_id: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for Opaque3dPrepass {
|
||||
// NOTE: (dynamic offset, -distance)
|
||||
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
|
||||
type SortKey = (u32, Reverse<FloatOrd>);
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -100,10 +96,7 @@ impl PhaseItem for Opaque3dPrepass {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
(
|
||||
self.per_object_binding_dynamic_offset,
|
||||
Reverse(FloatOrd(self.distance)),
|
||||
)
|
||||
Reverse(FloatOrd(self.distance))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -114,9 +107,7 @@ impl PhaseItem for Opaque3dPrepass {
|
|||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
// Key negated to match reversed SortKey ordering
|
||||
radsort::sort_by_key(items, |item| {
|
||||
(item.per_object_binding_dynamic_offset, -item.distance)
|
||||
});
|
||||
radsort::sort_by_key(items, |item| -item.distance);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,18 +125,14 @@ impl CachedRenderPipelinePhaseItem for Opaque3dPrepass {
|
|||
/// Used to render all meshes with a material with an alpha mask.
|
||||
pub struct AlphaMask3dPrepass {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub entity: Entity,
|
||||
pub pipeline_id: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for AlphaMask3dPrepass {
|
||||
// NOTE: (dynamic offset, -distance)
|
||||
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
|
||||
type SortKey = (u32, Reverse<FloatOrd>);
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -154,10 +141,7 @@ impl PhaseItem for AlphaMask3dPrepass {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
(
|
||||
self.per_object_binding_dynamic_offset,
|
||||
Reverse(FloatOrd(self.distance)),
|
||||
)
|
||||
Reverse(FloatOrd(self.distance))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -168,9 +152,7 @@ impl PhaseItem for AlphaMask3dPrepass {
|
|||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
// Key negated to match reversed SortKey ordering
|
||||
radsort::sort_by_key(items, |item| {
|
||||
(item.per_object_binding_dynamic_offset, -item.distance)
|
||||
});
|
||||
radsort::sort_by_key(items, |item| -item.distance);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ impl Plugin for SkyboxPlugin {
|
|||
Render,
|
||||
(
|
||||
prepare_skybox_pipelines.in_set(RenderSet::Prepare),
|
||||
queue_skybox_bind_groups.in_set(RenderSet::Queue),
|
||||
prepare_skybox_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ fn prepare_skybox_pipelines(
|
|||
#[derive(Component)]
|
||||
pub struct SkyboxBindGroup(pub BindGroup);
|
||||
|
||||
fn queue_skybox_bind_groups(
|
||||
fn prepare_skybox_bind_groups(
|
||||
mut commands: Commands,
|
||||
pipeline: Res<SkyboxPipeline>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
|
|
|
@ -10,7 +10,7 @@ use bevy_core::FrameCount;
|
|||
use bevy_ecs::{
|
||||
prelude::{Bundle, Component, Entity},
|
||||
query::{QueryItem, With},
|
||||
schedule::{apply_deferred, IntoSystemConfigs},
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
|
@ -31,7 +31,7 @@ use bevy_render::{
|
|||
},
|
||||
renderer::{RenderContext, RenderDevice},
|
||||
texture::{BevyDefault, CachedTexture, TextureCache},
|
||||
view::{prepare_view_uniforms, ExtractedView, Msaa, ViewTarget},
|
||||
view::{ExtractedView, Msaa, ViewTarget},
|
||||
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
||||
|
@ -67,12 +67,9 @@ impl Plugin for TemporalAntiAliasPlugin {
|
|||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
(prepare_taa_jitter_and_mip_bias, apply_deferred)
|
||||
.chain()
|
||||
.before(prepare_view_uniforms)
|
||||
.in_set(RenderSet::Prepare),
|
||||
prepare_taa_history_textures.in_set(RenderSet::Prepare),
|
||||
prepare_taa_jitter_and_mip_bias.in_set(RenderSet::ManageViews),
|
||||
prepare_taa_pipelines.in_set(RenderSet::Prepare),
|
||||
prepare_taa_history_textures.in_set(RenderSet::PrepareResources),
|
||||
),
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<TAANode>>(CORE_3D, draw_3d_graph::node::TAA)
|
||||
|
|
|
@ -97,7 +97,7 @@ impl Plugin for TonemappingPlugin {
|
|||
.init_resource::<SpecializedRenderPipelines<TonemappingPipeline>>()
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_view_tonemapping_pipelines.in_set(RenderSet::Queue),
|
||||
prepare_view_tonemapping_pipelines.in_set(RenderSet::Prepare),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -272,7 +272,7 @@ impl FromWorld for TonemappingPipeline {
|
|||
#[derive(Component)]
|
||||
pub struct ViewTonemappingPipeline(CachedRenderPipelineId);
|
||||
|
||||
pub fn queue_view_tonemapping_pipelines(
|
||||
pub fn prepare_view_tonemapping_pipelines(
|
||||
mut commands: Commands,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<TonemappingPipeline>>,
|
||||
|
|
|
@ -16,7 +16,7 @@ impl Plugin for UpscalingPlugin {
|
|||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.add_systems(
|
||||
Render,
|
||||
queue_view_upscaling_pipelines.in_set(RenderSet::Queue),
|
||||
prepare_view_upscaling_pipelines.in_set(RenderSet::Prepare),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ impl Plugin for UpscalingPlugin {
|
|||
#[derive(Component)]
|
||||
pub struct ViewUpscalingPipeline(CachedRenderPipelineId);
|
||||
|
||||
fn queue_view_upscaling_pipelines(
|
||||
fn prepare_view_upscaling_pipelines(
|
||||
mut commands: Commands,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<BlitPipeline>>,
|
||||
|
|
|
@ -102,7 +102,10 @@ impl Plugin for GizmoPlugin {
|
|||
|
||||
render_app
|
||||
.add_systems(ExtractSchedule, extract_gizmo_data)
|
||||
.add_systems(Render, queue_line_gizmo_bind_group.in_set(RenderSet::Queue));
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
app.add_plugins(pipeline_2d::LineGizmo2dPlugin);
|
||||
|
@ -413,7 +416,7 @@ struct LineGizmoUniformBindgroup {
|
|||
bindgroup: BindGroup,
|
||||
}
|
||||
|
||||
fn queue_line_gizmo_bind_group(
|
||||
fn prepare_line_gizmo_bind_group(
|
||||
mut commands: Commands,
|
||||
line_gizmo_uniform_layout: Res<LineGizmoUniformBindgroupLayout>,
|
||||
render_device: Res<RenderDevice>,
|
||||
|
|
|
@ -13,7 +13,7 @@ use bevy_ecs::{
|
|||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_render::{
|
||||
render_asset::RenderAssets,
|
||||
render_asset::{prepare_assets, RenderAssets},
|
||||
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::*,
|
||||
texture::BevyDefault,
|
||||
|
@ -34,7 +34,12 @@ impl Plugin for LineGizmo2dPlugin {
|
|||
render_app
|
||||
.add_render_command::<Transparent2d, DrawLineGizmo2d>()
|
||||
.init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
|
||||
.add_systems(Render, queue_line_gizmos_2d.in_set(RenderSet::Queue));
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_line_gizmos_2d
|
||||
.in_set(RenderSet::Queue)
|
||||
.after(prepare_assets::<LineGizmo>),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
|
@ -173,7 +178,7 @@ fn queue_line_gizmos_2d(
|
|||
draw_function,
|
||||
pipeline,
|
||||
sort_key: FloatOrd(f32::INFINITY),
|
||||
batch_range: None,
|
||||
batch_size: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use bevy_ecs::{
|
|||
};
|
||||
use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup};
|
||||
use bevy_render::{
|
||||
render_asset::RenderAssets,
|
||||
render_asset::{prepare_assets, RenderAssets},
|
||||
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::*,
|
||||
texture::BevyDefault,
|
||||
|
@ -32,7 +32,12 @@ impl Plugin for LineGizmo3dPlugin {
|
|||
render_app
|
||||
.add_render_command::<Transparent3d, DrawLineGizmo3d>()
|
||||
.init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
|
||||
.add_systems(Render, queue_line_gizmos_3d.in_set(RenderSet::Queue));
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_line_gizmos_3d
|
||||
.in_set(RenderSet::Queue)
|
||||
.after(prepare_assets::<LineGizmo>),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
|
@ -187,6 +192,7 @@ fn queue_line_gizmos_3d(
|
|||
draw_function,
|
||||
pipeline,
|
||||
distance: 0.,
|
||||
batch_size: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ bevy_derive = { path = "../bevy_derive", version = "0.12.0-dev" }
|
|||
|
||||
# other
|
||||
bitflags = "2.3"
|
||||
fixedbitset = "0.4"
|
||||
# direct dependency required for derive macro
|
||||
bytemuck = { version = "1", features = ["derive"] }
|
||||
naga_oil = "0.8"
|
||||
|
|
|
@ -56,14 +56,10 @@ use bevy_asset::{load_internal_asset, AddAsset, Assets, Handle, HandleUntyped};
|
|||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
camera::CameraUpdateSystem,
|
||||
extract_resource::ExtractResourcePlugin,
|
||||
prelude::Color,
|
||||
render_graph::RenderGraph,
|
||||
render_phase::sort_phase_system,
|
||||
render_resource::Shader,
|
||||
view::{ViewSet, VisibilitySystems},
|
||||
ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
camera::CameraUpdateSystem, extract_resource::ExtractResourcePlugin, prelude::Color,
|
||||
render_asset::prepare_assets, render_graph::RenderGraph, render_phase::sort_phase_system,
|
||||
render_resource::Shader, texture::Image, view::VisibilitySystems, ExtractSchedule, Render,
|
||||
RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_transform::TransformSystem;
|
||||
use environment_map::EnvironmentMapPlugin;
|
||||
|
@ -269,37 +265,18 @@ impl Plugin for PbrPlugin {
|
|||
|
||||
// Extract the required data from the main world
|
||||
render_app
|
||||
.configure_sets(
|
||||
Render,
|
||||
(
|
||||
RenderLightSystems::PrepareLights.in_set(RenderSet::Prepare),
|
||||
RenderLightSystems::PrepareClusters.in_set(RenderSet::Prepare),
|
||||
RenderLightSystems::QueueShadows.in_set(RenderSet::Queue),
|
||||
),
|
||||
)
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
(
|
||||
render::extract_clusters.in_set(RenderLightSystems::ExtractClusters),
|
||||
render::extract_lights.in_set(RenderLightSystems::ExtractLights),
|
||||
),
|
||||
(render::extract_clusters, render::extract_lights),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
render::prepare_lights
|
||||
.before(ViewSet::PrepareUniforms)
|
||||
.in_set(RenderLightSystems::PrepareLights),
|
||||
// A sync is needed after prepare_lights, before prepare_view_uniforms,
|
||||
// because prepare_lights creates new views for shadow mapping
|
||||
apply_deferred
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(RenderLightSystems::PrepareLights)
|
||||
.before(ViewSet::PrepareUniforms),
|
||||
render::prepare_clusters
|
||||
.after(render::prepare_lights)
|
||||
.in_set(RenderLightSystems::PrepareClusters),
|
||||
.in_set(RenderSet::ManageViews)
|
||||
.after(prepare_assets::<Image>),
|
||||
sort_phase_system::<Shadow>.in_set(RenderSet::PhaseSort),
|
||||
render::prepare_clusters.in_set(RenderSet::PrepareResources),
|
||||
),
|
||||
)
|
||||
.init_resource::<LightMeta>();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
render, AlphaMode, DrawMesh, DrawPrepass, EnvironmentMapLight, MeshPipeline, MeshPipelineKey,
|
||||
MeshTransforms, MeshUniform, PrepassPipelinePlugin, PrepassPlugin, RenderLightSystems,
|
||||
ScreenSpaceAmbientOcclusionSettings, SetMeshBindGroup, SetMeshViewBindGroup, Shadow,
|
||||
MeshTransforms, PrepassPipelinePlugin, PrepassPlugin, ScreenSpaceAmbientOcclusionSettings,
|
||||
SetMeshBindGroup, SetMeshViewBindGroup, Shadow,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
|
||||
|
@ -24,15 +24,15 @@ use bevy_render::{
|
|||
extract_component::ExtractComponentPlugin,
|
||||
mesh::{Mesh, MeshVertexBufferLayout},
|
||||
prelude::Image,
|
||||
render_asset::{PrepareAssetSet, RenderAssets},
|
||||
render_asset::{prepare_assets, RenderAssets},
|
||||
render_phase::{
|
||||
AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult,
|
||||
RenderPhase, SetItemPipeline, TrackedRenderPass,
|
||||
},
|
||||
render_resource::{
|
||||
AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, GpuArrayBufferIndex,
|
||||
OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef,
|
||||
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource,
|
||||
PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline,
|
||||
SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
texture::FallbackImage,
|
||||
|
@ -205,10 +205,14 @@ where
|
|||
Render,
|
||||
(
|
||||
prepare_materials::<M>
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(PrepareAssetSet::PreAssetPrepare),
|
||||
render::queue_shadows::<M>.in_set(RenderLightSystems::QueueShadows),
|
||||
queue_material_meshes::<M>.in_set(RenderSet::Queue),
|
||||
.in_set(RenderSet::PrepareAssets)
|
||||
.after(prepare_assets::<Image>),
|
||||
render::queue_shadows::<M>
|
||||
.in_set(RenderSet::QueueMeshes)
|
||||
.after(prepare_materials::<M>),
|
||||
queue_material_meshes::<M>
|
||||
.in_set(RenderSet::QueueMeshes)
|
||||
.after(prepare_materials::<M>),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -379,12 +383,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||
msaa: Res<Msaa>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_materials: Res<RenderMaterials<M>>,
|
||||
material_meshes: Query<(
|
||||
&Handle<M>,
|
||||
&Handle<Mesh>,
|
||||
&MeshTransforms,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
)>,
|
||||
material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshTransforms)>,
|
||||
images: Res<RenderAssets<Image>>,
|
||||
mut views: Query<(
|
||||
&ExtractedView,
|
||||
|
@ -468,7 +467,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||
|
||||
let rangefinder = view.rangefinder3d();
|
||||
for visible_entity in &visible_entities.entities {
|
||||
if let Ok((material_handle, mesh_handle, mesh_transforms, batch_indices)) =
|
||||
if let Ok((material_handle, mesh_handle, mesh_transforms)) =
|
||||
material_meshes.get(*visible_entity)
|
||||
{
|
||||
if let (Some(mesh), Some(material)) = (
|
||||
|
@ -526,9 +525,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||
draw_function: draw_opaque_pbr,
|
||||
pipeline: pipeline_id,
|
||||
distance,
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
batch_size: 1,
|
||||
});
|
||||
}
|
||||
AlphaMode::Mask(_) => {
|
||||
|
@ -537,9 +534,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||
draw_function: draw_alpha_mask_pbr,
|
||||
pipeline: pipeline_id,
|
||||
distance,
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
batch_size: 1,
|
||||
});
|
||||
}
|
||||
AlphaMode::Blend
|
||||
|
@ -551,6 +546,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||
draw_function: draw_transparent_pbr,
|
||||
pipeline: pipeline_id,
|
||||
distance,
|
||||
batch_size: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,11 +30,11 @@ use bevy_render::{
|
|||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
|
||||
BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType,
|
||||
ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState,
|
||||
DynamicUniformBuffer, FragmentState, FrontFace, GpuArrayBufferIndex, MultisampleState,
|
||||
PipelineCache, PolygonMode, PrimitiveState, PushConstantRange, RenderPipelineDescriptor,
|
||||
Shader, ShaderRef, ShaderStages, ShaderType, SpecializedMeshPipeline,
|
||||
SpecializedMeshPipelineError, SpecializedMeshPipelines, StencilFaceState, StencilState,
|
||||
TextureSampleType, TextureViewDimension, VertexState,
|
||||
DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache,
|
||||
PolygonMode, PrimitiveState, PushConstantRange, RenderPipelineDescriptor, Shader,
|
||||
ShaderRef, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError,
|
||||
SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType,
|
||||
TextureViewDimension, VertexState,
|
||||
},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
texture::{FallbackImagesDepth, FallbackImagesMsaa},
|
||||
|
@ -45,9 +45,9 @@ use bevy_transform::prelude::GlobalTransform;
|
|||
use bevy_utils::tracing::error;
|
||||
|
||||
use crate::{
|
||||
prepare_lights, setup_morph_and_skinning_defs, AlphaMode, DrawMesh, Material, MaterialPipeline,
|
||||
MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey, MeshTransforms, MeshUniform,
|
||||
RenderMaterials, SetMaterialBindGroup, SetMeshBindGroup,
|
||||
prepare_materials, setup_morph_and_skinning_defs, AlphaMode, DrawMesh, Material,
|
||||
MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey,
|
||||
MeshTransforms, RenderMaterials, SetMaterialBindGroup, SetMeshBindGroup,
|
||||
};
|
||||
|
||||
use std::{hash::Hash, marker::PhantomData};
|
||||
|
@ -105,7 +105,7 @@ where
|
|||
render_app
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_prepass_view_bind_group::<M>.in_set(RenderSet::Queue),
|
||||
prepare_prepass_view_bind_group::<M>.in_set(RenderSet::PrepareBindGroups),
|
||||
)
|
||||
.init_resource::<PrepassViewBindGroup>()
|
||||
.init_resource::<SpecializedMeshPipelines<PrepassPipeline<M>>>()
|
||||
|
@ -161,15 +161,7 @@ where
|
|||
.add_systems(ExtractSchedule, extract_camera_previous_view_projection)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_previous_view_projection_uniforms
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(PrepassLightsViewFlush),
|
||||
apply_deferred
|
||||
.in_set(RenderSet::Prepare)
|
||||
.in_set(PrepassLightsViewFlush)
|
||||
.after(prepare_lights),
|
||||
),
|
||||
prepare_previous_view_projection_uniforms.in_set(RenderSet::PrepareResources),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -178,7 +170,9 @@ where
|
|||
.add_render_command::<AlphaMask3dPrepass, DrawPrepass<M>>()
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_prepass_material_meshes::<M>.in_set(RenderSet::Queue),
|
||||
queue_prepass_material_meshes::<M>
|
||||
.in_set(RenderSet::QueueMeshes)
|
||||
.after(prepare_materials::<M>),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -697,7 +691,7 @@ pub struct PrepassViewBindGroup {
|
|||
no_motion_vectors: Option<BindGroup>,
|
||||
}
|
||||
|
||||
pub fn queue_prepass_view_bind_group<M: Material>(
|
||||
pub fn prepare_prepass_view_bind_group<M: Material>(
|
||||
render_device: Res<RenderDevice>,
|
||||
prepass_pipeline: Res<PrepassPipeline<M>>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
|
@ -759,12 +753,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
msaa: Res<Msaa>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_materials: Res<RenderMaterials<M>>,
|
||||
material_meshes: Query<(
|
||||
&Handle<M>,
|
||||
&Handle<Mesh>,
|
||||
&MeshTransforms,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
)>,
|
||||
material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshTransforms)>,
|
||||
mut views: Query<(
|
||||
&ExtractedView,
|
||||
&VisibleEntities,
|
||||
|
@ -809,7 +798,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
let rangefinder = view.rangefinder3d();
|
||||
|
||||
for visible_entity in &visible_entities.entities {
|
||||
let Ok((material_handle, mesh_handle, mesh_transforms, batch_indices)) =
|
||||
let Ok((material_handle, mesh_handle, mesh_transforms)) =
|
||||
material_meshes.get(*visible_entity)
|
||||
else {
|
||||
continue;
|
||||
|
@ -863,9 +852,6 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
draw_function: opaque_draw_prepass,
|
||||
pipeline_id,
|
||||
distance,
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
AlphaMode::Mask(_) => {
|
||||
|
@ -874,9 +860,6 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
draw_function: alpha_mask_draw_prepass,
|
||||
pipeline_id,
|
||||
distance,
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
AlphaMode::Blend
|
||||
|
|
|
@ -113,12 +113,6 @@ pub fn prepare_fog(
|
|||
.write_buffer(&render_device, &render_queue);
|
||||
}
|
||||
|
||||
/// Labels for fog-related systems
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
pub enum RenderFogSystems {
|
||||
PrepareFog,
|
||||
}
|
||||
|
||||
/// Inserted on each `Entity` with an `ExtractedView` to keep track of its offset
|
||||
/// in the `gpu_fogs` `DynamicUniformBuffer` within `FogMeta`
|
||||
#[derive(Component)]
|
||||
|
@ -143,11 +137,7 @@ impl Plugin for FogPlugin {
|
|||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.init_resource::<FogMeta>()
|
||||
.add_systems(Render, prepare_fog.in_set(RenderFogSystems::PrepareFog))
|
||||
.configure_set(
|
||||
Render,
|
||||
RenderFogSystems::PrepareFog.in_set(RenderSet::Prepare),
|
||||
);
|
||||
.add_systems(Render, prepare_fog.in_set(RenderSet::PrepareResources));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ use crate::{
|
|||
CascadeShadowConfig, Cascades, CascadesVisibleEntities, Clusters, CubemapVisibleEntities,
|
||||
DirectionalLight, DirectionalLightShadowMap, DrawPrepass, EnvironmentMapLight,
|
||||
GlobalVisiblePointLights, Material, MaterialPipelineKey, MeshPipeline, MeshPipelineKey,
|
||||
MeshUniform, NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline,
|
||||
RenderMaterials, SpotLight, VisiblePointLights,
|
||||
NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline, RenderMaterials, SpotLight,
|
||||
VisiblePointLights,
|
||||
};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_core_pipeline::core_3d::Transparent3d;
|
||||
|
@ -32,15 +32,6 @@ use bevy_utils::{
|
|||
};
|
||||
use std::{hash::Hash, num::NonZeroU64};
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
pub enum RenderLightSystems {
|
||||
ExtractClusters,
|
||||
ExtractLights,
|
||||
PrepareClusters,
|
||||
PrepareLights,
|
||||
QueueShadows,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ExtractedPointLight {
|
||||
color: Color,
|
||||
|
@ -1556,10 +1547,7 @@ pub fn prepare_clusters(
|
|||
pub fn queue_shadows<M: Material>(
|
||||
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
|
||||
prepass_pipeline: Res<PrepassPipeline<M>>,
|
||||
casting_meshes: Query<
|
||||
(&GpuArrayBufferIndex<MeshUniform>, &Handle<Mesh>, &Handle<M>),
|
||||
Without<NotShadowCaster>,
|
||||
>,
|
||||
casting_meshes: Query<(&Handle<Mesh>, &Handle<M>), Without<NotShadowCaster>>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_materials: Res<RenderMaterials<M>>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
|
||||
|
@ -1604,9 +1592,7 @@ pub fn queue_shadows<M: Material>(
|
|||
// NOTE: Lights with shadow mapping disabled will have no visible entities
|
||||
// so no meshes will be queued
|
||||
for entity in visible_entities.iter().copied() {
|
||||
if let Ok((batch_indices, mesh_handle, material_handle)) =
|
||||
casting_meshes.get(entity)
|
||||
{
|
||||
if let Ok((mesh_handle, material_handle)) = casting_meshes.get(entity) {
|
||||
if let (Some(mesh), Some(material)) = (
|
||||
render_meshes.get(mesh_handle),
|
||||
render_materials.get(material_handle),
|
||||
|
@ -1653,9 +1639,6 @@ pub fn queue_shadows<M: Material>(
|
|||
pipeline: pipeline_id,
|
||||
entity,
|
||||
distance: 0.0, // TODO: sort front-to-back
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1666,16 +1649,13 @@ pub fn queue_shadows<M: Material>(
|
|||
|
||||
pub struct Shadow {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub entity: Entity,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for Shadow {
|
||||
type SortKey = (usize, u32);
|
||||
type SortKey = usize;
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -1684,7 +1664,7 @@ impl PhaseItem for Shadow {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
(self.pipeline.id(), self.per_object_binding_dynamic_offset)
|
||||
self.pipeline.id()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use crate::{
|
||||
environment_map, prepass, EnvironmentMapLight, FogMeta, GlobalLightMeta, GpuFog, GpuLights,
|
||||
GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver, PreviousGlobalTransform,
|
||||
ScreenSpaceAmbientOcclusionTextures, ShadowSamplers, ViewClusterBindings, ViewFogUniformOffset,
|
||||
ViewLightsUniformOffset, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT,
|
||||
MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
|
||||
ScreenSpaceAmbientOcclusionTextures, Shadow, ShadowSamplers, ViewClusterBindings,
|
||||
ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings,
|
||||
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
|
||||
};
|
||||
use bevy_app::Plugin;
|
||||
use bevy_asset::{load_internal_asset, Assets, Handle, HandleId, HandleUntyped};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::{AlphaMask3d, Opaque3d, Transparent3d},
|
||||
prepass::ViewPrepassTextures,
|
||||
tonemapping::{
|
||||
get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
|
||||
|
@ -29,7 +30,7 @@ use bevy_render::{
|
|||
},
|
||||
prelude::Msaa,
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
|
||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, TrackedRenderPass},
|
||||
render_resource::*,
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
texture::{
|
||||
|
@ -41,6 +42,7 @@ use bevy_render::{
|
|||
};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use bevy_utils::{tracing::error, HashMap, Hashed};
|
||||
use fixedbitset::FixedBitSet;
|
||||
|
||||
use crate::render::{
|
||||
morph::{extract_morphs, prepare_morphs, MorphIndex, MorphUniform},
|
||||
|
@ -126,11 +128,11 @@ impl Plugin for MeshRenderPlugin {
|
|||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_mesh_uniforms.in_set(RenderSet::Prepare),
|
||||
prepare_skinned_meshes.in_set(RenderSet::Prepare),
|
||||
prepare_morphs.in_set(RenderSet::Prepare),
|
||||
queue_mesh_bind_group.in_set(RenderSet::Queue),
|
||||
queue_mesh_view_bind_groups.in_set(RenderSet::Queue),
|
||||
prepare_mesh_uniforms.in_set(RenderSet::PrepareResources),
|
||||
prepare_skinned_meshes.in_set(RenderSet::PrepareResources),
|
||||
prepare_morphs.in_set(RenderSet::PrepareResources),
|
||||
prepare_mesh_bind_group.in_set(RenderSet::PrepareBindGroups),
|
||||
prepare_mesh_view_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -380,25 +382,59 @@ pub fn extract_skinned_meshes(
|
|||
commands.insert_or_spawn_batch(values);
|
||||
}
|
||||
|
||||
fn prepare_mesh_uniforms(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn prepare_mesh_uniforms(
|
||||
mut seen: Local<FixedBitSet>,
|
||||
mut commands: Commands,
|
||||
mut previous_len: Local<usize>,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut gpu_array_buffer: ResMut<GpuArrayBuffer<MeshUniform>>,
|
||||
components: Query<(Entity, &MeshTransforms)>,
|
||||
views: Query<(
|
||||
&RenderPhase<Opaque3d>,
|
||||
&RenderPhase<Transparent3d>,
|
||||
&RenderPhase<AlphaMask3d>,
|
||||
)>,
|
||||
shadow_views: Query<&RenderPhase<Shadow>>,
|
||||
meshes: Query<(Entity, &MeshTransforms)>,
|
||||
) {
|
||||
gpu_array_buffer.clear();
|
||||
seen.clear();
|
||||
|
||||
let entities = components
|
||||
.iter()
|
||||
.map(|(entity, mesh_transforms)| {
|
||||
(
|
||||
entity,
|
||||
gpu_array_buffer.push(MeshUniform::from(mesh_transforms)),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
commands.insert_or_spawn_batch(entities);
|
||||
let mut indices = Vec::with_capacity(*previous_len);
|
||||
let mut push_indices = |(mesh, mesh_uniform): (Entity, &MeshTransforms)| {
|
||||
let index = mesh.index() as usize;
|
||||
if !seen.contains(index) {
|
||||
if index >= seen.len() {
|
||||
seen.grow(index + 1);
|
||||
}
|
||||
seen.insert(index);
|
||||
indices.push((mesh, gpu_array_buffer.push(mesh_uniform.into())));
|
||||
}
|
||||
};
|
||||
|
||||
for (opaque_phase, transparent_phase, alpha_phase) in &views {
|
||||
meshes
|
||||
.iter_many(opaque_phase.iter_entities())
|
||||
.for_each(&mut push_indices);
|
||||
|
||||
meshes
|
||||
.iter_many(transparent_phase.iter_entities())
|
||||
.for_each(&mut push_indices);
|
||||
|
||||
meshes
|
||||
.iter_many(alpha_phase.iter_entities())
|
||||
.for_each(&mut push_indices);
|
||||
}
|
||||
|
||||
for shadow_phase in &shadow_views {
|
||||
meshes
|
||||
.iter_many(shadow_phase.iter_entities())
|
||||
.for_each(&mut push_indices);
|
||||
}
|
||||
|
||||
*previous_len = indices.len();
|
||||
commands.insert_or_spawn_batch(indices);
|
||||
|
||||
gpu_array_buffer.write_buffer(&render_device, &render_queue);
|
||||
}
|
||||
|
@ -1062,7 +1098,7 @@ impl MeshBindGroups {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn queue_mesh_bind_group(
|
||||
pub fn prepare_mesh_bind_group(
|
||||
meshes: Res<RenderAssets<Mesh>>,
|
||||
mut groups: ResMut<MeshBindGroups>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
|
@ -1138,7 +1174,7 @@ pub struct MeshViewBindGroup {
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn queue_mesh_view_bind_groups(
|
||||
pub fn prepare_mesh_view_bind_groups(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
|
|
|
@ -116,9 +116,14 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin {
|
|||
.init_resource::<SsaoPipelines>()
|
||||
.init_resource::<SpecializedComputePipelines<SsaoPipelines>>()
|
||||
.add_systems(ExtractSchedule, extract_ssao_settings)
|
||||
.add_systems(Render, prepare_ssao_textures.in_set(RenderSet::Prepare))
|
||||
.add_systems(Render, prepare_ssao_pipelines.in_set(RenderSet::Prepare))
|
||||
.add_systems(Render, queue_ssao_bind_groups.in_set(RenderSet::Queue))
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_ssao_pipelines.in_set(RenderSet::Prepare),
|
||||
prepare_ssao_textures.in_set(RenderSet::PrepareResources),
|
||||
prepare_ssao_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
||||
),
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<SsaoNode>>(
|
||||
CORE_3D,
|
||||
draw_3d_graph::node::SCREEN_SPACE_AMBIENT_OCCLUSION,
|
||||
|
@ -755,7 +760,7 @@ struct SsaoBindGroups {
|
|||
spatial_denoise_bind_group: BindGroup,
|
||||
}
|
||||
|
||||
fn queue_ssao_bind_groups(
|
||||
fn prepare_ssao_bind_groups(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
pipelines: Res<SsaoPipelines>,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup};
|
||||
use crate::{DrawMesh, MeshPipelineKey, SetMeshBindGroup, SetMeshViewBindGroup};
|
||||
use crate::{MeshPipeline, MeshTransforms};
|
||||
use bevy_app::Plugin;
|
||||
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
|
||||
|
@ -7,7 +7,6 @@ use bevy_ecs::{prelude::*, reflect::ReflectComponent};
|
|||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::{Reflect, TypeUuid};
|
||||
use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin};
|
||||
use bevy_render::render_resource::GpuArrayBufferIndex;
|
||||
use bevy_render::Render;
|
||||
use bevy_render::{
|
||||
extract_resource::{ExtractResource, ExtractResourcePlugin},
|
||||
|
@ -50,7 +49,7 @@ impl Plugin for WireframePlugin {
|
|||
render_app
|
||||
.add_render_command::<Opaque3d, DrawWireframes>()
|
||||
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
|
||||
.add_systems(Render, queue_wireframes.in_set(RenderSet::Queue));
|
||||
.add_systems(Render, queue_wireframes.in_set(RenderSet::QueueMeshes));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,21 +117,8 @@ fn queue_wireframes(
|
|||
pipeline_cache: Res<PipelineCache>,
|
||||
msaa: Res<Msaa>,
|
||||
mut material_meshes: ParamSet<(
|
||||
Query<(
|
||||
Entity,
|
||||
&Handle<Mesh>,
|
||||
&MeshTransforms,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
)>,
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&Handle<Mesh>,
|
||||
&MeshTransforms,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
),
|
||||
With<Wireframe>,
|
||||
>,
|
||||
Query<(Entity, &Handle<Mesh>, &MeshTransforms)>,
|
||||
Query<(Entity, &Handle<Mesh>, &MeshTransforms), With<Wireframe>>,
|
||||
)>,
|
||||
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
|
||||
) {
|
||||
|
@ -142,36 +128,34 @@ fn queue_wireframes(
|
|||
let rangefinder = view.rangefinder3d();
|
||||
|
||||
let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);
|
||||
let add_render_phase = |(entity, mesh_handle, mesh_transforms, batch_indices): (
|
||||
Entity,
|
||||
&Handle<Mesh>,
|
||||
&MeshTransforms,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
)| {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let key =
|
||||
view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline_id =
|
||||
pipelines.specialize(&pipeline_cache, &wireframe_pipeline, key, &mesh.layout);
|
||||
let pipeline_id = match pipeline_id {
|
||||
Ok(id) => id,
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
opaque_phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline: pipeline_id,
|
||||
draw_function: draw_custom,
|
||||
distance: rangefinder
|
||||
.distance_translation(&mesh_transforms.transform.translation),
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
};
|
||||
let add_render_phase =
|
||||
|(entity, mesh_handle, mesh_transforms): (Entity, &Handle<Mesh>, &MeshTransforms)| {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let key = view_key
|
||||
| MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline_id = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&wireframe_pipeline,
|
||||
key,
|
||||
&mesh.layout,
|
||||
);
|
||||
let pipeline_id = match pipeline_id {
|
||||
Ok(id) => id,
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
opaque_phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline: pipeline_id,
|
||||
draw_function: draw_custom,
|
||||
distance: rangefinder
|
||||
.distance_translation(&mesh_transforms.transform.translation),
|
||||
batch_size: 1,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if wireframe_config.global {
|
||||
let query = material_meshes.p0();
|
||||
|
|
|
@ -39,7 +39,7 @@ impl Plugin for CameraPlugin {
|
|||
render_app
|
||||
.init_resource::<SortedCameras>()
|
||||
.add_systems(ExtractSchedule, extract_cameras)
|
||||
.add_systems(Render, sort_cameras.in_set(RenderSet::Prepare));
|
||||
.add_systems(Render, sort_cameras.in_set(RenderSet::ManageViews));
|
||||
let camera_driver_node = CameraDriverNode::new(&mut render_app.world);
|
||||
let mut render_graph = render_app.world.resource_mut::<RenderGraph>();
|
||||
render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node);
|
||||
|
|
|
@ -85,7 +85,7 @@ impl<C: Component + ShaderType + WriteInto + Clone> Plugin for UniformComponentP
|
|||
.insert_resource(ComponentUniforms::<C>::default())
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_uniform_components::<C>.in_set(RenderSet::Prepare),
|
||||
prepare_uniform_components::<C>.in_set(RenderSet::PrepareResources),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,10 @@ impl Plugin for GlobalsPlugin {
|
|||
.init_resource::<GlobalsBuffer>()
|
||||
.init_resource::<Time>()
|
||||
.add_systems(ExtractSchedule, (extract_frame_count, extract_time))
|
||||
.add_systems(Render, prepare_globals_buffer.in_set(RenderSet::Prepare));
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_globals_buffer.in_set(RenderSet::PrepareResources),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ impl<C: Component + GpuArrayBufferable> Plugin for GpuComponentArrayBufferPlugin
|
|||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.add_systems(
|
||||
Render,
|
||||
prepare_gpu_component_array_buffers::<C>.in_set(RenderSet::Prepare),
|
||||
prepare_gpu_component_array_buffers::<C>.in_set(RenderSet::PrepareResources),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,8 @@ use wgpu::Instance;
|
|||
|
||||
use crate::{
|
||||
camera::CameraPlugin,
|
||||
mesh::{morph::MorphPlugin, MeshPlugin},
|
||||
mesh::{morph::MorphPlugin, Mesh, MeshPlugin},
|
||||
render_asset::prepare_assets,
|
||||
render_resource::{PipelineCache, Shader, ShaderLoader},
|
||||
renderer::{render_system, RenderInstance},
|
||||
settings::WgpuSettings,
|
||||
|
@ -83,21 +84,31 @@ pub enum RenderSet {
|
|||
/// The copy of [`apply_deferred`] that runs at the beginning of this schedule.
|
||||
/// This is used for applying the commands from the [`ExtractSchedule`]
|
||||
ExtractCommands,
|
||||
/// Prepare render resources from the extracted data for the GPU.
|
||||
Prepare,
|
||||
/// The copy of [`apply_deferred`] that runs immediately after [`Prepare`](RenderSet::Prepare).
|
||||
PrepareFlush,
|
||||
/// Create [`BindGroups`](render_resource::BindGroup) that depend on
|
||||
/// [`Prepare`](RenderSet::Prepare) data and queue up draw calls to run during the
|
||||
/// [`Render`](RenderSet::Render) step.
|
||||
/// Prepare assets that have been created/modified/removed this frame.
|
||||
PrepareAssets,
|
||||
/// Create any additional views such as those used for shadow mapping.
|
||||
ManageViews,
|
||||
/// The copy of [`apply_deferred`] that runs immediately after [`ManageViews`](RenderSet::ManageViews).
|
||||
ManageViewsFlush,
|
||||
/// Queue drawable entities as phase items in [`RenderPhase`](crate::render_phase::RenderPhase)s
|
||||
/// ready for sorting
|
||||
Queue,
|
||||
/// The copy of [`apply_deferred`] that runs immediately after [`Queue`](RenderSet::Queue).
|
||||
QueueFlush,
|
||||
/// A sub-set within Queue where mesh entity queue systems are executed. Ensures `prepare_assets::<Mesh>` is completed.
|
||||
QueueMeshes,
|
||||
// TODO: This could probably be moved in favor of a system ordering abstraction in Render or Queue
|
||||
/// Sort the [`RenderPhases`](render_phase::RenderPhase) here.
|
||||
PhaseSort,
|
||||
/// The copy of [`apply_deferred`] that runs immediately after [`PhaseSort`](RenderSet::PhaseSort).
|
||||
PhaseSortFlush,
|
||||
/// Prepare render resources from extracted data for the GPU based on their sorted order.
|
||||
/// Create [`BindGroups`](crate::render_resource::BindGroup) that depend on those data.
|
||||
Prepare,
|
||||
/// A sub-set within Prepare for initializing buffers, textures and uniforms for use in bind groups.
|
||||
PrepareResources,
|
||||
/// The copy of [`apply_deferred`] that runs between [`PrepareResources`](RenderSet::PrepareResources) and ['PrepareBindGroups'](RenderSet::PrepareBindGroups).
|
||||
PrepareResourcesFlush,
|
||||
/// A sub-set within Prepare for constructing bind groups, or other data that relies on render resources prepared in [`PrepareResources`](RenderSet::PrepareResources).
|
||||
PrepareBindGroups,
|
||||
/// The copy of [`apply_deferred`] that runs immediately after [`Prepare`](RenderSet::Prepare).
|
||||
PrepareFlush,
|
||||
/// Actual rendering happens here.
|
||||
/// In most cases, only the render backend should insert resources here.
|
||||
Render,
|
||||
|
@ -125,22 +136,22 @@ impl Render {
|
|||
|
||||
// Create "stage-like" structure using buffer flushes + ordering
|
||||
schedule.add_systems((
|
||||
apply_deferred.in_set(PrepareFlush),
|
||||
apply_deferred.in_set(QueueFlush),
|
||||
apply_deferred.in_set(PhaseSortFlush),
|
||||
apply_deferred.in_set(ManageViewsFlush),
|
||||
apply_deferred.in_set(PrepareResourcesFlush),
|
||||
apply_deferred.in_set(RenderFlush),
|
||||
apply_deferred.in_set(PrepareFlush),
|
||||
apply_deferred.in_set(CleanupFlush),
|
||||
));
|
||||
|
||||
schedule.configure_sets(
|
||||
(
|
||||
ExtractCommands,
|
||||
ManageViews,
|
||||
ManageViewsFlush,
|
||||
Queue,
|
||||
PhaseSort,
|
||||
Prepare,
|
||||
PrepareFlush,
|
||||
Queue,
|
||||
QueueFlush,
|
||||
PhaseSort,
|
||||
PhaseSortFlush,
|
||||
Render,
|
||||
RenderFlush,
|
||||
Cleanup,
|
||||
|
@ -149,6 +160,18 @@ impl Render {
|
|||
.chain(),
|
||||
);
|
||||
|
||||
schedule.configure_sets((ExtractCommands, PrepareAssets, Prepare).chain());
|
||||
schedule.configure_set(
|
||||
QueueMeshes
|
||||
.in_set(RenderSet::Queue)
|
||||
.after(prepare_assets::<Mesh>),
|
||||
);
|
||||
schedule.configure_sets(
|
||||
(PrepareResources, PrepareResourcesFlush, PrepareBindGroups)
|
||||
.chain()
|
||||
.in_set(Prepare),
|
||||
);
|
||||
|
||||
schedule
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ pub mod shape;
|
|||
|
||||
pub use mesh::*;
|
||||
|
||||
use crate::render_asset::RenderAssetPlugin;
|
||||
use crate::{prelude::Image, render_asset::RenderAssetPlugin};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::AddAsset;
|
||||
use bevy_ecs::entity::Entity;
|
||||
|
@ -20,6 +20,7 @@ impl Plugin for MeshPlugin {
|
|||
.add_asset::<skinning::SkinnedMeshInverseBindposes>()
|
||||
.register_type::<skinning::SkinnedMesh>()
|
||||
.register_type::<Vec<Entity>>()
|
||||
.add_plugins(RenderAssetPlugin::<Mesh>::default());
|
||||
// 'Mesh' must be prepared after 'Image' as meshes rely on the morph target image being ready
|
||||
.add_plugins(RenderAssetPlugin::<Mesh, Image>::default());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use bevy_asset::{Asset, AssetEvent, Assets, Handle};
|
|||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
prelude::*,
|
||||
schedule::SystemConfigs,
|
||||
system::{StaticSystemParam, SystemParam, SystemParamItem},
|
||||
};
|
||||
use bevy_utils::{HashMap, HashSet};
|
||||
|
@ -20,7 +21,7 @@ pub enum PrepareAssetError<E: Send + Sync + 'static> {
|
|||
/// Therefore it is converted into a [`RenderAsset::ExtractedAsset`], which may be the same type
|
||||
/// as the render asset itself.
|
||||
///
|
||||
/// After that in the [`RenderSet::Prepare`](crate::RenderSet::Prepare) step the extracted asset
|
||||
/// After that in the [`RenderSet::PrepareAssets`](crate::RenderSet::PrepareAssets) step the extracted asset
|
||||
/// is transformed into its GPU-representation of type [`RenderAsset::PreparedAsset`].
|
||||
pub trait RenderAsset: Asset {
|
||||
/// The representation of the asset in the "render world".
|
||||
|
@ -40,68 +41,65 @@ pub trait RenderAsset: Asset {
|
|||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, Debug, Default, PartialEq, Eq, SystemSet)]
|
||||
pub enum PrepareAssetSet {
|
||||
PreAssetPrepare,
|
||||
#[default]
|
||||
AssetPrepare,
|
||||
PostAssetPrepare,
|
||||
}
|
||||
|
||||
/// This plugin extracts the changed assets from the "app world" into the "render world"
|
||||
/// and prepares them for the GPU. They can then be accessed from the [`RenderAssets`] resource.
|
||||
///
|
||||
/// Therefore it sets up the [`ExtractSchedule`](crate::ExtractSchedule) and
|
||||
/// [`RenderSet::Prepare`](crate::RenderSet::Prepare) steps for the specified [`RenderAsset`].
|
||||
pub struct RenderAssetPlugin<A: RenderAsset> {
|
||||
prepare_asset_set: PrepareAssetSet,
|
||||
phantom: PhantomData<fn() -> A>,
|
||||
/// [`RenderSet::PrepareAssets`](crate::RenderSet::PrepareAssets) steps for the specified [`RenderAsset`].
|
||||
///
|
||||
/// The `AFTER` generic parameter can be used to specify that `A::prepare_asset` should not be run until
|
||||
/// `prepare_assets::<AFTER>` has completed. This allows the `prepare_asset` function to depend on another
|
||||
/// prepared [`RenderAsset`], for example `Mesh::prepare_asset` relies on `RenderAssets::<Image>` for morph
|
||||
/// targets, so the plugin is created as `RenderAssetPlugin::<Mesh, Image>::default()`.
|
||||
pub struct RenderAssetPlugin<A: RenderAsset, AFTER: RenderAssetDependency + 'static = ()> {
|
||||
phantom: PhantomData<fn() -> (A, AFTER)>,
|
||||
}
|
||||
|
||||
impl<A: RenderAsset> RenderAssetPlugin<A> {
|
||||
pub fn with_prepare_asset_set(prepare_asset_set: PrepareAssetSet) -> Self {
|
||||
Self {
|
||||
prepare_asset_set,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: RenderAsset> Default for RenderAssetPlugin<A> {
|
||||
impl<A: RenderAsset, AFTER: RenderAssetDependency + 'static> Default
|
||||
for RenderAssetPlugin<A, AFTER>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
prepare_asset_set: Default::default(),
|
||||
phantom: PhantomData,
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
|
||||
impl<A: RenderAsset, AFTER: RenderAssetDependency + 'static> Plugin
|
||||
for RenderAssetPlugin<A, AFTER>
|
||||
{
|
||||
fn build(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.init_resource::<ExtractedAssets<A>>()
|
||||
.init_resource::<RenderAssets<A>>()
|
||||
.init_resource::<PrepareNextFrameAssets<A>>()
|
||||
.add_systems(ExtractSchedule, extract_render_asset::<A>)
|
||||
.configure_sets(
|
||||
Render,
|
||||
(
|
||||
PrepareAssetSet::PreAssetPrepare,
|
||||
PrepareAssetSet::AssetPrepare,
|
||||
PrepareAssetSet::PostAssetPrepare,
|
||||
)
|
||||
.chain()
|
||||
.in_set(RenderSet::Prepare),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_assets::<A>.in_set(self.prepare_asset_set.clone()),
|
||||
);
|
||||
.add_systems(ExtractSchedule, extract_render_asset::<A>);
|
||||
AFTER::register_system(
|
||||
render_app,
|
||||
prepare_assets::<A>.in_set(RenderSet::PrepareAssets),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper to allow specifying dependencies between render assets
|
||||
pub trait RenderAssetDependency {
|
||||
fn register_system(render_app: &mut App, system: SystemConfigs);
|
||||
}
|
||||
|
||||
impl RenderAssetDependency for () {
|
||||
fn register_system(render_app: &mut App, system: SystemConfigs) {
|
||||
render_app.add_systems(Render, system);
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: RenderAsset> RenderAssetDependency for A {
|
||||
fn register_system(render_app: &mut App, system: SystemConfigs) {
|
||||
render_app.add_systems(Render, system.after(prepare_assets::<A>));
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporarily stores the extracted and removed assets of the current frame.
|
||||
#[derive(Resource)]
|
||||
pub struct ExtractedAssets<A: RenderAsset> {
|
||||
|
|
|
@ -73,6 +73,12 @@ impl<I: PhaseItem> RenderPhase<I> {
|
|||
I::sort(&mut self.items);
|
||||
}
|
||||
|
||||
/// An [`Iterator`] through the associated [`Entity`] for each [`PhaseItem`] in order.
|
||||
#[inline]
|
||||
pub fn iter_entities(&'_ self) -> impl Iterator<Item = Entity> + '_ {
|
||||
self.items.iter().map(|item| item.entity())
|
||||
}
|
||||
|
||||
/// Renders all of its [`PhaseItem`]s using their corresponding draw functions.
|
||||
pub fn render<'w>(
|
||||
&self,
|
||||
|
@ -84,9 +90,17 @@ impl<I: PhaseItem> RenderPhase<I> {
|
|||
let mut draw_functions = draw_functions.write();
|
||||
draw_functions.prepare(world);
|
||||
|
||||
for item in &self.items {
|
||||
let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();
|
||||
draw_function.draw(world, render_pass, view, item);
|
||||
let mut index = 0;
|
||||
while index < self.items.len() {
|
||||
let item = &self.items[index];
|
||||
let batch_size = item.batch_size();
|
||||
if batch_size > 0 {
|
||||
let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();
|
||||
draw_function.draw(world, render_pass, view, item);
|
||||
index += batch_size;
|
||||
} else {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,41 +116,22 @@ impl<I: PhaseItem> RenderPhase<I> {
|
|||
let mut draw_functions = draw_functions.write();
|
||||
draw_functions.prepare(world);
|
||||
|
||||
for item in self
|
||||
let items = self
|
||||
.items
|
||||
.get(range)
|
||||
.expect("`Range` provided to `render_range()` is out of bounds")
|
||||
.iter()
|
||||
{
|
||||
let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();
|
||||
draw_function.draw(world, render_pass, view, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
.expect("`Range` provided to `render_range()` is out of bounds");
|
||||
|
||||
impl<I: BatchedPhaseItem> RenderPhase<I> {
|
||||
/// Batches the compatible [`BatchedPhaseItem`]s of this render phase
|
||||
pub fn batch(&mut self) {
|
||||
// TODO: this could be done in-place
|
||||
let mut items = std::mem::take(&mut self.items).into_iter();
|
||||
|
||||
self.items.reserve(items.len());
|
||||
|
||||
// Start the first batch from the first item
|
||||
if let Some(mut current_batch) = items.next() {
|
||||
// Batch following items until we find an incompatible item
|
||||
for next_item in items {
|
||||
if matches!(
|
||||
current_batch.add_to_batch(&next_item),
|
||||
BatchResult::IncompatibleItems
|
||||
) {
|
||||
// Store the completed batch, and start a new one from the incompatible item
|
||||
self.items.push(current_batch);
|
||||
current_batch = next_item;
|
||||
}
|
||||
let mut index = 0;
|
||||
while index < items.len() {
|
||||
let item = &items[index];
|
||||
let batch_size = item.batch_size();
|
||||
if batch_size > 0 {
|
||||
let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();
|
||||
draw_function.draw(world, render_pass, view, item);
|
||||
index += batch_size;
|
||||
} else {
|
||||
index += 1;
|
||||
}
|
||||
// Store the last batch
|
||||
self.items.push(current_batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +166,7 @@ pub trait PhaseItem: Sized + Send + Sync + 'static {
|
|||
fn draw_function(&self) -> DrawFunctionId;
|
||||
|
||||
/// Sorts a slice of phase items into render order. Generally if the same type
|
||||
/// implements [`BatchedPhaseItem`], this should use a stable sort like [`slice::sort_by_key`].
|
||||
/// is batched this should use a stable sort like [`slice::sort_by_key`].
|
||||
/// In almost all other cases, this should not be altered from the default,
|
||||
/// which uses a unstable sort, as this provides the best balance of CPU and GPU
|
||||
/// performance.
|
||||
|
@ -186,6 +181,13 @@ pub trait PhaseItem: Sized + Send + Sync + 'static {
|
|||
fn sort(items: &mut [Self]) {
|
||||
items.sort_unstable_by_key(|item| item.sort_key());
|
||||
}
|
||||
|
||||
/// The number of items to skip after rendering this [`PhaseItem`].
|
||||
///
|
||||
/// Items with a `batch_size` of 0 will not be rendered.
|
||||
fn batch_size(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline,
|
||||
|
@ -225,182 +227,9 @@ impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline {
|
|||
}
|
||||
}
|
||||
|
||||
/// A [`PhaseItem`] that can be batched dynamically.
|
||||
///
|
||||
/// Batching is an optimization that regroups multiple items in the same vertex buffer
|
||||
/// to render them in a single draw call.
|
||||
///
|
||||
/// If this is implemented on a type, the implementation of [`PhaseItem::sort`] should
|
||||
/// be changed to implement a stable sort, or incorrect/suboptimal batching may result.
|
||||
pub trait BatchedPhaseItem: PhaseItem {
|
||||
/// Range in the vertex buffer of this item.
|
||||
fn batch_range(&self) -> &Option<Range<u32>>;
|
||||
|
||||
/// Range in the vertex buffer of this item.
|
||||
fn batch_range_mut(&mut self) -> &mut Option<Range<u32>>;
|
||||
|
||||
/// Batches another item within this item if they are compatible.
|
||||
/// Items can be batched together if they have the same entity, and consecutive ranges.
|
||||
/// If batching is successful, the `other` item should be discarded from the render pass.
|
||||
#[inline]
|
||||
fn add_to_batch(&mut self, other: &Self) -> BatchResult {
|
||||
let self_entity = self.entity();
|
||||
if let (Some(self_batch_range), Some(other_batch_range)) = (
|
||||
self.batch_range_mut().as_mut(),
|
||||
other.batch_range().as_ref(),
|
||||
) {
|
||||
// If the items are compatible, join their range into `self`
|
||||
if self_entity == other.entity() {
|
||||
if self_batch_range.end == other_batch_range.start {
|
||||
self_batch_range.end = other_batch_range.end;
|
||||
return BatchResult::Success;
|
||||
} else if self_batch_range.start == other_batch_range.end {
|
||||
self_batch_range.start = other_batch_range.start;
|
||||
return BatchResult::Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
BatchResult::IncompatibleItems
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a batching operation.
|
||||
pub enum BatchResult {
|
||||
/// The `other` item was batched into `self`
|
||||
Success,
|
||||
/// `self` and `other` cannot be batched together
|
||||
IncompatibleItems,
|
||||
}
|
||||
|
||||
/// This system sorts the [`PhaseItem`]s of all [`RenderPhase`]s of this type.
|
||||
pub fn sort_phase_system<I: PhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
|
||||
for mut phase in &mut render_phases {
|
||||
phase.sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// This system batches the [`PhaseItem`]s of all [`RenderPhase`]s of this type.
|
||||
pub fn batch_phase_system<I: BatchedPhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
|
||||
for mut phase in &mut render_phases {
|
||||
phase.batch();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bevy_ecs::entity::Entity;
|
||||
use std::ops::Range;
|
||||
|
||||
#[test]
|
||||
fn batching() {
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct TestPhaseItem {
|
||||
entity: Entity,
|
||||
batch_range: Option<Range<u32>>,
|
||||
}
|
||||
impl PhaseItem for TestPhaseItem {
|
||||
type SortKey = ();
|
||||
|
||||
fn entity(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
|
||||
fn sort_key(&self) -> Self::SortKey {}
|
||||
|
||||
fn draw_function(&self) -> DrawFunctionId {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
impl BatchedPhaseItem for TestPhaseItem {
|
||||
fn batch_range(&self) -> &Option<Range<u32>> {
|
||||
&self.batch_range
|
||||
}
|
||||
|
||||
fn batch_range_mut(&mut self) -> &mut Option<Range<u32>> {
|
||||
&mut self.batch_range
|
||||
}
|
||||
}
|
||||
let mut render_phase = RenderPhase::<TestPhaseItem>::default();
|
||||
let items = [
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(0),
|
||||
batch_range: Some(0..5),
|
||||
},
|
||||
// This item should be batched
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(0),
|
||||
batch_range: Some(5..10),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(0..5),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(0),
|
||||
batch_range: Some(10..15),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(5..10),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: None,
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(10..15),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(20..25),
|
||||
},
|
||||
// This item should be batched
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(25..30),
|
||||
},
|
||||
// This item should be batched
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(30..35),
|
||||
},
|
||||
];
|
||||
for item in items {
|
||||
render_phase.add(item);
|
||||
}
|
||||
render_phase.batch();
|
||||
let items_batched = [
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(0),
|
||||
batch_range: Some(0..10),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(0..5),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(0),
|
||||
batch_range: Some(10..15),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(5..10),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: None,
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(10..15),
|
||||
},
|
||||
TestPhaseItem {
|
||||
entity: Entity::from_raw(1),
|
||||
batch_range: Some(20..35),
|
||||
},
|
||||
];
|
||||
assert_eq!(&*render_phase.items, items_batched);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,7 @@ pub use image_texture_loader::*;
|
|||
pub use texture_cache::*;
|
||||
|
||||
use crate::{
|
||||
render_asset::{PrepareAssetSet, RenderAssetPlugin},
|
||||
renderer::RenderDevice,
|
||||
Render, RenderApp, RenderSet,
|
||||
render_asset::RenderAssetPlugin, renderer::RenderDevice, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{AddAsset, Assets};
|
||||
|
@ -80,12 +78,10 @@ impl Plugin for ImagePlugin {
|
|||
app.init_asset_loader::<HdrTextureLoader>();
|
||||
}
|
||||
|
||||
app.add_plugins(RenderAssetPlugin::<Image>::with_prepare_asset_set(
|
||||
PrepareAssetSet::PreAssetPrepare,
|
||||
))
|
||||
.register_type::<Image>()
|
||||
.add_asset::<Image>()
|
||||
.register_asset_reflect::<Image>();
|
||||
app.add_plugins(RenderAssetPlugin::<Image>::default())
|
||||
.register_type::<Image>()
|
||||
.add_asset::<Image>()
|
||||
.register_asset_reflect::<Image>();
|
||||
app.world
|
||||
.resource_mut::<Assets<Image>>()
|
||||
.set_untracked(DEFAULT_IMAGE_HANDLE, Image::default());
|
||||
|
|
|
@ -53,19 +53,16 @@ impl Plugin for ViewPlugin {
|
|||
.add_plugins((ExtractResourcePlugin::<Msaa>::default(), VisibilityPlugin));
|
||||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.init_resource::<ViewUniforms>()
|
||||
.configure_set(Render, ViewSet::PrepareUniforms.in_set(RenderSet::Prepare))
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_view_uniforms.in_set(ViewSet::PrepareUniforms),
|
||||
prepare_view_targets
|
||||
.after(WindowSystem::Prepare)
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(crate::render_asset::prepare_assets::<Image>),
|
||||
),
|
||||
);
|
||||
render_app.init_resource::<ViewUniforms>().add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_view_targets
|
||||
.in_set(RenderSet::ManageViews)
|
||||
.after(prepare_windows)
|
||||
.after(crate::render_asset::prepare_assets::<Image>),
|
||||
prepare_view_uniforms.in_set(RenderSet::PrepareResources),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -517,10 +514,3 @@ fn prepare_view_targets(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// System sets for the [`view`](crate::view) module.
|
||||
#[derive(SystemSet, PartialEq, Eq, Hash, Debug, Clone)]
|
||||
pub enum ViewSet {
|
||||
/// Prepares view uniforms
|
||||
PrepareUniforms,
|
||||
}
|
||||
|
|
|
@ -27,11 +27,6 @@ pub struct NonSendMarker;
|
|||
|
||||
pub struct WindowRenderPlugin;
|
||||
|
||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum WindowSystem {
|
||||
Prepare,
|
||||
}
|
||||
|
||||
impl Plugin for WindowRenderPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(ScreenshotPlugin);
|
||||
|
@ -42,8 +37,7 @@ impl Plugin for WindowRenderPlugin {
|
|||
.init_resource::<WindowSurfaces>()
|
||||
.init_non_send_resource::<NonSendMarker>()
|
||||
.add_systems(ExtractSchedule, extract_windows)
|
||||
.configure_set(Render, WindowSystem::Prepare.in_set(RenderSet::Prepare))
|
||||
.add_systems(Render, prepare_windows.in_set(WindowSystem::Prepare));
|
||||
.add_systems(Render, prepare_windows.in_set(RenderSet::ManageViews));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,9 +91,12 @@ impl Plugin for SpritePlugin {
|
|||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_sprites
|
||||
.in_set(RenderSet::Queue)
|
||||
.ambiguous_with(queue_material2d_meshes::<ColorMaterial>),
|
||||
(
|
||||
queue_sprites
|
||||
.in_set(RenderSet::Queue)
|
||||
.ambiguous_with(queue_material2d_meshes::<ColorMaterial>),
|
||||
prepare_sprites.in_set(RenderSet::PrepareBindGroups),
|
||||
),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use bevy_render::{
|
|||
extract_component::ExtractComponentPlugin,
|
||||
mesh::{Mesh, MeshVertexBufferLayout},
|
||||
prelude::Image,
|
||||
render_asset::{PrepareAssetSet, RenderAssets},
|
||||
render_asset::{prepare_assets, RenderAssets},
|
||||
render_phase::{
|
||||
AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult,
|
||||
RenderPhase, SetItemPipeline, TrackedRenderPass,
|
||||
|
@ -165,9 +165,11 @@ where
|
|||
Render,
|
||||
(
|
||||
prepare_materials_2d::<M>
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(PrepareAssetSet::PreAssetPrepare),
|
||||
queue_material2d_meshes::<M>.in_set(RenderSet::Queue),
|
||||
.in_set(RenderSet::PrepareAssets)
|
||||
.after(prepare_assets::<Image>),
|
||||
queue_material2d_meshes::<M>
|
||||
.in_set(RenderSet::QueueMeshes)
|
||||
.after(prepare_materials_2d::<M>),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -415,7 +417,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||
// camera. As such we can just use mesh_z as the distance
|
||||
sort_key: FloatOrd(mesh_z),
|
||||
// This material is not batched
|
||||
batch_range: None,
|
||||
batch_size: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,8 +106,8 @@ impl Plugin for Mesh2dRenderPlugin {
|
|||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
queue_mesh2d_bind_group.in_set(RenderSet::Queue),
|
||||
queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue),
|
||||
prepare_mesh2d_bind_group.in_set(RenderSet::PrepareBindGroups),
|
||||
prepare_mesh2d_view_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -480,7 +480,7 @@ pub struct Mesh2dBindGroup {
|
|||
pub value: BindGroup,
|
||||
}
|
||||
|
||||
pub fn queue_mesh2d_bind_group(
|
||||
pub fn prepare_mesh2d_bind_group(
|
||||
mut commands: Commands,
|
||||
mesh2d_pipeline: Res<Mesh2dPipeline>,
|
||||
render_device: Res<RenderDevice>,
|
||||
|
@ -505,7 +505,7 @@ pub struct Mesh2dViewBindGroup {
|
|||
pub value: BindGroup,
|
||||
}
|
||||
|
||||
pub fn queue_mesh2d_view_bind_groups(
|
||||
pub fn prepare_mesh2d_view_bind_groups(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
mesh2d_pipeline: Res<Mesh2dPipeline>,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
||||
|
@ -11,16 +11,16 @@ use bevy_core_pipeline::{
|
|||
};
|
||||
use bevy_ecs::{
|
||||
prelude::*,
|
||||
storage::SparseSet,
|
||||
system::{lifetimeless::*, SystemParamItem, SystemState},
|
||||
};
|
||||
use bevy_math::{Rect, Vec2};
|
||||
use bevy_reflect::Uuid;
|
||||
use bevy_render::{
|
||||
color::Color,
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{
|
||||
BatchedPhaseItem, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult,
|
||||
RenderPhase, SetItemPipeline, TrackedRenderPass,
|
||||
DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline,
|
||||
TrackedRenderPass,
|
||||
},
|
||||
render_resource::*,
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
|
@ -34,8 +34,7 @@ use bevy_render::{
|
|||
Extract,
|
||||
};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use bevy_utils::FloatOrd;
|
||||
use bevy_utils::HashMap;
|
||||
use bevy_utils::{FloatOrd, HashMap, Uuid};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use fixedbitset::FixedBitSet;
|
||||
|
||||
|
@ -296,9 +295,7 @@ impl SpecializedRenderPipeline for SpritePipeline {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
pub struct ExtractedSprite {
|
||||
pub entity: Entity,
|
||||
pub transform: GlobalTransform,
|
||||
pub color: Color,
|
||||
/// Select an area of the texture
|
||||
|
@ -315,7 +312,7 @@ pub struct ExtractedSprite {
|
|||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ExtractedSprites {
|
||||
pub sprites: Vec<ExtractedSprite>,
|
||||
pub sprites: SparseSet<Entity, ExtractedSprite>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
|
@ -368,24 +365,25 @@ pub fn extract_sprites(
|
|||
)>,
|
||||
>,
|
||||
) {
|
||||
extracted_sprites.sprites.clear();
|
||||
for (entity, visibility, sprite, transform, handle) in sprite_query.iter() {
|
||||
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 {
|
||||
extracted_sprites.sprites.insert(
|
||||
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(),
|
||||
});
|
||||
ExtractedSprite {
|
||||
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(),
|
||||
},
|
||||
);
|
||||
}
|
||||
for (entity, visibility, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
|
||||
if !visibility.is_visible() {
|
||||
|
@ -404,19 +402,21 @@ pub fn extract_sprites(
|
|||
)
|
||||
}),
|
||||
);
|
||||
extracted_sprites.sprites.push(ExtractedSprite {
|
||||
extracted_sprites.sprites.insert(
|
||||
entity,
|
||||
color: atlas_sprite.color,
|
||||
transform: *transform,
|
||||
// Select the area in the texture atlas
|
||||
rect,
|
||||
// Pass the custom size
|
||||
custom_size: atlas_sprite.custom_size,
|
||||
flip_x: atlas_sprite.flip_x,
|
||||
flip_y: atlas_sprite.flip_y,
|
||||
image_handle_id: texture_atlas.texture.id(),
|
||||
anchor: atlas_sprite.anchor.as_vec(),
|
||||
});
|
||||
ExtractedSprite {
|
||||
color: atlas_sprite.color,
|
||||
transform: *transform,
|
||||
// Select the area in the texture atlas
|
||||
rect,
|
||||
// Pass the custom size
|
||||
custom_size: atlas_sprite.custom_size,
|
||||
flip_x: atlas_sprite.flip_x,
|
||||
flip_y: atlas_sprite.flip_y,
|
||||
image_handle_id: texture_atlas.texture.id(),
|
||||
anchor: atlas_sprite.anchor.as_vec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -469,8 +469,9 @@ const QUAD_UVS: [Vec2; 4] = [
|
|||
Vec2::new(0., 0.),
|
||||
];
|
||||
|
||||
#[derive(Component, Eq, PartialEq, Copy, Clone)]
|
||||
#[derive(Component)]
|
||||
pub struct SpriteBatch {
|
||||
range: Range<u32>,
|
||||
image_handle_id: HandleId,
|
||||
colored: bool,
|
||||
}
|
||||
|
@ -482,20 +483,13 @@ pub struct ImageBindGroups {
|
|||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn queue_sprites(
|
||||
mut commands: Commands,
|
||||
mut view_entities: Local<FixedBitSet>,
|
||||
draw_functions: Res<DrawFunctions<Transparent2d>>,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut sprite_meta: ResMut<SpriteMeta>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
sprite_pipeline: Res<SpritePipeline>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<SpritePipeline>>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut image_bind_groups: ResMut<ImageBindGroups>,
|
||||
gpu_images: Res<RenderAssets<Image>>,
|
||||
msaa: Res<Msaa>,
|
||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||
extracted_sprites: Res<ExtractedSprites>,
|
||||
mut views: Query<(
|
||||
&mut RenderPhase<Transparent2d>,
|
||||
&VisibleEntities,
|
||||
|
@ -503,6 +497,100 @@ pub fn queue_sprites(
|
|||
Option<&Tonemapping>,
|
||||
Option<&DebandDither>,
|
||||
)>,
|
||||
) {
|
||||
let msaa_key = SpritePipelineKey::from_msaa_samples(msaa.samples());
|
||||
|
||||
let draw_sprite_function = draw_functions.read().id::<DrawSprite>();
|
||||
|
||||
for (mut transparent_phase, visible_entities, view, tonemapping, dither) in &mut views {
|
||||
let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key;
|
||||
|
||||
if !view.hdr {
|
||||
if let Some(tonemapping) = tonemapping {
|
||||
view_key |= SpritePipelineKey::TONEMAP_IN_SHADER;
|
||||
view_key |= match tonemapping {
|
||||
Tonemapping::None => SpritePipelineKey::TONEMAP_METHOD_NONE,
|
||||
Tonemapping::Reinhard => SpritePipelineKey::TONEMAP_METHOD_REINHARD,
|
||||
Tonemapping::ReinhardLuminance => {
|
||||
SpritePipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE
|
||||
}
|
||||
Tonemapping::AcesFitted => SpritePipelineKey::TONEMAP_METHOD_ACES_FITTED,
|
||||
Tonemapping::AgX => SpritePipelineKey::TONEMAP_METHOD_AGX,
|
||||
Tonemapping::SomewhatBoringDisplayTransform => {
|
||||
SpritePipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
|
||||
}
|
||||
Tonemapping::TonyMcMapface => SpritePipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
|
||||
Tonemapping::BlenderFilmic => SpritePipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
|
||||
};
|
||||
}
|
||||
if let Some(DebandDither::Enabled) = dither {
|
||||
view_key |= SpritePipelineKey::DEBAND_DITHER;
|
||||
}
|
||||
}
|
||||
|
||||
let pipeline = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&sprite_pipeline,
|
||||
view_key | SpritePipelineKey::from_colored(false),
|
||||
);
|
||||
let colored_pipeline = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&sprite_pipeline,
|
||||
view_key | SpritePipelineKey::from_colored(true),
|
||||
);
|
||||
|
||||
view_entities.clear();
|
||||
view_entities.extend(visible_entities.entities.iter().map(|e| e.index() as usize));
|
||||
|
||||
transparent_phase
|
||||
.items
|
||||
.reserve(extracted_sprites.sprites.len());
|
||||
|
||||
for (entity, extracted_sprite) in extracted_sprites.sprites.iter() {
|
||||
if !view_entities.contains(entity.index() as usize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// These items will be sorted by depth with other phase items
|
||||
let sort_key = FloatOrd(extracted_sprite.transform.translation().z);
|
||||
|
||||
// Add the item to the render phase
|
||||
if extracted_sprite.color != Color::WHITE {
|
||||
transparent_phase.add(Transparent2d {
|
||||
draw_function: draw_sprite_function,
|
||||
pipeline: colored_pipeline,
|
||||
entity: *entity,
|
||||
sort_key,
|
||||
// batch_size will be calculated in prepare_sprites
|
||||
batch_size: 0,
|
||||
});
|
||||
} else {
|
||||
transparent_phase.add(Transparent2d {
|
||||
draw_function: draw_sprite_function,
|
||||
pipeline,
|
||||
entity: *entity,
|
||||
sort_key,
|
||||
// batch_size will be calculated in prepare_sprites
|
||||
batch_size: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn prepare_sprites(
|
||||
mut commands: Commands,
|
||||
mut previous_len: Local<usize>,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut sprite_meta: ResMut<SpriteMeta>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
sprite_pipeline: Res<SpritePipeline>,
|
||||
mut image_bind_groups: ResMut<ImageBindGroups>,
|
||||
gpu_images: Res<RenderAssets<Image>>,
|
||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||
mut phases: Query<&mut RenderPhase<Transparent2d>>,
|
||||
events: Res<SpriteAssetEvents>,
|
||||
) {
|
||||
// If an image has changed, the GpuImage has (probably) changed
|
||||
|
@ -515,9 +603,8 @@ pub fn queue_sprites(
|
|||
};
|
||||
}
|
||||
|
||||
let msaa_key = SpritePipelineKey::from_msaa_samples(msaa.samples());
|
||||
|
||||
if let Some(view_binding) = view_uniforms.uniforms.binding() {
|
||||
let mut batches: Vec<(Entity, SpriteBatch)> = Vec::with_capacity(*previous_len);
|
||||
let sprite_meta = &mut sprite_meta;
|
||||
|
||||
// Clear the vertex buffers
|
||||
|
@ -533,210 +620,141 @@ pub fn queue_sprites(
|
|||
layout: &sprite_pipeline.view_layout,
|
||||
}));
|
||||
|
||||
let draw_sprite_function = draw_functions.read().id::<DrawSprite>();
|
||||
|
||||
// Vertex buffer indices
|
||||
let mut index = 0;
|
||||
let mut colored_index = 0;
|
||||
|
||||
// FIXME: VisibleEntities is ignored
|
||||
|
||||
let extracted_sprites = &mut extracted_sprites.sprites;
|
||||
// Sort sprites by z for correct transparency and then by handle to improve batching
|
||||
// NOTE: This can be done independent of views by reasonably assuming that all 2D views look along the negative-z axis in world space
|
||||
extracted_sprites.sort_unstable_by(|a, b| {
|
||||
match a
|
||||
.transform
|
||||
.translation()
|
||||
.z
|
||||
.partial_cmp(&b.transform.translation().z)
|
||||
{
|
||||
Some(Ordering::Equal) | None => a.image_handle_id.cmp(&b.image_handle_id),
|
||||
Some(other) => other,
|
||||
}
|
||||
});
|
||||
let image_bind_groups = &mut *image_bind_groups;
|
||||
|
||||
for (mut transparent_phase, visible_entities, view, tonemapping, dither) in &mut views {
|
||||
let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key;
|
||||
for mut transparent_phase in &mut phases {
|
||||
let mut batch_item_index = 0;
|
||||
let mut batch_image_size = Vec2::ZERO;
|
||||
let mut batch_image_handle = HandleId::Id(Uuid::nil(), u64::MAX);
|
||||
let mut batch_colored = false;
|
||||
|
||||
if !view.hdr {
|
||||
if let Some(tonemapping) = tonemapping {
|
||||
view_key |= SpritePipelineKey::TONEMAP_IN_SHADER;
|
||||
view_key |= match tonemapping {
|
||||
Tonemapping::None => SpritePipelineKey::TONEMAP_METHOD_NONE,
|
||||
Tonemapping::Reinhard => SpritePipelineKey::TONEMAP_METHOD_REINHARD,
|
||||
Tonemapping::ReinhardLuminance => {
|
||||
SpritePipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE
|
||||
}
|
||||
Tonemapping::AcesFitted => SpritePipelineKey::TONEMAP_METHOD_ACES_FITTED,
|
||||
Tonemapping::AgX => SpritePipelineKey::TONEMAP_METHOD_AGX,
|
||||
Tonemapping::SomewhatBoringDisplayTransform => {
|
||||
SpritePipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
|
||||
}
|
||||
Tonemapping::TonyMcMapface => {
|
||||
SpritePipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE
|
||||
}
|
||||
Tonemapping::BlenderFilmic => {
|
||||
SpritePipelineKey::TONEMAP_METHOD_BLENDER_FILMIC
|
||||
}
|
||||
};
|
||||
}
|
||||
if let Some(DebandDither::Enabled) = dither {
|
||||
view_key |= SpritePipelineKey::DEBAND_DITHER;
|
||||
}
|
||||
}
|
||||
|
||||
let pipeline = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&sprite_pipeline,
|
||||
view_key | SpritePipelineKey::from_colored(false),
|
||||
);
|
||||
let colored_pipeline = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&sprite_pipeline,
|
||||
view_key | SpritePipelineKey::from_colored(true),
|
||||
);
|
||||
|
||||
view_entities.clear();
|
||||
view_entities.extend(visible_entities.entities.iter().map(|e| e.index() as usize));
|
||||
transparent_phase.items.reserve(extracted_sprites.len());
|
||||
|
||||
// Impossible starting values that will be replaced on the first iteration
|
||||
let mut current_batch = SpriteBatch {
|
||||
image_handle_id: HandleId::Id(Uuid::nil(), u64::MAX),
|
||||
colored: false,
|
||||
};
|
||||
let mut current_batch_entity = Entity::PLACEHOLDER;
|
||||
let mut current_image_size = Vec2::ZERO;
|
||||
// Add a phase item for each sprite, and detect when successive items can be batched.
|
||||
// Iterate through the phase items and detect when successive sprites that 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
|
||||
// by any other phase item (and they can interrupt other items from batching).
|
||||
for extracted_sprite in extracted_sprites.iter() {
|
||||
if !view_entities.contains(extracted_sprite.entity.index() as usize) {
|
||||
continue;
|
||||
}
|
||||
let new_batch = SpriteBatch {
|
||||
image_handle_id: extracted_sprite.image_handle_id,
|
||||
colored: extracted_sprite.color != Color::WHITE,
|
||||
};
|
||||
if new_batch != current_batch {
|
||||
// Set-up a new possible batch
|
||||
if let Some(gpu_image) =
|
||||
gpu_images.get(&Handle::weak(new_batch.image_handle_id))
|
||||
{
|
||||
current_batch = new_batch;
|
||||
current_image_size = Vec2::new(gpu_image.size.x, gpu_image.size.y);
|
||||
current_batch_entity = commands.spawn(current_batch).id();
|
||||
for item_index in 0..transparent_phase.items.len() {
|
||||
let item = &mut transparent_phase.items[item_index];
|
||||
if let Some(extracted_sprite) = extracted_sprites.sprites.get(item.entity) {
|
||||
// Take a reference to an existing compatible batch if one exists
|
||||
let mut existing_batch = batches.last_mut().filter(|_| {
|
||||
batch_image_handle == extracted_sprite.image_handle_id
|
||||
&& batch_colored == (extracted_sprite.color != Color::WHITE)
|
||||
});
|
||||
|
||||
image_bind_groups
|
||||
.values
|
||||
.entry(Handle::weak(current_batch.image_handle_id))
|
||||
.or_insert_with(|| {
|
||||
render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(
|
||||
&gpu_image.texture_view,
|
||||
),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::Sampler(&gpu_image.sampler),
|
||||
},
|
||||
],
|
||||
label: Some("sprite_material_bind_group"),
|
||||
layout: &sprite_pipeline.material_layout,
|
||||
})
|
||||
if existing_batch.is_none() {
|
||||
if let Some(gpu_image) =
|
||||
gpu_images.get(&Handle::weak(extracted_sprite.image_handle_id))
|
||||
{
|
||||
batch_item_index = item_index;
|
||||
batch_image_size = Vec2::new(gpu_image.size.x, gpu_image.size.y);
|
||||
batch_image_handle = extracted_sprite.image_handle_id;
|
||||
batch_colored = extracted_sprite.color != Color::WHITE;
|
||||
|
||||
let new_batch = SpriteBatch {
|
||||
range: if batch_colored {
|
||||
colored_index..colored_index
|
||||
} else {
|
||||
index..index
|
||||
},
|
||||
colored: batch_colored,
|
||||
image_handle_id: batch_image_handle,
|
||||
};
|
||||
|
||||
batches.push((item.entity, new_batch));
|
||||
|
||||
image_bind_groups
|
||||
.values
|
||||
.entry(Handle::weak(batch_image_handle))
|
||||
.or_insert_with(|| {
|
||||
render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(
|
||||
&gpu_image.texture_view,
|
||||
),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::Sampler(
|
||||
&gpu_image.sampler,
|
||||
),
|
||||
},
|
||||
],
|
||||
label: Some("sprite_material_bind_group"),
|
||||
layout: &sprite_pipeline.material_layout,
|
||||
})
|
||||
});
|
||||
existing_batch = batches.last_mut();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate vertex data for this item
|
||||
let mut uvs = QUAD_UVS;
|
||||
if extracted_sprite.flip_x {
|
||||
uvs = [uvs[1], uvs[0], uvs[3], uvs[2]];
|
||||
}
|
||||
if extracted_sprite.flip_y {
|
||||
uvs = [uvs[3], uvs[2], uvs[1], uvs[0]];
|
||||
}
|
||||
|
||||
// By default, the size of the quad is the size of the texture
|
||||
let mut quad_size = batch_image_size;
|
||||
|
||||
// If a rect is specified, adjust UVs and the size of the quad
|
||||
if let Some(rect) = extracted_sprite.rect {
|
||||
let rect_size = rect.size();
|
||||
for uv in &mut uvs {
|
||||
*uv = (rect.min + *uv * rect_size) / batch_image_size;
|
||||
}
|
||||
quad_size = rect_size;
|
||||
}
|
||||
|
||||
// Override the size if a custom one is specified
|
||||
if let Some(custom_size) = extracted_sprite.custom_size {
|
||||
quad_size = custom_size;
|
||||
}
|
||||
|
||||
// Apply size and global transform
|
||||
let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| {
|
||||
extracted_sprite
|
||||
.transform
|
||||
.transform_point(
|
||||
((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.),
|
||||
)
|
||||
.into()
|
||||
});
|
||||
|
||||
// Store the vertex data and add the item to the render phase
|
||||
if batch_colored {
|
||||
let vertex_color = extracted_sprite.color.as_linear_rgba_f32();
|
||||
for i in QUAD_INDICES {
|
||||
sprite_meta.colored_vertices.push(ColoredSpriteVertex {
|
||||
position: positions[i],
|
||||
uv: uvs[i].into(),
|
||||
color: vertex_color,
|
||||
});
|
||||
}
|
||||
colored_index += QUAD_INDICES.len() as u32;
|
||||
existing_batch.unwrap().1.range.end = colored_index;
|
||||
} else {
|
||||
// Skip this item if the texture is not ready
|
||||
continue;
|
||||
for i in QUAD_INDICES {
|
||||
sprite_meta.vertices.push(SpriteVertex {
|
||||
position: positions[i],
|
||||
uv: uvs[i].into(),
|
||||
});
|
||||
}
|
||||
index += QUAD_INDICES.len() as u32;
|
||||
existing_batch.unwrap().1.range.end = index;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate vertex data for this item
|
||||
|
||||
let mut uvs = QUAD_UVS;
|
||||
if extracted_sprite.flip_x {
|
||||
uvs = [uvs[1], uvs[0], uvs[3], uvs[2]];
|
||||
}
|
||||
if extracted_sprite.flip_y {
|
||||
uvs = [uvs[3], uvs[2], uvs[1], uvs[0]];
|
||||
}
|
||||
|
||||
// By default, the size of the quad is the size of the texture
|
||||
let mut quad_size = current_image_size;
|
||||
|
||||
// If a rect is specified, adjust UVs and the size of the quad
|
||||
if let Some(rect) = extracted_sprite.rect {
|
||||
let rect_size = rect.size();
|
||||
for uv in &mut uvs {
|
||||
*uv = (rect.min + *uv * rect_size) / current_image_size;
|
||||
}
|
||||
quad_size = rect_size;
|
||||
}
|
||||
|
||||
// Override the size if a custom one is specified
|
||||
if let Some(custom_size) = extracted_sprite.custom_size {
|
||||
quad_size = custom_size;
|
||||
}
|
||||
|
||||
// Apply size and global transform
|
||||
let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| {
|
||||
extracted_sprite
|
||||
.transform
|
||||
.transform_point(
|
||||
((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.),
|
||||
)
|
||||
.into()
|
||||
});
|
||||
|
||||
// These items will be sorted by depth with other phase items
|
||||
let sort_key = FloatOrd(extracted_sprite.transform.translation().z);
|
||||
|
||||
// Store the vertex data and add the item to the render phase
|
||||
if current_batch.colored {
|
||||
let vertex_color = extracted_sprite.color.as_linear_rgba_f32();
|
||||
for i in QUAD_INDICES {
|
||||
sprite_meta.colored_vertices.push(ColoredSpriteVertex {
|
||||
position: positions[i],
|
||||
uv: uvs[i].into(),
|
||||
color: vertex_color,
|
||||
});
|
||||
}
|
||||
let item_start = colored_index;
|
||||
colored_index += QUAD_INDICES.len() as u32;
|
||||
let item_end = colored_index;
|
||||
|
||||
transparent_phase.add(Transparent2d {
|
||||
draw_function: draw_sprite_function,
|
||||
pipeline: colored_pipeline,
|
||||
entity: current_batch_entity,
|
||||
sort_key,
|
||||
batch_range: Some(item_start..item_end),
|
||||
});
|
||||
transparent_phase.items[batch_item_index].batch_size += 1;
|
||||
} else {
|
||||
for i in QUAD_INDICES {
|
||||
sprite_meta.vertices.push(SpriteVertex {
|
||||
position: positions[i],
|
||||
uv: uvs[i].into(),
|
||||
});
|
||||
}
|
||||
let item_start = index;
|
||||
index += QUAD_INDICES.len() as u32;
|
||||
let item_end = index;
|
||||
|
||||
transparent_phase.add(Transparent2d {
|
||||
draw_function: draw_sprite_function,
|
||||
pipeline,
|
||||
entity: current_batch_entity,
|
||||
sort_key,
|
||||
batch_range: Some(item_start..item_end),
|
||||
});
|
||||
batch_image_handle = HandleId::Id(Uuid::nil(), u64::MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -746,7 +764,10 @@ pub fn queue_sprites(
|
|||
sprite_meta
|
||||
.colored_vertices
|
||||
.write_buffer(&render_device, &render_queue);
|
||||
*previous_len = batches.len();
|
||||
commands.insert_or_spawn_batch(batches);
|
||||
}
|
||||
extracted_sprites.sprites.clear();
|
||||
}
|
||||
|
||||
pub type DrawSprite = (
|
||||
|
@ -786,7 +807,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteTextureBindGrou
|
|||
fn render<'w>(
|
||||
_item: &P,
|
||||
_view: (),
|
||||
sprite_batch: &'_ SpriteBatch,
|
||||
batch: &'_ SpriteBatch,
|
||||
image_bind_groups: SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
|
@ -796,7 +817,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteTextureBindGrou
|
|||
I,
|
||||
image_bind_groups
|
||||
.values
|
||||
.get(&Handle::weak(sprite_batch.image_handle_id))
|
||||
.get(&Handle::weak(batch.image_handle_id))
|
||||
.unwrap(),
|
||||
&[],
|
||||
);
|
||||
|
@ -805,25 +826,25 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteTextureBindGrou
|
|||
}
|
||||
|
||||
pub struct DrawSpriteBatch;
|
||||
impl<P: BatchedPhaseItem> RenderCommand<P> for DrawSpriteBatch {
|
||||
impl<P: PhaseItem> RenderCommand<P> for DrawSpriteBatch {
|
||||
type Param = SRes<SpriteMeta>;
|
||||
type ViewWorldQuery = ();
|
||||
type ItemWorldQuery = Read<SpriteBatch>;
|
||||
|
||||
fn render<'w>(
|
||||
item: &P,
|
||||
_item: &P,
|
||||
_view: (),
|
||||
sprite_batch: &'_ SpriteBatch,
|
||||
batch: &'_ SpriteBatch,
|
||||
sprite_meta: SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let sprite_meta = sprite_meta.into_inner();
|
||||
if sprite_batch.colored {
|
||||
if batch.colored {
|
||||
pass.set_vertex_buffer(0, sprite_meta.colored_vertices.buffer().unwrap().slice(..));
|
||||
} else {
|
||||
pass.set_vertex_buffer(0, sprite_meta.vertices.buffer().unwrap().slice(..));
|
||||
}
|
||||
pass.draw(item.batch_range().as_ref().unwrap().clone(), 0..1);
|
||||
pass.draw(batch.range.clone(), 0..1);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use bevy_ecs::{
|
|||
event::EventReader,
|
||||
prelude::With,
|
||||
reflect::ReflectComponent,
|
||||
system::{Local, Query, Res, ResMut},
|
||||
system::{Commands, Local, Query, Res, ResMut},
|
||||
};
|
||||
use bevy_math::{Vec2, Vec3};
|
||||
use bevy_reflect::Reflect;
|
||||
|
@ -77,12 +77,12 @@ pub struct Text2dBundle {
|
|||
}
|
||||
|
||||
pub fn extract_text2d_sprite(
|
||||
mut commands: Commands,
|
||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||
text2d_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
&ComputedVisibility,
|
||||
&Text,
|
||||
&TextLayoutInfo,
|
||||
|
@ -98,7 +98,7 @@ pub fn extract_text2d_sprite(
|
|||
.unwrap_or(1.0);
|
||||
let scaling = GlobalTransform::from_scale(Vec3::splat(scale_factor.recip()));
|
||||
|
||||
for (entity, computed_visibility, text, text_layout_info, anchor, global_transform) in
|
||||
for (computed_visibility, text, text_layout_info, anchor, global_transform) in
|
||||
text2d_query.iter()
|
||||
{
|
||||
if !computed_visibility.is_visible() {
|
||||
|
@ -125,17 +125,19 @@ pub fn extract_text2d_sprite(
|
|||
}
|
||||
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
|
||||
|
||||
extracted_sprites.sprites.push(ExtractedSprite {
|
||||
entity,
|
||||
transform: transform * GlobalTransform::from_translation(position.extend(0.)),
|
||||
color,
|
||||
rect: Some(atlas.textures[atlas_info.glyph_index]),
|
||||
custom_size: None,
|
||||
image_handle_id: atlas.texture.id(),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
anchor: Anchor::Center.as_vec(),
|
||||
});
|
||||
extracted_sprites.sprites.insert(
|
||||
commands.spawn_empty().id(),
|
||||
ExtractedSprite {
|
||||
transform: transform * GlobalTransform::from_translation(position.extend(0.)),
|
||||
color,
|
||||
rect: Some(atlas.textures[atlas_info.glyph_index]),
|
||||
custom_size: None,
|
||||
image_handle_id: atlas.texture.id(),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
anchor: Anchor::Center.as_vec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ mod pipeline;
|
|||
mod render_pass;
|
||||
|
||||
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
||||
use bevy_ecs::storage::SparseSet;
|
||||
use bevy_hierarchy::Parent;
|
||||
use bevy_render::{ExtractSchedule, Render};
|
||||
use bevy_window::{PrimaryWindow, Window};
|
||||
|
@ -14,7 +15,7 @@ use crate::{
|
|||
};
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped};
|
||||
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleId, HandleUntyped};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4Swizzles};
|
||||
use bevy_reflect::TypeUuid;
|
||||
|
@ -36,8 +37,8 @@ use bevy_sprite::TextureAtlas;
|
|||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use bevy_utils::FloatOrd;
|
||||
use bevy_utils::HashMap;
|
||||
use bevy_utils::{FloatOrd, Uuid};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::ops::Range;
|
||||
|
||||
|
@ -93,9 +94,9 @@ pub fn build_ui_render(app: &mut App) {
|
|||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_uinodes.in_set(RenderSet::Prepare),
|
||||
queue_uinodes.in_set(RenderSet::Queue),
|
||||
sort_phase_system::<TransparentUi>.in_set(RenderSet::PhaseSort),
|
||||
prepare_uinodes.in_set(RenderSet::PrepareBindGroups),
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -166,7 +167,7 @@ pub struct ExtractedUiNode {
|
|||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ExtractedUiNodes {
|
||||
pub uinodes: Vec<ExtractedUiNode>,
|
||||
pub uinodes: SparseSet<Entity, ExtractedUiNode>,
|
||||
}
|
||||
|
||||
pub fn extract_atlas_uinodes(
|
||||
|
@ -177,6 +178,7 @@ pub fn extract_atlas_uinodes(
|
|||
uinode_query: Extract<
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&BackgroundColor,
|
||||
|
@ -190,8 +192,16 @@ pub fn extract_atlas_uinodes(
|
|||
>,
|
||||
) {
|
||||
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
||||
if let Ok((uinode, transform, color, visibility, clip, texture_atlas_handle, atlas_image)) =
|
||||
uinode_query.get(*entity)
|
||||
if let Ok((
|
||||
entity,
|
||||
uinode,
|
||||
transform,
|
||||
color,
|
||||
visibility,
|
||||
clip,
|
||||
texture_atlas_handle,
|
||||
atlas_image,
|
||||
)) = uinode_query.get(*entity)
|
||||
{
|
||||
// Skip invisible and completely transparent nodes
|
||||
if !visibility.is_visible() || color.0.a() == 0.0 {
|
||||
|
@ -230,17 +240,20 @@ pub fn extract_atlas_uinodes(
|
|||
atlas_rect.max *= scale;
|
||||
atlas_size *= scale;
|
||||
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: color.0,
|
||||
rect: atlas_rect,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
image,
|
||||
atlas_size: Some(atlas_size),
|
||||
flip_x: atlas_image.flip_x,
|
||||
flip_y: atlas_image.flip_y,
|
||||
});
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
ExtractedUiNode {
|
||||
stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: color.0,
|
||||
rect: atlas_rect,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
image,
|
||||
atlas_size: Some(atlas_size),
|
||||
flip_x: atlas_image.flip_x,
|
||||
flip_y: atlas_image.flip_y,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -258,6 +271,7 @@ fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2)
|
|||
}
|
||||
|
||||
pub fn extract_uinode_borders(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
|
@ -355,21 +369,24 @@ pub fn extract_uinode_borders(
|
|||
|
||||
for edge in border_rects {
|
||||
if edge.min.x < edge.max.x && edge.min.y < edge.max.y {
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
stack_index,
|
||||
// This translates the uinode's transform to the center of the current border rectangle
|
||||
transform: transform * Mat4::from_translation(edge.center().extend(0.)),
|
||||
color: border_color.0,
|
||||
rect: Rect {
|
||||
max: edge.size(),
|
||||
..Default::default()
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn_empty().id(),
|
||||
ExtractedUiNode {
|
||||
stack_index,
|
||||
// This translates the uinode's transform to the center of the current border rectangle
|
||||
transform: transform * Mat4::from_translation(edge.center().extend(0.)),
|
||||
color: border_color.0,
|
||||
rect: Rect {
|
||||
max: edge.size(),
|
||||
..Default::default()
|
||||
},
|
||||
image: image.clone_weak(),
|
||||
atlas_size: None,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
},
|
||||
image: image.clone_weak(),
|
||||
atlas_size: None,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -383,6 +400,7 @@ pub fn extract_uinodes(
|
|||
uinode_query: Extract<
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&BackgroundColor,
|
||||
|
@ -395,7 +413,7 @@ pub fn extract_uinodes(
|
|||
>,
|
||||
) {
|
||||
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
||||
if let Ok((uinode, transform, color, maybe_image, visibility, clip)) =
|
||||
if let Ok((entity, uinode, transform, color, maybe_image, visibility, clip)) =
|
||||
uinode_query.get(*entity)
|
||||
{
|
||||
// Skip invisible and completely transparent nodes
|
||||
|
@ -413,20 +431,23 @@ pub fn extract_uinodes(
|
|||
(DEFAULT_IMAGE_HANDLE.typed(), false, false)
|
||||
};
|
||||
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: color.0,
|
||||
rect: Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
ExtractedUiNode {
|
||||
stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: color.0,
|
||||
rect: Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
},
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
image,
|
||||
atlas_size: None,
|
||||
flip_x,
|
||||
flip_y,
|
||||
},
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
image,
|
||||
atlas_size: None,
|
||||
flip_x,
|
||||
flip_y,
|
||||
});
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -506,6 +527,7 @@ pub fn extract_default_ui_camera_view<T: Component>(
|
|||
|
||||
#[cfg(feature = "bevy_text")]
|
||||
pub fn extract_text_uinodes(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||
|
@ -560,18 +582,21 @@ pub fn extract_text_uinodes(
|
|||
let mut rect = atlas.textures[atlas_info.glyph_index];
|
||||
rect.min *= inverse_scale_factor;
|
||||
rect.max *= inverse_scale_factor;
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
stack_index,
|
||||
transform: transform
|
||||
* Mat4::from_translation(position.extend(0.) * inverse_scale_factor),
|
||||
color,
|
||||
rect,
|
||||
image: atlas.texture.clone_weak(),
|
||||
atlas_size: Some(atlas.size * inverse_scale_factor),
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
});
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn_empty().id(),
|
||||
ExtractedUiNode {
|
||||
stack_index,
|
||||
transform: transform
|
||||
* Mat4::from_translation(position.extend(0.) * inverse_scale_factor),
|
||||
color,
|
||||
rect,
|
||||
image: atlas.texture.clone_weak(),
|
||||
atlas_size: Some(atlas.size * inverse_scale_factor),
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -613,176 +638,42 @@ const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];
|
|||
#[derive(Component)]
|
||||
pub struct UiBatch {
|
||||
pub range: Range<u32>,
|
||||
pub image: Handle<Image>,
|
||||
pub z: f32,
|
||||
pub image_handle_id: HandleId,
|
||||
}
|
||||
|
||||
const TEXTURED_QUAD: u32 = 0;
|
||||
const UNTEXTURED_QUAD: u32 = 1;
|
||||
|
||||
pub fn prepare_uinodes(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut ui_meta: ResMut<UiMeta>,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn queue_uinodes(
|
||||
extracted_uinodes: Res<ExtractedUiNodes>,
|
||||
ui_pipeline: Res<UiPipeline>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<UiPipeline>>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<TransparentUi>)>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
draw_functions: Res<DrawFunctions<TransparentUi>>,
|
||||
) {
|
||||
ui_meta.vertices.clear();
|
||||
|
||||
// sort by ui stack index, starting from the deepest node
|
||||
extracted_uinodes
|
||||
.uinodes
|
||||
.sort_by_key(|node| node.stack_index);
|
||||
|
||||
let mut start = 0;
|
||||
let mut end = 0;
|
||||
let mut current_batch_image = DEFAULT_IMAGE_HANDLE.typed();
|
||||
let mut last_z = 0.0;
|
||||
|
||||
#[inline]
|
||||
fn is_textured(image: &Handle<Image>) -> bool {
|
||||
image.id() != DEFAULT_IMAGE_HANDLE.id()
|
||||
}
|
||||
|
||||
for extracted_uinode in extracted_uinodes.uinodes.drain(..) {
|
||||
let mode = if is_textured(&extracted_uinode.image) {
|
||||
if current_batch_image.id() != extracted_uinode.image.id() {
|
||||
if is_textured(¤t_batch_image) && start != end {
|
||||
commands.spawn(UiBatch {
|
||||
range: start..end,
|
||||
image: current_batch_image,
|
||||
z: last_z,
|
||||
});
|
||||
start = end;
|
||||
}
|
||||
current_batch_image = extracted_uinode.image.clone_weak();
|
||||
}
|
||||
TEXTURED_QUAD
|
||||
} else {
|
||||
// Untextured `UiBatch`es are never spawned within the loop.
|
||||
// If all the `extracted_uinodes` are untextured a single untextured UiBatch will be spawned after the loop terminates.
|
||||
UNTEXTURED_QUAD
|
||||
};
|
||||
|
||||
let mut uinode_rect = extracted_uinode.rect;
|
||||
|
||||
let rect_size = uinode_rect.size().extend(1.0);
|
||||
|
||||
// Specify the corners of the node
|
||||
let positions = QUAD_VERTEX_POSITIONS
|
||||
.map(|pos| (extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz());
|
||||
|
||||
// Calculate the effect of clipping
|
||||
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
||||
let mut positions_diff = if let Some(clip) = extracted_uinode.clip {
|
||||
[
|
||||
Vec2::new(
|
||||
f32::max(clip.min.x - positions[0].x, 0.),
|
||||
f32::max(clip.min.y - positions[0].y, 0.),
|
||||
),
|
||||
Vec2::new(
|
||||
f32::min(clip.max.x - positions[1].x, 0.),
|
||||
f32::max(clip.min.y - positions[1].y, 0.),
|
||||
),
|
||||
Vec2::new(
|
||||
f32::min(clip.max.x - positions[2].x, 0.),
|
||||
f32::min(clip.max.y - positions[2].y, 0.),
|
||||
),
|
||||
Vec2::new(
|
||||
f32::max(clip.min.x - positions[3].x, 0.),
|
||||
f32::min(clip.max.y - positions[3].y, 0.),
|
||||
),
|
||||
]
|
||||
} else {
|
||||
[Vec2::ZERO; 4]
|
||||
};
|
||||
|
||||
let positions_clipped = [
|
||||
positions[0] + positions_diff[0].extend(0.),
|
||||
positions[1] + positions_diff[1].extend(0.),
|
||||
positions[2] + positions_diff[2].extend(0.),
|
||||
positions[3] + positions_diff[3].extend(0.),
|
||||
];
|
||||
|
||||
let transformed_rect_size = extracted_uinode.transform.transform_vector3(rect_size);
|
||||
|
||||
// Don't try to cull nodes that have a rotation
|
||||
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
||||
// In those two cases, the culling check can proceed normally as corners will be on
|
||||
// horizontal / vertical lines
|
||||
// For all other angles, bypass the culling check
|
||||
// This does not properly handles all rotations on all axis
|
||||
if extracted_uinode.transform.x_axis[1] == 0.0 {
|
||||
// Cull nodes that are completely clipped
|
||||
if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x
|
||||
|| positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let uvs = if mode == UNTEXTURED_QUAD {
|
||||
[Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y]
|
||||
} else {
|
||||
let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max);
|
||||
if extracted_uinode.flip_x {
|
||||
std::mem::swap(&mut uinode_rect.max.x, &mut uinode_rect.min.x);
|
||||
positions_diff[0].x *= -1.;
|
||||
positions_diff[1].x *= -1.;
|
||||
positions_diff[2].x *= -1.;
|
||||
positions_diff[3].x *= -1.;
|
||||
}
|
||||
if extracted_uinode.flip_y {
|
||||
std::mem::swap(&mut uinode_rect.max.y, &mut uinode_rect.min.y);
|
||||
positions_diff[0].y *= -1.;
|
||||
positions_diff[1].y *= -1.;
|
||||
positions_diff[2].y *= -1.;
|
||||
positions_diff[3].y *= -1.;
|
||||
}
|
||||
[
|
||||
Vec2::new(
|
||||
uinode_rect.min.x + positions_diff[0].x,
|
||||
uinode_rect.min.y + positions_diff[0].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.max.x + positions_diff[1].x,
|
||||
uinode_rect.min.y + positions_diff[1].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.max.x + positions_diff[2].x,
|
||||
uinode_rect.max.y + positions_diff[2].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.min.x + positions_diff[3].x,
|
||||
uinode_rect.max.y + positions_diff[3].y,
|
||||
),
|
||||
]
|
||||
.map(|pos| pos / atlas_extent)
|
||||
};
|
||||
|
||||
let color = extracted_uinode.color.as_linear_rgba_f32();
|
||||
for i in QUAD_INDICES {
|
||||
ui_meta.vertices.push(UiVertex {
|
||||
position: positions_clipped[i].into(),
|
||||
uv: uvs[i].into(),
|
||||
color,
|
||||
mode,
|
||||
let draw_function = draw_functions.read().id::<DrawUi>();
|
||||
for (view, mut transparent_phase) in &mut views {
|
||||
let pipeline = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&ui_pipeline,
|
||||
UiPipelineKey { hdr: view.hdr },
|
||||
);
|
||||
transparent_phase
|
||||
.items
|
||||
.reserve(extracted_uinodes.uinodes.len());
|
||||
for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() {
|
||||
transparent_phase.add(TransparentUi {
|
||||
draw_function,
|
||||
pipeline,
|
||||
entity: *entity,
|
||||
sort_key: FloatOrd(extracted_uinode.stack_index as f32),
|
||||
// batch_size will be calculated in prepare_uinodes
|
||||
batch_size: 0,
|
||||
});
|
||||
}
|
||||
|
||||
last_z = extracted_uinode.transform.w_axis[2];
|
||||
end += QUAD_INDICES.len() as u32;
|
||||
}
|
||||
|
||||
// if start != end, there is one last batch to process
|
||||
if start != end {
|
||||
commands.spawn(UiBatch {
|
||||
range: start..end,
|
||||
image: current_batch_image,
|
||||
z: last_z,
|
||||
});
|
||||
}
|
||||
|
||||
ui_meta.vertices.write_buffer(&render_device, &render_queue);
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
|
@ -791,19 +682,19 @@ pub struct UiImageBindGroups {
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn queue_uinodes(
|
||||
draw_functions: Res<DrawFunctions<TransparentUi>>,
|
||||
pub fn prepare_uinodes(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut ui_meta: ResMut<UiMeta>,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
ui_pipeline: Res<UiPipeline>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<UiPipeline>>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut image_bind_groups: ResMut<UiImageBindGroups>,
|
||||
gpu_images: Res<RenderAssets<Image>>,
|
||||
ui_batches: Query<(Entity, &UiBatch)>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<TransparentUi>)>,
|
||||
mut phases: Query<&mut RenderPhase<TransparentUi>>,
|
||||
events: Res<SpriteAssetEvents>,
|
||||
mut previous_len: Local<usize>,
|
||||
) {
|
||||
// If an image has changed, the GpuImage has (probably) changed
|
||||
for event in &events.images {
|
||||
|
@ -815,7 +706,15 @@ pub fn queue_uinodes(
|
|||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_textured(image: &Handle<Image>) -> bool {
|
||||
image.id() != DEFAULT_IMAGE_HANDLE.id()
|
||||
}
|
||||
|
||||
if let Some(view_binding) = view_uniforms.uniforms.binding() {
|
||||
let mut batches: Vec<(Entity, UiBatch)> = Vec::with_capacity(*previous_len);
|
||||
|
||||
ui_meta.vertices.clear();
|
||||
ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
|
@ -824,41 +723,186 @@ pub fn queue_uinodes(
|
|||
label: Some("ui_view_bind_group"),
|
||||
layout: &ui_pipeline.view_layout,
|
||||
}));
|
||||
let draw_ui_function = draw_functions.read().id::<DrawUi>();
|
||||
for (view, mut transparent_phase) in &mut views {
|
||||
let pipeline = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&ui_pipeline,
|
||||
UiPipelineKey { hdr: view.hdr },
|
||||
);
|
||||
for (entity, batch) in &ui_batches {
|
||||
image_bind_groups
|
||||
.values
|
||||
.entry(batch.image.clone_weak())
|
||||
.or_insert_with(|| {
|
||||
let gpu_image = gpu_images.get(&batch.image).unwrap();
|
||||
render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(&gpu_image.texture_view),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::Sampler(&gpu_image.sampler),
|
||||
},
|
||||
],
|
||||
label: Some("ui_material_bind_group"),
|
||||
layout: &ui_pipeline.image_layout,
|
||||
})
|
||||
|
||||
// Vertex buffer index
|
||||
let mut index = 0;
|
||||
|
||||
for mut ui_phase in &mut phases {
|
||||
let mut batch_item_index = 0;
|
||||
let mut batch_image_handle = HandleId::Id(Uuid::nil(), u64::MAX);
|
||||
|
||||
for item_index in 0..ui_phase.items.len() {
|
||||
let item = &mut ui_phase.items[item_index];
|
||||
if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(item.entity) {
|
||||
let mut existing_batch = batches
|
||||
.last_mut()
|
||||
.filter(|_| batch_image_handle == extracted_uinode.image.id());
|
||||
|
||||
if existing_batch.is_none() {
|
||||
if let Some(gpu_image) = gpu_images.get(&extracted_uinode.image) {
|
||||
batch_item_index = item_index;
|
||||
batch_image_handle = extracted_uinode.image.id();
|
||||
|
||||
let new_batch = UiBatch {
|
||||
range: index..index,
|
||||
image_handle_id: extracted_uinode.image.id(),
|
||||
};
|
||||
|
||||
batches.push((item.entity, new_batch));
|
||||
|
||||
image_bind_groups
|
||||
.values
|
||||
.entry(Handle::weak(batch_image_handle))
|
||||
.or_insert_with(|| {
|
||||
render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(
|
||||
&gpu_image.texture_view,
|
||||
),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::Sampler(
|
||||
&gpu_image.sampler,
|
||||
),
|
||||
},
|
||||
],
|
||||
label: Some("ui_material_bind_group"),
|
||||
layout: &ui_pipeline.image_layout,
|
||||
})
|
||||
});
|
||||
|
||||
existing_batch = batches.last_mut();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mode = if is_textured(&extracted_uinode.image) {
|
||||
TEXTURED_QUAD
|
||||
} else {
|
||||
UNTEXTURED_QUAD
|
||||
};
|
||||
|
||||
let mut uinode_rect = extracted_uinode.rect;
|
||||
|
||||
let rect_size = uinode_rect.size().extend(1.0);
|
||||
|
||||
// Specify the corners of the node
|
||||
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
|
||||
(extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz()
|
||||
});
|
||||
transparent_phase.add(TransparentUi {
|
||||
draw_function: draw_ui_function,
|
||||
pipeline,
|
||||
entity,
|
||||
sort_key: FloatOrd(batch.z),
|
||||
});
|
||||
|
||||
// Calculate the effect of clipping
|
||||
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
||||
let mut positions_diff = if let Some(clip) = extracted_uinode.clip {
|
||||
[
|
||||
Vec2::new(
|
||||
f32::max(clip.min.x - positions[0].x, 0.),
|
||||
f32::max(clip.min.y - positions[0].y, 0.),
|
||||
),
|
||||
Vec2::new(
|
||||
f32::min(clip.max.x - positions[1].x, 0.),
|
||||
f32::max(clip.min.y - positions[1].y, 0.),
|
||||
),
|
||||
Vec2::new(
|
||||
f32::min(clip.max.x - positions[2].x, 0.),
|
||||
f32::min(clip.max.y - positions[2].y, 0.),
|
||||
),
|
||||
Vec2::new(
|
||||
f32::max(clip.min.x - positions[3].x, 0.),
|
||||
f32::min(clip.max.y - positions[3].y, 0.),
|
||||
),
|
||||
]
|
||||
} else {
|
||||
[Vec2::ZERO; 4]
|
||||
};
|
||||
|
||||
let positions_clipped = [
|
||||
positions[0] + positions_diff[0].extend(0.),
|
||||
positions[1] + positions_diff[1].extend(0.),
|
||||
positions[2] + positions_diff[2].extend(0.),
|
||||
positions[3] + positions_diff[3].extend(0.),
|
||||
];
|
||||
|
||||
let transformed_rect_size =
|
||||
extracted_uinode.transform.transform_vector3(rect_size);
|
||||
|
||||
// Don't try to cull nodes that have a rotation
|
||||
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
|
||||
// In those two cases, the culling check can proceed normally as corners will be on
|
||||
// horizontal / vertical lines
|
||||
// For all other angles, bypass the culling check
|
||||
// This does not properly handles all rotations on all axis
|
||||
if extracted_uinode.transform.x_axis[1] == 0.0 {
|
||||
// Cull nodes that are completely clipped
|
||||
if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x
|
||||
|| positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let uvs = if mode == UNTEXTURED_QUAD {
|
||||
[Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y]
|
||||
} else {
|
||||
let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max);
|
||||
if extracted_uinode.flip_x {
|
||||
std::mem::swap(&mut uinode_rect.max.x, &mut uinode_rect.min.x);
|
||||
positions_diff[0].x *= -1.;
|
||||
positions_diff[1].x *= -1.;
|
||||
positions_diff[2].x *= -1.;
|
||||
positions_diff[3].x *= -1.;
|
||||
}
|
||||
if extracted_uinode.flip_y {
|
||||
std::mem::swap(&mut uinode_rect.max.y, &mut uinode_rect.min.y);
|
||||
positions_diff[0].y *= -1.;
|
||||
positions_diff[1].y *= -1.;
|
||||
positions_diff[2].y *= -1.;
|
||||
positions_diff[3].y *= -1.;
|
||||
}
|
||||
[
|
||||
Vec2::new(
|
||||
uinode_rect.min.x + positions_diff[0].x,
|
||||
uinode_rect.min.y + positions_diff[0].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.max.x + positions_diff[1].x,
|
||||
uinode_rect.min.y + positions_diff[1].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.max.x + positions_diff[2].x,
|
||||
uinode_rect.max.y + positions_diff[2].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.min.x + positions_diff[3].x,
|
||||
uinode_rect.max.y + positions_diff[3].y,
|
||||
),
|
||||
]
|
||||
.map(|pos| pos / atlas_extent)
|
||||
};
|
||||
|
||||
let color = extracted_uinode.color.as_linear_rgba_f32();
|
||||
for i in QUAD_INDICES {
|
||||
ui_meta.vertices.push(UiVertex {
|
||||
position: positions_clipped[i].into(),
|
||||
uv: uvs[i].into(),
|
||||
color,
|
||||
mode,
|
||||
});
|
||||
}
|
||||
index += QUAD_INDICES.len() as u32;
|
||||
existing_batch.unwrap().1.range.end = index;
|
||||
ui_phase.items[batch_item_index].batch_size += 1;
|
||||
} else {
|
||||
batch_image_handle = HandleId::Id(Uuid::nil(), u64::MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
ui_meta.vertices.write_buffer(&render_device, &render_queue);
|
||||
*previous_len = batches.len();
|
||||
commands.insert_or_spawn_batch(batches);
|
||||
}
|
||||
extracted_uinodes.uinodes.clear();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::{UiBatch, UiImageBindGroups, UiMeta};
|
||||
use crate::{prelude::UiCameraConfig, DefaultCameraView};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_ecs::{
|
||||
prelude::*,
|
||||
system::{lifetimeless::*, SystemParamItem},
|
||||
|
@ -90,6 +91,7 @@ pub struct TransparentUi {
|
|||
pub entity: Entity,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
pub batch_size: usize,
|
||||
}
|
||||
|
||||
impl PhaseItem for TransparentUi {
|
||||
|
@ -109,6 +111,11 @@ impl PhaseItem for TransparentUi {
|
|||
fn draw_function(&self) -> DrawFunctionId {
|
||||
self.draw_function
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn batch_size(&self) -> usize {
|
||||
self.batch_size
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedRenderPipelinePhaseItem for TransparentUi {
|
||||
|
@ -161,7 +168,14 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetUiTextureBindGroup<I>
|
|||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let image_bind_groups = image_bind_groups.into_inner();
|
||||
pass.set_bind_group(I, image_bind_groups.values.get(&batch.image).unwrap(), &[]);
|
||||
pass.set_bind_group(
|
||||
I,
|
||||
image_bind_groups
|
||||
.values
|
||||
.get(&Handle::weak(batch.image_handle_id))
|
||||
.unwrap(),
|
||||
&[],
|
||||
);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
|
|
@ -279,7 +279,7 @@ impl Plugin for ColoredMesh2dPlugin {
|
|||
.add_render_command::<Transparent2d, DrawColoredMesh2d>()
|
||||
.init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
|
||||
.add_systems(ExtractSchedule, extract_colored_mesh2d)
|
||||
.add_systems(Render, queue_colored_mesh2d.in_set(RenderSet::Queue));
|
||||
.add_systems(Render, queue_colored_mesh2d.in_set(RenderSet::QueueMeshes));
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
|
@ -357,7 +357,7 @@ pub fn queue_colored_mesh2d(
|
|||
// in order to get correct transparency
|
||||
sort_key: FloatOrd(mesh_z),
|
||||
// This material is not batched
|
||||
batch_range: None,
|
||||
batch_size: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,10 @@ impl Plugin for GameOfLifeComputePlugin {
|
|||
// for operation on by the compute shader and display on the sprite.
|
||||
app.add_plugins(ExtractResourcePlugin::<GameOfLifeImage>::default());
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
render_app.add_systems(Render, queue_bind_group.in_set(RenderSet::Queue));
|
||||
render_app.add_systems(
|
||||
Render,
|
||||
prepare_bind_group.in_set(RenderSet::PrepareBindGroups),
|
||||
);
|
||||
|
||||
let mut render_graph = render_app.world.resource_mut::<RenderGraph>();
|
||||
render_graph.add_node("game_of_life", GameOfLifeNode::default());
|
||||
|
@ -96,7 +99,7 @@ struct GameOfLifeImage(Handle<Image>);
|
|||
#[derive(Resource)]
|
||||
struct GameOfLifeImageBindGroup(BindGroup);
|
||||
|
||||
fn queue_bind_group(
|
||||
fn prepare_bind_group(
|
||||
mut commands: Commands,
|
||||
pipeline: Res<GameOfLifePipeline>,
|
||||
gpu_images: Res<RenderAssets<Image>>,
|
||||
|
|
|
@ -86,8 +86,8 @@ impl Plugin for CustomMaterialPlugin {
|
|||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
queue_custom.in_set(RenderSet::Queue),
|
||||
prepare_instance_buffers.in_set(RenderSet::Prepare),
|
||||
queue_custom.in_set(RenderSet::QueueMeshes),
|
||||
prepare_instance_buffers.in_set(RenderSet::PrepareResources),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -136,6 +136,7 @@ fn queue_custom(
|
|||
draw_function: draw_custom,
|
||||
distance: rangefinder
|
||||
.distance_translation(&mesh_transforms.transform.translation),
|
||||
batch_size: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue