use alpha2_gpu::{Capabilities, CapabilityTier, IntermediateFormat};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CapabilityDiagnostic {
Rgba8Intermediate,
TextureBudgetReduced {
requested: u64,
applied: u64,
},
DecoderSessionLimit {
limit: u32,
},
FilterSamplesReduced {
requested: u32,
applied: u32,
},
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct ResourceBudget {
pub texture_bytes: u64,
pub decoder_sessions: u32,
pub active_decoder_sessions: u32,
}
impl ResourceBudget {
#[must_use]
pub const fn new(
texture_bytes: u64,
decoder_sessions: u32,
active_decoder_sessions: u32,
) -> Self {
Self {
texture_bytes,
decoder_sessions,
active_decoder_sessions,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DegradationDecision {
pub texture_bytes: u64,
pub max_filter_samples: u32,
pub decoder_available: bool,
pub diagnostics: Vec<CapabilityDiagnostic>,
}
#[must_use]
pub const fn filter_sample_limit(tier: CapabilityTier) -> u32 {
match tier {
CapabilityTier::TierB => 16,
_ => crate::RenderQualityControls::MAX_FILTER_SAMPLES,
}
}
#[must_use]
pub fn resolve_degradation(
capabilities: &Capabilities,
requested_texture_bytes: u64,
requested_filter_samples: u32,
budget: ResourceBudget,
) -> DegradationDecision {
let texture_bytes = requested_texture_bytes.min(budget.texture_bytes);
let max_filter_samples = requested_filter_samples
.min(filter_sample_limit(capabilities.tier))
.max(1);
let decoder_available = budget.active_decoder_sessions < budget.decoder_sessions;
let mut diagnostics = Vec::new();
if capabilities.intermediate_format == IntermediateFormat::Rgba8Unorm {
diagnostics.push(CapabilityDiagnostic::Rgba8Intermediate);
}
if texture_bytes < requested_texture_bytes {
diagnostics.push(CapabilityDiagnostic::TextureBudgetReduced {
requested: requested_texture_bytes,
applied: texture_bytes,
});
}
if max_filter_samples < requested_filter_samples {
diagnostics.push(CapabilityDiagnostic::FilterSamplesReduced {
requested: requested_filter_samples,
applied: max_filter_samples,
});
}
if !decoder_available {
diagnostics.push(CapabilityDiagnostic::DecoderSessionLimit {
limit: budget.decoder_sessions,
});
}
DegradationDecision {
texture_bytes,
max_filter_samples,
decoder_available,
diagnostics,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tier_b_and_caller_budgets_degrade_observably() {
let capabilities = Capabilities::new(
CapabilityTier::TierB,
IntermediateFormat::Rgba8Unorm,
"test",
4096,
false,
"Gl",
"software",
"fixture",
);
let decision = resolve_degradation(
&capabilities,
1_000,
64,
ResourceBudget {
texture_bytes: 500,
decoder_sessions: 2,
active_decoder_sessions: 2,
},
);
assert_eq!(decision.texture_bytes, 500);
assert_eq!(decision.max_filter_samples, 16);
assert!(!decision.decoder_available);
assert_eq!(decision.diagnostics.len(), 4);
}
#[test]
fn the_filter_sample_limit_is_the_renderers_on_every_tier() {
assert_eq!(filter_sample_limit(CapabilityTier::TierB), 16);
assert_eq!(
filter_sample_limit(CapabilityTier::TierA),
crate::RenderQualityControls::MAX_FILTER_SAMPLES
);
let capabilities = Capabilities::new(
CapabilityTier::TierA,
IntermediateFormat::Rgba16Float,
"test",
4096,
false,
"Vulkan",
"driver",
"fixture",
);
let budget = ResourceBudget::new(1_000, 1, 0);
let decision = resolve_degradation(&capabilities, 1_000, 256, budget);
assert_eq!(decision.max_filter_samples, 64);
assert_eq!(
decision.diagnostics,
[CapabilityDiagnostic::FilterSamplesReduced {
requested: 256,
applied: 64
}]
);
assert_eq!(
resolve_degradation(&capabilities, 1_000, 64, budget).diagnostics,
[] as [CapabilityDiagnostic; 0]
);
}
}