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