feat: prefer the dedicated gpu on windows (#792)
This commit is contained in:
parent
e40c0185c4
commit
86145c7180
6 changed files with 168 additions and 2 deletions
|
|
@ -1,11 +1,15 @@
|
|||
use freya::prelude::*;
|
||||
use oneclient_common::Patch;
|
||||
use oneclient_core::settings::{PackageUpdateMode, ProfileUpdate, Resolution};
|
||||
#[cfg(windows)]
|
||||
use oneclient_core::settings::LauncherSettings;
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{
|
||||
Dropdown, Icon, IconType, TextInput, memory_field, toggle, validate_number,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use crate::components::toggle_controlled;
|
||||
use crate::hooks::{use_dispatch, use_settings_snapshot};
|
||||
use crate::theme::colors;
|
||||
use crate::view::app::settings::{section_header, settings_row};
|
||||
|
|
@ -20,7 +24,8 @@ pub struct SettingsMinecraft;
|
|||
|
||||
impl Component for SettingsMinecraft {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let profile = use_settings_snapshot().settings.global_game_settings;
|
||||
let settings = use_settings_snapshot().settings;
|
||||
let profile = settings.global_game_settings.clone();
|
||||
let dispatch = use_dispatch();
|
||||
|
||||
let fullscreen = use_state({
|
||||
|
|
@ -146,6 +151,14 @@ impl Component for SettingsMinecraft {
|
|||
.width(Size::px(220.)),
|
||||
));
|
||||
|
||||
#[cfg(windows)]
|
||||
let page = page.child(settings_row(
|
||||
IconType::Rocket02,
|
||||
"Prefer Dedicated GPU",
|
||||
"Ask Windows to run Java on the high-performance GPU.",
|
||||
discrete_gpu_field(settings, dispatch.clone()),
|
||||
));
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let page = page
|
||||
.child(section_header("GRAPHICS"))
|
||||
|
|
@ -218,6 +231,20 @@ fn build_update(
|
|||
}
|
||||
}
|
||||
|
||||
/// A launcher setting rather than a profile field, so it bypasses [`build_update`]
|
||||
#[cfg(windows)]
|
||||
fn discrete_gpu_field(settings: LauncherSettings, dispatch: crate::Actions) -> impl IntoElement {
|
||||
let on = settings.use_discrete_gpu;
|
||||
let on_toggle: EventHandler<()> = (move |()| {
|
||||
let mut next = settings.clone();
|
||||
next.use_discrete_gpu = !on;
|
||||
dispatch.set_settings(next);
|
||||
})
|
||||
.into();
|
||||
|
||||
toggle_controlled(on, on_toggle)
|
||||
}
|
||||
|
||||
/// Dispatched separately from [`build_update`] which debounces keystrokes a dropdown has no intermediate states
|
||||
fn update_mode_field(
|
||||
selected: PackageUpdateMode,
|
||||
|
|
|
|||
|
|
@ -294,6 +294,11 @@ pub async fn launch_cluster(
|
|||
);
|
||||
tracing::debug!(cluster_id, ?jvm_args, main_class = %version_info.main_class, "jvm arguments");
|
||||
|
||||
let use_discrete_gpu = state.settings.read().use_discrete_gpu;
|
||||
if use_discrete_gpu {
|
||||
oneclient_java::prefer_dedicated_gpu(std::path::Path::new(&java.absolute_path)).await;
|
||||
}
|
||||
|
||||
let mut command = base_command(&profile, &java.absolute_path);
|
||||
apply_env(&mut command, &profile);
|
||||
command
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ pub struct LauncherSettings {
|
|||
pub auto_update: bool,
|
||||
pub crash_reporting: bool,
|
||||
pub enable_gamemode: bool,
|
||||
pub use_discrete_gpu: bool,
|
||||
pub discord_enabled: bool,
|
||||
pub max_concurrent_requests: usize,
|
||||
pub global_game_settings: GameSettingsProfile,
|
||||
|
|
@ -82,6 +83,7 @@ impl Default for LauncherSettings {
|
|||
crash_reporting: true,
|
||||
discord_enabled: true,
|
||||
enable_gamemode: false,
|
||||
use_discrete_gpu: false,
|
||||
max_concurrent_requests: 25,
|
||||
global_game_settings: GameSettingsProfile::default_global_profile(),
|
||||
allow_parallel_running_clusters: false,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ pub use data::{JavaPackage, JavaRuntime, PackageArchive, java_executable_relativ
|
|||
pub use error::{JavaError, JavaResult};
|
||||
pub use install::{install_package, is_launcher_managed};
|
||||
pub use locate::{LocatedJava, best_for_major, locate_java};
|
||||
pub use platform::{HostArch, HostOs, HostTarget};
|
||||
pub use platform::{HostArch, HostOs, HostTarget, prefer_dedicated_gpu};
|
||||
pub use service::{
|
||||
AvailableJava, INSTALLABLE_MAJORS, JAVA_CHOICE_DOWNLOAD, JAVA_CHOICE_FOLDER, JAVA_VENDOR_HINT,
|
||||
JavaService,
|
||||
|
|
|
|||
|
|
@ -76,6 +76,136 @@ impl HostTarget {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
const GPU_PREFERENCES_KEY: &str = r"HKCU\Software\Microsoft\DirectX\UserGpuPreferences";
|
||||
|
||||
/// A JVM is started through either stub so both are registered
|
||||
#[cfg(windows)]
|
||||
const GPU_PREFERENCE_EXECUTABLES: [&str; 2] = ["javaw.exe", "java.exe"];
|
||||
|
||||
/// Absolute so a `reg.exe` earlier in the search order cannot run in its place
|
||||
#[cfg(windows)]
|
||||
fn reg_command() -> tokio::process::Command {
|
||||
const NO_WINDOW: u32 = 0x0800_0000;
|
||||
|
||||
let system_root = std::env::var_os("SystemRoot").unwrap_or_else(|| r"C:\Windows".into());
|
||||
let exe = std::path::Path::new(&system_root)
|
||||
.join("System32")
|
||||
.join("reg.exe");
|
||||
|
||||
let mut command = tokio::process::Command::new(exe);
|
||||
command.creation_flags(NO_WINDOW);
|
||||
command
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn registered_value<'a>(query: &'a str, name: &str) -> Option<&'a str> {
|
||||
query.lines().find_map(|line| {
|
||||
let (value_name, data) = line.split_once("REG_SZ")?;
|
||||
if value_name.trim().eq_ignore_ascii_case(name) {
|
||||
Some(data.trim())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Windows writes `GpuPreference=0;` ("let Windows decide") for any app added
|
||||
/// through Settings, so only 1 or 2 is a choice worth leaving alone
|
||||
#[cfg(windows)]
|
||||
fn has_explicit_preference(data: &str) -> bool {
|
||||
data.split(';')
|
||||
.find_map(|part| part.trim().strip_prefix("GpuPreference="))
|
||||
.is_some_and(|preference| matches!(preference.trim(), "1" | "2"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub async fn prefer_dedicated_gpu(java_path: &std::path::Path) {
|
||||
let Some(dir) = java_path.parent() else { return };
|
||||
|
||||
let mut targets = Vec::new();
|
||||
for exe in GPU_PREFERENCE_EXECUTABLES {
|
||||
let path = dir.join(exe);
|
||||
if polyio::try_exists(&path).await.unwrap_or(false) {
|
||||
targets.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
if targets.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// One query for the whole key instead of one per executable
|
||||
let query = match reg_command()
|
||||
.args(["query", GPU_PREFERENCES_KEY])
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
Ok(out) => String::from_utf8_lossy(&out.stdout).into_owned(),
|
||||
Err(err) => {
|
||||
tracing::warn!(%err, "could not run reg.exe");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for path in targets {
|
||||
if registered_value(&query, &path.to_string_lossy()).is_some_and(has_explicit_preference) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let added = reg_command()
|
||||
.args(["add", GPU_PREFERENCES_KEY, "/v"])
|
||||
.arg(&path)
|
||||
.args(["/t", "REG_SZ", "/d", "GpuPreference=2;", "/f"])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
match added {
|
||||
Ok(out) if out.status.success() => {
|
||||
tracing::info!(path = %path.display(), "registered JVM for high-performance GPU");
|
||||
}
|
||||
Ok(out) => tracing::warn!(
|
||||
path = %path.display(),
|
||||
stderr = %String::from_utf8_lossy(&out.stderr).trim(),
|
||||
"could not set GPU preference"
|
||||
),
|
||||
Err(err) => tracing::warn!(%err, "could not run reg.exe"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtimes install into version-stamped folders so without this the key keeps
|
||||
/// a dead entry for every upgrade
|
||||
#[cfg(windows)]
|
||||
pub async fn forget_dedicated_gpu(java_path: &std::path::Path) {
|
||||
let Some(dir) = java_path.parent() else { return };
|
||||
|
||||
for exe in GPU_PREFERENCE_EXECUTABLES {
|
||||
let path = dir.join(exe);
|
||||
let removed = reg_command()
|
||||
.args(["delete", GPU_PREFERENCES_KEY, "/v"])
|
||||
.arg(&path)
|
||||
.arg("/f")
|
||||
.output()
|
||||
.await;
|
||||
|
||||
match removed {
|
||||
Ok(out) if out.status.success() => {
|
||||
tracing::info!(path = %path.display(), "removed GPU preference");
|
||||
}
|
||||
// A value that was never written is reported as an error
|
||||
Ok(_) => {}
|
||||
Err(err) => tracing::warn!(%err, "could not run reg.exe"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub async fn prefer_dedicated_gpu(_java_path: &std::path::Path) {}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub async fn forget_dedicated_gpu(_java_path: &std::path::Path) {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -156,6 +156,8 @@ impl JavaService {
|
|||
pub async fn remove_runtime(&self, absolute_path: &str) -> JavaResult<()> {
|
||||
self.store.delete_by_path(absolute_path).await?;
|
||||
|
||||
crate::platform::forget_dedicated_gpu(Path::new(absolute_path)).await;
|
||||
|
||||
let removed_files =
|
||||
match crate::install::remove_installed_package(Path::new(absolute_path)).await {
|
||||
Ok(removed) => removed,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue