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