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