Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_dev_tools/src/render_debug.rs
9328 views
1
//! Renderer debugging overlay
2
3
use bevy_app::{App, Plugin};
4
use bevy_asset::{embedded_asset, Handle};
5
use bevy_core_pipeline::{
6
mip_generation::experimental::depth::ViewDepthPyramid,
7
oit::OrderIndependentTransparencySettingsOffset,
8
schedule::{Core3d, Core3dSystems},
9
tonemapping::tonemapping,
10
FullscreenShader,
11
};
12
use bevy_ecs::{
13
component::Component,
14
entity::Entity,
15
message::{Message, MessageReader, MessageWriter},
16
prelude::{Has, ReflectComponent, World},
17
reflect::ReflectResource,
18
resource::Resource,
19
schedule::IntoScheduleConfigs,
20
system::{Commands, Query, Res, ResMut},
21
world::FromWorld,
22
};
23
use bevy_input::{prelude::KeyCode, ButtonInput};
24
use bevy_log::info;
25
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
26
use bevy_render::{
27
extract_component::{ExtractComponent, ExtractComponentPlugin},
28
extract_resource::{ExtractResource, ExtractResourcePlugin},
29
render_asset::RenderAssets,
30
render_resource::{
31
binding_types, BindGroupEntries, BindGroupLayout, BindGroupLayoutDescriptor,
32
BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites,
33
DynamicUniformBuffer, FragmentState, Operations, PipelineCache, RenderPassColorAttachment,
34
RenderPassDescriptor, RenderPipelineDescriptor, Sampler, SamplerDescriptor, ShaderStages,
35
ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat,
36
TextureSampleType, VertexState,
37
},
38
renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
39
texture::{FallbackImage, GpuImage},
40
view::{Msaa, ViewTarget, ViewUniformOffset},
41
Render, RenderApp, RenderSystems,
42
};
43
use bevy_shader::Shader;
44
45
use bevy_pbr::{
46
Bluenoise, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup,
47
ViewContactShadowsUniformOffset, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset,
48
ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset,
49
};
50
51
/// Adds a rendering debug overlay to visualize various renderer buffers.
52
#[derive(Default)]
53
pub struct RenderDebugOverlayPlugin;
54
55
impl Plugin for RenderDebugOverlayPlugin {
56
fn build(&self, app: &mut App) {
57
embedded_asset!(app, "debug_overlay.wgsl");
58
59
app.register_type::<RenderDebugOverlay>()
60
.init_resource::<RenderDebugOverlay>()
61
.add_message::<RenderDebugOverlayEvent>()
62
.add_plugins((
63
ExtractResourcePlugin::<RenderDebugOverlay>::default(),
64
ExtractComponentPlugin::<RenderDebugOverlay>::default(),
65
))
66
.add_systems(bevy_app::Update, (handle_input, update_overlay).chain());
67
}
68
69
fn finish(&self, app: &mut App) {
70
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
71
return;
72
};
73
74
render_app
75
.init_resource::<RenderDebugOverlayPipeline>()
76
.init_resource::<SpecializedRenderPipelines<RenderDebugOverlayPipeline>>()
77
.init_resource::<RenderDebugOverlayUniforms>()
78
.add_systems(
79
Render,
80
(
81
prepare_debug_overlay_pipelines.in_set(RenderSystems::Prepare),
82
prepare_debug_overlay_resources.in_set(RenderSystems::PrepareResources),
83
),
84
)
85
.add_systems(
86
Core3d,
87
render_debug_overlay
88
.after(tonemapping)
89
.in_set(Core3dSystems::PostProcess),
90
);
91
}
92
}
93
94
/// Automatically attach keybinds to make render debug overlays available to users without code
95
/// changes when the feature is enabled.
96
pub fn handle_input(
97
keyboard: Res<ButtonInput<KeyCode>>,
98
mut events: MessageWriter<RenderDebugOverlayEvent>,
99
) {
100
if keyboard.just_pressed(KeyCode::F1) {
101
events.write(RenderDebugOverlayEvent::CycleMode);
102
}
103
if keyboard.just_pressed(KeyCode::F2) {
104
events.write(RenderDebugOverlayEvent::CycleOpacity);
105
}
106
}
107
108
/// Listen to messages to update the debug overlay configuration.
109
pub fn update_overlay(
110
mut commands: Commands,
111
mut events: MessageReader<RenderDebugOverlayEvent>,
112
mut config_res: ResMut<RenderDebugOverlay>,
113
cameras: Query<
114
(
115
Entity,
116
Option<&RenderDebugOverlay>,
117
Has<bevy_core_pipeline::prepass::DepthPrepass>,
118
Has<bevy_core_pipeline::prepass::NormalPrepass>,
119
Has<bevy_core_pipeline::prepass::MotionVectorPrepass>,
120
Has<bevy_core_pipeline::prepass::DeferredPrepass>,
121
Has<bevy_render::occlusion_culling::OcclusionCulling>,
122
Has<bevy_pbr::ScreenSpaceReflections>,
123
),
124
bevy_ecs::query::With<bevy_camera::Camera>,
125
>,
126
) {
127
let mut changed = false;
128
129
for event in events.read() {
130
match event {
131
RenderDebugOverlayEvent::CycleMode => {
132
let modes = [
133
RenderDebugMode::Depth,
134
RenderDebugMode::Normal,
135
RenderDebugMode::MotionVectors,
136
RenderDebugMode::Deferred,
137
RenderDebugMode::DeferredBaseColor,
138
RenderDebugMode::DeferredEmissive,
139
RenderDebugMode::DeferredMetallicRoughness,
140
RenderDebugMode::DepthPyramid { mip_level: 0 },
141
];
142
143
let is_supported = |mode: &RenderDebugMode| {
144
cameras.iter().any(
145
|(_, _, depth, normal, motion, deferred, occlusion, _ssr)| {
146
match mode {
147
RenderDebugMode::Depth => depth || deferred,
148
RenderDebugMode::Normal => normal || deferred,
149
RenderDebugMode::MotionVectors => motion,
150
RenderDebugMode::Deferred
151
| RenderDebugMode::DeferredBaseColor
152
| RenderDebugMode::DeferredEmissive
153
| RenderDebugMode::DeferredMetallicRoughness => deferred,
154
// We don't have a good way to check for DepthPyramid in the main
155
// world, but it usually depends on DepthPrepass.
156
// However, we can at least check if OcclusionCulling is present.
157
RenderDebugMode::DepthPyramid { .. } => depth && occlusion,
158
}
159
},
160
)
161
};
162
163
if !config_res.enabled {
164
for mode in modes {
165
if is_supported(&mode) {
166
config_res.enabled = true;
167
config_res.mode = mode;
168
break;
169
}
170
}
171
} else {
172
let current_index = modes
173
.iter()
174
.position(|m| {
175
core::mem::discriminant(m) == core::mem::discriminant(&config_res.mode)
176
})
177
.unwrap_or(0);
178
179
let mut next_mode = None;
180
181
if let RenderDebugMode::DepthPyramid { mip_level } = config_res.mode
182
&& mip_level < 7
183
{
184
config_res.mode = RenderDebugMode::DepthPyramid {
185
mip_level: mip_level + 1,
186
};
187
next_mode = Some(config_res.mode);
188
}
189
190
if next_mode.is_none() {
191
for i in 1..modes.len() {
192
let idx = (current_index + i) % modes.len();
193
if is_supported(&modes[idx]) {
194
next_mode = Some(modes[idx]);
195
break;
196
}
197
}
198
199
if let Some(mode) = next_mode {
200
let next_index = modes
201
.iter()
202
.position(|m| {
203
core::mem::discriminant(m) == core::mem::discriminant(&mode)
204
})
205
.unwrap();
206
207
if next_index <= current_index {
208
config_res.enabled = false;
209
} else {
210
config_res.mode = mode;
211
}
212
} else {
213
config_res.enabled = false;
214
}
215
}
216
}
217
changed = true;
218
219
if config_res.enabled {
220
info!("Debug Overlay: {:?}", config_res.mode);
221
} else {
222
info!("Debug Overlay Disabled");
223
}
224
}
225
RenderDebugOverlayEvent::CycleOpacity => {
226
config_res.opacity = if config_res.opacity < 0.5 {
227
0.5
228
} else if config_res.opacity < 0.8 {
229
0.8
230
} else if config_res.opacity < 1.0 {
231
1.0
232
} else {
233
0.5
234
};
235
changed = true;
236
info!("Debug Overlay Opacity: {}", config_res.opacity);
237
}
238
}
239
}
240
241
for (entity, existing_config, ..) in &cameras {
242
if existing_config.is_none() || (changed && Some(config_res.as_ref()) != existing_config) {
243
commands.entity(entity).insert(config_res.clone());
244
}
245
}
246
}
247
248
/// Configure the render debug overlay.
249
#[derive(Message, Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect)]
250
#[reflect(Debug, PartialEq, Hash)]
251
pub enum RenderDebugOverlayEvent {
252
/// Cycle to the next debug mode.
253
CycleMode,
254
/// Cycle to the next opacity level.
255
CycleOpacity,
256
}
257
258
/// Configure the render debug overlay.
259
#[derive(Resource, Component, Clone, ExtractResource, ExtractComponent, Reflect, PartialEq)]
260
#[reflect(Resource, Component, Default)]
261
pub struct RenderDebugOverlay {
262
/// Enables or disables drawing the overlay.
263
pub enabled: bool,
264
/// The kind of data to write to the overlay.
265
pub mode: RenderDebugMode,
266
/// The opacity of the overlay, to allow seeing the rendered image underneath.
267
pub opacity: f32,
268
}
269
270
impl Default for RenderDebugOverlay {
271
fn default() -> Self {
272
Self {
273
enabled: false,
274
mode: RenderDebugMode::Depth,
275
opacity: 1.0,
276
}
277
}
278
}
279
280
/// The kind of renderer data to visualize.
281
#[expect(missing_docs, reason = "Enum variants are self-explanatory")]
282
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect)]
283
pub enum RenderDebugMode {
284
#[default]
285
Depth,
286
Normal,
287
MotionVectors,
288
Deferred,
289
DeferredBaseColor,
290
DeferredEmissive,
291
DeferredMetallicRoughness,
292
DepthPyramid {
293
mip_level: u32,
294
},
295
}
296
297
#[derive(ShaderType)]
298
struct RenderDebugOverlayUniform {
299
pub opacity: f32,
300
pub mip_level: u32,
301
}
302
303
#[derive(Resource, Default)]
304
struct RenderDebugOverlayUniforms {
305
pub uniforms: DynamicUniformBuffer<RenderDebugOverlayUniform>,
306
}
307
308
#[derive(Component)]
309
struct RenderDebugOverlayUniformOffset {
310
pub offset: u32,
311
}
312
313
#[derive(Resource)]
314
struct RenderDebugOverlayPipeline {
315
shader: Handle<Shader>,
316
mesh_view_layouts: MeshPipelineViewLayouts,
317
bind_group_layout: BindGroupLayout,
318
bind_group_layout_descriptor: BindGroupLayoutDescriptor,
319
sampler: Sampler,
320
fullscreen_vertex_shader: VertexState,
321
}
322
323
impl FromWorld for RenderDebugOverlayPipeline {
324
fn from_world(world: &mut World) -> Self {
325
let render_device = world.resource::<RenderDevice>();
326
let asset_server = world.resource::<bevy_asset::AssetServer>();
327
let mesh_view_layouts = world.resource::<MeshPipelineViewLayouts>().clone();
328
let fullscreen_vertex_shader = world.resource::<FullscreenShader>().to_vertex_state();
329
330
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
331
332
let bind_group_layout_descriptor = BindGroupLayoutDescriptor::new(
333
"debug_overlay_bind_group_layout",
334
&BindGroupLayoutEntries::sequential(
335
ShaderStages::FRAGMENT,
336
(
337
binding_types::uniform_buffer::<RenderDebugOverlayUniform>(true),
338
binding_types::texture_2d(TextureSampleType::Float { filterable: true }),
339
binding_types::sampler(
340
bevy_render::render_resource::SamplerBindingType::Filtering,
341
),
342
binding_types::texture_2d(TextureSampleType::Float { filterable: true }),
343
binding_types::sampler(
344
bevy_render::render_resource::SamplerBindingType::Filtering,
345
),
346
),
347
),
348
);
349
350
let bind_group_layout = render_device.create_bind_group_layout(
351
bind_group_layout_descriptor.label.as_ref(),
352
&bind_group_layout_descriptor.entries,
353
);
354
355
Self {
356
shader: asset_server.load("embedded://bevy_dev_tools/debug_overlay.wgsl"),
357
mesh_view_layouts,
358
bind_group_layout,
359
bind_group_layout_descriptor,
360
sampler,
361
fullscreen_vertex_shader,
362
}
363
}
364
}
365
366
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
367
struct RenderDebugOverlayPipelineKey {
368
mode: RenderDebugMode,
369
view_layout_key: MeshPipelineViewLayoutKey,
370
texture_format: TextureFormat,
371
}
372
373
impl SpecializedRenderPipeline for RenderDebugOverlayPipeline {
374
type Key = RenderDebugOverlayPipelineKey;
375
376
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
377
let mut shader_defs = Vec::new();
378
match key.mode {
379
RenderDebugMode::Depth => {
380
shader_defs.push("DEBUG_DEPTH".into());
381
if key
382
.view_layout_key
383
.contains(MeshPipelineViewLayoutKey::DEPTH_PREPASS)
384
{
385
shader_defs.push("DEPTH_PREPASS".into());
386
}
387
if key
388
.view_layout_key
389
.contains(MeshPipelineViewLayoutKey::DEFERRED_PREPASS)
390
{
391
shader_defs.push("DEFERRED_PREPASS".into());
392
}
393
}
394
RenderDebugMode::Normal => {
395
shader_defs.push("DEBUG_NORMAL".into());
396
if key
397
.view_layout_key
398
.contains(MeshPipelineViewLayoutKey::NORMAL_PREPASS)
399
{
400
shader_defs.push("NORMAL_PREPASS".into());
401
}
402
if key
403
.view_layout_key
404
.contains(MeshPipelineViewLayoutKey::DEFERRED_PREPASS)
405
{
406
shader_defs.push("DEFERRED_PREPASS".into());
407
}
408
}
409
RenderDebugMode::MotionVectors => {
410
shader_defs.push("DEBUG_MOTION_VECTORS".into());
411
if key
412
.view_layout_key
413
.contains(MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS)
414
{
415
shader_defs.push("MOTION_VECTOR_PREPASS".into());
416
}
417
}
418
RenderDebugMode::Deferred => {
419
shader_defs.push("DEBUG_DEFERRED".into());
420
if key
421
.view_layout_key
422
.contains(MeshPipelineViewLayoutKey::DEFERRED_PREPASS)
423
{
424
shader_defs.push("DEFERRED_PREPASS".into());
425
}
426
}
427
RenderDebugMode::DeferredBaseColor => {
428
shader_defs.push("DEBUG_DEFERRED_BASE_COLOR".into());
429
if key
430
.view_layout_key
431
.contains(MeshPipelineViewLayoutKey::DEFERRED_PREPASS)
432
{
433
shader_defs.push("DEFERRED_PREPASS".into());
434
}
435
}
436
RenderDebugMode::DeferredEmissive => {
437
shader_defs.push("DEBUG_DEFERRED_EMISSIVE".into());
438
if key
439
.view_layout_key
440
.contains(MeshPipelineViewLayoutKey::DEFERRED_PREPASS)
441
{
442
shader_defs.push("DEFERRED_PREPASS".into());
443
}
444
}
445
RenderDebugMode::DeferredMetallicRoughness => {
446
shader_defs.push("DEBUG_DEFERRED_METALLIC_ROUGHNESS".into());
447
if key
448
.view_layout_key
449
.contains(MeshPipelineViewLayoutKey::DEFERRED_PREPASS)
450
{
451
shader_defs.push("DEFERRED_PREPASS".into());
452
}
453
}
454
RenderDebugMode::DepthPyramid { .. } => shader_defs.push("DEBUG_DEPTH_PYRAMID".into()),
455
}
456
457
if key
458
.view_layout_key
459
.contains(MeshPipelineViewLayoutKey::MULTISAMPLED)
460
{
461
shader_defs.push("MULTISAMPLED".into());
462
}
463
464
let mesh_view_layout_descriptor = self
465
.mesh_view_layouts
466
.get_view_layout(key.view_layout_key)
467
.main_layout
468
.clone();
469
470
RenderPipelineDescriptor {
471
label: Some("debug_overlay_pipeline".into()),
472
layout: vec![
473
mesh_view_layout_descriptor,
474
self.bind_group_layout_descriptor.clone(),
475
],
476
vertex: self.fullscreen_vertex_shader.clone(),
477
fragment: Some(FragmentState {
478
shader: self.shader.clone(),
479
shader_defs,
480
entry_point: Some("fragment".into()),
481
targets: vec![Some(ColorTargetState {
482
format: key.texture_format,
483
blend: None,
484
write_mask: ColorWrites::ALL,
485
})],
486
}),
487
primitive: bevy_render::render_resource::PrimitiveState::default(),
488
depth_stencil: None,
489
multisample: bevy_render::render_resource::MultisampleState::default(),
490
immediate_size: 0,
491
zero_initialize_workgroup_memory: false,
492
}
493
}
494
}
495
496
fn prepare_debug_overlay_pipelines(
497
mut commands: Commands,
498
pipeline_cache: Res<PipelineCache>,
499
mut pipelines: ResMut<SpecializedRenderPipelines<RenderDebugOverlayPipeline>>,
500
pipeline: Res<RenderDebugOverlayPipeline>,
501
images: Res<RenderAssets<GpuImage>>,
502
blue_noise: Res<Bluenoise>,
503
views: Query<(
504
Entity,
505
&ViewTarget,
506
&RenderDebugOverlay,
507
&Msaa,
508
Option<&bevy_core_pipeline::prepass::ViewPrepassTextures>,
509
Has<bevy_core_pipeline::oit::OrderIndependentTransparencySettings>,
510
Has<bevy_pbr::ExtractedAtmosphere>,
511
)>,
512
) {
513
for (entity, target, config, msaa, prepass_textures, has_oit, has_atmosphere) in &views {
514
if !config.enabled {
515
continue;
516
}
517
518
let mut view_layout_key = MeshPipelineViewLayoutKey::from(*msaa)
519
| MeshPipelineViewLayoutKey::from(prepass_textures);
520
521
if has_oit {
522
view_layout_key |= MeshPipelineViewLayoutKey::OIT_ENABLED;
523
}
524
if has_atmosphere {
525
view_layout_key |= MeshPipelineViewLayoutKey::ATMOSPHERE;
526
}
527
528
if let Some(gpu_image) = images.get(&blue_noise.texture)
529
&& gpu_image.texture.depth_or_array_layers() > 1
530
{
531
view_layout_key |= MeshPipelineViewLayoutKey::STBN;
532
}
533
534
let pipeline_id = pipelines.specialize(
535
&pipeline_cache,
536
&pipeline,
537
RenderDebugOverlayPipelineKey {
538
mode: config.mode,
539
view_layout_key,
540
texture_format: target.main_texture_format(),
541
},
542
);
543
544
commands
545
.entity(entity)
546
.insert(RenderDebugOverlayPipelineId(pipeline_id));
547
}
548
}
549
550
#[derive(Component)]
551
struct RenderDebugOverlayPipelineId(CachedRenderPipelineId);
552
553
fn prepare_debug_overlay_resources(
554
mut commands: Commands,
555
render_device: Res<RenderDevice>,
556
render_queue: Res<RenderQueue>,
557
mut uniforms: ResMut<RenderDebugOverlayUniforms>,
558
views: Query<(Entity, &RenderDebugOverlay)>,
559
) {
560
let len = views.iter().len();
561
if len == 0 {
562
return;
563
}
564
565
let Some(mut writer) = uniforms
566
.uniforms
567
.get_writer(len, &render_device, &render_queue)
568
else {
569
return;
570
};
571
572
for (entity, config) in &views {
573
let offset = writer.write(&RenderDebugOverlayUniform {
574
opacity: config.opacity,
575
mip_level: if let RenderDebugMode::DepthPyramid { mip_level } = config.mode {
576
mip_level
577
} else {
578
0
579
},
580
});
581
582
commands
583
.entity(entity)
584
.insert(RenderDebugOverlayUniformOffset { offset });
585
}
586
}
587
588
fn render_debug_overlay(
589
view: ViewQuery<(
590
&ViewTarget,
591
&RenderDebugOverlay,
592
&RenderDebugOverlayPipelineId,
593
&RenderDebugOverlayUniformOffset,
594
&MeshViewBindGroup,
595
&ViewUniformOffset,
596
&ViewLightsUniformOffset,
597
&ViewFogUniformOffset,
598
&ViewLightProbesUniformOffset,
599
&ViewScreenSpaceReflectionsUniformOffset,
600
&ViewContactShadowsUniformOffset,
601
&ViewEnvironmentMapUniformOffset,
602
Has<bevy_core_pipeline::oit::OrderIndependentTransparencySettings>,
603
Option<&OrderIndependentTransparencySettingsOffset>,
604
Option<&ViewDepthPyramid>,
605
)>,
606
pipeline_cache: Res<PipelineCache>,
607
pipeline_res: Res<RenderDebugOverlayPipeline>,
608
uniforms: Res<RenderDebugOverlayUniforms>,
609
fallback_image: Res<FallbackImage>,
610
mut ctx: RenderContext,
611
) {
612
let (
613
target,
614
config,
615
pipeline_id,
616
uniform_offset,
617
mesh_view_bind_group,
618
view_uniform_offset,
619
view_lights_offset,
620
view_fog_offset,
621
view_light_probes_offset,
622
view_ssr_offset,
623
view_contact_shadows_offset,
624
view_environment_map_offset,
625
has_oit,
626
view_oit_offset,
627
depth_pyramid,
628
) = view.into_inner();
629
630
if !config.enabled {
631
return;
632
}
633
634
let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else {
635
return;
636
};
637
638
let Some(uniform_binding) = uniforms.uniforms.binding() else {
639
return;
640
};
641
642
let post_process = target.post_process_write();
643
644
let depth_pyramid_view = if let Some(dp) = depth_pyramid {
645
&dp.all_mips
646
} else {
647
&fallback_image.d2.texture_view
648
};
649
650
let debug_bind_group = ctx.render_device().create_bind_group(
651
"debug_buffer_bind_group",
652
&pipeline_res.bind_group_layout,
653
&BindGroupEntries::sequential((
654
uniform_binding,
655
post_process.source,
656
&pipeline_res.sampler,
657
depth_pyramid_view,
658
&pipeline_res.sampler,
659
)),
660
);
661
662
let pass_descriptor = RenderPassDescriptor {
663
label: Some("debug_buffer_pass"),
664
color_attachments: &[Some(RenderPassColorAttachment {
665
view: post_process.destination,
666
depth_slice: None,
667
resolve_target: None,
668
ops: Operations {
669
load: bevy_render::render_resource::LoadOp::Clear(Default::default()),
670
store: bevy_render::render_resource::StoreOp::Store,
671
},
672
})],
673
depth_stencil_attachment: None,
674
timestamp_writes: None,
675
occlusion_query_set: None,
676
multiview_mask: None,
677
};
678
679
let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor);
680
681
render_pass.set_pipeline(pipeline);
682
683
let mut dynamic_offsets = vec![
684
view_uniform_offset.offset,
685
view_lights_offset.offset,
686
view_fog_offset.offset,
687
**view_light_probes_offset,
688
**view_ssr_offset,
689
**view_contact_shadows_offset,
690
**view_environment_map_offset,
691
];
692
if has_oit && let Some(view_oit_offset) = view_oit_offset {
693
dynamic_offsets.push(view_oit_offset.offset);
694
}
695
696
render_pass.set_bind_group(0, &mesh_view_bind_group.main, &dynamic_offsets);
697
render_pass.set_bind_group(1, &debug_bind_group, &[uniform_offset.offset]);
698
699
render_pass.draw(0..3, 0..1);
700
}
701
702