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:
3 files changed
+84
-24
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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' },
|
||||
|
||||
Reference in new issue
Block a user