Record bloom render commands in parallel (#18330)

Closes https://github.com/bevyengine/bevy/issues/18304.

Requesting that someone more experienced with tracy test performance on
a larger scene please!
This commit is contained in:
JMS55 2025-03-24 21:06:42 -07:00 committed by GitHub
parent 37b62b83c4
commit 8481b63ed8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,7 +2,6 @@ mod downsampling_pipeline;
mod settings;
mod upsampling_pipeline;
use bevy_color::{Gray, LinearRgba};
pub use settings::{Bloom, BloomCompositeMode, BloomPrefilter};
use crate::{
@ -11,6 +10,7 @@ use crate::{
};
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, weak_handle, Handle};
use bevy_color::{Gray, LinearRgba};
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_math::{ops, UVec2};
use bevy_render::{
@ -30,6 +30,8 @@ use downsampling_pipeline::{
prepare_downsampling_pipeline, BloomDownsamplingPipeline, BloomDownsamplingPipelineIds,
BloomUniforms,
};
#[cfg(feature = "trace")]
use tracing::info_span;
use upsampling_pipeline::{
prepare_upsampling_pipeline, BloomUpsamplingPipeline, UpsamplingPipelineIds,
};
@ -108,10 +110,10 @@ impl ViewNode for BloomNode {
// Atypically for a post-processing effect, we do not need to
// use a secondary texture normally provided by view_target.post_process_write(),
// instead we write into our own bloom texture and then directly back onto main.
fn run(
fn run<'w>(
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
render_context: &mut RenderContext<'w>,
(
camera,
view_target,
@ -121,8 +123,8 @@ impl ViewNode for BloomNode {
bloom_settings,
upsampling_pipeline_ids,
downsampling_pipeline_ids,
): QueryItem<Self::ViewQuery>,
world: &World,
): QueryItem<'w, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
if bloom_settings.intensity == 0.0 {
return Ok(());
@ -149,133 +151,152 @@ impl ViewNode for BloomNode {
return Ok(());
};
render_context.command_encoder().push_debug_group("bloom");
let view_texture = view_target.main_texture_view();
let view_texture_unsampled = view_target.get_unsampled_color_attachment();
let diagnostics = render_context.diagnostic_recorder();
let command_encoder = render_context.command_encoder();
let time_span = diagnostics.time_span(command_encoder, "bloom");
// First downsample pass
{
let downsampling_first_bind_group = render_context.render_device().create_bind_group(
"bloom_downsampling_first_bind_group",
&downsampling_pipeline_res.bind_group_layout,
&BindGroupEntries::sequential((
// Read from main texture directly
view_target.main_texture_view(),
&bind_groups.sampler,
uniforms.clone(),
)),
);
render_context.add_command_buffer_generation_task(move |render_device| {
#[cfg(feature = "trace")]
let _bloom_span = info_span!("bloom").entered();
let view = &bloom_texture.view(0);
let mut downsampling_first_pass =
render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("bloom_downsampling_first_pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view,
resolve_target: None,
ops: Operations::default(),
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
let mut command_encoder =
render_device.create_command_encoder(&CommandEncoderDescriptor {
label: Some("bloom_command_encoder"),
});
downsampling_first_pass.set_render_pipeline(downsampling_first_pipeline);
downsampling_first_pass.set_bind_group(
0,
&downsampling_first_bind_group,
&[uniform_index.index()],
);
downsampling_first_pass.draw(0..3, 0..1);
}
command_encoder.push_debug_group("bloom");
let time_span = diagnostics.time_span(&mut command_encoder, "bloom");
// Other downsample passes
for mip in 1..bloom_texture.mip_count {
let view = &bloom_texture.view(mip);
let mut downsampling_pass =
render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("bloom_downsampling_pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view,
resolve_target: None,
ops: Operations::default(),
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
downsampling_pass.set_render_pipeline(downsampling_pipeline);
downsampling_pass.set_bind_group(
0,
&bind_groups.downsampling_bind_groups[mip as usize - 1],
&[uniform_index.index()],
);
downsampling_pass.draw(0..3, 0..1);
}
// First downsample pass
{
let downsampling_first_bind_group = render_device.create_bind_group(
"bloom_downsampling_first_bind_group",
&downsampling_pipeline_res.bind_group_layout,
&BindGroupEntries::sequential((
// Read from main texture directly
view_texture,
&bind_groups.sampler,
uniforms.clone(),
)),
);
// Upsample passes except the final one
for mip in (1..bloom_texture.mip_count).rev() {
let view = &bloom_texture.view(mip - 1);
let mut upsampling_pass =
render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("bloom_upsampling_pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view,
resolve_target: None,
ops: Operations {
load: LoadOp::Load,
store: StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
upsampling_pass.set_render_pipeline(upsampling_pipeline);
upsampling_pass.set_bind_group(
0,
&bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - mip - 1) as usize],
&[uniform_index.index()],
);
let blend = compute_blend_factor(
bloom_settings,
mip as f32,
(bloom_texture.mip_count - 1) as f32,
);
upsampling_pass.set_blend_constant(LinearRgba::gray(blend));
upsampling_pass.draw(0..3, 0..1);
}
// Final upsample pass
// This is very similar to the above upsampling passes with the only difference
// being the pipeline (which itself is barely different) and the color attachment
{
let mut upsampling_final_pass =
render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("bloom_upsampling_final_pass"),
color_attachments: &[Some(view_target.get_unsampled_color_attachment())],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
upsampling_final_pass.set_render_pipeline(upsampling_final_pipeline);
upsampling_final_pass.set_bind_group(
0,
&bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize],
&[uniform_index.index()],
);
if let Some(viewport) = camera.viewport.as_ref() {
upsampling_final_pass.set_camera_viewport(viewport);
let view = &bloom_texture.view(0);
let mut downsampling_first_pass =
command_encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("bloom_downsampling_first_pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view,
resolve_target: None,
ops: Operations::default(),
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
downsampling_first_pass.set_pipeline(downsampling_first_pipeline);
downsampling_first_pass.set_bind_group(
0,
&downsampling_first_bind_group,
&[uniform_index.index()],
);
downsampling_first_pass.draw(0..3, 0..1);
}
let blend =
compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32);
upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend));
upsampling_final_pass.draw(0..3, 0..1);
}
time_span.end(render_context.command_encoder());
render_context.command_encoder().pop_debug_group();
// Other downsample passes
for mip in 1..bloom_texture.mip_count {
let view = &bloom_texture.view(mip);
let mut downsampling_pass =
command_encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("bloom_downsampling_pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view,
resolve_target: None,
ops: Operations::default(),
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
downsampling_pass.set_pipeline(downsampling_pipeline);
downsampling_pass.set_bind_group(
0,
&bind_groups.downsampling_bind_groups[mip as usize - 1],
&[uniform_index.index()],
);
downsampling_pass.draw(0..3, 0..1);
}
// Upsample passes except the final one
for mip in (1..bloom_texture.mip_count).rev() {
let view = &bloom_texture.view(mip - 1);
let mut upsampling_pass =
command_encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("bloom_upsampling_pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view,
resolve_target: None,
ops: Operations {
load: LoadOp::Load,
store: StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
upsampling_pass.set_pipeline(upsampling_pipeline);
upsampling_pass.set_bind_group(
0,
&bind_groups.upsampling_bind_groups
[(bloom_texture.mip_count - mip - 1) as usize],
&[uniform_index.index()],
);
let blend = compute_blend_factor(
bloom_settings,
mip as f32,
(bloom_texture.mip_count - 1) as f32,
);
upsampling_pass.set_blend_constant(LinearRgba::gray(blend).into());
upsampling_pass.draw(0..3, 0..1);
}
// Final upsample pass
// This is very similar to the above upsampling passes with the only difference
// being the pipeline (which itself is barely different) and the color attachment
{
let mut upsampling_final_pass =
command_encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("bloom_upsampling_final_pass"),
color_attachments: &[Some(view_texture_unsampled)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
upsampling_final_pass.set_pipeline(upsampling_final_pipeline);
upsampling_final_pass.set_bind_group(
0,
&bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize],
&[uniform_index.index()],
);
if let Some(viewport) = camera.viewport.as_ref() {
upsampling_final_pass.set_viewport(
viewport.physical_position.x as f32,
viewport.physical_position.y as f32,
viewport.physical_size.x as f32,
viewport.physical_size.y as f32,
viewport.depth.start,
viewport.depth.end,
);
}
let blend =
compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32);
upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend).into());
upsampling_final_pass.draw(0..3, 0..1);
}
time_span.end(&mut command_encoder);
command_encoder.pop_debug_group();
command_encoder.finish()
});
Ok(())
}