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#[derive(Debug, Default)]
72pub struct WireframePlugin {
73 pub debug_flags: RenderDebugFlags,
75}
76
77impl WireframePlugin {
78 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 (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#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
177#[reflect(Component, Default, Debug, PartialEq)]
178pub struct Wireframe;
179
180pub struct Wireframe3d {
181 pub batch_set_key: Wireframe3dBatchSetKey,
186 pub bin_key: Wireframe3dBinKey,
188 pub representative_entity: (Entity, MainEntity),
191 pub batch_range: Range<u32>,
193 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 pub pipeline: CachedRenderPipelineId,
259
260 pub asset_id: UntypedAssetId,
262
263 pub draw_function: DrawFunctionId,
265 pub vertex_slab: SlabId,
270
271 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#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
287pub struct Wireframe3dBinKey {
288 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#[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#[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 pub global: bool,
449 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#[derive(Resource, Deref, DerefMut, Default)]
508pub struct SpecializedWireframePipelineCache {
509 #[deref]
511 map: HashMap<RetainedViewEntity, SpecializedWireframeViewPipelineCache>,
512}
513
514#[derive(Deref, DerefMut, Default)]
517pub struct SpecializedWireframeViewPipelineCache {
518 #[deref]
520 map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
521}
522
523#[derive(Resource)]
524struct GlobalWireframeMaterial {
525 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 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 commands.insert_resource(GlobalWireframeMaterial {
568 handle: materials.add(WireframeMaterial {
569 color: config.default_color,
570 }),
571 });
572}
573
574fn 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
585fn 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
600fn 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
629fn 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 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
657fn 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 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 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 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 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 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 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 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}