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 {
25 requested: u32,
27 applied: u32,
29 },
30}
31
32#[derive(Copy, Clone, Debug, Eq, PartialEq)]
34#[non_exhaustive]
35pub struct ResourceBudget {
36 pub texture_bytes: u64,
38 pub decoder_sessions: u32,
40 pub active_decoder_sessions: u32,
42}
43
44impl ResourceBudget {
45 #[must_use]
47 pub const fn new(
48 texture_bytes: u64,
49 decoder_sessions: u32,
50 active_decoder_sessions: u32,
51 ) -> Self {
52 Self {
53 texture_bytes,
54 decoder_sessions,
55 active_decoder_sessions,
56 }
57 }
58}
59
60#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct DegradationDecision {
63 pub texture_bytes: u64,
65 pub max_filter_samples: u32,
67 pub decoder_available: bool,
69 pub diagnostics: Vec<CapabilityDiagnostic>,
71}
72
73#[must_use]
77pub const fn filter_sample_limit(tier: CapabilityTier) -> u32 {
78 match tier {
79 CapabilityTier::TierB => 16,
80 _ => crate::RenderQualityControls::MAX_FILTER_SAMPLES,
81 }
82}
83
84#[must_use]
86pub fn resolve_degradation(
87 capabilities: &Capabilities,
88 requested_texture_bytes: u64,
89 requested_filter_samples: u32,
90 budget: ResourceBudget,
91) -> DegradationDecision {
92 let texture_bytes = requested_texture_bytes.min(budget.texture_bytes);
93 let max_filter_samples = requested_filter_samples
94 .min(filter_sample_limit(capabilities.tier))
95 .max(1);
96 let decoder_available = budget.active_decoder_sessions < budget.decoder_sessions;
97 let mut diagnostics = Vec::new();
98 if capabilities.intermediate_format == IntermediateFormat::Rgba8Unorm {
99 diagnostics.push(CapabilityDiagnostic::Rgba8Intermediate);
100 }
101 if texture_bytes < requested_texture_bytes {
102 diagnostics.push(CapabilityDiagnostic::TextureBudgetReduced {
103 requested: requested_texture_bytes,
104 applied: texture_bytes,
105 });
106 }
107 if max_filter_samples < requested_filter_samples {
108 diagnostics.push(CapabilityDiagnostic::FilterSamplesReduced {
109 requested: requested_filter_samples,
110 applied: max_filter_samples,
111 });
112 }
113 if !decoder_available {
114 diagnostics.push(CapabilityDiagnostic::DecoderSessionLimit {
115 limit: budget.decoder_sessions,
116 });
117 }
118 DegradationDecision {
119 texture_bytes,
120 max_filter_samples,
121 decoder_available,
122 diagnostics,
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn tier_b_and_caller_budgets_degrade_observably() {
132 let capabilities = Capabilities::new(
133 CapabilityTier::TierB,
134 IntermediateFormat::Rgba8Unorm,
135 "test",
136 4096,
137 false,
138 "Gl",
139 "software",
140 "fixture",
141 "",
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 );
177 let budget = ResourceBudget::new(1_000, 1, 0);
178 let decision = resolve_degradation(&capabilities, 1_000, 256, budget);
179 assert_eq!(decision.max_filter_samples, 64);
180 assert_eq!(
181 decision.diagnostics,
182 [CapabilityDiagnostic::FilterSamplesReduced {
183 requested: 256,
184 applied: 64
185 }]
186 );
187 assert_eq!(
188 resolve_degradation(&capabilities, 1_000, 64, budget).diagnostics,
189 [] as [CapabilityDiagnostic; 0]
190 );
191 }
192}