bevy/crates/bevy_pbr/src/wireframe.rs
James O'Brien 4f1d9a6315
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>
2023-08-27 14:33:49 +00:00

184 lines
6.7 KiB
Rust

use crate::{DrawMesh, MeshPipelineKey, SetMeshBindGroup, SetMeshViewBindGroup};
use crate::{MeshPipeline, MeshTransforms};
use bevy_app::Plugin;
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
use bevy_core_pipeline::core_3d::Opaque3d;
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;
use bevy_render::{
extract_resource::{ExtractResource, ExtractResourcePlugin},
mesh::{Mesh, MeshVertexBufferLayout},
render_asset::RenderAssets,
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
render_resource::{
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline,
SpecializedMeshPipelineError, SpecializedMeshPipelines,
},
view::{ExtractedView, Msaa, VisibleEntities},
RenderApp, RenderSet,
};
use bevy_utils::tracing::error;
pub const WIREFRAME_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766);
#[derive(Debug, Default)]
pub struct WireframePlugin;
impl Plugin for WireframePlugin {
fn build(&self, app: &mut bevy_app::App) {
load_internal_asset!(
app,
WIREFRAME_SHADER_HANDLE,
"render/wireframe.wgsl",
Shader::from_wgsl
);
app.register_type::<Wireframe>()
.register_type::<WireframeConfig>()
.init_resource::<WireframeConfig>()
.add_plugins((
ExtractResourcePlugin::<WireframeConfig>::default(),
ExtractComponentPlugin::<Wireframe>::default(),
));
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.add_render_command::<Opaque3d, DrawWireframes>()
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
.add_systems(Render, queue_wireframes.in_set(RenderSet::QueueMeshes));
}
}
fn finish(&self, app: &mut bevy_app::App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<WireframePipeline>();
}
}
}
/// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled
#[derive(Component, Debug, Clone, Default, ExtractComponent, Reflect)]
#[reflect(Component, Default)]
pub struct Wireframe;
#[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)]
#[reflect(Resource)]
pub struct WireframeConfig {
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered.
pub global: bool,
}
#[derive(Resource, Clone)]
pub struct WireframePipeline {
mesh_pipeline: MeshPipeline,
shader: Handle<Shader>,
}
impl FromWorld for WireframePipeline {
fn from_world(render_world: &mut World) -> Self {
WireframePipeline {
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
shader: WIREFRAME_SHADER_HANDLE.typed(),
}
}
}
impl SpecializedMeshPipeline for WireframePipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
descriptor.vertex.shader = self.shader.clone_weak();
descriptor
.vertex
.shader_defs
.push("MESH_BINDGROUP_1".into());
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
descriptor.primitive.polygon_mode = PolygonMode::Line;
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
Ok(descriptor)
}
}
#[allow(clippy::too_many_arguments)]
fn queue_wireframes(
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
render_meshes: Res<RenderAssets<Mesh>>,
wireframe_config: Res<WireframeConfig>,
wireframe_pipeline: Res<WireframePipeline>,
mut pipelines: ResMut<SpecializedMeshPipelines<WireframePipeline>>,
pipeline_cache: Res<PipelineCache>,
msaa: Res<Msaa>,
mut material_meshes: ParamSet<(
Query<(Entity, &Handle<Mesh>, &MeshTransforms)>,
Query<(Entity, &Handle<Mesh>, &MeshTransforms), With<Wireframe>>,
)>,
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
) {
let draw_custom = opaque_3d_draw_functions.read().id::<DrawWireframes>();
let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples());
for (view, visible_entities, mut opaque_phase) in &mut views {
let rangefinder = view.rangefinder3d();
let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);
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();
visible_entities
.entities
.iter()
.filter_map(|visible_entity| query.get(*visible_entity).ok())
.for_each(add_render_phase);
} else {
let query = material_meshes.p1();
visible_entities
.entities
.iter()
.filter_map(|visible_entity| query.get(*visible_entity).ok())
.for_each(add_render_phase);
}
}
}
type DrawWireframes = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMeshBindGroup<1>,
DrawMesh,
);