Skip to main content

embedded_3dgfx/
config.rs

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
153/// Resolve the runtime default cap profile.
154///
155/// Priority:
156/// 1. `desktop-unbounded` cargo feature => no caps
157/// 2. `EMBEDDED_3DGFX_CAPS=off` => no caps
158/// 3. `EMBEDDED_3DGFX_CAPS=m3|m4|m33|m55` => corresponding balanced/perf profile
159/// 4. Fallback => `DEFAULT_PROFILE_CAPS` (`PROFILE_M33_BALANCED`)
160pub 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}