Linux: draw on the desktop's GPU, not always the discrete one (#164)

* Linux: draw on the desktop's GPU, not always the discrete one

egui asks wgpu for the high-performance adapter. On a hybrid laptop in
PRIME on-demand mode (Intel + NVIDIA, X11, GNOME) that is the NVIDIA GPU
while the display server runs on the Intel one, so every frame is copied
between the GPUs. During a resize the window then shows stale or garbled
frames and can stop drawing until it is resized again.

On Linux and the BSDs, ask for no power preference instead: wgpu then
takes the first adapter Vulkan lists, and Mesa's device-select layer lists
the display server's GPU first (or the one DRI_PRIME picks). Measured on
an RTX 4060 + Intel RPL-S laptop: in on-demand mode the list starts with
the Intel GPU, in `prime-select nvidia` mode with the NVIDIA one, so the
launcher draws on whichever GPU drives the screens. WGPU_POWER_PREF still
overrides it. Windows and macOS keep their current choice.

Refs #69

* Linux GPU: keep wgpu's adapter order, but a software rasterizer last

Without Mesa's device-select layer the Vulkan order follows the ICD files, and lavapipe can come
before the real driver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-12 02:50:10 -04:00
1 parent 1c03e2d2ea
commit 78ef4a8c77
1 file changed
+45 -1
+45 -1
View File
@@ -218,7 +218,38 @@ fn adapter_rank(backend: eframe::wgpu::Backend, device_type: eframe::wgpu::Devic
(backend != Backend::Dx12, device)
}
#[cfg(not(windows))]
/// wgpu's adapter on Linux and the BSDs: the GPU the desktop runs on. egui asks for the
/// high-performance GPU, which on a hybrid laptop in PRIME on-demand mode is the discrete one while
/// the display server runs on the integrated one: every frame then crosses between the GPUs, and
/// while the window is resized it shows stale or garbled frames and can stop drawing (issue #69).
/// With no preference wgpu takes the first adapter Vulkan lists, and Mesa's device-select layer
/// lists the display server's GPU first (or the one `DRI_PRIME` picks). Without that layer the
/// order follows the ICD files, where lavapipe (`lvp_icd`) can sort before the real driver, so a
/// software rasterizer only wins when nothing else can draw. `WGPU_POWER_PREF` (`low`, `high` or
/// `none`) still overrides it, and then wgpu picks the adapter itself.
#[cfg(not(any(windows, target_os = "macos")))]
fn wgpu_options() -> eframe::WgpuConfiguration {
use eframe::wgpu::{DeviceType, PowerPreference};
let mut config = eframe::WgpuConfiguration::default();
if let eframe::egui_wgpu::WgpuSetup::CreateNew(setup) = &mut config.wgpu_setup {
if let Some(preference) = PowerPreference::from_env() {
setup.power_preference = preference;
} else {
setup.power_preference = PowerPreference::None;
setup.native_adapter_selector = Some(Arc::new(|adapters, surface| {
adapters
.iter()
.filter(|a| surface.is_none_or(|s| a.is_surface_supported(s)))
.min_by_key(|a| a.get_info().device_type == DeviceType::Cpu)
.cloned()
.ok_or_else(|| "no graphics adapter can draw to the window".to_owned())
}));
}
}
config
}
#[cfg(target_os = "macos")]
fn wgpu_options() -> eframe::WgpuConfiguration {
eframe::WgpuConfiguration::default()
}
@@ -970,4 +1001,17 @@ mod tests {
assert!(adapter_rank(Backend::Dx12, discrete, false) < adapter_rank(Backend::Dx12, integrated, false));
assert!(adapter_rank(Backend::Dx12, integrated, true) < adapter_rank(Backend::Dx12, discrete, true));
}
#[cfg(not(any(windows, target_os = "macos")))]
#[test]
fn linux_draws_on_the_desktops_gpu_unless_told_otherwise() {
use eframe::wgpu::PowerPreference;
let config = wgpu_options();
let eframe::egui_wgpu::WgpuSetup::CreateNew(setup) = &config.wgpu_setup else {
panic!("the launcher creates its own wgpu instance");
};
// egui would ask for the discrete GPU: on a PRIME laptop that's not the desktop's (issue #69).
let expected = PowerPreference::from_env().unwrap_or(PowerPreference::None);
assert_eq!(setup.power_preference, expected);
}
}