Skip to main content

wde_pbr/deferred/lights/
lights_binding.rs

1use wde_logger::prelude::*;
2
3use bevy::{ecs::system::SystemParamItem, prelude::*};
4use wde_renderer::prelude::*;
5
6use crate::deferred::lights::*;
7use crate::prelude::{AtmosphereParamsUniform, AtmosphereSettings, PbrParamsUniform, PbrSettings};
8
9/// Maximum number of lights in the scene.
10pub const MAX_LIGHTS: usize = 64;
11
12pub(crate) struct LightsBindingPlugin;
13impl Plugin for LightsBindingPlugin {
14    fn build(&self, app: &mut App) {
15        // Add the render bindings
16        app.init_resource::<AtmosphereSettings>()
17            .register_type::<AtmosphereSettings>()
18            .init_resource::<PbrSettings>()
19            .register_type::<PbrSettings>()
20            .add_plugins(RenderDataRegisterPlugin::<LightsData>::default());
21
22        // Add the systems to extract the lights from the world, and to update the lights buffer
23        app.get_sub_app_mut(RenderApp)
24            .unwrap()
25            .init_resource::<ExtractedLights>()
26            .init_resource::<AtmosphereParamsUniform>()
27            .init_resource::<PbrParamsUniform>()
28            .add_systems(Extract, extract)
29            .add_systems(Extract, extract_atmosphere_params)
30            .add_systems(Extract, extract_pbr_params)
31            .add_systems(Render, update_lights_buffer.in_set(RenderSet::Prepare));
32    }
33}
34
35#[derive(Asset, Clone, TypePath, Default)]
36pub struct LightsData;
37impl LightsData {
38    pub const LIGHTS_BUFFER_IDX: u32 = 0;
39    pub const LIGHTS_BUFFER_STAGING_IDX: u32 = 1;
40    pub const ATMOSPHERE_PARAMS_BUFFER_IDX: u32 = 2;
41    pub const PBR_PARAMS_BUFFER_IDX: u32 = 3;
42}
43impl RenderData for LightsData {
44    type Params = ();
45
46    fn describe(_params: &mut SystemParamItem<Self::Params>, builder: &mut RenderDataBuilder) {
47        builder
48            .add_buffer(
49                Self::LIGHTS_BUFFER_IDX,
50                Buffer {
51                    label: "lights-buffer-gpu".to_string(),
52                    size: std::mem::size_of::<LightsStorageElement>() * MAX_LIGHTS,
53                    usage: BufferUsage::STORAGE | BufferUsage::COPY_DST,
54                    content: None
55                }
56            )
57            .add_buffer(
58                Self::LIGHTS_BUFFER_STAGING_IDX,
59                Buffer {
60                    label: "lights-buffer-staging".to_string(),
61                    size: std::mem::size_of::<LightsStorageElement>() * MAX_LIGHTS,
62                    usage: BufferUsage::COPY_SRC | BufferUsage::COPY_DST,
63                    content: None
64                }
65            )
66            .add_buffer(
67                Self::ATMOSPHERE_PARAMS_BUFFER_IDX,
68                Buffer {
69                    label: "atmosphere-params".to_string(),
70                    size: std::mem::size_of::<AtmosphereParamsUniform>(),
71                    usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
72                    content: Some(
73                        bytemuck::cast_slice(&[AtmosphereParamsUniform::default()]).to_vec()
74                    )
75                }
76            )
77            .add_buffer(
78                Self::PBR_PARAMS_BUFFER_IDX,
79                Buffer {
80                    label: "pbr-params".to_string(),
81                    size: std::mem::size_of::<PbrParamsUniform>(),
82                    usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
83                    content: Some(bytemuck::cast_slice(&[PbrParamsUniform::default()]).to_vec())
84                }
85            );
86    }
87}
88
89#[derive(Resource, Default)]
90struct ExtractedLights {
91    pub directional_lights: Vec<DirectionalLight>,
92    pub point_lights: Vec<PointLight>,
93    pub spot_lights: Vec<SpotLight>
94}
95
96fn extract(
97    lights_directional: ExtractWorld<Query<&DirectionalLight>>,
98    lights_point: ExtractWorld<Query<&PointLight>>,
99    lights_spot: ExtractWorld<Query<&SpotLight>>,
100    mut extracted_lights: ResMut<ExtractedLights>
101) {
102    // Extract lights each frame. This is necessary to keep the lights buffer up to date, and to handle dynamic lights.
103    extracted_lights.directional_lights = lights_directional.iter().copied().collect();
104    extracted_lights.point_lights = lights_point.iter().copied().collect();
105    extracted_lights.spot_lights = lights_spot.iter().copied().collect();
106}
107
108fn extract_atmosphere_params(
109    settings: ExtractWorld<Res<AtmosphereSettings>>,
110    time: ExtractWorld<Res<Time>>,
111    mut uniform: ResMut<AtmosphereParamsUniform>
112) {
113    let day_length = settings.day_length_seconds.max(1.0);
114    let time_of_day = if settings.animate_time {
115        (time.elapsed_secs() / day_length).fract()
116    } else {
117        settings.time_of_day.fract()
118    };
119    *uniform = AtmosphereParamsUniform::from_settings(&settings, time_of_day);
120}
121
122fn extract_pbr_params(
123    settings: ExtractWorld<Res<PbrSettings>>,
124    mut uniform: ResMut<PbrParamsUniform>
125) {
126    *uniform = PbrParamsUniform::from_settings(&settings);
127}
128
129fn update_lights_buffer(
130    lights_buffer: ResRenderData<LightsData>,
131    buffers: Res<RenderAssets<GpuBuffer>>,
132    render_instance: Res<RenderInstance>,
133    extracted_lights: Res<ExtractedLights>,
134    atmosphere_uniform: Res<AtmosphereParamsUniform>,
135    pbr_uniform: Res<PbrParamsUniform>
136) {
137    // Get the lights buffer
138    let lights_buffer_cpu = match lights_buffer.iter().next() {
139        Some((_, buffer)) => match buffers.get(
140            &buffer
141                .get_buffer(LightsData::LIGHTS_BUFFER_STAGING_IDX)
142                .unwrap()
143        ) {
144            Some(lights_buffer) => lights_buffer,
145            None => return
146        },
147        None => return
148    };
149
150    let render_instance = render_instance.0.read().unwrap();
151    let mut offset = 0;
152    for light in extracted_lights.directional_lights.iter() {
153        let data = LightsStorageElement::from_directional(light);
154        lights_buffer_cpu.buffer.write(
155            &render_instance,
156            bytemuck::bytes_of(&data),
157            offset * std::mem::size_of::<LightsStorageElement>()
158        );
159        offset += 1;
160    }
161    for light in extracted_lights.point_lights.iter() {
162        let data = LightsStorageElement::from_point(light);
163        lights_buffer_cpu.buffer.write(
164            &render_instance,
165            bytemuck::bytes_of(&data),
166            offset * std::mem::size_of::<LightsStorageElement>()
167        );
168        offset += 1;
169    }
170    for light in extracted_lights.spot_lights.iter() {
171        let data = LightsStorageElement::from_spot(light);
172        lights_buffer_cpu.buffer.write(
173            &render_instance,
174            bytemuck::bytes_of(&data),
175            offset * std::mem::size_of::<LightsStorageElement>()
176        );
177        offset += 1;
178    }
179    if offset > MAX_LIGHTS {
180        warn!(
181            "Number of lights exceeded the maximum of {}. Some lights will be ignored in rendering.",
182            MAX_LIGHTS
183        );
184    }
185
186    // Update the buffer
187    let lights_buffer_gpu = match buffers.get(
188        &lights_buffer
189            .iter()
190            .next()
191            .unwrap()
192            .1
193            .get_buffer(LightsData::LIGHTS_BUFFER_IDX)
194            .unwrap()
195    ) {
196        Some(buffer) => buffer,
197        None => return
198    };
199    lights_buffer_gpu
200        .buffer
201        .copy_from_buffer(&render_instance, &lights_buffer_cpu.buffer);
202
203    // Update atmosphere uniform buffer stored in the same render data as lights.
204    let atmosphere_buffer = match buffers.get(
205        &lights_buffer
206            .iter()
207            .next()
208            .unwrap()
209            .1
210            .get_buffer(LightsData::ATMOSPHERE_PARAMS_BUFFER_IDX)
211            .unwrap()
212    ) {
213        Some(buffer) => buffer,
214        None => return
215    };
216    atmosphere_buffer.buffer.write(
217        &render_instance,
218        bytemuck::cast_slice(&[*atmosphere_uniform]),
219        0
220    );
221
222    let pbr_buffer = match buffers.get(
223        &lights_buffer
224            .iter()
225            .next()
226            .unwrap()
227            .1
228            .get_buffer(LightsData::PBR_PARAMS_BUFFER_IDX)
229            .unwrap()
230    ) {
231        Some(buffer) => buffer,
232        None => return
233    };
234    pbr_buffer
235        .buffer
236        .write(&render_instance, bytemuck::cast_slice(&[*pbr_uniform]), 0);
237}