1use 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
20fn 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
34fn setup(
35 mut commands: Commands,
36 mut meshes: ResMut<Assets<Mesh>>,
37 mut materials: ResMut<Assets<StandardMaterial>>,
38) {
39 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 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 commands.spawn((
53 PointLight {
54 shadow_maps_enabled: true,
55 ..default()
56 },
57 Transform::from_xyz(4.0, 8.0, 4.0),
58 ));
59 commands.spawn((
61 Camera3d::default(),
62 Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
63 ));
64 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
89fn 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#[extract_app(RenderApp)]
98enum RenderError {
99 #[default]
100 None,
101 OutOfMemory,
102 Validation,
103 DeviceLost,
104 Loop,
105}
106
107fn input(
108 input: Res<ButtonInput<Key>>,
109 mut error: ResMut<RenderError>,
110 mut handler: ResMut<RenderErrorHandler>,
111) {
112 *error = RenderError::None;
113 if input.just_pressed(Key::Character("o".into())) {
114 *error = RenderError::OutOfMemory;
115 }
116 if input.just_pressed(Key::Character("v".into())) {
117 *error = RenderError::Validation;
118 }
119 if input.just_pressed(Key::Character("d".into())) {
120 *error = RenderError::DeviceLost;
121 }
122 if input.just_pressed(Key::Character("l".into())) {
123 *error = RenderError::Loop;
124 }
125
126 if input.just_pressed(Key::Character("1".into())) {
127 *handler = RenderErrorHandler(|_, _, _| RenderErrorPolicy::Ignore);
128 }
129 if input.just_pressed(Key::Character("2".into())) {
130 *handler = RenderErrorHandler(|error, _, _| panic!("Rendering error {error:?}"));
131 }
132 if input.just_pressed(Key::Character("3".into())) {
133 *handler = RenderErrorHandler(|_, main_world, _| {
134 main_world.write_message(AppExit::error());
135 RenderErrorPolicy::StopRendering
136 });
137 }
138 if input.just_pressed(Key::Character("4".into())) {
139 *handler = RenderErrorHandler(|_, _, _| RenderErrorPolicy::StopRendering);
140 }
141 if input.just_pressed(Key::Character("5".into())) {
142 *handler = RenderErrorHandler(|_, _, _| RenderErrorPolicy::Recover(default()));
143 }
144}
145
146fn cause_error(error: If<Res<RenderError>>, device: Res<RenderDevice>, queue: Res<RenderQueue>) {
147 match **error {
148 RenderError::None => {}
149 RenderError::OutOfMemory => {
150 let mut textures = Vec::new();
151 for _ in 0..64 {
152 textures.push(device.create_texture(&TextureDescriptor {
153 label: None,
154 size: Extent3d {
155 width: 8192,
156 height: 8192,
157 depth_or_array_layers: 1,
158 },
159 mip_level_count: 1,
160 sample_count: 1,
161 dimension: TextureDimension::D2,
162 format: TextureFormat::Rgba16Float,
163 usage: TextureUsages::RENDER_ATTACHMENT,
164 view_formats: &[],
165 }));
166 }
167 }
168 RenderError::Validation => {
169 device.create_buffer(&BufferDescriptor {
170 label: None,
171 size: 1 << 63,
172 usage: BufferUsages::COPY_SRC,
173 mapped_at_creation: false,
174 });
175 }
176 RenderError::DeviceLost => {
177 device.wgpu_device().destroy();
178 device.poll(PollType::wait_indefinitely()).unwrap();
179 }
180 RenderError::Loop => {
181 let sm = device.create_and_validate_shader_module(ShaderModuleDescriptor {
182 label: Some("shader"),
183 source: ShaderSource::Wgsl(
184 "@compute @workgroup_size(1, 1, 1) fn main() { loop { workgroupBarrier(); } }"
185 .into(),
186 ),
187 });
188
189 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
190 label: Some("pipeline_layout"),
191 bind_group_layouts: &[],
192 immediate_size: 0,
193 });
194
195 let pipeline = device.create_compute_pipeline(&RawComputePipelineDescriptor {
196 label: Some("pipeline"),
197 layout: Some(&pipeline_layout),
198 module: &sm,
199 entry_point: Some("main"),
200 compilation_options: Default::default(),
201 cache: None,
202 });
203
204 let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor::default());
205 {
206 let mut cpass = encoder.begin_compute_pass(&ComputePassDescriptor::default());
207 cpass.set_pipeline(&pipeline);
208 cpass.dispatch_workgroups(1, 1, 1);
209 }
210 device.poll(PollType::wait_indefinitely()).unwrap();
211 queue.submit([encoder.finish()]);
212 }
213 }
214}