Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/wireframe.rs
9297 views
1
use crate::{
2
DrawMesh, MeshPipeline, MeshPipelineKey, RenderLightmaps, RenderMeshInstanceFlags,
3
RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup,
4
ViewKeyCache, ViewSpecializationTicks,
5
};
6
use bevy_app::{App, Plugin, PostUpdate, Startup, Update};
7
use bevy_asset::{
8
embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp,
9
AssetEventSystems, AssetId, AssetServer, Assets, Handle, UntypedAssetId,
10
};
11
use bevy_camera::{visibility::ViewVisibility, Camera, Camera3d};
12
use bevy_color::{Color, ColorToComponents};
13
use bevy_core_pipeline::schedule::{Core3d, Core3dSystems};
14
use bevy_derive::{Deref, DerefMut};
15
use bevy_ecs::{
16
change_detection::Tick,
17
prelude::*,
18
system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem},
19
};
20
use bevy_mesh::{Mesh3d, MeshVertexBufferLayoutRef};
21
use bevy_platform::{
22
collections::{HashMap, HashSet},
23
hash::FixedHasher,
24
};
25
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
26
use bevy_render::{
27
batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
28
camera::{extract_cameras, ExtractedCamera},
29
extract_resource::ExtractResource,
30
mesh::{
31
allocator::{MeshAllocator, SlabId},
32
RenderMesh,
33
},
34
prelude::*,
35
render_asset::{
36
prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets,
37
},
38
render_phase::{
39
AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType,
40
CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
41
PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,
42
SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases,
43
},
44
render_resource::*,
45
renderer::{RenderContext, RenderDevice, ViewQuery},
46
sync_world::{MainEntity, MainEntityHashMap},
47
view::{
48
ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities,
49
RetainedViewEntity, ViewDepthTexture, ViewTarget,
50
},
51
Extract, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems,
52
};
53
use bevy_shader::Shader;
54
use core::{hash::Hash, ops::Range};
55
use tracing::{error, warn};
56
57
/// A [`Plugin`] that draws wireframes.
58
///
59
/// Wireframes currently do not work when using webgl or webgpu.
60
/// Supported rendering backends:
61
/// - DX12
62
/// - Vulkan
63
/// - Metal
64
///
65
/// This is a native only feature.
66
#[derive(Debug, Default)]
67
pub struct WireframePlugin {
68
/// Debugging flags that can optionally be set when constructing the renderer.
69
pub debug_flags: RenderDebugFlags,
70
}
71
72
impl WireframePlugin {
73
/// Creates a new [`WireframePlugin`] with the given debug flags.
74
pub fn new(debug_flags: RenderDebugFlags) -> Self {
75
Self { debug_flags }
76
}
77
}
78
79
impl Plugin for WireframePlugin {
80
fn build(&self, app: &mut App) {
81
embedded_asset!(app, "render/wireframe.wgsl");
82
83
app.add_plugins((
84
BinnedRenderPhasePlugin::<Wireframe3d, MeshPipeline>::new(self.debug_flags),
85
RenderAssetPlugin::<RenderWireframeMaterial>::default(),
86
))
87
.init_asset::<WireframeMaterial>()
88
.init_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
89
.init_resource::<WireframeConfig>()
90
.init_resource::<WireframeEntitiesNeedingSpecialization>()
91
.add_systems(Startup, setup_global_wireframe_material)
92
.add_systems(
93
Update,
94
(
95
global_color_changed.run_if(resource_changed::<WireframeConfig>),
96
wireframe_color_changed,
97
// Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global
98
// wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed.
99
(apply_wireframe_material, apply_global_wireframe_material).chain(),
100
),
101
)
102
.add_systems(
103
PostUpdate,
104
check_wireframe_entities_needing_specialization
105
.after(AssetEventSystems)
106
.run_if(resource_exists::<WireframeConfig>),
107
);
108
}
109
110
fn finish(&self, app: &mut App) {
111
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
112
return;
113
};
114
115
let required_features = WgpuFeatures::POLYGON_MODE_LINE | WgpuFeatures::IMMEDIATES;
116
let render_device = render_app.world().resource::<RenderDevice>();
117
if !render_device.features().contains(required_features) {
118
warn!(
119
"WireframePlugin not loaded. GPU lacks support for required features: {:?}.",
120
required_features
121
);
122
return;
123
}
124
125
render_app
126
.init_resource::<WireframeEntitySpecializationTicks>()
127
.init_resource::<SpecializedWireframePipelineCache>()
128
.init_resource::<DrawFunctions<Wireframe3d>>()
129
.add_render_command::<Wireframe3d, DrawWireframe3d>()
130
.init_resource::<RenderWireframeInstances>()
131
.init_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
132
.add_systems(RenderStartup, init_wireframe_3d_pipeline)
133
.add_systems(
134
Core3d,
135
wireframe_3d
136
.after(Core3dSystems::MainPass)
137
.before(Core3dSystems::PostProcess),
138
)
139
.add_systems(
140
ExtractSchedule,
141
(
142
extract_wireframe_3d_camera,
143
extract_wireframe_entities_needing_specialization.after(extract_cameras),
144
extract_wireframe_materials,
145
),
146
)
147
.add_systems(
148
Render,
149
(
150
specialize_wireframes
151
.in_set(RenderSystems::PrepareMeshes)
152
.after(prepare_assets::<RenderWireframeMaterial>)
153
.after(prepare_assets::<RenderMesh>),
154
queue_wireframes
155
.in_set(RenderSystems::QueueMeshes)
156
.after(prepare_assets::<RenderWireframeMaterial>),
157
),
158
);
159
}
160
}
161
162
/// Enables wireframe rendering for any entity it is attached to.
163
/// It will ignore the [`WireframeConfig`] global setting.
164
///
165
/// This requires the [`WireframePlugin`] to be enabled.
166
#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
167
#[reflect(Component, Default, Debug, PartialEq)]
168
pub struct Wireframe;
169
170
pub struct Wireframe3d {
171
/// Determines which objects can be placed into a *batch set*.
172
///
173
/// Objects in a single batch set can potentially be multi-drawn together,
174
/// if it's enabled and the current platform supports it.
175
pub batch_set_key: Wireframe3dBatchSetKey,
176
/// The key, which determines which can be batched.
177
pub bin_key: Wireframe3dBinKey,
178
/// An entity from which data will be fetched, including the mesh if
179
/// applicable.
180
pub representative_entity: (Entity, MainEntity),
181
/// The ranges of instances.
182
pub batch_range: Range<u32>,
183
/// An extra index, which is either a dynamic offset or an index in the
184
/// indirect parameters list.
185
pub extra_index: PhaseItemExtraIndex,
186
}
187
188
impl PhaseItem for Wireframe3d {
189
fn entity(&self) -> Entity {
190
self.representative_entity.0
191
}
192
193
fn main_entity(&self) -> MainEntity {
194
self.representative_entity.1
195
}
196
197
fn draw_function(&self) -> DrawFunctionId {
198
self.batch_set_key.draw_function
199
}
200
201
fn batch_range(&self) -> &Range<u32> {
202
&self.batch_range
203
}
204
205
fn batch_range_mut(&mut self) -> &mut Range<u32> {
206
&mut self.batch_range
207
}
208
209
fn extra_index(&self) -> PhaseItemExtraIndex {
210
self.extra_index.clone()
211
}
212
213
fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
214
(&mut self.batch_range, &mut self.extra_index)
215
}
216
}
217
218
impl CachedRenderPipelinePhaseItem for Wireframe3d {
219
fn cached_pipeline(&self) -> CachedRenderPipelineId {
220
self.batch_set_key.pipeline
221
}
222
}
223
224
impl BinnedPhaseItem for Wireframe3d {
225
type BinKey = Wireframe3dBinKey;
226
type BatchSetKey = Wireframe3dBatchSetKey;
227
228
fn new(
229
batch_set_key: Self::BatchSetKey,
230
bin_key: Self::BinKey,
231
representative_entity: (Entity, MainEntity),
232
batch_range: Range<u32>,
233
extra_index: PhaseItemExtraIndex,
234
) -> Self {
235
Self {
236
batch_set_key,
237
bin_key,
238
representative_entity,
239
batch_range,
240
extra_index,
241
}
242
}
243
}
244
245
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
246
pub struct Wireframe3dBatchSetKey {
247
/// The identifier of the render pipeline.
248
pub pipeline: CachedRenderPipelineId,
249
250
/// The wireframe material asset ID.
251
pub asset_id: UntypedAssetId,
252
253
/// The function used to draw.
254
pub draw_function: DrawFunctionId,
255
/// The ID of the slab of GPU memory that contains vertex data.
256
///
257
/// For non-mesh items, you can fill this with 0 if your items can be
258
/// multi-drawn, or with a unique value if they can't.
259
pub vertex_slab: SlabId,
260
261
/// The ID of the slab of GPU memory that contains index data, if present.
262
///
263
/// For non-mesh items, you can safely fill this with `None`.
264
pub index_slab: Option<SlabId>,
265
}
266
267
impl PhaseItemBatchSetKey for Wireframe3dBatchSetKey {
268
fn indexed(&self) -> bool {
269
self.index_slab.is_some()
270
}
271
}
272
273
/// Data that must be identical in order to *batch* phase items together.
274
///
275
/// Note that a *batch set* (if multi-draw is in use) contains multiple batches.
276
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
277
pub struct Wireframe3dBinKey {
278
/// The wireframe mesh asset ID.
279
pub asset_id: UntypedAssetId,
280
}
281
282
pub struct SetWireframe3dImmediates;
283
284
impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dImmediates {
285
type Param = (
286
SRes<RenderWireframeInstances>,
287
SRes<RenderAssets<RenderWireframeMaterial>>,
288
);
289
type ViewQuery = ();
290
type ItemQuery = ();
291
292
#[inline]
293
fn render<'w>(
294
item: &P,
295
_view: (),
296
_item_query: Option<()>,
297
(wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,
298
pass: &mut TrackedRenderPass<'w>,
299
) -> RenderCommandResult {
300
let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {
301
return RenderCommandResult::Failure("No wireframe material found for entity");
302
};
303
let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {
304
return RenderCommandResult::Failure("No wireframe material found for entity");
305
};
306
307
pass.set_immediates(0, bytemuck::bytes_of(&wireframe_material.color));
308
RenderCommandResult::Success
309
}
310
}
311
312
pub type DrawWireframe3d = (
313
SetItemPipeline,
314
SetMeshViewBindGroup<0>,
315
SetMeshViewBindingArrayBindGroup<1>,
316
SetMeshBindGroup<2>,
317
SetWireframe3dImmediates,
318
DrawMesh,
319
);
320
321
#[derive(Resource, Clone)]
322
pub struct Wireframe3dPipeline {
323
mesh_pipeline: MeshPipeline,
324
shader: Handle<Shader>,
325
}
326
327
pub fn init_wireframe_3d_pipeline(
328
mut commands: Commands,
329
mesh_pipeline: Res<MeshPipeline>,
330
asset_server: Res<AssetServer>,
331
) {
332
commands.insert_resource(Wireframe3dPipeline {
333
mesh_pipeline: mesh_pipeline.clone(),
334
shader: load_embedded_asset!(asset_server.as_ref(), "render/wireframe.wgsl"),
335
});
336
}
337
338
impl SpecializedMeshPipeline for Wireframe3dPipeline {
339
type Key = MeshPipelineKey;
340
341
fn specialize(
342
&self,
343
key: Self::Key,
344
layout: &MeshVertexBufferLayoutRef,
345
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
346
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
347
descriptor.label = Some("wireframe_3d_pipeline".into());
348
descriptor.immediate_size = 16;
349
let fragment = descriptor.fragment.as_mut().unwrap();
350
fragment.shader = self.shader.clone();
351
descriptor.primitive.polygon_mode = PolygonMode::Line;
352
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
353
Ok(descriptor)
354
}
355
}
356
357
pub fn wireframe_3d(
358
world: &World,
359
view: ViewQuery<(
360
&ExtractedCamera,
361
&ExtractedView,
362
&ViewTarget,
363
&ViewDepthTexture,
364
)>,
365
wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
366
mut ctx: RenderContext,
367
) {
368
let view_entity = view.entity();
369
370
let (camera, extracted_view, target, depth) = view.into_inner();
371
372
let Some(wireframe_phase) = wireframe_phases.get(&extracted_view.retained_view_entity) else {
373
return;
374
};
375
376
if wireframe_phase.is_empty() {
377
return;
378
}
379
380
let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
381
label: Some("wireframe_3d"),
382
color_attachments: &[Some(target.get_color_attachment())],
383
depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
384
timestamp_writes: None,
385
occlusion_query_set: None,
386
multiview_mask: None,
387
});
388
389
if let Some(viewport) = camera.viewport.as_ref() {
390
render_pass.set_camera_viewport(viewport);
391
}
392
393
if let Err(err) = wireframe_phase.render(&mut render_pass, world, view_entity) {
394
error!("Error encountered while rendering the wireframe phase {err:?}");
395
}
396
}
397
398
/// Sets the color of the [`Wireframe`] of the entity it is attached to.
399
///
400
/// If this component is present but there's no [`Wireframe`] component,
401
/// it will still affect the color of the wireframe when [`WireframeConfig::global`] is set to true.
402
///
403
/// This overrides the [`WireframeConfig::default_color`].
404
#[derive(Component, Debug, Clone, Default, Reflect)]
405
#[reflect(Component, Default, Debug)]
406
pub struct WireframeColor {
407
pub color: Color,
408
}
409
410
#[derive(Component, Debug, Clone, Default)]
411
pub struct ExtractedWireframeColor {
412
pub color: [f32; 4],
413
}
414
415
/// Disables wireframe rendering for any entity it is attached to.
416
/// It will ignore the [`WireframeConfig`] global setting.
417
///
418
/// This requires the [`WireframePlugin`] to be enabled.
419
#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
420
#[reflect(Component, Default, Debug, PartialEq)]
421
pub struct NoWireframe;
422
423
#[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)]
424
#[reflect(Resource, Debug, Default)]
425
pub struct WireframeConfig {
426
/// Whether to show wireframes for all meshes.
427
/// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component.
428
pub global: bool,
429
/// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
430
/// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`],
431
/// but no [`WireframeColor`].
432
pub default_color: Color,
433
}
434
435
#[derive(Asset, Reflect, Clone, Debug, Default)]
436
#[reflect(Clone, Default)]
437
pub struct WireframeMaterial {
438
pub color: Color,
439
}
440
441
pub struct RenderWireframeMaterial {
442
pub color: [f32; 4],
443
}
444
445
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)]
446
#[reflect(Component, Default, Clone, PartialEq)]
447
pub struct Mesh3dWireframe(pub Handle<WireframeMaterial>);
448
449
impl AsAssetId for Mesh3dWireframe {
450
type Asset = WireframeMaterial;
451
452
fn as_asset_id(&self) -> AssetId<Self::Asset> {
453
self.0.id()
454
}
455
}
456
457
impl RenderAsset for RenderWireframeMaterial {
458
type SourceAsset = WireframeMaterial;
459
type Param = ();
460
461
fn prepare_asset(
462
source_asset: Self::SourceAsset,
463
_asset_id: AssetId<Self::SourceAsset>,
464
_param: &mut SystemParamItem<Self::Param>,
465
_previous_asset: Option<&Self>,
466
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
467
Ok(RenderWireframeMaterial {
468
color: source_asset.color.to_linear().to_f32_array(),
469
})
470
}
471
}
472
473
#[derive(Resource, Deref, DerefMut, Default)]
474
pub struct RenderWireframeInstances(MainEntityHashMap<AssetId<WireframeMaterial>>);
475
476
#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)]
477
pub struct WireframeEntitiesNeedingSpecialization {
478
#[deref]
479
pub entities: Vec<Entity>,
480
}
481
482
#[derive(Resource, Deref, DerefMut, Clone, Debug, Default)]
483
pub struct WireframeEntitySpecializationTicks {
484
pub entities: MainEntityHashMap<Tick>,
485
}
486
487
/// Stores the [`SpecializedWireframeViewPipelineCache`] for each view.
488
#[derive(Resource, Deref, DerefMut, Default)]
489
pub struct SpecializedWireframePipelineCache {
490
// view entity -> view pipeline cache
491
#[deref]
492
map: HashMap<RetainedViewEntity, SpecializedWireframeViewPipelineCache>,
493
}
494
495
/// Stores the cached render pipeline ID for each entity in a single view, as
496
/// well as the last time it was changed.
497
#[derive(Deref, DerefMut, Default)]
498
pub struct SpecializedWireframeViewPipelineCache {
499
// material entity -> (tick, pipeline_id)
500
#[deref]
501
map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
502
}
503
504
#[derive(Resource)]
505
struct GlobalWireframeMaterial {
506
// This handle will be reused when the global config is enabled
507
handle: Handle<WireframeMaterial>,
508
}
509
510
pub fn extract_wireframe_materials(
511
mut material_instances: ResMut<RenderWireframeInstances>,
512
changed_meshes_query: Extract<
513
Query<
514
(Entity, &ViewVisibility, &Mesh3dWireframe),
515
Or<(Changed<ViewVisibility>, Changed<Mesh3dWireframe>)>,
516
>,
517
>,
518
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
519
mut removed_materials_query: Extract<RemovedComponents<Mesh3dWireframe>>,
520
) {
521
for (entity, view_visibility, material) in &changed_meshes_query {
522
if view_visibility.get() {
523
material_instances.insert(entity.into(), material.id());
524
} else {
525
material_instances.remove(&MainEntity::from(entity));
526
}
527
}
528
529
for entity in removed_visibilities_query
530
.read()
531
.chain(removed_materials_query.read())
532
{
533
// Only queue a mesh for removal if we didn't pick it up above.
534
// It's possible that a necessary component was removed and re-added in
535
// the same frame.
536
if !changed_meshes_query.contains(entity) {
537
material_instances.remove(&MainEntity::from(entity));
538
}
539
}
540
}
541
542
fn setup_global_wireframe_material(
543
mut commands: Commands,
544
mut materials: ResMut<Assets<WireframeMaterial>>,
545
config: Res<WireframeConfig>,
546
) {
547
// Create the handle used for the global material
548
commands.insert_resource(GlobalWireframeMaterial {
549
handle: materials.add(WireframeMaterial {
550
color: config.default_color,
551
}),
552
});
553
}
554
555
/// Updates the wireframe material of all entities without a [`WireframeColor`] or without a [`Wireframe`] component
556
fn global_color_changed(
557
config: Res<WireframeConfig>,
558
mut materials: ResMut<Assets<WireframeMaterial>>,
559
global_material: Res<GlobalWireframeMaterial>,
560
) {
561
if let Some(global_material) = materials.get_mut(&global_material.handle) {
562
global_material.color = config.default_color;
563
}
564
}
565
566
/// Updates the wireframe material when the color in [`WireframeColor`] changes
567
fn wireframe_color_changed(
568
mut materials: ResMut<Assets<WireframeMaterial>>,
569
mut colors_changed: Query<
570
(&mut Mesh3dWireframe, &WireframeColor),
571
(With<Wireframe>, Changed<WireframeColor>),
572
>,
573
) {
574
for (mut handle, wireframe_color) in &mut colors_changed {
575
handle.0 = materials.add(WireframeMaterial {
576
color: wireframe_color.color,
577
});
578
}
579
}
580
581
/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component, and removes it
582
/// for any mesh with a [`NoWireframe`] component.
583
fn apply_wireframe_material(
584
mut commands: Commands,
585
mut materials: ResMut<Assets<WireframeMaterial>>,
586
wireframes: Query<
587
(Entity, Option<&WireframeColor>),
588
(With<Wireframe>, Without<Mesh3dWireframe>),
589
>,
590
no_wireframes: Query<Entity, (With<NoWireframe>, With<Mesh3dWireframe>)>,
591
mut removed_wireframes: RemovedComponents<Wireframe>,
592
global_material: Res<GlobalWireframeMaterial>,
593
) {
594
for e in removed_wireframes.read().chain(no_wireframes.iter()) {
595
if let Ok(mut commands) = commands.get_entity(e) {
596
commands.remove::<Mesh3dWireframe>();
597
}
598
}
599
600
let mut material_to_spawn = vec![];
601
for (e, maybe_color) in &wireframes {
602
let material = get_wireframe_material(maybe_color, &mut materials, &global_material);
603
material_to_spawn.push((e, Mesh3dWireframe(material)));
604
}
605
commands.try_insert_batch(material_to_spawn);
606
}
607
608
type WireframeFilter = (With<Mesh3d>, Without<Wireframe>, Without<NoWireframe>);
609
610
/// Applies or removes a wireframe material on any mesh without a [`Wireframe`] or [`NoWireframe`] component.
611
fn apply_global_wireframe_material(
612
mut commands: Commands,
613
config: Res<WireframeConfig>,
614
meshes_without_material: Query<
615
(Entity, Option<&WireframeColor>),
616
(WireframeFilter, Without<Mesh3dWireframe>),
617
>,
618
meshes_with_global_material: Query<Entity, (WireframeFilter, With<Mesh3dWireframe>)>,
619
global_material: Res<GlobalWireframeMaterial>,
620
mut materials: ResMut<Assets<WireframeMaterial>>,
621
) {
622
if config.global {
623
let mut material_to_spawn = vec![];
624
for (e, maybe_color) in &meshes_without_material {
625
let material = get_wireframe_material(maybe_color, &mut materials, &global_material);
626
// We only add the material handle but not the Wireframe component
627
// This makes it easy to detect which mesh is using the global material and which ones are user specified
628
material_to_spawn.push((e, Mesh3dWireframe(material)));
629
}
630
commands.try_insert_batch(material_to_spawn);
631
} else {
632
for e in &meshes_with_global_material {
633
commands.entity(e).remove::<Mesh3dWireframe>();
634
}
635
}
636
}
637
638
/// Gets a handle to a wireframe material with a fallback on the default material
639
fn get_wireframe_material(
640
maybe_color: Option<&WireframeColor>,
641
wireframe_materials: &mut Assets<WireframeMaterial>,
642
global_material: &GlobalWireframeMaterial,
643
) -> Handle<WireframeMaterial> {
644
if let Some(wireframe_color) = maybe_color {
645
wireframe_materials.add(WireframeMaterial {
646
color: wireframe_color.color,
647
})
648
} else {
649
// If there's no color specified we can use the global material since it's already set to use the default_color
650
global_material.handle.clone()
651
}
652
}
653
654
fn extract_wireframe_3d_camera(
655
mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
656
cameras: Extract<Query<(Entity, &Camera, Has<NoIndirectDrawing>), With<Camera3d>>>,
657
mut live_entities: Local<HashSet<RetainedViewEntity>>,
658
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
659
) {
660
live_entities.clear();
661
for (main_entity, camera, no_indirect_drawing) in &cameras {
662
if !camera.is_active {
663
continue;
664
}
665
let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
666
GpuPreprocessingMode::Culling
667
} else {
668
GpuPreprocessingMode::PreprocessingOnly
669
});
670
671
let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0);
672
wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
673
live_entities.insert(retained_view_entity);
674
}
675
676
// Clear out all dead views.
677
wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
678
}
679
680
pub fn extract_wireframe_entities_needing_specialization(
681
entities_needing_specialization: Extract<Res<WireframeEntitiesNeedingSpecialization>>,
682
mut entity_specialization_ticks: ResMut<WireframeEntitySpecializationTicks>,
683
views: Query<&ExtractedView>,
684
mut specialized_wireframe_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
685
mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
686
ticks: SystemChangeTick,
687
) {
688
for entity in entities_needing_specialization.iter() {
689
// Update the entity's specialization tick with this run's tick
690
entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
691
}
692
693
for entity in removed_meshes_query.read() {
694
for view in &views {
695
if let Some(specialized_wireframe_pipeline_cache) =
696
specialized_wireframe_pipeline_cache.get_mut(&view.retained_view_entity)
697
{
698
specialized_wireframe_pipeline_cache.remove(&MainEntity::from(entity));
699
}
700
}
701
}
702
}
703
704
pub fn check_wireframe_entities_needing_specialization(
705
needs_specialization: Query<
706
Entity,
707
Or<(
708
Changed<Mesh3d>,
709
AssetChanged<Mesh3d>,
710
Changed<Mesh3dWireframe>,
711
AssetChanged<Mesh3dWireframe>,
712
)>,
713
>,
714
mut entities_needing_specialization: ResMut<WireframeEntitiesNeedingSpecialization>,
715
) {
716
entities_needing_specialization.clear();
717
for entity in &needs_specialization {
718
entities_needing_specialization.push(entity);
719
}
720
}
721
722
pub fn specialize_wireframes(
723
render_meshes: Res<RenderAssets<RenderMesh>>,
724
render_mesh_instances: Res<RenderMeshInstances>,
725
render_wireframe_instances: Res<RenderWireframeInstances>,
726
render_visibility_ranges: Res<RenderVisibilityRanges>,
727
wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
728
views: Query<(&ExtractedView, &RenderVisibleEntities)>,
729
view_key_cache: Res<ViewKeyCache>,
730
entity_specialization_ticks: Res<WireframeEntitySpecializationTicks>,
731
view_specialization_ticks: Res<ViewSpecializationTicks>,
732
mut specialized_material_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
733
mut pipelines: ResMut<SpecializedMeshPipelines<Wireframe3dPipeline>>,
734
pipeline: Res<Wireframe3dPipeline>,
735
pipeline_cache: Res<PipelineCache>,
736
render_lightmaps: Res<RenderLightmaps>,
737
ticks: SystemChangeTick,
738
) {
739
// Record the retained IDs of all views so that we can expire old
740
// pipeline IDs.
741
let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
742
743
for (view, visible_entities) in &views {
744
all_views.insert(view.retained_view_entity);
745
746
if !wireframe_phases.contains_key(&view.retained_view_entity) {
747
continue;
748
}
749
750
let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
751
continue;
752
};
753
754
let view_tick = view_specialization_ticks
755
.get(&view.retained_view_entity)
756
.unwrap();
757
let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
758
.entry(view.retained_view_entity)
759
.or_default();
760
761
for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
762
if !render_wireframe_instances.contains_key(visible_entity) {
763
continue;
764
};
765
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
766
else {
767
continue;
768
};
769
let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
770
let last_specialized_tick = view_specialized_material_pipeline_cache
771
.get(visible_entity)
772
.map(|(tick, _)| *tick);
773
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
774
view_tick.is_newer_than(tick, ticks.this_run())
775
|| entity_tick.is_newer_than(tick, ticks.this_run())
776
});
777
if !needs_specialization {
778
continue;
779
}
780
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
781
continue;
782
};
783
784
let mut mesh_key = *view_key;
785
mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology());
786
787
if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
788
mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
789
}
790
791
if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
792
// If the previous frame have skins or morph targets, note that.
793
if mesh_instance
794
.flags
795
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
796
{
797
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
798
}
799
if mesh_instance
800
.flags
801
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
802
{
803
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
804
}
805
}
806
807
// Even though we don't use the lightmap in the wireframe, the
808
// `SetMeshBindGroup` render command will bind the data for it. So
809
// we need to include the appropriate flag in the mesh pipeline key
810
// to ensure that the necessary bind group layout entries are
811
// present.
812
if render_lightmaps
813
.render_lightmaps
814
.contains_key(visible_entity)
815
{
816
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
817
}
818
819
let pipeline_id =
820
pipelines.specialize(&pipeline_cache, &pipeline, mesh_key, &mesh.layout);
821
let pipeline_id = match pipeline_id {
822
Ok(id) => id,
823
Err(err) => {
824
error!("{}", err);
825
continue;
826
}
827
};
828
829
view_specialized_material_pipeline_cache
830
.insert(*visible_entity, (ticks.this_run(), pipeline_id));
831
}
832
}
833
834
// Delete specialized pipelines belonging to views that have expired.
835
specialized_material_pipeline_cache
836
.retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
837
}
838
839
fn queue_wireframes(
840
custom_draw_functions: Res<DrawFunctions<Wireframe3d>>,
841
render_mesh_instances: Res<RenderMeshInstances>,
842
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
843
mesh_allocator: Res<MeshAllocator>,
844
specialized_wireframe_pipeline_cache: Res<SpecializedWireframePipelineCache>,
845
render_wireframe_instances: Res<RenderWireframeInstances>,
846
mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
847
mut views: Query<(&ExtractedView, &RenderVisibleEntities)>,
848
) {
849
for (view, visible_entities) in &mut views {
850
let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else {
851
continue;
852
};
853
let draw_wireframe = custom_draw_functions.read().id::<DrawWireframe3d>();
854
855
let Some(view_specialized_material_pipeline_cache) =
856
specialized_wireframe_pipeline_cache.get(&view.retained_view_entity)
857
else {
858
continue;
859
};
860
861
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
862
let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else {
863
continue;
864
};
865
let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache
866
.get(visible_entity)
867
.map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))
868
else {
869
continue;
870
};
871
872
// Skip the entity if it's cached in a bin and up to date.
873
if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) {
874
continue;
875
}
876
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
877
else {
878
continue;
879
};
880
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
881
let bin_key = Wireframe3dBinKey {
882
asset_id: mesh_instance.mesh_asset_id.untyped(),
883
};
884
let batch_set_key = Wireframe3dBatchSetKey {
885
pipeline: pipeline_id,
886
asset_id: wireframe_instance.untyped(),
887
draw_function: draw_wireframe,
888
vertex_slab: vertex_slab.unwrap_or_default(),
889
index_slab,
890
};
891
wireframe_phase.add(
892
batch_set_key,
893
bin_key,
894
(*render_entity, *visible_entity),
895
mesh_instance.current_uniform_index,
896
BinnedRenderPhaseType::mesh(
897
mesh_instance.should_batch(),
898
&gpu_preprocessing_support,
899
),
900
current_change_tick,
901
);
902
}
903
}
904
}
905
906