bevy_pbr/
wireframe.rs

1use crate::{
2    DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstanceFlags, RenderMeshInstances,
3    SetMeshBindGroup, SetMeshViewBindGroup, ViewKeyCache, ViewSpecializationTicks,
4};
5use bevy_app::{App, Plugin, PostUpdate, Startup, Update};
6use bevy_asset::{
7    load_internal_asset, prelude::AssetChanged, weak_handle, AsAssetId, Asset, AssetApp,
8    AssetEvents, AssetId, Assets, Handle, UntypedAssetId,
9};
10use bevy_color::{Color, ColorToComponents};
11use bevy_core_pipeline::core_3d::{
12    graph::{Core3d, Node3d},
13    Camera3d,
14};
15use bevy_derive::{Deref, DerefMut};
16use bevy_ecs::{
17    component::Tick,
18    prelude::*,
19    query::QueryItem,
20    system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem},
21};
22use bevy_platform::{
23    collections::{HashMap, HashSet},
24    hash::FixedHasher,
25};
26use bevy_reflect::{std_traits::ReflectDefault, Reflect};
27use bevy_render::camera::extract_cameras;
28use bevy_render::{
29    batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
30    camera::ExtractedCamera,
31    extract_resource::ExtractResource,
32    mesh::{
33        allocator::{MeshAllocator, SlabId},
34        Mesh3d, MeshVertexBufferLayoutRef, RenderMesh,
35    },
36    prelude::*,
37    render_asset::{
38        prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets,
39    },
40    render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
41    render_phase::{
42        AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType,
43        CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
44        PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,
45        SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases,
46    },
47    render_resource::*,
48    renderer::RenderContext,
49    sync_world::{MainEntity, MainEntityHashMap},
50    view::{
51        ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities,
52        RetainedViewEntity, ViewDepthTexture, ViewTarget,
53    },
54    Extract, Render, RenderApp, RenderDebugFlags, RenderSet,
55};
56use core::{hash::Hash, ops::Range};
57use tracing::error;
58
59pub const WIREFRAME_SHADER_HANDLE: Handle<Shader> =
60    weak_handle!("2646a633-f8e3-4380-87ae-b44d881abbce");
61
62/// A [`Plugin`] that draws wireframes.
63///
64/// Wireframes currently do not work when using webgl or webgpu.
65/// Supported rendering backends:
66/// - DX12
67/// - Vulkan
68/// - Metal
69///
70/// This is a native only feature.
71#[derive(Debug, Default)]
72pub struct WireframePlugin {
73    /// Debugging flags that can optionally be set when constructing the renderer.
74    pub debug_flags: RenderDebugFlags,
75}
76
77impl WireframePlugin {
78    /// Creates a new [`WireframePlugin`] with the given debug flags.
79    pub fn new(debug_flags: RenderDebugFlags) -> Self {
80        Self { debug_flags }
81    }
82}
83
84impl Plugin for WireframePlugin {
85    fn build(&self, app: &mut App) {
86        load_internal_asset!(
87            app,
88            WIREFRAME_SHADER_HANDLE,
89            "render/wireframe.wgsl",
90            Shader::from_wgsl
91        );
92
93        app.add_plugins((
94            BinnedRenderPhasePlugin::<Wireframe3d, MeshPipeline>::new(self.debug_flags),
95            RenderAssetPlugin::<RenderWireframeMaterial>::default(),
96        ))
97        .init_asset::<WireframeMaterial>()
98        .init_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
99        .register_type::<NoWireframe>()
100        .register_type::<WireframeConfig>()
101        .register_type::<WireframeColor>()
102        .init_resource::<WireframeConfig>()
103        .init_resource::<WireframeEntitiesNeedingSpecialization>()
104        .add_systems(Startup, setup_global_wireframe_material)
105        .add_systems(
106            Update,
107            (
108                global_color_changed.run_if(resource_changed::<WireframeConfig>),
109                wireframe_color_changed,
110                // Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global
111                // wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed.
112                (apply_wireframe_material, apply_global_wireframe_material).chain(),
113            ),
114        )
115        .add_systems(
116            PostUpdate,
117            check_wireframe_entities_needing_specialization
118                .after(AssetEvents)
119                .run_if(resource_exists::<WireframeConfig>),
120        );
121
122        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
123            return;
124        };
125
126        render_app
127            .init_resource::<WireframeEntitySpecializationTicks>()
128            .init_resource::<SpecializedWireframePipelineCache>()
129            .init_resource::<DrawFunctions<Wireframe3d>>()
130            .add_render_command::<Wireframe3d, DrawWireframe3d>()
131            .init_resource::<RenderWireframeInstances>()
132            .init_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
133            .add_render_graph_node::<ViewNodeRunner<Wireframe3dNode>>(Core3d, Node3d::Wireframe)
134            .add_render_graph_edges(
135                Core3d,
136                (
137                    Node3d::EndMainPass,
138                    Node3d::Wireframe,
139                    Node3d::PostProcessing,
140                ),
141            )
142            .add_systems(
143                ExtractSchedule,
144                (
145                    extract_wireframe_3d_camera,
146                    extract_wireframe_entities_needing_specialization.after(extract_cameras),
147                    extract_wireframe_materials,
148                ),
149            )
150            .add_systems(
151                Render,
152                (
153                    specialize_wireframes
154                        .in_set(RenderSet::PrepareMeshes)
155                        .after(prepare_assets::<RenderWireframeMaterial>)
156                        .after(prepare_assets::<RenderMesh>),
157                    queue_wireframes
158                        .in_set(RenderSet::QueueMeshes)
159                        .after(prepare_assets::<RenderWireframeMaterial>),
160                ),
161            );
162    }
163
164    fn finish(&self, app: &mut App) {
165        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
166            return;
167        };
168        render_app.init_resource::<Wireframe3dPipeline>();
169    }
170}
171
172/// Enables wireframe rendering for any entity it is attached to.
173/// It will ignore the [`WireframeConfig`] global setting.
174///
175/// This requires the [`WireframePlugin`] to be enabled.
176#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
177#[reflect(Component, Default, Debug, PartialEq)]
178pub struct Wireframe;
179
180pub struct Wireframe3d {
181    /// Determines which objects can be placed into a *batch set*.
182    ///
183    /// Objects in a single batch set can potentially be multi-drawn together,
184    /// if it's enabled and the current platform supports it.
185    pub batch_set_key: Wireframe3dBatchSetKey,
186    /// The key, which determines which can be batched.
187    pub bin_key: Wireframe3dBinKey,
188    /// An entity from which data will be fetched, including the mesh if
189    /// applicable.
190    pub representative_entity: (Entity, MainEntity),
191    /// The ranges of instances.
192    pub batch_range: Range<u32>,
193    /// An extra index, which is either a dynamic offset or an index in the
194    /// indirect parameters list.
195    pub extra_index: PhaseItemExtraIndex,
196}
197
198impl PhaseItem for Wireframe3d {
199    fn entity(&self) -> Entity {
200        self.representative_entity.0
201    }
202
203    fn main_entity(&self) -> MainEntity {
204        self.representative_entity.1
205    }
206
207    fn draw_function(&self) -> DrawFunctionId {
208        self.batch_set_key.draw_function
209    }
210
211    fn batch_range(&self) -> &Range<u32> {
212        &self.batch_range
213    }
214
215    fn batch_range_mut(&mut self) -> &mut Range<u32> {
216        &mut self.batch_range
217    }
218
219    fn extra_index(&self) -> PhaseItemExtraIndex {
220        self.extra_index.clone()
221    }
222
223    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
224        (&mut self.batch_range, &mut self.extra_index)
225    }
226}
227
228impl CachedRenderPipelinePhaseItem for Wireframe3d {
229    fn cached_pipeline(&self) -> CachedRenderPipelineId {
230        self.batch_set_key.pipeline
231    }
232}
233
234impl BinnedPhaseItem for Wireframe3d {
235    type BinKey = Wireframe3dBinKey;
236    type BatchSetKey = Wireframe3dBatchSetKey;
237
238    fn new(
239        batch_set_key: Self::BatchSetKey,
240        bin_key: Self::BinKey,
241        representative_entity: (Entity, MainEntity),
242        batch_range: Range<u32>,
243        extra_index: PhaseItemExtraIndex,
244    ) -> Self {
245        Self {
246            batch_set_key,
247            bin_key,
248            representative_entity,
249            batch_range,
250            extra_index,
251        }
252    }
253}
254
255#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
256pub struct Wireframe3dBatchSetKey {
257    /// The identifier of the render pipeline.
258    pub pipeline: CachedRenderPipelineId,
259
260    /// The wireframe material asset ID.
261    pub asset_id: UntypedAssetId,
262
263    /// The function used to draw.
264    pub draw_function: DrawFunctionId,
265    /// The ID of the slab of GPU memory that contains vertex data.
266    ///
267    /// For non-mesh items, you can fill this with 0 if your items can be
268    /// multi-drawn, or with a unique value if they can't.
269    pub vertex_slab: SlabId,
270
271    /// The ID of the slab of GPU memory that contains index data, if present.
272    ///
273    /// For non-mesh items, you can safely fill this with `None`.
274    pub index_slab: Option<SlabId>,
275}
276
277impl PhaseItemBatchSetKey for Wireframe3dBatchSetKey {
278    fn indexed(&self) -> bool {
279        self.index_slab.is_some()
280    }
281}
282
283/// Data that must be identical in order to *batch* phase items together.
284///
285/// Note that a *batch set* (if multi-draw is in use) contains multiple batches.
286#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
287pub struct Wireframe3dBinKey {
288    /// The wireframe mesh asset ID.
289    pub asset_id: UntypedAssetId,
290}
291
292pub struct SetWireframe3dPushConstants;
293
294impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dPushConstants {
295    type Param = (
296        SRes<RenderWireframeInstances>,
297        SRes<RenderAssets<RenderWireframeMaterial>>,
298    );
299    type ViewQuery = ();
300    type ItemQuery = ();
301
302    #[inline]
303    fn render<'w>(
304        item: &P,
305        _view: (),
306        _item_query: Option<()>,
307        (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,
308        pass: &mut TrackedRenderPass<'w>,
309    ) -> RenderCommandResult {
310        let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {
311            return RenderCommandResult::Failure("No wireframe material found for entity");
312        };
313        let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {
314            return RenderCommandResult::Failure("No wireframe material found for entity");
315        };
316
317        pass.set_push_constants(
318            ShaderStages::FRAGMENT,
319            0,
320            bytemuck::bytes_of(&wireframe_material.color),
321        );
322        RenderCommandResult::Success
323    }
324}
325
326pub type DrawWireframe3d = (
327    SetItemPipeline,
328    SetMeshViewBindGroup<0>,
329    SetMeshBindGroup<1>,
330    SetWireframe3dPushConstants,
331    DrawMesh,
332);
333
334#[derive(Resource, Clone)]
335pub struct Wireframe3dPipeline {
336    mesh_pipeline: MeshPipeline,
337    shader: Handle<Shader>,
338}
339
340impl FromWorld for Wireframe3dPipeline {
341    fn from_world(render_world: &mut World) -> Self {
342        Wireframe3dPipeline {
343            mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
344            shader: WIREFRAME_SHADER_HANDLE,
345        }
346    }
347}
348
349impl SpecializedMeshPipeline for Wireframe3dPipeline {
350    type Key = MeshPipelineKey;
351
352    fn specialize(
353        &self,
354        key: Self::Key,
355        layout: &MeshVertexBufferLayoutRef,
356    ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
357        let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
358        descriptor.label = Some("wireframe_3d_pipeline".into());
359        descriptor.push_constant_ranges.push(PushConstantRange {
360            stages: ShaderStages::FRAGMENT,
361            range: 0..16,
362        });
363        let fragment = descriptor.fragment.as_mut().unwrap();
364        fragment.shader = self.shader.clone();
365        descriptor.primitive.polygon_mode = PolygonMode::Line;
366        descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
367        Ok(descriptor)
368    }
369}
370
371#[derive(Default)]
372struct Wireframe3dNode;
373impl ViewNode for Wireframe3dNode {
374    type ViewQuery = (
375        &'static ExtractedCamera,
376        &'static ExtractedView,
377        &'static ViewTarget,
378        &'static ViewDepthTexture,
379    );
380
381    fn run<'w>(
382        &self,
383        graph: &mut RenderGraphContext,
384        render_context: &mut RenderContext<'w>,
385        (camera, view, target, depth): QueryItem<'w, Self::ViewQuery>,
386        world: &'w World,
387    ) -> Result<(), NodeRunError> {
388        let Some(wireframe_phase) = world.get_resource::<ViewBinnedRenderPhases<Wireframe3d>>()
389        else {
390            return Ok(());
391        };
392
393        let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else {
394            return Ok(());
395        };
396
397        let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
398            label: Some("wireframe_3d_pass"),
399            color_attachments: &[Some(target.get_color_attachment())],
400            depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
401            timestamp_writes: None,
402            occlusion_query_set: None,
403        });
404
405        if let Some(viewport) = camera.viewport.as_ref() {
406            render_pass.set_camera_viewport(viewport);
407        }
408
409        if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) {
410            error!("Error encountered while rendering the stencil phase {err:?}");
411            return Err(NodeRunError::DrawError(err));
412        }
413
414        Ok(())
415    }
416}
417
418/// Sets the color of the [`Wireframe`] of the entity it is attached to.
419///
420/// If this component is present but there's no [`Wireframe`] component,
421/// it will still affect the color of the wireframe when [`WireframeConfig::global`] is set to true.
422///
423/// This overrides the [`WireframeConfig::default_color`].
424#[derive(Component, Debug, Clone, Default, Reflect)]
425#[reflect(Component, Default, Debug)]
426pub struct WireframeColor {
427    pub color: Color,
428}
429
430#[derive(Component, Debug, Clone, Default)]
431pub struct ExtractedWireframeColor {
432    pub color: [f32; 4],
433}
434
435/// Disables wireframe rendering for any entity it is attached to.
436/// It will ignore the [`WireframeConfig`] global setting.
437///
438/// This requires the [`WireframePlugin`] to be enabled.
439#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
440#[reflect(Component, Default, Debug, PartialEq)]
441pub struct NoWireframe;
442
443#[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)]
444#[reflect(Resource, Debug, Default)]
445pub struct WireframeConfig {
446    /// Whether to show wireframes for all meshes.
447    /// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component.
448    pub global: bool,
449    /// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
450    /// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`],
451    /// but no [`WireframeColor`].
452    pub default_color: Color,
453}
454
455#[derive(Asset, Reflect, Clone, Debug, Default)]
456#[reflect(Clone, Default)]
457pub struct WireframeMaterial {
458    pub color: Color,
459}
460
461pub struct RenderWireframeMaterial {
462    pub color: [f32; 4],
463}
464
465#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)]
466#[reflect(Component, Default, Clone, PartialEq)]
467pub struct Mesh3dWireframe(pub Handle<WireframeMaterial>);
468
469impl AsAssetId for Mesh3dWireframe {
470    type Asset = WireframeMaterial;
471
472    fn as_asset_id(&self) -> AssetId<Self::Asset> {
473        self.0.id()
474    }
475}
476
477impl RenderAsset for RenderWireframeMaterial {
478    type SourceAsset = WireframeMaterial;
479    type Param = ();
480
481    fn prepare_asset(
482        source_asset: Self::SourceAsset,
483        _asset_id: AssetId<Self::SourceAsset>,
484        _param: &mut SystemParamItem<Self::Param>,
485    ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
486        Ok(RenderWireframeMaterial {
487            color: source_asset.color.to_linear().to_f32_array(),
488        })
489    }
490}
491
492#[derive(Resource, Deref, DerefMut, Default)]
493pub struct RenderWireframeInstances(MainEntityHashMap<AssetId<WireframeMaterial>>);
494
495#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)]
496pub struct WireframeEntitiesNeedingSpecialization {
497    #[deref]
498    pub entities: Vec<Entity>,
499}
500
501#[derive(Resource, Deref, DerefMut, Clone, Debug, Default)]
502pub struct WireframeEntitySpecializationTicks {
503    pub entities: MainEntityHashMap<Tick>,
504}
505
506/// Stores the [`SpecializedWireframeViewPipelineCache`] for each view.
507#[derive(Resource, Deref, DerefMut, Default)]
508pub struct SpecializedWireframePipelineCache {
509    // view entity -> view pipeline cache
510    #[deref]
511    map: HashMap<RetainedViewEntity, SpecializedWireframeViewPipelineCache>,
512}
513
514/// Stores the cached render pipeline ID for each entity in a single view, as
515/// well as the last time it was changed.
516#[derive(Deref, DerefMut, Default)]
517pub struct SpecializedWireframeViewPipelineCache {
518    // material entity -> (tick, pipeline_id)
519    #[deref]
520    map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
521}
522
523#[derive(Resource)]
524struct GlobalWireframeMaterial {
525    // This handle will be reused when the global config is enabled
526    handle: Handle<WireframeMaterial>,
527}
528
529pub fn extract_wireframe_materials(
530    mut material_instances: ResMut<RenderWireframeInstances>,
531    changed_meshes_query: Extract<
532        Query<
533            (Entity, &ViewVisibility, &Mesh3dWireframe),
534            Or<(Changed<ViewVisibility>, Changed<Mesh3dWireframe>)>,
535        >,
536    >,
537    mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
538    mut removed_materials_query: Extract<RemovedComponents<Mesh3dWireframe>>,
539) {
540    for (entity, view_visibility, material) in &changed_meshes_query {
541        if view_visibility.get() {
542            material_instances.insert(entity.into(), material.id());
543        } else {
544            material_instances.remove(&MainEntity::from(entity));
545        }
546    }
547
548    for entity in removed_visibilities_query
549        .read()
550        .chain(removed_materials_query.read())
551    {
552        // Only queue a mesh for removal if we didn't pick it up above.
553        // It's possible that a necessary component was removed and re-added in
554        // the same frame.
555        if !changed_meshes_query.contains(entity) {
556            material_instances.remove(&MainEntity::from(entity));
557        }
558    }
559}
560
561fn setup_global_wireframe_material(
562    mut commands: Commands,
563    mut materials: ResMut<Assets<WireframeMaterial>>,
564    config: Res<WireframeConfig>,
565) {
566    // Create the handle used for the global material
567    commands.insert_resource(GlobalWireframeMaterial {
568        handle: materials.add(WireframeMaterial {
569            color: config.default_color,
570        }),
571    });
572}
573
574/// Updates the wireframe material of all entities without a [`WireframeColor`] or without a [`Wireframe`] component
575fn global_color_changed(
576    config: Res<WireframeConfig>,
577    mut materials: ResMut<Assets<WireframeMaterial>>,
578    global_material: Res<GlobalWireframeMaterial>,
579) {
580    if let Some(global_material) = materials.get_mut(&global_material.handle) {
581        global_material.color = config.default_color;
582    }
583}
584
585/// Updates the wireframe material when the color in [`WireframeColor`] changes
586fn wireframe_color_changed(
587    mut materials: ResMut<Assets<WireframeMaterial>>,
588    mut colors_changed: Query<
589        (&mut Mesh3dWireframe, &WireframeColor),
590        (With<Wireframe>, Changed<WireframeColor>),
591    >,
592) {
593    for (mut handle, wireframe_color) in &mut colors_changed {
594        handle.0 = materials.add(WireframeMaterial {
595            color: wireframe_color.color,
596        });
597    }
598}
599
600/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component, and removes it
601/// for any mesh with a [`NoWireframe`] component.
602fn apply_wireframe_material(
603    mut commands: Commands,
604    mut materials: ResMut<Assets<WireframeMaterial>>,
605    wireframes: Query<
606        (Entity, Option<&WireframeColor>),
607        (With<Wireframe>, Without<Mesh3dWireframe>),
608    >,
609    no_wireframes: Query<Entity, (With<NoWireframe>, With<Mesh3dWireframe>)>,
610    mut removed_wireframes: RemovedComponents<Wireframe>,
611    global_material: Res<GlobalWireframeMaterial>,
612) {
613    for e in removed_wireframes.read().chain(no_wireframes.iter()) {
614        if let Ok(mut commands) = commands.get_entity(e) {
615            commands.remove::<Mesh3dWireframe>();
616        }
617    }
618
619    let mut material_to_spawn = vec![];
620    for (e, maybe_color) in &wireframes {
621        let material = get_wireframe_material(maybe_color, &mut materials, &global_material);
622        material_to_spawn.push((e, Mesh3dWireframe(material)));
623    }
624    commands.try_insert_batch(material_to_spawn);
625}
626
627type WireframeFilter = (With<Mesh3d>, Without<Wireframe>, Without<NoWireframe>);
628
629/// Applies or removes a wireframe material on any mesh without a [`Wireframe`] or [`NoWireframe`] component.
630fn apply_global_wireframe_material(
631    mut commands: Commands,
632    config: Res<WireframeConfig>,
633    meshes_without_material: Query<
634        (Entity, Option<&WireframeColor>),
635        (WireframeFilter, Without<Mesh3dWireframe>),
636    >,
637    meshes_with_global_material: Query<Entity, (WireframeFilter, With<Mesh3dWireframe>)>,
638    global_material: Res<GlobalWireframeMaterial>,
639    mut materials: ResMut<Assets<WireframeMaterial>>,
640) {
641    if config.global {
642        let mut material_to_spawn = vec![];
643        for (e, maybe_color) in &meshes_without_material {
644            let material = get_wireframe_material(maybe_color, &mut materials, &global_material);
645            // We only add the material handle but not the Wireframe component
646            // This makes it easy to detect which mesh is using the global material and which ones are user specified
647            material_to_spawn.push((e, Mesh3dWireframe(material)));
648        }
649        commands.try_insert_batch(material_to_spawn);
650    } else {
651        for e in &meshes_with_global_material {
652            commands.entity(e).remove::<Mesh3dWireframe>();
653        }
654    }
655}
656
657/// Gets a handle to a wireframe material with a fallback on the default material
658fn get_wireframe_material(
659    maybe_color: Option<&WireframeColor>,
660    wireframe_materials: &mut Assets<WireframeMaterial>,
661    global_material: &GlobalWireframeMaterial,
662) -> Handle<WireframeMaterial> {
663    if let Some(wireframe_color) = maybe_color {
664        wireframe_materials.add(WireframeMaterial {
665            color: wireframe_color.color,
666        })
667    } else {
668        // If there's no color specified we can use the global material since it's already set to use the default_color
669        global_material.handle.clone()
670    }
671}
672
673fn extract_wireframe_3d_camera(
674    mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
675    cameras: Extract<Query<(Entity, &Camera, Has<NoIndirectDrawing>), With<Camera3d>>>,
676    mut live_entities: Local<HashSet<RetainedViewEntity>>,
677    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
678) {
679    live_entities.clear();
680    for (main_entity, camera, no_indirect_drawing) in &cameras {
681        if !camera.is_active {
682            continue;
683        }
684        let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
685            GpuPreprocessingMode::Culling
686        } else {
687            GpuPreprocessingMode::PreprocessingOnly
688        });
689
690        let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0);
691        wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
692        live_entities.insert(retained_view_entity);
693    }
694
695    // Clear out all dead views.
696    wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
697}
698
699pub fn extract_wireframe_entities_needing_specialization(
700    entities_needing_specialization: Extract<Res<WireframeEntitiesNeedingSpecialization>>,
701    mut entity_specialization_ticks: ResMut<WireframeEntitySpecializationTicks>,
702    views: Query<&ExtractedView>,
703    mut specialized_wireframe_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
704    mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
705    ticks: SystemChangeTick,
706) {
707    for entity in entities_needing_specialization.iter() {
708        // Update the entity's specialization tick with this run's tick
709        entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
710    }
711
712    for entity in removed_meshes_query.read() {
713        for view in &views {
714            if let Some(specialized_wireframe_pipeline_cache) =
715                specialized_wireframe_pipeline_cache.get_mut(&view.retained_view_entity)
716            {
717                specialized_wireframe_pipeline_cache.remove(&MainEntity::from(entity));
718            }
719        }
720    }
721}
722
723pub fn check_wireframe_entities_needing_specialization(
724    needs_specialization: Query<
725        Entity,
726        Or<(
727            Changed<Mesh3d>,
728            AssetChanged<Mesh3d>,
729            Changed<Mesh3dWireframe>,
730            AssetChanged<Mesh3dWireframe>,
731        )>,
732    >,
733    mut entities_needing_specialization: ResMut<WireframeEntitiesNeedingSpecialization>,
734) {
735    entities_needing_specialization.clear();
736    for entity in &needs_specialization {
737        entities_needing_specialization.push(entity);
738    }
739}
740
741pub fn specialize_wireframes(
742    render_meshes: Res<RenderAssets<RenderMesh>>,
743    render_mesh_instances: Res<RenderMeshInstances>,
744    render_wireframe_instances: Res<RenderWireframeInstances>,
745    render_visibility_ranges: Res<RenderVisibilityRanges>,
746    wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
747    views: Query<(&ExtractedView, &RenderVisibleEntities)>,
748    view_key_cache: Res<ViewKeyCache>,
749    entity_specialization_ticks: Res<WireframeEntitySpecializationTicks>,
750    view_specialization_ticks: Res<ViewSpecializationTicks>,
751    mut specialized_material_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
752    mut pipelines: ResMut<SpecializedMeshPipelines<Wireframe3dPipeline>>,
753    pipeline: Res<Wireframe3dPipeline>,
754    pipeline_cache: Res<PipelineCache>,
755    ticks: SystemChangeTick,
756) {
757    // Record the retained IDs of all views so that we can expire old
758    // pipeline IDs.
759    let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
760
761    for (view, visible_entities) in &views {
762        all_views.insert(view.retained_view_entity);
763
764        if !wireframe_phases.contains_key(&view.retained_view_entity) {
765            continue;
766        }
767
768        let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
769            continue;
770        };
771
772        let view_tick = view_specialization_ticks
773            .get(&view.retained_view_entity)
774            .unwrap();
775        let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
776            .entry(view.retained_view_entity)
777            .or_default();
778
779        for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
780            if !render_wireframe_instances.contains_key(visible_entity) {
781                continue;
782            };
783            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
784            else {
785                continue;
786            };
787            let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
788            let last_specialized_tick = view_specialized_material_pipeline_cache
789                .get(visible_entity)
790                .map(|(tick, _)| *tick);
791            let needs_specialization = last_specialized_tick.is_none_or(|tick| {
792                view_tick.is_newer_than(tick, ticks.this_run())
793                    || entity_tick.is_newer_than(tick, ticks.this_run())
794            });
795            if !needs_specialization {
796                continue;
797            }
798            let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
799                continue;
800            };
801
802            let mut mesh_key = *view_key;
803            mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology());
804
805            if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
806                mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
807            }
808
809            if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
810                // If the previous frame have skins or morph targets, note that.
811                if mesh_instance
812                    .flags
813                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
814                {
815                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
816                }
817                if mesh_instance
818                    .flags
819                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
820                {
821                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
822                }
823            }
824
825            let pipeline_id =
826                pipelines.specialize(&pipeline_cache, &pipeline, mesh_key, &mesh.layout);
827            let pipeline_id = match pipeline_id {
828                Ok(id) => id,
829                Err(err) => {
830                    error!("{}", err);
831                    continue;
832                }
833            };
834
835            view_specialized_material_pipeline_cache
836                .insert(*visible_entity, (ticks.this_run(), pipeline_id));
837        }
838    }
839
840    // Delete specialized pipelines belonging to views that have expired.
841    specialized_material_pipeline_cache
842        .retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
843}
844
845fn queue_wireframes(
846    custom_draw_functions: Res<DrawFunctions<Wireframe3d>>,
847    render_mesh_instances: Res<RenderMeshInstances>,
848    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
849    mesh_allocator: Res<MeshAllocator>,
850    specialized_wireframe_pipeline_cache: Res<SpecializedWireframePipelineCache>,
851    render_wireframe_instances: Res<RenderWireframeInstances>,
852    mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
853    mut views: Query<(&ExtractedView, &RenderVisibleEntities)>,
854) {
855    for (view, visible_entities) in &mut views {
856        let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else {
857            continue;
858        };
859        let draw_wireframe = custom_draw_functions.read().id::<DrawWireframe3d>();
860
861        let Some(view_specialized_material_pipeline_cache) =
862            specialized_wireframe_pipeline_cache.get(&view.retained_view_entity)
863        else {
864            continue;
865        };
866
867        for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
868            let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else {
869                continue;
870            };
871            let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache
872                .get(visible_entity)
873                .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))
874            else {
875                continue;
876            };
877
878            // Skip the entity if it's cached in a bin and up to date.
879            if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) {
880                continue;
881            }
882            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
883            else {
884                continue;
885            };
886            let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
887            let bin_key = Wireframe3dBinKey {
888                asset_id: mesh_instance.mesh_asset_id.untyped(),
889            };
890            let batch_set_key = Wireframe3dBatchSetKey {
891                pipeline: pipeline_id,
892                asset_id: wireframe_instance.untyped(),
893                draw_function: draw_wireframe,
894                vertex_slab: vertex_slab.unwrap_or_default(),
895                index_slab,
896            };
897            wireframe_phase.add(
898                batch_set_key,
899                bin_key,
900                (*render_entity, *visible_entity),
901                mesh_instance.current_uniform_index,
902                BinnedRenderPhaseType::mesh(
903                    mesh_instance.should_batch(),
904                    &gpu_preprocessing_support,
905                ),
906                current_change_tick,
907            );
908        }
909    }
910}