Skip to main content

bevy_core_pipeline/tonemapping/
node.rs

1use crate::tonemapping::{TonemappingLuts, TonemappingPipeline, ViewTonemappingPipeline};
2
3use bevy_ecs::prelude::*;
4use bevy_render::{
5    camera::ExtractedCamera,
6    diagnostic::RecordDiagnostics,
7    render_asset::RenderAssets,
8    render_resource::{
9        BindGroup, BindGroupEntries, BufferId, LoadOp, Operations, PipelineCache,
10        RenderPassColorAttachment, RenderPassDescriptor, StoreOp, TextureViewId,
11    },
12    renderer::{RenderContext, ViewQuery},
13    texture::{FallbackImage, GpuImage},
14    view::{ViewTarget, ViewUniformOffset, ViewUniforms},
15};
16
17use super::{get_lut_bindings, Tonemapping};
18
19/// Cached bind group state for tonemapping.
20#[derive(Default)]
21pub struct TonemappingBindGroupCache {
22    cached: Option<(BufferId, TextureViewId, TextureViewId, BindGroup)>,
23    last_tonemapping: Option<Tonemapping>,
24}
25
26pub fn tonemapping(
27    view: ViewQuery<(
28        &ExtractedCamera,
29        &ViewUniformOffset,
30        &ViewTarget,
31        &ViewTonemappingPipeline,
32        &Tonemapping,
33    )>,
34    pipeline_cache: Res<PipelineCache>,
35    tonemapping_pipeline: Res<TonemappingPipeline>,
36    gpu_images: Res<RenderAssets<GpuImage>>,
37    fallback_image: Res<FallbackImage>,
38    view_uniforms: Res<ViewUniforms>,
39    tonemapping_luts: Res<TonemappingLuts>,
40    mut cache: Local<TonemappingBindGroupCache>,
41    mut ctx: RenderContext,
42) {
43    let (camera, view_uniform_offset, target, view_tonemapping_pipeline, tonemapping) =
44        view.into_inner();
45
46    if *tonemapping == Tonemapping::None {
47        return;
48    }
49
50    if !camera.hdr {
51        return;
52    }
53
54    let Some(pipeline) = pipeline_cache.get_render_pipeline(view_tonemapping_pipeline.0) else {
55        return;
56    };
57
58    let view_uniforms_buffer = &view_uniforms.uniforms;
59    let view_uniforms_id = view_uniforms_buffer.buffer().unwrap().id();
60
61    let post_process = target.post_process_write();
62    let source = post_process.source;
63    let destination = post_process.destination;
64
65    let tonemapping_changed = cache.last_tonemapping != Some(*tonemapping);
66    if tonemapping_changed {
67        cache.last_tonemapping = Some(*tonemapping);
68    }
69
70    let bind_group = match &mut cache.cached {
71        Some((buffer_id, texture_id, lut_id, bind_group))
72            if view_uniforms_id == *buffer_id
73                && source.id() == *texture_id
74                && *lut_id != fallback_image.d3.texture_view.id()
75                && !tonemapping_changed =>
76        {
77            bind_group
78        }
79        cached => {
80            let lut_bindings =
81                get_lut_bindings(&gpu_images, &tonemapping_luts, tonemapping, &fallback_image);
82
83            let bind_group = ctx.render_device().create_bind_group(
84                None,
85                &pipeline_cache.get_bind_group_layout(&tonemapping_pipeline.texture_bind_group),
86                &BindGroupEntries::sequential((
87                    view_uniforms_buffer,
88                    source,
89                    &tonemapping_pipeline.sampler,
90                    lut_bindings.0,
91                    lut_bindings.1,
92                )),
93            );
94
95            let (_, _, _, bind_group) = cached.insert((
96                view_uniforms_id,
97                source.id(),
98                lut_bindings.0.id(),
99                bind_group,
100            ));
101            bind_group
102        }
103    };
104
105    let pass_descriptor = RenderPassDescriptor {
106        label: Some("tonemapping"),
107        color_attachments: &[Some(RenderPassColorAttachment {
108            view: destination,
109            depth_slice: None,
110            resolve_target: None,
111            ops: Operations {
112                load: LoadOp::Clear(Default::default()), // TODO shouldn't need to be cleared
113                store: StoreOp::Store,
114            },
115        })],
116        depth_stencil_attachment: None,
117        timestamp_writes: None,
118        occlusion_query_set: None,
119        multiview_mask: None,
120    };
121
122    let diagnostics = ctx.diagnostic_recorder();
123    let diagnostics = diagnostics.as_deref();
124    let time_span = diagnostics.time_span(ctx.command_encoder(), "tonemapping");
125
126    {
127        let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor);
128
129        render_pass.set_pipeline(pipeline);
130        render_pass.set_bind_group(0, bind_group, &[view_uniform_offset.offset]);
131        render_pass.draw(0..3, 0..1);
132    }
133
134    time_span.end(ctx.command_encoder());
135}