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