Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/app/render_recovery.rs
9333 views
1
//! Demonstrates how to trigger various rendering errors, and how bevy can recover from them.
2
3
use bevy::{
4
input::keyboard::Key,
5
prelude::*,
6
render::{
7
error_handler::{RenderErrorHandler, RenderErrorPolicy},
8
extract_resource::{ExtractResource, ExtractResourcePlugin},
9
render_resource::{
10
BufferDescriptor, BufferUsages, CommandEncoderDescriptor, ComputePassDescriptor,
11
Extent3d, PipelineLayoutDescriptor, PollType, RawComputePipelineDescriptor,
12
ShaderModuleDescriptor, ShaderSource, TextureDescriptor, TextureDimension,
13
TextureFormat, TextureUsages,
14
},
15
renderer::{RenderDevice, RenderQueue},
16
Render, RenderApp,
17
},
18
};
19
20
fn main() {
21
let mut app = App::new();
22
app.add_plugins((
23
DefaultPlugins,
24
ExtractResourcePlugin::<RenderError>::default(),
25
))
26
.add_systems(Startup, setup)
27
.add_systems(Update, (update_camera, input))
28
.init_resource::<RenderError>()
29
.sub_app_mut(RenderApp)
30
.add_systems(Render, cause_error);
31
app.run();
32
}
33
34
fn setup(
35
mut commands: Commands,
36
mut meshes: ResMut<Assets<Mesh>>,
37
mut materials: ResMut<Assets<StandardMaterial>>,
38
) {
39
// circular base
40
commands.spawn((
41
Mesh3d(meshes.add(Circle::new(4.0))),
42
MeshMaterial3d(materials.add(Color::WHITE)),
43
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
44
));
45
// cube
46
commands.spawn((
47
Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
48
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
49
Transform::from_xyz(0.0, 1.0, 0.0),
50
));
51
// light
52
commands.spawn((
53
PointLight {
54
shadow_maps_enabled: true,
55
..default()
56
},
57
Transform::from_xyz(4.0, 8.0, 4.0),
58
));
59
// camera
60
commands.spawn((
61
Camera3d::default(),
62
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
63
));
64
// help text
65
commands.spawn((
66
Text::new(
67
"Test at your own risk: you may need to restart your computer to fully recover\n\
68
Press O to trigger an OutOfMemory error\n\
69
Press V to trigger a Validation error\n\
70
Press D to Destroy the render device (causes device lost error)\n\
71
Press L to Loop infinitely in a compute shader (causes device lost error)\n\
72
\n\
73
Press 1 to ignore errors, pretending nothing happened and continue rendering.\n\
74
Press 2 to panic on error.\n\
75
Press 3 to signals app exit on error.\n\
76
Press 4 to keeps the app alive, but stops rendering further on error.\n\
77
Press 5 to attempt renderer recovery.\n\
78
",
79
),
80
Node {
81
position_type: PositionType::Absolute,
82
top: px(12),
83
left: px(12),
84
..default()
85
},
86
));
87
}
88
89
fn update_camera(mut camera: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
90
for mut t in camera.iter_mut() {
91
let (s, c) = ops::sin_cos(time.elapsed_secs() * 0.3);
92
*t = Transform::from_xyz(s * 10.0, 4.5, c * 10.0).looking_at(Vec3::ZERO, Vec3::Y);
93
}
94
}
95
96
#[derive(Resource, ExtractResource, Clone, Default)]
97
enum RenderError {
98
#[default]
99
None,
100
OutOfMemory,
101
Validation,
102
DeviceLost,
103
Loop,
104
}
105
106
fn input(
107
input: Res<ButtonInput<Key>>,
108
mut error: ResMut<RenderError>,
109
mut handler: ResMut<RenderErrorHandler>,
110
) {
111
*error = RenderError::None;
112
if input.just_pressed(Key::Character("o".into())) {
113
*error = RenderError::OutOfMemory;
114
}
115
if input.just_pressed(Key::Character("v".into())) {
116
*error = RenderError::Validation;
117
}
118
if input.just_pressed(Key::Character("d".into())) {
119
*error = RenderError::DeviceLost;
120
}
121
if input.just_pressed(Key::Character("l".into())) {
122
*error = RenderError::Loop;
123
}
124
125
if input.just_pressed(Key::Character("1".into())) {
126
*handler = RenderErrorHandler(|_, _, _| RenderErrorPolicy::Ignore);
127
}
128
if input.just_pressed(Key::Character("2".into())) {
129
*handler = RenderErrorHandler(|error, _, _| panic!("Rendering error {error:?}"));
130
}
131
if input.just_pressed(Key::Character("3".into())) {
132
*handler = RenderErrorHandler(|_, main_world, _| {
133
main_world.write_message(AppExit::error());
134
RenderErrorPolicy::StopRendering
135
});
136
}
137
if input.just_pressed(Key::Character("4".into())) {
138
*handler = RenderErrorHandler(|_, _, _| RenderErrorPolicy::StopRendering);
139
}
140
if input.just_pressed(Key::Character("5".into())) {
141
*handler = RenderErrorHandler(|_, _, _| RenderErrorPolicy::Recover(default()));
142
}
143
}
144
145
fn cause_error(error: If<Res<RenderError>>, device: Res<RenderDevice>, queue: Res<RenderQueue>) {
146
match **error {
147
RenderError::None => {}
148
RenderError::OutOfMemory => {
149
let mut textures = Vec::new();
150
for _ in 0..64 {
151
textures.push(device.create_texture(&TextureDescriptor {
152
label: None,
153
size: Extent3d {
154
width: 8192,
155
height: 8192,
156
depth_or_array_layers: 1,
157
},
158
mip_level_count: 1,
159
sample_count: 1,
160
dimension: TextureDimension::D2,
161
format: TextureFormat::Rgba16Float,
162
usage: TextureUsages::RENDER_ATTACHMENT,
163
view_formats: &[],
164
}));
165
}
166
}
167
RenderError::Validation => {
168
device.create_buffer(&BufferDescriptor {
169
label: None,
170
size: 1 << 63,
171
usage: BufferUsages::COPY_SRC,
172
mapped_at_creation: false,
173
});
174
}
175
RenderError::DeviceLost => {
176
device.wgpu_device().destroy();
177
device.poll(PollType::wait_indefinitely()).unwrap();
178
}
179
RenderError::Loop => {
180
let sm = device.create_and_validate_shader_module(ShaderModuleDescriptor {
181
label: Some("shader"),
182
source: ShaderSource::Wgsl(
183
"@compute @workgroup_size(1, 1, 1) fn main() { loop { workgroupBarrier(); } }"
184
.into(),
185
),
186
});
187
188
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
189
label: Some("pipeline_layout"),
190
bind_group_layouts: &[],
191
immediate_size: 0,
192
});
193
194
let pipeline = device.create_compute_pipeline(&RawComputePipelineDescriptor {
195
label: Some("pipeline"),
196
layout: Some(&pipeline_layout),
197
module: &sm,
198
entry_point: Some("main"),
199
compilation_options: Default::default(),
200
cache: None,
201
});
202
203
let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor::default());
204
{
205
let mut cpass = encoder.begin_compute_pass(&ComputePassDescriptor::default());
206
cpass.set_pipeline(&pipeline);
207
cpass.dispatch_workgroups(1, 1, 1);
208
}
209
device.poll(PollType::wait_indefinitely()).unwrap();
210
queue.submit([encoder.finish()]);
211
}
212
}
213
}
214
215