use alloc::sync::Arc;
use bevy_app::AppExit;
use bevy_ecs::{
resource::Resource,
world::{Mut, World},
};
use std::sync::Mutex;
use wgpu::ErrorSource;
pub use wgpu_types::error::ErrorType;
use crate::{
insert_future_resources,
render_resource::PipelineCache,
renderer::{RenderDevice, WgpuWrapper},
settings::RenderCreation,
FutureRenderResources, RenderStartup,
};
pub enum RenderErrorPolicy {
Ignore,
StopRendering,
Recover(RenderCreation),
}
#[derive(Resource)]
pub struct RenderErrorHandler(
pub for<'a> fn(&'a RenderError, &'a mut World, &'a mut World) -> RenderErrorPolicy,
);
impl RenderErrorHandler {
fn handle(&self, error: &RenderError, main_world: &mut World, render_world: &mut World) {
match self.0(error, main_world, render_world) {
RenderErrorPolicy::Ignore => {
render_world.insert_resource(RenderState::Ready);
}
RenderErrorPolicy::StopRendering => {
}
RenderErrorPolicy::Recover(render_creation) => {
assert!(insert_future_resources(&render_creation, main_world));
render_world.insert_resource(RenderState::Reinitializing);
}
}
}
}
impl Default for RenderErrorHandler {
fn default() -> Self {
Self(|error, main_world, _| {
bevy_log::error!("Quitting the application due to {:?} RenderError", error.ty);
main_world.write_message(AppExit::error());
RenderErrorPolicy::StopRendering
})
}
}
#[derive(Debug)]
pub struct RenderError {
pub ty: ErrorType,
pub description: String,
pub source: Option<WgpuWrapper<ErrorSource>>,
}
#[derive(Resource, Debug)]
pub(crate) enum RenderState {
Initializing,
Ready,
Errored(RenderError),
Reinitializing,
}
#[derive(Resource)]
pub(crate) struct DeviceErrorHandler {
device_lost: Arc<Mutex<Option<(wgpu::DeviceLostReason, String)>>>,
uncaptured: Arc<Mutex<Option<WgpuWrapper<wgpu::Error>>>>,
}
impl DeviceErrorHandler {
pub(crate) fn new(device: &RenderDevice) -> Self {
let device_lost = Arc::new(Mutex::new(None));
let uncaptured = Arc::new(Mutex::new(None));
{
let device_lost = device_lost.clone();
let uncaptured = uncaptured.clone();
let device = device.wgpu_device();
device.set_device_lost_callback(move |reason, str| {
bevy_log::error!("Caught DeviceLost error: {reason:?} {str}");
assert!(device_lost.lock().unwrap().replace((reason, str)).is_none());
});
device.on_uncaptured_error(Arc::new(move |e| {
bevy_log::error!("Caught rendering error: {e}");
uncaptured
.lock()
.unwrap()
.get_or_insert(WgpuWrapper::new(e));
}));
}
Self {
device_lost,
uncaptured,
}
}
pub(crate) fn poll(&self) -> Option<RenderError> {
if let Some((_, description)) = self.device_lost.lock().unwrap().take() {
return Some(RenderError {
ty: ErrorType::DeviceLost,
description,
source: None,
});
}
if let Some(error) = self.uncaptured.lock().unwrap().take() {
let (ty, description, source) = match error.into_inner() {
wgpu::Error::OutOfMemory { source } => {
(ErrorType::OutOfMemory, "".to_string(), source)
}
wgpu::Error::Validation {
source,
description,
} => (ErrorType::Validation, description, source),
wgpu::Error::Internal {
source,
description,
} => (ErrorType::Internal, description, source),
};
return Some(RenderError {
ty,
description,
source: Some(WgpuWrapper::new(source)),
});
}
None
}
}
pub(crate) fn update_state(main_world: &mut World, render_world: &mut World) {
if let Some(error) = render_world.resource::<DeviceErrorHandler>().poll() {
render_world.insert_resource(RenderState::Errored(error));
};
let state = render_world.remove_resource::<RenderState>().unwrap();
match &state {
RenderState::Initializing => {
render_world.run_schedule(RenderStartup);
render_world.insert_resource(RenderState::Ready);
}
RenderState::Ready => {
}
RenderState::Errored(error) => {
main_world.resource_scope(|main_world, error_handler: Mut<RenderErrorHandler>| {
error_handler.handle(error, main_world, render_world);
});
}
RenderState::Reinitializing => {
if let Some(render_resources) = main_world
.get_resource::<FutureRenderResources>()
.unwrap()
.clone()
.lock()
.unwrap()
.take()
{
let synchronous_pipeline_compilation = render_world
.resource::<PipelineCache>()
.synchronous_pipeline_compilation;
render_resources.unpack_into(
main_world,
render_world,
synchronous_pipeline_compilation,
);
render_world.insert_resource(RenderState::Initializing);
}
}
}
if render_world.get_resource::<RenderState>().is_none() {
render_world.insert_resource(state);
}
}