Skip to content

Commit fd5a4ea

Browse files
committed
Support recording multiple CommandBuffers in RenderContext (#7248)
# Objective `RenderContext`, the core abstraction for running the render graph, currently only supports recording one `CommandBuffer` across the entire render graph. This means the entire buffer must be recorded sequentially, usually via the render graph itself. This prevents parallelization and forces users to only encode their commands in the render graph. ## Solution Allow `RenderContext` to store a `Vec<CommandBuffer>` that it progressively appends to. By default, the context will not have a command encoder, but will create one as soon as either `begin_tracked_render_pass` or the `command_encoder` accesor is first called. `RenderContext::add_command_buffer` allows users to interrupt the current command encoder, flush it to the vec, append a user-provided `CommandBuffer` and reset the command encoder to start a new buffer. Users or the render graph will call `RenderContext::finish` to retrieve the series of buffers for submitting to the queue. This allows users to encode their own `CommandBuffer`s outside of the render graph, potentially in different threads, and store them in components or resources. Ideally, in the future, the core pipeline passes can run in `RenderStage::Render` systems and end up saving the completed command buffers to either `Commands` or a field in `RenderPhase`. ## Alternatives The alternative is to use to use wgpu's `RenderBundle`s, which can achieve similar results; however it's not universally available (no OpenGL, WebGL, and DX11). --- ## Changelog Added: `RenderContext::new` Added: `RenderContext::add_command_buffer` Added: `RenderContext::finish` Changed: `RenderContext::render_device` is now private. Use the accessor `RenderContext::render_device()` instead. Changed: `RenderContext::command_encoder` is now private. Use the accessor `RenderContext::command_encoder()` instead. Changed: `RenderContext` now supports adding external `CommandBuffer`s for inclusion into the render graphs. These buffers can be encoded outside of the render graph (i.e. in a system). ## Migration Guide `RenderContext`'s fields are now private. Use the accessors on `RenderContext` instead, and construct it with `RenderContext::new`.
1 parent ff5e4fd commit fd5a4ea

File tree

9 files changed

+72
-28
lines changed

9 files changed

+72
-28
lines changed

crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ impl Node for MainPass2dNode {
101101
};
102102

103103
render_context
104-
.command_encoder
104+
.command_encoder()
105105
.begin_render_pass(&pass_descriptor);
106106
}
107107

crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ impl Node for MainPass3dNode {
205205
};
206206

207207
render_context
208-
.command_encoder
208+
.command_encoder()
209209
.begin_render_pass(&pass_descriptor);
210210
}
211211

