Skip to content

Commit 3faca1e

Browse files
Don't ignore draw errors (#13240)
# Objective - It's possible to have errors in a draw command, but these errors are ignored ## Solution - Return a result with the error ## Changelog Renamed `RenderCommandResult::Failure` to `RenderCommandResult::Skip` Added a `reason` string parameter to `RenderCommandResult::Failure` ## Migration Guide If you were using `RenderCommandResult::Failure` to just ignore an error and retry later, use `RenderCommandResult::Skip` instead. This wasn't intentional, but this PR should also help with #12660 since we can turn a few unwraps into error messages now. --------- Co-authored-by: Charlotte McElwain <[email protected]>
1 parent ee88d79 commit 3faca1e

File tree

19 files changed

+170
-82
lines changed

19 files changed

+170
-82
lines changed

crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl ViewNode for MainTransparentPass2dNode {
5858
}
5959

6060
if !transparent_phase.items.is_empty() {
61-
transparent_phase.render(&mut render_pass, world, view_entity);
61+
transparent_phase.render(&mut render_pass, world, view_entity)?;
6262
}
6363

6464
pass_span.end(&mut render_pass);

crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use bevy_render::{
1212
renderer::RenderContext,
1313
view::{ViewDepthTexture, ViewTarget, ViewUniformOffset},
1414
};
15+
use bevy_utils::tracing::error;
1516
#[cfg(feature = "trace")]
1617
use bevy_utils::tracing::info_span;
1718

@@ -95,14 +96,18 @@ impl ViewNode for MainOpaquePass3dNode {
9596
if !opaque_phase.is_empty() {
9697
#[cfg(feature = "trace")]
9798
let _opaque_main_pass_3d_span = info_span!("opaque_main_pass_3d").entered();
98-
opaque_phase.render(&mut render_pass, world, view_entity);
99+
if let Err(err) = opaque_phase.render(&mut render_pass, world, view_entity) {
100+
error!("Error encountered while rendering the opaque phase {err:?}");
101+
}
99102
}
100103

101104
// Alpha draws
102105
if !alpha_mask_phase.is_empty() {
103106
#[cfg(feature = "trace")]
104107
let _alpha_mask_main_pass_3d_span = info_span!("alpha_mask_main_pass_3d").entered();
105-
alpha_mask_phase.render(&mut render_pass, world, view_entity);
108+
if let Err(err) = alpha_mask_phase.render(&mut render_pass, world, view_entity) {
109+
error!("Error encountered while rendering the alpha mask phase {err:?}");
110+
}
106111
}
107112

108113
// Skybox draw using a fullscreen triangle

crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use bevy_render::{
99
renderer::RenderContext,
1010
view::{ViewDepthTexture, ViewTarget},
1111
};
12+
use bevy_utils::tracing::error;
1213
#[cfg(feature = "trace")]
1314
use bevy_utils::tracing::info_span;
1415
use std::ops::Range;
@@ -98,7 +99,11 @@ impl ViewNode for MainTransmissivePass3dNode {
9899
}
99100

100101
// render items in range
101-
transmissive_phase.render_range(&mut render_pass, world, view_entity, range);
102+
if let Err(err) =
103+
transmissive_phase.render_range(&mut render_pass, world, view_entity, range)
104+
{
105+
error!("Error encountered while rendering the transmissive phase {err:?}");
106+
}
102107
}
103108
} else {
104109
let mut render_pass =
@@ -108,7 +113,9 @@ impl ViewNode for MainTransmissivePass3dNode {
108113
render_pass.set_camera_viewport(viewport);
109114
}
110115

111-
transmissive_phase.render(&mut render_pass, world, view_entity);
116+
if let Err(err) = transmissive_phase.render(&mut render_pass, world, view_entity) {
117+
error!("Error encountered while rendering the transmissive phase {err:?}");
118+
}
112119
}
113120
}
114121

crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use bevy_render::{
99
renderer::RenderContext,
1010
view::{ViewDepthTexture, ViewTarget},
1111
};
12+
use bevy_utils::tracing::error;
1213
#[cfg(feature = "trace")]
1314
use bevy_utils::tracing::info_span;
1415

@@ -70,7 +71,9 @@ impl ViewNode for MainTransparentPass3dNode {
7071
render_pass.set_camera_viewport(viewport);
7172
}
7273

