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#[derive(Debug, Default)]
75pub struct WireframePlugin {
76 pub debug_flags: RenderDebugFlags,
78}
79
80impl WireframePlugin {
81 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 (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 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#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
200#[reflect(Component, Default, Debug, PartialEq)]
201pub struct Wireframe;
202
203pub struct Wireframe3d {
204 pub batch_set_key: Wireframe3dBatchSetKey,
209 pub bin_key: Wireframe3dBinKey,
211 pub representative_entity: (Entity, MainEntity),
214 pub batch_range: Range<u32>,
216 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 pub pipeline: CachedRenderPipelineId,
282
283 pub asset_id: UntypedAssetId,
285
286 pub draw_function: DrawFunctionId,
288
289 pub slabs: MeshSlabs,
296
297 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#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
313pub struct Wireframe3dBinKey {
314 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 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 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
674pub type DrawWireframe3dThin = (
676 SetItemPipeline,
677 SetMeshViewBindGroup<0>,
678 SetMeshViewBindingArrayBindGroup<1>,
679 SetMeshBindGroup<2>,
680 SetWireframe3dThinImmediates,
681 DrawMesh,
682);
683
684pub 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), storage_buffer_read_only::<u32>(false), 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![]; 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; 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#[derive(Component, Debug, Clone, Default, Reflect)]
844#[reflect(Component, Default, Debug)]
845pub struct WireframeColor {
846 pub color: Color,
847}
848
849#[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#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
869#[reflect(Component, Default, Debug, PartialEq)]
870pub struct NoWireframe;
871
872#[derive(Component, Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Reflect)]
874#[reflect(Component, Default, Debug)]
875pub enum WireframeTopology {
876 #[default]
877 Triangles,
878 Quads,
881}
882
883#[derive(Resource, Debug, Clone, ExtractResource, Reflect)]
884#[reflect(Resource, Debug, Default)]
885pub struct WireframeConfig {
886 pub global: bool,
889 pub default_color: Color,
893 pub default_line_width: f32,
895 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#[derive(Clone, Resource, Debug, Default)]
973pub struct WireframeEntitiesNeedingSpecialization {
974 pub changed: Vec<Entity>,
976 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 #[deref]
990 map: MainEntityHashMap<CachedRenderPipelineId>,
991}
992
993#[derive(Resource)]
994struct GlobalWireframeMaterial {
995 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 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
1149fn 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
1191fn 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 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
1231fn 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 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 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 for entity in entities_needing_specialization.changed.iter() {
1287 dirty_wireframe_specializations
1288 .changed_renderables
1289 .insert(MainEntity::from(*entity));
1290 }
1291}
1292
1293pub 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
1307pub 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 for entity in &needs_specialization {
1331 entities_needing_specialization.changed.push(entity);
1332 }
1333
1334 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 let view_pending_wireframe_queues =
1398 pending_wireframe_queues.prepare_for_new_frame(view.retained_view_entity);
1399
1400 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 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 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 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 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 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 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 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 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 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}