1use serde::Serialize;
2
3use crate::config::{DemoConfig, SceneConfig};
4use crate::cost::{build_cost_report, CostMode};
5use crate::dsfb::run_profiled_taa;
6use crate::error::{Error, Result};
7use crate::host::{default_host_realistic_profile, motion_augmented_profile};
8use crate::metrics::{analyze_demo_a_suite, RunAnalysisInput, ScenarioReport};
9use crate::scene::{
10 generate_sequence_for_definition, scenario_by_id, ScenarioExpectation, ScenarioId,
11 ScenarioSupportCategory, SceneSequence,
12};
13use crate::taa::{run_fixed_alpha_baseline, run_strong_heuristic_baseline};
14
15const RESOLUTION_TIERS: &[(&str, usize, usize, bool)] = &[
16 ("default_full_suite", 160, 96, false),
17 ("intermediate_selected_suite", 640, 360, false),
18 ("high_resolution_proxy_selected_suite", 960, 540, true),
19];
20
21const SELECTED_SCENARIOS: &[ScenarioId] = &[
22 ScenarioId::ThinReveal,
23 ScenarioId::RevealBand,
24 ScenarioId::MotionBiasBand,
25 ScenarioId::ContrastPulse,
26];
27
28#[derive(Clone, Debug, Serialize)]
29pub struct ResolutionScenarioMetrics {
30 pub tier_id: String,
31 pub width: usize,
32 pub height: usize,
33 pub selected_high_resolution_mode: bool,
34 pub scenario_id: String,
35 pub scenario_title: String,
36 pub expectation: ScenarioExpectation,
37 pub support_category: ScenarioSupportCategory,
38 pub target_pixels: usize,
39 pub target_area_fraction: f32,
40 pub fixed_alpha_cumulative_roi_mae: f32,
41 pub strong_heuristic_cumulative_roi_mae: f32,
42 pub host_realistic_cumulative_roi_mae: f32,
43 pub motion_augmented_cumulative_roi_mae: f32,
44 pub host_realistic_vs_fixed_alpha_gain: f32,
45 pub motion_augmented_vs_host_realistic_gain: f32,
46 pub host_realistic_non_roi_mae: f32,
47 pub buffer_memory_megabytes: f32,
48 pub roi_note: String,
49}
50
51#[derive(Clone, Debug, Serialize)]
52pub struct ResolutionScalingMetrics {
53 pub entries: Vec<ResolutionScenarioMetrics>,
54 pub notes: Vec<String>,
55}
56
57pub fn run_resolution_scaling_study(config: &DemoConfig) -> Result<ResolutionScalingMetrics> {
58 let mut entries = Vec::new();
59 let host_cost = build_cost_report(CostMode::HostRealistic);
60 let bytes_per_pixel = host_cost
61 .buffers
62 .iter()
63 .map(|buffer| buffer.bytes_per_pixel)
64 .sum::<usize>();
65
66 for (tier_id, width, height, selected_high_resolution_mode) in RESOLUTION_TIERS {
67 let scaled_scene = scaled_scene_config(&config.scene, *width, *height);
68 let tier_config = DemoConfig {
69 scene: scaled_scene,
70 ..config.clone()
71 };
72 for scenario_id in SELECTED_SCENARIOS {
73 if *width > config.scene.width && matches!(scenario_id, ScenarioId::ThinReveal) {
74 continue;
75 }
76 let definition = scenario_by_id(&tier_config.scene, *scenario_id).ok_or_else(|| {
77 Error::Message(format!(
78 "resolution scaling scenario {} was unavailable",
79 scenario_id.as_str()
80 ))
81 })?;
82 let sequence = generate_sequence_for_definition(&definition);
83 let scenario_report = run_resolution_scenario(&tier_config, &sequence)?;
84 let fixed = find_run(&scenario_report, "fixed_alpha")?;
85 let strong = find_run(&scenario_report, "strong_heuristic")?;
86 let host = find_run(&scenario_report, "dsfb_host_realistic")?;
87 let motion = find_run(&scenario_report, "dsfb_motion_augmented")?;
88 let total_pixels = width * height;
89 entries.push(ResolutionScenarioMetrics {
90 tier_id: (*tier_id).to_string(),
91 width: *width,
92 height: *height,
93 selected_high_resolution_mode: *selected_high_resolution_mode,
94 scenario_id: scenario_report.scenario_id.clone(),
95 scenario_title: scenario_report.scenario_title.clone(),
96 expectation: scenario_report.expectation,
97 support_category: scenario_report.support_category,
98 target_pixels: scenario_report.target_pixels,
99 target_area_fraction: scenario_report.target_area_fraction,
100 fixed_alpha_cumulative_roi_mae: fixed.summary.cumulative_roi_mae,
101 strong_heuristic_cumulative_roi_mae: strong.summary.cumulative_roi_mae,
102 host_realistic_cumulative_roi_mae: host.summary.cumulative_roi_mae,
103 motion_augmented_cumulative_roi_mae: motion.summary.cumulative_roi_mae,
104 host_realistic_vs_fixed_alpha_gain: scenario_report
105 .host_realistic_vs_fixed_alpha_cumulative_roi_gain,
106 motion_augmented_vs_host_realistic_gain: host.summary.cumulative_roi_mae
107 - motion.summary.cumulative_roi_mae,
108 host_realistic_non_roi_mae: host.summary.average_non_roi_mae,
109 buffer_memory_megabytes: bytes_per_pixel as f32 * total_pixels as f32
110 / (1024.0 * 1024.0),
111 roi_note: scenario_report.roi_note.clone(),
112 });
113 }
114 }
115
116 Ok(ResolutionScalingMetrics {
117 entries,
118 notes: vec![
119 "The high-resolution tier is a selected-scenario scalable proxy rather than a full 1080p sweep. It is intended to demonstrate structural persistence beyond the toy default resolution without pretending to be a shipping-engine benchmark.".to_string(),
120 "The canonical thin_reveal point-ROI case is intentionally kept at the default resolution only. At higher resolutions its exact one-pixel disocclusion geometry becomes path-dependent and is not a stable scaling metric.".to_string(),
121 "Memory footprint numbers are analytical host-realistic buffer estimates from the crate cost model.".to_string(),
122 ],
123 })
124}
125
126pub fn scaled_scene_config(base: &SceneConfig, width: usize, height: usize) -> SceneConfig {
127 let scale_x = width as f32 / base.width.max(1) as f32;
128 let scale_y = height as f32 / base.height.max(1) as f32;
129 let scale_len = scale_x.min(scale_y);
130 let clamp_x = |value: i32| ((value as f32 * scale_x).round() as i32).clamp(0, width as i32);
131 let clamp_y = |value: i32| ((value as f32 * scale_y).round() as i32).clamp(0, height as i32);
132
133 SceneConfig {
134 width,
135 height,
136 frame_count: base.frame_count,
137 object_width: ((base.object_width as f32 * scale_len).round() as usize).max(8),
138 object_height: ((base.object_height as f32 * scale_len).round() as usize).max(8),
139 object_start_x: clamp_x(base.object_start_x),
140 object_stop_x: clamp_x(base.object_stop_x),
141 object_top_y: clamp_y(base.object_top_y),
142 move_frames: base.move_frames,
143 thin_vertical_x: clamp_x(base.thin_vertical_x),
144 }
145}
146
147fn run_resolution_scenario(
148 config: &DemoConfig,
149 sequence: &SceneSequence,
150) -> Result<ScenarioReport> {
151 let fixed = run_fixed_alpha_baseline(sequence, config.baseline.fixed_alpha);
152 let strong = run_strong_heuristic_baseline(sequence, &config.baseline);
153 let host = run_profiled_taa(
154 sequence,
155 &default_host_realistic_profile(config.dsfb_alpha_range.min, config.dsfb_alpha_range.max),
156 );
157 let motion = run_profiled_taa(
158 sequence,
159 &motion_augmented_profile(config.dsfb_alpha_range.min, config.dsfb_alpha_range.max),
160 );
161 let host_alpha = host
162 .supervision_frames
163 .iter()
164 .map(|frame| frame.alpha.clone())
165 .collect::<Vec<_>>();
166 let host_response = host
167 .supervision_frames
168 .iter()
169 .map(|frame| frame.intervention.clone())
170 .collect::<Vec<_>>();
171 let host_trust = host
172 .supervision_frames
173 .iter()
174 .map(|frame| frame.trust.clone())
175 .collect::<Vec<_>>();
176 let motion_alpha = motion
177 .supervision_frames
178 .iter()
179 .map(|frame| frame.alpha.clone())
180 .collect::<Vec<_>>();
181 let motion_response = motion
182 .supervision_frames
183 .iter()
184 .map(|frame| frame.intervention.clone())
185 .collect::<Vec<_>>();
186 let motion_trust = motion
187 .supervision_frames
188 .iter()
189 .map(|frame| frame.trust.clone())
190 .collect::<Vec<_>>();
191
192 let analysis = analyze_demo_a_suite(&[(
193 sequence.clone(),
194 vec![
195 RunAnalysisInput {
196 id: &fixed.id,
197 label: &fixed.label,
198 category: "baseline",
199 resolved_frames: &fixed.taa.resolved_frames,
200 reprojected_history_frames: &fixed.taa.reprojected_history_frames,
201 alpha_frames: &fixed.alpha_frames,
202 response_frames: &fixed.response_frames,
203 trust_frames: None,
204 },
205 RunAnalysisInput {
206 id: &strong.id,
207 label: &strong.label,
208 category: "baseline",
209 resolved_frames: &strong.taa.resolved_frames,
210 reprojected_history_frames: &strong.taa.reprojected_history_frames,
211 alpha_frames: &strong.alpha_frames,
212 response_frames: &strong.response_frames,
213 trust_frames: None,
214 },
215 RunAnalysisInput {
216 id: &host.profile.id,
217 label: &host.profile.label,
218 category: "dsfb",
219 resolved_frames: &host.resolved_frames,
220 reprojected_history_frames: &host.reprojected_history_frames,
221 alpha_frames: &host_alpha,
222 response_frames: &host_response,
223 trust_frames: Some(&host_trust),
224 },
225 RunAnalysisInput {
226 id: &motion.profile.id,
227 label: &motion.profile.label,
228 category: "dsfb",
229 resolved_frames: &motion.resolved_frames,
230 reprojected_history_frames: &motion.reprojected_history_frames,
231 alpha_frames: &motion_alpha,
232 response_frames: &motion_response,
233 trust_frames: Some(&motion_trust),
234 },
235 ],
236 )])?;
237 analysis.scenarios.into_iter().next().ok_or_else(|| {
238 Error::Message("resolution scaling analysis produced no scenario".to_string())
239 })
240}
241
242fn find_run<'a>(
243 scenario: &'a ScenarioReport,
244 run_id: &str,
245) -> Result<&'a crate::metrics::ScenarioRunReport> {
246 scenario
247 .runs
248 .iter()
249 .find(|run| run.summary.run_id == run_id)
250 .ok_or_else(|| Error::Message(format!("resolution scaling run {run_id} missing")))
251}