73-
transparent_phase.render(&mut render_pass, world, view_entity);
74+
if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) {
75+
error!("Error encountered while rendering the transparent phase {err:?}");
76+
}
7477

7578
pass_span.end(&mut render_pass);
7679
}

crates/bevy_core_pipeline/src/deferred/node.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use bevy_render::{
1111
renderer::RenderContext,
1212
view::ViewDepthTexture,
1313
};
14+
use bevy_utils::tracing::error;
1415
#[cfg(feature = "trace")]
1516
use bevy_utils::tracing::info_span;
1617

@@ -149,14 +150,23 @@ impl ViewNode for DeferredGBufferPrepassNode {
149150
{
150151
#[cfg(feature = "trace")]
151152
let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered();
152-
opaque_deferred_phase.render(&mut render_pass, world, view_entity);
153+
if let Err(err) = opaque_deferred_phase.render(&mut render_pass, world, view_entity)
154+
{
155+
error!("Error encountered while rendering the opaque deferred phase {err:?}");
156+
}
153157
}
154158

155159
// Alpha masked draws
156160
if !alpha_mask_deferred_phase.is_empty() {
157161
#[cfg(feature = "trace")]
158162
let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred_prepass").entered();
159-
alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity);
163+
if let Err(err) =
164+
alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity)
165+
{
166+
error!(
167+
"Error encountered while rendering the alpha mask deferred phase {err:?}"
168+
);
169+
}
160170
}
161171

162172
drop(render_pass);

crates/bevy_core_pipeline/src/prepass/node.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use bevy_render::{
99
renderer::RenderContext,
1010
view::{ViewDepthTexture, ViewUniformOffset},
1111
};
12+
use bevy_utils::tracing::error;
1213
#[cfg(feature = "trace")]
1314
use bevy_utils::tracing::info_span;
1415

@@ -125,14 +126,23 @@ impl ViewNode for PrepassNode {
125126
{
126127
#[cfg(feature = "trace")]
127128
let _opaque_prepass_span = info_span!("opaque_prepass").entered();
128-
opaque_prepass_phase.render(&mut render_pass, world, view_entity);
129+
if let Err(err) = opaque_prepass_phase.render(&mut render_pass, world, view_entity)
130+
{
131+
error!("Error encountered while rendering the opaque prepass phase {err:?}");
132+
}
129133
}
130134

131135
// Alpha masked draws
132136
if !alpha_mask_prepass_phase.is_empty() {
133137
#[cfg(feature = "trace")]
134138
let _alpha_mask_prepass_span = info_span!("alpha_mask_prepass").entered();
135-
alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity);
139+
if let Err(err) =
140+
alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity)
141+
{
142+
error!(
143+
"Error encountered while rendering the alpha mask prepass phase {err:?}"
144+
);
145+
}
136146
}
137147

138148
// Skybox draw using a fullscreen triangle

