1use crate::error::{BudgetKind, RenderError};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum QualityTier {
5 Fastest,
6 Balanced,
7 Quality,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum MaterialProfile {
12 Unlit,
13 Lambert,
14 SimpleSpecular,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct RenderDefaults {
19 pub quality_tier: QualityTier,
20 pub material_profile: MaterialProfile,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum DegradationStep {
25 RaisePriorityFloor(u8),
26 MeshDecimationStride(usize),
27 DowngradeQuality,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct DegradationPolicy<'a> {
32 pub steps: &'a [DegradationStep],
33}
34
35impl Default for RenderDefaults {
36 fn default() -> Self {
37 Self {
38 quality_tier: QualityTier::Balanced,
39 material_profile: MaterialProfile::Lambert,
40 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct ProfileCaps {
46 pub max_draw_primitives: usize,
47 pub max_meshes_per_frame: usize,
48 pub max_textures: usize,
49 pub max_width: usize,
50 pub max_height: usize,
51 pub max_triangles_per_mesh: usize,
52 pub max_vertices_per_mesh: usize,
53}
54
55impl ProfileCaps {
56 pub const fn validate_framebuffer(
57 &self,
58 width: usize,
59 height: usize,
60 ) -> Result<(), RenderError> {
61 if width > self.max_width || height > self.max_height {
62 return Err(RenderError::OutOfBudget(
63 BudgetKind::FramebufferDimensions {
64 width,
65 height,
66 max_width: self.max_width,
67 max_height: self.max_height,
68 },
69 ));
70 }
71 Ok(())
72 }
73}
74
75pub const PROFILE_M3_MIN: ProfileCaps = ProfileCaps {
76 max_draw_primitives: 1_024,
77 max_meshes_per_frame: 64,
78 max_textures: 4,
79 max_width: 240,
80 max_height: 240,
81 max_triangles_per_mesh: 2_048,
82 max_vertices_per_mesh: 2_048,
83};
84
85pub const PROFILE_M3_BALANCED: ProfileCaps = ProfileCaps {
86 max_draw_primitives: 1_536,
87 max_meshes_per_frame: 96,
88 max_textures: 6,
89 max_width: 320,
90 max_height: 240,
91 max_triangles_per_mesh: 3_072,
92 max_vertices_per_mesh: 3_072,
93};
94
95pub const PROFILE_M4_BALANCED: ProfileCaps = ProfileCaps {
96 max_draw_primitives: 2_048,
97 max_meshes_per_frame: 128,
98 max_textures: 8,
99 max_width: 320,
100 max_height: 240,
101 max_triangles_per_mesh: 4_096,
102 max_vertices_per_mesh: 4_096,
103};
104
105pub const PROFILE_M33_BALANCED: ProfileCaps = ProfileCaps {
106 max_draw_primitives: 2_048,
107 max_meshes_per_frame: 128,
108 max_textures: 8,
109 max_width: 320,
110 max_height: 240,
111 max_triangles_per_mesh: 4_096,
112 max_vertices_per_mesh: 4_096,
113};
114
115pub const PROFILE_M33_SECURE: ProfileCaps = ProfileCaps {
116 max_draw_primitives: 2_048,
117 max_meshes_per_frame: 128,
118 max_textures: 8,
119 max_width: 320,
120 max_height: 240,
121 max_triangles_per_mesh: 4_096,
122 max_vertices_per_mesh: 4_096,
123};
124
125pub const PROFILE_M55_PERF: ProfileCaps = ProfileCaps {
126 max_draw_primitives: 4_096,
127 max_meshes_per_frame: 256,
128 max_textures: 16,
129 max_width: 480,
130 max_height: 320,
131 max_triangles_per_mesh: 8_192,
132 max_vertices_per_mesh: 8_192,
133};
134
135pub const DEFAULT_PROFILE_CAPS: ProfileCaps = PROFILE_M33_BALANCED;
136
137pub fn render_defaults_for_profile(profile: ProfileCaps) -> RenderDefaults {
138 if profile.max_draw_primitives <= PROFILE_M3_BALANCED.max_draw_primitives {
139 return RenderDefaults {
140 quality_tier: QualityTier::Fastest,
141 material_profile: MaterialProfile::Unlit,
142 };
143 }
144 if profile.max_draw_primitives >= PROFILE_M55_PERF.max_draw_primitives {
145 return RenderDefaults {
146 quality_tier: QualityTier::Quality,
147 material_profile: MaterialProfile::SimpleSpecular,
148 };
149 }
150 RenderDefaults::default()
151}
152
153pub fn default_profile_caps() -> Option<ProfileCaps> {
161 if cfg!(feature = "desktop-unbounded") {
162 return None;
163 }
164
165 #[cfg(feature = "std")]
166 {
167 if let Ok(raw) = std::env::var("EMBEDDED_3DGFX_CAPS") {
168 let value = raw.trim().to_ascii_lowercase();
169 return match value.as_str() {
170 "off" | "none" | "unbounded" => None,
171 "m3" | "m3_balanced" => Some(PROFILE_M3_BALANCED),
172 "m4" | "m4_balanced" => Some(PROFILE_M4_BALANCED),
173 "m33" | "m33_balanced" => Some(PROFILE_M33_BALANCED),
174 "m55" | "m55_perf" => Some(PROFILE_M55_PERF),
175 _ => Some(DEFAULT_PROFILE_CAPS),
176 };
177 }
178 }
179
180 Some(DEFAULT_PROFILE_CAPS)
181}
182
183pub fn apply_default_caps(engine: &mut crate::K3dengine) {
184 if let Some(caps) = default_profile_caps() {
185 engine.set_caps(caps);
186 engine.apply_render_defaults(render_defaults_for_profile(caps));
187 } else {
188 engine.clear_caps();
189 engine.apply_render_defaults(RenderDefaults::default());
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_render_defaults_for_m3_prefers_fastest_unlit() {
199 let d = render_defaults_for_profile(PROFILE_M3_BALANCED);
200 assert_eq!(d.quality_tier, QualityTier::Fastest);
201 assert_eq!(d.material_profile, MaterialProfile::Unlit);
202 }
203
204 #[test]
205 fn test_render_defaults_for_m55_prefers_quality_specular() {
206 let d = render_defaults_for_profile(PROFILE_M55_PERF);
207 assert_eq!(d.quality_tier, QualityTier::Quality);
208 assert_eq!(d.material_profile, MaterialProfile::SimpleSpecular);
209 }
210}