1use core::sync::atomic::AtomicUsize;
10use std::sync::Arc;
11
12use craballoc::{
13 slab::SlabBuffer,
14 value::{GpuArrayContainer, Hybrid, HybridArrayContainer, IsContainer},
15};
16use crabslab::Id;
17use glam::{UVec2, UVec3};
18
19use crate::{
20 bindgroup::ManagedBindGroup,
21 light::{
22 shader::{LightTile, LightTilingDescriptor},
23 Lighting,
24 },
25 stage::Stage,
26};
27
28pub struct LightTiling<Ct: IsContainer = GpuArrayContainer> {
36 pub(crate) tiling_descriptor: Hybrid<LightTilingDescriptor>,
37 tiles: Ct::Container<LightTile>,
40 depth_texture_id: Arc<AtomicUsize>,
44
45 bindgroup: ManagedBindGroup,
46 bindgroup_layout: Arc<wgpu::BindGroupLayout>,
47 bindgroup_creation_time: Arc<AtomicUsize>,
48
49 clear_tiles_pipeline: Arc<wgpu::ComputePipeline>,
50 compute_min_max_depth_pipeline: Arc<wgpu::ComputePipeline>,
51 compute_bins_pipeline: Arc<wgpu::ComputePipeline>,
52}
53
54const LABEL: Option<&'static str> = Some("light-tiling");
55
56impl<Ct: IsContainer> LightTiling<Ct> {
57 fn create_bindgroup_layout(device: &wgpu::Device, multisampled: bool) -> wgpu::BindGroupLayout {
58 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
59 label: LABEL,
60 entries: &[
61 wgpu::BindGroupLayoutEntry {
63 binding: 0,
64 visibility: wgpu::ShaderStages::COMPUTE,
65 ty: wgpu::BindingType::Buffer {
66 ty: wgpu::BufferBindingType::Storage { read_only: true },
67 has_dynamic_offset: false,
68 min_binding_size: None,
69 },
70 count: None,
71 },
72 wgpu::BindGroupLayoutEntry {
74 binding: 1,
75 visibility: wgpu::ShaderStages::COMPUTE,
76 ty: wgpu::BindingType::Buffer {
77 ty: wgpu::BufferBindingType::Storage { read_only: false },
78 has_dynamic_offset: false,
79 min_binding_size: None,
80 },
81 count: None,
82 },
83 wgpu::BindGroupLayoutEntry {
85 binding: 2,
86 visibility: wgpu::ShaderStages::COMPUTE,
87 ty: wgpu::BindingType::Texture {
88 sample_type: wgpu::TextureSampleType::Depth,
89 view_dimension: wgpu::TextureViewDimension::D2,
90 multisampled,
91 },
92 count: None,
93 },
94 ],
95 })
96 }
97
98 fn create_clear_tiles_pipeline(
99 device: &wgpu::Device,
100 multisampled: bool,
101 ) -> wgpu::ComputePipeline {
102 const LABEL: Option<&'static str> = Some("light-tiling-clear-tiles");
103 let module = crate::linkage::light_tiling_clear_tiles::linkage(device);
104 let (pipeline_layout, _) = Self::create_layouts(device, multisampled);
105 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
106 label: LABEL,
107 layout: Some(&pipeline_layout),
108 module: &module.module,
109 entry_point: Some(module.entry_point),
110 compilation_options: wgpu::PipelineCompilationOptions::default(),
111 cache: None,
112 })
113 }
114
115 fn create_compute_min_max_depth_pipeline(
116 device: &wgpu::Device,
117 multisampled: bool,
118 ) -> wgpu::ComputePipeline {
119 const LABEL: Option<&'static str> = Some("light-tiling-compute-min-max-depth");
120 let module = crate::linkage::light_tiling_compute_tile_min_and_max_depth::linkage(device);
121 let (pipeline_layout, _) = Self::create_layouts(device, multisampled);
122 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
123 label: LABEL,
124 layout: Some(&pipeline_layout),
125 module: &module.module,
126 entry_point: Some(module.entry_point),
127 compilation_options: wgpu::PipelineCompilationOptions::default(),
128 cache: None,
129 })
130 }
131
132 fn create_compute_bins_pipeline(
133 device: &wgpu::Device,
134 multisampled: bool,
135 ) -> wgpu::ComputePipeline {
136 const LABEL: Option<&'static str> = Some("light-tiling-compute-bins");
137 let module = crate::linkage::light_tiling_bin_lights::linkage(device);
138 let (pipeline_layout, _) = Self::create_layouts(device, multisampled);
139 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
140 label: LABEL,
141 layout: Some(&pipeline_layout),
142 module: &module.module,
143 entry_point: Some(module.entry_point),
144 compilation_options: wgpu::PipelineCompilationOptions::default(),
145 cache: None,
146 })
147 }
148
149 fn create_layouts(
151 device: &wgpu::Device,
152 multisampled: bool,
153 ) -> (wgpu::PipelineLayout, wgpu::BindGroupLayout) {
154 let bindgroup_layout = Self::create_bindgroup_layout(device, multisampled);
155 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
156 label: LABEL,
157 bind_group_layouts: &[&bindgroup_layout],
158 push_constant_ranges: &[],
159 });
160 (pipeline_layout, bindgroup_layout)
161 }
162
163 pub(crate) fn prepare(&self, lighting: &Lighting, depth_texture_size: UVec2) {
164 self.tiling_descriptor.modify(|d| {
165 d.depth_texture_size = depth_texture_size;
166 });
167 lighting.lighting_descriptor.modify(|desc| {
168 desc.light_tiling_descriptor_id = self.tiling_descriptor.id();
169 });
170 }
171
172 pub(crate) fn clear_tiles(
173 &self,
174 encoder: &mut wgpu::CommandEncoder,
175 bindgroup: &wgpu::BindGroup,
176 depth_texture_size: UVec2,
177 ) {
178 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
179 label: Some("light-tiling-clear-tiles"),
180 timestamp_writes: None,
181 });
182 compute_pass.set_pipeline(&self.clear_tiles_pipeline);
183 compute_pass.set_bind_group(0, bindgroup, &[]);
184
185 let tile_size = self.tiling_descriptor.get().tile_size;
186 let dims_f32 = depth_texture_size.as_vec2() / tile_size as f32;
187 let workgroups = (dims_f32 / 16.0).ceil().as_uvec2();
188 let x = workgroups.x;
189 let y = workgroups.y;
190 let z = 1;
191 compute_pass.dispatch_workgroups(x, y, z);
192 }
193
194 const WORKGROUP_SIZE: UVec3 = UVec3::new(16, 16, 1);
195
196 pub(crate) fn compute_min_max_depth(
197 &self,
198 encoder: &mut wgpu::CommandEncoder,
199 bindgroup: &wgpu::BindGroup,
200 depth_texture_size: UVec2,
201 ) {
202 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
203 label: Some("light-tiling-compute-min-max-depth"),
204 timestamp_writes: None,
205 });
206 compute_pass.set_pipeline(&self.compute_min_max_depth_pipeline);
207 compute_pass.set_bind_group(0, bindgroup, &[]);
208
209 let x = (depth_texture_size.x / Self::WORKGROUP_SIZE.x) + 1;
210 let y = (depth_texture_size.y / Self::WORKGROUP_SIZE.y) + 1;
211 let z = 1;
212 compute_pass.dispatch_workgroups(x, y, z);
213 }
214
215 pub(crate) fn compute_bins(
216 &self,
217 encoder: &mut wgpu::CommandEncoder,
218 bindgroup: &wgpu::BindGroup,
219 depth_texture_size: UVec2,
220 ) {
221 let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
222 label: Some("light-tiling-compute-bins"),
223 timestamp_writes: None,
224 });
225 compute_pass.set_pipeline(&self.compute_bins_pipeline);
226 compute_pass.set_bind_group(0, bindgroup, &[]);
227
228 let tile_size = self.tiling_descriptor.get().tile_size;
229 let x = (depth_texture_size.x / tile_size) + 1;
230 let y = (depth_texture_size.y / tile_size) + 1;
231 let z = 1;
232 compute_pass.dispatch_workgroups(x, y, z);
233 }
234
235 pub fn get_bindgroup(
237 &self,
238 device: &wgpu::Device,
239 geometry_slab: &SlabBuffer<wgpu::Buffer>,
240 lighting_slab: &SlabBuffer<wgpu::Buffer>,
241 depth_texture: &crate::texture::Texture,
242 ) -> Arc<wgpu::BindGroup> {
243 let latest_buffer_creation = [geometry_slab.creation_time(), lighting_slab.creation_time()]
245 .into_iter()
246 .max()
247 .unwrap();
248 let prev_buffer_creation = self
249 .bindgroup_creation_time
250 .swap(latest_buffer_creation, std::sync::atomic::Ordering::Relaxed);
251 let prev_depth_texture_id = self
252 .depth_texture_id
253 .swap(depth_texture.id(), std::sync::atomic::Ordering::Relaxed);
254 let should_invalidate = prev_buffer_creation < latest_buffer_creation
255 || prev_depth_texture_id < depth_texture.id();
256 self.bindgroup.get(should_invalidate, || {
257 device.create_bind_group(&wgpu::BindGroupDescriptor {
258 label: Some("light-tiling"),
259 layout: &self.bindgroup_layout,
260 entries: &[
261 wgpu::BindGroupEntry {
262 binding: 0,
263 resource: geometry_slab.as_entire_binding(),
264 },
265 wgpu::BindGroupEntry {
266 binding: 1,
267 resource: lighting_slab.as_entire_binding(),
268 },
269 wgpu::BindGroupEntry {
270 binding: 2,
271 resource: wgpu::BindingResource::TextureView(&depth_texture.view),
272 },
273 ],
274 })
275 })
276 }
277
278 pub fn set_minimum_illuminance(&self, minimum_illuminance_lux: f32) {
280 self.tiling_descriptor.modify(|desc| {
281 desc.minimum_illuminance_lux = minimum_illuminance_lux;
282 });
283 }
284
285 pub fn run(&self, stage: &Stage) {
287 let depth_texture = stage.depth_texture.read().unwrap();
288 let depth_texture_size = depth_texture.size();
289 let lighting = stage.as_ref();
290 self.prepare(lighting, depth_texture_size);
291
292 let light_slab = &lighting.light_slab;
293 let geometry_slab = &lighting.geometry_slab;
294 let runtime = light_slab.runtime();
295 let label = Some("light-tiling-run");
296 let bindgroup = self.get_bindgroup(
297 &runtime.device,
298 &geometry_slab.commit(),
299 &light_slab.commit(),
300 &depth_texture,
301 );
302
303 let mut encoder = runtime
304 .device
305 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label });
306 {
307 self.clear_tiles(&mut encoder, bindgroup.as_ref(), depth_texture_size);
308 self.compute_min_max_depth(&mut encoder, bindgroup.as_ref(), depth_texture_size);
309 self.compute_bins(&mut encoder, bindgroup.as_ref(), depth_texture_size);
310 }
311 runtime.queue.submit(Some(encoder.finish()));
312 }
313
314 pub fn tiles(&self) -> &Ct::Container<LightTile> {
315 &self.tiles
316 }
317
318 #[cfg(test)]
319 pub(crate) async fn read_tiles(&self, lighting: &Lighting) -> Vec<LightTile> {
321 lighting
322 .light_slab
323 .read_array(self.tiling_descriptor.get().tiles_array)
324 .await
325 .unwrap()
326 }
327
328 #[cfg(test)]
329 #[allow(dead_code)]
330 pub(crate) fn read_tile(&self, lighting: &Lighting, tile_coord: UVec2) -> LightTile {
331 let desc = self.tiling_descriptor.get();
332 let tile_index = tile_coord.y * desc.tile_grid_size().x + tile_coord.x;
333 let tile_id = desc.tiles_array.at(tile_index as usize);
334 futures_lite::future::block_on(lighting.light_slab.read_one(tile_id)).unwrap()
335 }
336
337 #[cfg(test)]
338 pub(crate) async fn read_images(
340 &self,
341 lighting: &Lighting,
342 ) -> (image::GrayImage, image::GrayImage, image::GrayImage) {
343 use crabslab::Slab;
344
345 use crate::light::shader::dequantize_depth_u32_to_f32;
346
347 let tile_dimensions = self.tiling_descriptor.get().tile_grid_size();
348 let slab = lighting.light_slab.read(..).await.unwrap();
349 let tiling_descriptor_id_in_lighting = lighting
350 .lighting_descriptor
351 .get()
352 .light_tiling_descriptor_id;
353 let tiling_descriptor_id = self.tiling_descriptor.id();
354 assert_eq!(tiling_descriptor_id_in_lighting, tiling_descriptor_id);
355 let desc = slab.read(
356 lighting
357 .lighting_descriptor
358 .get()
359 .light_tiling_descriptor_id,
360 );
361 let should_be_len = tile_dimensions.x * tile_dimensions.y;
362 if should_be_len != desc.tiles_array.len() as u32 {
363 log::error!(
364 "LightTilingDescriptor's tiles array is borked: {:?}\n\
365 expected {should_be_len} tiles\n\
366 tile_dimensions: {tile_dimensions}",
367 desc.tiles_array,
368 );
369 }
370 let mut mins_img = image::GrayImage::new(tile_dimensions.x, tile_dimensions.y);
371 let mut maxs_img = image::GrayImage::new(tile_dimensions.x, tile_dimensions.y);
372 let mut lights_img = image::GrayImage::new(tile_dimensions.x, tile_dimensions.y);
373 slab.read_vec(desc.tiles_array)
374 .into_iter()
375 .enumerate()
376 .for_each(|(i, tile)| {
377 let x = i as u32 % tile_dimensions.x;
378 let y = i as u32 / tile_dimensions.x;
379 let min = dequantize_depth_u32_to_f32(tile.depth_min);
380 let max = dequantize_depth_u32_to_f32(tile.depth_max);
381
382 mins_img.get_pixel_mut(x, y).0[0] = crate::math::scaled_f32_to_u8(min);
383 maxs_img.get_pixel_mut(x, y).0[0] = crate::math::scaled_f32_to_u8(max);
384 lights_img.get_pixel_mut(x, y).0[0] = crate::math::scaled_f32_to_u8(
385 tile.next_light_index as f32 / tile.lights_array.len() as f32,
386 );
387 });
388
389 (mins_img, maxs_img, lights_img)
390 }
391}
392
393#[derive(Debug, Clone, Copy)]
395pub struct LightTilingConfig {
396 pub tile_size: u32,
400 pub max_lights_per_tile: u32,
404 pub minimum_illuminance: f32,
420}
421
422impl Default for LightTilingConfig {
423 fn default() -> Self {
424 LightTilingConfig {
425 tile_size: 16,
426 max_lights_per_tile: 32,
427 minimum_illuminance: 0.1,
428 }
429 }
430}
431
432impl LightTiling<HybridArrayContainer> {
433 pub(crate) fn new_hybrid(
435 lighting: &Lighting,
436 multisampled: bool,
437 depth_texture_size: UVec2,
438 config: LightTilingConfig,
439 ) -> Self {
440 log::trace!("creating LightTiling");
441 let lighting_slab = lighting.slab_allocator();
442 let runtime = lighting_slab.runtime();
443 let desc = LightTilingDescriptor {
444 depth_texture_size,
445 tile_size: config.tile_size,
446 minimum_illuminance_lux: config.minimum_illuminance,
447 ..Default::default()
448 };
449 let tiling_descriptor = lighting_slab.new_value(desc);
450 lighting.lighting_descriptor.modify(|desc| {
451 desc.light_tiling_descriptor_id = tiling_descriptor.id();
452 });
453 log::trace!("created tiling descriptor: {tiling_descriptor:#?}");
454 let tiled_size = desc.tile_grid_size();
455 log::trace!(" grid size: {tiled_size}");
456 let mut tiles = Vec::new();
457 for _ in 0..tiled_size.x * tiled_size.y {
458 let lights =
459 lighting_slab.new_array(vec![Id::NONE; config.max_lights_per_tile as usize]);
460 tiles.push(LightTile {
461 lights_array: lights.array(),
462 ..Default::default()
463 });
464 }
465 let tiles = lighting_slab.new_array(tiles);
466 tiling_descriptor.modify(|d| {
467 let tiles_array = tiles.array();
468 log::trace!(" setting tiles array: {tiles_array:?}");
469 d.tiles_array = tiles_array;
470 });
471 let clear_tiles_pipeline = Arc::new(Self::create_clear_tiles_pipeline(
472 &runtime.device,
473 multisampled,
474 ));
475 let compute_min_max_depth_pipeline = Arc::new(Self::create_compute_min_max_depth_pipeline(
476 &runtime.device,
477 multisampled,
478 ));
479 let compute_bins_pipeline = Arc::new(Self::create_compute_bins_pipeline(
480 &runtime.device,
481 multisampled,
482 ));
483 let bindgroup_layout =
484 Arc::new(Self::create_bindgroup_layout(&runtime.device, multisampled));
485
486 Self {
487 tiling_descriptor,
488 tiles,
489 bindgroup: ManagedBindGroup::default(),
491 bindgroup_creation_time: Default::default(),
492 bindgroup_layout,
493 depth_texture_id: Default::default(),
494 clear_tiles_pipeline,
495 compute_min_max_depth_pipeline,
496 compute_bins_pipeline,
497 }
498 }
499}
500
501impl LightTiling {
502 pub fn new(
504 lighting: &Lighting,
505 multisampled: bool,
506 depth_texture_size: UVec2,
507 config: LightTilingConfig,
508 ) -> Self {
509 let LightTiling {
511 tiling_descriptor,
512 tiles,
513 bindgroup_creation_time,
514 depth_texture_id,
515 bindgroup_layout,
516 bindgroup,
517 clear_tiles_pipeline,
518 compute_min_max_depth_pipeline,
519 compute_bins_pipeline,
520 } = LightTiling::new_hybrid(lighting, multisampled, depth_texture_size, config);
521 Self {
522 tiling_descriptor,
523 tiles: tiles.into_gpu_only(),
524 depth_texture_id,
525 bindgroup,
526 bindgroup_layout,
527 bindgroup_creation_time,
528 clear_tiles_pipeline,
529 compute_min_max_depth_pipeline,
530 compute_bins_pipeline,
531 }
532 }
533}