crates/bevy_gizmos/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetLineGizmoBindGroup<I>
540540
pass: &mut TrackedRenderPass<'w>,
541541
) -> RenderCommandResult {
542542
let Some(uniform_index) = uniform_index else {
543-
return RenderCommandResult::Failure;
543+
return RenderCommandResult::Skip;
544544
};
545545
pass.set_bind_group(
546546
I,
@@ -566,10 +566,10 @@ impl<P: PhaseItem> RenderCommand<P> for DrawLineGizmo {
566566
pass: &mut TrackedRenderPass<'w>,
567567
) -> RenderCommandResult {
568568
let Some(handle) = handle else {
569-
return RenderCommandResult::Failure;
569+
return RenderCommandResult::Skip;
570570
};
571571
let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else {
572-
return RenderCommandResult::Failure;
572+
return RenderCommandResult::Skip;
573573
};
574574

575575
if line_gizmo.vertex_count < 2 {
@@ -612,10 +612,10 @@ impl<P: PhaseItem> RenderCommand<P> for DrawLineJointGizmo {
612612
pass: &mut TrackedRenderPass<'w>,
613613
) -> RenderCommandResult {
614614
let Some(handle) = handle else {
615-
return RenderCommandResult::Failure;
615+
return RenderCommandResult::Skip;
616616
};
617617
let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else {
618-
return RenderCommandResult::Failure;
618+
return RenderCommandResult::Skip;
619619
};
620620

621621
if line_gizmo.vertex_count <= 2 || !line_gizmo.strip {

crates/bevy_pbr/src/material.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,10 +456,10 @@ impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterial
456456
let material_instances = material_instances.into_inner();
457457

458458
let Some(material_asset_id) = material_instances.get(&item.entity()) else {
459-
return RenderCommandResult::Failure;
459+
return RenderCommandResult::Skip;
460460
};
461461
let Some(material) = materials.get(*material_asset_id) else {
462-
return RenderCommandResult::Failure;
462+
return RenderCommandResult::Skip;
463463
};
464464
pass.set_bind_group(I, &material.bind_group, &[]);
465465
RenderCommandResult::Success

crates/bevy_pbr/src/render/light.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1445,7 +1445,11 @@ impl Node for ShadowPassNode {
14451445
let pass_span =
14461446
diagnostics.pass_span(&mut render_pass, view_light.pass_name.clone());
14471447

1448-
shadow_phase.render(&mut render_pass, world, view_light_entity);
1448+
if let Err(err) =
1449+
shadow_phase.render(&mut render_pass, world, view_light_entity)
1450+
{
1451+
error!("Error encountered while rendering the shadow phase {err:?}");
1452+
}
14491453

14501454
pass_span.end(&mut render_pass);
14511455
drop(render_pass);

crates/bevy_pbr/src/render/mesh.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2261,12 +2261,11 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
22612261
is_morphed,
22622262
has_motion_vector_prepass,
22632263
) else {
2264-
error!(
2264+
return RenderCommandResult::Failure(
22652265
"The MeshBindGroups resource wasn't set in the render phase. \
22662266
It should be set by the prepare_mesh_bind_group system.\n\
2267-
This is a bevy bug! Please open an issue."
2267+
This is a bevy bug! Please open an issue.",
22682268
);
2269-
return RenderCommandResult::Failure;
22702269
};
22712270

22722271
let mut dynamic_offsets: [u32; 3] = Default::default();
@@ -2349,7 +2348,7 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
23492348
if !has_preprocess_bind_group
23502349
|| !preprocess_pipelines.pipelines_are_loaded(&pipeline_cache)
23512350
{
2352-
return RenderCommandResult::Failure;
2351+
return RenderCommandResult::Skip;
23532352
}
23542353
}
23552354

@@ -2359,13 +2358,13 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
23592358
let mesh_allocator = mesh_allocator.into_inner();
23602359

23612360
let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(item.entity()) else {
2362-
return RenderCommandResult::Failure;
2361+
return RenderCommandResult::Skip;
23632362
};
23642363
let Some(gpu_mesh) = meshes.get(mesh_asset_id) else {
2365-
return RenderCommandResult::Failure;
2364+
return RenderCommandResult::Skip;
23662365
};
23672366
let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_asset_id) else {
2368-
return RenderCommandResult::Failure;
2367+
return RenderCommandResult::Skip;
23692368
};
23702369

23712370
// Calculate the indirect offset, and look up the buffer.
@@ -2374,7 +2373,7 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
23742373
Some(index) => match indirect_parameters_buffer.buffer() {
23752374
None => {
23762375
warn!("Not rendering mesh because indirect parameters buffer wasn't present");
2377-
return RenderCommandResult::Failure;
2376+
return RenderCommandResult::Skip;
23782377
}
23792378
Some(buffer) => Some((
23802379
index as u64 * mem::size_of::<IndirectParameters>() as u64,
@@ -2395,7 +2394,7 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
23952394
} => {
23962395
let Some(index_buffer_slice) = mesh_allocator.mesh_index_slice(&mesh_asset_id)
23972396
else {
2398-
return RenderCommandResult::Failure;
2397+
return RenderCommandResult::Skip;
23992398
};
24002399

24012400
pass.set_index_buffer(index_buffer_slice.buffer.slice(..), 0, *index_format);

crates/bevy_render/src/render_graph/node.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{
33
Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError,
44
RunSubGraphError, SlotInfo, SlotInfos,
55
},
6+
render_phase::DrawError,
67
renderer::RenderContext,
78
};
89
pub use bevy_ecs::label::DynEq;
@@ -97,6 +98,8 @@ pub enum NodeRunError {
9798
OutputSlotError(#[from] OutputSlotError),
9899
#[error("encountered an error when running a sub-graph")]
99100
RunSubGraphError(#[from] RunSubGraphError),
101+
#[error("encountered an error when executing draw command")]
102+
DrawError(#[from] DrawError),
100103
}
101104

102105
/// A collection of input and output [`Edges`](Edge) for a [`Node`].

crates/bevy_render/src/render_phase/draw.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::render_phase::{PhaseItem, TrackedRenderPass};
22
use bevy_app::{App, SubApp};
33
use bevy_ecs::{
44
entity::Entity,
5-
query::{QueryState, ROQueryItem, ReadOnlyQueryData},
5+
query::{QueryEntityError, QueryState, ROQueryItem, ReadOnlyQueryData},
66
system::{ReadOnlySystemParam, Resource, SystemParam, SystemParamItem, SystemState},
77
world::World,
88
};
@@ -13,6 +13,7 @@ use std::{
1313
hash::Hash,
1414
sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
1515
};
16+
use thiserror::Error;
1617

1718
/// A draw function used to draw [`PhaseItem`]s.
1819
///
@@ -34,7 +35,17 @@ pub trait Draw<P: PhaseItem>: Send + Sync + 'static {
3435
pass: &mut TrackedRenderPass<'w>,
3536
view: Entity,
3637
item: &P,
37-
);
38+
) -> Result<(), DrawError>;
39+
}
40+
41+
#[derive(Error, Debug, PartialEq, Eq)]
42+
pub enum DrawError {
43+
#[error("Failed to execute render command {0:?}")]
44+
RenderCommandFailure(&'static str),
45+
#[error("Failed to get execute view query")]
46+
InvalidViewQuery,
47+
#[error("View entity not found")]
48+
ViewEntityNotFound,
3849
}
3950

4051
// TODO: make this generic?
@@ -212,7 +223,8 @@ pub trait RenderCommand<P: PhaseItem> {
212223
#[derive(Debug)]
213224
pub enum RenderCommandResult {
214225
Success,
215-
Failure,
226+
Skip,
227+
Failure(&'static str),
216228
}
217229

218230
macro_rules! render_command_tuple_impl {
@@ -232,14 +244,22 @@ macro_rules! render_command_tuple_impl {
232244
) -> RenderCommandResult {
233245
match maybe_entities {
234246
None => {
235-
$(if let RenderCommandResult::Failure = $name::render(_item, $view, None, $name, _pass) {
236-
return RenderCommandResult::Failure;
237-
})*
247+
$(
248+
match $name::render(_item, $view, None, $name, _pass) {
249+
RenderCommandResult::Skip => return RenderCommandResult::Skip,
250+
RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason),
251+
_ => {},
252+
}
253+
)*
238254
}
239255
Some(($($entity,)*)) => {
240-
$(if let RenderCommandResult::Failure = $name::render(_item, $view, Some($entity), $name, _pass) {
241-
return RenderCommandResult::Failure;
242-
})*
256+
$(
257+
match $name::render(_item, $view, Some($entity), $name, _pass) {
258+
RenderCommandResult::Skip => return RenderCommandResult::Skip,
259+
RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason),
260+
_ => {},
261+
}
262+
)*
243263
}
244264
}
245265
RenderCommandResult::Success
@@ -290,12 +310,23 @@ where
290310
pass: &mut TrackedRenderPass<'w>,
291311
view: Entity,
292312
item: &P,
293-
) {
313+
) -> Result<(), DrawError> {
294314
let param = self.state.get_manual(world);
295-
let view = self.view.get_manual(world, view).unwrap();
315+
let view = match self.view.get_manual(world, view) {
316+
Ok(view) => view,
317+
Err(err) => match err {
318+
QueryEntityError::NoSuchEntity(_) => return Err(DrawError::ViewEntityNotFound),
319+
QueryEntityError::QueryDoesNotMatch(_) | QueryEntityError::AliasedMutability(_) => {
320+
return Err(DrawError::InvalidViewQuery)
321+
}
322+
},
323+
};
324+
296325
let entity = self.entity.get_manual(world, item.entity()).ok();
297-
// TODO: handle/log `RenderCommand` failure
298-
C::render(item, view, entity, param, pass);
326+
match C::render(item, view, entity, param, pass) {
327+
RenderCommandResult::Success | RenderCommandResult::Skip => Ok(()),
328+
RenderCommandResult::Failure(reason) => Err(DrawError::RenderCommandFailure(reason)),
329+
}
299330
}
300331
}
301332

0 commit comments

Comments
 (0)