From d3b24b899a6cc6a730fdbcd96e1228bce1b8c212 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 11:51:59 -0700 Subject: [PATCH 01/16] Start wireframe. --- crates/bevy_core_pipeline/src/lib.rs | 1 - crates/bevy_pbr/src/render/wireframe.wgsl | 12 +- crates/bevy_pbr/src/wireframe.rs | 576 +++++++++++++++------ crates/bevy_render/src/render_phase/mod.rs | 1 + 4 files changed, 435 insertions(+), 155 deletions(-) diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 9e046142760a9..e6a300cc7ad25 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -23,7 +23,6 @@ pub mod prepass; mod skybox; pub mod tonemapping; pub mod upscaling; - pub use skybox::Skybox; /// The core pipeline prelude. diff --git a/crates/bevy_pbr/src/render/wireframe.wgsl b/crates/bevy_pbr/src/render/wireframe.wgsl index 981e5e1b1db3e..3873ffa3dd909 100644 --- a/crates/bevy_pbr/src/render/wireframe.wgsl +++ b/crates/bevy_pbr/src/render/wireframe.wgsl @@ -1,12 +1,12 @@ #import bevy_pbr::forward_io::VertexOutput -struct WireframeMaterial { - color: vec4, -}; +struct PushConstants { + color: vec4 +} + +var push_constants: PushConstants; -@group(2) @binding(0) -var material: WireframeMaterial; @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { - return material.color; + return push_constants.color; } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 88082c2880546..3ce1c5f983c05 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,15 +1,44 @@ -use crate::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin, MeshMaterial3d}; +use crate::material_bind_groups::MaterialBindGroupAllocator; +use crate::{DrawMesh, Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin, MeshMaterial3d, MeshPipeline, MeshPipelineKey, PreparedMaterial, RenderMaterialInstances, RenderMeshInstances, SetMaterialBindGroup, SetMeshBindGroup, SetMeshViewBindGroup}; use bevy_app::{Plugin, Startup, Update}; -use bevy_asset::{load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle}; -use bevy_color::{Color, LinearRgba}; +use bevy_asset::{ + load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle, UntypedAssetId, +}; +use bevy_color::{Color, ColorToComponents, LinearRgba}; +use bevy_core_pipeline::core_3d::{Camera3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey}; +use bevy_ecs::entity::hash_map::EntityHashMap; +use bevy_ecs::entity::hash_set::EntityHashSet; use bevy_ecs::prelude::*; +use bevy_ecs::query::QueryItem; +use bevy_ecs::system::lifetimeless::SRes; +use bevy_ecs::system::SystemParamItem; +use bevy_math::{FloatOrd, Vec4}; +use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::camera::ExtractedCamera; +use bevy_render::extract_component::UniformComponentPlugin; +use bevy_render::mesh::allocator::SlabId; +use bevy_render::mesh::{MeshVertexBufferLayout, RenderMesh}; +use bevy_render::render_asset::RenderAssets; +use bevy_render::render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode}; +use bevy_render::render_phase::{BinnedPhaseItem, BinnedRenderPhasePlugin, DrawFunctionId, DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, ViewSortedRenderPhases}; +use bevy_render::render_resource::binding_types::{ + sampler, storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer, +}; +use bevy_render::renderer::{RenderContext, RenderDevice}; +use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet}; +use bevy_render::view::{ExtractedView, NoIndirectDrawing, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget}; use bevy_render::{ extract_resource::ExtractResource, mesh::{Mesh3d, MeshVertexBufferLayoutRef}, prelude::*, render_resource::*, + Extract, RenderApp, }; +use nonmax::NonMaxU32; +use std::ops::Range; +use tracing::error; +use bevy_render::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}; pub const WIREFRAME_SHADER_HANDLE: Handle = weak_handle!("2646a633-f8e3-4380-87ae-b44d881abbce"); @@ -34,24 +63,17 @@ impl Plugin for WireframePlugin { Shader::from_wgsl ); - app.register_type::() + app.add_plugins((BinnedRenderPhasePlugin::::default(),)) + .init_resource::>() + .register_type::() .register_type::() .register_type::() .register_type::() - .init_resource::() - .add_plugins(MaterialPlugin::::default()) - .register_asset_reflect::() - .add_systems(Startup, setup_global_wireframe_material) - .add_systems( - Update, - ( - global_color_changed.run_if(resource_changed::), - wireframe_color_changed, - // Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global - // wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed. - (apply_wireframe_material, apply_global_wireframe_material).chain(), - ), - ); + .init_resource::(); + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; } } @@ -60,34 +82,264 @@ impl Plugin for WireframePlugin { /// /// This requires the [`WireframePlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default, Debug, PartialEq, Clone)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct Wireframe; +struct Wireframe3d { + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: Wireframe3dBatchSetKey, + /// The key, which determines which can be batched. + pub bin_key: Wireframe3dBinKey, + /// An entity from which data will be fetched, including the mesh if + /// applicable. + pub representative_entity: (Entity, MainEntity), + /// The ranges of instances. + pub batch_range: Range, + /// An extra index, which is either a dynamic offset or an index in the + /// indirect parameters list. + pub extra_index: PhaseItemExtraIndex, +} + +impl PhaseItem for Wireframe3d { + fn entity(&self) -> Entity { + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 + } + + fn draw_function(&self) -> DrawFunctionId { + todo!() + } + + fn batch_range(&self) -> &Range { + &self.batch_range + } + + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + fn extra_index(&self) -> PhaseItemExtraIndex { + self.extra_index.clone() + } + + fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range, &mut PhaseItemExtraIndex) { + (&mut self.batch_range, &mut self.extra_index) + } +} + +impl BinnedPhaseItem for Wireframe3d { + type BinKey = Wireframe3dBinKey; + type BatchSetKey = Wireframe3dBatchSetKey; + + fn new( + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, + representative_entity: (Entity, MainEntity), + batch_range: Range, + extra_index: PhaseItemExtraIndex, + ) -> Self { + Self { + batch_set_key, + bin_key, + representative_entity, + batch_range, + extra_index, + } + } +} + +struct Wireframe3dBatchSetKey { + /// The identifier of the render pipeline. + pub pipeline: CachedRenderPipelineId, + + /// The function used to draw. + pub draw_function: DrawFunctionId, + /// The ID of the slab of GPU memory that contains vertex data. + /// + /// For non-mesh items, you can fill this with 0 if your items can be + /// multi-drawn, or with a unique value if they can't. + pub vertex_slab: SlabId, + + /// The ID of the slab of GPU memory that contains index data, if present. + /// + /// For non-mesh items, you can safely fill this with `None`. + pub index_slab: Option, +} + +/// Data that must be identical in order to *batch* phase items together. +/// +/// Note that a *batch set* (if multi-draw is in use) contains multiple batches. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Wireframe3dBinKey { + pub color: Entity, +} + +pub struct SetWireframe3dPushConstants; + +impl RenderCommand

