feat(graphics): detect hybrid NVIDIA+iGPU and surface GPU + DE in Diagnostics

autoconfig_graphics gains a hybrid Intel/AMD + NVIDIA branch that keeps
GSK renderer on Auto and DMA-BUF off so the iGPU handles compositing
without the dGPU getting dragged in. detect_gpu_name is exposed and
labels hybrid setups explicitly (e.g. "NVIDIA (driver 580) + Intel
(i915)"). RuntimeDiagnostics gets runtime_gpu_name and
runtime_desktop_environment so the Diagnostics panel can show what the
app sees at runtime, which is the first thing useful to triage
performance reports from log uploads.

cc
This commit is contained in:
vicrodh committed 2026-05-14 00:36:27 -06:00
1 parent b06580afc1
commit cd035a34a4
3 files changed
+84 -24

No files matched your search

+66 -24
View File
@@ -149,38 +149,56 @@ fn is_virtual_machine() -> bool {
false
}
fn detect_gpu_name(nvidia: bool, amd: bool, intel: bool) -> String {
// Try to get a human-readable GPU name
pub fn detect_gpu_name(nvidia: bool, amd: bool, intel: bool) -> String {
// Hybrid laptops have more than one of these set; join the names
// so diagnostics surface the full picture instead of returning only
// the first vendor matched.
let mut parts: Vec<String> = Vec::new();
if nvidia {
if let Ok(version) = std::fs::read_to_string("/proc/driver/nvidia/version") {
if let Some(line) = version.lines().next() {
return format!("NVIDIA ({})", line.trim());
}
}
return "NVIDIA (driver loaded)".to_string();
parts.push(nvidia_name());
}
if amd {
// Try to read GPU model from DRM
if let Ok(entries) = std::fs::read_dir("/sys/class/drm") {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with("card") && !name.contains('-') {
let model_path = entry.path().join("device/product_name");
if let Ok(model) = std::fs::read_to_string(&model_path) {
let model = model.trim();
if !model.is_empty() {
return format!("AMD {}", model);
}
parts.push(amd_name());
}
if intel {
parts.push(intel_name());
}
if parts.is_empty() {
"Unknown / None detected".to_string()
} else {
parts.join(" + ")
}
}
fn nvidia_name() -> String {
if let Ok(version) = std::fs::read_to_string("/proc/driver/nvidia/version") {
if let Some(line) = version.lines().next() {
return format!("NVIDIA ({})", line.trim());
}
}
"NVIDIA (driver loaded)".to_string()
}
fn amd_name() -> String {
if let Ok(entries) = std::fs::read_dir("/sys/class/drm") {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with("card") && !name.contains('-') {
let model_path = entry.path().join("device/product_name");
if let Ok(model) = std::fs::read_to_string(&model_path) {
let model = model.trim();
if !model.is_empty() {
return format!("AMD {}", model);
}
}
}
}
return "AMD (amdgpu driver loaded)".to_string();
}
if intel {
return "Intel (i915 driver loaded)".to_string();
}
"Unknown / None detected".to_string()
"AMD (amdgpu driver loaded)".to_string()
}
fn intel_name() -> String {
"Intel (i915/xe driver loaded)".to_string()
}
fn detect_desktop() -> String {
@@ -228,6 +246,30 @@ pub fn compute_recommendation(env: &Environment) -> Recommendation {
let is_wayland = env.display_server == "Wayland";
let desktop_lower = env.desktop.to_lowercase();
let is_gnome = desktop_lower.contains("gnome");
let has_hybrid_igpu = env.gpu_nvidia && (env.gpu_intel || env.gpu_amd);
// Hybrid laptops (NVIDIA dGPU + Intel/AMD iGPU). WebKit composes via
// the iGPU through EGL/GLX defaults — the NVIDIA card sits idle for
// PRIME render offload. Forcing GSK_RENDERER=gl here was hurting
// performance because the GL renderer biases toward the dGPU stack
// even when the iGPU is the actual paint target. Auto (None) lets
// GTK4 pick NGL/Vulkan as appropriate for the iGPU. DMA-BUF stays
// disabled — that one is fragile on any NVIDIA-touching setup.
if has_hybrid_igpu {
let igpu_label = if env.gpu_intel { "Intel" } else { "AMD" };
rationale.push(format!(
"NVIDIA + {} hybrid: iGPU handles WebKit compositing, leaving GSK at Auto",
igpu_label
));
return Recommendation {
hardware_acceleration: true,
force_x11: false,
gsk_renderer: None,
disable_dmabuf: true,
disable_blur_background: false,
rationale,
};
}
// NVIDIA + Wayland + GNOME: known stutter combo
if env.gpu_nvidia && is_wayland && is_gnome {
+14
View File
@@ -69,6 +69,11 @@ pub struct RuntimeDiagnostics {
pub runtime_is_vm: bool,
pub runtime_hw_accel_enabled: bool,
pub runtime_force_x11_active: bool,
/// Human-readable GPU model name (driver-reported on Linux).
/// For hybrid laptops joins both vendors: "NVIDIA (...) + Intel (...)".
pub runtime_gpu_name: String,
/// Desktop environment string ($XDG_CURRENT_DESKTOP or fallbacks).
pub runtime_desktop_environment: String,
// Developer settings
pub dev_force_dmabuf: bool,
@@ -152,6 +157,15 @@ pub fn v2_get_runtime_diagnostics(
runtime_is_vm: gfx_status.is_vm,
runtime_hw_accel_enabled: gfx_status.hardware_accel_enabled,
runtime_force_x11_active: gfx_status.force_x11_active,
runtime_gpu_name: crate::autoconfig_graphics::detect_gpu_name(
gfx_status.has_nvidia,
gfx_status.has_amd,
gfx_status.has_intel,
),
runtime_desktop_environment: std::env::var("XDG_CURRENT_DESKTOP")
.or_else(|_| std::env::var("XDG_SESSION_DESKTOP"))
.or_else(|_| std::env::var("DESKTOP_SESSION"))
.unwrap_or_else(|_| "Unknown".to_string()),
dev_force_dmabuf: dev.force_dmabuf,
@@ -40,6 +40,8 @@
runtimeIsVm: boolean;
runtimeHwAccelEnabled: boolean;
runtimeForceX11Active: boolean;
runtimeGpuName: string;
runtimeDesktopEnvironment: string;
devForceDmabuf: boolean;
envWebkitDisableDmabuf: string | null;
envWebkitDisableCompositing: string | null;
@@ -306,9 +308,11 @@
{ label: 'GDK Scale', saved: str(diag.gfxGdkScale), runtime: '—', status: 'info' },
{ label: 'GDK DPI Scale', saved: str(diag.gfxGdkDpiScale), runtime: '—', status: 'info' },
{ label: 'Compositing Mode', saved: '—', runtime: compositing, status: 'info' },
{ label: 'GPU', saved: '—', runtime: diag.runtimeGpuName || 'Unknown', status: 'info' },
{ label: 'GPU: NVIDIA', saved: '—', runtime: diag.runtimeHasNvidia ? 'Detected' : 'No', status: 'info' },
{ label: 'GPU: Intel', saved: '—', runtime: diag.runtimeHasIntel ? 'Detected' : 'No', status: 'info' },
{ label: 'GPU: AMD', saved: '—', runtime: diag.runtimeHasAmd ? 'Detected' : 'No', status: 'info' },
{ label: 'Desktop Environment', saved: '—', runtime: diag.runtimeDesktopEnvironment || 'Unknown', status: 'info' },
{ label: 'Wayland', saved: '—', runtime: diag.runtimeIsWayland ? 'Yes' : 'No (X11)', status: 'info' },
{ label: 'VM', saved: '—', runtime: diag.runtimeIsVm ? 'Yes' : 'No', status: 'info' },
{ label: 'Using Fallback', saved: '—', runtime: bool(diag.runtimeUsingFallback), status: diag.runtimeUsingFallback ? 'mismatch' : 'info' },