diff --git a/Cargo.toml b/Cargo.toml index 717b40d97aa57..324fbdc4e6a49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1957,6 +1957,17 @@ description = "Using bevy with manually driven update to render images" category = "Application" wasm = false +[[example]] +name = "render_recovery" +path = "examples/app/render_recovery.rs" +doc-scrape-examples = true + +[package.metadata.example.render_recovery] +name = "Render Recovery" +description = "Demonstrates how bevy can recover from rendering failures." +category = "Application" +wasm = false + [[example]] name = "headless_renderer" path = "examples/app/headless_renderer.rs" diff --git a/examples/README.md b/examples/README.md index 70c7cbfb8d442..ebc7661dbf141 100644 --- a/examples/README.md +++ b/examples/README.md @@ -241,6 +241,7 @@ Example | Description [No Renderer](../examples/app/no_renderer.rs) | An application that runs with default plugins and displays an empty window, but without an actual renderer [Plugin](../examples/app/plugin.rs) | Demonstrates the creation and registration of a custom plugin [Plugin Group](../examples/app/plugin_group.rs) | Demonstrates the creation and registration of a custom plugin group +[Render Recovery](../examples/app/render_recovery.rs) | Demonstrates how bevy can recover from rendering failures. [Return after Run](../examples/app/return_after_run.rs) | Show how to return to main after the Bevy app has exited [Thread Pool Resources](../examples/app/thread_pool_resources.rs) | Creates and customizes the internal thread pool [Without Winit](../examples/app/without_winit.rs) | Create an application without winit (runs single time, no event loop) diff --git a/examples/app/render_recovery.rs b/examples/app/render_recovery.rs new file mode 100644 index 0000000000000..acdca3c1b9833 --- /dev/null +++ b/examples/app/render_recovery.rs @@ -0,0 +1,182 @@ +//! Demonstrates how to trigger various rendering errors, and how bevy can recover from them. + +use bevy::{ + input::keyboard::Key, + prelude::*, + render::{ + 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::::default(), + )) + .add_systems(Startup, setup) + .add_systems(Update, (update_camera, input)) + .init_resource::() + .sub_app_mut(RenderApp) + .add_systems(Render, cause_error); + app.run(); +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // circular base + 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)), + )); + // cube + 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), + )); + // light + commands.spawn(( + PointLight { + shadow_maps_enabled: true, + ..default() + }, + Transform::from_xyz(4.0, 8.0, 4.0), + )); + // camera + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), + )); + // help text + commands.spawn(( + Text::new( + "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\ + ", + ), + Node { + position_type: PositionType::Absolute, + top: px(12), + left: px(12), + ..default() + }, + )); +} + +fn update_camera(mut camera: Query<&mut Transform, With>, time: Res