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