Skip to main content

bevy_pbr/
wireframe.rs

1use crate::{
2    collect_meshes_for_gpu_building,
3    render::{PreprocessBindGroups, PreprocessPipelines},
4    set_mesh_motion_vector_flags, DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineSystems,
5    RenderLightmaps, RenderMeshInstanceFlags, RenderMeshInstances, SetMeshBindGroup,
6    SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup, ViewKeyCache,
7};
8use bevy_app::{App, Plugin, PostUpdate, Startup};
9use bevy_asset::{
10    embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp,
11    AssetEventSystems, AssetId, AssetServer, Assets, Handle, UntypedAssetId,
12};
13use bevy_camera::{visibility::ViewVisibility, Camera, Camera3d};
14use bevy_color::{Color, ColorToComponents};
15use bevy_core_pipeline::schedule::{Core3d, Core3dSystems};
16use bevy_derive::{Deref, DerefMut};
17use bevy_ecs::{
18    prelude::*,
19    query::ROQueryItem,
20    system::{lifetimeless::SRes, SystemParamItem},
21};
22use bevy_mesh::{Mesh, Mesh3d, MeshVertexBufferLayoutRef};
23use bevy_platform::{
24    collections::{HashMap, HashSet},
25    hash::FixedHasher,
26};
27use bevy_reflect::{std_traits::ReflectDefault, Reflect};
28use bevy_render::{
29    batching::gpu_preprocessing::{
30        GpuPreprocessingMode, GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers,
31        IndirectParametersNonIndexed,
32    },
33    camera::{
34        DirtySpecializationSystems, DirtyWireframeSpecializations, ExtractedCamera, PendingQueues,
35    },
36    extract_resource::ExtractResource,
37    mesh::{
38        allocator::{MeshAllocator, MeshAllocatorSettings, MeshSlabs},
39        RenderMesh, RenderMeshBufferInfo,
40    },
41    prelude::*,
42    render_asset::{
43        prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets,
44    },
45    render_phase::{
46        AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType,
47        CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
48        PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,
49        SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases,
50    },
51    render_resource::{binding_types::*, *},
52    renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
53    sync_world::{MainEntity, MainEntityHashMap},
54    view::{
55        ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities,
56        RetainedViewEntity, ViewDepthTexture, ViewTarget,
57    },
58    Extract, GpuResourceAppExt, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems,
59};
60use bevy_shader::Shader;
61use bytemuck::{Pod, Zeroable};
62use core::{any::TypeId, hash::Hash, mem::size_of, ops::Range};
63use tracing::{error, warn};
64
65/// A [`Plugin`] that draws wireframes.
66///
67/// Wireframes currently do not work when using webgl or webgpu.
68/// Supported rendering backends:
69/// - DX12
70/// - Vulkan
71/// - Metal
72///
73/// This is a native only feature.
74#[derive(Debug, Default)]
75pub struct WireframePlugin {
76    /// Debugging flags that can optionally be set when constructing the renderer.
77    pub debug_flags: RenderDebugFlags,
78}
79
80impl WireframePlugin {
81    /// Creates a new [`WireframePlugin`] with the given debug flags.
82    pub fn new(debug_flags: RenderDebugFlags) -> Self {
83        Self { debug_flags }
84    }
85}
86
87impl Plugin for WireframePlugin {
88    fn build(&self, app: &mut App) {
89        embedded_asset!(app, "render/wireframe.wgsl");
90
91        app.add_plugins((
92            BinnedRenderPhasePlugin::<Wireframe3d, MeshPipeline>::new(self.debug_flags),
93            RenderAssetPlugin::<RenderWireframeMaterial>::default(),
94        ))
95        .init_asset::<WireframeMaterial>()
96        .init_resource::<WireframeEntitiesNeedingSpecialization>()
97        .init_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
98        .init_resource::<WireframeConfig>()
99        .init_resource::<WireframeEntitiesNeedingSpecialization>()
100        .register_type::<WireframeLineWidth>()
101        .register_type::<WireframeTopology>()
102        .add_systems(Startup, setup_global_wireframe_material)
103        .add_systems(
104            PostUpdate,
105            (
106                wireframe_config_changed.run_if(resource_changed::<WireframeConfig>),
107                wireframe_color_changed,
108                wireframe_line_width_changed,
109                wireframe_topology_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(AssetEventSystems)
119                .run_if(resource_exists::<WireframeConfig>),
120        );
121    }
122
123    fn finish(&self, app: &mut App) {
124        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
125            return;
126        };
127
128        let required_features = WgpuFeatures::POLYGON_MODE_LINE | WgpuFeatures::IMMEDIATES;
129        let render_device = render_app.world().resource::<RenderDevice>();
130        if !render_device.features().contains(required_features) {
131            warn!(
132                "WireframePlugin not loaded. GPU lacks support for required features: {:?}.",
133                required_features
134            );
135            return;
136        }
137
138        // we need storage for vertex pulling in the wide wireframe path
139        render_app
140            .world_mut()
141            .resource_mut::<MeshAllocatorSettings>()
142            .extra_buffer_usages |= BufferUsages::STORAGE;
143
144        render_app
145            .init_gpu_resource::<SpecializedWireframePipelineCache>()
146            .init_resource::<DrawFunctions<Wireframe3d>>()
147            .add_render_command::<Wireframe3d, DrawWireframe3dThin>()
148            .add_render_command::<Wireframe3d, DrawWireframe3dWide>()
149            .init_gpu_resource::<RenderWireframeInstances>()
150            .init_gpu_resource::<WireframeWideBindGroups>()
151            .init_gpu_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
152            .init_gpu_resource::<PendingWireframeQueues>()
153            .add_systems(
154                RenderStartup,
155                init_wireframe_3d_pipeline.after(MeshPipelineSystems),
156            )
157            .add_systems(
158                Core3d,
159                wireframe_3d
160                    .after(Core3dSystems::MainPass)
161                    .before(Core3dSystems::EarlyPostProcess),
162            )
163            .add_systems(
164                ExtractSchedule,
165                (
166                    extract_wireframe_3d_camera,
167                    extract_wireframe_entities_needing_specialization
168                        .in_set(DirtySpecializationSystems::CheckForChanges),
169                    extract_wireframe_entities_that_need_specializations_removed
170                        .in_set(DirtySpecializationSystems::CheckForRemovals),
171                    extract_wireframe_materials,
172                ),
173            )
174            .add_systems(
175                Render,
176                (
177                    specialize_wireframes
178                        .in_set(RenderSystems::Specialize)
179                        .after(prepare_assets::<RenderWireframeMaterial>)
180                        .after(prepare_assets::<RenderMesh>)
181                        .after(collect_meshes_for_gpu_building)
182                        .after(set_mesh_motion_vector_flags),
183                    prepare_wireframe_wide_bind_groups
184                        .in_set(RenderSystems::PrepareBindGroups)
185                        .after(prepare_assets::<RenderWireframeMaterial>)
186                        .after(prepare_assets::<RenderMesh>),
187                    queue_wireframes
188                        .in_set(RenderSystems::QueueMeshes)
189                        .after(prepare_assets::<RenderWireframeMaterial>),
190                ),
191            );
192    }
193}
194
195/// Enables wireframe rendering for any entity it is attached to.
196/// It will ignore the [`WireframeConfig`] global setting.
197///
198/// This requires the [`WireframePlugin`] to be enabled.
199#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
200#[reflect(Component, Default, Debug, PartialEq)]
201pub struct Wireframe;
202
203pub struct Wireframe3d {
204    /// Determines which objects can be placed into a *batch set*.
205    ///
206    /// Objects in a single batch set can potentially be multi-drawn together,
207    /// if it's enabled and the current platform supports it.
208    pub batch_set_key: Wireframe3dBatchSetKey,
209    /// The key, which determines which can be batched.
210    pub bin_key: Wireframe3dBinKey,
211    /// An entity from which data will be fetched, including the mesh if
212    /// applicable.
213    pub representative_entity: (Entity, MainEntity),
214    /// The ranges of instances.
215    pub batch_range: Range<u32>,
216    /// An extra index, which is either a dynamic offset or an index in the
217    /// indirect parameters list.
218    pub extra_index: PhaseItemExtraIndex,
219}
220
221impl PhaseItem for Wireframe3d {
222    fn entity(&self) -> Entity {
223        self.representative_entity.0
224    }
225
226    fn main_entity(&self) -> MainEntity {
227        self.representative_entity.1
228    }
229
230    fn draw_function(&self) -> DrawFunctionId {
231        self.batch_set_key.draw_function
232    }
233
234    fn batch_range(&self) -> &Range<u32> {
235        &self.batch_range
236    }
237
238    fn batch_range_mut(&mut self) -> &mut Range<u32> {
239        &mut self.batch_range
240    }
241
242    fn extra_index(&self) -> PhaseItemExtraIndex {
243        self.extra_index.clone()
244    }
245
246    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
247        (&mut self.batch_range, &mut self.extra_index)
248    }
249}
250
251impl CachedRenderPipelinePhaseItem for Wireframe3d {
252    fn cached_pipeline(&self) -> CachedRenderPipelineId {
253        self.batch_set_key.pipeline
254    }
255}
256
257impl BinnedPhaseItem for Wireframe3d {
258    type BinKey = Wireframe3dBinKey;
259    type BatchSetKey = Wireframe3dBatchSetKey;
260
261    fn new(
262        batch_set_key: Self::BatchSetKey,
263        bin_key: Self::BinKey,
264        representative_entity: (Entity, MainEntity),
265        batch_range: Range<u32>,
266        extra_index: PhaseItemExtraIndex,
267    ) -> Self {
268        Self {
269            batch_set_key,
270            bin_key,
271            representative_entity,
272            batch_range,
273            extra_index,
274        }
275    }
276}
277
278#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
279pub struct Wireframe3dBatchSetKey {
280    /// The identifier of the render pipeline.
281    pub pipeline: CachedRenderPipelineId,
282
283    /// The wireframe material asset ID.
284    pub asset_id: UntypedAssetId,
285
286    /// The function used to draw.
287    pub draw_function: DrawFunctionId,
288
289    /// The IDs of the slabs of GPU memory in the mesh allocator that contain
290    /// the mesh data.
291    ///
292    /// For non-mesh items, you can fill the [`MeshSlabs::vertex_slab_id`] with
293    /// 0 if your items can be multi-drawn, or with a unique value if they
294    /// can't.
295    pub slabs: MeshSlabs,
296
297    /// For the wide wireframe path, the mesh asset ID ensures all draws in one
298    /// batch set share the same vertex-pull params uniform. `None` for the thin
299    /// path, which doesn't need per-mesh bind groups.
300    pub mesh_asset_id: Option<UntypedAssetId>,
301}
302
303impl PhaseItemBatchSetKey for Wireframe3dBatchSetKey {
304    fn indexed(&self) -> bool {
305        self.slabs.index_slab_id.is_some()
306    }
307}
308
309/// Data that must be identical in order to *batch* phase items together.
310///
311/// Note that a *batch set* (if multi-draw is in use) contains multiple batches.
312#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
313pub struct Wireframe3dBinKey {
314    /// The wireframe mesh asset ID.
315    pub asset_id: UntypedAssetId,
316}
317
318pub struct SetWireframe3dThinImmediates;
319
320impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dThinImmediates {
321    type Param = (
322        SRes<RenderWireframeInstances>,
323        SRes<RenderAssets<RenderWireframeMaterial>>,
324    );
325    type ViewQuery = ();
326    type ItemQuery = ();
327
328    #[inline]
329    fn render<'w>(
330        item: &P,
331        _view: (),
332        _item_query: Option<()>,
333        (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,
334        pass: &mut TrackedRenderPass<'w>,
335    ) -> RenderCommandResult {
336        let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {
337            return RenderCommandResult::Failure("No wireframe material found for entity");
338        };
339        let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {
340            return RenderCommandResult::Failure("No wireframe material found for entity");
341        };
342
343        pass.set_immediates(0, bytemuck::bytes_of(&wireframe_material.color));
344        RenderCommandResult::Success
345    }
346}
347
348pub struct SetWireframe3dWideImmediates;
349
350#[derive(Clone, Copy, Pod, Zeroable)]
351#[repr(C)]
352struct WireframeWideImmediates {
353    color: [f32; 4],
354    line_width: f32,
355    smoothing: f32,
356    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
357    _padding: [f32; 2],
358}
359
360impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dWideImmediates {
361    type Param = (
362        SRes<RenderWireframeInstances>,
363        SRes<RenderAssets<RenderWireframeMaterial>>,
364    );
365    type ViewQuery = ();
366    type ItemQuery = ();
367
368    #[inline]
369    fn render<'w>(
370        item: &P,
371        _view: (),
372        _item_query: Option<()>,
373        (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,
374        pass: &mut TrackedRenderPass<'w>,
375    ) -> RenderCommandResult {
376        let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {
377            return RenderCommandResult::Failure("No wireframe material found for entity");
378        };
379        let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {
380            return RenderCommandResult::Failure("No wireframe material found for entity");
381        };
382
383        let push = WireframeWideImmediates {
384            color: wireframe_material.color,
385            line_width: wireframe_material.line_width,
386            smoothing: if wireframe_material.line_width <= 1.0 {
387                0.5
388            } else {
389                1.0
390            },
391            #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
392            _padding: [0.0; 2],
393        };
394        pass.set_immediates(0, bytemuck::bytes_of(&push));
395        RenderCommandResult::Success
396    }
397}
398
399#[derive(Clone, Copy, ShaderType, Pod, Zeroable)]
400#[repr(C)]
401pub struct WireframeVertexPullParams {
402    pub index_offset: u32,
403    pub vertex_stride_u32s: u32,
404    pub position_offset_u32s: u32,
405}
406
407#[derive(Resource, Default)]
408pub struct WireframeWideBindGroups {
409    pub params: DynamicUniformBuffer<WireframeVertexPullParams>,
410    pub bind_groups: HashMap<AssetId<Mesh>, (BindGroup, u32)>,
411}
412
413pub fn prepare_wireframe_wide_bind_groups(
414    render_mesh_instances: Res<RenderMeshInstances>,
415    render_meshes: Res<RenderAssets<RenderMesh>>,
416    render_wireframe_instances: Res<RenderWireframeInstances>,
417    render_wireframe_assets: Res<RenderAssets<RenderWireframeMaterial>>,
418    mesh_allocator: Res<MeshAllocator>,
419    pipeline: Res<Wireframe3dPipeline>,
420    render_device: Res<RenderDevice>,
421    render_queue: Res<RenderQueue>,
422    mut wide_bind_groups: ResMut<WireframeWideBindGroups>,
423) {
424    wide_bind_groups.bind_groups.clear();
425
426    struct MeshInfo {
427        mesh_id: AssetId<Mesh>,
428        params: WireframeVertexPullParams,
429        vertex_buffer: Buffer,
430        index_buffer: Buffer,
431    }
432
433    let mut infos: Vec<MeshInfo> = Vec::new();
434    let mut seen: HashSet<AssetId<Mesh>, FixedHasher> = HashSet::default();
435
436    for (entity, wireframe_asset_id) in render_wireframe_instances.iter() {
437        let Some(material) = render_wireframe_assets.get(*wireframe_asset_id) else {
438            continue;
439        };
440        if material.line_width <= 1.0 && material.topology != WireframeTopology::Quads {
441            continue;
442        }
443
444        let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*entity) else {
445            continue;
446        };
447        let mesh_id = mesh_instance.mesh_asset_id();
448        if !seen.insert(mesh_id) {
449            continue;
450        }
451
452        let Some(mesh) = render_meshes.get(mesh_id) else {
453            continue;
454        };
455        let Some(vertex_slice) = mesh_allocator.mesh_vertex_slice(&mesh_id) else {
456            continue;
457        };
458        let Some(index_slice) = mesh_allocator.mesh_index_slice(&mesh_id) else {
459            continue;
460        };
461
462        let vertex_stride_bytes = mesh.layout.0.layout().array_stride as u32;
463        let position_offset_bytes = mesh
464            .layout
465            .0
466            .layout()
467            .attributes
468            .first()
469            .map(|a| a.offset as u32)
470            .unwrap_or(0);
471
472        infos.push(MeshInfo {
473            mesh_id,
474            params: WireframeVertexPullParams {
475                index_offset: index_slice.range.start,
476                vertex_stride_u32s: vertex_stride_bytes / 4,
477                position_offset_u32s: position_offset_bytes / 4,
478            },
479            vertex_buffer: vertex_slice.buffer.clone(),
480            index_buffer: index_slice.buffer.clone(),
481        });
482    }
483
484    if infos.is_empty() {
485        return;
486    }
487
488    let Some(mut writer) =
489        wide_bind_groups
490            .params
491            .get_writer(infos.len(), &render_device, &render_queue)
492    else {
493        return;
494    };
495
496    let offsets: Vec<u32> = infos
497        .iter()
498        .map(|info| writer.write(&info.params))
499        .collect();
500    drop(writer);
501
502    let WireframeWideBindGroups {
503        ref params,
504        ref mut bind_groups,
505    } = *wide_bind_groups;
506    let Some(params_binding) = params.binding() else {
507        return;
508    };
509
510    for (i, info) in infos.iter().enumerate() {
511        let bind_group = render_device.create_bind_group(
512            "wireframe_wide_bind_group",
513            &pipeline.wide_bind_group_layout,
514            &BindGroupEntries::sequential((
515                info.vertex_buffer.as_entire_buffer_binding(),
516                info.index_buffer.as_entire_buffer_binding(),
517                params_binding.clone(),
518            )),
519        );
520        bind_groups.insert(info.mesh_id, (bind_group, offsets[i]));
521    }
522}
523
524pub struct SetWireframe3dWideBindGroup;
525
526impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dWideBindGroup {
527    type Param = (SRes<RenderMeshInstances>, SRes<WireframeWideBindGroups>);
528    type ViewQuery = ();
529    type ItemQuery = ();
530
531    #[inline]
532    fn render<'w>(
533        item: &P,
534        _view: (),
535        _item_query: Option<()>,
536        (render_mesh_instances, wide_bind_groups): SystemParamItem<'w, '_, Self::Param>,
537        pass: &mut TrackedRenderPass<'w>,
538    ) -> RenderCommandResult {
539        let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(item.main_entity())
540        else {
541            return RenderCommandResult::Skip;
542        };
543        let Some((bind_group, dynamic_offset)) = wide_bind_groups
544            .into_inner()
545            .bind_groups
546            .get(&mesh_instance.mesh_asset_id())
547        else {
548            return RenderCommandResult::Skip;
549        };
550
551        pass.set_bind_group(3, bind_group, &[*dynamic_offset]);
552        RenderCommandResult::Success
553    }
554}
555
556pub struct DrawWireframeMeshPulled;
557
558impl<P: PhaseItem> RenderCommand<P> for DrawWireframeMeshPulled {
559    type Param = (
560        SRes<RenderMeshInstances>,
561        SRes<RenderAssets<RenderMesh>>,
562        SRes<MeshAllocator>,
563        SRes<IndirectParametersBuffers>,
564        SRes<PipelineCache>,
565        Option<SRes<PreprocessPipelines>>,
566        SRes<GpuPreprocessingSupport>,
567    );
568    type ViewQuery = Has<PreprocessBindGroups>;
569    type ItemQuery = ();
570
571    #[inline]
572    fn render<'w>(
573        item: &P,
574        has_preprocess_bind_group: ROQueryItem<Self::ViewQuery>,
575        _item_query: Option<()>,
576        (
577            render_mesh_instances,
578            render_meshes,
579            mesh_allocator,
580            indirect_parameters_buffers,
581            pipeline_cache,
582            preprocess_pipelines,
583            preprocessing_support,
584        ): SystemParamItem<'w, '_, Self::Param>,
585        pass: &mut TrackedRenderPass<'w>,
586    ) -> RenderCommandResult {
587        if let Some(preprocess_pipelines) = preprocess_pipelines
588            && (!has_preprocess_bind_group
589                || !preprocess_pipelines
590                    .pipelines_are_loaded(&pipeline_cache, &preprocessing_support))
591        {
592            return RenderCommandResult::Skip;
593        }
594
595        let render_mesh_instances = render_mesh_instances.into_inner();
596        let render_meshes = render_meshes.into_inner();
597        let mesh_allocator = mesh_allocator.into_inner();
598        let indirect_parameters_buffers = indirect_parameters_buffers.into_inner();
599
600        let Some(mesh_asset_id) = render_mesh_instances.mesh_asset_id(item.main_entity()) else {
601            return RenderCommandResult::Skip;
602        };
603        let Some(gpu_mesh) = render_meshes.get(mesh_asset_id) else {
604            return RenderCommandResult::Skip;
605        };
606
607        let index_count = match &gpu_mesh.buffer_info {
608            RenderMeshBufferInfo::Indexed { count, .. } => *count,
609            RenderMeshBufferInfo::NonIndexed => gpu_mesh.vertex_count,
610        };
611
612        match item.extra_index() {
613            PhaseItemExtraIndex::None | PhaseItemExtraIndex::DynamicOffset(_) => {
614                // direct draw: use vertex range starting at first_vertex_index so
615                // the shader can recover draw_id via mesh[instance_index].first_vertex_index.
616                let Some(vertex_slice) = mesh_allocator.mesh_vertex_slice(&mesh_asset_id) else {
617                    return RenderCommandResult::Skip;
618                };
619                let first_vertex = vertex_slice.range.start;
620                pass.draw(
621                    first_vertex..(first_vertex + index_count),
622                    item.batch_range().clone(),
623                );
624            }
625            PhaseItemExtraIndex::IndirectParametersIndex {
626                range: indirect_parameters_range,
627                batch_set_index,
628            } => {
629                // no indexes - the preprocessor sets base_vertex = first_vertex_index.
630                let Some(phase_indirect) = indirect_parameters_buffers.get(&TypeId::of::<P>())
631                else {
632                    warn!("Wireframe wide: indirect parameters buffer missing for phase");
633                    return RenderCommandResult::Skip;
634                };
635                let (Some(indirect_buffer), Some(batch_sets_buffer)) = (
636                    phase_indirect.non_indexed.data_buffer(),
637                    phase_indirect.non_indexed.batch_sets_buffer(),
638                ) else {
639                    warn!("Wireframe wide: non-indexed indirect parameters buffer not ready");
640                    return RenderCommandResult::Skip;
641                };
642
643                let indirect_parameters_offset = indirect_parameters_range.start as u64
644                    * size_of::<IndirectParametersNonIndexed>() as u64;
645                let indirect_parameters_count =
646                    indirect_parameters_range.end - indirect_parameters_range.start;
647
648                match batch_set_index {
649                    Some(batch_set_index) => {
650                        let count_offset =
651                            u32::from(batch_set_index) * (size_of::<IndirectBatchSet>() as u32);
652                        pass.multi_draw_indirect_count(
653                            indirect_buffer,
654                            indirect_parameters_offset,
655                            batch_sets_buffer,
656                            count_offset as u64,
657                            indirect_parameters_count,
658                        );
659                    }
660                    None => {
661                        pass.multi_draw_indirect(
662                            indirect_buffer,
663                            indirect_parameters_offset,
664                            indirect_parameters_count,
665                        );
666                    }
667                }
668            }
669        }
670        RenderCommandResult::Success
671    }
672}
673
674/// Draw wireframes with `PolygonMode::Line`, i.e. the fast path.
675pub type DrawWireframe3dThin = (
676    SetItemPipeline,
677    SetMeshViewBindGroup<0>,
678    SetMeshViewBindingArrayBindGroup<1>,
679    SetMeshBindGroup<2>,
680    SetWireframe3dThinImmediates,
681    DrawMesh,
682);
683
684/// Draw wireframes using vertex pulling for wide lines or quad topology.
685pub type DrawWireframe3dWide = (
686    SetItemPipeline,
687    SetMeshViewBindGroup<0>,
688    SetMeshViewBindingArrayBindGroup<1>,
689    SetMeshBindGroup<2>,
690    SetWireframe3dWideBindGroup,
691    SetWireframe3dWideImmediates,
692    DrawWireframeMeshPulled,
693);
694
695#[derive(Resource, Clone)]
696pub struct Wireframe3dPipeline {
697    mesh_pipeline: MeshPipeline,
698    shader: Handle<Shader>,
699    pub wide_bind_group_layout: BindGroupLayout,
700    pub wide_bind_group_layout_descriptor: BindGroupLayoutDescriptor,
701}
702
703pub fn init_wireframe_3d_pipeline(
704    mut commands: Commands,
705    mesh_pipeline: Res<MeshPipeline>,
706    asset_server: Res<AssetServer>,
707    render_device: Res<RenderDevice>,
708) {
709    let wide_bgl_entries = BindGroupLayoutEntries::sequential(
710        ShaderStages::VERTEX,
711        (
712            storage_buffer_read_only::<u32>(false), // vertex data
713            storage_buffer_read_only::<u32>(false), // index data
714            uniform_buffer::<WireframeVertexPullParams>(true),
715        ),
716    );
717
718    let wide_bind_group_layout = render_device
719        .create_bind_group_layout("wireframe_wide_bind_group_layout", &wide_bgl_entries);
720
721    let wide_bind_group_layout_descriptor =
722        BindGroupLayoutDescriptor::new("wireframe_wide_bind_group_layout", &wide_bgl_entries);
723
724    commands.insert_resource(Wireframe3dPipeline {
725        mesh_pipeline: mesh_pipeline.clone(),
726        shader: load_embedded_asset!(asset_server.as_ref(), "render/wireframe.wgsl"),
727        wide_bind_group_layout,
728        wide_bind_group_layout_descriptor,
729    });
730}
731
732#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
733pub struct WireframePipelineKey {
734    pub mesh_key: MeshPipelineKey,
735    pub wide: bool,
736    pub quads: bool,
737    pub line_mode: bool,
738}
739
740impl SpecializedMeshPipeline for Wireframe3dPipeline {
741    type Key = WireframePipelineKey;
742
743    fn specialize(
744        &self,
745        key: Self::Key,
746        layout: &MeshVertexBufferLayoutRef,
747    ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
748        let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?;
749
750        if descriptor.primitive.topology.is_triangles() {
751            descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
752        }
753
754        if key.wide {
755            descriptor.label = Some("wireframe_3d_wide_pipeline".into());
756
757            descriptor.vertex.shader = self.shader.clone();
758            descriptor.vertex.shader_defs.push("WIREFRAME_WIDE".into());
759            if key.quads {
760                descriptor.vertex.shader_defs.push("WIREFRAME_QUADS".into());
761            }
762            descriptor.vertex.entry_point = Some("vertex".into());
763            descriptor.vertex.buffers = vec![]; // vertex pulling from storage
764
765            let fragment = descriptor.fragment.as_mut().unwrap();
766            fragment.shader = self.shader.clone();
767            fragment.shader_defs.push("WIREFRAME_WIDE".into());
768            fragment.entry_point = Some("fragment".into());
769
770            for state in fragment.targets.iter_mut().flatten() {
771                state.blend = Some(BlendState::ALPHA_BLENDING);
772            }
773
774            descriptor.primitive.polygon_mode = if key.line_mode {
775                PolygonMode::Line
776            } else {
777                PolygonMode::Fill
778            };
779            descriptor.immediate_size = 32; // color(16) + line_width(4) + smoothing(4) + pad(8)
780
781            descriptor
782                .layout
783                .push(self.wide_bind_group_layout_descriptor.clone());
784        } else {
785            descriptor.label = Some("wireframe_3d_pipeline".into());
786            descriptor.immediate_size = 16;
787            let fragment = descriptor.fragment.as_mut().unwrap();
788            fragment.shader = self.shader.clone();
789            descriptor.primitive.polygon_mode = PolygonMode::Line;
790        }
791
792        Ok(descriptor)
793    }
794}
795
796pub fn wireframe_3d(
797    world: &World,
798    view: ViewQuery<(
799        &ExtractedCamera,
800        &ExtractedView,
801        &ViewTarget,
802        &ViewDepthTexture,
803    )>,
804    wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
805    mut ctx: RenderContext,
806) {
807    let view_entity = view.entity();
808
809    let (camera, extracted_view, target, depth) = view.into_inner();
810
811    let Some(wireframe_phase) = wireframe_phases.get(&extracted_view.retained_view_entity) else {
812        return;
813    };
814
815    if wireframe_phase.is_empty() {
816        return;
817    }
818
819    let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
820        label: Some("wireframe_3d"),
821        color_attachments: &[Some(target.get_color_attachment())],
822        depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
823        timestamp_writes: None,
824        occlusion_query_set: None,
825        multiview_mask: None,
826    });
827
828    if let Some(viewport) = camera.viewport.as_ref() {
829        render_pass.set_camera_viewport(viewport);
830    }
831
832    if let Err(err) = wireframe_phase.render(&mut render_pass, world, view_entity) {
833        error!("Error encountered while rendering the wireframe phase {err:?}");
834    }
835}
836
837/// Sets the color of the [`Wireframe`] of the entity it is attached to.
838///
839/// If this component is present but there's no [`Wireframe`] component,
840/// it will still affect the color of the wireframe when [`WireframeConfig::global`] is set to true.
841///
842/// This overrides the [`WireframeConfig::default_color`].
843#[derive(Component, Debug, Clone, Default, Reflect)]
844#[reflect(Component, Default, Debug)]
845pub struct WireframeColor {
846    pub color: Color,
847}
848
849/// Sets the line width (in screen-space pixels) of the wireframe.
850///
851/// Overrides [`WireframeConfig::default_line_width`].
852#[derive(Component, Debug, Clone, Reflect)]
853#[reflect(Component, Default, Debug)]
854pub struct WireframeLineWidth {
855    pub width: f32,
856}
857
858impl Default for WireframeLineWidth {
859    fn default() -> Self {
860        Self { width: 1.0 }
861    }
862}
863
864/// Disables wireframe rendering for any entity it is attached to.
865/// It will ignore the [`WireframeConfig`] global setting.
866///
867/// This requires the [`WireframePlugin`] to be enabled.
868#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
869#[reflect(Component, Default, Debug, PartialEq)]
870pub struct NoWireframe;
871
872/// Controls whether wireframe edges follow triangle or quad topology.
873#[derive(Component, Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Reflect)]
874#[reflect(Component, Default, Debug)]
875pub enum WireframeTopology {
876    #[default]
877    Triangles,
878    /// Does a best-effort attempt to detect quads from a triangle mesh. No guarantee of accuracy is made,
879    /// that is, there may be both false positives and false negatives in the rendered output.
880    Quads,
881}
882
883#[derive(Resource, Debug, Clone, ExtractResource, Reflect)]
884#[reflect(Resource, Debug, Default)]
885pub struct WireframeConfig {
886    /// Whether to show wireframes for all meshes.
887    /// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component.
888    pub global: bool,
889    /// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
890    /// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`],
891    /// but no [`WireframeColor`].
892    pub default_color: Color,
893    /// Default line width in screen-space pixels.
894    pub default_line_width: f32,
895    /// Default edge topology.
896    pub default_topology: WireframeTopology,
897}
898
899impl Default for WireframeConfig {
900    fn default() -> Self {
901        Self {
902            global: false,
903            default_color: Color::default(),
904            default_line_width: 1.0,
905            default_topology: WireframeTopology::default(),
906        }
907    }
908}
909
910#[derive(Asset, Reflect, Clone, Debug)]
911#[reflect(Clone, Default)]
912pub struct WireframeMaterial {
913    pub color: Color,
914    pub line_width: f32,
915    pub topology: WireframeTopology,
916}
917
918impl Default for WireframeMaterial {
919    fn default() -> Self {
920        Self {
921            color: Color::default(),
922            line_width: 1.0,
923            topology: WireframeTopology::default(),
924        }
925    }
926}
927
928pub struct RenderWireframeMaterial {
929    pub color: [f32; 4],
930    pub line_width: f32,
931    pub topology: WireframeTopology,
932}
933
934#[derive(
935    Component, FromTemplate, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq,
936)]
937#[reflect(Component, Default, Clone, PartialEq)]
938pub struct Mesh3dWireframe(pub Handle<WireframeMaterial>);
939
940impl AsAssetId for Mesh3dWireframe {
941    type Asset = WireframeMaterial;
942
943    fn as_asset_id(&self) -> AssetId<Self::Asset> {
944        self.0.id()
945    }
946}
947
948impl RenderAsset for RenderWireframeMaterial {
949    type SourceAsset = WireframeMaterial;
950    type Param = ();
951
952    fn prepare_asset(
953        source_asset: Self::SourceAsset,
954        _asset_id: AssetId<Self::SourceAsset>,
955        _param: &mut SystemParamItem<Self::Param>,
956        _previous_asset: Option<&Self>,
957    ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
958        Ok(RenderWireframeMaterial {
959            color: source_asset.color.to_linear().to_f32_array(),
960            line_width: source_asset.line_width,
961            topology: source_asset.topology,
962        })
963    }
964}
965
966#[derive(Resource, Deref, DerefMut, Default)]
967pub struct RenderWireframeInstances(MainEntityHashMap<AssetId<WireframeMaterial>>);
968
969/// Temporarily stores entities that were determined to either need their
970/// specialized pipelines for wireframes updated or to have their specialized
971/// pipelines for wireframes removed.
972#[derive(Clone, Resource, Debug, Default)]
973pub struct WireframeEntitiesNeedingSpecialization {
974    /// Entities that need to have their pipelines updated.
975    pub changed: Vec<Entity>,
976    /// Entities that need to have their pipelines removed.
977    pub removed: Vec<Entity>,
978}
979
980#[derive(Resource, Default)]
981pub struct SpecializedWireframePipelineCache {
982    views: HashMap<RetainedViewEntity, SpecializedWireframeViewPipelineCache>,
983    wide: HashMap<(MeshPipelineKey, MeshVertexBufferLayoutRef, bool, bool), CachedRenderPipelineId>,
984}
985
986#[derive(Deref, DerefMut, Default)]
987pub struct SpecializedWireframeViewPipelineCache {
988    // material entity -> (tick, pipeline_id)
989    #[deref]
990    map: MainEntityHashMap<CachedRenderPipelineId>,
991}
992
993#[derive(Resource)]
994struct GlobalWireframeMaterial {
995    // This handle will be reused when the global config is enabled
996    handle: Handle<WireframeMaterial>,
997}
998
999pub fn extract_wireframe_materials(
1000    mut material_instances: ResMut<RenderWireframeInstances>,
1001    changed_meshes_query: Extract<
1002        Query<
1003            (Entity, &ViewVisibility, &Mesh3dWireframe),
1004            Or<(Changed<ViewVisibility>, Changed<Mesh3dWireframe>)>,
1005        >,
1006    >,
1007    mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
1008    mut removed_materials_query: Extract<RemovedComponents<Mesh3dWireframe>>,
1009) {
1010    for (entity, view_visibility, material) in &changed_meshes_query {
1011        if view_visibility.get() {
1012            material_instances.insert(entity.into(), material.id());
1013        } else {
1014            material_instances.remove(&MainEntity::from(entity));
1015        }
1016    }
1017
1018    for entity in removed_visibilities_query
1019        .read()
1020        .chain(removed_materials_query.read())
1021    {
1022        // Only queue a mesh for removal if we didn't pick it up above.
1023        // It's possible that a necessary component was removed and re-added in
1024        // the same frame.
1025        if !changed_meshes_query.contains(entity) {
1026            material_instances.remove(&MainEntity::from(entity));
1027        }
1028    }
1029}
1030
1031fn setup_global_wireframe_material(
1032    mut commands: Commands,
1033    mut materials: ResMut<Assets<WireframeMaterial>>,
1034    config: Res<WireframeConfig>,
1035) {
1036    commands.insert_resource(GlobalWireframeMaterial {
1037        handle: materials.add(WireframeMaterial {
1038            color: config.default_color,
1039            line_width: config.default_line_width,
1040            topology: config.default_topology,
1041        }),
1042    });
1043}
1044
1045fn wireframe_config_changed(
1046    config: Res<WireframeConfig>,
1047    mut materials: ResMut<Assets<WireframeMaterial>>,
1048    global_material: Res<GlobalWireframeMaterial>,
1049    mut per_entity_wireframes: Query<
1050        (
1051            &mut Mesh3dWireframe,
1052            Option<&WireframeColor>,
1053            Option<&WireframeLineWidth>,
1054            Option<&WireframeTopology>,
1055        ),
1056        With<Wireframe>,
1057    >,
1058) {
1059    if let Some(mut mat) = materials.get_mut(&global_material.handle) {
1060        mat.color = config.default_color;
1061        mat.line_width = config.default_line_width;
1062        mat.topology = config.default_topology;
1063    }
1064
1065    for (mut handle, maybe_color, maybe_width, maybe_topology) in &mut per_entity_wireframes {
1066        if handle.0 == global_material.handle {
1067            continue;
1068        }
1069        handle.0 = materials.add(WireframeMaterial {
1070            color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1071            line_width: maybe_width
1072                .map(|w| w.width)
1073                .unwrap_or(config.default_line_width),
1074            topology: maybe_topology.copied().unwrap_or(config.default_topology),
1075        });
1076    }
1077}
1078
1079fn wireframe_color_changed(
1080    mut materials: ResMut<Assets<WireframeMaterial>>,
1081    mut colors_changed: Query<
1082        (
1083            &mut Mesh3dWireframe,
1084            &WireframeColor,
1085            Option<&WireframeLineWidth>,
1086            Option<&WireframeTopology>,
1087        ),
1088        (With<Wireframe>, Changed<WireframeColor>),
1089    >,
1090    config: Res<WireframeConfig>,
1091) {
1092    for (mut handle, wireframe_color, maybe_width, maybe_topology) in &mut colors_changed {
1093        handle.0 = materials.add(WireframeMaterial {
1094            color: wireframe_color.color,
1095            line_width: maybe_width
1096                .map(|w| w.width)
1097                .unwrap_or(config.default_line_width),
1098            topology: maybe_topology.copied().unwrap_or(config.default_topology),
1099        });
1100    }
1101}
1102
1103fn wireframe_line_width_changed(
1104    mut materials: ResMut<Assets<WireframeMaterial>>,
1105    mut widths_changed: Query<
1106        (
1107            &mut Mesh3dWireframe,
1108            &WireframeLineWidth,
1109            Option<&WireframeColor>,
1110            Option<&WireframeTopology>,
1111        ),
1112        (With<Wireframe>, Changed<WireframeLineWidth>),
1113    >,
1114    config: Res<WireframeConfig>,
1115) {
1116    for (mut handle, wireframe_width, maybe_color, maybe_topology) in &mut widths_changed {
1117        handle.0 = materials.add(WireframeMaterial {
1118            color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1119            line_width: wireframe_width.width,
1120            topology: maybe_topology.copied().unwrap_or(config.default_topology),
1121        });
1122    }
1123}
1124
1125fn wireframe_topology_changed(
1126    mut materials: ResMut<Assets<WireframeMaterial>>,
1127    mut topology_changed: Query<
1128        (
1129            &mut Mesh3dWireframe,
1130            &WireframeTopology,
1131            Option<&WireframeColor>,
1132            Option<&WireframeLineWidth>,
1133        ),
1134        (With<Wireframe>, Changed<WireframeTopology>),
1135    >,
1136    config: Res<WireframeConfig>,
1137) {
1138    for (mut handle, topology, maybe_color, maybe_width) in &mut topology_changed {
1139        handle.0 = materials.add(WireframeMaterial {
1140            color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1141            line_width: maybe_width
1142                .map(|w| w.width)
1143                .unwrap_or(config.default_line_width),
1144            topology: *topology,
1145        });
1146    }
1147}
1148
1149/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component, and removes it
1150/// for any mesh with a [`NoWireframe`] component.
1151fn apply_wireframe_material(
1152    mut commands: Commands,
1153    mut materials: ResMut<Assets<WireframeMaterial>>,
1154    wireframes: Query<
1155        (
1156            Entity,
1157            Option<&WireframeColor>,
1158            Option<&WireframeLineWidth>,
1159            Option<&WireframeTopology>,
1160        ),
1161        (With<Wireframe>, Without<Mesh3dWireframe>),
1162    >,
1163    no_wireframes: Query<Entity, (With<NoWireframe>, With<Mesh3dWireframe>)>,
1164    mut removed_wireframes: RemovedComponents<Wireframe>,
1165    global_material: Res<GlobalWireframeMaterial>,
1166    config: Res<WireframeConfig>,
1167) {
1168    for e in removed_wireframes.read().chain(no_wireframes.iter()) {
1169        if let Ok(mut commands) = commands.get_entity(e) {
1170            commands.remove::<Mesh3dWireframe>();
1171        }
1172    }
1173
1174    let mut material_to_spawn = vec![];
1175    for (e, maybe_color, maybe_width, maybe_topology) in &wireframes {
1176        let material = get_wireframe_material(
1177            maybe_color,
1178            maybe_width,
1179            maybe_topology,
1180            &mut materials,
1181            &global_material,
1182            &config,
1183        );
1184        material_to_spawn.push((e, Mesh3dWireframe(material)));
1185    }
1186    commands.try_insert_batch(material_to_spawn);
1187}
1188
1189type WireframeFilter = (With<Mesh3d>, Without<Wireframe>, Without<NoWireframe>);
1190
1191/// Applies or removes a wireframe material on any mesh without a [`Wireframe`] or [`NoWireframe`] component.
1192fn apply_global_wireframe_material(
1193    mut commands: Commands,
1194    config: Res<WireframeConfig>,
1195    meshes_without_material: Query<
1196        (
1197            Entity,
1198            Option<&WireframeColor>,
1199            Option<&WireframeLineWidth>,
1200            Option<&WireframeTopology>,
1201        ),
1202        (WireframeFilter, Without<Mesh3dWireframe>),
1203    >,
1204    meshes_with_global_material: Query<Entity, (WireframeFilter, With<Mesh3dWireframe>)>,
1205    global_material: Res<GlobalWireframeMaterial>,
1206    mut materials: ResMut<Assets<WireframeMaterial>>,
1207) {
1208    if config.global {
1209        let mut material_to_spawn = vec![];
1210        for (e, maybe_color, maybe_width, maybe_topology) in &meshes_without_material {
1211            let material = get_wireframe_material(
1212                maybe_color,
1213                maybe_width,
1214                maybe_topology,
1215                &mut materials,
1216                &global_material,
1217                &config,
1218            );
1219            // We only add the material handle but not the Wireframe component
1220            // This makes it easy to detect which mesh is using the global material and which ones are user specified
1221            material_to_spawn.push((e, Mesh3dWireframe(material)));
1222        }
1223        commands.try_insert_batch(material_to_spawn);
1224    } else {
1225        for e in &meshes_with_global_material {
1226            commands.entity(e).remove::<Mesh3dWireframe>();
1227        }
1228    }
1229}
1230
1231/// Gets a handle to a wireframe material with a fallback on the default material
1232fn get_wireframe_material(
1233    maybe_color: Option<&WireframeColor>,
1234    maybe_width: Option<&WireframeLineWidth>,
1235    maybe_topology: Option<&WireframeTopology>,
1236    wireframe_materials: &mut Assets<WireframeMaterial>,
1237    global_material: &GlobalWireframeMaterial,
1238    config: &WireframeConfig,
1239) -> Handle<WireframeMaterial> {
1240    if maybe_color.is_some() || maybe_width.is_some() || maybe_topology.is_some() {
1241        wireframe_materials.add(WireframeMaterial {
1242            color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1243            line_width: maybe_width
1244                .map(|w| w.width)
1245                .unwrap_or(config.default_line_width),
1246            topology: maybe_topology.copied().unwrap_or(config.default_topology),
1247        })
1248    } else {
1249        // If there's no color specified we can use the global material since it's already set to use the default_color
1250        global_material.handle.clone()
1251    }
1252}
1253
1254fn extract_wireframe_3d_camera(
1255    mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
1256    cameras: Extract<Query<(Entity, &Camera, Has<NoIndirectDrawing>), With<Camera3d>>>,
1257    mut live_entities: Local<HashSet<RetainedViewEntity>>,
1258    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1259) {
1260    live_entities.clear();
1261    for (main_entity, camera, no_indirect_drawing) in &cameras {
1262        if !camera.is_active {
1263            continue;
1264        }
1265        let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
1266            GpuPreprocessingMode::Culling
1267        } else {
1268            GpuPreprocessingMode::PreprocessingOnly
1269        });
1270
1271        let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0);
1272        wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1273        live_entities.insert(retained_view_entity);
1274    }
1275
1276    // Clear out all dead views.
1277    wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
1278}
1279
1280pub fn extract_wireframe_entities_needing_specialization(
1281    entities_needing_specialization: Extract<Res<WireframeEntitiesNeedingSpecialization>>,
1282    mut dirty_wireframe_specializations: ResMut<DirtyWireframeSpecializations>,
1283) {
1284    // Drain the list of entities needing specialization from the main world
1285    // into the render-world `DirtySpecializations` table.
1286    for entity in entities_needing_specialization.changed.iter() {
1287        dirty_wireframe_specializations
1288            .changed_renderables
1289            .insert(MainEntity::from(*entity));
1290    }
1291}
1292
1293/// A system that adds entities that were judged to need their wireframe
1294/// specializations removed to the appropriate table in
1295/// [`DirtyWireframeSpecializations`].
1296pub fn extract_wireframe_entities_that_need_specializations_removed(
1297    entities_needing_specialization: Extract<Res<WireframeEntitiesNeedingSpecialization>>,
1298    mut dirty_wireframe_specializations: ResMut<DirtyWireframeSpecializations>,
1299) {
1300    for entity in entities_needing_specialization.removed.iter() {
1301        dirty_wireframe_specializations
1302            .removed_renderables
1303            .insert(MainEntity::from(*entity));
1304    }
1305}
1306
1307/// Finds 3D wireframe entities that have changed in such a way as to
1308/// potentially require specialization and adds them to the
1309/// [`WireframeEntitiesNeedingSpecialization`] list.
1310pub fn check_wireframe_entities_needing_specialization(
1311    needs_specialization: Query<
1312        Entity,
1313        Or<(
1314            Changed<Mesh3d>,
1315            AssetChanged<Mesh3d>,
1316            Changed<Mesh3dWireframe>,
1317            AssetChanged<Mesh3dWireframe>,
1318            Changed<WireframeLineWidth>,
1319            Changed<WireframeTopology>,
1320        )>,
1321    >,
1322    mut entities_needing_specialization: ResMut<WireframeEntitiesNeedingSpecialization>,
1323    mut removed_mesh_3d_components: RemovedComponents<Mesh3d>,
1324    mut removed_mesh_3d_wireframe_components: RemovedComponents<Mesh3dWireframe>,
1325) {
1326    entities_needing_specialization.changed.clear();
1327    entities_needing_specialization.removed.clear();
1328
1329    // Gather all entities that need their specializations regenerated.
1330    for entity in &needs_specialization {
1331        entities_needing_specialization.changed.push(entity);
1332    }
1333
1334    // All entities that removed their `Mesh3d` or `Mesh3dWireframe` components
1335    // need to have their specializations removed as well.
1336    //
1337    // It's possible that `Mesh3d` was removed and re-added in the same frame,
1338    // but we don't have to handle that situation specially here, because
1339    // `specialize_wireframes` processes specialization removals before
1340    // additions. So, if the pipeline specialization gets spuriously removed,
1341    // it'll just be immediately re-added again, which is harmless.
1342    for entity in removed_mesh_3d_components
1343        .read()
1344        .chain(removed_mesh_3d_wireframe_components.read())
1345    {
1346        entities_needing_specialization.removed.push(entity);
1347    }
1348}
1349
1350#[derive(Default, Deref, DerefMut, Resource)]
1351pub struct PendingWireframeQueues(pub PendingQueues);
1352
1353pub fn specialize_wireframes(
1354    render_meshes: Res<RenderAssets<RenderMesh>>,
1355    render_mesh_instances: Res<RenderMeshInstances>,
1356    render_wireframe_instances: Res<RenderWireframeInstances>,
1357    render_wireframe_assets: Res<RenderAssets<RenderWireframeMaterial>>,
1358    render_visibility_ranges: Res<RenderVisibilityRanges>,
1359    wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
1360    views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1361    view_key_cache: Res<ViewKeyCache>,
1362    dirty_wireframe_specializations: Res<DirtyWireframeSpecializations>,
1363    mut specialized_material_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
1364    mut pipelines: ResMut<SpecializedMeshPipelines<Wireframe3dPipeline>>,
1365    mut pending_wireframe_queues: ResMut<PendingWireframeQueues>,
1366    pipeline: Res<Wireframe3dPipeline>,
1367    pipeline_cache: Res<PipelineCache>,
1368    render_lightmaps: Res<RenderLightmaps>,
1369) {
1370    let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
1371
1372    let SpecializedWireframePipelineCache {
1373        views: ref mut views_pipeline_cache,
1374        wide: ref mut wide_pipeline_cache,
1375    } = *specialized_material_pipeline_cache;
1376
1377    for (view, visible_entities) in &views {
1378        all_views.insert(view.retained_view_entity);
1379
1380        if !wireframe_phases.contains_key(&view.retained_view_entity) {
1381            continue;
1382        }
1383
1384        let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
1385            continue;
1386        };
1387
1388        let view_specialized_material_pipeline_cache = views_pipeline_cache
1389            .entry(view.retained_view_entity)
1390            .or_default();
1391
1392        let Some(render_visible_mesh_entities) = visible_entities.get::<Mesh3d>() else {
1393            continue;
1394        };
1395
1396        // Initialize the pending queues.
1397        let view_pending_wireframe_queues =
1398            pending_wireframe_queues.prepare_for_new_frame(view.retained_view_entity);
1399
1400        // Remove cached pipeline IDs corresponding to entities that
1401        // either have been removed or need to be respecialized.
1402        if dirty_wireframe_specializations
1403            .must_wipe_specializations_for_view(view.retained_view_entity)
1404        {
1405            view_specialized_material_pipeline_cache.clear();
1406        } else {
1407            for &renderable_entity in dirty_wireframe_specializations.iter_to_despecialize() {
1408                view_specialized_material_pipeline_cache.remove(&renderable_entity);
1409            }
1410        }
1411
1412        // Now process all wireframe meshes that need to be re-specialized.
1413        for (render_entity, visible_entity) in dirty_wireframe_specializations.iter_to_specialize(
1414            view.retained_view_entity,
1415            render_visible_mesh_entities,
1416            &view_pending_wireframe_queues.prev_frame,
1417        ) {
1418            if view_specialized_material_pipeline_cache.contains_key(visible_entity) {
1419                continue;
1420            }
1421
1422            if !render_wireframe_instances.contains_key(visible_entity) {
1423                continue;
1424            };
1425            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1426            else {
1427                // We couldn't fetch the mesh, probably because it hasn't loaded
1428                // yet. Add the entity to the list of pending wireframes and
1429                // bail.
1430                view_pending_wireframe_queues
1431                    .current_frame
1432                    .insert((*render_entity, *visible_entity));
1433                continue;
1434            };
1435            let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id()) else {
1436                continue;
1437            };
1438
1439            let mut mesh_key = *view_key;
1440            mesh_key |= MeshPipelineKey::from_primitive_topology_and_strip_index(
1441                mesh.primitive_topology(),
1442                mesh.index_format(),
1443            );
1444
1445            if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
1446                mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
1447            }
1448
1449            if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
1450                // If the previous frame have skins or morph targets, note that.
1451                if mesh_instance
1452                    .flags()
1453                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
1454                {
1455                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
1456                }
1457                if mesh_instance
1458                    .flags()
1459                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
1460                {
1461                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
1462                }
1463            }
1464
1465            // Even though we don't use the lightmap in the wireframe, the
1466            // `SetMeshBindGroup` render command will bind the data for it. So
1467            // we need to include the appropriate flag in the mesh pipeline key
1468            // to ensure that the necessary bind group layout entries are
1469            // present.
1470            if render_lightmaps
1471                .render_lightmaps
1472                .contains_key(visible_entity)
1473            {
1474                mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1475            }
1476
1477            let mat = render_wireframe_instances
1478                .get(visible_entity)
1479                .and_then(|asset_id| render_wireframe_assets.get(*asset_id));
1480            let quads = mat
1481                .map(|m| m.topology == WireframeTopology::Quads)
1482                .unwrap_or(false);
1483            let thick = mat.map(|m| m.line_width > 1.0).unwrap_or(false);
1484            let wide = thick || quads;
1485            let line_mode = wide && !thick;
1486
1487            let pipeline_id = if wide {
1488                let cache_key = (mesh_key, mesh.layout.clone(), quads, line_mode);
1489                *wide_pipeline_cache.entry(cache_key).or_insert_with(|| {
1490                    let wireframe_key = WireframePipelineKey {
1491                        mesh_key,
1492                        wide: true,
1493                        quads,
1494                        line_mode,
1495                    };
1496                    match pipeline.specialize(wireframe_key, &mesh.layout) {
1497                        Ok(descriptor) => pipeline_cache.queue_render_pipeline(descriptor),
1498                        Err(err) => {
1499                            error!("{}", err);
1500                            CachedRenderPipelineId::INVALID
1501                        }
1502                    }
1503                })
1504            } else {
1505                let wireframe_key = WireframePipelineKey {
1506                    mesh_key,
1507                    wide: false,
1508                    quads: false,
1509                    line_mode: false,
1510                };
1511                match pipelines.specialize(&pipeline_cache, &pipeline, wireframe_key, &mesh.layout)
1512                {
1513                    Ok(id) => id,
1514                    Err(err) => {
1515                        error!("{}", err);
1516                        continue;
1517                    }
1518                }
1519            };
1520
1521            view_specialized_material_pipeline_cache.insert(*visible_entity, pipeline_id);
1522        }
1523    }
1524
1525    pending_wireframe_queues.expire_stale_views(&all_views);
1526
1527    // Delete specialized pipelines belonging to views that have expired.
1528    views_pipeline_cache.retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
1529}
1530
1531fn queue_wireframes(
1532    custom_draw_functions: Res<DrawFunctions<Wireframe3d>>,
1533    render_mesh_instances: Res<RenderMeshInstances>,
1534    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1535    mesh_allocator: Res<MeshAllocator>,
1536    specialized_wireframe_pipeline_cache: Res<SpecializedWireframePipelineCache>,
1537    render_wireframe_instances: Res<RenderWireframeInstances>,
1538    dirty_wireframe_specializations: Res<DirtyWireframeSpecializations>,
1539    render_wireframe_assets: Res<RenderAssets<RenderWireframeMaterial>>,
1540    mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
1541    mut pending_wireframe_queues: ResMut<PendingWireframeQueues>,
1542    mut views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1543) {
1544    for (view, visible_entities) in &mut views {
1545        let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else {
1546            continue;
1547        };
1548        let draw_functions = custom_draw_functions.read();
1549        let draw_thin = draw_functions.id::<DrawWireframe3dThin>();
1550        let draw_wide = draw_functions.id::<DrawWireframe3dWide>();
1551
1552        let Some(view_specialized_material_pipeline_cache) = specialized_wireframe_pipeline_cache
1553            .views
1554            .get(&view.retained_view_entity)
1555        else {
1556            continue;
1557        };
1558
1559        let Some(render_mesh_visible_entities) = visible_entities.get::<Mesh3d>() else {
1560            continue;
1561        };
1562
1563        let view_pending_wireframe_queues = pending_wireframe_queues
1564            .get_mut(&view.retained_view_entity)
1565            .expect(
1566                "View pending wireframe queues should have been created in `specialize_wireframes`",
1567            );
1568
1569        // First, remove meshes that need to be respecialized, and those that were removed, from the bins.
1570        for &main_entity in dirty_wireframe_specializations
1571            .iter_to_dequeue(view.retained_view_entity, render_mesh_visible_entities)
1572        {
1573            wireframe_phase.remove(main_entity);
1574        }
1575
1576        // Now iterate through all newly-visible entities and those needing respecialization.
1577        for (render_entity, visible_entity) in dirty_wireframe_specializations.iter_to_queue(
1578            view.retained_view_entity,
1579            render_mesh_visible_entities,
1580            &view_pending_wireframe_queues.prev_frame,
1581        ) {
1582            let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else {
1583                continue;
1584            };
1585            let Some(pipeline_id) = view_specialized_material_pipeline_cache
1586                .get(visible_entity)
1587                .copied()
1588            else {
1589                continue;
1590            };
1591
1592            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1593            else {
1594                // We couldn't fetch the mesh, probably because it hasn't loaded
1595                // yet. Add the entity to the list of pending wireframes and
1596                // bail.
1597                view_pending_wireframe_queues
1598                    .current_frame
1599                    .insert((*render_entity, *visible_entity));
1600                continue;
1601            };
1602
1603            let is_wide = render_wireframe_assets
1604                .get(*wireframe_instance)
1605                .map(|mat| mat.line_width > 1.0 || mat.topology == WireframeTopology::Quads)
1606                .unwrap_or(false);
1607            let draw_function = if is_wide { draw_wide } else { draw_thin };
1608
1609            let Some(MeshSlabs {
1610                vertex_slab_id: vertex_slab,
1611                index_slab_id: index_slab,
1612                morph_target_slab_id: morph_target_slab,
1613            }) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id())
1614            else {
1615                continue;
1616            };
1617            let bin_key = Wireframe3dBinKey {
1618                asset_id: mesh_instance.mesh_asset_id().untyped(),
1619            };
1620            let batch_set_key = Wireframe3dBatchSetKey {
1621                pipeline: pipeline_id,
1622                asset_id: wireframe_instance.untyped(),
1623                draw_function,
1624                slabs: MeshSlabs {
1625                    vertex_slab_id: vertex_slab,
1626                    morph_target_slab_id: morph_target_slab,
1627                    // wide wireframes use non-indexed draws (vertex pulling
1628                    // from storage), so set index_slab to None to make the
1629                    // preprocessor emit IndirectParametersNonIndexed instead of
1630                    // IndirectParametersIndexed.
1631                    index_slab_id: if is_wide { None } else { index_slab },
1632                },
1633                mesh_asset_id: if is_wide {
1634                    Some(mesh_instance.mesh_asset_id().untyped())
1635                } else {
1636                    None
1637                },
1638            };
1639            wireframe_phase.add(
1640                batch_set_key,
1641                bin_key,
1642                (*render_entity, *visible_entity),
1643                mesh_instance.current_uniform_index,
1644                BinnedRenderPhaseType::mesh(
1645                    mesh_instance.should_batch(),
1646                    &gpu_preprocessing_support,
1647                ),
1648            );
1649        }
1650    }
1651}