Skip to content

Commit

Permalink
fix cluster tiling calculations (bevyengine#4148)
Browse files Browse the repository at this point in the history
# Objective

fix cluster tilesize and tilecount calculations.
Fixes bevyengine#4127 & bevyengine#3596

## Solution

- calculate tilesize as smallest integers such that dimensions.xy() tiles will cover the screen
- calculate final dimensions as smallest integers such that final dimensions * tilesize will cover the screen

there is more cleanup that could be done in these functions. a future PR will likely remove the tilesize completely, so this is just a minimal change set to fix the current bug at small screen sizes

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
  • Loading branch information
2 people authored and ItsDoot committed Feb 1, 2023
1 parent 194d9dc commit e699584
Showing 1 changed file with 96 additions and 11 deletions.
107 changes: 96 additions & 11 deletions crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,29 @@ impl ClusterConfig {
total, z_slices, ..
} => {
let aspect_ratio = screen_size.x as f32 / screen_size.y as f32;
let per_layer = *total as f32 / *z_slices as f32;
let mut z_slices = *z_slices;
if *total < z_slices {
warn!("ClusterConfig has more z-slices than total clusters!");
z_slices = *total;
}
let per_layer = *total as f32 / z_slices as f32;

let y = f32::sqrt(per_layer / aspect_ratio);
let x = (y * aspect_ratio).floor() as u32;
let y = y.floor() as u32;
UVec3::new(x, y, *z_slices)

let mut x = (y * aspect_ratio) as u32;
let mut y = y as u32;

// check extremes
if x == 0 {
x = 1;
y = per_layer as u32;
}
if y == 0 {
x = per_layer as u32;
y = 1;
}

UVec3::new(x, y, z_slices)
}
}
}
Expand Down Expand Up @@ -369,8 +387,12 @@ impl Clusters {
near: f32,
far: f32,
) -> Self {
debug_assert!(screen_size.x > 0 && screen_size.y > 0);
debug_assert!(dimensions.x > 0 && dimensions.y > 0 && dimensions.z > 0);
Clusters::new(
(screen_size + UVec2::ONE) / dimensions.xy(),
(screen_size.as_vec2() / dimensions.xy().as_vec2())
.ceil()
.as_uvec2(),
screen_size,
dimensions.z,
near,
Expand All @@ -380,13 +402,12 @@ impl Clusters {

fn update(&mut self, tile_size: UVec2, screen_size: UVec2, z_slices: u32) {
self.tile_size = tile_size;
self.axis_slices = UVec3::new(
(screen_size.x + 1) / tile_size.x,
(screen_size.y + 1) / tile_size.y,
z_slices,
);
self.axis_slices = (screen_size.as_vec2() / tile_size.as_vec2())
.ceil()
.as_uvec2()
.extend(z_slices);
// NOTE: Maximum 4096 clusters due to uniform buffer size constraints
assert!(self.axis_slices.x * self.axis_slices.y * self.axis_slices.z <= 4096);
debug_assert!(self.axis_slices.x * self.axis_slices.y * self.axis_slices.z <= 4096);
}
}

Expand Down Expand Up @@ -1240,3 +1261,67 @@ pub fn check_light_mesh_visibility(
}
}
}

#[cfg(test)]
mod test {
use super::*;

fn test_cluster_tiling(config: ClusterConfig, screen_size: UVec2) -> Clusters {
let dims = config.dimensions_for_screen_size(screen_size);

// note: near & far do not affect tiling
let clusters = Clusters::from_screen_size_and_dimensions(screen_size, dims, 5.0, 1000.0);

// check we cover the screen
assert!(clusters.tile_size.x * clusters.axis_slices.x >= screen_size.x);
assert!(clusters.tile_size.y * clusters.axis_slices.y >= screen_size.y);
// check a smaller number of clusters would not cover the screen
assert!(clusters.tile_size.x * (clusters.axis_slices.x - 1) < screen_size.x);
assert!(clusters.tile_size.y * (clusters.axis_slices.y - 1) < screen_size.y);
// check a smaller tilesize would not cover the screen
assert!((clusters.tile_size.x - 1) * clusters.axis_slices.x < screen_size.x);
assert!((clusters.tile_size.y - 1) * clusters.axis_slices.y < screen_size.y);
// check we don't have more clusters than pixels
assert!(clusters.axis_slices.x <= screen_size.x);
assert!(clusters.axis_slices.y <= screen_size.y);

clusters
}

#[test]
// check tiling for small screen sizes
fn test_default_cluster_setup_small_screensizes() {
for x in 1..100 {
for y in 1..100 {
let screen_size = UVec2::new(x, y);
let clusters = test_cluster_tiling(ClusterConfig::default(), screen_size);
assert!(
clusters.axis_slices.x * clusters.axis_slices.y * clusters.axis_slices.z
<= 4096
);
}
}
}

#[test]
// check tiling for long thin screen sizes
fn test_default_cluster_setup_small_x() {
for x in 1..10 {
for y in 1..5000 {
let screen_size = UVec2::new(x, y);
let clusters = test_cluster_tiling(ClusterConfig::default(), screen_size);
assert!(
clusters.axis_slices.x * clusters.axis_slices.y * clusters.axis_slices.z
<= 4096
);

let screen_size = UVec2::new(y, x);
let clusters = test_cluster_tiling(ClusterConfig::default(), screen_size);
assert!(
clusters.axis_slices.x * clusters.axis_slices.y * clusters.axis_slices.z
<= 4096
);
}
}
}
}

0 comments on commit e699584

Please sign in to comment.