Path: blob/main/crates/bevy_render/src/error_handler.rs
9321 views
use alloc::sync::Arc;1use bevy_ecs::{2resource::Resource,3world::{Mut, World},4};5use std::sync::Mutex;6use wgpu::ErrorSource;7use wgpu_types::error::ErrorType;89use crate::{10insert_future_resources,11render_resource::PipelineCache,12renderer::{RenderDevice, WgpuWrapper},13settings::RenderCreation,14FutureRenderResources, RenderStartup,15};1617/// Resource to indicate renderer behavior upon error.18pub enum RenderErrorPolicy {19/// Pretends nothing happened and continues rendering.20/// This discards the error after logging it to console.21Ignore,22/// Keeps the app alive, but stops rendering further.23/// This keeps the error state, and will continue polling the [`RenderErrorHandler`]24/// every frame until some other policy is returned.25StopRendering,26/// Attempt renderer recovery with the given [`RenderCreation`].27Recover(RenderCreation),28}2930/// Determines what [`RenderErrorPolicy`] should be used to respond to a given [`RenderError`].31///32/// The handler has access to both the main world and the render world in that order.33/// By the time this is invoked, the error has already been logged. The error is provided34/// for the decision-making reason of how to appropriately respond to it. Not all errors35/// are equally severe: validation errors may be ignored for example, while device lost errors36/// require recovery to continue rendering.37#[derive(Resource)]38pub struct RenderErrorHandler(39pub for<'a> fn(&'a RenderError, &'a mut World, &'a mut World) -> RenderErrorPolicy,40);4142impl RenderErrorHandler {43fn handle(&self, error: &RenderError, main_world: &mut World, render_world: &mut World) {44match self.0(error, main_world, render_world) {45RenderErrorPolicy::Ignore => {46// Pretend that didn't happen.47render_world.insert_resource(RenderState::Ready);48}49RenderErrorPolicy::StopRendering => {50// do nothing51}52RenderErrorPolicy::Recover(render_creation) => {53assert!(insert_future_resources(&render_creation, main_world));54render_world.insert_resource(RenderState::Reinitializing);55}56}57}58}5960impl Default for RenderErrorHandler {61fn default() -> Self {62// This is what we've always done historically,63// but we could choose a new default once recovery works better.64Self(|_, _, _| RenderErrorPolicy::Ignore)65}66}6768/// An error encountered during rendering.69#[derive(Debug)]70pub struct RenderError {71pub ty: ErrorType,72pub description: String,73pub source: Option<WgpuWrapper<ErrorSource>>,74}7576/// The current state of the renderer.77#[derive(Resource, Debug)]78pub(crate) enum RenderState {79/// Just started, [`crate::RenderStartup`] will run in this state.80Initializing,81/// Everything is okay and we are rendering stuff every frame.82Ready,83/// An error was encountered, and we may decide how to handle it.84Errored(RenderError),85/// We are recreating the render context after an error to recover.86Reinitializing,87}8889/// Resource to allow polling wgpu error handlers.90#[derive(Resource)]91pub(crate) struct DeviceErrorHandler {92device_lost: Arc<Mutex<Option<(wgpu::DeviceLostReason, String)>>>,93uncaptured: Arc<Mutex<Option<WgpuWrapper<wgpu::Error>>>>,94}9596impl DeviceErrorHandler {97/// Creates and registers error handlers on the given device and stores them to later be polled.98pub(crate) fn new(device: &RenderDevice) -> Self {99let device_lost = Arc::new(Mutex::new(None));100let uncaptured = Arc::new(Mutex::new(None));101{102// scoped clone to move into closures103let device_lost = device_lost.clone();104let uncaptured = uncaptured.clone();105let device = device.wgpu_device();106// we log errors as soon as they are captured so they stay chronological in logs107// and only keep the first error, as it often causes other errors downstream108device.set_device_lost_callback(move |reason, str| {109bevy_log::error!("Caught DeviceLost error: {reason:?} {str}");110assert!(device_lost.lock().unwrap().replace((reason, str)).is_none());111});112device.on_uncaptured_error(Arc::new(move |e| {113bevy_log::error!("Caught rendering error: {e}");114uncaptured115.lock()116.unwrap()117.get_or_insert(WgpuWrapper::new(e));118}));119}120Self {121device_lost,122uncaptured,123}124}125126/// Checks to see if any errors have been caught, and returns an appropriate `RenderState`127pub(crate) fn poll(&self) -> Option<RenderError> {128// Device lost is more important so we let it take precedence; every error gets logged anyways.129if let Some((_, description)) = self.device_lost.lock().unwrap().take() {130return Some(RenderError {131ty: ErrorType::DeviceLost,132description,133source: None,134});135}136if let Some(error) = self.uncaptured.lock().unwrap().take() {137let (ty, description, source) = match error.into_inner() {138wgpu::Error::OutOfMemory { source } => {139(ErrorType::OutOfMemory, "".to_string(), source)140}141wgpu::Error::Validation {142source,143description,144} => (ErrorType::Validation, description, source),145wgpu::Error::Internal {146source,147description,148} => (ErrorType::Internal, description, source),149};150return Some(RenderError {151ty,152description,153source: Some(WgpuWrapper::new(source)),154});155}156None157}158}159160/// Updates the state machine that handles the renderer and device lifecycle.161/// Polls the [`DeviceErrorHandler`] and fires the [`RenderErrorHandler`] if needed.162///163/// Runs [`crate::RenderStartup`] after every time a [`RenderDevice`] is acquired.164///165/// We need both the main and render world to properly handle errors, so we wedge ourselves into [extract](bevy_app::SubApp::set_extract).166pub(crate) fn update_state(main_world: &mut World, render_world: &mut World) {167if let Some(error) = render_world.resource::<DeviceErrorHandler>().poll() {168render_world.insert_resource(RenderState::Errored(error));169};170171// Remove the render state so we can provide both worlds to the `RenderErrorHandler`.172let state = render_world.remove_resource::<RenderState>().unwrap();173174match &state {175RenderState::Initializing => {176render_world.run_schedule(RenderStartup);177render_world.insert_resource(RenderState::Ready);178}179RenderState::Ready => {180// all is well181}182RenderState::Errored(error) => {183main_world.resource_scope(|main_world, error_handler: Mut<RenderErrorHandler>| {184error_handler.handle(error, main_world, render_world);185});186}187RenderState::Reinitializing => {188if let Some(render_resources) = main_world189.get_resource::<FutureRenderResources>()190.unwrap()191.clone()192.lock()193.unwrap()194.take()195{196let synchronous_pipeline_compilation = render_world197.resource::<PipelineCache>()198.synchronous_pipeline_compilation;199render_resources.unpack_into(200main_world,201render_world,202synchronous_pipeline_compilation,203);204render_world.insert_resource(RenderState::Initializing);205}206}207}208209// Put the state back if we didn't set a new one210if render_world.get_resource::<RenderState>().is_none() {211render_world.insert_resource(state);212}213}214215216