Path: blob/main/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs
6849 views
use crate::{1init_mesh_2d_pipeline, DrawMesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances,2SetMesh2dBindGroup, SetMesh2dViewBindGroup, ViewKeyCache, ViewSpecializationTicks,3};4use bevy_app::{App, Plugin, PostUpdate, Startup, Update};5use bevy_asset::{6embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp,7AssetEventSystems, AssetId, AssetServer, Assets, Handle, UntypedAssetId,8};9use bevy_camera::{visibility::ViewVisibility, Camera, Camera2d};10use bevy_color::{Color, ColorToComponents};11use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d};12use bevy_derive::{Deref, DerefMut};13use bevy_ecs::{14component::Tick,15prelude::*,16query::QueryItem,17system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem},18};19use bevy_mesh::{Mesh2d, MeshVertexBufferLayoutRef};20use bevy_platform::{21collections::{HashMap, HashSet},22hash::FixedHasher,23};24use bevy_reflect::{std_traits::ReflectDefault, Reflect};25use bevy_render::{26batching::gpu_preprocessing::GpuPreprocessingMode,27camera::ExtractedCamera,28diagnostic::RecordDiagnostics,29extract_resource::ExtractResource,30mesh::{31allocator::{MeshAllocator, SlabId},32RenderMesh,33},34prelude::*,35render_asset::{36prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets,37},38render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner},39render_phase::{40AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType,41CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem,42PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,43SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases,44},45render_resource::*,46renderer::RenderContext,47sync_world::{MainEntity, MainEntityHashMap},48view::{49ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget,50},51Extract, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems,52};53use bevy_shader::Shader;54use core::{hash::Hash, ops::Range};55use tracing::error;5657/// A [`Plugin`] that draws wireframes for 2D meshes.58///59/// Wireframes currently do not work when using webgl or webgpu.60/// Supported rendering backends:61/// - DX1262/// - Vulkan63/// - Metal64///65/// This is a native only feature.66#[derive(Debug, Default)]67pub struct Wireframe2dPlugin {68/// Debugging flags that can optionally be set when constructing the renderer.69pub debug_flags: RenderDebugFlags,70}7172impl Wireframe2dPlugin {73/// Creates a new [`Wireframe2dPlugin`] with the given debug flags.74pub fn new(debug_flags: RenderDebugFlags) -> Self {75Self { debug_flags }76}77}7879impl Plugin for Wireframe2dPlugin {80fn build(&self, app: &mut App) {81embedded_asset!(app, "wireframe2d.wgsl");8283app.add_plugins((84BinnedRenderPhasePlugin::<Wireframe2dPhaseItem, Mesh2dPipeline>::new(self.debug_flags),85RenderAssetPlugin::<RenderWireframeMaterial>::default(),86))87.init_asset::<Wireframe2dMaterial>()88.init_resource::<SpecializedMeshPipelines<Wireframe2dPipeline>>()89.init_resource::<Wireframe2dConfig>()90.init_resource::<WireframeEntitiesNeedingSpecialization>()91.add_systems(Startup, setup_global_wireframe_material)92.add_systems(93Update,94(95global_color_changed.run_if(resource_changed::<Wireframe2dConfig>),96wireframe_color_changed,97// Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global98// wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed.99(apply_wireframe_material, apply_global_wireframe_material).chain(),100),101)102.add_systems(103PostUpdate,104check_wireframe_entities_needing_specialization105.after(AssetEventSystems)106.run_if(resource_exists::<Wireframe2dConfig>),107);108109let Some(render_app) = app.get_sub_app_mut(RenderApp) else {110return;111};112113render_app114.init_resource::<WireframeEntitySpecializationTicks>()115.init_resource::<SpecializedWireframePipelineCache>()116.init_resource::<DrawFunctions<Wireframe2dPhaseItem>>()117.add_render_command::<Wireframe2dPhaseItem, DrawWireframe2d>()118.init_resource::<RenderWireframeInstances>()119.init_resource::<SpecializedMeshPipelines<Wireframe2dPipeline>>()120.add_render_graph_node::<ViewNodeRunner<Wireframe2dNode>>(Core2d, Node2d::Wireframe)121.add_render_graph_edges(122Core2d,123(124Node2d::EndMainPass,125Node2d::Wireframe,126Node2d::PostProcessing,127),128)129.add_systems(130RenderStartup,131init_wireframe_2d_pipeline.after(init_mesh_2d_pipeline),132)133.add_systems(134ExtractSchedule,135(136extract_wireframe_2d_camera,137extract_wireframe_entities_needing_specialization,138extract_wireframe_materials,139),140)141.add_systems(142Render,143(144specialize_wireframes145.in_set(RenderSystems::PrepareMeshes)146.after(prepare_assets::<RenderWireframeMaterial>)147.after(prepare_assets::<RenderMesh>),148queue_wireframes149.in_set(RenderSystems::QueueMeshes)150.after(prepare_assets::<RenderWireframeMaterial>),151),152);153}154}155156/// Enables wireframe rendering for any entity it is attached to.157/// It will ignore the [`Wireframe2dConfig`] global setting.158///159/// This requires the [`Wireframe2dPlugin`] to be enabled.160#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]161#[reflect(Component, Default, Debug, PartialEq)]162pub struct Wireframe2d;163164pub struct Wireframe2dPhaseItem {165/// Determines which objects can be placed into a *batch set*.166///167/// Objects in a single batch set can potentially be multi-drawn together,168/// if it's enabled and the current platform supports it.169pub batch_set_key: Wireframe2dBatchSetKey,170/// The key, which determines which can be batched.171pub bin_key: Wireframe2dBinKey,172/// An entity from which data will be fetched, including the mesh if173/// applicable.174pub representative_entity: (Entity, MainEntity),175/// The ranges of instances.176pub batch_range: Range<u32>,177/// An extra index, which is either a dynamic offset or an index in the178/// indirect parameters list.179pub extra_index: PhaseItemExtraIndex,180}181182impl PhaseItem for Wireframe2dPhaseItem {183fn entity(&self) -> Entity {184self.representative_entity.0185}186187fn main_entity(&self) -> MainEntity {188self.representative_entity.1189}190191fn draw_function(&self) -> DrawFunctionId {192self.batch_set_key.draw_function193}194195fn batch_range(&self) -> &Range<u32> {196&self.batch_range197}198199fn batch_range_mut(&mut self) -> &mut Range<u32> {200&mut self.batch_range201}202203fn extra_index(&self) -> PhaseItemExtraIndex {204self.extra_index.clone()205}206207fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {208(&mut self.batch_range, &mut self.extra_index)209}210}211212impl CachedRenderPipelinePhaseItem for Wireframe2dPhaseItem {213fn cached_pipeline(&self) -> CachedRenderPipelineId {214self.batch_set_key.pipeline215}216}217218impl BinnedPhaseItem for Wireframe2dPhaseItem {219type BinKey = Wireframe2dBinKey;220type BatchSetKey = Wireframe2dBatchSetKey;221222fn new(223batch_set_key: Self::BatchSetKey,224bin_key: Self::BinKey,225representative_entity: (Entity, MainEntity),226batch_range: Range<u32>,227extra_index: PhaseItemExtraIndex,228) -> Self {229Self {230batch_set_key,231bin_key,232representative_entity,233batch_range,234extra_index,235}236}237}238239#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]240pub struct Wireframe2dBatchSetKey {241/// The identifier of the render pipeline.242pub pipeline: CachedRenderPipelineId,243244/// The wireframe material asset ID.245pub asset_id: UntypedAssetId,246247/// The function used to draw.248pub draw_function: DrawFunctionId,249/// The ID of the slab of GPU memory that contains vertex data.250///251/// For non-mesh items, you can fill this with 0 if your items can be252/// multi-drawn, or with a unique value if they can't.253pub vertex_slab: SlabId,254255/// The ID of the slab of GPU memory that contains index data, if present.256///257/// For non-mesh items, you can safely fill this with `None`.258pub index_slab: Option<SlabId>,259}260261impl PhaseItemBatchSetKey for Wireframe2dBatchSetKey {262fn indexed(&self) -> bool {263self.index_slab.is_some()264}265}266267/// Data that must be identical in order to *batch* phase items together.268///269/// Note that a *batch set* (if multi-draw is in use) contains multiple batches.270#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]271pub struct Wireframe2dBinKey {272/// The wireframe mesh asset ID.273pub asset_id: UntypedAssetId,274}275276pub struct SetWireframe2dPushConstants;277278impl<P: PhaseItem> RenderCommand<P> for SetWireframe2dPushConstants {279type Param = (280SRes<RenderWireframeInstances>,281SRes<RenderAssets<RenderWireframeMaterial>>,282);283type ViewQuery = ();284type ItemQuery = ();285286#[inline]287fn render<'w>(288item: &P,289_view: (),290_item_query: Option<()>,291(wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,292pass: &mut TrackedRenderPass<'w>,293) -> RenderCommandResult {294let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {295return RenderCommandResult::Failure("No wireframe material found for entity");296};297let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {298return RenderCommandResult::Failure("No wireframe material found for entity");299};300301pass.set_push_constants(302ShaderStages::FRAGMENT,3030,304bytemuck::bytes_of(&wireframe_material.color),305);306RenderCommandResult::Success307}308}309310pub type DrawWireframe2d = (311SetItemPipeline,312SetMesh2dViewBindGroup<0>,313SetMesh2dBindGroup<1>,314SetWireframe2dPushConstants,315DrawMesh2d,316);317318#[derive(Resource, Clone)]319pub struct Wireframe2dPipeline {320mesh_pipeline: Mesh2dPipeline,321shader: Handle<Shader>,322}323324pub fn init_wireframe_2d_pipeline(325mut commands: Commands,326mesh_2d_pipeline: Res<Mesh2dPipeline>,327asset_server: Res<AssetServer>,328) {329commands.insert_resource(Wireframe2dPipeline {330mesh_pipeline: mesh_2d_pipeline.clone(),331shader: load_embedded_asset!(asset_server.as_ref(), "wireframe2d.wgsl"),332});333}334335impl SpecializedMeshPipeline for Wireframe2dPipeline {336type Key = Mesh2dPipelineKey;337338fn specialize(339&self,340key: Self::Key,341layout: &MeshVertexBufferLayoutRef,342) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {343let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;344descriptor.label = Some("wireframe_2d_pipeline".into());345descriptor.push_constant_ranges.push(PushConstantRange {346stages: ShaderStages::FRAGMENT,347range: 0..16,348});349let fragment = descriptor.fragment.as_mut().unwrap();350fragment.shader = self.shader.clone();351descriptor.primitive.polygon_mode = PolygonMode::Line;352descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;353Ok(descriptor)354}355}356357#[derive(Default)]358struct Wireframe2dNode;359impl ViewNode for Wireframe2dNode {360type ViewQuery = (361&'static ExtractedCamera,362&'static ExtractedView,363&'static ViewTarget,364&'static ViewDepthTexture,365);366367fn run<'w>(368&self,369graph: &mut RenderGraphContext,370render_context: &mut RenderContext<'w>,371(camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>,372world: &'w World,373) -> Result<(), NodeRunError> {374let Some(wireframe_phase) =375world.get_resource::<ViewBinnedRenderPhases<Wireframe2dPhaseItem>>()376else {377return Ok(());378};379380let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else {381return Ok(());382};383384let diagnostics = render_context.diagnostic_recorder();385386let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {387label: Some("wireframe_2d"),388color_attachments: &[Some(target.get_color_attachment())],389depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),390timestamp_writes: None,391occlusion_query_set: None,392});393let pass_span = diagnostics.pass_span(&mut render_pass, "wireframe_2d");394395if let Some(viewport) = camera.viewport.as_ref() {396render_pass.set_camera_viewport(viewport);397}398399if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) {400error!("Error encountered while rendering the stencil phase {err:?}");401return Err(NodeRunError::DrawError(err));402}403404pass_span.end(&mut render_pass);405406Ok(())407}408}409410/// Sets the color of the [`Wireframe2d`] of the entity it is attached to.411///412/// If this component is present but there's no [`Wireframe2d`] component,413/// it will still affect the color of the wireframe when [`Wireframe2dConfig::global`] is set to true.414///415/// This overrides the [`Wireframe2dConfig::default_color`].416#[derive(Component, Debug, Clone, Default, Reflect)]417#[reflect(Component, Default, Debug)]418pub struct Wireframe2dColor {419pub color: Color,420}421422#[derive(Component, Debug, Clone, Default)]423pub struct ExtractedWireframeColor {424pub color: [f32; 4],425}426427/// Disables wireframe rendering for any entity it is attached to.428/// It will ignore the [`Wireframe2dConfig`] global setting.429///430/// This requires the [`Wireframe2dPlugin`] to be enabled.431#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]432#[reflect(Component, Default, Debug, PartialEq)]433pub struct NoWireframe2d;434435#[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)]436#[reflect(Resource, Debug, Default)]437pub struct Wireframe2dConfig {438/// Whether to show wireframes for all meshes.439/// Can be overridden for individual meshes by adding a [`Wireframe2d`] or [`NoWireframe2d`] component.440pub global: bool,441/// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe2d`] component attached to it will have442/// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe2d`],443/// but no [`Wireframe2dColor`].444pub default_color: Color,445}446447#[derive(Asset, Reflect, Clone, Debug, Default)]448#[reflect(Clone, Default)]449pub struct Wireframe2dMaterial {450pub color: Color,451}452453pub struct RenderWireframeMaterial {454pub color: [f32; 4],455}456457#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)]458#[reflect(Component, Default, Clone, PartialEq)]459pub struct Mesh2dWireframe(pub Handle<Wireframe2dMaterial>);460461impl AsAssetId for Mesh2dWireframe {462type Asset = Wireframe2dMaterial;463464fn as_asset_id(&self) -> AssetId<Self::Asset> {465self.0.id()466}467}468469impl RenderAsset for RenderWireframeMaterial {470type SourceAsset = Wireframe2dMaterial;471type Param = ();472473fn prepare_asset(474source_asset: Self::SourceAsset,475_asset_id: AssetId<Self::SourceAsset>,476_param: &mut SystemParamItem<Self::Param>,477_previous_asset: Option<&Self>,478) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {479Ok(RenderWireframeMaterial {480color: source_asset.color.to_linear().to_f32_array(),481})482}483}484485#[derive(Resource, Deref, DerefMut, Default)]486pub struct RenderWireframeInstances(MainEntityHashMap<AssetId<Wireframe2dMaterial>>);487488#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)]489pub struct WireframeEntitiesNeedingSpecialization {490#[deref]491pub entities: Vec<Entity>,492}493494#[derive(Resource, Deref, DerefMut, Clone, Debug, Default)]495pub struct WireframeEntitySpecializationTicks {496pub entities: MainEntityHashMap<Tick>,497}498499/// Stores the [`SpecializedWireframeViewPipelineCache`] for each view.500#[derive(Resource, Deref, DerefMut, Default)]501pub struct SpecializedWireframePipelineCache {502// view entity -> view pipeline cache503#[deref]504map: HashMap<RetainedViewEntity, SpecializedWireframeViewPipelineCache>,505}506507/// Stores the cached render pipeline ID for each entity in a single view, as508/// well as the last time it was changed.509#[derive(Deref, DerefMut, Default)]510pub struct SpecializedWireframeViewPipelineCache {511// material entity -> (tick, pipeline_id)512#[deref]513map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,514}515516#[derive(Resource)]517struct GlobalWireframeMaterial {518// This handle will be reused when the global config is enabled519handle: Handle<Wireframe2dMaterial>,520}521522pub fn extract_wireframe_materials(523mut material_instances: ResMut<RenderWireframeInstances>,524changed_meshes_query: Extract<525Query<526(Entity, &ViewVisibility, &Mesh2dWireframe),527Or<(Changed<ViewVisibility>, Changed<Mesh2dWireframe>)>,528>,529>,530mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,531mut removed_materials_query: Extract<RemovedComponents<Mesh2dWireframe>>,532) {533for (entity, view_visibility, material) in &changed_meshes_query {534if view_visibility.get() {535material_instances.insert(entity.into(), material.id());536} else {537material_instances.remove(&MainEntity::from(entity));538}539}540541for entity in removed_visibilities_query542.read()543.chain(removed_materials_query.read())544{545// Only queue a mesh for removal if we didn't pick it up above.546// It's possible that a necessary component was removed and re-added in547// the same frame.548if !changed_meshes_query.contains(entity) {549material_instances.remove(&MainEntity::from(entity));550}551}552}553554fn setup_global_wireframe_material(555mut commands: Commands,556mut materials: ResMut<Assets<Wireframe2dMaterial>>,557config: Res<Wireframe2dConfig>,558) {559// Create the handle used for the global material560commands.insert_resource(GlobalWireframeMaterial {561handle: materials.add(Wireframe2dMaterial {562color: config.default_color,563}),564});565}566567/// Updates the wireframe material of all entities without a [`Wireframe2dColor`] or without a [`Wireframe2d`] component568fn global_color_changed(569config: Res<Wireframe2dConfig>,570mut materials: ResMut<Assets<Wireframe2dMaterial>>,571global_material: Res<GlobalWireframeMaterial>,572) {573if let Some(global_material) = materials.get_mut(&global_material.handle) {574global_material.color = config.default_color;575}576}577578/// Updates the wireframe material when the color in [`Wireframe2dColor`] changes579fn wireframe_color_changed(580mut materials: ResMut<Assets<Wireframe2dMaterial>>,581mut colors_changed: Query<582(&mut Mesh2dWireframe, &Wireframe2dColor),583(With<Wireframe2d>, Changed<Wireframe2dColor>),584>,585) {586for (mut handle, wireframe_color) in &mut colors_changed {587handle.0 = materials.add(Wireframe2dMaterial {588color: wireframe_color.color,589});590}591}592593/// Applies or remove the wireframe material to any mesh with a [`Wireframe2d`] component, and removes it594/// for any mesh with a [`NoWireframe2d`] component.595fn apply_wireframe_material(596mut commands: Commands,597mut materials: ResMut<Assets<Wireframe2dMaterial>>,598wireframes: Query<599(Entity, Option<&Wireframe2dColor>),600(With<Wireframe2d>, Without<Mesh2dWireframe>),601>,602no_wireframes: Query<Entity, (With<NoWireframe2d>, With<Mesh2dWireframe>)>,603mut removed_wireframes: RemovedComponents<Wireframe2d>,604global_material: Res<GlobalWireframeMaterial>,605) {606for e in removed_wireframes.read().chain(no_wireframes.iter()) {607if let Ok(mut commands) = commands.get_entity(e) {608commands.remove::<Mesh2dWireframe>();609}610}611612let mut material_to_spawn = vec![];613for (e, maybe_color) in &wireframes {614let material = get_wireframe_material(maybe_color, &mut materials, &global_material);615material_to_spawn.push((e, Mesh2dWireframe(material)));616}617commands.try_insert_batch(material_to_spawn);618}619620type WireframeFilter = (With<Mesh2d>, Without<Wireframe2d>, Without<NoWireframe2d>);621622/// Applies or removes a wireframe material on any mesh without a [`Wireframe2d`] or [`NoWireframe2d`] component.623fn apply_global_wireframe_material(624mut commands: Commands,625config: Res<Wireframe2dConfig>,626meshes_without_material: Query<627(Entity, Option<&Wireframe2dColor>),628(WireframeFilter, Without<Mesh2dWireframe>),629>,630meshes_with_global_material: Query<Entity, (WireframeFilter, With<Mesh2dWireframe>)>,631global_material: Res<GlobalWireframeMaterial>,632mut materials: ResMut<Assets<Wireframe2dMaterial>>,633) {634if config.global {635let mut material_to_spawn = vec![];636for (e, maybe_color) in &meshes_without_material {637let material = get_wireframe_material(maybe_color, &mut materials, &global_material);638// We only add the material handle but not the Wireframe component639// This makes it easy to detect which mesh is using the global material and which ones are user specified640material_to_spawn.push((e, Mesh2dWireframe(material)));641}642commands.try_insert_batch(material_to_spawn);643} else {644for e in &meshes_with_global_material {645commands.entity(e).remove::<Mesh2dWireframe>();646}647}648}649650/// Gets a handle to a wireframe material with a fallback on the default material651fn get_wireframe_material(652maybe_color: Option<&Wireframe2dColor>,653wireframe_materials: &mut Assets<Wireframe2dMaterial>,654global_material: &GlobalWireframeMaterial,655) -> Handle<Wireframe2dMaterial> {656if let Some(wireframe_color) = maybe_color {657wireframe_materials.add(Wireframe2dMaterial {658color: wireframe_color.color,659})660} else {661// If there's no color specified we can use the global material since it's already set to use the default_color662global_material.handle.clone()663}664}665666fn extract_wireframe_2d_camera(667mut wireframe_2d_phases: ResMut<ViewBinnedRenderPhases<Wireframe2dPhaseItem>>,668cameras: Extract<Query<(Entity, &Camera), With<Camera2d>>>,669mut live_entities: Local<HashSet<RetainedViewEntity>>,670) {671live_entities.clear();672for (main_entity, camera) in &cameras {673if !camera.is_active {674continue;675}676let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0);677wireframe_2d_phases.prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None);678live_entities.insert(retained_view_entity);679}680681// Clear out all dead views.682wireframe_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));683}684685pub fn extract_wireframe_entities_needing_specialization(686entities_needing_specialization: Extract<Res<WireframeEntitiesNeedingSpecialization>>,687mut entity_specialization_ticks: ResMut<WireframeEntitySpecializationTicks>,688views: Query<&ExtractedView>,689mut specialized_wireframe_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,690mut removed_meshes_query: Extract<RemovedComponents<Mesh2d>>,691ticks: SystemChangeTick,692) {693for entity in entities_needing_specialization.iter() {694// Update the entity's specialization tick with this run's tick695entity_specialization_ticks.insert((*entity).into(), ticks.this_run());696}697698for entity in removed_meshes_query.read() {699for view in &views {700if let Some(specialized_wireframe_pipeline_cache) =701specialized_wireframe_pipeline_cache.get_mut(&view.retained_view_entity)702{703specialized_wireframe_pipeline_cache.remove(&MainEntity::from(entity));704}705}706}707}708709pub fn check_wireframe_entities_needing_specialization(710needs_specialization: Query<711Entity,712Or<(713Changed<Mesh2d>,714AssetChanged<Mesh2d>,715Changed<Mesh2dWireframe>,716AssetChanged<Mesh2dWireframe>,717)>,718>,719mut entities_needing_specialization: ResMut<WireframeEntitiesNeedingSpecialization>,720) {721entities_needing_specialization.clear();722for entity in &needs_specialization {723entities_needing_specialization.push(entity);724}725}726727pub fn specialize_wireframes(728render_meshes: Res<RenderAssets<RenderMesh>>,729render_mesh_instances: Res<RenderMesh2dInstances>,730render_wireframe_instances: Res<RenderWireframeInstances>,731wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe2dPhaseItem>>,732views: Query<(&ExtractedView, &RenderVisibleEntities)>,733view_key_cache: Res<ViewKeyCache>,734entity_specialization_ticks: Res<WireframeEntitySpecializationTicks>,735view_specialization_ticks: Res<ViewSpecializationTicks>,736mut specialized_material_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,737mut pipelines: ResMut<SpecializedMeshPipelines<Wireframe2dPipeline>>,738pipeline: Res<Wireframe2dPipeline>,739pipeline_cache: Res<PipelineCache>,740ticks: SystemChangeTick,741) {742// Record the retained IDs of all views so that we can expire old743// pipeline IDs.744let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();745746for (view, visible_entities) in &views {747all_views.insert(view.retained_view_entity);748749if !wireframe_phases.contains_key(&view.retained_view_entity) {750continue;751}752753let Some(view_key) = view_key_cache.get(&view.retained_view_entity.main_entity) else {754continue;755};756757let view_tick = view_specialization_ticks758.get(&view.retained_view_entity.main_entity)759.unwrap();760let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache761.entry(view.retained_view_entity)762.or_default();763764for (_, visible_entity) in visible_entities.iter::<Mesh2d>() {765if !render_wireframe_instances.contains_key(visible_entity) {766continue;767};768let Some(mesh_instance) = render_mesh_instances.get(visible_entity) else {769continue;770};771let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();772let last_specialized_tick = view_specialized_material_pipeline_cache773.get(visible_entity)774.map(|(tick, _)| *tick);775let needs_specialization = last_specialized_tick.is_none_or(|tick| {776view_tick.is_newer_than(tick, ticks.this_run())777|| entity_tick.is_newer_than(tick, ticks.this_run())778});779if !needs_specialization {780continue;781}782let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {783continue;784};785786let mut mesh_key = *view_key;787mesh_key |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology());788789let pipeline_id =790pipelines.specialize(&pipeline_cache, &pipeline, mesh_key, &mesh.layout);791let pipeline_id = match pipeline_id {792Ok(id) => id,793Err(err) => {794error!("{}", err);795continue;796}797};798799view_specialized_material_pipeline_cache800.insert(*visible_entity, (ticks.this_run(), pipeline_id));801}802}803804// Delete specialized pipelines belonging to views that have expired.805specialized_material_pipeline_cache806.retain(|retained_view_entity, _| all_views.contains(retained_view_entity));807}808809fn queue_wireframes(810custom_draw_functions: Res<DrawFunctions<Wireframe2dPhaseItem>>,811render_mesh_instances: Res<RenderMesh2dInstances>,812mesh_allocator: Res<MeshAllocator>,813specialized_wireframe_pipeline_cache: Res<SpecializedWireframePipelineCache>,814render_wireframe_instances: Res<RenderWireframeInstances>,815mut wireframe_2d_phases: ResMut<ViewBinnedRenderPhases<Wireframe2dPhaseItem>>,816mut views: Query<(&ExtractedView, &RenderVisibleEntities)>,817) {818for (view, visible_entities) in &mut views {819let Some(wireframe_phase) = wireframe_2d_phases.get_mut(&view.retained_view_entity) else {820continue;821};822let draw_wireframe = custom_draw_functions.read().id::<DrawWireframe2d>();823824let Some(view_specialized_material_pipeline_cache) =825specialized_wireframe_pipeline_cache.get(&view.retained_view_entity)826else {827continue;828};829830for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {831let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else {832continue;833};834let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache835.get(visible_entity)836.map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))837else {838continue;839};840841// Skip the entity if it's cached in a bin and up to date.842if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) {843continue;844}845let Some(mesh_instance) = render_mesh_instances.get(visible_entity) else {846continue;847};848let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);849let bin_key = Wireframe2dBinKey {850asset_id: mesh_instance.mesh_asset_id.untyped(),851};852let batch_set_key = Wireframe2dBatchSetKey {853pipeline: pipeline_id,854asset_id: wireframe_instance.untyped(),855draw_function: draw_wireframe,856vertex_slab: vertex_slab.unwrap_or_default(),857index_slab,858};859wireframe_phase.add(860batch_set_key,861bin_key,862(*render_entity, *visible_entity),863InputUniformIndex::default(),864if mesh_instance.automatic_batching {865BinnedRenderPhaseType::BatchableMesh866} else {867BinnedRenderPhaseType::UnbatchableMesh868},869current_change_tick,870);871}872}873}874875876