Skip to main content

cvkg_render_gpu/subsystems/
gpu_capabilities.rs

1//! P1-26: GPU capability detection.
2//!
3//! Detects GPU vendor and capabilities at startup. Used by the
4//! renderer to log GPU info and (in future) select shader
5//! variants appropriate for the available hardware.
6
7use std::fmt;
8
9/// Detected GPU vendor. This is a coarse classification derived
10/// from the adapter name string. It's used to enable
11/// vendor-specific workarounds in shaders or pipeline creation.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum GpuVendor {
14    Nvidia,
15    Amd,
16    Intel,
17    Apple,
18    Qualcomm,
19    Arm,
20    ImgTec,
21    Microsoft,
22    Mesa,
23    Broadcom,
24    Unknown,
25}
26
27impl fmt::Display for GpuVendor {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        let s = match self {
30            GpuVendor::Nvidia => "NVIDIA",
31            GpuVendor::Amd => "AMD",
32            GpuVendor::Intel => "Intel",
33            GpuVendor::Apple => "Apple",
34            GpuVendor::Qualcomm => "Qualcomm",
35            GpuVendor::Arm => "ARM",
36            GpuVendor::ImgTec => "Imagination",
37            GpuVendor::Microsoft => "Microsoft",
38            GpuVendor::Mesa => "Mesa",
39            GpuVendor::Broadcom => "Broadcom",
40            GpuVendor::Unknown => "Unknown",
41        };
42        f.write_str(s)
43    }
44}
45
46/// Detect GPU vendor from an adapter name string. The name
47/// comes from `wgpu::Adapter::get_info().name`.
48///
49/// This is a best-effort heuristic. Vendors may be misidentified
50/// when the adapter name is unusual or contains a substring
51/// from another vendor (e.g., an AMD card with "NVIDIA" in
52/// the user-customized name).
53pub fn detect_gpu_vendor(adapter_name: &str) -> GpuVendor {
54    let name = adapter_name.to_lowercase();
55    if name.contains("nvidia") || name.contains("geforce") || name.contains("quadro") || name.contains("tesla") {
56        GpuVendor::Nvidia
57    } else if name.contains("amd") || name.contains("radeon") || name.contains("rx ") || name.contains("firepro") {
58        GpuVendor::Amd
59    } else if name.contains("intel") || name.contains("uhd") || name.contains("iris") || name.contains("hd graphics") {
60        GpuVendor::Intel
61    } else if name.contains("apple") || name.contains("m1") || name.contains("m2") || name.contains("m3") {
62        GpuVendor::Apple
63    } else if name.contains("qualcomm") || name.contains("adreno") {
64        GpuVendor::Qualcomm
65    } else if name.contains("arm") || name.contains("mali") {
66        GpuVendor::Arm
67    } else if name.contains("imgtec") || name.contains("powervr") {
68        GpuVendor::ImgTec
69    } else if name.contains("microsoft") || name.contains("direct3d12") {
70        GpuVendor::Microsoft
71    } else if name.contains("mesa") || name.contains("llvmpipe") || name.contains("swiftshader") {
72        GpuVendor::Mesa
73    } else if name.contains("broadcom") || name.contains("videocore") {
74        GpuVendor::Broadcom
75    } else {
76        GpuVendor::Unknown
77    }
78}
79
80/// GPU capability summary. Currently just includes the vendor;
81/// future versions will add feature flags (e.g., bindless
82/// textures, advanced compute features).
83#[derive(Debug, Clone)]
84pub struct GpuCapabilities {
85    /// Detected vendor.
86    pub vendor: GpuVendor,
87    /// The original adapter name string.
88    pub adapter_name: String,
89    /// The wgpu backend in use (Vulkan, Metal, DX12, etc.).
90    pub backend: String,
91}
92
93impl GpuCapabilities {
94    /// Detect capabilities from an adapter name and backend.
95    pub fn detect(adapter_name: &str, backend: impl Into<String>) -> Self {
96        Self {
97            vendor: detect_gpu_vendor(adapter_name),
98            adapter_name: adapter_name.to_string(),
99            backend: backend.into(),
100        }
101    }
102}
103
104impl fmt::Display for GpuCapabilities {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(
107            f,
108            "{} GPU '{}' on {} backend",
109            self.vendor, self.adapter_name, self.backend
110        )
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn detects_nvidia() {
120        assert_eq!(detect_gpu_vendor("NVIDIA GeForce RTX 3080"), GpuVendor::Nvidia);
121        assert_eq!(detect_gpu_vendor("Quadro P4000"), GpuVendor::Nvidia);
122    }
123
124    #[test]
125    fn detects_amd() {
126        assert_eq!(detect_gpu_vendor("AMD Radeon RX 6800 XT"), GpuVendor::Amd);
127        assert_eq!(detect_gpu_vendor("Radeon Pro 5500M"), GpuVendor::Amd);
128    }
129
130    #[test]
131    fn detects_intel() {
132        assert_eq!(detect_gpu_vendor("Intel UHD Graphics 630"), GpuVendor::Intel);
133        assert_eq!(detect_gpu_vendor("Intel Iris Xe"), GpuVendor::Intel);
134    }
135
136    #[test]
137    fn detects_apple() {
138        assert_eq!(detect_gpu_vendor("Apple M1 Pro"), GpuVendor::Apple);
139        assert_eq!(detect_gpu_vendor("Apple M2 GPU"), GpuVendor::Apple);
140    }
141
142    #[test]
143    fn detects_qualcomm() {
144        assert_eq!(detect_gpu_vendor("Qualcomm Adreno 660"), GpuVendor::Qualcomm);
145    }
146
147    #[test]
148    fn detects_arm_mali() {
149        assert_eq!(detect_gpu_vendor("ARM Mali-G78"), GpuVendor::Arm);
150    }
151
152    #[test]
153    fn detects_mesa_software() {
154        assert_eq!(detect_gpu_vendor("Mesa llvmpipe"), GpuVendor::Mesa);
155        assert_eq!(detect_gpu_vendor("Google SwiftShader"), GpuVendor::Mesa);
156    }
157
158    #[test]
159    fn unknown_for_garbage() {
160        assert_eq!(detect_gpu_vendor("Custom XYZ GPU 9000"), GpuVendor::Unknown);
161    }
162
163    #[test]
164    fn case_insensitive() {
165        assert_eq!(detect_gpu_vendor("nvidia geforce gtx 1080"), GpuVendor::Nvidia);
166        assert_eq!(detect_gpu_vendor("AMD RADEON VII"), GpuVendor::Amd);
167    }
168
169    #[test]
170    fn capabilities_display() {
171        let caps = GpuCapabilities::detect("NVIDIA GeForce RTX 3080", "Vulkan");
172        let s = format!("{}", caps);
173        assert!(s.contains("NVIDIA"));
174        assert!(s.contains("GeForce"));
175        assert!(s.contains("Vulkan"));
176    }
177}