Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/occlusion_culling.rs
6592 views
1
//! Demonstrates occlusion culling.
2
//!
3
//! This demo rotates many small cubes around a rotating large cube at the
4
//! origin. At all times, the large cube will be occluding several of the small
5
//! cubes. The demo displays the number of cubes that were actually rendered, so
6
//! the effects of occlusion culling can be seen.
7
8
use std::{
9
any::TypeId,
10
f32::consts::PI,
11
fmt::Write as _,
12
result::Result,
13
sync::{Arc, Mutex},
14
};
15
16
use bevy::{
17
color::palettes::css::{SILVER, WHITE},
18
core_pipeline::{
19
core_3d::{
20
graph::{Core3d, Node3d},
21
Opaque3d,
22
},
23
prepass::DepthPrepass,
24
},
25
pbr::PbrPlugin,
26
prelude::*,
27
render::{
28
batching::gpu_preprocessing::{
29
GpuPreprocessingSupport, IndirectParametersBuffers, IndirectParametersIndexed,
30
},
31
experimental::occlusion_culling::OcclusionCulling,
32
render_graph::{self, NodeRunError, RenderGraphContext, RenderGraphExt, RenderLabel},
33
render_resource::{Buffer, BufferDescriptor, BufferUsages, MapMode},
34
renderer::{RenderContext, RenderDevice},
35
settings::WgpuFeatures,
36
Render, RenderApp, RenderDebugFlags, RenderPlugin, RenderStartup, RenderSystems,
37
},
38
};
39
use bytemuck::Pod;
40
41
/// The radius of the spinning sphere of cubes.
42
const OUTER_RADIUS: f32 = 3.0;
43
44
/// The density of cubes in the other sphere.
45
const OUTER_SUBDIVISION_COUNT: u32 = 5;
46
47
/// The speed at which the outer sphere and large cube rotate in radians per
48
/// frame.
49
const ROTATION_SPEED: f32 = 0.01;
50
51
/// The length of each side of the small cubes, in meters.
52
const SMALL_CUBE_SIZE: f32 = 0.1;
53
54
/// The length of each side of the large cube, in meters.
55
const LARGE_CUBE_SIZE: f32 = 2.0;
56
57
/// A marker component for the immediate parent of the large sphere of cubes.
58
#[derive(Default, Component)]
59
struct SphereParent;
60
61
/// A marker component for the large spinning cube at the origin.
62
#[derive(Default, Component)]
63
struct LargeCube;
64
65
/// A plugin for the render app that reads the number of culled meshes from the
66
/// GPU back to the CPU.
67
struct ReadbackIndirectParametersPlugin;
68
69
/// The node that we insert into the render graph in order to read the number of
70
/// culled meshes from the GPU back to the CPU.
71
#[derive(Default)]
72
struct ReadbackIndirectParametersNode;
73
74
/// The [`RenderLabel`] that we use to identify the
75
/// [`ReadbackIndirectParametersNode`].
76
#[derive(Clone, PartialEq, Eq, Hash, Debug, RenderLabel)]
77
struct ReadbackIndirectParameters;
78
79
/// The intermediate staging buffers that we use to read back the indirect
80
/// parameters from the GPU to the CPU.
81
///
82
/// We read back the GPU indirect parameters so that we can determine the number
83
/// of meshes that were culled.
84
///
85
/// `wgpu` doesn't allow us to read indirect buffers back from the GPU to the
86
/// CPU directly. Instead, we have to copy them to a temporary staging buffer
87
/// first, and then read *those* buffers back from the GPU to the CPU. This
88
/// resource holds those temporary buffers.
89
#[derive(Resource, Default)]
90
struct IndirectParametersStagingBuffers {
91
/// The buffer that stores the indirect draw commands.
92
///
93
/// See [`IndirectParametersIndexed`] for more information about the memory
94
/// layout of this buffer.
95
data: Option<Buffer>,
96
/// The buffer that stores the *number* of indirect draw commands.
97
///
98
/// We only care about the first `u32` in this buffer.
99
batch_sets: Option<Buffer>,
100
}
101
102
/// A resource, shared between the main world and the render world, that saves a
103
/// CPU-side copy of the GPU buffer that stores the indirect draw parameters.
104
///
105
/// This is needed so that we can display the number of meshes that were culled.
106
/// It's reference counted, and protected by a lock, because we don't precisely
107
/// know when the GPU will be ready to present the CPU with the buffer copy.
108
/// Even though the rendering runs at least a frame ahead of the main app logic,
109
/// we don't require more precise synchronization than the lock because we don't
110
/// really care how up-to-date the counter of culled meshes is. If it's off by a
111
/// few frames, that's no big deal.
112
#[derive(Clone, Resource, Deref, DerefMut)]
113
struct SavedIndirectParameters(Arc<Mutex<Option<SavedIndirectParametersData>>>);
114
115
/// A CPU-side copy of the GPU buffer that stores the indirect draw parameters.
116
///
117
/// This is needed so that we can display the number of meshes that were culled.
118
struct SavedIndirectParametersData {
119
/// The CPU-side copy of the GPU buffer that stores the indirect draw
120
/// parameters.
121
data: Vec<IndirectParametersIndexed>,
122
/// The CPU-side copy of the GPU buffer that stores the *number* of indirect
123
/// draw parameters that we have.
124
///
125
/// All we care about is the number of indirect draw parameters for a single
126
/// view, so this is only one word in size.
127
count: u32,
128
/// True if occlusion culling is supported at all; false if it's not.
129
occlusion_culling_supported: bool,
130
/// True if we support inspecting the number of meshes that were culled on
131
/// this platform; false if we don't.
132
///
133
/// If `multi_draw_indirect_count` isn't supported, then we would have to
134
/// employ a more complicated approach in order to determine the number of
135
/// meshes that are occluded, and that would be out of scope for this
136
/// example.
137
occlusion_culling_introspection_supported: bool,
138
}
139
140
impl SavedIndirectParameters {
141
fn new() -> Self {
142
Self(Arc::new(Mutex::new(None)))
143
}
144
}
145
146
fn init_saved_indirect_parameters(
147
render_device: Res<RenderDevice>,
148
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
149
saved_indirect_parameters: Res<SavedIndirectParameters>,
150
) {
151
let mut saved_indirect_parameters = saved_indirect_parameters.0.lock().unwrap();
152
*saved_indirect_parameters = Some(SavedIndirectParametersData {
153
data: vec![],
154
count: 0,
155
occlusion_culling_supported: gpu_preprocessing_support.is_culling_supported(),
156
// In order to determine how many meshes were culled, we look at the indirect count buffer
157
// that Bevy only populates if the platform supports `multi_draw_indirect_count`. So, if we
158
// don't have that feature, then we don't bother to display how many meshes were culled.
159
occlusion_culling_introspection_supported: render_device
160
.features()
161
.contains(WgpuFeatures::MULTI_DRAW_INDIRECT_COUNT),
162
});
163
}
164
165
/// The demo's current settings.
166
#[derive(Resource)]
167
struct AppStatus {
168
/// Whether occlusion culling is presently enabled.
169
///
170
/// By default, this is set to true.
171
occlusion_culling: bool,
172
}
173
174
impl Default for AppStatus {
175
fn default() -> Self {
176
AppStatus {
177
occlusion_culling: true,
178
}
179
}
180
}
181
182
fn main() {
183
let render_debug_flags = RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS;
184
185
App::new()
186
.add_plugins(
187
DefaultPlugins
188
.set(WindowPlugin {
189
primary_window: Some(Window {
190
title: "Bevy Occlusion Culling Example".into(),
191
..default()
192
}),
193
..default()
194
})
195
.set(RenderPlugin {
196
debug_flags: render_debug_flags,
197
..default()
198
})
199
.set(PbrPlugin {
200
debug_flags: render_debug_flags,
201
..default()
202
}),
203
)
204
.add_plugins(ReadbackIndirectParametersPlugin)
205
.init_resource::<AppStatus>()
206
.add_systems(Startup, setup)
207
.add_systems(Update, spin_small_cubes)
208
.add_systems(Update, spin_large_cube)
209
.add_systems(Update, update_status_text)
210
.add_systems(Update, toggle_occlusion_culling_on_request)
211
.run();
212
}
213
214
impl Plugin for ReadbackIndirectParametersPlugin {
215
fn build(&self, app: &mut App) {
216
// Create the `SavedIndirectParameters` resource that we're going to use
217
// to communicate between the thread that the GPU-to-CPU readback
218
// callback runs on and the main application threads. This resource is
219
// atomically reference counted. We store one reference to the
220
// `SavedIndirectParameters` in the main app and another reference in
221
// the render app.
222
let saved_indirect_parameters = SavedIndirectParameters::new();
223
app.insert_resource(saved_indirect_parameters.clone());
224
225
// Fetch the render app.
226
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
227
return;
228
};
229
230
render_app
231
// Insert another reference to the `SavedIndirectParameters`.
232
.insert_resource(saved_indirect_parameters)
233
// Setup the parameters in RenderStartup.
234
.add_systems(RenderStartup, init_saved_indirect_parameters)
235
.init_resource::<IndirectParametersStagingBuffers>()
236
.add_systems(ExtractSchedule, readback_indirect_parameters)
237
.add_systems(
238
Render,
239
create_indirect_parameters_staging_buffers
240
.in_set(RenderSystems::PrepareResourcesFlush),
241
)
242
// Add the node that allows us to read the indirect parameters back
243
// from the GPU to the CPU, which allows us to determine how many
244
// meshes were culled.
245
.add_render_graph_node::<ReadbackIndirectParametersNode>(
246
Core3d,
247
ReadbackIndirectParameters,
248
)
249
// We read back the indirect parameters any time after
250
// `EndMainPass`. Readback doesn't particularly need to execute
251
// before `EndMainPassPostProcessing`, but we specify that anyway
252
// because we want to make the indirect parameters run before
253
// *something* in the graph, and `EndMainPassPostProcessing` is a
254
// good a node as any other.
255
.add_render_graph_edges(
256
Core3d,
257
(
258
Node3d::EndMainPass,
259
ReadbackIndirectParameters,
260
Node3d::EndMainPassPostProcessing,
261
),
262
);
263
}
264
}
265
266
/// Spawns all the objects in the scene.
267
fn setup(
268
mut commands: Commands,
269
asset_server: Res<AssetServer>,
270
mut meshes: ResMut<Assets<Mesh>>,
271
mut materials: ResMut<Assets<StandardMaterial>>,
272
) {
273
spawn_small_cubes(&mut commands, &mut meshes, &mut materials);
274
spawn_large_cube(&mut commands, &asset_server, &mut meshes, &mut materials);
275
spawn_light(&mut commands);
276
spawn_camera(&mut commands);
277
spawn_help_text(&mut commands);
278
}
279
280
/// Spawns the rotating sphere of small cubes.
281
fn spawn_small_cubes(
282
commands: &mut Commands,
283
meshes: &mut Assets<Mesh>,
284
materials: &mut Assets<StandardMaterial>,
285
) {
286
// Add the cube mesh.
287
let small_cube = meshes.add(Cuboid::new(
288
SMALL_CUBE_SIZE,
289
SMALL_CUBE_SIZE,
290
SMALL_CUBE_SIZE,
291
));
292
293
// Add the cube material.
294
let small_cube_material = materials.add(StandardMaterial {
295
base_color: SILVER.into(),
296
..default()
297
});
298
299
// Create the entity that the small cubes will be parented to. This is the
300
// entity that we rotate.
301
let sphere_parent = commands
302
.spawn(Transform::from_translation(Vec3::ZERO))
303
.insert(Visibility::default())
304
.insert(SphereParent)
305
.id();
306
307
// Now we have to figure out where to place the cubes. To do that, we create
308
// a sphere mesh, but we don't add it to the scene. Instead, we inspect the
309
// sphere mesh to find the positions of its vertices, and spawn a small cube
310
// at each one. That way, we end up with a bunch of cubes arranged in a
311
// spherical shape.
312
313
// Create the sphere mesh, and extract the positions of its vertices.
314
let sphere = Sphere::new(OUTER_RADIUS)
315
.mesh()
316
.ico(OUTER_SUBDIVISION_COUNT)
317
.unwrap();
318
let sphere_positions = sphere.attribute(Mesh::ATTRIBUTE_POSITION).unwrap();
319
320
// At each vertex, create a small cube.
321
for sphere_position in sphere_positions.as_float3().unwrap() {
322
let sphere_position = Vec3::from_slice(sphere_position);
323
let small_cube = commands
324
.spawn(Mesh3d(small_cube.clone()))
325
.insert(MeshMaterial3d(small_cube_material.clone()))
326
.insert(Transform::from_translation(sphere_position))
327
.id();
328
commands.entity(sphere_parent).add_child(small_cube);
329
}
330
}
331
332
/// Spawns the large cube at the center of the screen.
333
///
334
/// This cube rotates chaotically and occludes small cubes behind it.
335
fn spawn_large_cube(
336
commands: &mut Commands,
337
asset_server: &AssetServer,
338
meshes: &mut Assets<Mesh>,
339
materials: &mut Assets<StandardMaterial>,
340
) {
341
commands
342
.spawn(Mesh3d(meshes.add(Cuboid::new(
343
LARGE_CUBE_SIZE,
344
LARGE_CUBE_SIZE,
345
LARGE_CUBE_SIZE,
346
))))
347
.insert(MeshMaterial3d(materials.add(StandardMaterial {
348
base_color: WHITE.into(),
349
base_color_texture: Some(asset_server.load("branding/icon.png")),
350
..default()
351
})))
352
.insert(Transform::IDENTITY)
353
.insert(LargeCube);
354
}
355
356
// Spins the outer sphere a bit every frame.
357
//
358
// This ensures that the set of cubes that are hidden and shown varies over
359
// time.
360
fn spin_small_cubes(mut sphere_parents: Query<&mut Transform, With<SphereParent>>) {
361
for mut sphere_parent_transform in &mut sphere_parents {
362
sphere_parent_transform.rotate_y(ROTATION_SPEED);
363
}
364
}
365
366
/// Spins the large cube a bit every frame.
367
///
368
/// The chaotic rotation adds a bit of randomness to the scene to better
369
/// demonstrate the dynamicity of the occlusion culling.
370
fn spin_large_cube(mut large_cubes: Query<&mut Transform, With<LargeCube>>) {
371
for mut transform in &mut large_cubes {
372
transform.rotate(Quat::from_euler(
373
EulerRot::XYZ,
374
0.13 * ROTATION_SPEED,
375
0.29 * ROTATION_SPEED,
376
0.35 * ROTATION_SPEED,
377
));
378
}
379
}
380
381
/// Spawns a directional light to illuminate the scene.
382
fn spawn_light(commands: &mut Commands) {
383
commands
384
.spawn(DirectionalLight::default())
385
.insert(Transform::from_rotation(Quat::from_euler(
386
EulerRot::ZYX,
387
0.0,
388
PI * -0.15,
389
PI * -0.15,
390
)));
391
}
392
393
/// Spawns a camera that includes the depth prepass and occlusion culling.
394
fn spawn_camera(commands: &mut Commands) {
395
commands
396
.spawn(Camera3d::default())
397
.insert(Transform::from_xyz(0.0, 0.0, 9.0).looking_at(Vec3::ZERO, Vec3::Y))
398
.insert(DepthPrepass)
399
.insert(OcclusionCulling);
400
}
401
402
/// Spawns the help text at the upper left of the screen.
403
fn spawn_help_text(commands: &mut Commands) {
404
commands.spawn((
405
Text::new(""),
406
Node {
407
position_type: PositionType::Absolute,
408
top: px(12),
409
left: px(12),
410
..default()
411
},
412
));
413
}
414
415
impl render_graph::Node for ReadbackIndirectParametersNode {
416
fn run<'w>(
417
&self,
418
_: &mut RenderGraphContext,
419
render_context: &mut RenderContext<'w>,
420
world: &'w World,
421
) -> Result<(), NodeRunError> {
422
// Extract the buffers that hold the GPU indirect draw parameters from
423
// the world resources. We're going to read those buffers to determine
424
// how many meshes were actually drawn.
425
let (Some(indirect_parameters_buffers), Some(indirect_parameters_mapping_buffers)) = (
426
world.get_resource::<IndirectParametersBuffers>(),
427
world.get_resource::<IndirectParametersStagingBuffers>(),
428
) else {
429
return Ok(());
430
};
431
432
// Get the indirect parameters buffers corresponding to the opaque 3D
433
// phase, since all our meshes are in that phase.
434
let Some(phase_indirect_parameters_buffers) =
435
indirect_parameters_buffers.get(&TypeId::of::<Opaque3d>())
436
else {
437
return Ok(());
438
};
439
440
// Grab both the buffers we're copying from and the staging buffers
441
// we're copying to. Remember that we can't map the indirect parameters
442
// buffers directly, so we have to copy their contents to a staging
443
// buffer.
444
let (
445
Some(indexed_data_buffer),
446
Some(indexed_batch_sets_buffer),
447
Some(indirect_parameters_staging_data_buffer),
448
Some(indirect_parameters_staging_batch_sets_buffer),
449
) = (
450
phase_indirect_parameters_buffers.indexed.data_buffer(),
451
phase_indirect_parameters_buffers
452
.indexed
453
.batch_sets_buffer(),
454
indirect_parameters_mapping_buffers.data.as_ref(),
455
indirect_parameters_mapping_buffers.batch_sets.as_ref(),
456
)
457
else {
458
return Ok(());
459
};
460
461
// Copy from the indirect parameters buffers to the staging buffers.
462
render_context.command_encoder().copy_buffer_to_buffer(
463
indexed_data_buffer,
464
0,
465
indirect_parameters_staging_data_buffer,
466
0,
467
indexed_data_buffer.size(),
468
);
469
render_context.command_encoder().copy_buffer_to_buffer(
470
indexed_batch_sets_buffer,
471
0,
472
indirect_parameters_staging_batch_sets_buffer,
473
0,
474
indexed_batch_sets_buffer.size(),
475
);
476
477
Ok(())
478
}
479
}
480
481
/// Creates the staging buffers that we use to read back the indirect parameters
482
/// from the GPU to the CPU.
483
///
484
/// We read the indirect parameters from the GPU to the CPU in order to display
485
/// the number of meshes that were culled each frame.
486
///
487
/// We need these staging buffers because `wgpu` doesn't allow us to read the
488
/// contents of the indirect parameters buffers directly. We must first copy
489
/// them from the GPU to a staging buffer, and then read the staging buffer.
490
fn create_indirect_parameters_staging_buffers(
491
mut indirect_parameters_staging_buffers: ResMut<IndirectParametersStagingBuffers>,
492
indirect_parameters_buffers: Res<IndirectParametersBuffers>,
493
render_device: Res<RenderDevice>,
494
) {
495
let Some(phase_indirect_parameters_buffers) =
496
indirect_parameters_buffers.get(&TypeId::of::<Opaque3d>())
497
else {
498
return;
499
};
500
501
// Fetch the indirect parameters buffers that we're going to copy from.
502
let (Some(indexed_data_buffer), Some(indexed_batch_set_buffer)) = (
503
phase_indirect_parameters_buffers.indexed.data_buffer(),
504
phase_indirect_parameters_buffers
505
.indexed
506
.batch_sets_buffer(),
507
) else {
508
return;
509
};
510
511
// Build the staging buffers. Make sure they have the same sizes as the
512
// buffers we're copying from.
513
indirect_parameters_staging_buffers.data =
514
Some(render_device.create_buffer(&BufferDescriptor {
515
label: Some("indexed data staging buffer"),
516
size: indexed_data_buffer.size(),
517
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
518
mapped_at_creation: false,
519
}));
520
indirect_parameters_staging_buffers.batch_sets =
521
Some(render_device.create_buffer(&BufferDescriptor {
522
label: Some("indexed batch set staging buffer"),
523
size: indexed_batch_set_buffer.size(),
524
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
525
mapped_at_creation: false,
526
}));
527
}
528
529
/// Updates the app status text at the top of the screen.
530
fn update_status_text(
531
saved_indirect_parameters: Res<SavedIndirectParameters>,
532
mut texts: Query<&mut Text>,
533
meshes: Query<Entity, With<Mesh3d>>,
534
app_status: Res<AppStatus>,
535
) {
536
// How many meshes are in the scene?
537
let total_mesh_count = meshes.iter().count();
538
539
// Sample the rendered object count. Note that we don't synchronize beyond
540
// locking the data and therefore this will value will generally at least
541
// one frame behind. This is fine; this app is just a demonstration after
542
// all.
543
let (
544
rendered_object_count,
545
occlusion_culling_supported,
546
occlusion_culling_introspection_supported,
547
): (u32, bool, bool) = {
548
let saved_indirect_parameters = saved_indirect_parameters.lock().unwrap();
549
let Some(saved_indirect_parameters) = saved_indirect_parameters.as_ref() else {
550
// Bail out early if the resource isn't initialized yet.
551
return;
552
};
553
(
554
saved_indirect_parameters
555
.data
556
.iter()
557
.take(saved_indirect_parameters.count as usize)
558
.map(|indirect_parameters| indirect_parameters.instance_count)
559
.sum(),
560
saved_indirect_parameters.occlusion_culling_supported,
561
saved_indirect_parameters.occlusion_culling_introspection_supported,
562
)
563
};
564
565
// Change the text.
566
for mut text in &mut texts {
567
text.0 = String::new();
568
if !occlusion_culling_supported {
569
text.0
570
.push_str("Occlusion culling not supported on this platform");
571
continue;
572
}
573
574
let _ = writeln!(
575
&mut text.0,
576
"Occlusion culling {} (Press Space to toggle)",
577
if app_status.occlusion_culling {
578
"ON"
579
} else {
580
"OFF"
581
},
582
);
583
584
if !occlusion_culling_introspection_supported {
585
continue;
586
}
587
588
let _ = write!(
589
&mut text.0,
590
"{rendered_object_count}/{total_mesh_count} meshes rendered"
591
);
592
}
593
}
594
595
/// A system that reads the indirect parameters back from the GPU so that we can
596
/// report how many meshes were culled.
597
fn readback_indirect_parameters(
598
mut indirect_parameters_staging_buffers: ResMut<IndirectParametersStagingBuffers>,
599
saved_indirect_parameters: Res<SavedIndirectParameters>,
600
) {
601
// If culling isn't supported on this platform, bail.
602
if !saved_indirect_parameters
603
.lock()
604
.unwrap()
605
.as_ref()
606
.unwrap()
607
.occlusion_culling_supported
608
{
609
return;
610
}
611
612
// Grab the staging buffers.
613
let (Some(data_buffer), Some(batch_sets_buffer)) = (
614
indirect_parameters_staging_buffers.data.take(),
615
indirect_parameters_staging_buffers.batch_sets.take(),
616
) else {
617
return;
618
};
619
620
// Read the GPU buffers back.
621
let saved_indirect_parameters_0 = (**saved_indirect_parameters).clone();
622
let saved_indirect_parameters_1 = (**saved_indirect_parameters).clone();
623
readback_buffer::<IndirectParametersIndexed>(data_buffer, move |indirect_parameters| {
624
saved_indirect_parameters_0
625
.lock()
626
.unwrap()
627
.as_mut()
628
.unwrap()
629
.data = indirect_parameters.to_vec();
630
});
631
readback_buffer::<u32>(batch_sets_buffer, move |indirect_parameters_count| {
632
saved_indirect_parameters_1
633
.lock()
634
.unwrap()
635
.as_mut()
636
.unwrap()
637
.count = indirect_parameters_count[0];
638
});
639
}
640
641
// A helper function to asynchronously read an array of [`Pod`] values back from
642
// the GPU to the CPU.
643
//
644
// The given callback is invoked when the data is ready. The buffer will
645
// automatically be unmapped after the callback executes.
646
fn readback_buffer<T>(buffer: Buffer, callback: impl FnOnce(&[T]) + Send + 'static)
647
where
648
T: Pod,
649
{
650
// We need to make another reference to the buffer so that we can move the
651
// original reference into the closure below.
652
let original_buffer = buffer.clone();
653
original_buffer
654
.slice(..)
655
.map_async(MapMode::Read, move |result| {
656
// Make sure we succeeded.
657
if result.is_err() {
658
return;
659
}
660
661
{
662
// Cast the raw bytes in the GPU buffer to the appropriate type.
663
let buffer_view = buffer.slice(..).get_mapped_range();
664
let indirect_parameters: &[T] = bytemuck::cast_slice(
665
&buffer_view[0..(buffer_view.len() / size_of::<T>() * size_of::<T>())],
666
);
667
668
// Invoke the callback.
669
callback(indirect_parameters);
670
}
671
672
// Unmap the buffer. We have to do this before submitting any more
673
// GPU command buffers, or `wgpu` will assert.
674
buffer.unmap();
675
});
676
}
677
678
/// Adds or removes the [`OcclusionCulling`] and [`DepthPrepass`] components
679
/// when the user presses the spacebar.
680
fn toggle_occlusion_culling_on_request(
681
mut commands: Commands,
682
input: Res<ButtonInput<KeyCode>>,
683
mut app_status: ResMut<AppStatus>,
684
cameras: Query<Entity, With<Camera3d>>,
685
) {
686
// Only run when the user presses the spacebar.
687
if !input.just_pressed(KeyCode::Space) {
688
return;
689
}
690
691
// Toggle the occlusion culling flag in `AppStatus`.
692
app_status.occlusion_culling = !app_status.occlusion_culling;
693
694
// Add or remove the `OcclusionCulling` and `DepthPrepass` components as
695
// requested.
696
for camera in &cameras {
697
if app_status.occlusion_culling {
698
commands
699
.entity(camera)
700
.insert(DepthPrepass)
701
.insert(OcclusionCulling);
702
} else {
703
commands
704
.entity(camera)
705
.remove::<DepthPrepass>()
706
.remove::<OcclusionCulling>();
707
}
708
}
709
}
710
711