1use alpha2_gpu::{Capabilities, CapabilityTier, IntermediateFormat};
4
5#[derive(Clone, Debug, Eq, PartialEq)]
7pub enum CapabilityDiagnostic {
8 Rgba8Intermediate,
10 TextureBudgetReduced {
12 requested: u64,
14 applied: u64,
16 },
17 DecoderSessionLimit {
19 limit: u32,
21 },
22 FilterSamplesReduced {
26 requested: u32,
28 applied: u32,
30 },
31}
32
33#[derive(Copy, Clone, Debug, Eq, PartialEq)]
35#[non_exhaustive]
36pub struct ResourceBudget {
37 pub texture_bytes: u64,
39 pub decoder_sessions: u32,
41 pub active_decoder_sessions: u32,
43}
44
45impl ResourceBudget {
46 #[must_use]
48 pub const fn new(
49 texture_bytes: u64,
50 decoder_sessions: u32,
51 active_decoder_sessions: u32,
52 ) -> Self {
53 Self {
54 texture_bytes,
55 decoder_sessions,
56 active_decoder_sessions,
57 }
58 }
59}
60
61#[derive(Clone, Debug, Eq, PartialEq)]
63pub struct DegradationDecision {
64 pub texture_bytes: u64,
66 pub max_filter_samples: u32,
68 pub decoder_available: bool,
70 pub diagnostics: Vec<CapabilityDiagnostic>,
72}
73
74#[must_use]
78pub const fn filter_sample_limit(tier: CapabilityTier) -> u32 {
79 match tier {
80 CapabilityTier::TierB => 16,
81 _ => crate::RenderQualityControls::MAX_FILTER_SAMPLES,
82 }
83}
84
85#[must_use]
87pub fn resolve_degradation(
88 capabilities: &Capabilities,
89 requested_texture_bytes: u64,
90 requested_filter_samples: u32,
91 budget: ResourceBudget,
92) -> DegradationDecision {
93 let texture_bytes = requested_texture_bytes.min(budget.texture_bytes);
94 let max_filter_samples = requested_filter_samples
95 .min(filter_sample_limit(capabilities.tier))
96 .max(1);
97 let decoder_available = budget.active_decoder_sessions < budget.decoder_sessions;
98 let mut diagnostics = Vec::new();
99 if capabilities.intermediate_format == IntermediateFormat::Rgba8Unorm {
100 diagnostics.push(CapabilityDiagnostic::Rgba8Intermediate);
101 }
102 if texture_bytes < requested_texture_bytes {
103 diagnostics.push(CapabilityDiagnostic::TextureBudgetReduced {
104 requested: requested_texture_bytes,
105 applied: texture_bytes,
106 });
107 }
108 if max_filter_samples < requested_filter_samples {
109 diagnostics.push(CapabilityDiagnostic::FilterSamplesReduced {
110 requested: requested_filter_samples,
111 applied: max_filter_samples,
112 });
113 }
114 if !decoder_available {
115 diagnostics.push(CapabilityDiagnostic::DecoderSessionLimit {
116 limit: budget.decoder_sessions,
117 });
118 }
119 DegradationDecision {
120 texture_bytes,
121 max_filter_samples,
122 decoder_available,
123 diagnostics,
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn tier_b_and_caller_budgets_degrade_observably() {
133 let capabilities = Capabilities::new(
134 CapabilityTier::TierB,
135 IntermediateFormat::Rgba8Unorm,
136 "test",
137 4096,
138 false,
139 "Gl",
140 "software",
141 "fixture",
142 );
143 let decision = resolve_degradation(
144 &capabilities,
145 1_000,
146 64,
147 ResourceBudget {
148 texture_bytes: 500,
149 decoder_sessions: 2,
150 active_decoder_sessions: 2,
151 },
152 );
153 assert_eq!(decision.texture_bytes, 500);
154 assert_eq!(decision.max_filter_samples, 16);
155 assert!(!decision.decoder_available);
156 assert_eq!(decision.diagnostics.len(), 4);
157 }
158
159 #[test]
160 fn the_filter_sample_limit_is_the_renderers_on_every_tier() {
161 assert_eq!(filter_sample_limit(CapabilityTier::TierB), 16);
162 assert_eq!(
163 filter_sample_limit(CapabilityTier::TierA),
164 crate::RenderQualityControls::MAX_FILTER_SAMPLES
165 );
166 let capabilities = Capabilities::new(
167 CapabilityTier::TierA,
168 IntermediateFormat::Rgba16Float,
169 "test",
170 4096,
171 false,
172 "Vulkan",
173 "driver",
174 "fixture",
175 );
176 let budget = ResourceBudget::new(1_000, 1, 0);
177 let decision = resolve_degradation(&capabilities, 1_000, 256, budget);
178 assert_eq!(decision.max_filter_samples, 64);
179 assert_eq!(
180 decision.diagnostics,
181 [CapabilityDiagnostic::FilterSamplesReduced {
182 requested: 256,
183 applied: 64
184 }]
185 );
186 assert_eq!(
187 resolve_degradation(&capabilities, 1_000, 64, budget).diagnostics,
188 [] as [CapabilityDiagnostic; 0]
189 );
190 }
191}