#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum OptimizationMode {
Off,
#[default]
Auto,
Force,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RendererOptimizationPolicy {
pub gpu_culling: OptimizationMode,
pub gpu_culling_enable_threshold: u32,
pub gpu_culling_disable_threshold: u32,
pub gpu_culling_cooldown_frames: u32,
}
impl Default for RendererOptimizationPolicy {
fn default() -> Self {
Self {
gpu_culling: OptimizationMode::Auto,
gpu_culling_enable_threshold: 800,
gpu_culling_disable_threshold: 500,
gpu_culling_cooldown_frames: 30,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct FrameOptimizations {
pub gpu_occlusion: bool,
pub indirect_geometry: bool,
pub hzb: bool,
pub decal_hzb_gate: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FrameOptimizationStats {
pub features_gpu_culling: bool,
pub features_decals: bool,
pub opaque_count: u32,
pub non_instanced_with_aabb_count: u32,
pub decals_count: u32,
pub args_ready: bool,
}
pub fn compute_frame_optimizations(
policy: &RendererOptimizationPolicy,
stats: &FrameOptimizationStats,
prev: &FrameOptimizations,
frames_in_current_mode: u32,
) -> FrameOptimizations {
let gpu_occlusion = if !stats.features_gpu_culling {
false
} else {
match policy.gpu_culling {
OptimizationMode::Off => false,
OptimizationMode::Force => true,
OptimizationMode::Auto => {
let cooldown_elapsed = frames_in_current_mode >= policy.gpu_culling_cooldown_frames;
let optimizable = stats.non_instanced_with_aabb_count;
if prev.gpu_occlusion {
!(cooldown_elapsed && optimizable < policy.gpu_culling_disable_threshold)
} else {
cooldown_elapsed && optimizable >= policy.gpu_culling_enable_threshold
}
}
}
};
let decal_hzb_gate =
stats.features_gpu_culling && stats.features_decals && stats.decals_count > 0;
let hzb = gpu_occlusion || decal_hzb_gate;
let indirect_geometry = gpu_occlusion && stats.args_ready;
FrameOptimizations {
gpu_occlusion,
indirect_geometry,
hzb,
decal_hzb_gate,
}
}
impl FrameOptimizations {
pub fn stable_mode(&self, prev: &FrameOptimizations) -> bool {
self.gpu_occlusion == prev.gpu_occlusion
}
}
#[cfg(test)]
mod tests {
use super::*;
fn stats(opaque: u32, decals: u32, args_ready: bool) -> FrameOptimizationStats {
FrameOptimizationStats {
features_gpu_culling: true,
features_decals: true,
opaque_count: opaque,
non_instanced_with_aabb_count: opaque,
decals_count: decals,
args_ready,
}
}
fn policy() -> RendererOptimizationPolicy {
RendererOptimizationPolicy::default()
}
fn off_prev() -> FrameOptimizations {
FrameOptimizations::default()
}
fn on_prev() -> FrameOptimizations {
FrameOptimizations {
gpu_occlusion: true,
indirect_geometry: true,
hzb: true,
decal_hzb_gate: false,
}
}
#[test]
fn off_disables_gpu_occlusion_but_keeps_decal_hzb_gate() {
let mut p = policy();
p.gpu_culling = OptimizationMode::Off;
let s = stats(10_000, 5, true);
let out = compute_frame_optimizations(&p, &s, &on_prev(), 1000);
assert!(!out.gpu_occlusion);
assert!(!out.indirect_geometry);
assert!(out.decal_hzb_gate);
assert!(
out.hzb,
"hzb must still be built so decal classify sees fresh data"
);
}
#[test]
fn off_with_no_decals_disables_hzb_entirely() {
let mut p = policy();
p.gpu_culling = OptimizationMode::Off;
let s = stats(10_000, 0, true);
let out = compute_frame_optimizations(&p, &s, &on_prev(), 1000);
assert!(!out.hzb);
assert!(!out.decal_hzb_gate);
}
#[test]
fn force_enables_occlusion_only_indirect_needs_args_ready() {
let mut p = policy();
p.gpu_culling = OptimizationMode::Force;
let s = stats(10, 0, false);
let out = compute_frame_optimizations(&p, &s, &off_prev(), 0);
assert!(out.gpu_occlusion, "Force ignores opaque_count");
assert!(
!out.indirect_geometry,
"indirect_geometry waits on args_ready"
);
assert!(out.hzb);
}
#[test]
fn force_with_args_ready_enables_indirect() {
let mut p = policy();
p.gpu_culling = OptimizationMode::Force;
let s = stats(10, 0, true);
let out = compute_frame_optimizations(&p, &s, &on_prev(), 100);
assert!(out.gpu_occlusion);
assert!(out.indirect_geometry);
}
#[test]
fn auto_enables_only_when_threshold_and_cooldown_met() {
let p = policy(); let out = compute_frame_optimizations(&p, &stats(799, 0, false), &off_prev(), 60);
assert!(!out.gpu_occlusion);
let out = compute_frame_optimizations(&p, &stats(800, 0, false), &off_prev(), 5);
assert!(!out.gpu_occlusion);
let out = compute_frame_optimizations(&p, &stats(800, 0, false), &off_prev(), 60);
assert!(out.gpu_occlusion);
}
#[test]
fn auto_holds_on_inside_hysteresis_band() {
let p = policy(); let out = compute_frame_optimizations(&p, &stats(700, 0, true), &on_prev(), 60);
assert!(out.gpu_occlusion);
let out = compute_frame_optimizations(&p, &stats(501, 0, true), &on_prev(), 60);
assert!(out.gpu_occlusion);
}
#[test]
fn auto_disables_below_threshold_after_cooldown() {
let p = policy();
let out = compute_frame_optimizations(&p, &stats(100, 0, true), &on_prev(), 5);
assert!(out.gpu_occlusion);
let out = compute_frame_optimizations(&p, &stats(100, 0, true), &on_prev(), 60);
assert!(!out.gpu_occlusion);
}
#[test]
fn force_to_off_drops_indirect_immediately() {
let mut p = policy();
p.gpu_culling = OptimizationMode::Force;
let s = stats(10, 0, true);
let on = compute_frame_optimizations(&p, &s, &on_prev(), 100);
assert!(on.indirect_geometry);
p.gpu_culling = OptimizationMode::Off;
let off = compute_frame_optimizations(&p, &s, &on, 1);
assert!(!off.gpu_occlusion);
assert!(!off.indirect_geometry);
}
#[test]
fn missing_capability_blocks_everything() {
let p = policy();
let mut s = stats(10_000, 5, true);
s.features_gpu_culling = false;
let out = compute_frame_optimizations(&p, &s, &on_prev(), 1000);
assert!(!out.gpu_occlusion);
assert!(!out.indirect_geometry);
assert!(!out.decal_hzb_gate);
assert!(!out.hzb);
}
#[test]
fn auto_ignores_opaque_when_optimizable_subset_is_small() {
let p = policy(); let stats = FrameOptimizationStats {
features_gpu_culling: true,
features_decals: false,
opaque_count: 10_000, non_instanced_with_aabb_count: 10,
decals_count: 0,
args_ready: false,
};
let out = compute_frame_optimizations(&p, &stats, &off_prev(), 1000);
assert!(
!out.gpu_occlusion,
"Auto must look at the optimizable subset, not opaque_count"
);
}
#[test]
fn auto_engages_on_optimizable_subset_threshold() {
let p = policy(); let stats = FrameOptimizationStats {
features_gpu_culling: true,
features_decals: false,
opaque_count: 800,
non_instanced_with_aabb_count: 800,
decals_count: 0,
args_ready: false,
};
let out = compute_frame_optimizations(&p, &stats, &off_prev(), 1000);
assert!(out.gpu_occlusion);
}
#[test]
fn stable_mode_helper_tracks_occlusion_flips() {
let on = FrameOptimizations {
gpu_occlusion: true,
..Default::default()
};
let off = FrameOptimizations::default();
assert!(on.stable_mode(&on));
assert!(off.stable_mode(&off));
assert!(!on.stable_mode(&off));
}
}