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
@@ -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};new file mode 100644@@ -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:?}");+ }+ }+}@@ -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;