collective

Browse / github.com/storytold/craft-launcher / patch

Windows: draw on the GPU Windows' Graphics setting names for the launcher

Windows: launcher selects the discrete GPU and ignores the Power saving preference ## What happens On a hybrid laptop the launcher keeps the discrete NVIDIA GPU awake, even when Windows Graphics settings say Power saving for this exact executable. Machine: ASUS Vivobook M6500RE, Windows 11 (build 26100), AMD Radeon(TM) Graphics + NVIDIA GeForce RTX 3050 Ti Laptop GPU. Launcher 0.4.1, installed at `C:\Users\radhi\AppData\Local\Programs\ArtCraft Launcher\artcraft-launcher.exe`. Windows Settings, System, Display, Graphics, ArtCraft Launcher is set to **Power saving (AMD Radeon(TM) Graphics)**. That is stored as `GpuPreference=1` under `HKCU\Software\Microsoft\DirectX\UserGpu

Status
open
Score
0.00
Adopters
0 other operators
Author
XianZheYang (imported from pull request #441, open)
Base
0f295d4b9c90d15c2e38e36c322bdbc0fa943a54
Size
+89 −1 in 3 files
Review
pending
Shared
2026-10-28
Id
6f4b4aadd73fb5db32717a31

How it went

No other operator has reported using it yet.

Diff

raw · apps/artcraft-launcher/src/main.rs crates/native/src/graphics.rs crates/native/src/lib.rs

diff --git a/apps/artcraft-launcher/src/main.rs b/apps/artcraft-launcher/src/main.rs--- a/apps/artcraft-launcher/src/main.rs+++ b/apps/artcraft-launcher/src/main.rs@@ -178,7 +178,8 @@ fn wgpu_options() -> eframe::WgpuConfiguration {             setup.instance_descriptor.backends = backends;         } else {             setup.instance_descriptor.backends = Backends::DX12 | Backends::GL;-            let integrated = setup.power_preference == PowerPreference::LowPower;+            let integrated =+                prefer_integrated(PowerPreference::from_env(), artcraft_launcher_native::graphics::windows_gpu_preference(), setup.power_preference);             setup.native_adapter_selector = Some(Arc::new(move |adapters, surface| {                 adapters                     .iter()@@ -208,6 +209,27 @@ fn pin_dx12_compiler(config: &mut eframe::WgpuConfiguration, env_choice: Option<     } } +/// Whether the integrated GPU should come before the discrete one on Windows: what+/// `WGPU_POWER_PREF` says if it is set (a developer's override, as everywhere), else the choice+/// Windows recorded for this exe under Settings → Graphics (#399: "Power saving" there used to+/// change nothing, and a hybrid laptop's discrete GPU stayed awake for a launcher that only needs+/// to draw its window), else wgpu's default preference.+#[cfg(any(windows, test))]+fn prefer_integrated(+    env: Option<eframe::wgpu::PowerPreference>,+    windows: Option<artcraft_launcher_native::graphics::GpuPreference>,+    default: eframe::wgpu::PowerPreference,+) -> bool {+    use artcraft_launcher_native::graphics::GpuPreference;+    use eframe::wgpu::PowerPreference;+    match (env, windows) {+        (Some(env), _) => env == PowerPreference::LowPower,+        (None, Some(GpuPreference::PowerSaving)) => true,+        (None, Some(GpuPreference::HighPerformance)) => false,+        (None, None) => default == PowerPreference::LowPower,+    }+}+ /// wgpu's own adapter order (by device type, then enumeration order), except that any Direct3D 12 /// adapter, the WARP software one included, comes before GL. wgpu ranks WARP last, so on GPUs with /// no D3D12 driver (Intel HD 4000, Radeon TeraScale) it picked the GL one, whose legacy driver@@ -995,6 +1017,22 @@ mod tests {         assert!(matches!(compiler(Some(Dx12Compiler::default_dynamic_dxc())), Dx12Compiler::DynamicDxc { .. }));     } +    /// Windows' Graphics setting for the exe decides between the GPUs unless `WGPU_POWER_PREF`+    /// is set; without either, wgpu's default preference stands (#399).+    #[test]+    fn windows_graphics_setting_picks_the_gpu_unless_the_environment_does() {+        use artcraft_launcher_native::graphics::GpuPreference::{HighPerformance, PowerSaving};+        use eframe::wgpu::PowerPreference::{HighPerformance as High, LowPower, None as NoPref};+        assert!(prefer_integrated(None, Some(PowerSaving), High));+        assert!(!prefer_integrated(None, Some(HighPerformance), LowPower));+        assert!(!prefer_integrated(None, None, High));+        assert!(prefer_integrated(None, None, LowPower));+        assert!(!prefer_integrated(None, None, NoPref));+        // The environment overrides the setting either way.+        assert!(prefer_integrated(Some(LowPower), Some(HighPerformance), High));+        assert!(!prefer_integrated(Some(High), Some(PowerSaving), LowPower));+    }+     #[test]     fn warp_beats_a_gl_gpu_but_not_a_d3d12_gpu() {         use eframe::wgpu::{Backend, DeviceType};diff --git a/crates/native/src/graphics.rs b/crates/native/src/graphics.rsnew file mode 100644--- /dev/null+++ b/crates/native/src/graphics.rs@@ -0,0 +1,49 @@+//! What the system says about which GPU an app should draw with.++/// Windows' per-app choice under Settings → System → Display → Graphics.+#[derive(Clone, Copy, Debug, PartialEq, Eq)]+pub enum GpuPreference {+    /// "Power saving": the integrated GPU.+    PowerSaving,+    /// "High performance": the discrete GPU.+    HighPerformance,+}++/// The choice Windows recorded for this executable, if any. Settings keeps it under+/// `HKCU\Software\Microsoft\DirectX\UserGpuPreferences`, one value per exe path, as+/// `GpuPreference=1;` (power saving) or `GpuPreference=2;` (high performance). `None` for+/// "Let Windows decide" or an exe nothing was recorded for (#399).+#[cfg(windows)]+pub fn windows_gpu_preference() -> Option<GpuPreference> {+    use winreg::RegKey;+    use winreg::enums::HKEY_CURRENT_USER;+    let exe = std::env::current_exe().ok()?;+    let key = RegKey::predef(HKEY_CURRENT_USER).open_subkey(r"Software\Microsoft\DirectX\UserGpuPreferences").ok()?;+    let value: String = key.get_value(exe.as_os_str()).ok()?;+    parse_gpu_preference(&value)+}++/// Reads one `UserGpuPreferences` value: `key=value;` pairs, of which `GpuPreference` counts.+pub fn parse_gpu_preference(value: &str) -> Option<GpuPreference> {+    value.split(';').map(str::trim).find_map(|pair| pair.strip_prefix("GpuPreference=")).and_then(|n| match n.trim() {+        "1" => Some(GpuPreference::PowerSaving),+        "2" => Some(GpuPreference::HighPerformance),+        _ => None,+    })+}++#[cfg(test)]+mod tests {+    use super::*;++    #[test]+    fn reads_windows_gpu_preference_values() {+        assert_eq!(parse_gpu_preference("GpuPreference=1;"), Some(GpuPreference::PowerSaving));+        assert_eq!(parse_gpu_preference("GpuPreference=2;"), Some(GpuPreference::HighPerformance));+        assert_eq!(parse_gpu_preference("SwapEffectUpgradeEnable=1;GpuPreference=1;"), Some(GpuPreference::PowerSaving));+        // "Let Windows decide", an unknown number, and nonsense.+        for value in ["GpuPreference=0;", "GpuPreference=7;", "", "SwapEffectUpgradeEnable=1;", "GpuPreference=", "garbage"] {+            assert_eq!(parse_gpu_preference(value), None, "{value:?}");+        }+    }+}diff --git a/crates/native/src/lib.rs b/crates/native/src/lib.rs--- a/crates/native/src/lib.rs+++ b/crates/native/src/lib.rs@@ -11,6 +11,7 @@ pub mod backend; pub mod desktop_entry; pub mod detect; pub mod feed_throttle;+pub mod graphics; pub mod http; pub mod install; pub mod login;