1use alpha2_model::ProjectSettings;
2use alpha2_render::RenderSize;
3
4use crate::EngineError;
5
6const _: () = assert!(
9 alpha2_model::MAX_CANVAS_DIMENSION == RenderSize::MAX_DIMENSION
10 && alpha2_model::MAX_CANVAS_PIXELS == RenderSize::MAX_PIXELS
11);
12
13#[derive(Copy, Clone, Debug, PartialEq)]
15#[non_exhaustive]
16pub struct QualityProfile {
17 pub resolution_scale: f32,
19 pub max_filter_samples: u32,
21 pub motion_blur_subframes: u32,
23 pub texture_budget_bytes: u64,
25}
26
27impl Default for QualityProfile {
28 fn default() -> Self {
29 Self::export()
30 }
31}
32
33impl QualityProfile {
34 pub const MAX_RESOLUTION_SCALE: f32 = 8.0;
36 pub const MAX_FILTER_SAMPLES: u32 = alpha2_render::RenderQualityControls::MAX_FILTER_SAMPLES;
40 pub const MAX_MOTION_BLUR_SUBFRAMES: u32 = 256;
42 pub const MIN_TEXTURE_BUDGET_BYTES: u64 = 1024 * 1024;
44 pub const MAX_TEXTURE_BUDGET_BYTES: u64 = 64 * 1024 * 1024 * 1024;
46
47 #[must_use]
49 pub const fn export() -> Self {
50 Self {
51 resolution_scale: 1.0,
52 max_filter_samples: 64,
53 motion_blur_subframes: 1,
54 texture_budget_bytes: 512 * 1024 * 1024,
55 }
56 }
57
58 #[must_use]
60 pub const fn preview() -> Self {
61 Self {
62 resolution_scale: 0.5,
63 max_filter_samples: 32,
64 motion_blur_subframes: 1,
65 texture_budget_bytes: 256 * 1024 * 1024,
66 }
67 }
68
69 pub fn validate(self) -> Result<(), EngineError> {
74 if self.resolution_scale.is_finite()
75 && self.resolution_scale > 0.0
76 && self.resolution_scale <= Self::MAX_RESOLUTION_SCALE
77 && (1..=Self::MAX_FILTER_SAMPLES).contains(&self.max_filter_samples)
78 && (1..=Self::MAX_MOTION_BLUR_SUBFRAMES).contains(&self.motion_blur_subframes)
79 && (Self::MIN_TEXTURE_BUDGET_BYTES..=Self::MAX_TEXTURE_BUDGET_BYTES)
80 .contains(&self.texture_budget_bytes)
81 {
82 Ok(())
83 } else {
84 Err(EngineError::InvalidQualityProfile)
85 }
86 }
87
88 pub fn render_size(self, settings: &ProjectSettings) -> Result<RenderSize, EngineError> {
93 self.validate()?;
94 let width = scaled_dimension(settings.width, self.resolution_scale)?;
95 let height = scaled_dimension(settings.height, self.resolution_scale)?;
96 RenderSize::new(width, height).map_err(EngineError::from)
97 }
98
99 pub fn export_size(self, settings: &ProjectSettings) -> Result<RenderSize, EngineError> {
109 let size = self.render_size(settings)?;
110 if size.width % 2 != 0 || size.height % 2 != 0 {
111 let even = |value: u32| match value {
112 1 => 2,
113 odd if odd % 2 != 0 => odd - 1,
114 even => even,
115 };
116 return Err(EngineError::OddExportDimensions {
117 width: size.width,
118 height: size.height,
119 suggested: (even(size.width), even(size.height)),
120 });
121 }
122 Ok(size)
123 }
124
125 pub fn benchmark_metadata(
130 self,
131 project: impl Into<String>,
132 settings: &ProjectSettings,
133 renderer: &alpha2_render::Renderer,
134 ) -> Result<alpha2_render::BenchmarkMetadata, EngineError> {
135 let size = self.render_size(settings)?;
136 Ok(alpha2_render::BenchmarkMetadata {
137 project: project.into(),
138 alpha2_version: env!("CARGO_PKG_VERSION"),
139 adapter: renderer.diagnostics().adapter,
140 dimensions: (size.width, size.height),
141 resolution_scale: self.resolution_scale,
142 max_filter_samples: self.max_filter_samples,
143 motion_blur_subframes: self.motion_blur_subframes,
144 texture_budget_bytes: self.texture_budget_bytes,
145 })
146 }
147}
148
149fn scaled_dimension(value: u32, scale: f32) -> Result<u32, EngineError> {
150 let scaled = f64::from(value) * f64::from(scale);
151 if scaled.is_finite() && scaled <= f64::from(u32::MAX) {
152 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
153 Ok((scaled.round() as u32).max(1))
154 } else {
155 Err(EngineError::OutOfRange)
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use alpha2_time::FrameRate;
162
163 use super::*;
164
165 #[test]
166 fn quality_scales_dimensions_and_rejects_zero() {
167 let settings = ProjectSettings::new(
168 1920,
169 1080,
170 FrameRate::new(30, 1).expect("valid rate"),
171 48_000,
172 )
173 .expect("valid settings");
174 assert_eq!(
175 QualityProfile::preview()
176 .render_size(&settings)
177 .expect("valid size"),
178 RenderSize::new(960, 540).expect("valid size")
179 );
180 assert_eq!(
181 QualityProfile {
182 resolution_scale: 0.0,
183 ..QualityProfile::default()
184 }
185 .validate(),
186 Err(EngineError::InvalidQualityProfile)
187 );
188 assert_eq!(
189 QualityProfile {
190 motion_blur_subframes: u32::MAX,
191 ..QualityProfile::default()
192 }
193 .validate(),
194 Err(EngineError::InvalidQualityProfile)
195 );
196 assert_eq!(
197 QualityProfile {
198 texture_budget_bytes: 1,
199 ..QualityProfile::default()
200 }
201 .validate(),
202 Err(EngineError::InvalidQualityProfile)
203 );
204 }
205}