Reduce branching in TrackedRenderPass (#7053)

# Objective
Speed up the render phase for rendering.

## Solution
 - Follow up #6988 and make the internals of atomic IDs `NonZeroU32`. This niches the `Option`s of the IDs in draw state, which reduces the size and branching behavior when evaluating for equality.
 - Require `&RenderDevice` to get the device's `Limits` when initializing a `TrackedRenderPass` to preallocate the bind groups and vertex buffer state in `DrawState`, this removes the branch on needing to resize those `Vec`s.

## Performance
This produces a similar speed up akin to that of #6885. This shows an approximate 6% speed up in `main_opaque_pass_3d` on `many_foxes` (408.79 us -> 388us). This should be orthogonal to the gains seen there.

![image](https://user-images.githubusercontent.com/3137680/209906239-e430f026-63c2-4b95-957e-a2045b810d79.png)

---

## Changelog
Added: `RenderContext::begin_tracked_render_pass`.
Changed: `TrackedRenderPass` now requires a `&RenderDevice` on construction.
Removed: `bevy_render::render_phase::DrawState`. It was not usable in any form outside of `bevy_render`.

## Migration Guide
TODO
This commit is contained in:
James Liu 2023-01-09 19:24:56 +00:00
parent d76b53bf4d
commit bef9bc1844
9 changed files with 105 additions and 122 deletions

View File

@ -17,7 +17,6 @@ use bevy_render::{
}, },
prelude::Camera, prelude::Camera,
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType},
render_phase::TrackedRenderPass,
render_resource::*, render_resource::*,
renderer::{RenderContext, RenderDevice}, renderer::{RenderContext, RenderDevice},
texture::{CachedTexture, TextureCache}, texture::{CachedTexture, TextureCache},
@ -232,17 +231,15 @@ impl Node for BloomNode {
{ {
let view = &BloomTextures::texture_view(&textures.texture_a, 0); let view = &BloomTextures::texture_view(&textures.texture_a, 0);
let mut prefilter_pass = let mut prefilter_pass =
TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( render_context.begin_tracked_render_pass(RenderPassDescriptor {
&RenderPassDescriptor { label: Some("bloom_prefilter_pass"),
label: Some("bloom_prefilter_pass"), color_attachments: &[Some(RenderPassColorAttachment {
color_attachments: &[Some(RenderPassColorAttachment { view,
view, resolve_target: None,
resolve_target: None, ops: Operations::default(),
ops: Operations::default(), })],
})], depth_stencil_attachment: None,
depth_stencil_attachment: None, });
},
));
prefilter_pass.set_render_pipeline(downsampling_prefilter_pipeline); prefilter_pass.set_render_pipeline(downsampling_prefilter_pipeline);
prefilter_pass.set_bind_group( prefilter_pass.set_bind_group(
0, 0,
@ -258,17 +255,15 @@ impl Node for BloomNode {
for mip in 1..textures.mip_count { for mip in 1..textures.mip_count {
let view = &BloomTextures::texture_view(&textures.texture_a, mip); let view = &BloomTextures::texture_view(&textures.texture_a, mip);
let mut downsampling_pass = let mut downsampling_pass =
TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( render_context.begin_tracked_render_pass(RenderPassDescriptor {
&RenderPassDescriptor { label: Some("bloom_downsampling_pass"),
label: Some("bloom_downsampling_pass"), color_attachments: &[Some(RenderPassColorAttachment {
color_attachments: &[Some(RenderPassColorAttachment { view,
view, resolve_target: None,
resolve_target: None, ops: Operations::default(),
ops: Operations::default(), })],
})], depth_stencil_attachment: None,
depth_stencil_attachment: None, });
},
));
downsampling_pass.set_render_pipeline(downsampling_pipeline); downsampling_pass.set_render_pipeline(downsampling_pipeline);
downsampling_pass.set_bind_group( downsampling_pass.set_bind_group(
0, 0,
@ -284,17 +279,15 @@ impl Node for BloomNode {
for mip in (1..textures.mip_count).rev() { for mip in (1..textures.mip_count).rev() {
let view = &BloomTextures::texture_view(&textures.texture_b, mip - 1); let view = &BloomTextures::texture_view(&textures.texture_b, mip - 1);
let mut upsampling_pass = let mut upsampling_pass =
TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( render_context.begin_tracked_render_pass(RenderPassDescriptor {
&RenderPassDescriptor { label: Some("bloom_upsampling_pass"),
label: Some("bloom_upsampling_pass"), color_attachments: &[Some(RenderPassColorAttachment {
color_attachments: &[Some(RenderPassColorAttachment { view,
view, resolve_target: None,
resolve_target: None, ops: Operations::default(),
ops: Operations::default(), })],
})], depth_stencil_attachment: None,
depth_stencil_attachment: None, });
},
));
upsampling_pass.set_render_pipeline(upsampling_pipeline); upsampling_pass.set_render_pipeline(upsampling_pipeline);
upsampling_pass.set_bind_group( upsampling_pass.set_bind_group(
0, 0,
@ -309,18 +302,16 @@ impl Node for BloomNode {
{ {
let mut upsampling_final_pass = let mut upsampling_final_pass =
TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( render_context.begin_tracked_render_pass(RenderPassDescriptor {
&RenderPassDescriptor { label: Some("bloom_upsampling_final_pass"),
label: Some("bloom_upsampling_final_pass"), color_attachments: &[Some(view_target.get_unsampled_color_attachment(
color_attachments: &[Some(view_target.get_unsampled_color_attachment( Operations {
Operations { load: LoadOp::Load,
load: LoadOp::Load, store: true,
store: true, },
}, ))],
))], depth_stencil_attachment: None,
depth_stencil_attachment: None, });
},
));
upsampling_final_pass.set_render_pipeline(upsampling_final_pipeline); upsampling_final_pass.set_render_pipeline(upsampling_final_pipeline);
upsampling_final_pass.set_bind_group( upsampling_final_pass.set_bind_group(
0, 0,

View File

@ -3,7 +3,6 @@ use crate::{
core_2d::{camera_2d::Camera2d, Transparent2d}, core_2d::{camera_2d::Camera2d, Transparent2d},
}; };
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_render::render_phase::TrackedRenderPass;
use bevy_render::{ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
@ -63,7 +62,8 @@ impl Node for MainPass2dNode {
{ {
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _main_pass_2d = info_span!("main_pass_2d").entered(); let _main_pass_2d = info_span!("main_pass_2d").entered();
let pass_descriptor = RenderPassDescriptor {
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("main_pass_2d"), label: Some("main_pass_2d"),
color_attachments: &[Some(target.get_color_attachment(Operations { color_attachments: &[Some(target.get_color_attachment(Operations {
load: match camera_2d.clear_color { load: match camera_2d.clear_color {
@ -76,12 +76,7 @@ impl Node for MainPass2dNode {
store: true, store: true,
}))], }))],
depth_stencil_attachment: None, depth_stencil_attachment: None,
}; });
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut render_pass = TrackedRenderPass::new(render_pass);
if let Some(viewport) = camera.viewport.as_ref() { if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport); render_pass.set_camera_viewport(viewport);

View File

@ -3,7 +3,6 @@ use crate::{
core_3d::{AlphaMask3d, Camera3d, Opaque3d, Transparent3d}, core_3d::{AlphaMask3d, Camera3d, Opaque3d, Transparent3d},
}; };
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_render::render_phase::TrackedRenderPass;
use bevy_render::{ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
@ -70,7 +69,8 @@ impl Node for MainPass3dNode {
// NOTE: Scoped to drop the mutable borrow of render_context // NOTE: Scoped to drop the mutable borrow of render_context
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _main_opaque_pass_3d_span = info_span!("main_opaque_pass_3d").entered(); let _main_opaque_pass_3d_span = info_span!("main_opaque_pass_3d").entered();
let pass_descriptor = RenderPassDescriptor {
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("main_opaque_pass_3d"), label: Some("main_opaque_pass_3d"),
// NOTE: The opaque pass loads the color // NOTE: The opaque pass loads the color
// buffer as well as writing to it. // buffer as well as writing to it.
@ -94,12 +94,7 @@ impl Node for MainPass3dNode {
}), }),
stencil_ops: None, stencil_ops: None,
}), }),
}; });
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut render_pass = TrackedRenderPass::new(render_pass);
if let Some(viewport) = camera.viewport.as_ref() { if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport); render_pass.set_camera_viewport(viewport);
@ -113,7 +108,8 @@ impl Node for MainPass3dNode {
// NOTE: Scoped to drop the mutable borrow of render_context // NOTE: Scoped to drop the mutable borrow of render_context
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _main_alpha_mask_pass_3d_span = info_span!("main_alpha_mask_pass_3d").entered(); let _main_alpha_mask_pass_3d_span = info_span!("main_alpha_mask_pass_3d").entered();
let pass_descriptor = RenderPassDescriptor {
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("main_alpha_mask_pass_3d"), label: Some("main_alpha_mask_pass_3d"),
// NOTE: The alpha_mask pass loads the color buffer as well as overwriting it where appropriate. // NOTE: The alpha_mask pass loads the color buffer as well as overwriting it where appropriate.
color_attachments: &[Some(target.get_color_attachment(Operations { color_attachments: &[Some(target.get_color_attachment(Operations {
@ -129,12 +125,7 @@ impl Node for MainPass3dNode {
}), }),
stencil_ops: None, stencil_ops: None,
}), }),
}; });
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut render_pass = TrackedRenderPass::new(render_pass);
if let Some(viewport) = camera.viewport.as_ref() { if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport); render_pass.set_camera_viewport(viewport);
@ -148,7 +139,8 @@ impl Node for MainPass3dNode {
// NOTE: Scoped to drop the mutable borrow of render_context // NOTE: Scoped to drop the mutable borrow of render_context
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _main_transparent_pass_3d_span = info_span!("main_transparent_pass_3d").entered(); let _main_transparent_pass_3d_span = info_span!("main_transparent_pass_3d").entered();
let pass_descriptor = RenderPassDescriptor {
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("main_transparent_pass_3d"), label: Some("main_transparent_pass_3d"),
// NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate. // NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate.
color_attachments: &[Some(target.get_color_attachment(Operations { color_attachments: &[Some(target.get_color_attachment(Operations {
@ -169,12 +161,7 @@ impl Node for MainPass3dNode {
}), }),
stencil_ops: None, stencil_ops: None,
}), }),
}; });
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut render_pass = TrackedRenderPass::new(render_pass);
if let Some(viewport) = camera.viewport.as_ref() { if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport); render_pass.set_camera_viewport(viewport);

View File

@ -1770,23 +1770,19 @@ impl Node for ShadowPassNode {
continue; continue;
} }
let pass_descriptor = RenderPassDescriptor { let mut render_pass =
label: Some(&view_light.pass_name), render_context.begin_tracked_render_pass(RenderPassDescriptor {
color_attachments: &[], label: Some(&view_light.pass_name),
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { color_attachments: &[],
view: &view_light.depth_texture_view, depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
depth_ops: Some(Operations { view: &view_light.depth_texture_view,
load: LoadOp::Clear(0.0), depth_ops: Some(Operations {
store: true, load: LoadOp::Clear(0.0),
store: true,
}),
stencil_ops: None,
}), }),
stencil_ops: None, });
}),
};
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut render_pass = TrackedRenderPass::new(render_pass);
shadow_phase.render(&mut render_pass, world, view_light_entity); shadow_phase.render(&mut render_pass, world, view_light_entity);
} }

View File

@ -50,6 +50,7 @@ image = { version = "0.24", default-features = false }
# misc # misc
wgpu = { version = "0.14.0", features = ["spirv"] } wgpu = { version = "0.14.0", features = ["spirv"] }
wgpu-hal = "0.14.1"
codespan-reporting = "0.11.0" codespan-reporting = "0.11.0"
naga = { version = "0.10.0", features = ["glsl-in", "spv-in", "spv-out", "wgsl-in", "wgsl-out"] } naga = { version = "0.10.0", features = ["glsl-in", "spv-in", "spv-out", "wgsl-in", "wgsl-out"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

View File

@ -5,14 +5,16 @@ use crate::{
BindGroup, BindGroupId, Buffer, BufferId, BufferSlice, RenderPipeline, RenderPipelineId, BindGroup, BindGroupId, Buffer, BufferId, BufferSlice, RenderPipeline, RenderPipelineId,
ShaderStages, ShaderStages,
}, },
renderer::RenderDevice,
}; };
use bevy_utils::tracing::trace; use bevy_utils::{default, tracing::trace};
use std::ops::Range; use std::ops::Range;
use wgpu::{IndexFormat, RenderPass}; use wgpu::{IndexFormat, RenderPass};
use wgpu_hal::{MAX_BIND_GROUPS, MAX_VERTEX_BUFFERS};
/// Tracks the current [`TrackedRenderPass`] state to ensure draw calls are valid. /// Tracks the current [`TrackedRenderPass`] state to ensure draw calls are valid.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct DrawState { struct DrawState {
pipeline: Option<RenderPipelineId>, pipeline: Option<RenderPipelineId>,
bind_groups: Vec<(Option<BindGroupId>, Vec<u32>)>, bind_groups: Vec<(Option<BindGroupId>, Vec<u32>)>,
vertex_buffers: Vec<Option<(BufferId, u64)>>, vertex_buffers: Vec<Option<(BufferId, u64)>>,
@ -26,12 +28,10 @@ impl DrawState {
bind_group: BindGroupId, bind_group: BindGroupId,
dynamic_indices: &[u32], dynamic_indices: &[u32],
) { ) {
if index >= self.bind_groups.len() { let group = &mut self.bind_groups[index];
self.bind_groups.resize(index + 1, (None, Vec::new())); group.0 = Some(bind_group);
} group.1.clear();
self.bind_groups[index].0 = Some(bind_group); group.1.extend(dynamic_indices);
self.bind_groups[index].1.clear();
self.bind_groups[index].1.extend(dynamic_indices);
} }
pub fn is_bind_group_set( pub fn is_bind_group_set(
@ -48,9 +48,6 @@ impl DrawState {
} }
pub fn set_vertex_buffer(&mut self, index: usize, buffer: BufferId, offset: u64) { pub fn set_vertex_buffer(&mut self, index: usize, buffer: BufferId, offset: u64) {
if index >= self.vertex_buffers.len() {
self.vertex_buffers.resize(index + 1, None);
}
self.vertex_buffers[index] = Some((buffer, offset)); self.vertex_buffers[index] = Some((buffer, offset));
} }
@ -98,9 +95,16 @@ pub struct TrackedRenderPass<'a> {
impl<'a> TrackedRenderPass<'a> { impl<'a> TrackedRenderPass<'a> {
/// Tracks the supplied render pass. /// Tracks the supplied render pass.
pub fn new(pass: RenderPass<'a>) -> Self { pub fn new(device: &RenderDevice, pass: RenderPass<'a>) -> Self {
let limits = device.limits();
let max_bind_groups = limits.max_bind_groups as usize;
let max_vertex_buffers = limits.max_vertex_buffers as usize;
Self { Self {
state: DrawState::default(), state: DrawState {
bind_groups: vec![(None, Vec::new()); max_bind_groups.min(MAX_BIND_GROUPS)],
vertex_buffers: vec![None; max_vertex_buffers.min(MAX_VERTEX_BUFFERS)],
..default()
},
pass, pass,
} }
} }

View File

@ -124,7 +124,7 @@ macro_rules! render_resource_wrapper {
macro_rules! define_atomic_id { macro_rules! define_atomic_id {
($atomic_id_type:ident) => { ($atomic_id_type:ident) => {
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct $atomic_id_type(u32); pub struct $atomic_id_type(core::num::NonZeroU32);
// We use new instead of default to indicate that each ID created will be unique. // We use new instead of default to indicate that each ID created will be unique.
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
@ -134,15 +134,13 @@ macro_rules! define_atomic_id {
static COUNTER: AtomicU32 = AtomicU32::new(1); static COUNTER: AtomicU32 = AtomicU32::new(1);
match COUNTER.fetch_add(1, Ordering::Relaxed) { let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
0 => { Self(core::num::NonZeroU32::new(counter).unwrap_or_else(|| {
panic!( panic!(
"The system ran out of unique `{}`s.", "The system ran out of unique `{}`s.",
stringify!($atomic_id_type) stringify!($atomic_id_type)
); );
} }))
id => Self(id),
}
} }
} }
}; };

View File

@ -8,6 +8,8 @@ pub use render_device::*;
use crate::{ use crate::{
render_graph::RenderGraph, render_graph::RenderGraph,
render_phase::TrackedRenderPass,
render_resource::RenderPassDescriptor,
settings::{WgpuSettings, WgpuSettingsPriority}, settings::{WgpuSettings, WgpuSettingsPriority},
view::{ExtractedWindows, ViewTarget}, view::{ExtractedWindows, ViewTarget},
}; };
@ -279,3 +281,17 @@ pub struct RenderContext {
pub render_device: RenderDevice, pub render_device: RenderDevice,
pub command_encoder: CommandEncoder, pub command_encoder: CommandEncoder,
} }
impl RenderContext {
/// Creates a new [`TrackedRenderPass`] for the context,
/// configured using the provided `descriptor`.
pub fn begin_tracked_render_pass<'a>(
&'a mut self,
descriptor: RenderPassDescriptor<'a, '_>,
) -> TrackedRenderPass<'a> {
TrackedRenderPass::new(
&self.render_device,
self.command_encoder.begin_render_pass(&descriptor),
)
}
}

View File

@ -76,19 +76,14 @@ impl Node for UiPassNode {
} else { } else {
input_view_entity input_view_entity
}; };
let pass_descriptor = RenderPassDescriptor { let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("ui_pass"), label: Some("ui_pass"),
color_attachments: &[Some(target.get_unsampled_color_attachment(Operations { color_attachments: &[Some(target.get_unsampled_color_attachment(Operations {
load: LoadOp::Load, load: LoadOp::Load,
store: true, store: true,
}))], }))],
depth_stencil_attachment: None, depth_stencil_attachment: None,
}; });
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut render_pass = TrackedRenderPass::new(render_pass);
transparent_phase.render(&mut render_pass, world, view_entity); transparent_phase.render(&mut render_pass, world, view_entity);