1use crate::{
2 DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstanceFlags, RenderMeshInstances,
3 SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup, ViewKeyCache,
4 ViewSpecializationTicks,
5};
6use bevy_app::{App, Plugin, PostUpdate, Startup, Update};
7use bevy_asset::{
8 embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp,
9 AssetEventSystems, AssetId, AssetServer, Assets, Handle, UntypedAssetId,
10};
11use bevy_camera::{visibility::ViewVisibility, Camera, Camera3d};
12use bevy_color::{Color, ColorToComponents};
13use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
14use bevy_derive::{Deref, DerefMut};
15use bevy_ecs::{
16 component::Tick,
17 prelude::*,
18 query::QueryItem,
19 system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem},
20};
21use bevy_mesh::{Mesh3d, MeshVertexBufferLayoutRef};
22use bevy_platform::{
23 collections::{HashMap, HashSet},
24 hash::FixedHasher,
25};
26use bevy_reflect::{std_traits::ReflectDefault, Reflect};
27use bevy_render::{
28 batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
29 camera::{extract_cameras, ExtractedCamera},
30 diagnostic::RecordDiagnostics,
31 extract_resource::ExtractResource,
32 mesh::{
33 allocator::{MeshAllocator, SlabId},
34 RenderMesh,
35 },
36 prelude::*,
37 render_asset::{
38 prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets,
39 },
40 render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, 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, RenderDevice},
49 sync_world::{MainEntity, MainEntityHashMap},
50 view::{
51 ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities,
52 RetainedViewEntity, ViewDepthTexture, ViewTarget,
53 },
54 Extract, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems,
55};
56use bevy_shader::Shader;
57use core::{hash::Hash, ops::Range};
58use tracing::{error, warn};
59
60#[derive(Debug, Default)]
70pub struct WireframePlugin {
71 pub debug_flags: RenderDebugFlags,
73}
74
75impl WireframePlugin {
76 pub fn new(debug_flags: RenderDebugFlags) -> Self {
78 Self { debug_flags }
79 }
80}
81
82impl Plugin for WireframePlugin {
83 fn build(&self, app: &mut App) {
84 embedded_asset!(app, "render/wireframe.wgsl");
85
86 app.add_plugins((
87 BinnedRenderPhasePlugin::<Wireframe3d, MeshPipeline>::new(self.debug_flags),
88 RenderAssetPlugin::<RenderWireframeMaterial>::default(),
89 ))
90 .init_asset::<WireframeMaterial>()
91 .init_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
92 .init_resource::<WireframeConfig>()
93 .init_resource::<WireframeEntitiesNeedingSpecialization>()
94 .add_systems(Startup, setup_global_wireframe_material)
95 .add_systems(
96 Update,
97 (
98 global_color_changed.run_if(resource_changed::<WireframeConfig>),
99 wireframe_color_changed,
100 (apply_wireframe_material, apply_global_wireframe_material).chain(),
103 ),
104 )
105 .add_systems(
106 PostUpdate,
107 check_wireframe_entities_needing_specialization
108 .after(AssetEventSystems)
109 .run_if(resource_exists::<WireframeConfig>),
110 );
111 }
112
113 fn finish(&self, app: &mut App) {
114 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
115 return;
116 };
117
118 let required_features = WgpuFeatures::POLYGON_MODE_LINE | WgpuFeatures::PUSH_CONSTANTS;
119 let render_device = render_app.world().resource::<RenderDevice>();
120 if !render_device.features().contains(required_features) {
121 warn!(
122 "WireframePlugin not loaded. GPU lacks support for required features: {:?}.",
123 required_features
124 );
125 return;
126 }
127
128 render_app
129 .init_resource::<WireframeEntitySpecializationTicks>()
130 .init_resource::<SpecializedWireframePipelineCache>()
131 .init_resource::<DrawFunctions<Wireframe3d>>()
132 .add_render_command::<Wireframe3d, DrawWireframe3d>()
133 .init_resource::<RenderWireframeInstances>()
134 .init_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
135 .add_render_graph_node::<ViewNodeRunner<Wireframe3dNode>>(Core3d, Node3d::Wireframe)
136 .add_render_graph_edges(
137 Core3d,
138 (
139 Node3d::EndMainPass,
140 Node3d::Wireframe,
141 Node3d::PostProcessing,
142 ),
143 )
144 .add_systems(RenderStartup, init_wireframe_3d_pipeline)
145 .add_systems(
146 ExtractSchedule,
147 (
148 extract_wireframe_3d_camera,
149 extract_wireframe_entities_needing_specialization.after(extract_cameras),
150 extract_wireframe_materials,
151 ),
152 )
153 .add_systems(
154 Render,
155 (
156 specialize_wireframes
157 .in_set(RenderSystems::PrepareMeshes)
158 .after(prepare_assets::<RenderWireframeMaterial>)
159 .after(prepare_assets::<RenderMesh>),
160 queue_wireframes
161 .in_set(RenderSystems::QueueMeshes)
162 .after(prepare_assets::<RenderWireframeMaterial>),
163 ),
164 );
165 }
166}
167
168#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
173#[reflect(Component, Default, Debug, PartialEq)]
174pub struct Wireframe;
175
176pub struct Wireframe3d {
177 pub batch_set_key: Wireframe3dBatchSetKey,
182 pub bin_key: Wireframe3dBinKey,
184 pub representative_entity: (Entity, MainEntity),
187 pub batch_range: Range<u32>,
189 pub extra_index: PhaseItemExtraIndex,
192}
193
194impl PhaseItem for Wireframe3d {
195 fn entity(&self) -> Entity {
196 self.representative_entity.0
197 }
198
199 fn main_entity(&self) -> MainEntity {
200 self.representative_entity.1
201 }
202
203 fn draw_function(&self) -> DrawFunctionId {
204 self.batch_set_key.draw_function
205 }
206
207 fn batch_range(&self) -> &Range<u32> {
208 &self.batch_range
209 }
210
211 fn batch_range_mut(&mut self) -> &mut Range<u32> {
212 &mut self.batch_range
213 }
214
215 fn extra_index(&self) -> PhaseItemExtraIndex {
216 self.extra_index.clone()
217 }
218
219 fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
220 (&mut self.batch_range, &mut self.extra_index)
221 }
222}
223
224impl CachedRenderPipelinePhaseItem for Wireframe3d {
225 fn cached_pipeline(&self) -> CachedRenderPipelineId {
226 self.batch_set_key.pipeline
227 }
228}
229
230impl BinnedPhaseItem for Wireframe3d {
231 type BinKey = Wireframe3dBinKey;
232 type BatchSetKey = Wireframe3dBatchSetKey;
233
234 fn new(
235 batch_set_key: Self::BatchSetKey,
236 bin_key: Self::BinKey,
237 representative_entity: (Entity, MainEntity),
238 batch_range: Range<u32>,
239 extra_index: PhaseItemExtraIndex,
240 ) -> Self {
241 Self {
242 batch_set_key,
243 bin_key,
244 representative_entity,
245 batch_range,
246 extra_index,
247 }
248 }
249}
250
251#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
252pub struct Wireframe3dBatchSetKey {
253 pub pipeline: CachedRenderPipelineId,
255
256 pub asset_id: UntypedAssetId,
258
259 pub draw_function: DrawFunctionId,
261 pub vertex_slab: SlabId,
266
267 pub index_slab: Option<SlabId>,
271}
272
273impl PhaseItemBatchSetKey for Wireframe3dBatchSetKey {
274 fn indexed(&self) -> bool {
275 self.index_slab.is_some()
276 }
277}
278
279#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
283pub struct Wireframe3dBinKey {
284 pub asset_id: UntypedAssetId,
286}
287
288pub struct SetWireframe3dPushConstants;
289
290impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dPushConstants {
291 type Param = (
292 SRes<RenderWireframeInstances>,
293 SRes<RenderAssets<RenderWireframeMaterial>>,
294 );
295 type ViewQuery = ();
296 type ItemQuery = ();
297
298 #[inline]
299 fn render<'w>(
300 item: &P,
301 _view: (),
302 _item_query: Option<()>,
303 (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,
304 pass: &mut TrackedRenderPass<'w>,
305 ) -> RenderCommandResult {
306 let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {
307 return RenderCommandResult::Failure("No wireframe material found for entity");
308 };
309 let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {
310 return RenderCommandResult::Failure("No wireframe material found for entity");
311 };
312
313 pass.set_push_constants(
314 ShaderStages::FRAGMENT,
315 0,
316 bytemuck::bytes_of(&wireframe_material.color),
317 );
318 RenderCommandResult::Success
319 }
320}
321
322pub type DrawWireframe3d = (
323 SetItemPipeline,
324 SetMeshViewBindGroup<0>,
325 SetMeshViewBindingArrayBindGroup<1>,
326 SetMeshBindGroup<2>,
327 SetWireframe3dPushConstants,
328 DrawMesh,
329);
330
331#[derive(Resource, Clone)]
332pub struct Wireframe3dPipeline {
333 mesh_pipeline: MeshPipeline,
334 shader: Handle<Shader>,
335}
336
337pub fn init_wireframe_3d_pipeline(
338 mut commands: Commands,
339 mesh_pipeline: Res<MeshPipeline>,
340 asset_server: Res<AssetServer>,
341) {
342 commands.insert_resource(Wireframe3dPipeline {
343 mesh_pipeline: mesh_pipeline.clone(),
344 shader: load_embedded_asset!(asset_server.as_ref(), "render/wireframe.wgsl"),
345 });
346}
347
348impl SpecializedMeshPipeline for Wireframe3dPipeline {
349 type Key = MeshPipelineKey;
350
351 fn specialize(
352 &self,
353 key: Self::Key,
354 layout: &MeshVertexBufferLayoutRef,
355 ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
356 let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
357 descriptor.label = Some("wireframe_3d_pipeline".into());
358 descriptor.push_constant_ranges.push(PushConstantRange {
359 stages: ShaderStages::FRAGMENT,
360 range: 0..16,
361 });
362 let fragment = descriptor.fragment.as_mut().unwrap();
363 fragment.shader = self.shader.clone();
364 descriptor.primitive.polygon_mode = PolygonMode::Line;
365 descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
366 Ok(descriptor)
367 }
368}
369
370#[derive(Default)]
371struct Wireframe3dNode;
372impl ViewNode for Wireframe3dNode {
373 type ViewQuery = (
374 &'static ExtractedCamera,
375 &'static ExtractedView,
376 &'static ViewTarget,
377 &'static ViewDepthTexture,
378 );
379
380 fn run<'w>(
381 &self,
382 graph: &mut RenderGraphContext,
383 render_context: &mut RenderContext<'w>,
384 (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>,
385 world: &'w World,
386 ) -> Result<(), NodeRunError> {
387 let Some(wireframe_phase) = world.get_resource::<ViewBinnedRenderPhases<Wireframe3d>>()
388 else {
389 return Ok(());
390 };
391
392 let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else {
393 return Ok(());
394 };
395
396 let diagnostics = render_context.diagnostic_recorder();
397
398 let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
399 label: Some("wireframe_3d"),
400 color_attachments: &[Some(target.get_color_attachment())],
401 depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
402 timestamp_writes: None,
403 occlusion_query_set: None,
404 });
405 let pass_span = diagnostics.pass_span(&mut render_pass, "wireframe_3d");
406
407 if let Some(viewport) = camera.viewport.as_ref() {
408 render_pass.set_camera_viewport(viewport);
409 }
410
411 if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) {
412 error!("Error encountered while rendering the stencil phase {err:?}");
413 return Err(NodeRunError::DrawError(err));
414 }
415
416 pass_span.end(&mut render_pass);
417
418 Ok(())
419 }
420}
421
422#[derive(Component, Debug, Clone, Default, Reflect)]
429#[reflect(Component, Default, Debug)]
430pub struct WireframeColor {
431 pub color: Color,
432}
433
434#[derive(Component, Debug, Clone, Default)]
435pub struct ExtractedWireframeColor {
436 pub color: [f32; 4],
437}
438
439#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
444#[reflect(Component, Default, Debug, PartialEq)]
445pub struct NoWireframe;
446
447#[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)]
448#[reflect(Resource, Debug, Default)]
449pub struct WireframeConfig {
450 pub global: bool,
453 pub default_color: Color,
457}
458
459#[derive(Asset, Reflect, Clone, Debug, Default)]
460#[reflect(Clone, Default)]
461pub struct WireframeMaterial {
462 pub color: Color,
463}
464
465pub struct RenderWireframeMaterial {
466 pub color: [f32; 4],
467}
468
469#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)]
470#[reflect(Component, Default, Clone, PartialEq)]
471pub struct Mesh3dWireframe(pub Handle<WireframeMaterial>);
472
473impl AsAssetId for Mesh3dWireframe {
474 type Asset = WireframeMaterial;
475
476 fn as_asset_id(&self) -> AssetId<Self::Asset> {
477 self.0.id()
478 }
479}
480
481impl RenderAsset for RenderWireframeMaterial {
482 type SourceAsset = WireframeMaterial;
483 type Param = ();
484
485 fn prepare_asset(
486 source_asset: Self::SourceAsset,
487 _asset_id: AssetId<Self::SourceAsset>,
488 _param: &mut SystemParamItem<Self::Param>,
489 _previous_asset: Option<&Self>,
490 ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
491 Ok(RenderWireframeMaterial {
492 color: source_asset.color.to_linear().to_f32_array(),
493 })
494 }
495}
496
497#[derive(Resource, Deref, DerefMut, Default)]
498pub struct RenderWireframeInstances(MainEntityHashMap<AssetId<WireframeMaterial>>);
499
500#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)]
501pub struct WireframeEntitiesNeedingSpecialization {
502 #[deref]
503 pub entities: Vec<Entity>,
504}
505
506#[derive(Resource, Deref, DerefMut, Clone, Debug, Default)]
507pub struct WireframeEntitySpecializationTicks {
508 pub entities: MainEntityHashMap<Tick>,
509}
510
511#[derive(Resource, Deref, DerefMut, Default)]
513pub struct SpecializedWireframePipelineCache {
514 #[deref]
516 map: HashMap<RetainedViewEntity, SpecializedWireframeViewPipelineCache>,
517}
518
519#[derive(Deref, DerefMut, Default)]
522pub struct SpecializedWireframeViewPipelineCache {
523 #[deref]
525 map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
526}
527
528#[derive(Resource)]
529struct GlobalWireframeMaterial {
530 handle: Handle<WireframeMaterial>,
532}
533
534pub fn extract_wireframe_materials(
535 mut material_instances: ResMut<RenderWireframeInstances>,
536 changed_meshes_query: Extract<
537 Query<
538 (Entity, &ViewVisibility, &Mesh3dWireframe),
539 Or<(Changed<ViewVisibility>, Changed<Mesh3dWireframe>)>,
540 >,
541 >,
542 mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
543 mut removed_materials_query: Extract<RemovedComponents<Mesh3dWireframe>>,
544) {
545 for (entity, view_visibility, material) in &changed_meshes_query {
546 if view_visibility.get() {
547 material_instances.insert(entity.into(), material.id());
548 } else {
549 material_instances.remove(&MainEntity::from(entity));
550 }
551 }
552
553 for entity in removed_visibilities_query
554 .read()
555 .chain(removed_materials_query.read())
556 {
557 if !changed_meshes_query.contains(entity) {
561 material_instances.remove(&MainEntity::from(entity));
562 }
563 }
564}
565
566fn setup_global_wireframe_material(
567 mut commands: Commands,
568 mut materials: ResMut<Assets<WireframeMaterial>>,
569 config: Res<WireframeConfig>,
570) {
571 commands.insert_resource(GlobalWireframeMaterial {
573 handle: materials.add(WireframeMaterial {
574 color: config.default_color,
575 }),
576 });
577}
578
579fn global_color_changed(
581 config: Res<WireframeConfig>,
582 mut materials: ResMut<Assets<WireframeMaterial>>,
583 global_material: Res<GlobalWireframeMaterial>,
584) {
585 if let Some(global_material) = materials.get_mut(&global_material.handle) {
586 global_material.color = config.default_color;
587 }
588}
589
590fn wireframe_color_changed(
592 mut materials: ResMut<Assets<WireframeMaterial>>,
593 mut colors_changed: Query<
594 (&mut Mesh3dWireframe, &WireframeColor),
595 (With<Wireframe>, Changed<WireframeColor>),
596 >,
597) {
598 for (mut handle, wireframe_color) in &mut colors_changed {
599 handle.0 = materials.add(WireframeMaterial {
600 color: wireframe_color.color,
601 });
602 }
603}
604
605fn apply_wireframe_material(
608 mut commands: Commands,
609 mut materials: ResMut<Assets<WireframeMaterial>>,
610 wireframes: Query<
611 (Entity, Option<&WireframeColor>),
612 (With<Wireframe>, Without<Mesh3dWireframe>),
613 >,
614 no_wireframes: Query<Entity, (With<NoWireframe>, With<Mesh3dWireframe>)>,
615 mut removed_wireframes: RemovedComponents<Wireframe>,
616 global_material: Res<GlobalWireframeMaterial>,
617) {
618 for e in removed_wireframes.read().chain(no_wireframes.iter()) {
619 if let Ok(mut commands) = commands.get_entity(e) {
620 commands.remove::<Mesh3dWireframe>();
621 }
622 }
623
624 let mut material_to_spawn = vec![];
625 for (e, maybe_color) in &wireframes {
626 let material = get_wireframe_material(maybe_color, &mut materials, &global_material);
627 material_to_spawn.push((e, Mesh3dWireframe(material)));
628 }
629 commands.try_insert_batch(material_to_spawn);
630}
631
632type WireframeFilter = (With<Mesh3d>, Without<Wireframe>, Without<NoWireframe>);
633
634fn apply_global_wireframe_material(
636 mut commands: Commands,
637 config: Res<WireframeConfig>,
638 meshes_without_material: Query<
639 (Entity, Option<&WireframeColor>),
640 (WireframeFilter, Without<Mesh3dWireframe>),
641 >,
642 meshes_with_global_material: Query<Entity, (WireframeFilter, With<Mesh3dWireframe>)>,
643 global_material: Res<GlobalWireframeMaterial>,
644 mut materials: ResMut<Assets<WireframeMaterial>>,
645) {
646 if config.global {
647 let mut material_to_spawn = vec![];
648 for (e, maybe_color) in &meshes_without_material {
649 let material = get_wireframe_material(maybe_color, &mut materials, &global_material);
650 material_to_spawn.push((e, Mesh3dWireframe(material)));
653 }
654 commands.try_insert_batch(material_to_spawn);
655 } else {
656 for e in &meshes_with_global_material {
657 commands.entity(e).remove::<Mesh3dWireframe>();
658 }
659 }
660}
661
662fn get_wireframe_material(
664 maybe_color: Option<&WireframeColor>,
665 wireframe_materials: &mut Assets<WireframeMaterial>,
666 global_material: &GlobalWireframeMaterial,
667) -> Handle<WireframeMaterial> {
668 if let Some(wireframe_color) = maybe_color {
669 wireframe_materials.add(WireframeMaterial {
670 color: wireframe_color.color,
671 })
672 } else {
673 global_material.handle.clone()
675 }
676}
677
678fn extract_wireframe_3d_camera(
679 mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
680 cameras: Extract<Query<(Entity, &Camera, Has<NoIndirectDrawing>), With<Camera3d>>>,
681 mut live_entities: Local<HashSet<RetainedViewEntity>>,
682 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
683) {
684 live_entities.clear();
685 for (main_entity, camera, no_indirect_drawing) in &cameras {
686 if !camera.is_active {
687 continue;
688 }
689 let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
690 GpuPreprocessingMode::Culling
691 } else {
692 GpuPreprocessingMode::PreprocessingOnly
693 });
694
695 let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0);
696 wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
697 live_entities.insert(retained_view_entity);
698 }
699
700 wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
702}
703
704pub fn extract_wireframe_entities_needing_specialization(
705 entities_needing_specialization: Extract<Res<WireframeEntitiesNeedingSpecialization>>,
706 mut entity_specialization_ticks: ResMut<WireframeEntitySpecializationTicks>,
707 views: Query<&ExtractedView>,
708 mut specialized_wireframe_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
709 mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
710 ticks: SystemChangeTick,
711) {
712 for entity in entities_needing_specialization.iter() {
713 entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
715 }
716
717 for entity in removed_meshes_query.read() {
718 for view in &views {
719 if let Some(specialized_wireframe_pipeline_cache) =
720 specialized_wireframe_pipeline_cache.get_mut(&view.retained_view_entity)
721 {
722 specialized_wireframe_pipeline_cache.remove(&MainEntity::from(entity));
723 }
724 }
725 }
726}
727
728pub fn check_wireframe_entities_needing_specialization(
729 needs_specialization: Query<
730 Entity,
731 Or<(
732 Changed<Mesh3d>,
733 AssetChanged<Mesh3d>,
734 Changed<Mesh3dWireframe>,
735 AssetChanged<Mesh3dWireframe>,
736 )>,
737 >,
738 mut entities_needing_specialization: ResMut<WireframeEntitiesNeedingSpecialization>,
739) {
740 entities_needing_specialization.clear();
741 for entity in &needs_specialization {
742 entities_needing_specialization.push(entity);
743 }
744}
745
746pub fn specialize_wireframes(
747 render_meshes: Res<RenderAssets<RenderMesh>>,
748 render_mesh_instances: Res<RenderMeshInstances>,
749 render_wireframe_instances: Res<RenderWireframeInstances>,
750 render_visibility_ranges: Res<RenderVisibilityRanges>,
751 wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
752 views: Query<(&ExtractedView, &RenderVisibleEntities)>,
753 view_key_cache: Res<ViewKeyCache>,
754 entity_specialization_ticks: Res<WireframeEntitySpecializationTicks>,
755 view_specialization_ticks: Res<ViewSpecializationTicks>,
756 mut specialized_material_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
757 mut pipelines: ResMut<SpecializedMeshPipelines<Wireframe3dPipeline>>,
758 pipeline: Res<Wireframe3dPipeline>,
759 pipeline_cache: Res<PipelineCache>,
760 ticks: SystemChangeTick,
761) {
762 let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
765
766 for (view, visible_entities) in &views {
767 all_views.insert(view.retained_view_entity);
768
769 if !wireframe_phases.contains_key(&view.retained_view_entity) {
770 continue;
771 }
772
773 let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
774 continue;
775 };
776
777 let view_tick = view_specialization_ticks
778 .get(&view.retained_view_entity)
779 .unwrap();
780 let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
781 .entry(view.retained_view_entity)
782 .or_default();
783
784 for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
785 if !render_wireframe_instances.contains_key(visible_entity) {
786 continue;
787 };
788 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
789 else {
790 continue;
791 };
792 let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
793 let last_specialized_tick = view_specialized_material_pipeline_cache
794 .get(visible_entity)
795 .map(|(tick, _)| *tick);
796 let needs_specialization = last_specialized_tick.is_none_or(|tick| {
797 view_tick.is_newer_than(tick, ticks.this_run())
798 || entity_tick.is_newer_than(tick, ticks.this_run())
799 });
800 if !needs_specialization {
801 continue;
802 }
803 let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
804 continue;
805 };
806
807 let mut mesh_key = *view_key;
808 mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology());
809
810 if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
811 mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
812 }
813
814 if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
815 if mesh_instance
817 .flags
818 .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
819 {
820 mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
821 }
822 if mesh_instance
823 .flags
824 .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
825 {
826 mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
827 }
828 }
829
830 let pipeline_id =
831 pipelines.specialize(&pipeline_cache, &pipeline, mesh_key, &mesh.layout);
832 let pipeline_id = match pipeline_id {
833 Ok(id) => id,
834 Err(err) => {
835 error!("{}", err);
836 continue;
837 }
838 };
839
840 view_specialized_material_pipeline_cache
841 .insert(*visible_entity, (ticks.this_run(), pipeline_id));
842 }
843 }
844
845 specialized_material_pipeline_cache
847 .retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
848}
849
850fn queue_wireframes(
851 custom_draw_functions: Res<DrawFunctions<Wireframe3d>>,
852 render_mesh_instances: Res<RenderMeshInstances>,
853 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
854 mesh_allocator: Res<MeshAllocator>,
855 specialized_wireframe_pipeline_cache: Res<SpecializedWireframePipelineCache>,
856 render_wireframe_instances: Res<RenderWireframeInstances>,
857 mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
858 mut views: Query<(&ExtractedView, &RenderVisibleEntities)>,
859) {
860 for (view, visible_entities) in &mut views {
861 let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else {
862 continue;
863 };
864 let draw_wireframe = custom_draw_functions.read().id::<DrawWireframe3d>();
865
866 let Some(view_specialized_material_pipeline_cache) =
867 specialized_wireframe_pipeline_cache.get(&view.retained_view_entity)
868 else {
869 continue;
870 };
871
872 for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
873 let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else {
874 continue;
875 };
876 let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache
877 .get(visible_entity)
878 .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))
879 else {
880 continue;
881 };
882
883 if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) {
885 continue;
886 }
887 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
888 else {
889 continue;
890 };
891 let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
892 let bin_key = Wireframe3dBinKey {
893 asset_id: mesh_instance.mesh_asset_id.untyped(),
894 };
895 let batch_set_key = Wireframe3dBatchSetKey {
896 pipeline: pipeline_id,
897 asset_id: wireframe_instance.untyped(),
898 draw_function: draw_wireframe,
899 vertex_slab: vertex_slab.unwrap_or_default(),
900 index_slab,
901 };
902 wireframe_phase.add(
903 batch_set_key,
904 bin_key,
905 (*render_entity, *visible_entity),
906 mesh_instance.current_uniform_index,
907 BinnedRenderPhaseType::mesh(
908 mesh_instance.should_batch(),
909 &gpu_preprocessing_support,
910 ),
911 current_change_tick,
912 );
913 }
914 }
915}