MONKEY (polish): per-view post uniforms written at prepare, not allocated per frame
Colour grading and sun shafts built a fresh `UniformBuffer` (a new GPU buffer) inside their render-graph node every frame. Each view now owns one small UNIFORM | COPY_DST buffer, created the first frame the effect is on and rewritten in place with `RenderQueue::write_buffer` in `RenderSystems::PrepareResources`, the same shape as bloom's prepare-time uniform. The node only binds it. The pass still gates on the extracted view component, so the kept buffer never runs a disabled pass. Verified: cargo check -p benilla-app; rustfmt clean on both files. No GPU here; built on Linux only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6yUWgPosSakvL23gtgvpH
This commit is contained in:
@@ -25,7 +25,7 @@ use bevy::{
|
||||
renderer::{RenderContext, RenderDevice, RenderQueue},
|
||||
texture::GpuImage,
|
||||
view::ViewTarget,
|
||||
RenderApp, RenderStartup,
|
||||
Render, RenderApp, RenderStartup, RenderSystems,
|
||||
},
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -73,6 +73,10 @@ impl Plugin for GradingPlugin {
|
||||
render_app
|
||||
.insert_resource(GradeShader(shader))
|
||||
.add_systems(RenderStartup, init_pipeline)
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_uniforms.in_set(RenderSystems::PrepareResources),
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<GradeNode>>(Core3d, GradeLabel)
|
||||
.add_render_graph_edges(
|
||||
Core3d,
|
||||
@@ -344,17 +348,47 @@ fn init_pipeline(
|
||||
});
|
||||
}
|
||||
|
||||
/// MONKEY (polish): the view's uniform, created once and rewritten in prepare, like bloom's.
|
||||
#[derive(Component)]
|
||||
struct GradeUniformBuffer(Buffer);
|
||||
|
||||
fn prepare_uniforms(
|
||||
mut commands: Commands,
|
||||
device: Res<RenderDevice>,
|
||||
queue: Res<RenderQueue>,
|
||||
views: Query<(Entity, &GradeView, Option<&GradeUniformBuffer>)>,
|
||||
) {
|
||||
for (entity, grade, buffer) in &views {
|
||||
let rows = [grade.control.to_array()];
|
||||
match buffer {
|
||||
Some(buffer) => queue.write_buffer(&buffer.0, 0, bytemuck::cast_slice(&rows)),
|
||||
None => {
|
||||
let buffer = device.create_buffer_with_data(&BufferInitDescriptor {
|
||||
label: Some("post_grading_uniform"),
|
||||
contents: bytemuck::cast_slice(&rows),
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
});
|
||||
commands.entity(entity).insert(GradeUniformBuffer(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct GradeNode;
|
||||
|
||||
impl ViewNode for GradeNode {
|
||||
type ViewQuery = (&'static ViewTarget, &'static GradeView);
|
||||
type ViewQuery = (
|
||||
&'static ViewTarget,
|
||||
&'static GradeView,
|
||||
&'static GradeUniformBuffer,
|
||||
);
|
||||
|
||||
fn run<'w>(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
context: &mut RenderContext<'w>,
|
||||
(target, grade): QueryItem<'w, '_, Self::ViewQuery>,
|
||||
(target, grade, uniform): QueryItem<'w, '_, Self::ViewQuery>,
|
||||
world: &'w World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let settings = world.resource::<GradePipeline>();
|
||||
@@ -367,10 +401,6 @@ impl ViewNode for GradeNode {
|
||||
return Ok(());
|
||||
};
|
||||
let device = context.render_device();
|
||||
let mut uniform = UniformBuffer::from(GradeUniform {
|
||||
control: grade.control,
|
||||
});
|
||||
uniform.write_buffer(device, world.resource::<RenderQueue>());
|
||||
let out = target.post_process_write();
|
||||
let bind = device.create_bind_group(
|
||||
"post_grading",
|
||||
@@ -381,7 +411,7 @@ impl ViewNode for GradeNode {
|
||||
&day.texture_view,
|
||||
&night.texture_view,
|
||||
&settings.lut_sampler,
|
||||
uniform.binding().unwrap(),
|
||||
uniform.0.as_entire_binding(),
|
||||
)),
|
||||
);
|
||||
let diagnostics = context.diagnostic_recorder();
|
||||
|
||||
@@ -165,6 +165,10 @@ const MASK_FORMAT: TextureFormat = TextureFormat::R8Unorm;
|
||||
#[derive(Component)]
|
||||
struct ShaftMask(CachedTexture);
|
||||
|
||||
/// MONKEY (polish): the view's uniform, created once and rewritten in prepare, like bloom's.
|
||||
#[derive(Component)]
|
||||
struct ShaftUniform(Buffer);
|
||||
|
||||
#[derive(Component)]
|
||||
struct ViewShaftPipeline(CachedRenderPipelineId);
|
||||
|
||||
@@ -277,17 +281,31 @@ fn prepare_pipelines(
|
||||
}
|
||||
}
|
||||
|
||||
/// The half-size mask target, rounded up so an odd edge column still has a texel.
|
||||
/// The half-size mask target, rounded up so an odd edge column still has a texel, and the
|
||||
/// view's uniform, written in place.
|
||||
fn prepare_masks(
|
||||
mut commands: Commands,
|
||||
mut textures: ResMut<TextureCache>,
|
||||
device: Res<RenderDevice>,
|
||||
views: Query<(Entity, &ExtractedCamera), With<ShaftView>>,
|
||||
queue: Res<RenderQueue>,
|
||||
views: Query<(Entity, &ExtractedCamera, &ShaftView, Option<&ShaftUniform>)>,
|
||||
) {
|
||||
for (entity, camera) in &views {
|
||||
for (entity, camera, shaft, uniform) in &views {
|
||||
let Some(size) = camera.physical_viewport_size else {
|
||||
continue;
|
||||
};
|
||||
let rows = [shaft.sun.to_array(), shaft.color.to_array()];
|
||||
match uniform {
|
||||
Some(uniform) => queue.write_buffer(&uniform.0, 0, bytemuck::cast_slice(&rows)),
|
||||
None => {
|
||||
let buffer = device.create_buffer_with_data(&BufferInitDescriptor {
|
||||
label: Some("post_sun_shafts_uniform"),
|
||||
contents: bytemuck::cast_slice(&rows),
|
||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||
});
|
||||
commands.entity(entity).insert(ShaftUniform(buffer));
|
||||
}
|
||||
}
|
||||
let mask = textures.get(
|
||||
&device,
|
||||
TextureDescriptor {
|
||||
@@ -316,16 +334,18 @@ impl ViewNode for ShaftNode {
|
||||
type ViewQuery = (
|
||||
&'static ViewTarget,
|
||||
&'static ViewDepthTexture,
|
||||
// Gates the pass: the mask and uniform outlive a disabled frame.
|
||||
&'static ShaftView,
|
||||
&'static ViewShaftPipeline,
|
||||
&'static ShaftMask,
|
||||
&'static ShaftUniform,
|
||||
);
|
||||
|
||||
fn run<'w>(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
context: &mut RenderContext<'w>,
|
||||
(target, depth, shaft, id, mask): QueryItem<'w, '_, Self::ViewQuery>,
|
||||
(target, depth, _, id, mask, uniform): QueryItem<'w, '_, Self::ViewQuery>,
|
||||
world: &'w World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let cache = world.resource::<PipelineCache>();
|
||||
@@ -350,8 +370,6 @@ impl ViewNode for ShaftNode {
|
||||
&cache.get_bind_group_layout(&settings.mask_layouts[multisampled]),
|
||||
&BindGroupEntries::single(depth.view()),
|
||||
);
|
||||
let mut uniform = UniformBuffer::from(*shaft);
|
||||
uniform.write_buffer(device, world.resource::<RenderQueue>());
|
||||
let out = target.post_process_write();
|
||||
let bind = device.create_bind_group(
|
||||
"post_sun_shafts",
|
||||
@@ -360,7 +378,7 @@ impl ViewNode for ShaftNode {
|
||||
out.source,
|
||||
&settings.sampler,
|
||||
&mask.0.default_view,
|
||||
uniform.binding().unwrap(),
|
||||
uniform.0.as_entire_binding(),
|
||||
)),
|
||||
);
|
||||
let diagnostics = context.diagnostic_recorder();
|
||||
|
||||
Reference in New Issue
Block a user