use bevy::{
input::keyboard::Key,
prelude::*,
render::{
error_handler::{RenderErrorHandler, RenderErrorPolicy},
extract_resource::{ExtractResource, ExtractResourcePlugin},
render_resource::{
BufferDescriptor, BufferUsages, CommandEncoderDescriptor, ComputePassDescriptor,
Extent3d, PipelineLayoutDescriptor, PollType, RawComputePipelineDescriptor,
ShaderModuleDescriptor, ShaderSource, TextureDescriptor, TextureDimension,
TextureFormat, TextureUsages,
},
renderer::{RenderDevice, RenderQueue},
Render, RenderApp,
},
};
fn main() {
let mut app = App::new();
app.add_plugins((
DefaultPlugins,
ExtractResourcePlugin::<RenderError>::default(),
))
.add_systems(Startup, setup)
.add_systems(Update, (update_camera, input))
.init_resource::<RenderError>()
.sub_app_mut(RenderApp)
.add_systems(Render, cause_error);
app.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Mesh3d(meshes.add(Circle::new(4.0))),
MeshMaterial3d(materials.add(Color::WHITE)),
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
));
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
Transform::from_xyz(0.0, 1.0, 0.0),
));
commands.spawn((
PointLight {
shadow_maps_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
));
commands.spawn((
Text::new(
"Test at your own risk: you may need to restart your computer to fully recover\n\
Press O to trigger an OutOfMemory error\n\
Press V to trigger a Validation error\n\
Press D to Destroy the render device (causes device lost error)\n\
Press L to Loop infinitely in a compute shader (causes device lost error)\n\
\n\
Press 1 to ignore errors, pretending nothing happened and continue rendering.\n\
Press 2 to panic on error.\n\
Press 3 to signals app exit on error.\n\
Press 4 to keeps the app alive, but stops rendering further on error.\n\
Press 5 to attempt renderer recovery.\n\
",
),
Node {
position_type: PositionType::Absolute,
top: px(12),
left: px(12),
..default()
},
));
}
fn update_camera(mut camera: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
for mut t in camera.iter_mut() {
let (s, c) = ops::sin_cos(time.elapsed_secs() * 0.3);
*t = Transform::from_xyz(s * 10.0, 4.5, c * 10.0).looking_at(Vec3::ZERO, Vec3::Y);
}
}
#[derive(Resource, ExtractResource, Clone, Default)]
enum RenderError {
#[default]
None,
OutOfMemory,
Validation,
DeviceLost,
Loop,
}
fn input(
input: Res<ButtonInput<Key>>,
mut error: ResMut<RenderError>,
mut handler: ResMut<RenderErrorHandler>,
) {
*error = RenderError::None;
if input.just_pressed(Key::Character("o".into())) {
*error = RenderError::OutOfMemory;
}
if input.just_pressed(Key::Character("v".into())) {
*error = RenderError::Validation;
}
if input.just_pressed(Key::Character("d".into())) {
*error = RenderError::DeviceLost;
}
if input.just_pressed(Key::Character("l".into())) {
*error = RenderError::Loop;
}
if input.just_pressed(Key::Character("1".into())) {
*handler = RenderErrorHandler(|_, _, _| RenderErrorPolicy::Ignore);
}
if input.just_pressed(Key::Character("2".into())) {
*handler = RenderErrorHandler(|error, _, _| panic!("Rendering error {error:?}"));
}
if input.just_pressed(Key::Character("3".into())) {
*handler = RenderErrorHandler(|_, main_world, _| {
main_world.write_message(AppExit::error());
RenderErrorPolicy::StopRendering
});
}
if input.just_pressed(Key::Character("4".into())) {
*handler = RenderErrorHandler(|_, _, _| RenderErrorPolicy::StopRendering);
}
if input.just_pressed(Key::Character("5".into())) {
*handler = RenderErrorHandler(|_, _, _| RenderErrorPolicy::Recover(default()));
}
}
fn cause_error(error: If<Res<RenderError>>, device: Res<RenderDevice>, queue: Res<RenderQueue>) {
match **error {
RenderError::None => {}
RenderError::OutOfMemory => {
let mut textures = Vec::new();
for _ in 0..64 {
textures.push(device.create_texture(&TextureDescriptor {
label: None,
size: Extent3d {
width: 8192,
height: 8192,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba16Float,
usage: TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
}));
}
}
RenderError::Validation => {
device.create_buffer(&BufferDescriptor {
label: None,
size: 1 << 63,
usage: BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
}
RenderError::DeviceLost => {
device.wgpu_device().destroy();
device.poll(PollType::wait_indefinitely()).unwrap();
}
RenderError::Loop => {
let sm = device.create_and_validate_shader_module(ShaderModuleDescriptor {
label: Some("shader"),
source: ShaderSource::Wgsl(
"@compute @workgroup_size(1, 1, 1) fn main() { loop { workgroupBarrier(); } }"
.into(),
),
});
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("pipeline_layout"),
bind_group_layouts: &[],
immediate_size: 0,
});
let pipeline = device.create_compute_pipeline(&RawComputePipelineDescriptor {
label: Some("pipeline"),
layout: Some(&pipeline_layout),
module: &sm,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor::default());
{
let mut cpass = encoder.begin_compute_pass(&ComputePassDescriptor::default());
cpass.set_pipeline(&pipeline);
cpass.dispatch_workgroups(1, 1, 1);
}
device.poll(PollType::wait_indefinitely()).unwrap();
queue.submit([encoder.finish()]);
}
}
}