Skip to content

Commit 5af7464

Browse files
robtfmcart
andcommitted
fix cluster tiling calculations (#4148)
# Objective fix cluster tilesize and tilecount calculations. Fixes #4127 & #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 <[email protected]>
1 parent b493165 commit 5af7464

File tree

1 file changed

+96
-11
lines changed

1 file changed

+96
-11
lines changed

crates/bevy_pbr/src/light.rs

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,29 @@ impl ClusterConfig {
293293
total, z_slices, ..
294294
} => {
295295
let aspect_ratio = screen_size.x as f32 / screen_size.y as f32;
296-
let per_layer = *total as f32 / *z_slices as f32;
296+
let mut z_slices = *z_slices;
297+
if *total < z_slices {
298+
warn!("ClusterConfig has more z-slices than total clusters!");
299+
z_slices = *total;
300+
}
301+
let per_layer = *total as f32 / z_slices as f32;
302+
297303
let y = f32::sqrt(per_layer / aspect_ratio);
298-
let x = (y * aspect_ratio).floor() as u32;
299-
let y = y.floor() as u32;
300-
UVec3::new(x, y, *z_slices)
304+
305+
let mut x = (y * aspect_ratio) as u32;
306+
let mut y = y as u32;
307+
308+
// check extremes
309+
if x == 0 {
310+
x = 1;
311+
y = per_layer as u32;
312+
}
313+
if y == 0 {
314+
x = per_layer as u32;
315+
y = 1;
316+
}
317+
318+
UVec3::new(x, y, z_slices)
301319
}
302320
}
303321
}
@@ -369,8 +387,12 @@ impl Clusters {
369387
near: f32,
370388
far: f32,
371389
) -> Self {
390+
debug_assert!(screen_size.x > 0 && screen_size.y > 0);
391+
debug_assert!(dimensions.x > 0 && dimensions.y > 0 && dimensions.z > 0);
372392
Clusters::new(
373-
(screen_size + UVec2::ONE) / dimensions.xy(),
393+
(screen_size.as_vec2() / dimensions.xy().as_vec2())
394+
.ceil()
395+
.as_uvec2(),
374396
screen_size,
375397
dimensions.z,
376398
near,
@@ -380,13 +402,12 @@ impl Clusters {
380402

381403
fn update(&mut self, tile_size: UVec2, screen_size: UVec2, z_slices: u32) {
382404
self.tile_size = tile_size;
383-
self.axis_slices = UVec3::new(
384-
(screen_size.x + 1) / tile_size.x,
385-
(screen_size.y + 1) / tile_size.y,
386-
z_slices,
387-
);
405+
self.axis_slices = (screen_size.as_vec2() / tile_size.as_vec2())
406+
.ceil()
407+
.as_uvec2()
408+
.extend(z_slices);
388409
// NOTE: Maximum 4096 clusters due to uniform buffer size constraints
389-
assert!(self.axis_slices.x * self.axis_slices.y * self.axis_slices.z <= 4096);
410+
debug_assert!(self.axis_slices.x * self.axis_slices.y * self.axis_slices.z <= 4096);
390411
}
391412
}
392413

@@ -1240,3 +1261,67 @@ pub fn check_light_mesh_visibility(
12401261
}
12411262
}
12421263
}
1264+
1265+
#[cfg(test)]
1266+
mod test {
1267+
use super::*;
1268+
1269+
fn test_cluster_tiling(config: ClusterConfig, screen_size: UVec2) -> Clusters {
1270+
let dims = config.dimensions_for_screen_size(screen_size);
1271+
1272+
// note: near & far do not affect tiling
1273+
let clusters = Clusters::from_screen_size_and_dimensions(screen_size, dims, 5.0, 1000.0);
1274+
1275+
// check we cover the screen
1276+
assert!(clusters.tile_size.x * clusters.axis_slices.x >= screen_size.x);
1277+
assert!(clusters.tile_size.y * clusters.axis_slices.y >= screen_size.y);
1278+
// check a smaller number of clusters would not cover the screen
1279+
assert!(clusters.tile_size.x * (clusters.axis_slices.x - 1) < screen_size.x);
1280+
assert!(clusters.tile_size.y * (clusters.axis_slices.y - 1) < screen_size.y);
1281+
// check a smaller tilesize would not cover the screen
1282+
assert!((clusters.tile_size.x - 1) * clusters.axis_slices.x < screen_size.x);
1283+
assert!((clusters.tile_size.y - 1) * clusters.axis_slices.y < screen_size.y);
1284+
// check we don't have more clusters than pixels
1285+
assert!(clusters.axis_slices.x <= screen_size.x);
1286+
assert!(clusters.axis_slices.y <= screen_size.y);
1287+
1288+
clusters
1289+
}
1290+
1291+
#[test]
1292+
// check tiling for small screen sizes
1293+
fn test_default_cluster_setup_small_screensizes() {
1294+
for x in 1..100 {
1295+
for y in 1..100 {
1296+
let screen_size = UVec2::new(x, y);
1297+
let clusters = test_cluster_tiling(ClusterConfig::default(), screen_size);
1298+
assert!(
1299+
clusters.axis_slices.x * clusters.axis_slices.y * clusters.axis_slices.z
1300+
<= 4096
1301+
);
1302+
}
1303+
}
1304+
}
1305+
1306+
#[test]
1307+
// check tiling for long thin screen sizes
1308+
fn test_default_cluster_setup_small_x() {
1309+
for x in 1..10 {
1310+
for y in 1..5000 {
1311+
let screen_size = UVec2::new(x, y);
1312+
let clusters = test_cluster_tiling(ClusterConfig::default(), screen_size);
1313+
assert!(
1314+
clusters.axis_slices.x * clusters.axis_slices.y * clusters.axis_slices.z
1315+
<= 4096
1316+
);
1317+
1318+
let screen_size = UVec2::new(y, x);
1319+
let clusters = test_cluster_tiling(ClusterConfig::default(), screen_size);
1320+
assert!(
1321+
clusters.axis_slices.x * clusters.axis_slices.y * clusters.axis_slices.z
1322+
<= 4096
1323+
);
1324+
}
1325+
}
1326+
}
1327+
}

0 commit comments

Comments
 (0)