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 render_asset::{
42 prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets,
43 },
44 render_phase::{
45 AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType,
46 CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
47 PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,
48 SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases,
49 },
50 render_resource::{binding_types::*, *},
51 renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
52 sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet},
53 view::{
54 ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities,
55 RetainedViewEntity, ViewDepthStencilTexture, ViewTarget,
56 },
57 Extract, ExtractSchedule, GpuResourceAppExt, Render, RenderApp, RenderDebugFlags,
58 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.wesl");
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 .register_type::<WireframeXray>()
103 .add_systems(Startup, setup_global_wireframe_material)
104 .add_systems(
105 PostUpdate,
106 (
107 wireframe_config_changed.run_if(resource_changed::<WireframeConfig>),
108 wireframe_color_changed,
109 wireframe_line_width_changed,
110 wireframe_topology_changed,
111 wireframe_xray_changed,
112 (apply_wireframe_material, apply_global_wireframe_material).chain(),
115 ),
116 )
117 .add_systems(
118 PostUpdate,
119 check_wireframe_entities_needing_specialization
120 .after(AssetEventSystems)
121 .run_if(resource_exists::<WireframeConfig>),
122 );
123 }
124
125 fn finish(&self, app: &mut App) {
126 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
127 return;
128 };
129
130 let required_features = WgpuFeatures::POLYGON_MODE_LINE | WgpuFeatures::IMMEDIATES;
131 let render_device = render_app.world().resource::<RenderDevice>();
132 if !render_device.features().contains(required_features) {
133 warn!(
134 "WireframePlugin not loaded. GPU lacks support for required features: {:?}.",
135 required_features
136 );
137 return;
138 }
139
140 render_app
142 .world_mut()
143 .resource_mut::<MeshAllocatorSettings>()
144 .extra_buffer_usages |= BufferUsages::STORAGE;
145
146 render_app
147 .init_gpu_resource::<SpecializedWireframePipelineCache>()
148 .init_resource::<DrawFunctions<Wireframe3d>>()
149 .add_render_command::<Wireframe3d, DrawWireframe3dThin>()
150 .add_render_command::<Wireframe3d, DrawWireframe3dWide>()
151 .init_gpu_resource::<RenderWireframeInstances>()
152 .init_gpu_resource::<WireframeWideBindGroups>()
153 .init_gpu_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
154 .init_gpu_resource::<PendingWireframeQueues>()
155 .add_systems(
156 RenderStartup,
157 init_wireframe_3d_pipeline.after(MeshPipelineSystems),
158 )
159 .add_systems(
160 Core3d,
161 wireframe_3d
162 .after(Core3dSystems::MainPass)
163 .before(Core3dSystems::EarlyPostProcess),
164 )
165 .add_systems(
166 ExtractSchedule,
167 (
168 extract_wireframe_3d_camera,
169 extract_wireframe_entities_needing_specialization
170 .in_set(DirtySpecializationSystems::CheckForChanges),
171 extract_wireframe_entities_that_need_specializations_removed
172 .in_set(DirtySpecializationSystems::CheckForRemovals),
173 extract_wireframe_materials,
174 ),
175 )
176 .add_systems(
177 Render,
178 (
179 specialize_wireframes
180 .in_set(RenderSystems::Specialize)
181 .after(prepare_assets::<RenderWireframeMaterial>)
182 .after(prepare_assets::<RenderMesh>)
183 .after(collect_meshes_for_gpu_building)
184 .after(set_mesh_motion_vector_flags),
185 prepare_wireframe_wide_bind_groups
186 .in_set(RenderSystems::PrepareBindGroups)
187 .after(prepare_assets::<RenderWireframeMaterial>)
188 .after(prepare_assets::<RenderMesh>),
189 queue_wireframes
190 .in_set(RenderSystems::QueueMeshes)
191 .after(prepare_assets::<RenderWireframeMaterial>),
192 ),
193 );
194 }
195}
196
197#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
202#[reflect(Component, Default, Debug, PartialEq)]
203pub struct Wireframe;
204
205pub struct Wireframe3d {
206 pub batch_set_key: Wireframe3dBatchSetKey,
211 pub bin_key: Wireframe3dBinKey,
213 pub representative_entity: (Entity, MainEntity),
216 pub batch_range: Range<u32>,
218 pub extra_index: PhaseItemExtraIndex,
221}
222
223impl PhaseItem for Wireframe3d {
224 fn entity(&self) -> Entity {
225 self.representative_entity.0
226 }
227
228 fn main_entity(&self) -> MainEntity {
229 self.representative_entity.1
230 }
231
232 fn draw_function(&self) -> DrawFunctionId {
233 self.batch_set_key.draw_function
234 }
235
236 fn batch_range(&self) -> &Range<u32> {
237 &self.batch_range
238 }
239
240 fn batch_range_mut(&mut self) -> &mut Range<u32> {
241 &mut self.batch_range
242 }
243
244 fn extra_index(&self) -> PhaseItemExtraIndex {
245 self.extra_index.clone()
246 }
247
248 fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
249 (&mut self.batch_range, &mut self.extra_index)
250 }
251}
252
253impl CachedRenderPipelinePhaseItem for Wireframe3d {
254 fn cached_pipeline(&self) -> CachedRenderPipelineId {
255 self.batch_set_key.pipeline
256 }
257}
258
259impl BinnedPhaseItem for Wireframe3d {
260 type BinKey = Wireframe3dBinKey;
261 type BatchSetKey = Wireframe3dBatchSetKey;
262
263 fn new(
264 batch_set_key: Self::BatchSetKey,
265 bin_key: Self::BinKey,
266 representative_entity: (Entity, MainEntity),
267 batch_range: Range<u32>,
268 extra_index: PhaseItemExtraIndex,
269 ) -> Self {
270 Self {
271 batch_set_key,
272 bin_key,
273 representative_entity,
274 batch_range,
275 extra_index,
276 }
277 }
278}
279
280#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub struct Wireframe3dBatchSetKey {
282 pub pipeline: CachedRenderPipelineId,
284
285 pub asset_id: UntypedAssetId,
287
288 pub draw_function: DrawFunctionId,
290
291 pub slabs: MeshSlabs,
298
299 pub mesh_asset_id: Option<UntypedAssetId>,
303}
304
305impl PhaseItemBatchSetKey for Wireframe3dBatchSetKey {
306 fn indexed(&self) -> bool {
307 self.slabs.index_slab_id.is_some()
308 }
309}
310
311#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
315pub struct Wireframe3dBinKey {
316 pub asset_id: UntypedAssetId,
318}
319
320pub struct SetWireframe3dThinImmediates;
321
322impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dThinImmediates {
323 type Param = (
324 SRes<RenderWireframeInstances>,
325 SRes<RenderAssets<RenderWireframeMaterial>>,
326 );
327 type ViewQuery = ();
328 type ItemQuery = ();
329
330 #[inline]
331 fn render<'w>(
332 item: &P,
333 _view: (),
334 _item_query: Option<()>,
335 (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,
336 pass: &mut TrackedRenderPass<'w>,
337 ) -> RenderCommandResult {
338 let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {
339 return RenderCommandResult::Failure("No wireframe material found for entity");
340 };
341 let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {
342 return RenderCommandResult::Failure("No wireframe material found for entity");
343 };
344
345 pass.set_immediates(0, bytemuck::bytes_of(&wireframe_material.color));
346 RenderCommandResult::Success
347 }
348}
349
350pub struct SetWireframe3dWideImmediates;
351
352#[derive(Clone, Copy, Pod, Zeroable)]
353#[repr(C)]
354struct WireframeWideImmediates {
355 color: [f32; 4],
356 line_width: f32,
357 smoothing: f32,
358 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
359 _padding: [f32; 2],
360}
361
362impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dWideImmediates {
363 type Param = (
364 SRes<RenderWireframeInstances>,
365 SRes<RenderAssets<RenderWireframeMaterial>>,
366 );
367 type ViewQuery = ();
368 type ItemQuery = ();
369
370 #[inline]
371 fn render<'w>(
372 item: &P,
373 _view: (),
374 _item_query: Option<()>,
375 (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,
376 pass: &mut TrackedRenderPass<'w>,
377 ) -> RenderCommandResult {
378 let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {
379 return RenderCommandResult::Failure("No wireframe material found for entity");
380 };
381 let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {
382 return RenderCommandResult::Failure("No wireframe material found for entity");
383 };
384
385 let push = WireframeWideImmediates {
386 color: wireframe_material.color,
387 line_width: wireframe_material.line_width,
388 smoothing: if wireframe_material.line_width <= 1.0 {
389 0.5
390 } else {
391 1.0
392 },
393 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
394 _padding: [0.0; 2],
395 };
396 pass.set_immediates(0, bytemuck::bytes_of(&push));
397 RenderCommandResult::Success
398 }
399}
400
401#[derive(Clone, Copy, ShaderType, Pod, Zeroable)]
402#[repr(C)]
403pub struct WireframeVertexPullParams {
404 pub index_offset: u32,
405 pub vertex_stride_u32s: u32,
406 pub position_offset_u32s: u32,
407}
408
409#[derive(Resource, Default)]
410pub struct WireframeWideBindGroups {
411 pub params: DynamicUniformBuffer<WireframeVertexPullParams>,
412 pub bind_groups: HashMap<AssetId<Mesh>, (BindGroup, u32)>,
413}
414
415pub fn prepare_wireframe_wide_bind_groups(
416 render_mesh_instances: Res<RenderMeshInstances>,
417 render_meshes: Res<RenderAssets<RenderMesh>>,
418 render_wireframe_instances: Res<RenderWireframeInstances>,
419 render_wireframe_assets: Res<RenderAssets<RenderWireframeMaterial>>,
420 mesh_allocator: Res<MeshAllocator>,
421 pipeline: Res<Wireframe3dPipeline>,
422 render_device: Res<RenderDevice>,
423 render_queue: Res<RenderQueue>,
424 mut wide_bind_groups: ResMut<WireframeWideBindGroups>,
425) {
426 wide_bind_groups.bind_groups.clear();
427
428 struct MeshInfo {
429 mesh_id: AssetId<Mesh>,
430 params: WireframeVertexPullParams,
431 vertex_buffer: Buffer,
432 index_buffer: Buffer,
433 }
434
435 let mut infos: Vec<MeshInfo> = Vec::new();
436 let mut seen: HashSet<AssetId<Mesh>, FixedHasher> = HashSet::default();
437
438 for (entity, wireframe_asset_id) in render_wireframe_instances.iter() {
439 let Some(material) = render_wireframe_assets.get(*wireframe_asset_id) else {
440 continue;
441 };
442 if material.line_width <= 1.0 && material.topology != WireframeTopology::Quads {
443 continue;
444 }
445
446 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*entity) else {
447 continue;
448 };
449 let mesh_id = mesh_instance.mesh_asset_id();
450 if !seen.insert(mesh_id) {
451 continue;
452 }
453
454 let Some(mesh) = render_meshes.get(mesh_id) else {
455 continue;
456 };
457 let Some(vertex_slice) = mesh_allocator.mesh_vertex_slice(&mesh_id) else {
458 continue;
459 };
460 let Some(index_slice) = mesh_allocator.mesh_index_slice(&mesh_id) else {
461 continue;
462 };
463
464 let vertex_stride_bytes = mesh.layout.0.layout().array_stride as u32;
465 let position_offset_bytes = mesh
466 .layout
467 .0
468 .layout()
469 .attributes
470 .first()
471 .map(|a| a.offset as u32)
472 .unwrap_or(0);
473
474 infos.push(MeshInfo {
475 mesh_id,
476 params: WireframeVertexPullParams {
477 index_offset: index_slice.range.start,
478 vertex_stride_u32s: vertex_stride_bytes / 4,
479 position_offset_u32s: position_offset_bytes / 4,
480 },
481 vertex_buffer: vertex_slice.buffer.clone(),
482 index_buffer: index_slice.buffer.clone(),
483 });
484 }
485
486 if infos.is_empty() {
487 return;
488 }
489
490 let Some(mut writer) =
491 wide_bind_groups
492 .params
493 .get_writer(infos.len(), &render_device, &render_queue)
494 else {
495 return;
496 };
497
498 let offsets: Vec<u32> = infos
499 .iter()
500 .map(|info| writer.write(&info.params))
501 .collect();
502 drop(writer);
503
504 let WireframeWideBindGroups {
505 ref params,
506 ref mut bind_groups,
507 } = *wide_bind_groups;
508 let Some(params_binding) = params.binding() else {
509 return;
510 };
511
512 for (i, info) in infos.iter().enumerate() {
513 let bind_group = render_device.create_bind_group(
514 "wireframe_wide_bind_group",
515 &pipeline.wide_bind_group_layout,
516 &BindGroupEntries::sequential((
517 info.vertex_buffer.as_entire_buffer_binding(),
518 info.index_buffer.as_entire_buffer_binding(),
519 params_binding.clone(),
520 )),
521 );
522 bind_groups.insert(info.mesh_id, (bind_group, offsets[i]));
523 }
524}
525
526pub struct SetWireframe3dWideBindGroup;
527
528impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dWideBindGroup {
529 type Param = (SRes<RenderMeshInstances>, SRes<WireframeWideBindGroups>);
530 type ViewQuery = ();
531 type ItemQuery = ();
532
533 #[inline]
534 fn render<'w>(
535 item: &P,
536 _view: (),
537 _item_query: Option<()>,
538 (render_mesh_instances, wide_bind_groups): SystemParamItem<'w, '_, Self::Param>,
539 pass: &mut TrackedRenderPass<'w>,
540 ) -> RenderCommandResult {
541 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(item.main_entity())
542 else {
543 return RenderCommandResult::Skip;
544 };
545 let Some((bind_group, dynamic_offset)) = wide_bind_groups
546 .into_inner()
547 .bind_groups
548 .get(&mesh_instance.mesh_asset_id())
549 else {
550 return RenderCommandResult::Skip;
551 };
552
553 pass.set_bind_group(3, bind_group, &[*dynamic_offset]);
554 RenderCommandResult::Success
555 }
556}
557
558pub struct DrawWireframeMeshPulled;
559
560impl<P: PhaseItem> RenderCommand<P> for DrawWireframeMeshPulled {
561 type Param = (
562 SRes<RenderMeshInstances>,
563 SRes<RenderAssets<RenderMesh>>,
564 SRes<MeshAllocator>,
565 SRes<IndirectParametersBuffers>,
566 SRes<PipelineCache>,
567 Option<SRes<PreprocessPipelines>>,
568 SRes<GpuPreprocessingSupport>,
569 );
570 type ViewQuery = Has<PreprocessBindGroups>;
571 type ItemQuery = ();
572
573 #[inline]
574 fn render<'w>(
575 item: &P,
576 has_preprocess_bind_group: ROQueryItem<Self::ViewQuery>,
577 _item_query: Option<()>,
578 (
579 render_mesh_instances,
580 render_meshes,
581 mesh_allocator,
582 indirect_parameters_buffers,
583 pipeline_cache,
584 preprocess_pipelines,
585 preprocessing_support,
586 ): SystemParamItem<'w, '_, Self::Param>,
587 pass: &mut TrackedRenderPass<'w>,
588 ) -> RenderCommandResult {
589 if let Some(preprocess_pipelines) = preprocess_pipelines
590 && (!has_preprocess_bind_group
591 || !preprocess_pipelines
592 .pipelines_are_loaded(&pipeline_cache, &preprocessing_support))
593 {
594 return RenderCommandResult::Skip;
595 }
596
597 let render_mesh_instances = render_mesh_instances.into_inner();
598 let render_meshes = render_meshes.into_inner();
599 let mesh_allocator = mesh_allocator.into_inner();
600 let indirect_parameters_buffers = indirect_parameters_buffers.into_inner();
601
602 let Some(mesh_asset_id) = render_mesh_instances.mesh_asset_id(item.main_entity()) else {
603 return RenderCommandResult::Skip;
604 };
605 let Some(gpu_mesh) = render_meshes.get(mesh_asset_id) else {
606 return RenderCommandResult::Skip;
607 };
608
609 let index_count = match &gpu_mesh.buffer_info {
610 RenderMeshBufferInfo::Indexed { count, .. } => *count,
611 RenderMeshBufferInfo::NonIndexed => gpu_mesh.vertex_count,
612 };
613
614 match item.extra_index() {
615 PhaseItemExtraIndex::None | PhaseItemExtraIndex::DynamicOffset(_) => {
616 let Some(vertex_slice) = mesh_allocator.mesh_vertex_slice(&mesh_asset_id) else {
619 return RenderCommandResult::Skip;
620 };
621 let first_vertex = vertex_slice.range.start;
622 pass.draw(
623 first_vertex..(first_vertex + index_count),
624 item.batch_range().clone(),
625 );
626 }
627 PhaseItemExtraIndex::IndirectParametersIndex {
628 range: indirect_parameters_range,
629 batch_set_index,
630 } => {
631 let Some(phase_indirect) = indirect_parameters_buffers.get(&TypeId::of::<P>())
633 else {
634 warn!("Wireframe wide: indirect parameters buffer missing for phase");
635 return RenderCommandResult::Skip;
636 };
637 let (Some(indirect_buffer), Some(batch_sets_buffer)) = (
638 phase_indirect.non_indexed.data_buffer(),
639 phase_indirect.non_indexed.batch_sets_buffer(),
640 ) else {
641 warn!("Wireframe wide: non-indexed indirect parameters buffer not ready");
642 return RenderCommandResult::Skip;
643 };
644
645 let indirect_parameters_offset = indirect_parameters_range.start as u64
646 * size_of::<IndirectParametersNonIndexed>() as u64;
647 let indirect_parameters_count =
648 indirect_parameters_range.end - indirect_parameters_range.start;
649
650 match batch_set_index {
651 Some(batch_set_index) => {
652 let count_offset =
653 u32::from(batch_set_index) * (size_of::<IndirectBatchSet>() as u32);
654 pass.multi_draw_indirect_count(
655 indirect_buffer,
656 indirect_parameters_offset,
657 batch_sets_buffer,
658 count_offset as u64,
659 indirect_parameters_count,
660 );
661 }
662 None => {
663 pass.multi_draw_indirect(
664 indirect_buffer,
665 indirect_parameters_offset,
666 indirect_parameters_count,
667 );
668 }
669 }
670 }
671 }
672 RenderCommandResult::Success
673 }
674}
675
676pub type DrawWireframe3dThin = (
678 SetItemPipeline,
679 SetMeshViewBindGroup<0>,
680 SetMeshViewBindingArrayBindGroup<1>,
681 SetMeshBindGroup<2>,
682 SetWireframe3dThinImmediates,
683 DrawMesh,
684);
685
686pub type DrawWireframe3dWide = (
688 SetItemPipeline,
689 SetMeshViewBindGroup<0>,
690 SetMeshViewBindingArrayBindGroup<1>,
691 SetMeshBindGroup<2>,
692 SetWireframe3dWideBindGroup,
693 SetWireframe3dWideImmediates,
694 DrawWireframeMeshPulled,
695);
696
697#[derive(Resource, Clone)]
698pub struct Wireframe3dPipeline {
699 mesh_pipeline: MeshPipeline,
700 shader: Handle<Shader>,
701 pub wide_bind_group_layout: BindGroupLayout,
702 pub wide_bind_group_layout_descriptor: BindGroupLayoutDescriptor,
703}
704
705pub fn init_wireframe_3d_pipeline(
706 mut commands: Commands,
707 mesh_pipeline: Res<MeshPipeline>,
708 asset_server: Res<AssetServer>,
709 render_device: Res<RenderDevice>,
710) {
711 let wide_bgl_entries = BindGroupLayoutEntries::sequential(
712 ShaderStages::VERTEX,
713 (
714 storage_buffer_read_only::<u32>(false), storage_buffer_read_only::<u32>(false), uniform_buffer::<WireframeVertexPullParams>(true),
717 ),
718 );
719
720 let wide_bind_group_layout = render_device
721 .create_bind_group_layout("wireframe_wide_bind_group_layout", &wide_bgl_entries);
722
723 let wide_bind_group_layout_descriptor =
724 BindGroupLayoutDescriptor::new("wireframe_wide_bind_group_layout", &wide_bgl_entries);
725
726 commands.insert_resource(Wireframe3dPipeline {
727 mesh_pipeline: mesh_pipeline.clone(),
728 shader: load_embedded_asset!(asset_server.as_ref(), "render/wireframe.wesl"),
729 wide_bind_group_layout,
730 wide_bind_group_layout_descriptor,
731 });
732}
733
734#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
735pub struct WireframePipelineKey {
736 pub mesh_key: MeshPipelineKey,
737 pub wide: bool,
738 pub quads: bool,
739 pub line_mode: bool,
740 pub xray_mode: bool,
741}
742
743impl SpecializedMeshPipeline for Wireframe3dPipeline {
744 type Key = WireframePipelineKey;
745
746 fn specialize(
747 &self,
748 key: Self::Key,
749 layout: &MeshVertexBufferLayoutRef,
750 ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
751 let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?;
752
753 if key.xray_mode {
754 descriptor.primitive.cull_mode = None;
755 let depth_stencil = descriptor.depth_stencil.as_mut().unwrap();
756 depth_stencil.depth_compare = Some(CompareFunction::Always);
758 depth_stencil.depth_write_enabled = Some(false);
759 }
760
761 if descriptor.primitive.topology.is_triangles() {
762 descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
763 }
764
765 if key.wide {
766 descriptor.label = Some("wireframe_3d_wide_pipeline".into());
767
768 descriptor.vertex.shader = self.shader.clone();
769 descriptor.vertex.shader_defs.push("WIREFRAME_WIDE".into());
770 if key.quads {
771 descriptor.vertex.shader_defs.push("WIREFRAME_QUADS".into());
772 }
773 descriptor.vertex.entry_point = Some("vertex".into());
774 descriptor.vertex.buffers = vec![]; let fragment = descriptor.fragment.as_mut().unwrap();
777 fragment.shader = self.shader.clone();
778 fragment.shader_defs.push("WIREFRAME_WIDE".into());
779 fragment.entry_point = Some("fragment".into());
780
781 for state in fragment.targets.iter_mut().flatten() {
782 state.blend = Some(BlendState::ALPHA_BLENDING);
783 }
784
785 descriptor.primitive.polygon_mode = if key.line_mode {
786 PolygonMode::Line
787 } else {
788 PolygonMode::Fill
789 };
790 descriptor.immediate_size = 32; descriptor
793 .layout
794 .push(self.wide_bind_group_layout_descriptor.clone());
795 } else {
796 descriptor.label = Some("wireframe_3d_pipeline".into());
797 descriptor.immediate_size = 16;
798 let fragment = descriptor.fragment.as_mut().unwrap();
799 fragment.shader = self.shader.clone();
800 descriptor.primitive.polygon_mode = PolygonMode::Line;
801 }
802
803 Ok(descriptor)
804 }
805}
806
807pub fn wireframe_3d(
808 world: &World,
809 view: ViewQuery<(
810 &ExtractedCamera,
811 &ExtractedView,
812 &ViewTarget,
813 &ViewDepthStencilTexture,
814 )>,
815 wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
816 mut ctx: RenderContext,
817) {
818 let view_entity = view.entity();
819
820 let (camera, extracted_view, target, depth) = view.into_inner();
821
822 let Some(wireframe_phase) = wireframe_phases.get(&extracted_view.retained_view_entity) else {
823 return;
824 };
825
826 if wireframe_phase.is_empty() {
827 return;
828 }
829
830 let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
831 label: Some("wireframe_3d"),
832 color_attachments: &[Some(target.get_color_attachment())],
833 depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
834 timestamp_writes: None,
835 occlusion_query_set: None,
836 multiview_mask: None,
837 });
838
839 if let Some(viewport) = camera.viewport.as_ref() {
840 render_pass.set_camera_viewport(viewport);
841 }
842
843 if let Err(err) = wireframe_phase.render(&mut render_pass, world, view_entity) {
844 error!("Error encountered while rendering the wireframe phase {err:?}");
845 }
846}
847
848#[derive(Component, Debug, Clone, Default, Reflect)]
855#[reflect(Component, Default, Debug)]
856pub struct WireframeColor {
857 pub color: Color,
858}
859
860#[derive(Component, Debug, Clone, Reflect)]
864#[reflect(Component, Default, Debug)]
865pub struct WireframeLineWidth {
866 pub width: f32,
867}
868
869impl Default for WireframeLineWidth {
870 fn default() -> Self {
871 Self { width: 1.0 }
872 }
873}
874
875#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
880#[reflect(Component, Default, Debug, PartialEq)]
881pub struct NoWireframe;
882
883#[derive(Component, Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Reflect)]
885#[reflect(Component, Default, Debug)]
886pub enum WireframeTopology {
887 #[default]
888 Triangles,
889 Quads,
892}
893
894#[derive(Component, Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Reflect)]
898#[reflect(Component, Default, Debug)]
899pub struct WireframeXray(pub bool);
900
901#[derive(Resource, Debug, Clone, ExtractResource, Reflect)]
902#[reflect(Resource, Debug, Default)]
903#[extract_app(RenderApp)]
904pub struct WireframeConfig {
905 pub global: bool,
908 pub default_color: Color,
912 pub default_line_width: f32,
914 pub default_topology: WireframeTopology,
916 pub xray_mode: bool,
919}
920
921impl Default for WireframeConfig {
922 fn default() -> Self {
923 Self {
924 global: false,
925 default_color: Color::default(),
926 default_line_width: 1.0,
927 default_topology: WireframeTopology::default(),
928 xray_mode: false,
929 }
930 }
931}
932
933#[derive(Asset, Reflect, Clone, Debug)]
934#[reflect(Clone, Default)]
935pub struct WireframeMaterial {
936 pub color: Color,
937 pub line_width: f32,
938 pub topology: WireframeTopology,
939 pub xray_mode: bool,
940}
941
942impl Default for WireframeMaterial {
943 fn default() -> Self {
944 Self {
945 color: Color::default(),
946 line_width: 1.0,
947 topology: WireframeTopology::default(),
948 xray_mode: false,
949 }
950 }
951}
952
953pub struct RenderWireframeMaterial {
954 pub color: [f32; 4],
955 pub line_width: f32,
956 pub topology: WireframeTopology,
957 pub xray_mode: bool,
958}
959
960#[derive(
961 Component, FromTemplate, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq,
962)]
963#[reflect(Component, Default, Clone, PartialEq)]
964pub struct Mesh3dWireframe(pub Handle<WireframeMaterial>);
965
966impl AsAssetId for Mesh3dWireframe {
967 type Asset = WireframeMaterial;
968
969 fn as_asset_id(&self) -> AssetId<Self::Asset> {
970 self.0.id()
971 }
972}
973
974impl RenderAsset for RenderWireframeMaterial {
975 type SourceAsset = WireframeMaterial;
976 type Param = ();
977
978 fn prepare_asset(
979 source_asset: Self::SourceAsset,
980 _asset_id: AssetId<Self::SourceAsset>,
981 _param: &mut SystemParamItem<Self::Param>,
982 _previous_asset: Option<&Self>,
983 ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
984 Ok(RenderWireframeMaterial {
985 color: source_asset.color.to_linear().to_f32_array(),
986 line_width: source_asset.line_width,
987 topology: source_asset.topology,
988 xray_mode: source_asset.xray_mode,
989 })
990 }
991}
992
993#[derive(Resource, Deref, DerefMut, Default)]
994pub struct RenderWireframeInstances(MainEntityHashMap<AssetId<WireframeMaterial>>);
995
996#[derive(Clone, Resource, Debug, Default)]
1000pub struct WireframeEntitiesNeedingSpecialization {
1001 pub changed: Vec<Entity>,
1003 pub removed: Vec<Entity>,
1005}
1006
1007#[derive(Resource, Default)]
1008pub struct SpecializedWireframePipelineCache {
1009 views: HashMap<RetainedViewEntity, SpecializedWireframeViewPipelineCache>,
1010 wide: HashMap<
1011 (MeshPipelineKey, MeshVertexBufferLayoutRef, bool, bool, bool),
1012 CachedRenderPipelineId,
1013 >,
1014}
1015
1016#[derive(Deref, DerefMut, Default)]
1017pub struct SpecializedWireframeViewPipelineCache {
1018 #[deref]
1020 map: MainEntityHashMap<CachedRenderPipelineId>,
1021}
1022
1023#[derive(Resource)]
1024struct GlobalWireframeMaterial {
1025 handle: Handle<WireframeMaterial>,
1027}
1028
1029pub fn extract_wireframe_materials(
1030 mut material_instances: ResMut<RenderWireframeInstances>,
1031 changed_meshes_query: Extract<
1032 Query<
1033 (Entity, &ViewVisibility, &Mesh3dWireframe),
1034 Or<(Changed<ViewVisibility>, Changed<Mesh3dWireframe>)>,
1035 >,
1036 >,
1037 mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
1038 mut removed_materials_query: Extract<RemovedComponents<Mesh3dWireframe>>,
1039) {
1040 for (entity, view_visibility, material) in &changed_meshes_query {
1041 if view_visibility.get() {
1042 material_instances.insert(entity.into(), material.id());
1043 } else {
1044 material_instances.remove(&MainEntity::from(entity));
1045 }
1046 }
1047
1048 for entity in removed_visibilities_query
1049 .read()
1050 .chain(removed_materials_query.read())
1051 {
1052 if !changed_meshes_query.contains(entity) {
1056 material_instances.remove(&MainEntity::from(entity));
1057 }
1058 }
1059}
1060
1061fn setup_global_wireframe_material(
1062 mut commands: Commands,
1063 mut materials: ResMut<Assets<WireframeMaterial>>,
1064 config: Res<WireframeConfig>,
1065) {
1066 commands.insert_resource(GlobalWireframeMaterial {
1067 handle: materials.add(WireframeMaterial {
1068 color: config.default_color,
1069 line_width: config.default_line_width,
1070 topology: config.default_topology,
1071 xray_mode: config.xray_mode,
1072 }),
1073 });
1074}
1075
1076fn wireframe_config_changed(
1077 config: Res<WireframeConfig>,
1078 mut materials: ResMut<Assets<WireframeMaterial>>,
1079 global_material: Res<GlobalWireframeMaterial>,
1080 mut per_entity_wireframes: Query<
1081 (
1082 &mut Mesh3dWireframe,
1083 Option<&WireframeColor>,
1084 Option<&WireframeLineWidth>,
1085 Option<&WireframeTopology>,
1086 Option<&WireframeXray>,
1087 ),
1088 With<Wireframe>,
1089 >,
1090) {
1091 if let Some(mut mat) = materials.get_mut(&global_material.handle) {
1092 mat.color = config.default_color;
1093 mat.line_width = config.default_line_width;
1094 mat.topology = config.default_topology;
1095 mat.xray_mode = config.xray_mode;
1096 }
1097
1098 for (mut handle, maybe_color, maybe_width, maybe_topology, maybe_xray) in
1099 &mut per_entity_wireframes
1100 {
1101 if handle.0 == global_material.handle {
1102 continue;
1103 }
1104 handle.0 = materials.add(WireframeMaterial {
1105 color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1106 line_width: maybe_width
1107 .map(|w| w.width)
1108 .unwrap_or(config.default_line_width),
1109 topology: maybe_topology.copied().unwrap_or(config.default_topology),
1110 xray_mode: maybe_xray.map(|xray| xray.0).unwrap_or(config.xray_mode),
1111 });
1112 }
1113}
1114
1115fn wireframe_color_changed(
1116 mut materials: ResMut<Assets<WireframeMaterial>>,
1117 mut colors_changed: Query<
1118 (
1119 &mut Mesh3dWireframe,
1120 &WireframeColor,
1121 Option<&WireframeLineWidth>,
1122 Option<&WireframeTopology>,
1123 Option<&WireframeXray>,
1124 ),
1125 (With<Wireframe>, Changed<WireframeColor>),
1126 >,
1127 config: Res<WireframeConfig>,
1128) {
1129 for (mut handle, wireframe_color, maybe_width, maybe_topology, maybe_xray) in
1130 &mut colors_changed
1131 {
1132 handle.0 = materials.add(WireframeMaterial {
1133 color: wireframe_color.color,
1134 line_width: maybe_width
1135 .map(|w| w.width)
1136 .unwrap_or(config.default_line_width),
1137 topology: maybe_topology.copied().unwrap_or(config.default_topology),
1138 xray_mode: maybe_xray.map(|xray| xray.0).unwrap_or(config.xray_mode),
1139 });
1140 }
1141}
1142
1143fn wireframe_line_width_changed(
1144 mut materials: ResMut<Assets<WireframeMaterial>>,
1145 mut widths_changed: Query<
1146 (
1147 &mut Mesh3dWireframe,
1148 &WireframeLineWidth,
1149 Option<&WireframeColor>,
1150 Option<&WireframeTopology>,
1151 Option<&WireframeXray>,
1152 ),
1153 (With<Wireframe>, Changed<WireframeLineWidth>),
1154 >,
1155 config: Res<WireframeConfig>,
1156) {
1157 for (mut handle, wireframe_width, maybe_color, maybe_topology, maybe_xray) in
1158 &mut widths_changed
1159 {
1160 handle.0 = materials.add(WireframeMaterial {
1161 color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1162 line_width: wireframe_width.width,
1163 topology: maybe_topology.copied().unwrap_or(config.default_topology),
1164 xray_mode: maybe_xray.map(|xray| xray.0).unwrap_or(config.xray_mode),
1165 });
1166 }
1167}
1168
1169fn wireframe_topology_changed(
1170 mut materials: ResMut<Assets<WireframeMaterial>>,
1171 mut topology_changed: Query<
1172 (
1173 &mut Mesh3dWireframe,
1174 &WireframeTopology,
1175 Option<&WireframeColor>,
1176 Option<&WireframeLineWidth>,
1177 Option<&WireframeXray>,
1178 ),
1179 (With<Wireframe>, Changed<WireframeTopology>),
1180 >,
1181 config: Res<WireframeConfig>,
1182) {
1183 for (mut handle, topology, maybe_color, maybe_width, maybe_xray) in &mut topology_changed {
1184 handle.0 = materials.add(WireframeMaterial {
1185 color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1186 line_width: maybe_width
1187 .map(|w| w.width)
1188 .unwrap_or(config.default_line_width),
1189 topology: *topology,
1190 xray_mode: maybe_xray.map(|xray| xray.0).unwrap_or(config.xray_mode),
1191 });
1192 }
1193}
1194
1195fn wireframe_xray_changed(
1196 mut materials: ResMut<Assets<WireframeMaterial>>,
1197 mut xray_changed: Query<
1198 (
1199 &mut Mesh3dWireframe,
1200 &WireframeXray,
1201 Option<&WireframeColor>,
1202 Option<&WireframeLineWidth>,
1203 Option<&WireframeTopology>,
1204 ),
1205 (With<Wireframe>, Changed<WireframeXray>),
1206 >,
1207 config: Res<WireframeConfig>,
1208) {
1209 for (mut handle, xray, maybe_color, maybe_width, maybe_topology) in &mut xray_changed {
1210 handle.0 = materials.add(WireframeMaterial {
1211 color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1212 line_width: maybe_width
1213 .map(|w| w.width)
1214 .unwrap_or(config.default_line_width),
1215 topology: maybe_topology.copied().unwrap_or(config.default_topology),
1216 xray_mode: xray.0,
1217 });
1218 }
1219}
1220
1221fn apply_wireframe_material(
1224 mut commands: Commands,
1225 mut materials: ResMut<Assets<WireframeMaterial>>,
1226 wireframes: Query<
1227 (
1228 Entity,
1229 Option<&WireframeColor>,
1230 Option<&WireframeLineWidth>,
1231 Option<&WireframeTopology>,
1232 Option<&WireframeXray>,
1233 ),
1234 (With<Wireframe>, Without<Mesh3dWireframe>),
1235 >,
1236 no_wireframes: Query<Entity, (With<NoWireframe>, With<Mesh3dWireframe>)>,
1237 mut removed_wireframes: RemovedComponents<Wireframe>,
1238 global_material: Res<GlobalWireframeMaterial>,
1239 config: Res<WireframeConfig>,
1240) {
1241 for e in removed_wireframes.read().chain(no_wireframes.iter()) {
1242 if let Ok(mut commands) = commands.get_entity(e) {
1243 commands.remove::<Mesh3dWireframe>();
1244 }
1245 }
1246
1247 let mut material_to_spawn = vec![];
1248 for (e, maybe_color, maybe_width, maybe_topology, maybe_xray) in &wireframes {
1249 let material = get_wireframe_material(
1250 maybe_color,
1251 maybe_width,
1252 maybe_topology,
1253 maybe_xray,
1254 &mut materials,
1255 &global_material,
1256 &config,
1257 );
1258 material_to_spawn.push((e, Mesh3dWireframe(material)));
1259 }
1260 commands.try_insert_batch(material_to_spawn);
1261}
1262
1263type WireframeFilter = (With<Mesh3d>, Without<Wireframe>, Without<NoWireframe>);
1264
1265fn apply_global_wireframe_material(
1267 mut commands: Commands,
1268 config: Res<WireframeConfig>,
1269 meshes_without_material: Query<
1270 (
1271 Entity,
1272 Option<&WireframeColor>,
1273 Option<&WireframeLineWidth>,
1274 Option<&WireframeTopology>,
1275 Option<&WireframeXray>,
1276 ),
1277 (WireframeFilter, Without<Mesh3dWireframe>),
1278 >,
1279 meshes_with_global_material: Query<Entity, (WireframeFilter, With<Mesh3dWireframe>)>,
1280 global_material: Res<GlobalWireframeMaterial>,
1281 mut materials: ResMut<Assets<WireframeMaterial>>,
1282) {
1283 if config.global {
1284 let mut material_to_spawn = vec![];
1285 for (e, maybe_color, maybe_width, maybe_topology, maybe_xray) in &meshes_without_material {
1286 let material = get_wireframe_material(
1287 maybe_color,
1288 maybe_width,
1289 maybe_topology,
1290 maybe_xray,
1291 &mut materials,
1292 &global_material,
1293 &config,
1294 );
1295 material_to_spawn.push((e, Mesh3dWireframe(material)));
1298 }
1299 commands.try_insert_batch(material_to_spawn);
1300 } else {
1301 for e in &meshes_with_global_material {
1302 commands.entity(e).remove::<Mesh3dWireframe>();
1303 }
1304 }
1305}
1306
1307fn get_wireframe_material(
1309 maybe_color: Option<&WireframeColor>,
1310 maybe_width: Option<&WireframeLineWidth>,
1311 maybe_topology: Option<&WireframeTopology>,
1312 maybe_xray: Option<&WireframeXray>,
1313 wireframe_materials: &mut Assets<WireframeMaterial>,
1314 global_material: &GlobalWireframeMaterial,
1315 config: &WireframeConfig,
1316) -> Handle<WireframeMaterial> {
1317 if maybe_color.is_some()
1318 || maybe_width.is_some()
1319 || maybe_topology.is_some()
1320 || maybe_xray.is_some()
1321 {
1322 wireframe_materials.add(WireframeMaterial {
1323 color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1324 line_width: maybe_width
1325 .map(|w| w.width)
1326 .unwrap_or(config.default_line_width),
1327 topology: maybe_topology.copied().unwrap_or(config.default_topology),
1328 xray_mode: maybe_xray.map(|xray| xray.0).unwrap_or(config.xray_mode),
1329 })
1330 } else {
1331 global_material.handle.clone()
1333 }
1334}
1335
1336fn extract_wireframe_3d_camera(
1337 mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
1338 cameras: Extract<Query<(Entity, &Camera, Has<NoIndirectDrawing>), With<Camera3d>>>,
1339 mut live_entities: Local<HashSet<RetainedViewEntity>>,
1340 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1341) {
1342 live_entities.clear();
1343 for (main_entity, camera, no_indirect_drawing) in &cameras {
1344 if !camera.is_active {
1345 continue;
1346 }
1347 let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
1348 GpuPreprocessingMode::Culling
1349 } else {
1350 GpuPreprocessingMode::PreprocessingOnly
1351 });
1352
1353 let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0);
1354 wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1355 live_entities.insert(retained_view_entity);
1356 }
1357
1358 wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
1360}
1361
1362pub fn extract_wireframe_entities_needing_specialization(
1363 entities_needing_specialization: Extract<Res<WireframeEntitiesNeedingSpecialization>>,
1364 mut dirty_wireframe_specializations: ResMut<DirtyWireframeSpecializations>,
1365) {
1366 for entity in entities_needing_specialization.changed.iter() {
1369 dirty_wireframe_specializations
1370 .changed_renderables
1371 .insert(MainEntity::from(*entity));
1372 }
1373}
1374
1375pub fn extract_wireframe_entities_that_need_specializations_removed(
1379 entities_needing_specialization: Extract<Res<WireframeEntitiesNeedingSpecialization>>,
1380 mut dirty_wireframe_specializations: ResMut<DirtyWireframeSpecializations>,
1381) {
1382 for entity in entities_needing_specialization.removed.iter() {
1383 dirty_wireframe_specializations
1384 .removed_renderables
1385 .insert(MainEntity::from(*entity));
1386 }
1387}
1388
1389pub fn check_wireframe_entities_needing_specialization(
1393 needs_specialization: Query<
1394 Entity,
1395 Or<(
1396 Changed<Mesh3d>,
1397 AssetChanged<Mesh3d>,
1398 Changed<Mesh3dWireframe>,
1399 AssetChanged<Mesh3dWireframe>,
1400 Changed<WireframeLineWidth>,
1401 Changed<WireframeTopology>,
1402 Changed<WireframeXray>,
1403 )>,
1404 >,
1405 wireframe_entities: Query<Entity, With<Mesh3dWireframe>>,
1406 config: Res<WireframeConfig>,
1407 mut entities_needing_specialization: ResMut<WireframeEntitiesNeedingSpecialization>,
1408 mut removed_mesh_3d_components: RemovedComponents<Mesh3d>,
1409 mut removed_mesh_3d_wireframe_components: RemovedComponents<Mesh3dWireframe>,
1410) {
1411 entities_needing_specialization.changed.clear();
1412 entities_needing_specialization.removed.clear();
1413
1414 for entity in &needs_specialization {
1416 entities_needing_specialization.changed.push(entity);
1417 }
1418
1419 if config.is_changed() {
1420 entities_needing_specialization
1421 .changed
1422 .extend(wireframe_entities.iter());
1423 }
1424
1425 for entity in removed_mesh_3d_components
1434 .read()
1435 .chain(removed_mesh_3d_wireframe_components.read())
1436 {
1437 entities_needing_specialization.removed.push(entity);
1438 }
1439}
1440
1441#[derive(Default, Deref, DerefMut, Resource)]
1442pub struct PendingWireframeQueues(pub PendingQueues);
1443
1444pub fn specialize_wireframes(
1445 render_meshes: Res<RenderAssets<RenderMesh>>,
1446 render_mesh_instances: Res<RenderMeshInstances>,
1447 render_wireframe_instances: Res<RenderWireframeInstances>,
1448 render_wireframe_assets: Res<RenderAssets<RenderWireframeMaterial>>,
1449 render_visibility_ranges: Res<RenderVisibilityRanges>,
1450 wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
1451 views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1452 view_key_cache: Res<ViewKeyCache>,
1453 dirty_wireframe_specializations: Res<DirtyWireframeSpecializations>,
1454 mut specialized_material_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
1455 mut pipelines: ResMut<SpecializedMeshPipelines<Wireframe3dPipeline>>,
1456 mut pending_wireframe_queues: ResMut<PendingWireframeQueues>,
1457 pipeline: Res<Wireframe3dPipeline>,
1458 pipeline_cache: Res<PipelineCache>,
1459 render_lightmaps: Res<RenderLightmaps>,
1460) {
1461 let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
1462
1463 let SpecializedWireframePipelineCache {
1464 views: ref mut views_pipeline_cache,
1465 wide: ref mut wide_pipeline_cache,
1466 } = *specialized_material_pipeline_cache;
1467
1468 for (view, visible_entities) in &views {
1469 all_views.insert(view.retained_view_entity);
1470
1471 if !wireframe_phases.contains_key(&view.retained_view_entity) {
1472 continue;
1473 }
1474
1475 let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
1476 continue;
1477 };
1478
1479 let view_specialized_material_pipeline_cache = views_pipeline_cache
1480 .entry(view.retained_view_entity)
1481 .or_default();
1482
1483 let Some(render_visible_mesh_entities) = visible_entities.get::<Mesh3d>() else {
1484 continue;
1485 };
1486
1487 let view_pending_wireframe_queues =
1489 pending_wireframe_queues.prepare_for_new_frame(view.retained_view_entity);
1490
1491 if dirty_wireframe_specializations
1494 .must_wipe_specializations_for_view(view.retained_view_entity)
1495 {
1496 view_specialized_material_pipeline_cache.clear();
1497 } else {
1498 for &renderable_entity in dirty_wireframe_specializations.iter_to_despecialize() {
1499 view_specialized_material_pipeline_cache.remove(&renderable_entity);
1500 }
1501 }
1502
1503 for (render_entity, visible_entity) in dirty_wireframe_specializations.iter_to_specialize(
1505 view.retained_view_entity,
1506 render_visible_mesh_entities,
1507 &view_pending_wireframe_queues.prev_frame,
1508 ) {
1509 if view_specialized_material_pipeline_cache.contains_key(visible_entity) {
1510 continue;
1511 }
1512
1513 if !render_wireframe_instances.contains_key(visible_entity) {
1514 continue;
1515 };
1516 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1517 else {
1518 view_pending_wireframe_queues
1522 .current_frame
1523 .insert((*render_entity, *visible_entity));
1524 continue;
1525 };
1526 let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id()) else {
1527 continue;
1528 };
1529
1530 let mut mesh_key = *view_key;
1531 mesh_key |= MeshPipelineKey::from_primitive_topology_and_strip_index(
1532 mesh.primitive_topology(),
1533 mesh.index_format(),
1534 );
1535
1536 if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
1537 mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
1538 }
1539
1540 if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
1541 if mesh_instance
1543 .flags()
1544 .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
1545 {
1546 mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
1547 }
1548 if mesh_instance
1549 .flags()
1550 .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
1551 {
1552 mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
1553 }
1554 }
1555
1556 if render_lightmaps
1562 .render_lightmaps
1563 .contains_key(visible_entity)
1564 {
1565 mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1566 }
1567
1568 let mat = render_wireframe_instances
1569 .get(visible_entity)
1570 .and_then(|asset_id| render_wireframe_assets.get(*asset_id));
1571 let quads = mat
1572 .map(|m| m.topology == WireframeTopology::Quads)
1573 .unwrap_or(false);
1574 let thick = mat.map(|m| m.line_width > 1.0).unwrap_or(false);
1575 let xray_mode = mat.map(|m| m.xray_mode).unwrap_or(false);
1576 let wide = thick || quads;
1577 let line_mode = wide && !thick;
1578
1579 let pipeline_id = if wide {
1580 let cache_key = (mesh_key, mesh.layout.clone(), quads, line_mode, xray_mode);
1581 *wide_pipeline_cache.entry(cache_key).or_insert_with(|| {
1582 let wireframe_key = WireframePipelineKey {
1583 mesh_key,
1584 wide: true,
1585 quads,
1586 line_mode,
1587 xray_mode,
1588 };
1589 match pipeline.specialize(wireframe_key, &mesh.layout) {
1590 Ok(descriptor) => pipeline_cache.queue_render_pipeline(descriptor),
1591 Err(err) => {
1592 error!("{}", err);
1593 CachedRenderPipelineId::INVALID
1594 }
1595 }
1596 })
1597 } else {
1598 let wireframe_key = WireframePipelineKey {
1599 mesh_key,
1600 wide: false,
1601 quads: false,
1602 line_mode: false,
1603 xray_mode,
1604 };
1605 match pipelines.specialize(&pipeline_cache, &pipeline, wireframe_key, &mesh.layout)
1606 {
1607 Ok(id) => id,
1608 Err(err) => {
1609 error!("{}", err);
1610 continue;
1611 }
1612 }
1613 };
1614
1615 view_specialized_material_pipeline_cache.insert(*visible_entity, pipeline_id);
1616 }
1617 }
1618
1619 pending_wireframe_queues.expire_stale_views(&all_views);
1620
1621 views_pipeline_cache.retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
1623}
1624
1625fn queue_wireframes(
1626 custom_draw_functions: Res<DrawFunctions<Wireframe3d>>,
1627 render_mesh_instances: Res<RenderMeshInstances>,
1628 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1629 mesh_allocator: Res<MeshAllocator>,
1630 specialized_wireframe_pipeline_cache: Res<SpecializedWireframePipelineCache>,
1631 render_wireframe_instances: Res<RenderWireframeInstances>,
1632 dirty_wireframe_specializations: Res<DirtyWireframeSpecializations>,
1633 render_wireframe_assets: Res<RenderAssets<RenderWireframeMaterial>>,
1634 mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
1635 mut pending_wireframe_queues: ResMut<PendingWireframeQueues>,
1636 mut views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1637 mut mesh_instances_queued_this_iteration_scratch_space: Local<MainEntityHashSet>,
1638) {
1639 for (view, visible_entities) in &mut views {
1640 let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else {
1641 continue;
1642 };
1643 let draw_functions = custom_draw_functions.read();
1644 let draw_thin = draw_functions.id::<DrawWireframe3dThin>();
1645 let draw_wide = draw_functions.id::<DrawWireframe3dWide>();
1646
1647 let Some(view_specialized_material_pipeline_cache) = specialized_wireframe_pipeline_cache
1648 .views
1649 .get(&view.retained_view_entity)
1650 else {
1651 continue;
1652 };
1653
1654 let Some(render_mesh_visible_entities) = visible_entities.get::<Mesh3d>() else {
1655 continue;
1656 };
1657
1658 let view_pending_wireframe_queues = pending_wireframe_queues
1659 .get_mut(&view.retained_view_entity)
1660 .expect(
1661 "View pending wireframe queues should have been created in `specialize_wireframes`",
1662 );
1663
1664 for &main_entity in dirty_wireframe_specializations
1666 .iter_to_dequeue(view.retained_view_entity, render_mesh_visible_entities)
1667 {
1668 wireframe_phase.remove(main_entity);
1669 }
1670
1671 for (render_entity, visible_entity) in dirty_wireframe_specializations.iter_to_queue(
1673 view.retained_view_entity,
1674 render_mesh_visible_entities,
1675 &view_pending_wireframe_queues.prev_frame,
1676 &mut mesh_instances_queued_this_iteration_scratch_space,
1677 ) {
1678 let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else {
1679 continue;
1680 };
1681 let Some(pipeline_id) = view_specialized_material_pipeline_cache
1682 .get(visible_entity)
1683 .copied()
1684 else {
1685 continue;
1686 };
1687
1688 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1689 else {
1690 view_pending_wireframe_queues
1694 .current_frame
1695 .insert((*render_entity, *visible_entity));
1696 continue;
1697 };
1698
1699 let is_wide = render_wireframe_assets
1700 .get(*wireframe_instance)
1701 .map(|mat| mat.line_width > 1.0 || mat.topology == WireframeTopology::Quads)
1702 .unwrap_or(false);
1703 let draw_function = if is_wide { draw_wide } else { draw_thin };
1704
1705 let Some(MeshSlabs {
1706 vertex_slab_id: vertex_slab,
1707 index_slab_id: index_slab,
1708 metadata_slab_id: metadata_slab,
1709 morph_target_slab_id: morph_target_slab,
1710 }) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id())
1711 else {
1712 continue;
1713 };
1714 let bin_key = Wireframe3dBinKey {
1715 asset_id: mesh_instance.mesh_asset_id().untyped(),
1716 };
1717 let batch_set_key = Wireframe3dBatchSetKey {
1718 pipeline: pipeline_id,
1719 asset_id: wireframe_instance.untyped(),
1720 draw_function,
1721 slabs: MeshSlabs {
1722 vertex_slab_id: vertex_slab,
1723 morph_target_slab_id: morph_target_slab,
1724 metadata_slab_id: metadata_slab,
1725 index_slab_id: if is_wide { None } else { index_slab },
1730 },
1731 mesh_asset_id: if is_wide {
1732 Some(mesh_instance.mesh_asset_id().untyped())
1733 } else {
1734 None
1735 },
1736 };
1737 wireframe_phase.add(
1738 batch_set_key,
1739 bin_key,
1740 (*render_entity, *visible_entity),
1741 mesh_instance.current_uniform_index,
1742 BinnedRenderPhaseType::mesh(
1743 mesh_instance.should_batch(),
1744 &gpu_preprocessing_support,
1745 ),
1746 );
1747 }
1748 }
1749}