for SetWireframe3dPushConstants { + type Param = (SRes); + type ViewQuery = (); + type ItemQuery = (); + + #[inline] + fn render<'w>( + item: &P, + _view: (), + _item_query: Option<()>, + (wireframe_config): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + pass.set_push_constants( + ShaderStages::FRAGMENT, + 0, + bytemuck::bytes_of(&wireframe_config.color_for_entity(item.main_entity())), + ); + RenderCommandResult::Success + } +} + +pub type DrawWireframe3d = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetMeshBindGroup<1>, + SetWireframe3dPushConstants<2>, + DrawMesh, +); + +pub struct Wireframe3dPipelineKey { + mesh_key: MeshPipelineKey, + color: [f32; 4], +} + +#[derive(Resource, Clone)] +pub struct Wireframe3dPipeline { + mesh_pipeline: MeshPipeline, + shader: Handle, + layout: BindGroupLayout, +} + +impl FromWorld for Wireframe3dPipeline { + fn from_world(render_world: &mut World) -> Self { + let render_device = render_world.resource::(); + let layout = render_device.create_bind_group_layout( + "wireframe_material_bind_group_layout", + &BindGroupLayoutEntries::sequential( + ShaderStages::FRAGMENT, + (storage_buffer_read_only::(false),), + ), + ); + + Wireframe3dPipeline { + mesh_pipeline: render_world.resource::().clone(), + layout, + shader: WIREFRAME_SHADER_HANDLE, + } + } +} + +impl SpecializedMeshPipeline for Wireframe3dPipeline { + type Key = Wireframe3dPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayoutRef, + ) -> Result<(), SpecializedMeshPipelineError> { + let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?; + descriptor.layout.push(self.layout.clone()); + descriptor.push_constant_ranges.push(PushConstantRange { + stages: ShaderStages::FRAGMENT, + range: 0..4, + }); + descriptor.fragment.unwrap().shader = self.shader.clone(); + descriptor.primitive.polygon_mode = PolygonMode::Line; + descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; + Ok(()) + } +} + +#[derive(RenderLabel, Debug, Clone, Hash, PartialEq, Eq)] +struct Wireframe3dLabel; + +#[derive(Default)] +struct Wireframe3dNode; +impl ViewNode for Wireframe3dNode { + type ViewQuery = ( + &'static ExtractedCamera, + &'static ExtractedView, + &'static ViewTarget, + ); + + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + (camera, view, target): QueryItem<'w, Self::ViewQuery>, + world: &'w World, + ) -> Result<(), NodeRunError> { + let Some(wireframe_phase) = world.get_resource::>() + else { + return Ok(()); + }; + + let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else { + return Ok(()); + }; + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("wireframe_3d_pass"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } + + if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) { + error!("Error encountered while rendering the stencil phase {err:?}"); + return Err(NodeRunError::DrawError(err)); + } + + Ok(()) + } +} + /// Sets the color of the [`Wireframe`] of the entity it is attached to. /// /// If this component is present but there's no [`Wireframe`] component, /// it will still affect the color of the wireframe when [`WireframeConfig::global`] is set to true. /// /// This overrides the [`WireframeConfig::default_color`]. -// TODO: consider caching materials based on this color. -// This could blow up in size if people use random colored wireframes for each mesh. -// It will also be important to remove unused materials from the cache. #[derive(Component, Debug, Clone, Default, Reflect)] -#[reflect(Component, Default, Debug, Clone)] +#[reflect(Component, Default, Debug)] pub struct WireframeColor { pub color: Color, } +#[derive(Component, Debug, Clone, Default)] +pub struct ExtractedWireframeColor { + pub color: [f32; 4], +} + /// Disables wireframe rendering for any entity it is attached to. /// It will ignore the [`WireframeConfig`] global setting. /// /// This requires the [`WireframePlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default, Debug, PartialEq, Clone)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct NoWireframe; #[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] -#[reflect(Resource, Debug, Default, Clone)] +#[reflect(Resource, Debug, Default)] pub struct WireframeConfig { /// Whether to show wireframes for all meshes. /// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component. @@ -98,151 +350,179 @@ pub struct WireframeConfig { pub default_color: Color, } -#[derive(Resource)] -struct GlobalWireframeMaterial { - // This handle will be reused when the global config is enabled - handle: Handle, +pub struct ExtractedWireframeConfig { + pub global: bool, + pub default_color: [f32; 4], + pub color_entities: HashMap<[f32; 4], Entity>, + pub color_instances: MainEntityHashMap, + pub no_wireframe_instances: MainEntityHashSet, } -fn setup_global_wireframe_material( - mut commands: Commands, - mut materials: ResMut>, - config: Res, -) { - // Create the handle used for the global material - commands.insert_resource(GlobalWireframeMaterial { - handle: materials.add(WireframeMaterial { - color: config.default_color.into(), - }), - }); -} - -/// Updates the wireframe material of all entities without a [`WireframeColor`] or without a [`Wireframe`] component -fn global_color_changed( - config: Res, - mut materials: ResMut>, - global_material: Res, -) { - if let Some(global_material) = materials.get_mut(&global_material.handle) { - global_material.color = config.default_color.into(); +impl ExtractedWireframeConfig { + pub fn color_for_entity(&self, main_entity: MainEntity) -> Option<[f32; 4]> { + if let Some(color) = self.color_instances.get(&main_entity) { + return Some(*color); + } + if self.no_wireframe_instances.contains(&main_entity) { + return None; + } + Some(self.default_color) } } /// Updates the wireframe material when the color in [`WireframeColor`] changes -fn wireframe_color_changed( - mut materials: ResMut>, - mut colors_changed: Query< - (&mut MeshMaterial3d, &WireframeColor), - (With, Changed), +fn extract_wireframe_colors( + mut commands: Commands, + config: Extract>, + mut extracted_config: ResMut, + colors_changed: Extract< + Query<(Entity, &WireframeColor), (With, Changed)>, >, + without_colors: Extract, Without)>>, + no_wireframe_added: Extract>>, + colors_removed: Extract>, + no_wireframe_removed: Extract>, + wireframe_removed: Extract>, + mut seen_colors: Local>, ) { - for (mut handle, wireframe_color) in &mut colors_changed { - handle.0 = materials.add(WireframeMaterial { - color: wireframe_color.color.into(), + seen_colors.clear(); + extracted_config.global = config.global; + extracted_config.default_color = config.default_color.to_linear().to_f32_array(); + let default_color_entity = extracted_config + .color_entities + .entry(extracted_config.default_color) + .or_insert_with(|| { + commands.spawn(ExtractedWireframeColor { + color: extracted_config.default_color, + }).id() + }); + seen_colors.insert(extracted_config.default_color); + for (entity, wireframe_color) in &colors_changed { + let linear_color = wireframe_color.color.to_linear().to_f32_array(); + let color_entity = extracted_config.color_entities.entry(linear_color).or_insert_with(|| { + commands.spawn(ExtractedWireframeColor { + color: linear_color, + }).id() }); + extracted_config + .color_instances + .insert(entity.into(), *color_entity); } -} - -/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component, and removes it -/// for any mesh with a [`NoWireframe`] component. -fn apply_wireframe_material( - mut commands: Commands, - mut materials: ResMut>, - wireframes: Query< - (Entity, Option<&WireframeColor>), - (With, Without>), - >, - no_wireframes: Query, With>)>, - mut removed_wireframes: RemovedComponents, - global_material: Res, -) { - for e in removed_wireframes.read().chain(no_wireframes.iter()) { - if let Ok(mut commands) = commands.get_entity(e) { - commands.remove::>(); + for entity in &no_wireframe_added { + extracted_config + .no_wireframe_instances + .insert(entity.into()); + } + for entity in no_wireframe_removed.iter() { + extracted_config + .no_wireframe_instances + .remove(&entity.into()); + } + for entity in colors_removed.iter() { + if without_colors.contains(entity) { + extracted_config + .color_instances + .insert(&entity.into(), extracted_config.default_color); + } else { + extracted_config.color_instances.remove(&entity.into()); } } - - let mut material_to_spawn = vec![]; - for (e, maybe_color) in &wireframes { - let material = get_wireframe_material(maybe_color, &mut materials, &global_material); - material_to_spawn.push((e, MeshMaterial3d(material))); + for entity in wireframe_removed.iter() { + extracted_config.color_instances.remove(&entity.into()); } - commands.try_insert_batch(material_to_spawn); } -type WireframeFilter = (With, Without, Without); - -/// Applies or removes a wireframe material on any mesh without a [`Wireframe`] or [`NoWireframe`] component. -fn apply_global_wireframe_material( - mut commands: Commands, - config: Res, - meshes_without_material: Query< - (Entity, Option<&WireframeColor>), - (WireframeFilter, Without>), - >, - meshes_with_global_material: Query< - Entity, - (WireframeFilter, With>), - >, - global_material: Res, - mut materials: ResMut>, +fn extract_wireframe_3d_camera( + mut wireframe_3d_phases: ResMut>, + cameras: Extract, + ), With>>, + mut live_entities: Local>, + gpu_preprocessing_support: Res, ) { - if config.global { - let mut material_to_spawn = vec![]; - for (e, maybe_color) in &meshes_without_material { - let material = get_wireframe_material(maybe_color, &mut materials, &global_material); - // We only add the material handle but not the Wireframe component - // This makes it easy to detect which mesh is using the global material and which ones are user specified - material_to_spawn.push((e, MeshMaterial3d(material))); + live_entities.clear(); + for (main_entity, camera, no_indirect_drawing) in &cameras { + if !camera.is_active { + continue; } - commands.try_insert_batch(material_to_spawn); - } else { - for e in &meshes_with_global_material { - commands - .entity(e) - .remove::>(); - } - } -} + let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing { + GpuPreprocessingMode::Culling + } else { + GpuPreprocessingMode::PreprocessingOnly + }); -/// Gets a handle to a wireframe material with a fallback on the default material -fn get_wireframe_material( - maybe_color: Option<&WireframeColor>, - wireframe_materials: &mut Assets, - global_material: &GlobalWireframeMaterial, -) -> Handle { - if let Some(wireframe_color) = maybe_color { - wireframe_materials.add(WireframeMaterial { - color: wireframe_color.color.into(), - }) - } else { - // If there's no color specified we can use the global material since it's already set to use the default_color - global_material.handle.clone() + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); + live_entities.insert(retained_view_entity); } -} -#[derive(Default, AsBindGroup, Debug, Clone, Asset, Reflect)] -#[reflect(Default, Clone)] -pub struct WireframeMaterial { - #[uniform(0)] - pub color: LinearRgba, + // Clear out all dead views. + wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); } -impl Material for WireframeMaterial { - fn fragment_shader() -> ShaderRef { - WIREFRAME_SHADER_HANDLE.into() - } +fn queue_wireframe( + custom_draw_functions: Res>, + mut pipelines: ResMut>, + pipeline_cache: Res, + wireframe_3d_pipeline: Res, + render_meshes: Res>, + render_mesh_instances: Res, + mut wireframe_3d_phases: ResMut>, + mut views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, + extracted_wireframe_config: Res, +) { + for (view, visible_entities, msaa) in &mut views { + let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else { + continue; + }; + let draw_custom = custom_draw_functions.read().id::(); - fn specialize( - _pipeline: &MaterialPipeline, - descriptor: &mut RenderPipelineDescriptor, - _layout: &MeshVertexBufferLayoutRef, - _key: MaterialPipelineKey, - ) -> Result<(), SpecializedMeshPipelineError> { - descriptor.primitive.polygon_mode = PolygonMode::Line; - if let Some(depth_stencil) = descriptor.depth_stencil.as_mut() { - depth_stencil.bias.slope_scale = 1.0; + let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) + | MeshPipelineKey::from_hdr(view.hdr); + + for (render_entity, visible_entity) in visible_entities.iter::() { + let Some(color) = extracted_wireframe_config.color_for_entity(*visible_entity) else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) + else { + continue; + }; + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + + let mut mesh_key = view_key; + mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology()); + let pipeline_key = Wireframe3dPipelineKey { + mesh_key, + color, + }; + + let pipeline_id = pipelines.specialize( + &pipeline_cache, + &wireframe_3d_pipeline, + pipeline_key, + &mesh.layout, + ); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + let bin_key = Wireframe3dBinKey { + color, + }; + let batch_set_key = Wireframe3dBatchSetKey { + vertex_slab: mesh_instance.vertex_slab, + index_slab: mesh_instance.index_slab, + }; + wireframe_phase.add(Wireframe3dBinKey { + color_entity: (), + }); } - Ok(()) } -} +} \ No newline at end of file diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index fae06f0c60dea..312261a6c0b0d 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -420,6 +420,7 @@ impl ViewBinnedRenderPhases where BPI: BinnedPhaseItem, { + pub fn prepare_for_new_frame( &mut self, retained_view_entity: RetainedViewEntity, From 00880a1d3e8328109544d789c0b8656bcbcac230 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 14:18:00 -0700 Subject: [PATCH 02/16] More. --- crates/bevy_core_pipeline/src/core_3d/mod.rs | 6 + crates/bevy_pbr/src/wireframe.rs | 693 +++++++++++++++---- examples/3d/wireframe.rs | 2 +- 3 files changed, 554 insertions(+), 147 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index fa092b7fd494c..8808f2c8713a5 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -28,6 +28,8 @@ pub mod graph { MainTransmissivePass, MainTransparentPass, EndMainPass, + StartDebugPass, + EndDebugPass, LateDownsampleDepth, Taa, MotionBlur, @@ -208,6 +210,8 @@ impl Plugin for Core3dPlugin { Node3d::MainTransparentPass, ) .add_render_graph_node::(Core3d, Node3d::EndMainPass) + .add_render_graph_node::(Core3d, Node3d::StartDebugPass) + .add_render_graph_node::(Core3d, Node3d::EndDebugPass) .add_render_graph_node::>(Core3d, Node3d::DepthOfField) .add_render_graph_node::>(Core3d, Node3d::Tonemapping) .add_render_graph_node::(Core3d, Node3d::EndMainPassPostProcessing) @@ -226,6 +230,8 @@ impl Plugin for Core3dPlugin { Node3d::MainTransmissivePass, Node3d::MainTransparentPass, Node3d::EndMainPass, + Node3d::StartDebugPass, + Node3d::EndDebugPass, Node3d::Tonemapping, Node3d::EndMainPassPostProcessing, Node3d::Upscaling, diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 3ce1c5f983c05..d68bf428d1fc1 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,44 +1,70 @@ -use crate::material_bind_groups::MaterialBindGroupAllocator; -use crate::{DrawMesh, Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin, MeshMaterial3d, MeshPipeline, MeshPipelineKey, PreparedMaterial, RenderMaterialInstances, RenderMeshInstances, SetMaterialBindGroup, SetMeshBindGroup, SetMeshViewBindGroup}; -use bevy_app::{Plugin, Startup, Update}; +use crate::{ + alpha_mode_pipeline_key, DrawMesh, EntitySpecializationTicks, Material, MaterialPipeline, + MaterialPipelineKey, MaterialPlugin, MeshMaterial3d, MeshPipeline, MeshPipelineKey, + PreparedMaterial, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, + RenderMeshInstances, SetMaterialBindGroup, SetMeshBindGroup, SetMeshViewBindGroup, + SpecializedMaterialPipelineCache, ViewKeyCache, ViewSpecializationTicks, +}; +use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; +use bevy_asset::prelude::AssetChanged; use bevy_asset::{ - load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle, UntypedAssetId, + load_internal_asset, weak_handle, AsAssetId, Asset, AssetApp, AssetEvents, AssetId, Assets, + Handle, }; -use bevy_color::{Color, ColorToComponents, LinearRgba}; -use bevy_core_pipeline::core_3d::{Camera3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey}; +use bevy_color::{Color, ColorToComponents}; +use bevy_core_pipeline::core_2d::{AlphaMask2d, Opaque2d, Transparent2d}; +use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; +use bevy_core_pipeline::core_3d::Camera3d; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; use bevy_ecs::entity::hash_map::EntityHashMap; -use bevy_ecs::entity::hash_set::EntityHashSet; use bevy_ecs::prelude::*; use bevy_ecs::query::QueryItem; use bevy_ecs::system::lifetimeless::SRes; -use bevy_ecs::system::SystemParamItem; +use bevy_ecs::system::{SystemChangeTick, SystemParamItem}; use bevy_math::{FloatOrd, Vec4}; use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform_support::hash::FixedHasher; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}; use bevy_render::camera::ExtractedCamera; use bevy_render::extract_component::UniformComponentPlugin; -use bevy_render::mesh::allocator::SlabId; +use bevy_render::mesh::allocator::{MeshAllocator, SlabId}; use bevy_render::mesh::{MeshVertexBufferLayout, RenderMesh}; -use bevy_render::render_asset::RenderAssets; -use bevy_render::render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode}; -use bevy_render::render_phase::{BinnedPhaseItem, BinnedRenderPhasePlugin, DrawFunctionId, DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, ViewSortedRenderPhases}; +use bevy_render::render_asset::{ + prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, +}; +use bevy_render::render_graph::{ + NodeRunError, RenderGraphApp, RenderGraphContext, RenderLabel, ViewNode, ViewNodeRunner, +}; +use bevy_render::render_phase::{ + AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, + CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, + PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, + TrackedRenderPass, ViewBinnedRenderPhases, ViewSortedRenderPhases, +}; use bevy_render::render_resource::binding_types::{ sampler, storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer, }; use bevy_render::renderer::{RenderContext, RenderDevice}; use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet}; -use bevy_render::view::{ExtractedView, NoIndirectDrawing, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget}; +use bevy_render::view::{ + ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities, + RetainedViewEntity, ViewDepthTexture, ViewTarget, +}; use bevy_render::{ extract_resource::ExtractResource, mesh::{Mesh3d, MeshVertexBufferLayoutRef}, prelude::*, render_resource::*, - Extract, RenderApp, + Extract, Render, RenderApp, RenderDebugFlags, RenderSet, }; +use derive_more::From; use nonmax::NonMaxU32; +use std::hash::Hash; +use std::marker::PhantomData; use std::ops::Range; use tracing::error; -use bevy_render::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}; pub const WIREFRAME_SHADER_HANDLE: Handle = weak_handle!("2646a633-f8e3-4380-87ae-b44d881abbce"); @@ -53,7 +79,18 @@ pub const WIREFRAME_SHADER_HANDLE: Handle = /// /// This is a native only feature. #[derive(Debug, Default)] -pub struct WireframePlugin; +pub struct WireframePlugin { + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, +} + +impl WireframePlugin { + /// Creates a new [`WireframePlugin`] with the given debug flags. + pub fn new(debug_flags: RenderDebugFlags) -> Self { + Self { debug_flags } + } +} + impl Plugin for WireframePlugin { fn build(&self, app: &mut bevy_app::App) { load_internal_asset!( @@ -63,17 +100,82 @@ impl Plugin for WireframePlugin { Shader::from_wgsl ); - app.add_plugins((BinnedRenderPhasePlugin::::default(),)) + app.add_plugins(( + BinnedRenderPhasePlugin::::new(self.debug_flags), + RenderAssetPlugin::::default(), + )) + .init_asset::() + .init_resource::>() + .register_type::() + .register_type::() + .register_type::() + .init_resource::() + .init_resource::() + .add_systems(Startup, setup_global_wireframe_material) + .add_systems( + Update, + ( + global_color_changed.run_if(resource_changed::), + wireframe_color_changed, + // Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global + // wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed. + (apply_wireframe_material, apply_global_wireframe_material).chain(), + ), + ) + .add_systems( + PostUpdate, + check_wireframe_entities_needing_specialization + .after(AssetEvents) + .run_if(resource_exists::), + ); + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .init_resource::() + .init_resource::() + .init_resource::>() + .add_render_command::() + .init_resource::() .init_resource::>() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_resource::(); + .add_render_graph_node::>(Core3d, Wireframe3dLabel) + .add_render_graph_edges( + Core3d, + ( + Node3d::StartDebugPass, + Wireframe3dLabel, + Node3d::EndDebugPass, + ), + ) + .add_systems( + ExtractSchedule, + ( + extract_wireframe_3d_camera, + extract_wireframe_entities_needing_specialization, + extract_wireframe_materials, + ), + ) + .add_systems( + Render, + ( + specialize_wireframes + .in_set(RenderSet::PrepareMeshes) + .after(prepare_assets::) + .after(prepare_assets::), + queue_wireframes + .in_set(RenderSet::QueueMeshes) + .after(prepare_assets::), + ), + ); + } + fn finish(&self, app: &mut App) { let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; + render_app.init_resource::(); } } @@ -113,7 +215,7 @@ impl PhaseItem for Wireframe3d { } fn draw_function(&self) -> DrawFunctionId { - todo!() + self.batch_set_key.draw_function } fn batch_range(&self) -> &Range { @@ -133,6 +235,12 @@ impl PhaseItem for Wireframe3d { } } +impl CachedRenderPipelinePhaseItem for Wireframe3d { + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.batch_set_key.pipeline + } +} + impl BinnedPhaseItem for Wireframe3d { type BinKey = Wireframe3dBinKey; type BatchSetKey = Wireframe3dBatchSetKey; @@ -154,6 +262,7 @@ impl BinnedPhaseItem for Wireframe3d { } } +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Wireframe3dBatchSetKey { /// The identifier of the render pipeline. pub pipeline: CachedRenderPipelineId, @@ -172,18 +281,28 @@ struct Wireframe3dBatchSetKey { pub index_slab: Option, } +impl PhaseItemBatchSetKey for Wireframe3dBatchSetKey { + fn indexed(&self) -> bool { + self.index_slab.is_some() + } +} + /// Data that must be identical in order to *batch* phase items together. /// /// Note that a *batch set* (if multi-draw is in use) contains multiple batches. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Wireframe3dBinKey { - pub color: Entity, + /// The wireframe material asset ID. + pub asset_id: AssetId, } -pub struct SetWireframe3dPushConstants; +pub struct SetWireframe3dPushConstants; -impl RenderCommand

for SetWireframe3dPushConstants { - type Param = (SRes); +impl RenderCommand

for SetWireframe3dPushConstants { + type Param = ( + SRes, + SRes>, + ); type ViewQuery = (); type ItemQuery = (); @@ -192,13 +311,20 @@ impl RenderCommand

for SetWireframe3dPushConsta item: &P, _view: (), _item_query: Option<()>, - (wireframe_config): SystemParamItem<'w, '_, Self::Param>, + (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { + let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else { + return RenderCommandResult::Failure("No wireframe material found for entity"); + }; + let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else { + return RenderCommandResult::Failure("No wireframe material found for entity"); + }; + pass.set_push_constants( ShaderStages::FRAGMENT, 0, - bytemuck::bytes_of(&wireframe_config.color_for_entity(item.main_entity())), + bytemuck::bytes_of(&wireframe_material.color), ); RenderCommandResult::Success } @@ -208,15 +334,10 @@ pub type DrawWireframe3d = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMeshBindGroup<1>, - SetWireframe3dPushConstants<2>, + SetWireframe3dPushConstants, DrawMesh, ); -pub struct Wireframe3dPipelineKey { - mesh_key: MeshPipelineKey, - color: [f32; 4], -} - #[derive(Resource, Clone)] pub struct Wireframe3dPipeline { mesh_pipeline: MeshPipeline, @@ -244,23 +365,25 @@ impl FromWorld for Wireframe3dPipeline { } impl SpecializedMeshPipeline for Wireframe3dPipeline { - type Key = Wireframe3dPipelineKey; + type Key = MeshPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayoutRef, - ) -> Result<(), SpecializedMeshPipelineError> { - let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?; + ) -> Result { + let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; + descriptor.label = Some("wireframe_3d_pipeline".into()); descriptor.layout.push(self.layout.clone()); descriptor.push_constant_ranges.push(PushConstantRange { stages: ShaderStages::FRAGMENT, - range: 0..4, + range: 0..16, }); - descriptor.fragment.unwrap().shader = self.shader.clone(); + let fragment = descriptor.fragment.as_mut().unwrap(); + fragment.shader = self.shader.clone(); descriptor.primitive.polygon_mode = PolygonMode::Line; descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; - Ok(()) + Ok(descriptor) } } @@ -274,13 +397,14 @@ impl ViewNode for Wireframe3dNode { &'static ExtractedCamera, &'static ExtractedView, &'static ViewTarget, + &'static ViewDepthTexture, ); fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target): QueryItem<'w, Self::ViewQuery>, + (camera, view, target, depth): QueryItem<'w, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let Some(wireframe_phase) = world.get_resource::>() @@ -295,7 +419,7 @@ impl ViewNode for Wireframe3dNode { let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("wireframe_3d_pass"), color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: None, + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), timestamp_writes: None, occlusion_query_set: None, }); @@ -350,94 +474,199 @@ pub struct WireframeConfig { pub default_color: Color, } -pub struct ExtractedWireframeConfig { - pub global: bool, - pub default_color: [f32; 4], - pub color_entities: HashMap<[f32; 4], Entity>, - pub color_instances: MainEntityHashMap, - pub no_wireframe_instances: MainEntityHashSet, +#[derive(Asset, Reflect, Clone, Debug, Default)] +#[reflect(Clone, Default)] +pub struct WireframeMaterial { + pub color: Color, +} + +pub struct RenderWireframeMaterial { + pub color: [f32; 4], +} + +#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] +#[reflect(Component, Default, Clone, PartialEq)] +pub struct Mesh3dWireframe(pub Handle); + +impl AsAssetId for Mesh3dWireframe { + type Asset = WireframeMaterial; + + fn as_asset_id(&self) -> AssetId { + self.0.id() + } +} + +impl RenderAsset for RenderWireframeMaterial { + type SourceAsset = WireframeMaterial; + type Param = (); + + fn prepare_asset( + source_asset: Self::SourceAsset, + _asset_id: AssetId, + _param: &mut SystemParamItem, + ) -> std::result::Result> { + Ok(RenderWireframeMaterial { + color: source_asset.color.to_linear().to_f32_array(), + }) + } } -impl ExtractedWireframeConfig { - pub fn color_for_entity(&self, main_entity: MainEntity) -> Option<[f32; 4]> { - if let Some(color) = self.color_instances.get(&main_entity) { - return Some(*color); +#[derive(Resource, Deref, DerefMut, Default)] +pub struct RenderWireframeInstances(MainEntityHashMap>); + +pub fn extract_wireframe_materials( + mut material_instances: ResMut, + changed_meshes_query: Extract< + Query< + (Entity, &ViewVisibility, &Mesh3dWireframe), + Or<(Changed, Changed)>, + >, + >, + mut removed_visibilities_query: Extract>, + mut removed_materials_query: Extract>, +) { + for (entity, view_visibility, material) in &changed_meshes_query { + if view_visibility.get() { + material_instances.insert(entity.into(), material.id()); + } else { + material_instances.remove(&MainEntity::from(entity)); } - if self.no_wireframe_instances.contains(&main_entity) { - return None; + } + + for entity in removed_visibilities_query + .read() + .chain(removed_materials_query.read()) + { + // Only queue a mesh for removal if we didn't pick it up above. + // It's possible that a necessary component was removed and re-added in + // the same frame. + if !changed_meshes_query.contains(entity) { + material_instances.remove(&MainEntity::from(entity)); } - Some(self.default_color) } } -/// Updates the wireframe material when the color in [`WireframeColor`] changes -fn extract_wireframe_colors( +#[derive(Resource)] +struct GlobalWireframeMaterial { + // This handle will be reused when the global config is enabled + handle: Handle, +} + +fn setup_global_wireframe_material( mut commands: Commands, - config: Extract>, - mut extracted_config: ResMut, - colors_changed: Extract< - Query<(Entity, &WireframeColor), (With, Changed)>, + mut materials: ResMut>, + config: Res, +) { + // Create the handle used for the global material + commands.insert_resource(GlobalWireframeMaterial { + handle: materials.add(WireframeMaterial { + color: config.default_color.into(), + }), + }); +} + +/// Updates the wireframe material of all entities without a [`WireframeColor`] or without a [`Wireframe`] component +fn global_color_changed( + config: Res, + mut materials: ResMut>, + global_material: Res, +) { + if let Some(global_material) = materials.get_mut(&global_material.handle) { + global_material.color = config.default_color.into(); + } +} + +/// Updates the wireframe material when the color in [`WireframeColor`] changes +fn wireframe_color_changed( + mut materials: ResMut>, + mut colors_changed: Query< + (&mut Mesh3dWireframe, &WireframeColor), + (With, Changed), >, - without_colors: Extract, Without)>>, - no_wireframe_added: Extract>>, - colors_removed: Extract>, - no_wireframe_removed: Extract>, - wireframe_removed: Extract>, - mut seen_colors: Local>, ) { - seen_colors.clear(); - extracted_config.global = config.global; - extracted_config.default_color = config.default_color.to_linear().to_f32_array(); - let default_color_entity = extracted_config - .color_entities - .entry(extracted_config.default_color) - .or_insert_with(|| { - commands.spawn(ExtractedWireframeColor { - color: extracted_config.default_color, - }).id() + for (mut handle, wireframe_color) in &mut colors_changed { + handle.0 = materials.add(WireframeMaterial { + color: wireframe_color.color.into(), }); - seen_colors.insert(extracted_config.default_color); - for (entity, wireframe_color) in &colors_changed { - let linear_color = wireframe_color.color.to_linear().to_f32_array(); - let color_entity = extracted_config.color_entities.entry(linear_color).or_insert_with(|| { - commands.spawn(ExtractedWireframeColor { - color: linear_color, - }).id() - }); - extracted_config - .color_instances - .insert(entity.into(), *color_entity); - } - for entity in &no_wireframe_added { - extracted_config - .no_wireframe_instances - .insert(entity.into()); - } - for entity in no_wireframe_removed.iter() { - extracted_config - .no_wireframe_instances - .remove(&entity.into()); - } - for entity in colors_removed.iter() { - if without_colors.contains(entity) { - extracted_config - .color_instances - .insert(&entity.into(), extracted_config.default_color); - } else { - extracted_config.color_instances.remove(&entity.into()); + } +} + +/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component, and removes it +/// for any mesh with a [`NoWireframe`] component. +fn apply_wireframe_material( + mut commands: Commands, + mut materials: ResMut>, + wireframes: Query< + (Entity, Option<&WireframeColor>), + (With, Without), + >, + no_wireframes: Query, With)>, + mut removed_wireframes: RemovedComponents, + global_material: Res, +) { + for e in removed_wireframes.read().chain(no_wireframes.iter()) { + if let Ok(mut commands) = commands.get_entity(e) { + commands.remove::(); } } - for entity in wireframe_removed.iter() { - extracted_config.color_instances.remove(&entity.into()); + + let mut material_to_spawn = vec![]; + for (e, maybe_color) in &wireframes { + let material = get_wireframe_material(maybe_color, &mut materials, &global_material); + material_to_spawn.push((e, Mesh3dWireframe(material))); + } + commands.try_insert_batch(material_to_spawn); +} + +type WireframeFilter = (With, Without, Without); + +/// Applies or removes a wireframe material on any mesh without a [`Wireframe`] or [`NoWireframe`] component. +fn apply_global_wireframe_material( + mut commands: Commands, + config: Res, + meshes_without_material: Query< + (Entity, Option<&WireframeColor>), + (WireframeFilter, Without), + >, + meshes_with_global_material: Query)>, + global_material: Res, + mut materials: ResMut>, +) { + if config.global { + let mut material_to_spawn = vec![]; + for (e, maybe_color) in &meshes_without_material { + let material = get_wireframe_material(maybe_color, &mut materials, &global_material); + // We only add the material handle but not the Wireframe component + // This makes it easy to detect which mesh is using the global material and which ones are user specified + material_to_spawn.push((e, Mesh3dWireframe(material))); + } + commands.try_insert_batch(material_to_spawn); + } else { + for e in &meshes_with_global_material { + commands.entity(e).remove::(); + } + } +} + +/// Gets a handle to a wireframe material with a fallback on the default material +fn get_wireframe_material( + maybe_color: Option<&WireframeColor>, + wireframe_materials: &mut Assets, + global_material: &GlobalWireframeMaterial, +) -> Handle { + if let Some(wireframe_color) = maybe_color { + wireframe_materials.add(WireframeMaterial { + color: wireframe_color.color.into(), + }) + } else { + // If there's no color specified we can use the global material since it's already set to use the default_color + global_material.handle.clone() } } fn extract_wireframe_3d_camera( mut wireframe_3d_phases: ResMut>, - cameras: Extract, - ), With>>, + cameras: Extract), With>>, mut live_entities: Local>, gpu_preprocessing_support: Res, ) { @@ -461,30 +690,124 @@ fn extract_wireframe_3d_camera( wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); } -fn queue_wireframe( - custom_draw_functions: Res>, - mut pipelines: ResMut>, - pipeline_cache: Res, - wireframe_3d_pipeline: Res, +pub fn extract_wireframe_entities_needing_specialization( + entities_needing_specialization: Extract>, + mut entity_specialization_ticks: ResMut, + ticks: SystemChangeTick, +) { + for entity in entities_needing_specialization.iter() { + // Update the entity's specialization tick with this run's tick + entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); + } +} + +#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)] +pub struct WireframeEntitiesNeedingSpecialization { + #[deref] + pub entities: Vec, +} + +#[derive(Resource, Deref, DerefMut, Clone, Debug)] +pub struct WireframeEntitySpecializationTicks { + pub entities: MainEntityHashMap, +} + +impl Default for WireframeEntitySpecializationTicks { + fn default() -> Self { + Self { + entities: MainEntityHashMap::default(), + } + } +} + +/// Stores the [`crate::SpecializedMaterialViewPipelineCache`] for each view. +#[derive(Resource, Deref, DerefMut, Default)] +pub struct SpecializedWireframePipelineCache { + // view entity -> view pipeline cache + #[deref] + map: HashMap, +} + +/// Stores the cached render pipeline ID for each entity in a single view, as +/// well as the last time it was changed. +#[derive(Deref, DerefMut, Default)] +pub struct SpecializedWireframeViewPipelineCache { + // material entity -> (tick, pipeline_id) + #[deref] + map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, +} + +pub fn check_wireframe_entities_needing_specialization( + needs_specialization: Query< + Entity, + Or<( + Changed, + AssetChanged, + Changed, + AssetChanged, + )>, + >, + mut entities_needing_specialization: ResMut, +) { + entities_needing_specialization.clear(); + for entity in &needs_specialization { + entities_needing_specialization.push(entity); + } +} + +pub fn specialize_wireframes( render_meshes: Res>, render_mesh_instances: Res, - mut wireframe_3d_phases: ResMut>, - mut views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, - extracted_wireframe_config: Res, + render_wireframe_instances: Res, + render_visibility_ranges: Res, + wireframe_phases: Res>, + views: Query<(&ExtractedView, &RenderVisibleEntities)>, + view_key_cache: Res, + entity_specialization_ticks: Res, + view_specialization_ticks: Res, + mut specialized_material_pipeline_cache: ResMut, + mut pipelines: ResMut>, + pipeline: Res, + pipeline_cache: Res, + ticks: SystemChangeTick, ) { - for (view, visible_entities, msaa) in &mut views { - let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else { + // Record the retained IDs of all shadow views so that we can expire old + // pipeline IDs. + let mut all_views: HashSet = HashSet::default(); + + for (view, visible_entities) in &views { + all_views.insert(view.retained_view_entity); + + if !wireframe_phases.contains_key(&view.retained_view_entity) { + continue; + } + + let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else { continue; }; - let draw_custom = custom_draw_functions.read().id::(); - let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); + let view_tick = view_specialization_ticks + .get(&view.retained_view_entity) + .unwrap(); + let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache + .entry(view.retained_view_entity) + .or_default(); - for (render_entity, visible_entity) in visible_entities.iter::() { - let Some(color) = extracted_wireframe_config.color_for_entity(*visible_entity) else { + for (_, visible_entity) in visible_entities.iter::() { + let Some(material_asset_id) = render_wireframe_instances.get(visible_entity) else { continue; }; + let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); + let last_specialized_tick = view_specialized_material_pipeline_cache + .get(visible_entity) + .map(|(tick, _)| *tick); + let needs_specialization = last_specialized_tick.is_none_or(|tick| { + view_tick.is_newer_than(tick, ticks.this_run()) + || entity_tick.is_newer_than(tick, ticks.this_run()) + }); + if !needs_specialization { + continue; + } let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) else { continue; @@ -493,19 +816,31 @@ fn queue_wireframe( continue; }; - let mut mesh_key = view_key; + let mut mesh_key = *view_key; mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology()); - let pipeline_key = Wireframe3dPipelineKey { - mesh_key, - color, - }; - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &wireframe_3d_pipeline, - pipeline_key, - &mesh.layout, - ); + if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) { + mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; + } + + if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + // If the previous frame have skins or morph targets, note that. + if mesh_instance + .flags + .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN) + { + mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN; + } + if mesh_instance + .flags + .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH) + { + mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH; + } + } + + let pipeline_id = + pipelines.specialize(&pipeline_cache, &pipeline, mesh_key, &mesh.layout); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { @@ -513,16 +848,82 @@ fn queue_wireframe( continue; } }; + + view_specialized_material_pipeline_cache + .insert(*visible_entity, (ticks.this_run(), pipeline_id)); + } + } + + // Delete specialized pipelines belonging to views that have expired. + specialized_material_pipeline_cache + .retain(|retained_view_entity, _| all_views.contains(retained_view_entity)); +} + +fn queue_wireframes( + custom_draw_functions: Res>, + render_meshes: Res>, + render_mesh_instances: Res, + gpu_preprocessing_support: Res, + mesh_allocator: Res, + specialized_wireframe_pipeline_cache: Res, + render_wireframe_instances: Res, + mut wireframe_3d_phases: ResMut>, + mut views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, +) { + for (view, visible_entities, msaa) in &mut views { + let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else { + continue; + }; + let draw_wireframe = custom_draw_functions.read().id::(); + + let Some(view_specialized_material_pipeline_cache) = + specialized_wireframe_pipeline_cache.get(&view.retained_view_entity) + else { + continue; + }; + + for (render_entity, visible_entity) in visible_entities.iter::() { + let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache + .get(visible_entity) + .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id)) + else { + continue; + }; + + // Skip the entity if it's cached in a bin and up to date. + if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) { + continue; + } + + let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) + else { + continue; + }; + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + let bin_key = Wireframe3dBinKey { - color, + asset_id: *wireframe_instance, }; let batch_set_key = Wireframe3dBatchSetKey { - vertex_slab: mesh_instance.vertex_slab, - index_slab: mesh_instance.index_slab, + pipeline: pipeline_id, + draw_function: draw_wireframe, + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, }; - wireframe_phase.add(Wireframe3dBinKey { - color_entity: (), - }); + wireframe_phase.add( + batch_set_key, + bin_key, + (*render_entity, *visible_entity), + InputUniformIndex::default(), + BinnedRenderPhaseType::BatchableMesh, + current_change_tick, + ); } } -} \ No newline at end of file +} diff --git a/examples/3d/wireframe.rs b/examples/3d/wireframe.rs index e62e9f0029537..eedee90f962b6 100644 --- a/examples/3d/wireframe.rs +++ b/examples/3d/wireframe.rs @@ -31,7 +31,7 @@ fn main() { ..default() }), // You need to add this plugin to enable wireframe rendering - WireframePlugin, + WireframePlugin::default(), )) // Wireframes can be configured with this resource. This can be changed at runtime. .insert_resource(WireframeConfig { From 1bf7e8177ab684b6c693a5f3ad6234cb9fd02546 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 14:43:33 -0700 Subject: [PATCH 03/16] Use correct. --- crates/bevy_pbr/src/wireframe.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index d68bf428d1fc1..99eb895769982 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,9 +1,6 @@ use crate::{ - alpha_mode_pipeline_key, DrawMesh, EntitySpecializationTicks, Material, MaterialPipeline, - MaterialPipelineKey, MaterialPlugin, MeshMaterial3d, MeshPipeline, MeshPipelineKey, - PreparedMaterial, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, - RenderMeshInstances, SetMaterialBindGroup, SetMeshBindGroup, SetMeshViewBindGroup, - SpecializedMaterialPipelineCache, ViewKeyCache, ViewSpecializationTicks, + DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstanceFlags, RenderMeshInstances, + SetMeshBindGroup, SetMeshViewBindGroup, ViewKeyCache, ViewSpecializationTicks, }; use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; use bevy_asset::prelude::AssetChanged; @@ -921,7 +918,10 @@ fn queue_wireframes( bin_key, (*render_entity, *visible_entity), InputUniformIndex::default(), - BinnedRenderPhaseType::BatchableMesh, + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), current_change_tick, ); } From d929af26c3f861e9a37cc7fe208f21483379e0a4 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 19:31:16 -0700 Subject: [PATCH 04/16] Fixes. --- crates/bevy_pbr/src/render/wireframe.wgsl | 9 +- crates/bevy_pbr/src/wireframe.rs | 99 ++++++++----------- .../bevy_sprite/src/mesh2d/wireframe2d.wgsl | 4 +- 3 files changed, 50 insertions(+), 62 deletions(-) diff --git a/crates/bevy_pbr/src/render/wireframe.wgsl b/crates/bevy_pbr/src/render/wireframe.wgsl index 3873ffa3dd909..de205e7d5f33a 100644 --- a/crates/bevy_pbr/src/render/wireframe.wgsl +++ b/crates/bevy_pbr/src/render/wireframe.wgsl @@ -1,4 +1,11 @@ -#import bevy_pbr::forward_io::VertexOutput +#import bevy_pbr::{ + mesh_bindings::mesh, + mesh_functions, + skinning, + morph::morph, + forward_io::{Vertex, VertexOutput}, + view_transformations::position_world_to_clip, +} struct PushConstants { color: vec4 diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 99eb895769982..66f8bfc33ab76 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -9,7 +9,6 @@ use bevy_asset::{ Handle, }; use bevy_color::{Color, ColorToComponents}; -use bevy_core_pipeline::core_2d::{AlphaMask2d, Opaque2d, Transparent2d}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_core_pipeline::core_3d::Camera3d; use bevy_derive::{Deref, DerefMut}; @@ -339,23 +338,12 @@ pub type DrawWireframe3d = ( pub struct Wireframe3dPipeline { mesh_pipeline: MeshPipeline, shader: Handle, - layout: BindGroupLayout, } impl FromWorld for Wireframe3dPipeline { fn from_world(render_world: &mut World) -> Self { - let render_device = render_world.resource::(); - let layout = render_device.create_bind_group_layout( - "wireframe_material_bind_group_layout", - &BindGroupLayoutEntries::sequential( - ShaderStages::FRAGMENT, - (storage_buffer_read_only::(false),), - ), - ); - Wireframe3dPipeline { mesh_pipeline: render_world.resource::().clone(), - layout, shader: WIREFRAME_SHADER_HANDLE, } } @@ -371,7 +359,6 @@ impl SpecializedMeshPipeline for Wireframe3dPipeline { ) -> Result { let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; descriptor.label = Some("wireframe_3d_pipeline".into()); - descriptor.layout.push(self.layout.clone()); descriptor.push_constant_ranges.push(PushConstantRange { stages: ShaderStages::FRAGMENT, range: 0..16, @@ -511,6 +498,40 @@ impl RenderAsset for RenderWireframeMaterial { #[derive(Resource, Deref, DerefMut, Default)] pub struct RenderWireframeInstances(MainEntityHashMap>); +#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)] +pub struct WireframeEntitiesNeedingSpecialization { + #[deref] + pub entities: Vec, +} + +#[derive(Resource, Deref, DerefMut, Clone, Debug, Default)] +pub struct WireframeEntitySpecializationTicks { + pub entities: MainEntityHashMap, +} + +/// Stores the [`crate::SpecializedMaterialViewPipelineCache`] for each view. +#[derive(Resource, Deref, DerefMut, Default)] +pub struct SpecializedWireframePipelineCache { + // view entity -> view pipeline cache + #[deref] + map: HashMap, +} + +/// Stores the cached render pipeline ID for each entity in a single view, as +/// well as the last time it was changed. +#[derive(Deref, DerefMut, Default)] +pub struct SpecializedWireframeViewPipelineCache { + // material entity -> (tick, pipeline_id) + #[deref] + map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, +} + +#[derive(Resource)] +struct GlobalWireframeMaterial { + // This handle will be reused when the global config is enabled + handle: Handle, +} + pub fn extract_wireframe_materials( mut material_instances: ResMut, changed_meshes_query: Extract< @@ -524,8 +545,10 @@ pub fn extract_wireframe_materials( ) { for (entity, view_visibility, material) in &changed_meshes_query { if view_visibility.get() { + println!("Adding wireframe material for entity {:?}", entity); material_instances.insert(entity.into(), material.id()); } else { + println!("Removing wireframe material for entity {:?}", entity); material_instances.remove(&MainEntity::from(entity)); } } @@ -538,17 +561,12 @@ pub fn extract_wireframe_materials( // It's possible that a necessary component was removed and re-added in // the same frame. if !changed_meshes_query.contains(entity) { + println!("Removing wireframe material for entity {:?}", entity); material_instances.remove(&MainEntity::from(entity)); } } } -#[derive(Resource)] -struct GlobalWireframeMaterial { - // This handle will be reused when the global config is enabled - handle: Handle, -} - fn setup_global_wireframe_material( mut commands: Commands, mut materials: ResMut>, @@ -698,42 +716,6 @@ pub fn extract_wireframe_entities_needing_specialization( } } -#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)] -pub struct WireframeEntitiesNeedingSpecialization { - #[deref] - pub entities: Vec, -} - -#[derive(Resource, Deref, DerefMut, Clone, Debug)] -pub struct WireframeEntitySpecializationTicks { - pub entities: MainEntityHashMap, -} - -impl Default for WireframeEntitySpecializationTicks { - fn default() -> Self { - Self { - entities: MainEntityHashMap::default(), - } - } -} - -/// Stores the [`crate::SpecializedMaterialViewPipelineCache`] for each view. -#[derive(Resource, Deref, DerefMut, Default)] -pub struct SpecializedWireframePipelineCache { - // view entity -> view pipeline cache - #[deref] - map: HashMap, -} - -/// Stores the cached render pipeline ID for each entity in a single view, as -/// well as the last time it was changed. -#[derive(Deref, DerefMut, Default)] -pub struct SpecializedWireframeViewPipelineCache { - // material entity -> (tick, pipeline_id) - #[deref] - map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, -} - pub fn check_wireframe_entities_needing_specialization( needs_specialization: Query< Entity, @@ -858,7 +840,6 @@ pub fn specialize_wireframes( fn queue_wireframes( custom_draw_functions: Res>, - render_meshes: Res>, render_mesh_instances: Res, gpu_preprocessing_support: Res, mesh_allocator: Res, @@ -884,24 +865,24 @@ fn queue_wireframes( .get(visible_entity) .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id)) else { + println!("No specialized pipeline found for entity {:?}", visible_entity); continue; }; // Skip the entity if it's cached in a bin and up to date. if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) { + println!("Entity {:?} is already cached and up to date.", visible_entity); continue; } let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else { + println!("No wireframe instance found for entity {:?}", visible_entity); continue; }; let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) else { continue; }; - let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { - continue; - }; let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let bin_key = Wireframe3dBinKey { diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl b/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl index fac02d6456a86..92d4ed2f00b13 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl @@ -4,8 +4,8 @@ struct WireframeMaterial { color: vec4, }; -@group(2) @binding(0) var material: WireframeMaterial; +//@group(2) @binding(0) var material: WireframeMaterial; @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { - return material.color; + return vec4(1.0); } From f8a6e93d33359adce26ae2674e8e594b0092f96f Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 20:13:51 -0700 Subject: [PATCH 05/16] Fixes for 3d. --- crates/bevy_pbr/src/wireframe.rs | 122 ++++++++++++++----------------- 1 file changed, 56 insertions(+), 66 deletions(-) diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 66f8bfc33ab76..ddcde6eed5b74 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -3,63 +3,61 @@ use crate::{ SetMeshBindGroup, SetMeshViewBindGroup, ViewKeyCache, ViewSpecializationTicks, }; use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; -use bevy_asset::prelude::AssetChanged; use bevy_asset::{ - load_internal_asset, weak_handle, AsAssetId, Asset, AssetApp, AssetEvents, AssetId, Assets, - Handle, + load_internal_asset, prelude::AssetChanged, weak_handle, AsAssetId, Asset, AssetApp, + AssetEvents, AssetId, Assets, Handle, }; use bevy_color::{Color, ColorToComponents}; -use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; -use bevy_core_pipeline::core_3d::Camera3d; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::component::Tick; -use bevy_ecs::entity::hash_map::EntityHashMap; -use bevy_ecs::prelude::*; -use bevy_ecs::query::QueryItem; -use bevy_ecs::system::lifetimeless::SRes; -use bevy_ecs::system::{SystemChangeTick, SystemParamItem}; -use bevy_math::{FloatOrd, Vec4}; -use bevy_platform_support::collections::{HashMap, HashSet}; -use bevy_platform_support::hash::FixedHasher; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}; -use bevy_render::camera::ExtractedCamera; -use bevy_render::extract_component::UniformComponentPlugin; -use bevy_render::mesh::allocator::{MeshAllocator, SlabId}; -use bevy_render::mesh::{MeshVertexBufferLayout, RenderMesh}; -use bevy_render::render_asset::{ - prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, -}; -use bevy_render::render_graph::{ - NodeRunError, RenderGraphApp, RenderGraphContext, RenderLabel, ViewNode, ViewNodeRunner, +use bevy_core_pipeline::core_3d::{ + graph::{Core3d, Node3d}, + Camera3d, }; -use bevy_render::render_phase::{ - AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, - CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, - PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, - TrackedRenderPass, ViewBinnedRenderPhases, ViewSortedRenderPhases, -}; -use bevy_render::render_resource::binding_types::{ - sampler, storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer, +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + component::Tick, + prelude::*, + query::QueryItem, + system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem}, }; -use bevy_render::renderer::{RenderContext, RenderDevice}; -use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet}; -use bevy_render::view::{ - ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities, - RetainedViewEntity, ViewDepthTexture, ViewTarget, +use bevy_platform_support::{ + collections::{HashMap, HashSet}, + hash::FixedHasher, }; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ + batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, + camera::ExtractedCamera, extract_resource::ExtractResource, - mesh::{Mesh3d, MeshVertexBufferLayoutRef}, + mesh::{ + allocator::{MeshAllocator, SlabId}, + Mesh3d, MeshVertexBufferLayoutRef, RenderMesh, + }, prelude::*, - render_resource::*, + render_asset::{ + prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, + }, + render_graph::{ + NodeRunError, RenderGraphApp, RenderGraphContext, RenderLabel, ViewNode, ViewNodeRunner, + }, + render_phase::{ + AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, + CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, + PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, + SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, + }, + render_resource::{ + *, + }, + renderer::RenderContext, + sync_world::{MainEntity, MainEntityHashMap}, + view::{ + ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities, + RetainedViewEntity, ViewDepthTexture, ViewTarget, + }, Extract, Render, RenderApp, RenderDebugFlags, RenderSet, }; use derive_more::From; -use nonmax::NonMaxU32; -use std::hash::Hash; -use std::marker::PhantomData; -use std::ops::Range; +use std::{hash::Hash, ops::Range}; use tracing::error; pub const WIREFRAME_SHADER_HANDLE: Handle = @@ -88,7 +86,7 @@ impl WireframePlugin { } impl Plugin for WireframePlugin { - fn build(&self, app: &mut bevy_app::App) { + fn build(&self, app: &mut App) { load_internal_asset!( app, WIREFRAME_SHADER_HANDLE, @@ -183,7 +181,7 @@ impl Plugin for WireframePlugin { #[reflect(Component, Default, Debug, PartialEq)] pub struct Wireframe; -struct Wireframe3d { +pub struct Wireframe3d { /// Determines which objects can be placed into a *batch set*. /// /// Objects in a single batch set can potentially be multi-drawn together, @@ -259,10 +257,13 @@ impl BinnedPhaseItem for Wireframe3d { } #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct Wireframe3dBatchSetKey { +pub struct Wireframe3dBatchSetKey { /// The identifier of the render pipeline. pub pipeline: CachedRenderPipelineId, + /// The wireframe material asset ID. + pub asset_id: AssetId, + /// The function used to draw. pub draw_function: DrawFunctionId, /// The ID of the slab of GPU memory that contains vertex data. @@ -545,10 +546,8 @@ pub fn extract_wireframe_materials( ) { for (entity, view_visibility, material) in &changed_meshes_query { if view_visibility.get() { - println!("Adding wireframe material for entity {:?}", entity); material_instances.insert(entity.into(), material.id()); } else { - println!("Removing wireframe material for entity {:?}", entity); material_instances.remove(&MainEntity::from(entity)); } } @@ -561,7 +560,6 @@ pub fn extract_wireframe_materials( // It's possible that a necessary component was removed and re-added in // the same frame. if !changed_meshes_query.contains(entity) { - println!("Removing wireframe material for entity {:?}", entity); material_instances.remove(&MainEntity::from(entity)); } } @@ -737,7 +735,6 @@ pub fn check_wireframe_entities_needing_specialization( pub fn specialize_wireframes( render_meshes: Res>, render_mesh_instances: Res, - render_wireframe_instances: Res, render_visibility_ranges: Res, wireframe_phases: Res>, views: Query<(&ExtractedView, &RenderVisibleEntities)>, @@ -773,9 +770,6 @@ pub fn specialize_wireframes( .or_default(); for (_, visible_entity) in visible_entities.iter::() { - let Some(material_asset_id) = render_wireframe_instances.get(visible_entity) else { - continue; - }; let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); let last_specialized_tick = view_specialized_material_pipeline_cache .get(visible_entity) @@ -846,9 +840,9 @@ fn queue_wireframes( specialized_wireframe_pipeline_cache: Res, render_wireframe_instances: Res, mut wireframe_3d_phases: ResMut>, - mut views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, + mut views: Query<(&ExtractedView, &RenderVisibleEntities)>, ) { - for (view, visible_entities, msaa) in &mut views { + for (view, visible_entities) in &mut views { let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else { continue; }; @@ -861,35 +855,31 @@ fn queue_wireframes( }; for (render_entity, visible_entity) in visible_entities.iter::() { + let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else { + continue; + }; let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache .get(visible_entity) .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id)) else { - println!("No specialized pipeline found for entity {:?}", visible_entity); continue; }; // Skip the entity if it's cached in a bin and up to date. if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) { - println!("Entity {:?} is already cached and up to date.", visible_entity); continue; } - - let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else { - println!("No wireframe instance found for entity {:?}", visible_entity); - continue; - }; let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) else { continue; }; let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - let bin_key = Wireframe3dBinKey { asset_id: *wireframe_instance, }; let batch_set_key = Wireframe3dBatchSetKey { pipeline: pipeline_id, + asset_id: *wireframe_instance, draw_function: draw_wireframe, vertex_slab: vertex_slab.unwrap_or_default(), index_slab, @@ -898,7 +888,7 @@ fn queue_wireframes( batch_set_key, bin_key, (*render_entity, *visible_entity), - InputUniformIndex::default(), + mesh_instance.current_uniform_index, BinnedRenderPhaseType::mesh( mesh_instance.should_batch(), &gpu_preprocessing_support, From d99396121b5fe7e5697fa3cb80fec2799040dc92 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 20:41:35 -0700 Subject: [PATCH 06/16] 2d + fixes. --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 4 + crates/bevy_pbr/src/render/wireframe.wgsl | 9 +- crates/bevy_pbr/src/wireframe.rs | 21 +- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 810 ++++++++++++++++-- .../bevy_sprite/src/mesh2d/wireframe2d.wgsl | 13 +- examples/2d/2d_shapes.rs | 2 +- examples/2d/wireframe_2d.rs | 2 +- examples/3d/3d_shapes.rs | 2 +- 8 files changed, 744 insertions(+), 119 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 3261ef5a6167f..0a6af32963563 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -19,6 +19,8 @@ pub mod graph { MainOpaquePass, MainTransparentPass, EndMainPass, + StartDebugPass, + EndDebugPass, Bloom, PostProcessing, Tonemapping, @@ -109,6 +111,8 @@ impl Plugin for Core2dPlugin { Node2d::MainTransparentPass, ) .add_render_graph_node::(Core2d, Node2d::EndMainPass) + .add_render_graph_node::(Core2d, Node2d::StartDebugPass) + .add_render_graph_node::(Core2d, Node2d::EndDebugPass) .add_render_graph_node::>(Core2d, Node2d::Tonemapping) .add_render_graph_node::(Core2d, Node2d::EndMainPassPostProcessing) .add_render_graph_node::>(Core2d, Node2d::Upscaling) diff --git a/crates/bevy_pbr/src/render/wireframe.wgsl b/crates/bevy_pbr/src/render/wireframe.wgsl index de205e7d5f33a..3873ffa3dd909 100644 --- a/crates/bevy_pbr/src/render/wireframe.wgsl +++ b/crates/bevy_pbr/src/render/wireframe.wgsl @@ -1,11 +1,4 @@ -#import bevy_pbr::{ - mesh_bindings::mesh, - mesh_functions, - skinning, - morph::morph, - forward_io::{Vertex, VertexOutput}, - view_transformations::position_world_to_clip, -} +#import bevy_pbr::forward_io::VertexOutput struct PushConstants { color: vec4 diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index ddcde6eed5b74..1e16b4c64d7a4 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -3,10 +3,7 @@ use crate::{ SetMeshBindGroup, SetMeshViewBindGroup, ViewKeyCache, ViewSpecializationTicks, }; use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; -use bevy_asset::{ - load_internal_asset, prelude::AssetChanged, weak_handle, AsAssetId, Asset, AssetApp, - AssetEvents, AssetId, Assets, Handle, -}; +use bevy_asset::{load_internal_asset, prelude::AssetChanged, weak_handle, AsAssetId, Asset, AssetApp, AssetEvents, AssetId, Assets, Handle, UntypedAssetId}; use bevy_color::{Color, ColorToComponents}; use bevy_core_pipeline::core_3d::{ graph::{Core3d, Node3d}, @@ -41,7 +38,7 @@ use bevy_render::{ }, render_phase::{ AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, - CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, + CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, }, @@ -262,7 +259,7 @@ pub struct Wireframe3dBatchSetKey { pub pipeline: CachedRenderPipelineId, /// The wireframe material asset ID. - pub asset_id: AssetId, + pub asset_id: UntypedAssetId, /// The function used to draw. pub draw_function: DrawFunctionId, @@ -289,8 +286,8 @@ impl PhaseItemBatchSetKey for Wireframe3dBatchSetKey { /// Note that a *batch set* (if multi-draw is in use) contains multiple batches. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Wireframe3dBinKey { - /// The wireframe material asset ID. - pub asset_id: AssetId, + /// The wireframe mesh asset ID. + pub asset_id: UntypedAssetId, } pub struct SetWireframe3dPushConstants; @@ -735,6 +732,7 @@ pub fn check_wireframe_entities_needing_specialization( pub fn specialize_wireframes( render_meshes: Res>, render_mesh_instances: Res, + render_wireframe_instances: Res, render_visibility_ranges: Res, wireframe_phases: Res>, views: Query<(&ExtractedView, &RenderVisibleEntities)>, @@ -770,6 +768,9 @@ pub fn specialize_wireframes( .or_default(); for (_, visible_entity) in visible_entities.iter::() { + if !render_wireframe_instances.contains_key(visible_entity) { + continue; + }; let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); let last_specialized_tick = view_specialized_material_pipeline_cache .get(visible_entity) @@ -875,11 +876,11 @@ fn queue_wireframes( }; let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let bin_key = Wireframe3dBinKey { - asset_id: *wireframe_instance, + asset_id: mesh_instance.mesh_asset_id.untyped(), }; let batch_set_key = Wireframe3dBatchSetKey { pipeline: pipeline_id, - asset_id: *wireframe_instance, + asset_id: wireframe_instance.untyped(), draw_function: draw_wireframe, vertex_slab: vertex_slab.unwrap_or_default(), index_slab, diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 439dd9a6435c7..fa68fb56b9b29 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -1,18 +1,64 @@ -use crate::{Material2d, Material2dKey, Material2dPlugin, Mesh2d}; -use bevy_app::{Plugin, Startup, Update}; -use bevy_asset::{load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle}; -use bevy_color::{Color, LinearRgba}; -use bevy_ecs::prelude::*; +use crate::{ + DrawMesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, SetMesh2dBindGroup, + SetMesh2dViewBindGroup, ViewKeyCache, ViewSpecializationTicks, +}; +use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; +use bevy_asset::{ + load_internal_asset, prelude::AssetChanged, weak_handle, AsAssetId, Asset, AssetApp, + AssetEvents, AssetId, Assets, Handle, UntypedAssetId, +}; +use bevy_color::{Color, ColorToComponents}; +use bevy_core_pipeline::core_2d::{ + graph::{Core2d, Node2d}, + Camera2d, +}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + component::Tick, + prelude::*, + query::QueryItem, + system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem}, +}; +use bevy_platform_support::{ + collections::{HashMap, HashSet}, + hash::FixedHasher, +}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ - extract_resource::ExtractResource, mesh::MeshVertexBufferLayoutRef, prelude::*, + batching::gpu_preprocessing::GpuPreprocessingMode, + camera::ExtractedCamera, + extract_resource::ExtractResource, + mesh::{ + allocator::{MeshAllocator, SlabId}, + Mesh2d, MeshVertexBufferLayoutRef, RenderMesh, + }, + prelude::*, + render_asset::{ + prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, + }, + render_graph::{ + NodeRunError, RenderGraphApp, RenderGraphContext, RenderLabel, ViewNode, ViewNodeRunner, + }, + render_phase::{ + AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, + CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, + PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, + SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, + }, render_resource::*, + renderer::RenderContext, + sync_world::{MainEntity, MainEntityHashMap}, + view::{ + ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget, + }, + Extract, Render, RenderApp, RenderDebugFlags, RenderSet, }; - -use super::MeshMaterial2d; +use derive_more::From; +use std::{hash::Hash, ops::Range}; +use tracing::error; pub const WIREFRAME_2D_SHADER_HANDLE: Handle = - weak_handle!("3d8a3853-2927-4de2-9dc7-3971e7e40970"); + weak_handle!("2d8a3853-2927-4de2-9dc7-3971e7e40970"); /// A [`Plugin`] that draws wireframes for 2D meshes. /// @@ -24,9 +70,20 @@ pub const WIREFRAME_2D_SHADER_HANDLE: Handle = /// /// This is a native only feature. #[derive(Debug, Default)] -pub struct Wireframe2dPlugin; +pub struct Wireframe2dPlugin { + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, +} + +impl Wireframe2dPlugin { + /// Creates a new [`Wireframe2dPlugin`] with the given debug flags. + pub fn new(debug_flags: RenderDebugFlags) -> Self { + Self { debug_flags } + } +} + impl Plugin for Wireframe2dPlugin { - fn build(&self, app: &mut bevy_app::App) { + fn build(&self, app: &mut App) { load_internal_asset!( app, WIREFRAME_2D_SHADER_HANDLE, @@ -34,25 +91,83 @@ impl Plugin for Wireframe2dPlugin { Shader::from_wgsl ); - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_resource::() - .add_plugins(Material2dPlugin::::default()) - .register_asset_reflect::() - .add_systems(Startup, setup_global_wireframe_material) + app.add_plugins(( + BinnedRenderPhasePlugin::::new(self.debug_flags), + RenderAssetPlugin::::default(), + )) + .init_asset::() + .init_resource::>() + .register_type::() + .register_type::() + .register_type::() + .init_resource::() + .init_resource::() + .add_systems(Startup, setup_global_wireframe_material) + .add_systems( + Update, + ( + global_color_changed.run_if(resource_changed::), + wireframe_color_changed, + // Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global + // wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed. + (apply_wireframe_material, apply_global_wireframe_material).chain(), + ), + ) + .add_systems( + PostUpdate, + check_wireframe_entities_needing_specialization + .after(AssetEvents) + .run_if(resource_exists::), + ); + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .init_resource::() + .init_resource::() + .init_resource::>() + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_render_graph_node::>(Core2d, Wireframe2dLabel) + .add_render_graph_edges( + Core2d, + ( + Node2d::StartDebugPass, + Wireframe2dLabel, + Node2d::EndDebugPass, + ), + ) + .add_systems( + ExtractSchedule, + ( + extract_wireframe_2d_camera, + extract_wireframe_entities_needing_specialization, + extract_wireframe_materials, + ), + ) .add_systems( - Update, + Render, ( - global_color_changed.run_if(resource_changed::), - wireframe_color_changed, - // Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global - // wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed. - (apply_wireframe_material, apply_global_wireframe_material).chain(), + specialize_wireframes + .in_set(RenderSet::PrepareMeshes) + .after(prepare_assets::) + .after(prepare_assets::), + queue_wireframes + .in_set(RenderSet::QueueMeshes) + .after(prepare_assets::), ), ); } + + fn finish(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + render_app.init_resource::(); + } } /// Enables wireframe rendering for any entity it is attached to. @@ -60,9 +175,251 @@ impl Plugin for Wireframe2dPlugin { /// /// This requires the [`Wireframe2dPlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default, Debug, PartialEq, Clone)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct Wireframe2d; +pub struct Wireframe2dPhaseItem { + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: Wireframe2dBatchSetKey, + /// The key, which determines which can be batched. + pub bin_key: Wireframe2dBinKey, + /// An entity from which data will be fetched, including the mesh if + /// applicable. + pub representative_entity: (Entity, MainEntity), + /// The ranges of instances. + pub batch_range: Range, + /// An extra index, which is either a dynamic offset or an index in the + /// indirect parameters list. + pub extra_index: PhaseItemExtraIndex, +} + +impl PhaseItem for Wireframe2dPhaseItem { + fn entity(&self) -> Entity { + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 + } + + fn draw_function(&self) -> DrawFunctionId { + self.batch_set_key.draw_function + } + + fn batch_range(&self) -> &Range { + &self.batch_range + } + + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + fn extra_index(&self) -> PhaseItemExtraIndex { + self.extra_index.clone() + } + + fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range, &mut PhaseItemExtraIndex) { + (&mut self.batch_range, &mut self.extra_index) + } +} + +impl CachedRenderPipelinePhaseItem for Wireframe2dPhaseItem { + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.batch_set_key.pipeline + } +} + +impl BinnedPhaseItem for Wireframe2dPhaseItem { + type BinKey = Wireframe2dBinKey; + type BatchSetKey = Wireframe2dBatchSetKey; + + fn new( + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, + representative_entity: (Entity, MainEntity), + batch_range: Range, + extra_index: PhaseItemExtraIndex, + ) -> Self { + Self { + batch_set_key, + bin_key, + representative_entity, + batch_range, + extra_index, + } + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Wireframe2dBatchSetKey { + /// The identifier of the render pipeline. + pub pipeline: CachedRenderPipelineId, + + /// The wireframe material asset ID. + pub asset_id: UntypedAssetId, + + /// The function used to draw. + pub draw_function: DrawFunctionId, + /// The ID of the slab of GPU memory that contains vertex data. + /// + /// For non-mesh items, you can fill this with 0 if your items can be + /// multi-drawn, or with a unique value if they can't. + pub vertex_slab: SlabId, + + /// The ID of the slab of GPU memory that contains index data, if present. + /// + /// For non-mesh items, you can safely fill this with `None`. + pub index_slab: Option, +} + +impl PhaseItemBatchSetKey for Wireframe2dBatchSetKey { + fn indexed(&self) -> bool { + self.index_slab.is_some() + } +} + +/// Data that must be identical in order to *batch* phase items together. +/// +/// Note that a *batch set* (if multi-draw is in use) contains multiple batches. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Wireframe2dBinKey { + /// The wireframe mesh asset ID. + pub asset_id: UntypedAssetId, +} + +pub struct SetWireframe2dPushConstants; + +impl RenderCommand

for SetWireframe2dPushConstants { + type Param = ( + SRes, + SRes>, + ); + type ViewQuery = (); + type ItemQuery = (); + + #[inline] + fn render<'w>( + item: &P, + _view: (), + _item_query: Option<()>, + (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else { + return RenderCommandResult::Failure("No wireframe material found for entity"); + }; + let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else { + return RenderCommandResult::Failure("No wireframe material found for entity"); + }; + + pass.set_push_constants( + ShaderStages::FRAGMENT, + 0, + bytemuck::bytes_of(&wireframe_material.color), + ); + RenderCommandResult::Success + } +} + +pub type DrawWireframe2d = ( + SetItemPipeline, + SetMesh2dViewBindGroup<0>, + SetMesh2dBindGroup<1>, + SetWireframe2dPushConstants, + DrawMesh2d, +); + +#[derive(Resource, Clone)] +pub struct Wireframe2dPipeline { + mesh_pipeline: Mesh2dPipeline, + shader: Handle, +} + +impl FromWorld for Wireframe2dPipeline { + fn from_world(render_world: &mut World) -> Self { + Wireframe2dPipeline { + mesh_pipeline: render_world.resource::().clone(), + shader: WIREFRAME_2D_SHADER_HANDLE, + } + } +} + +impl SpecializedMeshPipeline for Wireframe2dPipeline { + type Key = Mesh2dPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayoutRef, + ) -> Result { + let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; + descriptor.label = Some("wireframe_2d_pipeline".into()); + descriptor.push_constant_ranges.push(PushConstantRange { + stages: ShaderStages::FRAGMENT, + range: 0..16, + }); + let fragment = descriptor.fragment.as_mut().unwrap(); + fragment.shader = self.shader.clone(); + descriptor.primitive.polygon_mode = PolygonMode::Line; + descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; + Ok(descriptor) + } +} + +#[derive(RenderLabel, Debug, Clone, Hash, PartialEq, Eq)] +struct Wireframe2dLabel; + +#[derive(Default)] +struct Wireframe2dNode; +impl ViewNode for Wireframe2dNode { + type ViewQuery = ( + &'static ExtractedCamera, + &'static ExtractedView, + &'static ViewTarget, + &'static ViewDepthTexture, + ); + + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + (camera, view, target, depth): QueryItem<'w, Self::ViewQuery>, + world: &'w World, + ) -> Result<(), NodeRunError> { + let Some(wireframe_phase) = + world.get_resource::>() + else { + return Ok(()); + }; + + let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else { + return Ok(()); + }; + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("wireframe_2d_pass"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), + timestamp_writes: None, + occlusion_query_set: None, + }); + + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } + + if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) { + error!("Error encountered while rendering the stencil phase {err:?}"); + return Err(NodeRunError::DrawError(err)); + } + + Ok(()) + } +} + /// Sets the color of the [`Wireframe2d`] of the entity it is attached to. /// /// If this component is present but there's no [`Wireframe2d`] component, @@ -70,23 +427,28 @@ pub struct Wireframe2d; /// /// This overrides the [`Wireframe2dConfig::default_color`]. #[derive(Component, Debug, Clone, Default, Reflect)] -#[reflect(Component, Default, Debug, Clone)] +#[reflect(Component, Default, Debug)] pub struct Wireframe2dColor { pub color: Color, } +#[derive(Component, Debug, Clone, Default)] +pub struct ExtractedWireframeColor { + pub color: [f32; 4], +} + /// Disables wireframe rendering for any entity it is attached to. /// It will ignore the [`Wireframe2dConfig`] global setting. /// /// This requires the [`Wireframe2dPlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default, Debug, PartialEq, Clone)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct NoWireframe2d; #[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] -#[reflect(Resource, Debug, Default, Clone)] +#[reflect(Resource, Debug, Default)] pub struct Wireframe2dConfig { - /// Whether to show wireframes for all 2D meshes. + /// Whether to show wireframes for all meshes. /// Can be overridden for individual meshes by adding a [`Wireframe2d`] or [`NoWireframe2d`] component. pub global: bool, /// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe2d`] component attached to it will have @@ -95,19 +457,119 @@ pub struct Wireframe2dConfig { pub default_color: Color, } +#[derive(Asset, Reflect, Clone, Debug, Default)] +#[reflect(Clone, Default)] +pub struct Wireframe2dMaterial { + pub color: Color, +} + +pub struct RenderWireframeMaterial { + pub color: [f32; 4], +} + +#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] +#[reflect(Component, Default, Clone, PartialEq)] +pub struct Mesh2dWireframe(pub Handle); + +impl AsAssetId for Mesh2dWireframe { + type Asset = Wireframe2dMaterial; + + fn as_asset_id(&self) -> AssetId { + self.0.id() + } +} + +impl RenderAsset for RenderWireframeMaterial { + type SourceAsset = Wireframe2dMaterial; + type Param = (); + + fn prepare_asset( + source_asset: Self::SourceAsset, + _asset_id: AssetId, + _param: &mut SystemParamItem, + ) -> std::result::Result> { + Ok(RenderWireframeMaterial { + color: source_asset.color.to_linear().to_f32_array(), + }) + } +} + +#[derive(Resource, Deref, DerefMut, Default)] +pub struct RenderWireframeInstances(MainEntityHashMap>); + +#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)] +pub struct WireframeEntitiesNeedingSpecialization { + #[deref] + pub entities: Vec, +} + +#[derive(Resource, Deref, DerefMut, Clone, Debug, Default)] +pub struct WireframeEntitySpecializationTicks { + pub entities: MainEntityHashMap, +} + +/// Stores the [`crate::SpecializedMaterialViewPipelineCache`] for each view. +#[derive(Resource, Deref, DerefMut, Default)] +pub struct SpecializedWireframePipelineCache { + // view entity -> view pipeline cache + #[deref] + map: HashMap, +} + +/// Stores the cached render pipeline ID for each entity in a single view, as +/// well as the last time it was changed. +#[derive(Deref, DerefMut, Default)] +pub struct SpecializedWireframeViewPipelineCache { + // material entity -> (tick, pipeline_id) + #[deref] + map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, +} + #[derive(Resource)] -struct GlobalWireframe2dMaterial { +struct GlobalWireframeMaterial { // This handle will be reused when the global config is enabled handle: Handle, } +pub fn extract_wireframe_materials( + mut material_instances: ResMut, + changed_meshes_query: Extract< + Query< + (Entity, &ViewVisibility, &Mesh2dWireframe), + Or<(Changed, Changed)>, + >, + >, + mut removed_visibilities_query: Extract>, + mut removed_materials_query: Extract>, +) { + for (entity, view_visibility, material) in &changed_meshes_query { + if view_visibility.get() { + material_instances.insert(entity.into(), material.id()); + } else { + material_instances.remove(&MainEntity::from(entity)); + } + } + + for entity in removed_visibilities_query + .read() + .chain(removed_materials_query.read()) + { + // Only queue a mesh for removal if we didn't pick it up above. + // It's possible that a necessary component was removed and re-added in + // the same frame. + if !changed_meshes_query.contains(entity) { + material_instances.remove(&MainEntity::from(entity)); + } + } +} + fn setup_global_wireframe_material( mut commands: Commands, mut materials: ResMut>, config: Res, ) { // Create the handle used for the global material - commands.insert_resource(GlobalWireframe2dMaterial { + commands.insert_resource(GlobalWireframeMaterial { handle: materials.add(Wireframe2dMaterial { color: config.default_color.into(), }), @@ -118,7 +580,7 @@ fn setup_global_wireframe_material( fn global_color_changed( config: Res, mut materials: ResMut>, - global_material: Res, + global_material: Res, ) { if let Some(global_material) = materials.get_mut(&global_material.handle) { global_material.color = config.default_color.into(); @@ -129,7 +591,7 @@ fn global_color_changed( fn wireframe_color_changed( mut materials: ResMut>, mut colors_changed: Query< - (&mut MeshMaterial2d, &Wireframe2dColor), + (&mut Mesh2dWireframe, &Wireframe2dColor), (With, Changed), >, ) { @@ -147,100 +609,264 @@ fn apply_wireframe_material( mut materials: ResMut>, wireframes: Query< (Entity, Option<&Wireframe2dColor>), - ( - With, - Without>, - ), - >, - no_wireframes: Query< - Entity, - ( - With, - With>, - ), + (With, Without), >, + no_wireframes: Query, With)>, mut removed_wireframes: RemovedComponents, - global_material: Res, + global_material: Res, ) { for e in removed_wireframes.read().chain(no_wireframes.iter()) { if let Ok(mut commands) = commands.get_entity(e) { - commands.remove::>(); + commands.remove::(); } } - let mut wireframes_to_spawn = vec![]; - for (e, wireframe_color) in &wireframes { - let material = if let Some(wireframe_color) = wireframe_color { - materials.add(Wireframe2dMaterial { - color: wireframe_color.color.into(), - }) - } else { - // If there's no color specified we can use the global material since it's already set to use the default_color - global_material.handle.clone() - }; - wireframes_to_spawn.push((e, MeshMaterial2d(material))); + let mut material_to_spawn = vec![]; + for (e, maybe_color) in &wireframes { + let material = get_wireframe_material(maybe_color, &mut materials, &global_material); + material_to_spawn.push((e, Mesh2dWireframe(material))); } - commands.try_insert_batch(wireframes_to_spawn); + commands.try_insert_batch(material_to_spawn); } -type Wireframe2dFilter = (With, Without, Without); +type WireframeFilter = (With, Without, Without); /// Applies or removes a wireframe material on any mesh without a [`Wireframe2d`] or [`NoWireframe2d`] component. fn apply_global_wireframe_material( mut commands: Commands, config: Res, meshes_without_material: Query< - Entity, - ( - Wireframe2dFilter, - Without>, - ), - >, - meshes_with_global_material: Query< - Entity, - (Wireframe2dFilter, With>), + (Entity, Option<&Wireframe2dColor>), + (WireframeFilter, Without), >, - global_material: Res, + meshes_with_global_material: Query)>, + global_material: Res, + mut materials: ResMut>, ) { if config.global { let mut material_to_spawn = vec![]; - for e in &meshes_without_material { + for (e, maybe_color) in &meshes_without_material { + let material = get_wireframe_material(maybe_color, &mut materials, &global_material); // We only add the material handle but not the Wireframe component // This makes it easy to detect which mesh is using the global material and which ones are user specified - material_to_spawn.push((e, MeshMaterial2d(global_material.handle.clone()))); + material_to_spawn.push((e, Mesh2dWireframe(material))); } commands.try_insert_batch(material_to_spawn); } else { for e in &meshes_with_global_material { - commands - .entity(e) - .remove::>(); + commands.entity(e).remove::(); } } } -#[derive(Default, AsBindGroup, Debug, Clone, Asset, Reflect)] -#[reflect(Clone, Default)] -pub struct Wireframe2dMaterial { - #[uniform(0)] - pub color: LinearRgba, +/// Gets a handle to a wireframe material with a fallback on the default material +fn get_wireframe_material( + maybe_color: Option<&Wireframe2dColor>, + wireframe_materials: &mut Assets, + global_material: &GlobalWireframeMaterial, +) -> Handle { + if let Some(wireframe_color) = maybe_color { + wireframe_materials.add(Wireframe2dMaterial { + color: wireframe_color.color.into(), + }) + } else { + // If there's no color specified we can use the global material since it's already set to use the default_color + global_material.handle.clone() + } +} + +fn extract_wireframe_2d_camera( + mut wireframe_2d_phases: ResMut>, + cameras: Extract>>, + mut live_entities: Local>, +) { + live_entities.clear(); + for (main_entity, camera) in &cameras { + if !camera.is_active { + continue; + } + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + wireframe_2d_phases.prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None); + live_entities.insert(retained_view_entity); + } + + // Clear out all dead views. + wireframe_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); } -impl Material2d for Wireframe2dMaterial { - fn fragment_shader() -> ShaderRef { - WIREFRAME_2D_SHADER_HANDLE.into() +pub fn extract_wireframe_entities_needing_specialization( + entities_needing_specialization: Extract>, + mut entity_specialization_ticks: ResMut, + ticks: SystemChangeTick, +) { + for entity in entities_needing_specialization.iter() { + // Update the entity's specialization tick with this run's tick + entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); } +} - fn depth_bias(&self) -> f32 { - 1.0 +pub fn check_wireframe_entities_needing_specialization( + needs_specialization: Query< + Entity, + Or<( + Changed, + AssetChanged, + Changed, + AssetChanged, + )>, + >, + mut entities_needing_specialization: ResMut, +) { + entities_needing_specialization.clear(); + for entity in &needs_specialization { + entities_needing_specialization.push(entity); } +} - fn specialize( - descriptor: &mut RenderPipelineDescriptor, - _layout: &MeshVertexBufferLayoutRef, - _key: Material2dKey, - ) -> Result<(), SpecializedMeshPipelineError> { - descriptor.primitive.polygon_mode = PolygonMode::Line; - Ok(()) +pub fn specialize_wireframes( + render_meshes: Res>, + render_mesh_instances: Res, + render_wireframe_instances: Res, + wireframe_phases: Res>, + views: Query<(&ExtractedView, &RenderVisibleEntities)>, + view_key_cache: Res, + entity_specialization_ticks: Res, + view_specialization_ticks: Res, + mut specialized_material_pipeline_cache: ResMut, + mut pipelines: ResMut>, + pipeline: Res, + pipeline_cache: Res, + ticks: SystemChangeTick, +) { + // Record the retained IDs of all shadow views so that we can expire old + // pipeline IDs. + let mut all_views: HashSet = HashSet::default(); + + for (view, visible_entities) in &views { + all_views.insert(view.retained_view_entity); + + if !wireframe_phases.contains_key(&view.retained_view_entity) { + continue; + } + + let Some(view_key) = view_key_cache.get(&view.retained_view_entity.main_entity) else { + continue; + }; + + let view_tick = view_specialization_ticks + .get(&view.retained_view_entity.main_entity) + .unwrap(); + let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache + .entry(view.retained_view_entity) + .or_default(); + + for (_, visible_entity) in visible_entities.iter::() { + if !render_wireframe_instances.contains_key(visible_entity) { + continue; + }; + let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); + let last_specialized_tick = view_specialized_material_pipeline_cache + .get(visible_entity) + .map(|(tick, _)| *tick); + let needs_specialization = last_specialized_tick.is_none_or(|tick| { + view_tick.is_newer_than(tick, ticks.this_run()) + || entity_tick.is_newer_than(tick, ticks.this_run()) + }); + if !needs_specialization { + continue; + } + let Some(mesh_instance) = render_mesh_instances.get(visible_entity) else { + continue; + }; + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + + let mut mesh_key = *view_key; + mesh_key |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology()); + + let pipeline_id = + pipelines.specialize(&pipeline_cache, &pipeline, mesh_key, &mesh.layout); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + + view_specialized_material_pipeline_cache + .insert(*visible_entity, (ticks.this_run(), pipeline_id)); + } + } + + // Delete specialized pipelines belonging to views that have expired. + specialized_material_pipeline_cache + .retain(|retained_view_entity, _| all_views.contains(retained_view_entity)); +} + +fn queue_wireframes( + custom_draw_functions: Res>, + render_mesh_instances: Res, + mesh_allocator: Res, + specialized_wireframe_pipeline_cache: Res, + render_wireframe_instances: Res, + mut wireframe_2d_phases: ResMut>, + mut views: Query<(&ExtractedView, &RenderVisibleEntities)>, +) { + for (view, visible_entities) in &mut views { + let Some(wireframe_phase) = wireframe_2d_phases.get_mut(&view.retained_view_entity) else { + continue; + }; + let draw_wireframe = custom_draw_functions.read().id::(); + + let Some(view_specialized_material_pipeline_cache) = + specialized_wireframe_pipeline_cache.get(&view.retained_view_entity) + else { + continue; + }; + + for (render_entity, visible_entity) in visible_entities.iter::() { + let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else { + continue; + }; + let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache + .get(visible_entity) + .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id)) + else { + continue; + }; + + // Skip the entity if it's cached in a bin and up to date. + if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) { + continue; + } + let Some(mesh_instance) = render_mesh_instances.get(visible_entity) else { + continue; + }; + let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + let bin_key = Wireframe2dBinKey { + asset_id: mesh_instance.mesh_asset_id.untyped(), + }; + let batch_set_key = Wireframe2dBatchSetKey { + pipeline: pipeline_id, + asset_id: wireframe_instance.untyped(), + draw_function: draw_wireframe, + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }; + wireframe_phase.add( + batch_set_key, + bin_key, + (*render_entity, *visible_entity), + InputUniformIndex::default(), + if mesh_instance.automatic_batching { + BinnedRenderPhaseType::BatchableMesh + } else { + BinnedRenderPhaseType::UnbatchableMesh + }, + current_change_tick, + ); + } } } diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl b/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl index 92d4ed2f00b13..3873ffa3dd909 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl @@ -1,11 +1,12 @@ -#import bevy_sprite::mesh2d_vertex_output::VertexOutput +#import bevy_pbr::forward_io::VertexOutput -struct WireframeMaterial { - color: vec4, -}; +struct PushConstants { + color: vec4 +} + +var push_constants: PushConstants; -//@group(2) @binding(0) var material: WireframeMaterial; @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { - return vec4(1.0); + return push_constants.color; } diff --git a/examples/2d/2d_shapes.rs b/examples/2d/2d_shapes.rs index bde8e7bb8c041..dbdd846ebaff6 100644 --- a/examples/2d/2d_shapes.rs +++ b/examples/2d/2d_shapes.rs @@ -12,7 +12,7 @@ fn main() { app.add_plugins(( DefaultPlugins, #[cfg(not(target_arch = "wasm32"))] - Wireframe2dPlugin, + Wireframe2dPlugin::default(), )) .add_systems(Startup, setup); #[cfg(not(target_arch = "wasm32"))] diff --git a/examples/2d/wireframe_2d.rs b/examples/2d/wireframe_2d.rs index fe4cc261870dd..aac599fcb0dbe 100644 --- a/examples/2d/wireframe_2d.rs +++ b/examples/2d/wireframe_2d.rs @@ -31,7 +31,7 @@ fn main() { ..default() }), // You need to add this plugin to enable wireframe rendering - Wireframe2dPlugin, + Wireframe2dPlugin::default(), )) // Wireframes can be configured with this resource. This can be changed at runtime. .insert_resource(Wireframe2dConfig { diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 0346abd3526d5..2ef3a65dc3719 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -22,7 +22,7 @@ fn main() { .add_plugins(( DefaultPlugins.set(ImagePlugin::default_nearest()), #[cfg(not(target_arch = "wasm32"))] - WireframePlugin, + WireframePlugin::default(), )) .add_systems(Startup, setup) .add_systems( From e21702731e6ee1d9c94c567e1c499d366cf56a39 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 20:42:00 -0700 Subject: [PATCH 07/16] Fmt. --- crates/bevy_pbr/src/wireframe.rs | 9 +++++---- crates/bevy_render/src/render_phase/mod.rs | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 1e16b4c64d7a4..0a1febb6b58be 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -3,7 +3,10 @@ use crate::{ SetMeshBindGroup, SetMeshViewBindGroup, ViewKeyCache, ViewSpecializationTicks, }; use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; -use bevy_asset::{load_internal_asset, prelude::AssetChanged, weak_handle, AsAssetId, Asset, AssetApp, AssetEvents, AssetId, Assets, Handle, UntypedAssetId}; +use bevy_asset::{ + load_internal_asset, prelude::AssetChanged, weak_handle, AsAssetId, Asset, AssetApp, + AssetEvents, AssetId, Assets, Handle, UntypedAssetId, +}; use bevy_color::{Color, ColorToComponents}; use bevy_core_pipeline::core_3d::{ graph::{Core3d, Node3d}, @@ -42,9 +45,7 @@ use bevy_render::{ PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, }, - render_resource::{ - *, - }, + render_resource::*, renderer::RenderContext, sync_world::{MainEntity, MainEntityHashMap}, view::{ diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 312261a6c0b0d..fae06f0c60dea 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -420,7 +420,6 @@ impl ViewBinnedRenderPhases where BPI: BinnedPhaseItem, { - pub fn prepare_for_new_frame( &mut self, retained_view_entity: RetainedViewEntity, From e00fbb131a3e9b7919044f0d4f5e5b0fe84c8460 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 20:49:34 -0700 Subject: [PATCH 08/16] Fix 2d shader import. --- crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl b/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl index 3873ffa3dd909..c7bb3aa791b18 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl @@ -1,4 +1,4 @@ -#import bevy_pbr::forward_io::VertexOutput +#import bevy_sprite::mesh2d_vertex_output::VertexOutput struct PushConstants { color: vec4 From a28ad6898de965452d7f93de0b2e0175749e61fa Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 20:51:07 -0700 Subject: [PATCH 09/16] Add missing edges. --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 0a6af32963563..bae442571b273 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -123,6 +123,8 @@ impl Plugin for Core2dPlugin { Node2d::MainOpaquePass, Node2d::MainTransparentPass, Node2d::EndMainPass, + Node2d::StartDebugPass, + Node2d::EndDebugPass, Node2d::Tonemapping, Node2d::EndMainPassPostProcessing, Node2d::Upscaling, From dabd22df94d0a6d1a8b5d1db6075c043c3ac0616 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 21:22:45 -0700 Subject: [PATCH 10/16] Ci. --- crates/bevy_pbr/src/wireframe.rs | 6 +++--- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 0a1febb6b58be..442e57b54b491 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -508,7 +508,7 @@ pub struct WireframeEntitySpecializationTicks { pub entities: MainEntityHashMap, } -/// Stores the [`crate::SpecializedMaterialViewPipelineCache`] for each view. +/// Stores the [`crate::SpecializedWireframeViewPipelineCache`] for each view. #[derive(Resource, Deref, DerefMut, Default)] pub struct SpecializedWireframePipelineCache { // view entity -> view pipeline cache @@ -571,7 +571,7 @@ fn setup_global_wireframe_material( // Create the handle used for the global material commands.insert_resource(GlobalWireframeMaterial { handle: materials.add(WireframeMaterial { - color: config.default_color.into(), + color: config.default_color, }), }); } @@ -583,7 +583,7 @@ fn global_color_changed( global_material: Res, ) { if let Some(global_material) = materials.get_mut(&global_material.handle) { - global_material.color = config.default_color.into(); + global_material.color = config.default_color; } } diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index fa68fb56b9b29..0a56ed24a6815 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -508,7 +508,7 @@ pub struct WireframeEntitySpecializationTicks { pub entities: MainEntityHashMap, } -/// Stores the [`crate::SpecializedMaterialViewPipelineCache`] for each view. +/// Stores the [`crate::SpecializedWireframeViewPipelineCache`] for each view. #[derive(Resource, Deref, DerefMut, Default)] pub struct SpecializedWireframePipelineCache { // view entity -> view pipeline cache @@ -571,7 +571,7 @@ fn setup_global_wireframe_material( // Create the handle used for the global material commands.insert_resource(GlobalWireframeMaterial { handle: materials.add(Wireframe2dMaterial { - color: config.default_color.into(), + color: config.default_color, }), }); } @@ -583,7 +583,7 @@ fn global_color_changed( global_material: Res, ) { if let Some(global_material) = materials.get_mut(&global_material.handle) { - global_material.color = config.default_color.into(); + global_material.color = config.default_color; } } From f4dc838e1baf3bcac92c3d8110771142204ea098 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 21:34:52 -0700 Subject: [PATCH 11/16] Ci. --- crates/bevy_pbr/src/wireframe.rs | 11 +++++------ crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 442e57b54b491..c59b1884b7bf2 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -54,8 +54,7 @@ use bevy_render::{ }, Extract, Render, RenderApp, RenderDebugFlags, RenderSet, }; -use derive_more::From; -use std::{hash::Hash, ops::Range}; +use core::{hash::Hash, ops::Range}; use tracing::error; pub const WIREFRAME_SHADER_HANDLE: Handle = @@ -467,7 +466,7 @@ pub struct RenderWireframeMaterial { pub color: [f32; 4], } -#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] +#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)] #[reflect(Component, Default, Clone, PartialEq)] pub struct Mesh3dWireframe(pub Handle); @@ -487,7 +486,7 @@ impl RenderAsset for RenderWireframeMaterial { source_asset: Self::SourceAsset, _asset_id: AssetId, _param: &mut SystemParamItem, - ) -> std::result::Result> { + ) -> Result> { Ok(RenderWireframeMaterial { color: source_asset.color.to_linear().to_f32_array(), }) @@ -597,7 +596,7 @@ fn wireframe_color_changed( ) { for (mut handle, wireframe_color) in &mut colors_changed { handle.0 = materials.add(WireframeMaterial { - color: wireframe_color.color.into(), + color: wireframe_color.color, }); } } @@ -667,7 +666,7 @@ fn get_wireframe_material( ) -> Handle { if let Some(wireframe_color) = maybe_color { wireframe_materials.add(WireframeMaterial { - color: wireframe_color.color.into(), + color: wireframe_color.color, }) } else { // If there's no color specified we can use the global material since it's already set to use the default_color diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 0a56ed24a6815..c852dbd43ffdc 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -53,8 +53,7 @@ use bevy_render::{ }, Extract, Render, RenderApp, RenderDebugFlags, RenderSet, }; -use derive_more::From; -use std::{hash::Hash, ops::Range}; +use core::{hash::Hash, ops::Range}; use tracing::error; pub const WIREFRAME_2D_SHADER_HANDLE: Handle = @@ -467,7 +466,7 @@ pub struct RenderWireframeMaterial { pub color: [f32; 4], } -#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] +#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)] #[reflect(Component, Default, Clone, PartialEq)] pub struct Mesh2dWireframe(pub Handle); @@ -487,7 +486,7 @@ impl RenderAsset for RenderWireframeMaterial { source_asset: Self::SourceAsset, _asset_id: AssetId, _param: &mut SystemParamItem, - ) -> std::result::Result> { + ) -> Result> { Ok(RenderWireframeMaterial { color: source_asset.color.to_linear().to_f32_array(), }) @@ -597,7 +596,7 @@ fn wireframe_color_changed( ) { for (mut handle, wireframe_color) in &mut colors_changed { handle.0 = materials.add(Wireframe2dMaterial { - color: wireframe_color.color.into(), + color: wireframe_color.color, }); } } @@ -667,7 +666,7 @@ fn get_wireframe_material( ) -> Handle { if let Some(wireframe_color) = maybe_color { wireframe_materials.add(Wireframe2dMaterial { - color: wireframe_color.color.into(), + color: wireframe_color.color, }) } else { // If there's no color specified we can use the global material since it's already set to use the default_color From 19b9e247994015de6a47940431f63a5004224f8b Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 27 Mar 2025 21:50:59 -0700 Subject: [PATCH 12/16] Ci. --- crates/bevy_pbr/src/wireframe.rs | 2 +- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index c59b1884b7bf2..343fdae3f92c8 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -507,7 +507,7 @@ pub struct WireframeEntitySpecializationTicks { pub entities: MainEntityHashMap, } -/// Stores the [`crate::SpecializedWireframeViewPipelineCache`] for each view. +/// Stores the [`SpecializedWireframeViewPipelineCache`] for each view. #[derive(Resource, Deref, DerefMut, Default)] pub struct SpecializedWireframePipelineCache { // view entity -> view pipeline cache diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index c852dbd43ffdc..39aec8d9531ad 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -507,7 +507,7 @@ pub struct WireframeEntitySpecializationTicks { pub entities: MainEntityHashMap, } -/// Stores the [`crate::SpecializedWireframeViewPipelineCache`] for each view. +/// Stores the [`SpecializedWireframeViewPipelineCache`] for each view. #[derive(Resource, Deref, DerefMut, Default)] pub struct SpecializedWireframePipelineCache { // view entity -> view pipeline cache From 10ce11f33384735319e6a311a9285e6432e672d5 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 31 Mar 2025 22:08:31 -0700 Subject: [PATCH 13/16] Fix whitespace removal. --- crates/bevy_core_pipeline/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index e6a300cc7ad25..9e046142760a9 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -23,6 +23,7 @@ pub mod prepass; mod skybox; pub mod tonemapping; pub mod upscaling; + pub use skybox::Skybox; /// The core pipeline prelude. From 97ceb4bd27f9b523a4b97bb6c6f56fd6d833791e Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 7 Apr 2025 11:58:34 -0700 Subject: [PATCH 14/16] Remove debug pass. --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 7 +------ crates/bevy_core_pipeline/src/core_3d/mod.rs | 7 +------ crates/bevy_pbr/src/wireframe.rs | 15 +++++---------- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 15 +++++---------- 4 files changed, 12 insertions(+), 32 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index bae442571b273..9b951ae2d1692 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -19,8 +19,7 @@ pub mod graph { MainOpaquePass, MainTransparentPass, EndMainPass, - StartDebugPass, - EndDebugPass, + Wireframe, Bloom, PostProcessing, Tonemapping, @@ -111,8 +110,6 @@ impl Plugin for Core2dPlugin { Node2d::MainTransparentPass, ) .add_render_graph_node::(Core2d, Node2d::EndMainPass) - .add_render_graph_node::(Core2d, Node2d::StartDebugPass) - .add_render_graph_node::(Core2d, Node2d::EndDebugPass) .add_render_graph_node::>(Core2d, Node2d::Tonemapping) .add_render_graph_node::(Core2d, Node2d::EndMainPassPostProcessing) .add_render_graph_node::>(Core2d, Node2d::Upscaling) @@ -123,8 +120,6 @@ impl Plugin for Core2dPlugin { Node2d::MainOpaquePass, Node2d::MainTransparentPass, Node2d::EndMainPass, - Node2d::StartDebugPass, - Node2d::EndDebugPass, Node2d::Tonemapping, Node2d::EndMainPassPostProcessing, Node2d::Upscaling, diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 8808f2c8713a5..5444ed05ecbda 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -28,8 +28,7 @@ pub mod graph { MainTransmissivePass, MainTransparentPass, EndMainPass, - StartDebugPass, - EndDebugPass, + Wireframe, LateDownsampleDepth, Taa, MotionBlur, @@ -210,8 +209,6 @@ impl Plugin for Core3dPlugin { Node3d::MainTransparentPass, ) .add_render_graph_node::(Core3d, Node3d::EndMainPass) - .add_render_graph_node::(Core3d, Node3d::StartDebugPass) - .add_render_graph_node::(Core3d, Node3d::EndDebugPass) .add_render_graph_node::>(Core3d, Node3d::DepthOfField) .add_render_graph_node::>(Core3d, Node3d::Tonemapping) .add_render_graph_node::(Core3d, Node3d::EndMainPassPostProcessing) @@ -230,8 +227,6 @@ impl Plugin for Core3dPlugin { Node3d::MainTransmissivePass, Node3d::MainTransparentPass, Node3d::EndMainPass, - Node3d::StartDebugPass, - Node3d::EndDebugPass, Node3d::Tonemapping, Node3d::EndMainPassPostProcessing, Node3d::Upscaling, diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 343fdae3f92c8..8428c41ae485f 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -36,9 +36,7 @@ use bevy_render::{ render_asset::{ prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, }, - render_graph::{ - NodeRunError, RenderGraphApp, RenderGraphContext, RenderLabel, ViewNode, ViewNodeRunner, - }, + render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, render_phase::{ AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, @@ -131,13 +129,13 @@ impl Plugin for WireframePlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_render_graph_node::>(Core3d, Wireframe3dLabel) + .add_render_graph_node::>(Core3d, Node3d::Wireframe) .add_render_graph_edges( Core3d, ( - Node3d::StartDebugPass, - Wireframe3dLabel, - Node3d::EndDebugPass, + Node3d::EndMainPass, + Node3d::Wireframe, + Node3d::PostProcessing, ), ) .add_systems( @@ -369,9 +367,6 @@ impl SpecializedMeshPipeline for Wireframe3dPipeline { } } -#[derive(RenderLabel, Debug, Clone, Hash, PartialEq, Eq)] -struct Wireframe3dLabel; - #[derive(Default)] struct Wireframe3dNode; impl ViewNode for Wireframe3dNode { diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 39aec8d9531ad..2fde191320351 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -36,9 +36,7 @@ use bevy_render::{ render_asset::{ prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, }, - render_graph::{ - NodeRunError, RenderGraphApp, RenderGraphContext, RenderLabel, ViewNode, ViewNodeRunner, - }, + render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, render_phase::{ AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, @@ -130,13 +128,13 @@ impl Plugin for Wireframe2dPlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_render_graph_node::>(Core2d, Wireframe2dLabel) + .add_render_graph_node::>(Core2d, Node2d::Wireframe) .add_render_graph_edges( Core2d, ( - Node2d::StartDebugPass, - Wireframe2dLabel, - Node2d::EndDebugPass, + Node2d::EndMainPass, + Node2d::Wireframe, + Node2d::PostProcessing, ), ) .add_systems( @@ -368,9 +366,6 @@ impl SpecializedMeshPipeline for Wireframe2dPipeline { } } -#[derive(RenderLabel, Debug, Clone, Hash, PartialEq, Eq)] -struct Wireframe2dLabel; - #[derive(Default)] struct Wireframe2dNode; impl ViewNode for Wireframe2dNode { From d04f038f5f7a4c2cedf2c1c2e370cb866c324152 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 7 Apr 2025 12:00:31 -0700 Subject: [PATCH 15/16] Incorrect comment. --- crates/bevy_pbr/src/wireframe.rs | 2 +- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 8428c41ae485f..c39ee79cd3be3 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -740,7 +740,7 @@ pub fn specialize_wireframes( pipeline_cache: Res, ticks: SystemChangeTick, ) { - // Record the retained IDs of all shadow views so that we can expire old + // Record the retained IDs of all views so that we can expire old // pipeline IDs. let mut all_views: HashSet = HashSet::default(); diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 2fde191320351..3d20fceafc300 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -732,7 +732,7 @@ pub fn specialize_wireframes( pipeline_cache: Res, ticks: SystemChangeTick, ) { - // Record the retained IDs of all shadow views so that we can expire old + // Record the retained IDs of all views so that we can expire old // pipeline IDs. let mut all_views: HashSet = HashSet::default(); From 65e898eb406750852508a05f64b066a8abb336bb Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 7 Apr 2025 12:21:58 -0700 Subject: [PATCH 16/16] Evict from caches. --- crates/bevy_pbr/src/wireframe.rs | 16 +++++++++++++++- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 13 +++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index c39ee79cd3be3..fa4e920539348 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -24,6 +24,7 @@ use bevy_platform_support::{ hash::FixedHasher, }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::camera::extract_cameras; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::ExtractedCamera, @@ -142,7 +143,7 @@ impl Plugin for WireframePlugin { ExtractSchedule, ( extract_wireframe_3d_camera, - extract_wireframe_entities_needing_specialization, + extract_wireframe_entities_needing_specialization.after(extract_cameras), extract_wireframe_materials, ), ) @@ -698,12 +699,25 @@ fn extract_wireframe_3d_camera( pub fn extract_wireframe_entities_needing_specialization( entities_needing_specialization: Extract>, mut entity_specialization_ticks: ResMut, + views: Query<&ExtractedView>, + mut specialized_wireframe_pipeline_cache: ResMut, + mut removed_meshes_query: Extract>, ticks: SystemChangeTick, ) { for entity in entities_needing_specialization.iter() { // Update the entity's specialization tick with this run's tick entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); } + + for entity in removed_meshes_query.read() { + for view in &views { + if let Some(specialized_wireframe_pipeline_cache) = + specialized_wireframe_pipeline_cache.get_mut(&view.retained_view_entity) + { + specialized_wireframe_pipeline_cache.remove(&MainEntity::from(entity)); + } + } + } } pub fn check_wireframe_entities_needing_specialization( diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 3d20fceafc300..0aef1777655c1 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -691,12 +691,25 @@ fn extract_wireframe_2d_camera( pub fn extract_wireframe_entities_needing_specialization( entities_needing_specialization: Extract>, mut entity_specialization_ticks: ResMut, + views: Query<&ExtractedView>, + mut specialized_wireframe_pipeline_cache: ResMut, + mut removed_meshes_query: Extract>, ticks: SystemChangeTick, ) { for entity in entities_needing_specialization.iter() { // Update the entity's specialization tick with this run's tick entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); } + + for entity in removed_meshes_query.read() { + for view in &views { + if let Some(specialized_wireframe_pipeline_cache) = + specialized_wireframe_pipeline_cache.get_mut(&view.retained_view_entity) + { + specialized_wireframe_pipeline_cache.remove(&MainEntity::from(entity)); + } + } + } } pub fn check_wireframe_entities_needing_specialization(