bevy_core_pipeline/auto_exposure/
mod.rs

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