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:
James O'Brien 2023-08-27 07:33:49 -07:00 committed by GitHub
parent e8b3892517
commit 4f1d9a6315
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 993 additions and 1140 deletions

View file

@ -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>,

View file

@ -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>>>,

View file

@ -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 {

View file

@ -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>>,

View file

@ -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);
}
}

View file

@ -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>,

View file

@ -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)

View file

@ -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>>,

View file

@ -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>>,

View file

@ -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>,

View file

@ -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,
});
}
}

View file

@ -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,
});
}
}

View file

@ -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"

View file

@ -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>();

View file

@ -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,
});
}
}

View file

@ -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

View file

@ -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));
}
}
}

View file

@ -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]

View file

@ -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>,

View file

@ -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>,

View file

@ -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();

View file

@ -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);

View file

@ -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),
);
}
}

View file

@ -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),
);
}
}
}

View file

@ -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),
);
}
}

View file

@ -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
}
}

View file

@ -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());
}
}

View file

@ -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> {

View file

@ -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);
}
}

View file

@ -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());

View file

@ -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,
}

View file

@ -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));
}
}

View file

@ -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),
),
);
};
}

View file

@ -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,
});
}
}

View file

@ -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>,

View file

@ -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
}
}

View file

@ -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(),
},
);
}
}
}

View file

@ -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(&current_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();
}

View file

@ -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
}
}

View file

@ -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,
});
}
}

View file

@ -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>>,

View file

@ -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,
});
}
}