crates/bevy_core_pipeline/src/fxaa/node.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl Node for FxaaNode {
7878
Some((id, bind_group)) if source.id() == *id => bind_group,
7979
cached_bind_group => {
8080
let sampler = render_context
81-
.render_device
81+
.render_device()
8282
.create_sampler(&SamplerDescriptor {
8383
mipmap_filter: FilterMode::Linear,
8484
mag_filter: FilterMode::Linear,
@@ -88,7 +88,7 @@ impl Node for FxaaNode {
8888

8989
let bind_group =
9090
render_context
91-
.render_device
91+
.render_device()
9292
.create_bind_group(&BindGroupDescriptor {
9393
label: None,
9494
layout: &fxaa_pipeline.texture_bind_group,
@@ -120,7 +120,7 @@ impl Node for FxaaNode {
120120
};
121121

122122
let mut render_pass = render_context
123-
.command_encoder
123+
.command_encoder()
124124
.begin_render_pass(&pass_descriptor);
125125

126126
render_pass.set_pipeline(pipeline);

crates/bevy_core_pipeline/src/tonemapping/node.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ impl Node for TonemappingNode {
7272
Some((id, bind_group)) if source.id() == *id => bind_group,
7373
cached_bind_group => {
7474
let sampler = render_context
75-
.render_device
75+
.render_device()
7676
.create_sampler(&SamplerDescriptor::default());
7777

7878
let bind_group =
7979
render_context
80-
.render_device
80+
.render_device()
8181
.create_bind_group(&BindGroupDescriptor {
8282
label: None,
8383
layout: &tonemapping_pipeline.texture_bind_group,
@@ -112,7 +112,7 @@ impl Node for TonemappingNode {
112112
};
113113

114114
let mut render_pass = render_context
115-
.command_encoder
115+
.command_encoder()
116116
.begin_render_pass(&pass_descriptor);
117117

118118
render_pass.set_pipeline(pipeline);

crates/bevy_core_pipeline/src/upscaling/node.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ impl Node for UpscalingNode {
6363
Some((id, bind_group)) if upscaled_texture.id() == *id => bind_group,
6464
cached_bind_group => {
6565
let sampler = render_context
66-
.render_device
66+
.render_device()
6767
.create_sampler(&SamplerDescriptor::default());
6868

6969
let bind_group =
7070
render_context
71-
.render_device
71+
.render_device()
7272
.create_bind_group(&BindGroupDescriptor {
7373
label: None,
7474
layout: &upscaling_pipeline.texture_bind_group,
@@ -108,7 +108,7 @@ impl Node for UpscalingNode {
108108
};
109109

110110
let mut render_pass = render_context
111-
.command_encoder
111+
.command_encoder()
112112
.begin_render_pass(&pass_descriptor);
113113

114114
render_pass.set_pipeline(pipeline);

crates/bevy_render/src/camera/camera_driver_node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl Node for CameraDriverNode {
9898
};
9999

100100
render_context
101-
.command_encoder
101+
.command_encoder()
102102
.begin_render_pass(&pass_descriptor);
103103
}
104104

crates/bevy_render/src/renderer/graph_runner.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,12 @@ impl RenderGraphRunner {
5858
queue: &wgpu::Queue,
5959
world: &World,
6060
) -> Result<(), RenderGraphRunnerError> {
61-
let command_encoder =
62-
render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
63-
let mut render_context = RenderContext {
64-
render_device,
65-
command_encoder,
66-
};
67-
61+
let mut render_context = RenderContext::new(render_device);
6862
Self::run_graph(graph, None, &mut render_context, world, &[])?;
6963
{
7064
#[cfg(feature = "trace")]
7165
let _span = info_span!("submit_graph_commands").entered();
72-
queue.submit(vec![render_context.command_encoder.finish()]);
66+
queue.submit(render_context.finish());
7367
}
7468
Ok(())
7569
}

crates/bevy_render/src/renderer/mod.rs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ use bevy_ecs::prelude::*;
1717
use bevy_time::TimeSender;
1818
use bevy_utils::Instant;
1919
use std::sync::Arc;
20-
use wgpu::{Adapter, AdapterInfo, CommandEncoder, Instance, Queue, RequestAdapterOptions};
20+
use wgpu::{
21+
Adapter, AdapterInfo, CommandBuffer, CommandEncoder, Instance, Queue, RequestAdapterOptions,
22+
};
2123

2224
/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame.
2325
pub fn render_system(world: &mut World) {
@@ -278,20 +280,68 @@ pub async fn initialize_renderer(
278280
/// The [`RenderDevice`] is used to create render resources and the
279281
/// the [`CommandEncoder`] is used to record a series of GPU operations.
280282
pub struct RenderContext {
281-
pub render_device: RenderDevice,
282-
pub command_encoder: CommandEncoder,
283+
render_device: RenderDevice,
284+
command_encoder: Option<CommandEncoder>,
285+
command_buffers: Vec<CommandBuffer>,
283286
}
284287

285288
impl RenderContext {
289+
/// Creates a new [`RenderContext`] from a [`RenderDevice`].
290+
pub fn new(render_device: RenderDevice) -> Self {
291+
Self {
292+
render_device,
293+
command_encoder: None,
294+
command_buffers: Vec::new(),
295+
}
296+
}
297+
298+
/// Gets the underlying [`RenderDevice`].
299+
pub fn render_device(&self) -> &RenderDevice {
300+
&self.render_device
301+
}
302+
303+
/// Gets the current [`CommandEncoder`].
304+
pub fn command_encoder(&mut self) -> &mut CommandEncoder {
305+
self.command_encoder.get_or_insert_with(|| {
306+
self.render_device
307+
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default())
308+
})
309+
}
310+
286311
/// Creates a new [`TrackedRenderPass`] for the context,
287312
/// configured using the provided `descriptor`.
288313
pub fn begin_tracked_render_pass<'a>(
289314
&'a mut self,
290315
descriptor: RenderPassDescriptor<'a, '_>,
291316
) -> TrackedRenderPass<'a> {
292-
TrackedRenderPass::new(
293-
&self.render_device,
294-
self.command_encoder.begin_render_pass(&descriptor),
295-
)
317+
// Cannot use command_encoder() as we need to split the borrow on self
318+
let command_encoder = self.command_encoder.get_or_insert_with(|| {
319+
self.render_device
320+
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default())
321+
});
322+
let render_pass = command_encoder.begin_render_pass(&descriptor);
323+
TrackedRenderPass::new(&self.render_device, render_pass)
324+
}
325+
326+
/// Append a [`CommandBuffer`] to the queue.
327+
///
328+
/// If present, this will flush the currently unflushed [`CommandEncoder`]
329+
/// into a [`CommandBuffer`] into the queue before append the provided
330+
/// buffer.
331+
pub fn add_command_buffer(&mut self, command_buffer: CommandBuffer) {
332+
self.flush_encoder();
333+
self.command_buffers.push(command_buffer);
334+
}
335+
336+
/// Finalizes the queue and returns the queue of [`CommandBuffer`]s.
337+
pub fn finish(mut self) -> Vec<CommandBuffer> {
338+
self.flush_encoder();
339+
self.command_buffers
340+
}
341+
342+
fn flush_encoder(&mut self) {
343+
if let Some(encoder) = self.command_encoder.take() {
344+
self.command_buffers.push(encoder.finish());
345+
}
296346
}
297347
}

examples/shader/compute_shader_game_of_life.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ impl render_graph::Node for GameOfLifeNode {
216216
let pipeline = world.resource::<GameOfLifePipeline>();
217217

218218
let mut pass = render_context
219-
.command_encoder
219+
.command_encoder()
220220
.begin_compute_pass(&ComputePassDescriptor::default());
221221

222222
pass.set_bind_group(0, texture_bind_group, &[]);

0 commit comments

Comments
 (0)