bevy_post_process/auto_exposure/
mod.rs

1use bevy_app::prelude::*;
2use bevy_asset::{embedded_asset, AssetApp, Assets, Handle};
3use bevy_ecs::prelude::*;
4use bevy_render::{
5    extract_component::ExtractComponentPlugin,
6    render_asset::RenderAssetPlugin,
7    render_graph::RenderGraphExt,
8    render_resource::{
9        Buffer, BufferDescriptor, BufferUsages, PipelineCache, SpecializedComputePipelines,
10    },
11    renderer::RenderDevice,
12    ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,
13};
14
15mod buffers;
16mod compensation_curve;
17mod node;
18mod pipeline;
19mod settings;
20
21use buffers::{extract_buffers, prepare_buffers, AutoExposureBuffers};
22pub use compensation_curve::{AutoExposureCompensationCurve, AutoExposureCompensationCurveError};
23use node::AutoExposureNode;
24use pipeline::{AutoExposurePass, AutoExposurePipeline, ViewAutoExposurePipeline};
25pub use settings::AutoExposure;
26
27use crate::auto_exposure::{
28    compensation_curve::GpuAutoExposureCompensationCurve, pipeline::init_auto_exposure_pipeline,
29};
30use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
31
32/// Plugin for the auto exposure feature.
33///
34/// See [`AutoExposure`] for more details.
35pub struct AutoExposurePlugin;
36
37#[derive(Resource)]
38struct AutoExposureResources {
39    histogram: Buffer,
40}
41
42impl Plugin for AutoExposurePlugin {
43    fn build(&self, app: &mut App) {
44        embedded_asset!(app, "auto_exposure.wgsl");
45
46        app.add_plugins(RenderAssetPlugin::<GpuAutoExposureCompensationCurve>::default())
47            .init_asset::<AutoExposureCompensationCurve>()
48            .register_asset_reflect::<AutoExposureCompensationCurve>();
49        app.world_mut()
50            .resource_mut::<Assets<AutoExposureCompensationCurve>>()
51            .insert(&Handle::default(), AutoExposureCompensationCurve::default())
52            .unwrap();
53
54        app.add_plugins(ExtractComponentPlugin::<AutoExposure>::default());
55
56        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
57            return;
58        };
59
60        render_app
61            .init_resource::<SpecializedComputePipelines<AutoExposurePipeline>>()
62            .init_resource::<AutoExposureBuffers>()
63            .add_systems(
64                RenderStartup,
65                (init_auto_exposure_pipeline, init_auto_exposure_resources),
66            )
67            .add_systems(ExtractSchedule, extract_buffers)
68            .add_systems(
69                Render,
70                (
71                    prepare_buffers.in_set(RenderSystems::Prepare),
72                    queue_view_auto_exposure_pipelines.in_set(RenderSystems::Queue),
73                ),
74            )
75            .add_render_graph_node::<AutoExposureNode>(Core3d, node::AutoExposure)
76            .add_render_graph_edges(
77                Core3d,
78                (
79                    Node3d::StartMainPassPostProcessing,
80                    node::AutoExposure,
81                    Node3d::Tonemapping,
82                ),
83            );
84    }
85}
86
87pub fn init_auto_exposure_resources(mut commands: Commands, render_device: Res<RenderDevice>) {
88    commands.insert_resource(AutoExposureResources {
89        histogram: render_device.create_buffer(&BufferDescriptor {
90            label: Some("histogram buffer"),
91            size: pipeline::HISTOGRAM_BIN_COUNT * 4,
92            usage: BufferUsages::STORAGE,
93            mapped_at_creation: false,
94        }),
95    });
96}
97
98fn queue_view_auto_exposure_pipelines(
99    mut commands: Commands,
100    pipeline_cache: Res<PipelineCache>,
101    mut compute_pipelines: ResMut<SpecializedComputePipelines<AutoExposurePipeline>>,
102    pipeline: Res<AutoExposurePipeline>,
103    view_targets: Query<(Entity, &AutoExposure)>,
104) {
105    for (entity, auto_exposure) in view_targets.iter() {
106        let histogram_pipeline =
107            compute_pipelines.specialize(&pipeline_cache, &pipeline, AutoExposurePass::Histogram);
108        let average_pipeline =
109            compute_pipelines.specialize(&pipeline_cache, &pipeline, AutoExposurePass::Average);
110
111        commands.entity(entity).insert(ViewAutoExposurePipeline {
112            histogram_pipeline,
113            mean_luminance_pipeline: average_pipeline,
114            compensation_curve: auto_exposure.compensation_curve.clone(),
115            metering_mask: auto_exposure.metering_mask.clone(),
116        });
117    }
118}