Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/wireframe.rs
9550 views
1
use crate::{
2
render::{PreprocessBindGroups, PreprocessPipelines},
3
DrawMesh, MeshPipeline, MeshPipelineKey, RenderLightmaps, RenderMeshInstanceFlags,
4
RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup,
5
ViewKeyCache, ViewSpecializationTicks,
6
};
7
use bevy_app::{App, Plugin, PostUpdate, Startup, Update};
8
use bevy_asset::{
9
embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp,
10
AssetEventSystems, AssetId, AssetServer, Assets, Handle, UntypedAssetId,
11
};
12
use bevy_camera::{visibility::ViewVisibility, Camera, Camera3d};
13
use bevy_color::{Color, ColorToComponents};
14
use bevy_core_pipeline::schedule::{Core3d, Core3dSystems};
15
use bevy_derive::{Deref, DerefMut};
16
use bevy_ecs::{
17
change_detection::Tick,
18
prelude::*,
19
query::ROQueryItem,
20
system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem},
21
};
22
use bevy_mesh::{Mesh, Mesh3d, MeshVertexBufferLayoutRef};
23
use bevy_platform::{
24
collections::{HashMap, HashSet},
25
hash::FixedHasher,
26
};
27
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
28
use bevy_render::{
29
batching::gpu_preprocessing::{
30
GpuPreprocessingMode, GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers,
31
IndirectParametersNonIndexed,
32
},
33
camera::{extract_cameras, ExtractedCamera},
34
extract_resource::ExtractResource,
35
mesh::{
36
allocator::{MeshAllocator, SlabId},
37
RenderMesh, RenderMeshBufferInfo,
38
},
39
prelude::*,
40
render_asset::{
41
prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets,
42
},
43
render_phase::{
44
AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType,
45
CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
46
PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,
47
SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases,
48
},
49
render_resource::{binding_types::*, *},
50
renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
51
sync_world::{MainEntity, MainEntityHashMap},
52
view::{
53
ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities,
54
RetainedViewEntity, ViewDepthTexture, ViewTarget,
55
},
56
Extract, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems,
57
};
58
use bevy_shader::Shader;
59
use bytemuck::{Pod, Zeroable};
60
use core::{any::TypeId, hash::Hash, mem::size_of, ops::Range};
61
use tracing::{error, warn};
62
63
/// A [`Plugin`] that draws wireframes.
64
///
65
/// Wireframes currently do not work when using webgl or webgpu.
66
/// Supported rendering backends:
67
/// - DX12
68
/// - Vulkan
69
/// - Metal
70
///
71
/// This is a native only feature.
72
#[derive(Debug, Default)]
73
pub struct WireframePlugin {
74
/// Debugging flags that can optionally be set when constructing the renderer.
75
pub debug_flags: RenderDebugFlags,
76
}
77
78
impl WireframePlugin {
79
/// Creates a new [`WireframePlugin`] with the given debug flags.
80
pub fn new(debug_flags: RenderDebugFlags) -> Self {
81
Self { debug_flags }
82
}
83
}
84
85
impl Plugin for WireframePlugin {
86
fn build(&self, app: &mut App) {
87
embedded_asset!(app, "render/wireframe.wgsl");
88
89
app.add_plugins((
90
BinnedRenderPhasePlugin::<Wireframe3d, MeshPipeline>::new(self.debug_flags),
91
RenderAssetPlugin::<RenderWireframeMaterial>::default(),
92
))
93
.init_asset::<WireframeMaterial>()
94
.init_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
95
.init_resource::<WireframeConfig>()
96
.init_resource::<WireframeEntitiesNeedingSpecialization>()
97
.register_type::<WireframeLineWidth>()
98
.register_type::<WireframeTopology>()
99
.add_systems(Startup, setup_global_wireframe_material)
100
.add_systems(
101
Update,
102
(
103
wireframe_config_changed.run_if(resource_changed::<WireframeConfig>),
104
wireframe_color_changed,
105
wireframe_line_width_changed,
106
wireframe_topology_changed,
107
// Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global
108
// wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed.
109
(apply_wireframe_material, apply_global_wireframe_material).chain(),
110
),
111
)
112
.add_systems(
113
PostUpdate,
114
check_wireframe_entities_needing_specialization
115
.after(AssetEventSystems)
116
.run_if(resource_exists::<WireframeConfig>),
117
);
118
}
119
120
fn finish(&self, app: &mut App) {
121
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
122
return;
123
};
124
125
let required_features = WgpuFeatures::POLYGON_MODE_LINE | WgpuFeatures::IMMEDIATES;
126
let render_device = render_app.world().resource::<RenderDevice>();
127
if !render_device.features().contains(required_features) {
128
warn!(
129
"WireframePlugin not loaded. GPU lacks support for required features: {:?}.",
130
required_features
131
);
132
return;
133
}
134
135
// we need storage for vertex pulling in the wide wireframe path
136
render_app
137
.world_mut()
138
.resource_mut::<MeshAllocator>()
139
.extra_buffer_usages |= BufferUsages::STORAGE;
140
141
render_app
142
.init_resource::<WireframeEntitySpecializationTicks>()
143
.init_resource::<SpecializedWireframePipelineCache>()
144
.init_resource::<DrawFunctions<Wireframe3d>>()
145
.add_render_command::<Wireframe3d, DrawWireframe3dThin>()
146
.add_render_command::<Wireframe3d, DrawWireframe3dWide>()
147
.init_resource::<RenderWireframeInstances>()
148
.init_resource::<WireframeWideBindGroups>()
149
.init_resource::<SpecializedMeshPipelines<Wireframe3dPipeline>>()
150
.add_systems(RenderStartup, init_wireframe_3d_pipeline)
151
.add_systems(
152
Core3d,
153
wireframe_3d
154
.after(Core3dSystems::MainPass)
155
.before(Core3dSystems::PostProcess),
156
)
157
.add_systems(
158
ExtractSchedule,
159
(
160
extract_wireframe_3d_camera,
161
extract_wireframe_entities_needing_specialization.after(extract_cameras),
162
extract_wireframe_materials,
163
),
164
)
165
.add_systems(
166
Render,
167
(
168
specialize_wireframes
169
.in_set(RenderSystems::PrepareMeshes)
170
.after(prepare_assets::<RenderWireframeMaterial>)
171
.after(prepare_assets::<RenderMesh>),
172
prepare_wireframe_wide_bind_groups
173
.in_set(RenderSystems::PrepareBindGroups)
174
.after(prepare_assets::<RenderWireframeMaterial>)
175
.after(prepare_assets::<RenderMesh>),
176
queue_wireframes
177
.in_set(RenderSystems::QueueMeshes)
178
.after(prepare_assets::<RenderWireframeMaterial>),
179
),
180
);
181
}
182
}
183
184
/// Enables wireframe rendering for any entity it is attached to.
185
/// It will ignore the [`WireframeConfig`] global setting.
186
///
187
/// This requires the [`WireframePlugin`] to be enabled.
188
#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
189
#[reflect(Component, Default, Debug, PartialEq)]
190
pub struct Wireframe;
191
192
pub struct Wireframe3d {
193
/// Determines which objects can be placed into a *batch set*.
194
///
195
/// Objects in a single batch set can potentially be multi-drawn together,
196
/// if it's enabled and the current platform supports it.
197
pub batch_set_key: Wireframe3dBatchSetKey,
198
/// The key, which determines which can be batched.
199
pub bin_key: Wireframe3dBinKey,
200
/// An entity from which data will be fetched, including the mesh if
201
/// applicable.
202
pub representative_entity: (Entity, MainEntity),
203
/// The ranges of instances.
204
pub batch_range: Range<u32>,
205
/// An extra index, which is either a dynamic offset or an index in the
206
/// indirect parameters list.
207
pub extra_index: PhaseItemExtraIndex,
208
}
209
210
impl PhaseItem for Wireframe3d {
211
fn entity(&self) -> Entity {
212
self.representative_entity.0
213
}
214
215
fn main_entity(&self) -> MainEntity {
216
self.representative_entity.1
217
}
218
219
fn draw_function(&self) -> DrawFunctionId {
220
self.batch_set_key.draw_function
221
}
222
223
fn batch_range(&self) -> &Range<u32> {
224
&self.batch_range
225
}
226
227
fn batch_range_mut(&mut self) -> &mut Range<u32> {
228
&mut self.batch_range
229
}
230
231
fn extra_index(&self) -> PhaseItemExtraIndex {
232
self.extra_index.clone()
233
}
234
235
fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
236
(&mut self.batch_range, &mut self.extra_index)
237
}
238
}
239
240
impl CachedRenderPipelinePhaseItem for Wireframe3d {
241
fn cached_pipeline(&self) -> CachedRenderPipelineId {
242
self.batch_set_key.pipeline
243
}
244
}
245
246
impl BinnedPhaseItem for Wireframe3d {
247
type BinKey = Wireframe3dBinKey;
248
type BatchSetKey = Wireframe3dBatchSetKey;
249
250
fn new(
251
batch_set_key: Self::BatchSetKey,
252
bin_key: Self::BinKey,
253
representative_entity: (Entity, MainEntity),
254
batch_range: Range<u32>,
255
extra_index: PhaseItemExtraIndex,
256
) -> Self {
257
Self {
258
batch_set_key,
259
bin_key,
260
representative_entity,
261
batch_range,
262
extra_index,
263
}
264
}
265
}
266
267
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
268
pub struct Wireframe3dBatchSetKey {
269
/// The identifier of the render pipeline.
270
pub pipeline: CachedRenderPipelineId,
271
272
/// The wireframe material asset ID.
273
pub asset_id: UntypedAssetId,
274
275
/// The function used to draw.
276
pub draw_function: DrawFunctionId,
277
/// The ID of the slab of GPU memory that contains vertex data.
278
///
279
/// For non-mesh items, you can fill this with 0 if your items can be
280
/// multi-drawn, or with a unique value if they can't.
281
pub vertex_slab: SlabId,
282
283
/// The ID of the slab of GPU memory that contains index data, if present.
284
///
285
/// For non-mesh items, you can safely fill this with `None`.
286
pub index_slab: Option<SlabId>,
287
288
/// For the wide wireframe path, the mesh asset ID ensures all draws in one
289
/// batch set share the same vertex-pull params uniform. `None` for the thin
290
/// path, which doesn't need per-mesh bind groups.
291
pub mesh_asset_id: Option<UntypedAssetId>,
292
}
293
294
impl PhaseItemBatchSetKey for Wireframe3dBatchSetKey {
295
fn indexed(&self) -> bool {
296
self.index_slab.is_some()
297
}
298
}
299
300
/// Data that must be identical in order to *batch* phase items together.
301
///
302
/// Note that a *batch set* (if multi-draw is in use) contains multiple batches.
303
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
304
pub struct Wireframe3dBinKey {
305
/// The wireframe mesh asset ID.
306
pub asset_id: UntypedAssetId,
307
}
308
309
pub struct SetWireframe3dThinImmediates;
310
311
impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dThinImmediates {
312
type Param = (
313
SRes<RenderWireframeInstances>,
314
SRes<RenderAssets<RenderWireframeMaterial>>,
315
);
316
type ViewQuery = ();
317
type ItemQuery = ();
318
319
#[inline]
320
fn render<'w>(
321
item: &P,
322
_view: (),
323
_item_query: Option<()>,
324
(wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,
325
pass: &mut TrackedRenderPass<'w>,
326
) -> RenderCommandResult {
327
let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {
328
return RenderCommandResult::Failure("No wireframe material found for entity");
329
};
330
let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {
331
return RenderCommandResult::Failure("No wireframe material found for entity");
332
};
333
334
pass.set_immediates(0, bytemuck::bytes_of(&wireframe_material.color));
335
RenderCommandResult::Success
336
}
337
}
338
339
pub struct SetWireframe3dWideImmediates;
340
341
#[derive(Clone, Copy, Pod, Zeroable)]
342
#[repr(C)]
343
struct WireframeWideImmediates {
344
color: [f32; 4],
345
line_width: f32,
346
smoothing: f32,
347
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
348
_padding: [f32; 2],
349
}
350
351
impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dWideImmediates {
352
type Param = (
353
SRes<RenderWireframeInstances>,
354
SRes<RenderAssets<RenderWireframeMaterial>>,
355
);
356
type ViewQuery = ();
357
type ItemQuery = ();
358
359
#[inline]
360
fn render<'w>(
361
item: &P,
362
_view: (),
363
_item_query: Option<()>,
364
(wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>,
365
pass: &mut TrackedRenderPass<'w>,
366
) -> RenderCommandResult {
367
let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else {
368
return RenderCommandResult::Failure("No wireframe material found for entity");
369
};
370
let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else {
371
return RenderCommandResult::Failure("No wireframe material found for entity");
372
};
373
374
let push = WireframeWideImmediates {
375
color: wireframe_material.color,
376
line_width: wireframe_material.line_width,
377
smoothing: if wireframe_material.line_width <= 1.0 {
378
0.5
379
} else {
380
1.0
381
},
382
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
383
_padding: [0.0; 2],
384
};
385
pass.set_immediates(0, bytemuck::bytes_of(&push));
386
RenderCommandResult::Success
387
}
388
}
389
390
#[derive(Clone, Copy, ShaderType, Pod, Zeroable)]
391
#[repr(C)]
392
pub struct WireframeVertexPullParams {
393
pub index_offset: u32,
394
pub vertex_stride_u32s: u32,
395
pub position_offset_u32s: u32,
396
}
397
398
#[derive(Resource, Default)]
399
pub struct WireframeWideBindGroups {
400
pub params: DynamicUniformBuffer<WireframeVertexPullParams>,
401
pub bind_groups: HashMap<AssetId<Mesh>, (BindGroup, u32)>,
402
}
403
404
pub fn prepare_wireframe_wide_bind_groups(
405
render_mesh_instances: Res<RenderMeshInstances>,
406
render_meshes: Res<RenderAssets<RenderMesh>>,
407
render_wireframe_instances: Res<RenderWireframeInstances>,
408
render_wireframe_assets: Res<RenderAssets<RenderWireframeMaterial>>,
409
mesh_allocator: Res<MeshAllocator>,
410
pipeline: Res<Wireframe3dPipeline>,
411
render_device: Res<RenderDevice>,
412
render_queue: Res<RenderQueue>,
413
mut wide_bind_groups: ResMut<WireframeWideBindGroups>,
414
) {
415
wide_bind_groups.bind_groups.clear();
416
417
struct MeshInfo {
418
mesh_id: AssetId<Mesh>,
419
params: WireframeVertexPullParams,
420
vertex_buffer: Buffer,
421
index_buffer: Buffer,
422
}
423
424
let mut infos: Vec<MeshInfo> = Vec::new();
425
let mut seen: HashSet<AssetId<Mesh>, FixedHasher> = HashSet::default();
426
427
for (entity, wireframe_asset_id) in render_wireframe_instances.iter() {
428
let Some(material) = render_wireframe_assets.get(*wireframe_asset_id) else {
429
continue;
430
};
431
if material.line_width <= 1.0 && material.topology != WireframeTopology::Quads {
432
continue;
433
}
434
435
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*entity) else {
436
continue;
437
};
438
let mesh_id = mesh_instance.mesh_asset_id;
439
if !seen.insert(mesh_id) {
440
continue;
441
}
442
443
let Some(mesh) = render_meshes.get(mesh_id) else {
444
continue;
445
};
446
let Some(vertex_slice) = mesh_allocator.mesh_vertex_slice(&mesh_id) else {
447
continue;
448
};
449
let Some(index_slice) = mesh_allocator.mesh_index_slice(&mesh_id) else {
450
continue;
451
};
452
453
let vertex_stride_bytes = mesh.layout.0.layout().array_stride as u32;
454
let position_offset_bytes = mesh
455
.layout
456
.0
457
.layout()
458
.attributes
459
.first()
460
.map(|a| a.offset as u32)
461
.unwrap_or(0);
462
463
infos.push(MeshInfo {
464
mesh_id,
465
params: WireframeVertexPullParams {
466
index_offset: index_slice.range.start,
467
vertex_stride_u32s: vertex_stride_bytes / 4,
468
position_offset_u32s: position_offset_bytes / 4,
469
},
470
vertex_buffer: vertex_slice.buffer.clone(),
471
index_buffer: index_slice.buffer.clone(),
472
});
473
}
474
475
if infos.is_empty() {
476
return;
477
}
478
479
let Some(mut writer) =
480
wide_bind_groups
481
.params
482
.get_writer(infos.len(), &render_device, &render_queue)
483
else {
484
return;
485
};
486
487
let offsets: Vec<u32> = infos
488
.iter()
489
.map(|info| writer.write(&info.params))
490
.collect();
491
drop(writer);
492
493
let WireframeWideBindGroups {
494
ref params,
495
ref mut bind_groups,
496
} = *wide_bind_groups;
497
let Some(params_binding) = params.binding() else {
498
return;
499
};
500
501
for (i, info) in infos.iter().enumerate() {
502
let bind_group = render_device.create_bind_group(
503
"wireframe_wide_bind_group",
504
&pipeline.wide_bind_group_layout,
505
&BindGroupEntries::sequential((
506
info.vertex_buffer.as_entire_buffer_binding(),
507
info.index_buffer.as_entire_buffer_binding(),
508
params_binding.clone(),
509
)),
510
);
511
bind_groups.insert(info.mesh_id, (bind_group, offsets[i]));
512
}
513
}
514
515
pub struct SetWireframe3dWideBindGroup;
516
517
impl<P: PhaseItem> RenderCommand<P> for SetWireframe3dWideBindGroup {
518
type Param = (SRes<RenderMeshInstances>, SRes<WireframeWideBindGroups>);
519
type ViewQuery = ();
520
type ItemQuery = ();
521
522
#[inline]
523
fn render<'w>(
524
item: &P,
525
_view: (),
526
_item_query: Option<()>,
527
(render_mesh_instances, wide_bind_groups): SystemParamItem<'w, '_, Self::Param>,
528
pass: &mut TrackedRenderPass<'w>,
529
) -> RenderCommandResult {
530
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(item.main_entity())
531
else {
532
return RenderCommandResult::Skip;
533
};
534
let Some((bind_group, dynamic_offset)) = wide_bind_groups
535
.into_inner()
536
.bind_groups
537
.get(&mesh_instance.mesh_asset_id)
538
else {
539
return RenderCommandResult::Skip;
540
};
541
542
pass.set_bind_group(3, bind_group, &[*dynamic_offset]);
543
RenderCommandResult::Success
544
}
545
}
546
547
pub struct DrawWireframeMeshPulled;
548
549
impl<P: PhaseItem> RenderCommand<P> for DrawWireframeMeshPulled {
550
type Param = (
551
SRes<RenderMeshInstances>,
552
SRes<RenderAssets<RenderMesh>>,
553
SRes<MeshAllocator>,
554
SRes<IndirectParametersBuffers>,
555
SRes<PipelineCache>,
556
Option<SRes<PreprocessPipelines>>,
557
SRes<GpuPreprocessingSupport>,
558
);
559
type ViewQuery = Has<PreprocessBindGroups>;
560
type ItemQuery = ();
561
562
#[inline]
563
fn render<'w>(
564
item: &P,
565
has_preprocess_bind_group: ROQueryItem<Self::ViewQuery>,
566
_item_query: Option<()>,
567
(
568
render_mesh_instances,
569
render_meshes,
570
mesh_allocator,
571
indirect_parameters_buffers,
572
pipeline_cache,
573
preprocess_pipelines,
574
preprocessing_support,
575
): SystemParamItem<'w, '_, Self::Param>,
576
pass: &mut TrackedRenderPass<'w>,
577
) -> RenderCommandResult {
578
if let Some(preprocess_pipelines) = preprocess_pipelines
579
&& (!has_preprocess_bind_group
580
|| !preprocess_pipelines
581
.pipelines_are_loaded(&pipeline_cache, &preprocessing_support))
582
{
583
return RenderCommandResult::Skip;
584
}
585
586
let render_mesh_instances = render_mesh_instances.into_inner();
587
let render_meshes = render_meshes.into_inner();
588
let mesh_allocator = mesh_allocator.into_inner();
589
let indirect_parameters_buffers = indirect_parameters_buffers.into_inner();
590
591
let Some(mesh_asset_id) = render_mesh_instances.mesh_asset_id(item.main_entity()) else {
592
return RenderCommandResult::Skip;
593
};
594
let Some(gpu_mesh) = render_meshes.get(mesh_asset_id) else {
595
return RenderCommandResult::Skip;
596
};
597
598
let index_count = match &gpu_mesh.buffer_info {
599
RenderMeshBufferInfo::Indexed { count, .. } => *count,
600
RenderMeshBufferInfo::NonIndexed => gpu_mesh.vertex_count,
601
};
602
603
match item.extra_index() {
604
PhaseItemExtraIndex::None | PhaseItemExtraIndex::DynamicOffset(_) => {
605
// direct draw: use vertex range starting at first_vertex_index so
606
// the shader can recover draw_id via mesh[instance_index].first_vertex_index.
607
let Some(vertex_slice) = mesh_allocator.mesh_vertex_slice(&mesh_asset_id) else {
608
return RenderCommandResult::Skip;
609
};
610
let first_vertex = vertex_slice.range.start;
611
pass.draw(
612
first_vertex..(first_vertex + index_count),
613
item.batch_range().clone(),
614
);
615
}
616
PhaseItemExtraIndex::IndirectParametersIndex {
617
range: indirect_parameters_range,
618
batch_set_index,
619
} => {
620
// no indexes - the preprocessor sets base_vertex = first_vertex_index.
621
let Some(phase_indirect) = indirect_parameters_buffers.get(&TypeId::of::<P>())
622
else {
623
warn!("Wireframe wide: indirect parameters buffer missing for phase");
624
return RenderCommandResult::Skip;
625
};
626
let (Some(indirect_buffer), Some(batch_sets_buffer)) = (
627
phase_indirect.non_indexed.data_buffer(),
628
phase_indirect.non_indexed.batch_sets_buffer(),
629
) else {
630
warn!("Wireframe wide: non-indexed indirect parameters buffer not ready");
631
return RenderCommandResult::Skip;
632
};
633
634
let indirect_parameters_offset = indirect_parameters_range.start as u64
635
* size_of::<IndirectParametersNonIndexed>() as u64;
636
let indirect_parameters_count =
637
indirect_parameters_range.end - indirect_parameters_range.start;
638
639
match batch_set_index {
640
Some(batch_set_index) => {
641
let count_offset =
642
u32::from(batch_set_index) * (size_of::<IndirectBatchSet>() as u32);
643
pass.multi_draw_indirect_count(
644
indirect_buffer,
645
indirect_parameters_offset,
646
batch_sets_buffer,
647
count_offset as u64,
648
indirect_parameters_count,
649
);
650
}
651
None => {
652
pass.multi_draw_indirect(
653
indirect_buffer,
654
indirect_parameters_offset,
655
indirect_parameters_count,
656
);
657
}
658
}
659
}
660
}
661
RenderCommandResult::Success
662
}
663
}
664
665
/// Draw wireframes with `PolygonMode::Line`, i.e. the fast path.
666
pub type DrawWireframe3dThin = (
667
SetItemPipeline,
668
SetMeshViewBindGroup<0>,
669
SetMeshViewBindingArrayBindGroup<1>,
670
SetMeshBindGroup<2>,
671
SetWireframe3dThinImmediates,
672
DrawMesh,
673
);
674
675
/// Draw wireframes using vertex pulling for wide lines or quad topology.
676
pub type DrawWireframe3dWide = (
677
SetItemPipeline,
678
SetMeshViewBindGroup<0>,
679
SetMeshViewBindingArrayBindGroup<1>,
680
SetMeshBindGroup<2>,
681
SetWireframe3dWideBindGroup,
682
SetWireframe3dWideImmediates,
683
DrawWireframeMeshPulled,
684
);
685
686
#[derive(Resource, Clone)]
687
pub struct Wireframe3dPipeline {
688
mesh_pipeline: MeshPipeline,
689
shader: Handle<Shader>,
690
pub wide_bind_group_layout: BindGroupLayout,
691
pub wide_bind_group_layout_descriptor: BindGroupLayoutDescriptor,
692
}
693
694
pub fn init_wireframe_3d_pipeline(
695
mut commands: Commands,
696
mesh_pipeline: Res<MeshPipeline>,
697
asset_server: Res<AssetServer>,
698
render_device: Res<RenderDevice>,
699
) {
700
let wide_bgl_entries = BindGroupLayoutEntries::sequential(
701
ShaderStages::VERTEX,
702
(
703
storage_buffer_read_only::<u32>(false), // vertex data
704
storage_buffer_read_only::<u32>(false), // index data
705
uniform_buffer::<WireframeVertexPullParams>(true),
706
),
707
);
708
709
let wide_bind_group_layout = render_device
710
.create_bind_group_layout("wireframe_wide_bind_group_layout", &wide_bgl_entries);
711
712
let wide_bind_group_layout_descriptor =
713
BindGroupLayoutDescriptor::new("wireframe_wide_bind_group_layout", &wide_bgl_entries);
714
715
commands.insert_resource(Wireframe3dPipeline {
716
mesh_pipeline: mesh_pipeline.clone(),
717
shader: load_embedded_asset!(asset_server.as_ref(), "render/wireframe.wgsl"),
718
wide_bind_group_layout,
719
wide_bind_group_layout_descriptor,
720
});
721
}
722
723
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
724
pub struct WireframePipelineKey {
725
pub mesh_key: MeshPipelineKey,
726
pub wide: bool,
727
pub quads: bool,
728
pub line_mode: bool,
729
}
730
731
impl SpecializedMeshPipeline for Wireframe3dPipeline {
732
type Key = WireframePipelineKey;
733
734
fn specialize(
735
&self,
736
key: Self::Key,
737
layout: &MeshVertexBufferLayoutRef,
738
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
739
let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?;
740
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
741
742
if key.wide {
743
descriptor.label = Some("wireframe_3d_wide_pipeline".into());
744
745
descriptor.vertex.shader = self.shader.clone();
746
descriptor.vertex.shader_defs.push("WIREFRAME_WIDE".into());
747
if key.quads {
748
descriptor.vertex.shader_defs.push("WIREFRAME_QUADS".into());
749
}
750
descriptor.vertex.entry_point = Some("vertex".into());
751
descriptor.vertex.buffers = vec![]; // vertex pulling from storage
752
753
let fragment = descriptor.fragment.as_mut().unwrap();
754
fragment.shader = self.shader.clone();
755
fragment.shader_defs.push("WIREFRAME_WIDE".into());
756
fragment.entry_point = Some("fragment".into());
757
758
for state in fragment.targets.iter_mut().flatten() {
759
state.blend = Some(BlendState::ALPHA_BLENDING);
760
}
761
762
descriptor.primitive.polygon_mode = if key.line_mode {
763
PolygonMode::Line
764
} else {
765
PolygonMode::Fill
766
};
767
descriptor.immediate_size = 32; // color(16) + line_width(4) + smoothing(4) + pad(8)
768
769
descriptor
770
.layout
771
.push(self.wide_bind_group_layout_descriptor.clone());
772
} else {
773
descriptor.label = Some("wireframe_3d_pipeline".into());
774
descriptor.immediate_size = 16;
775
let fragment = descriptor.fragment.as_mut().unwrap();
776
fragment.shader = self.shader.clone();
777
descriptor.primitive.polygon_mode = PolygonMode::Line;
778
}
779
780
Ok(descriptor)
781
}
782
}
783
784
pub fn wireframe_3d(
785
world: &World,
786
view: ViewQuery<(
787
&ExtractedCamera,
788
&ExtractedView,
789
&ViewTarget,
790
&ViewDepthTexture,
791
)>,
792
wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
793
mut ctx: RenderContext,
794
) {
795
let view_entity = view.entity();
796
797
let (camera, extracted_view, target, depth) = view.into_inner();
798
799
let Some(wireframe_phase) = wireframe_phases.get(&extracted_view.retained_view_entity) else {
800
return;
801
};
802
803
if wireframe_phase.is_empty() {
804
return;
805
}
806
807
let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
808
label: Some("wireframe_3d"),
809
color_attachments: &[Some(target.get_color_attachment())],
810
depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
811
timestamp_writes: None,
812
occlusion_query_set: None,
813
multiview_mask: None,
814
});
815
816
if let Some(viewport) = camera.viewport.as_ref() {
817
render_pass.set_camera_viewport(viewport);
818
}
819
820
if let Err(err) = wireframe_phase.render(&mut render_pass, world, view_entity) {
821
error!("Error encountered while rendering the wireframe phase {err:?}");
822
}
823
}
824
825
/// Sets the color of the [`Wireframe`] of the entity it is attached to.
826
///
827
/// If this component is present but there's no [`Wireframe`] component,
828
/// it will still affect the color of the wireframe when [`WireframeConfig::global`] is set to true.
829
///
830
/// This overrides the [`WireframeConfig::default_color`].
831
#[derive(Component, Debug, Clone, Default, Reflect)]
832
#[reflect(Component, Default, Debug)]
833
pub struct WireframeColor {
834
pub color: Color,
835
}
836
837
/// Sets the line width (in screen-space pixels) of the wireframe.
838
///
839
/// Overrides [`WireframeConfig::default_line_width`].
840
#[derive(Component, Debug, Clone, Reflect)]
841
#[reflect(Component, Default, Debug)]
842
pub struct WireframeLineWidth {
843
pub width: f32,
844
}
845
846
impl Default for WireframeLineWidth {
847
fn default() -> Self {
848
Self { width: 1.0 }
849
}
850
}
851
852
/// Disables wireframe rendering for any entity it is attached to.
853
/// It will ignore the [`WireframeConfig`] global setting.
854
///
855
/// This requires the [`WireframePlugin`] to be enabled.
856
#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
857
#[reflect(Component, Default, Debug, PartialEq)]
858
pub struct NoWireframe;
859
860
/// Controls whether wireframe edges follow triangle or quad topology.
861
#[derive(Component, Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Reflect)]
862
#[reflect(Component, Default, Debug)]
863
pub enum WireframeTopology {
864
#[default]
865
Triangles,
866
/// Does a best-effort attempt to detect quads from a triangle mesh. No guarantee of accuracy is made,
867
/// that is, there may be both false positives and false negatives in the rendered output.
868
Quads,
869
}
870
871
#[derive(Resource, Debug, Clone, ExtractResource, Reflect)]
872
#[reflect(Resource, Debug, Default)]
873
pub struct WireframeConfig {
874
/// Whether to show wireframes for all meshes.
875
/// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component.
876
pub global: bool,
877
/// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
878
/// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`],
879
/// but no [`WireframeColor`].
880
pub default_color: Color,
881
/// Default line width in screen-space pixels.
882
pub default_line_width: f32,
883
/// Default edge topology.
884
pub default_topology: WireframeTopology,
885
}
886
887
impl Default for WireframeConfig {
888
fn default() -> Self {
889
Self {
890
global: false,
891
default_color: Color::default(),
892
default_line_width: 1.0,
893
default_topology: WireframeTopology::default(),
894
}
895
}
896
}
897
898
#[derive(Asset, Reflect, Clone, Debug)]
899
#[reflect(Clone, Default)]
900
pub struct WireframeMaterial {
901
pub color: Color,
902
pub line_width: f32,
903
pub topology: WireframeTopology,
904
}
905
906
impl Default for WireframeMaterial {
907
fn default() -> Self {
908
Self {
909
color: Color::default(),
910
line_width: 1.0,
911
topology: WireframeTopology::default(),
912
}
913
}
914
}
915
916
pub struct RenderWireframeMaterial {
917
pub color: [f32; 4],
918
pub line_width: f32,
919
pub topology: WireframeTopology,
920
}
921
922
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)]
923
#[reflect(Component, Default, Clone, PartialEq)]
924
pub struct Mesh3dWireframe(pub Handle<WireframeMaterial>);
925
926
impl AsAssetId for Mesh3dWireframe {
927
type Asset = WireframeMaterial;
928
929
fn as_asset_id(&self) -> AssetId<Self::Asset> {
930
self.0.id()
931
}
932
}
933
934
impl RenderAsset for RenderWireframeMaterial {
935
type SourceAsset = WireframeMaterial;
936
type Param = ();
937
938
fn prepare_asset(
939
source_asset: Self::SourceAsset,
940
_asset_id: AssetId<Self::SourceAsset>,
941
_param: &mut SystemParamItem<Self::Param>,
942
_previous_asset: Option<&Self>,
943
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
944
Ok(RenderWireframeMaterial {
945
color: source_asset.color.to_linear().to_f32_array(),
946
line_width: source_asset.line_width,
947
topology: source_asset.topology,
948
})
949
}
950
}
951
952
#[derive(Resource, Deref, DerefMut, Default)]
953
pub struct RenderWireframeInstances(MainEntityHashMap<AssetId<WireframeMaterial>>);
954
955
#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)]
956
pub struct WireframeEntitiesNeedingSpecialization {
957
#[deref]
958
pub entities: Vec<Entity>,
959
}
960
961
#[derive(Resource, Deref, DerefMut, Clone, Debug, Default)]
962
pub struct WireframeEntitySpecializationTicks {
963
pub entities: MainEntityHashMap<Tick>,
964
}
965
966
#[derive(Resource, Default)]
967
pub struct SpecializedWireframePipelineCache {
968
views: HashMap<RetainedViewEntity, SpecializedWireframeViewPipelineCache>,
969
wide: HashMap<(MeshPipelineKey, MeshVertexBufferLayoutRef, bool, bool), CachedRenderPipelineId>,
970
}
971
972
#[derive(Deref, DerefMut, Default)]
973
pub struct SpecializedWireframeViewPipelineCache {
974
// material entity -> (tick, pipeline_id)
975
#[deref]
976
map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
977
}
978
979
#[derive(Resource)]
980
struct GlobalWireframeMaterial {
981
// This handle will be reused when the global config is enabled
982
handle: Handle<WireframeMaterial>,
983
}
984
985
pub fn extract_wireframe_materials(
986
mut material_instances: ResMut<RenderWireframeInstances>,
987
changed_meshes_query: Extract<
988
Query<
989
(Entity, &ViewVisibility, &Mesh3dWireframe),
990
Or<(Changed<ViewVisibility>, Changed<Mesh3dWireframe>)>,
991
>,
992
>,
993
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
994
mut removed_materials_query: Extract<RemovedComponents<Mesh3dWireframe>>,
995
) {
996
for (entity, view_visibility, material) in &changed_meshes_query {
997
if view_visibility.get() {
998
material_instances.insert(entity.into(), material.id());
999
} else {
1000
material_instances.remove(&MainEntity::from(entity));
1001
}
1002
}
1003
1004
for entity in removed_visibilities_query
1005
.read()
1006
.chain(removed_materials_query.read())
1007
{
1008
// Only queue a mesh for removal if we didn't pick it up above.
1009
// It's possible that a necessary component was removed and re-added in
1010
// the same frame.
1011
if !changed_meshes_query.contains(entity) {
1012
material_instances.remove(&MainEntity::from(entity));
1013
}
1014
}
1015
}
1016
1017
fn setup_global_wireframe_material(
1018
mut commands: Commands,
1019
mut materials: ResMut<Assets<WireframeMaterial>>,
1020
config: Res<WireframeConfig>,
1021
) {
1022
commands.insert_resource(GlobalWireframeMaterial {
1023
handle: materials.add(WireframeMaterial {
1024
color: config.default_color,
1025
line_width: config.default_line_width,
1026
topology: config.default_topology,
1027
}),
1028
});
1029
}
1030
1031
fn wireframe_config_changed(
1032
config: Res<WireframeConfig>,
1033
mut materials: ResMut<Assets<WireframeMaterial>>,
1034
global_material: Res<GlobalWireframeMaterial>,
1035
mut per_entity_wireframes: Query<
1036
(
1037
&mut Mesh3dWireframe,
1038
Option<&WireframeColor>,
1039
Option<&WireframeLineWidth>,
1040
Option<&WireframeTopology>,
1041
),
1042
With<Wireframe>,
1043
>,
1044
) {
1045
if let Some(mut mat) = materials.get_mut(&global_material.handle) {
1046
mat.color = config.default_color;
1047
mat.line_width = config.default_line_width;
1048
mat.topology = config.default_topology;
1049
}
1050
1051
for (mut handle, maybe_color, maybe_width, maybe_topology) in &mut per_entity_wireframes {
1052
if handle.0 == global_material.handle {
1053
continue;
1054
}
1055
handle.0 = materials.add(WireframeMaterial {
1056
color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1057
line_width: maybe_width
1058
.map(|w| w.width)
1059
.unwrap_or(config.default_line_width),
1060
topology: maybe_topology.copied().unwrap_or(config.default_topology),
1061
});
1062
}
1063
}
1064
1065
fn wireframe_color_changed(
1066
mut materials: ResMut<Assets<WireframeMaterial>>,
1067
mut colors_changed: Query<
1068
(
1069
&mut Mesh3dWireframe,
1070
&WireframeColor,
1071
Option<&WireframeLineWidth>,
1072
Option<&WireframeTopology>,
1073
),
1074
(With<Wireframe>, Changed<WireframeColor>),
1075
>,
1076
config: Res<WireframeConfig>,
1077
) {
1078
for (mut handle, wireframe_color, maybe_width, maybe_topology) in &mut colors_changed {
1079
handle.0 = materials.add(WireframeMaterial {
1080
color: wireframe_color.color,
1081
line_width: maybe_width
1082
.map(|w| w.width)
1083
.unwrap_or(config.default_line_width),
1084
topology: maybe_topology.copied().unwrap_or(config.default_topology),
1085
});
1086
}
1087
}
1088
1089
fn wireframe_line_width_changed(
1090
mut materials: ResMut<Assets<WireframeMaterial>>,
1091
mut widths_changed: Query<
1092
(
1093
&mut Mesh3dWireframe,
1094
&WireframeLineWidth,
1095
Option<&WireframeColor>,
1096
Option<&WireframeTopology>,
1097
),
1098
(With<Wireframe>, Changed<WireframeLineWidth>),
1099
>,
1100
config: Res<WireframeConfig>,
1101
) {
1102
for (mut handle, wireframe_width, maybe_color, maybe_topology) in &mut widths_changed {
1103
handle.0 = materials.add(WireframeMaterial {
1104
color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1105
line_width: wireframe_width.width,
1106
topology: maybe_topology.copied().unwrap_or(config.default_topology),
1107
});
1108
}
1109
}
1110
1111
fn wireframe_topology_changed(
1112
mut materials: ResMut<Assets<WireframeMaterial>>,
1113
mut topology_changed: Query<
1114
(
1115
&mut Mesh3dWireframe,
1116
&WireframeTopology,
1117
Option<&WireframeColor>,
1118
Option<&WireframeLineWidth>,
1119
),
1120
(With<Wireframe>, Changed<WireframeTopology>),
1121
>,
1122
config: Res<WireframeConfig>,
1123
) {
1124
for (mut handle, topology, maybe_color, maybe_width) in &mut topology_changed {
1125
handle.0 = materials.add(WireframeMaterial {
1126
color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1127
line_width: maybe_width
1128
.map(|w| w.width)
1129
.unwrap_or(config.default_line_width),
1130
topology: *topology,
1131
});
1132
}
1133
}
1134
1135
/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component, and removes it
1136
/// for any mesh with a [`NoWireframe`] component.
1137
fn apply_wireframe_material(
1138
mut commands: Commands,
1139
mut materials: ResMut<Assets<WireframeMaterial>>,
1140
wireframes: Query<
1141
(
1142
Entity,
1143
Option<&WireframeColor>,
1144
Option<&WireframeLineWidth>,
1145
Option<&WireframeTopology>,
1146
),
1147
(With<Wireframe>, Without<Mesh3dWireframe>),
1148
>,
1149
no_wireframes: Query<Entity, (With<NoWireframe>, With<Mesh3dWireframe>)>,
1150
mut removed_wireframes: RemovedComponents<Wireframe>,
1151
global_material: Res<GlobalWireframeMaterial>,
1152
config: Res<WireframeConfig>,
1153
) {
1154
for e in removed_wireframes.read().chain(no_wireframes.iter()) {
1155
if let Ok(mut commands) = commands.get_entity(e) {
1156
commands.remove::<Mesh3dWireframe>();
1157
}
1158
}
1159
1160
let mut material_to_spawn = vec![];
1161
for (e, maybe_color, maybe_width, maybe_topology) in &wireframes {
1162
let material = get_wireframe_material(
1163
maybe_color,
1164
maybe_width,
1165
maybe_topology,
1166
&mut materials,
1167
&global_material,
1168
&config,
1169
);
1170
material_to_spawn.push((e, Mesh3dWireframe(material)));
1171
}
1172
commands.try_insert_batch(material_to_spawn);
1173
}
1174
1175
type WireframeFilter = (With<Mesh3d>, Without<Wireframe>, Without<NoWireframe>);
1176
1177
/// Applies or removes a wireframe material on any mesh without a [`Wireframe`] or [`NoWireframe`] component.
1178
fn apply_global_wireframe_material(
1179
mut commands: Commands,
1180
config: Res<WireframeConfig>,
1181
meshes_without_material: Query<
1182
(
1183
Entity,
1184
Option<&WireframeColor>,
1185
Option<&WireframeLineWidth>,
1186
Option<&WireframeTopology>,
1187
),
1188
(WireframeFilter, Without<Mesh3dWireframe>),
1189
>,
1190
meshes_with_global_material: Query<Entity, (WireframeFilter, With<Mesh3dWireframe>)>,
1191
global_material: Res<GlobalWireframeMaterial>,
1192
mut materials: ResMut<Assets<WireframeMaterial>>,
1193
) {
1194
if config.global {
1195
let mut material_to_spawn = vec![];
1196
for (e, maybe_color, maybe_width, maybe_topology) in &meshes_without_material {
1197
let material = get_wireframe_material(
1198
maybe_color,
1199
maybe_width,
1200
maybe_topology,
1201
&mut materials,
1202
&global_material,
1203
&config,
1204
);
1205
// We only add the material handle but not the Wireframe component
1206
// This makes it easy to detect which mesh is using the global material and which ones are user specified
1207
material_to_spawn.push((e, Mesh3dWireframe(material)));
1208
}
1209
commands.try_insert_batch(material_to_spawn);
1210
} else {
1211
for e in &meshes_with_global_material {
1212
commands.entity(e).remove::<Mesh3dWireframe>();
1213
}
1214
}
1215
}
1216
1217
/// Gets a handle to a wireframe material with a fallback on the default material
1218
fn get_wireframe_material(
1219
maybe_color: Option<&WireframeColor>,
1220
maybe_width: Option<&WireframeLineWidth>,
1221
maybe_topology: Option<&WireframeTopology>,
1222
wireframe_materials: &mut Assets<WireframeMaterial>,
1223
global_material: &GlobalWireframeMaterial,
1224
config: &WireframeConfig,
1225
) -> Handle<WireframeMaterial> {
1226
if maybe_color.is_some() || maybe_width.is_some() || maybe_topology.is_some() {
1227
wireframe_materials.add(WireframeMaterial {
1228
color: maybe_color.map(|c| c.color).unwrap_or(config.default_color),
1229
line_width: maybe_width
1230
.map(|w| w.width)
1231
.unwrap_or(config.default_line_width),
1232
topology: maybe_topology.copied().unwrap_or(config.default_topology),
1233
})
1234
} else {
1235
// If there's no color specified we can use the global material since it's already set to use the default_color
1236
global_material.handle.clone()
1237
}
1238
}
1239
1240
fn extract_wireframe_3d_camera(
1241
mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
1242
cameras: Extract<Query<(Entity, &Camera, Has<NoIndirectDrawing>), With<Camera3d>>>,
1243
mut live_entities: Local<HashSet<RetainedViewEntity>>,
1244
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1245
) {
1246
live_entities.clear();
1247
for (main_entity, camera, no_indirect_drawing) in &cameras {
1248
if !camera.is_active {
1249
continue;
1250
}
1251
let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
1252
GpuPreprocessingMode::Culling
1253
} else {
1254
GpuPreprocessingMode::PreprocessingOnly
1255
});
1256
1257
let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0);
1258
wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1259
live_entities.insert(retained_view_entity);
1260
}
1261
1262
// Clear out all dead views.
1263
wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
1264
}
1265
1266
pub fn extract_wireframe_entities_needing_specialization(
1267
entities_needing_specialization: Extract<Res<WireframeEntitiesNeedingSpecialization>>,
1268
mut entity_specialization_ticks: ResMut<WireframeEntitySpecializationTicks>,
1269
views: Query<&ExtractedView>,
1270
mut specialized_wireframe_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
1271
mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
1272
ticks: SystemChangeTick,
1273
) {
1274
for entity in entities_needing_specialization.iter() {
1275
// Update the entity's specialization tick with this run's tick
1276
entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
1277
}
1278
1279
for entity in removed_meshes_query.read() {
1280
for view in &views {
1281
if let Some(view_cache) = specialized_wireframe_pipeline_cache
1282
.views
1283
.get_mut(&view.retained_view_entity)
1284
{
1285
view_cache.remove(&MainEntity::from(entity));
1286
}
1287
}
1288
}
1289
}
1290
1291
pub fn check_wireframe_entities_needing_specialization(
1292
needs_specialization: Query<
1293
Entity,
1294
Or<(
1295
Changed<Mesh3d>,
1296
AssetChanged<Mesh3d>,
1297
Changed<Mesh3dWireframe>,
1298
AssetChanged<Mesh3dWireframe>,
1299
Changed<WireframeLineWidth>,
1300
Changed<WireframeTopology>,
1301
)>,
1302
>,
1303
mut entities_needing_specialization: ResMut<WireframeEntitiesNeedingSpecialization>,
1304
) {
1305
entities_needing_specialization.clear();
1306
for entity in &needs_specialization {
1307
entities_needing_specialization.push(entity);
1308
}
1309
}
1310
1311
pub fn specialize_wireframes(
1312
render_meshes: Res<RenderAssets<RenderMesh>>,
1313
render_mesh_instances: Res<RenderMeshInstances>,
1314
render_wireframe_instances: Res<RenderWireframeInstances>,
1315
render_wireframe_assets: Res<RenderAssets<RenderWireframeMaterial>>,
1316
render_visibility_ranges: Res<RenderVisibilityRanges>,
1317
wireframe_phases: Res<ViewBinnedRenderPhases<Wireframe3d>>,
1318
views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1319
view_key_cache: Res<ViewKeyCache>,
1320
entity_specialization_ticks: Res<WireframeEntitySpecializationTicks>,
1321
view_specialization_ticks: Res<ViewSpecializationTicks>,
1322
mut specialized_material_pipeline_cache: ResMut<SpecializedWireframePipelineCache>,
1323
mut pipelines: ResMut<SpecializedMeshPipelines<Wireframe3dPipeline>>,
1324
pipeline: Res<Wireframe3dPipeline>,
1325
pipeline_cache: Res<PipelineCache>,
1326
render_lightmaps: Res<RenderLightmaps>,
1327
ticks: SystemChangeTick,
1328
) {
1329
let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
1330
1331
let SpecializedWireframePipelineCache {
1332
views: ref mut views_pipeline_cache,
1333
wide: ref mut wide_pipeline_cache,
1334
} = *specialized_material_pipeline_cache;
1335
1336
for (view, visible_entities) in &views {
1337
all_views.insert(view.retained_view_entity);
1338
1339
if !wireframe_phases.contains_key(&view.retained_view_entity) {
1340
continue;
1341
}
1342
1343
let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
1344
continue;
1345
};
1346
1347
let view_tick = view_specialization_ticks
1348
.get(&view.retained_view_entity)
1349
.unwrap();
1350
let view_specialized_material_pipeline_cache = views_pipeline_cache
1351
.entry(view.retained_view_entity)
1352
.or_default();
1353
1354
for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
1355
if !render_wireframe_instances.contains_key(visible_entity) {
1356
continue;
1357
};
1358
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1359
else {
1360
continue;
1361
};
1362
let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
1363
let last_specialized_tick = view_specialized_material_pipeline_cache
1364
.get(visible_entity)
1365
.map(|(tick, _)| *tick);
1366
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
1367
view_tick.is_newer_than(tick, ticks.this_run())
1368
|| entity_tick.is_newer_than(tick, ticks.this_run())
1369
});
1370
if !needs_specialization {
1371
continue;
1372
}
1373
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
1374
continue;
1375
};
1376
1377
let mut mesh_key = *view_key;
1378
mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology());
1379
1380
if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
1381
mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
1382
}
1383
1384
if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
1385
// If the previous frame have skins or morph targets, note that.
1386
if mesh_instance
1387
.flags
1388
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
1389
{
1390
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
1391
}
1392
if mesh_instance
1393
.flags
1394
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
1395
{
1396
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
1397
}
1398
}
1399
1400
// Even though we don't use the lightmap in the wireframe, the
1401
// `SetMeshBindGroup` render command will bind the data for it. So
1402
// we need to include the appropriate flag in the mesh pipeline key
1403
// to ensure that the necessary bind group layout entries are
1404
// present.
1405
if render_lightmaps
1406
.render_lightmaps
1407
.contains_key(visible_entity)
1408
{
1409
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1410
}
1411
1412
let mat = render_wireframe_instances
1413
.get(visible_entity)
1414
.and_then(|asset_id| render_wireframe_assets.get(*asset_id));
1415
let quads = mat
1416
.map(|m| m.topology == WireframeTopology::Quads)
1417
.unwrap_or(false);
1418
let thick = mat.map(|m| m.line_width > 1.0).unwrap_or(false);
1419
let wide = thick || quads;
1420
let line_mode = wide && !thick;
1421
1422
let pipeline_id = if wide {
1423
let cache_key = (mesh_key, mesh.layout.clone(), quads, line_mode);
1424
*wide_pipeline_cache.entry(cache_key).or_insert_with(|| {
1425
let wireframe_key = WireframePipelineKey {
1426
mesh_key,
1427
wide: true,
1428
quads,
1429
line_mode,
1430
};
1431
match pipeline.specialize(wireframe_key, &mesh.layout) {
1432
Ok(descriptor) => pipeline_cache.queue_render_pipeline(descriptor),
1433
Err(err) => {
1434
error!("{}", err);
1435
CachedRenderPipelineId::INVALID
1436
}
1437
}
1438
})
1439
} else {
1440
let wireframe_key = WireframePipelineKey {
1441
mesh_key,
1442
wide: false,
1443
quads: false,
1444
line_mode: false,
1445
};
1446
match pipelines.specialize(&pipeline_cache, &pipeline, wireframe_key, &mesh.layout)
1447
{
1448
Ok(id) => id,
1449
Err(err) => {
1450
error!("{}", err);
1451
continue;
1452
}
1453
}
1454
};
1455
1456
if pipeline_id == CachedRenderPipelineId::INVALID {
1457
continue;
1458
}
1459
1460
view_specialized_material_pipeline_cache
1461
.insert(*visible_entity, (ticks.this_run(), pipeline_id));
1462
}
1463
}
1464
1465
views_pipeline_cache.retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
1466
}
1467
1468
fn queue_wireframes(
1469
custom_draw_functions: Res<DrawFunctions<Wireframe3d>>,
1470
render_mesh_instances: Res<RenderMeshInstances>,
1471
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1472
mesh_allocator: Res<MeshAllocator>,
1473
specialized_wireframe_pipeline_cache: Res<SpecializedWireframePipelineCache>,
1474
render_wireframe_instances: Res<RenderWireframeInstances>,
1475
render_wireframe_assets: Res<RenderAssets<RenderWireframeMaterial>>,
1476
mut wireframe_3d_phases: ResMut<ViewBinnedRenderPhases<Wireframe3d>>,
1477
mut views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1478
) {
1479
for (view, visible_entities) in &mut views {
1480
let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else {
1481
continue;
1482
};
1483
let draw_functions = custom_draw_functions.read();
1484
let draw_thin = draw_functions.id::<DrawWireframe3dThin>();
1485
let draw_wide = draw_functions.id::<DrawWireframe3dWide>();
1486
1487
let Some(view_specialized_material_pipeline_cache) = specialized_wireframe_pipeline_cache
1488
.views
1489
.get(&view.retained_view_entity)
1490
else {
1491
continue;
1492
};
1493
1494
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
1495
let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else {
1496
continue;
1497
};
1498
let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache
1499
.get(visible_entity)
1500
.map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))
1501
else {
1502
continue;
1503
};
1504
1505
// Skip the entity if it's cached in a bin and up to date.
1506
if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) {
1507
continue;
1508
}
1509
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1510
else {
1511
continue;
1512
};
1513
1514
let is_wide = render_wireframe_assets
1515
.get(*wireframe_instance)
1516
.map(|mat| mat.line_width > 1.0 || mat.topology == WireframeTopology::Quads)
1517
.unwrap_or(false);
1518
let draw_function = if is_wide { draw_wide } else { draw_thin };
1519
1520
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1521
let bin_key = Wireframe3dBinKey {
1522
asset_id: mesh_instance.mesh_asset_id.untyped(),
1523
};
1524
let batch_set_key = Wireframe3dBatchSetKey {
1525
pipeline: pipeline_id,
1526
asset_id: wireframe_instance.untyped(),
1527
draw_function,
1528
vertex_slab: vertex_slab.unwrap_or_default(),
1529
// wide wireframes use non-indexed draws (vertex pulling from storage),
1530
// so set index_slab to None to make the preprocessor emit
1531
// IndirectParametersNonIndexed instead of IndirectParametersIndexed.
1532
index_slab: if is_wide { None } else { index_slab },
1533
mesh_asset_id: if is_wide {
1534
Some(mesh_instance.mesh_asset_id.untyped())
1535
} else {
1536
None
1537
},
1538
};
1539
wireframe_phase.add(
1540
batch_set_key,
1541
bin_key,
1542
(*render_entity, *visible_entity),
1543
mesh_instance.current_uniform_index,
1544
BinnedRenderPhaseType::mesh(
1545
mesh_instance.should_batch(),
1546
&gpu_preprocessing_support,
1547
),
1548
current_change_tick,
1549
);
1550
}
1551
}
1552
}
1553
1554