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)]
97enum RenderError {
98 #[default]
99 None,
100 OutOfMemory,
101 Validation,
102 DeviceLost,
103 Loop,
104}
105
106fn 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
145fn 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}