fix: url opening, dgpu dropdown instead of switcher, invisible icons (#941)
This commit is contained in:
parent
abf708fdb4
commit
e7ddd85960
15 changed files with 277 additions and 547 deletions
7
.changeset/choose_which_gpu_the_game_renders_on.md
Normal file
7
.changeset/choose_which_gpu_the_game_renders_on.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
default: minor
|
||||
---
|
||||
|
||||
# Choose which GPU the game renders on
|
||||
|
||||
The discrete GPU toggle is now a picker. Linux lists each graphics card by name, Windows offers integrated or dedicated.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
default: patch
|
||||
---
|
||||
|
||||
# Icons show on Linux systems with a comma decimal locale
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
default: patch
|
||||
---
|
||||
|
||||
# Open folder and open in browser work again on Linux
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -4928,6 +4928,7 @@ dependencies = [
|
|||
"freya-skia-safe",
|
||||
"freya-testing",
|
||||
"futures-util",
|
||||
"gtk-sys",
|
||||
"image",
|
||||
"libloading 0.8.9",
|
||||
"mimalloc",
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ mimalloc = "=0.1.52"
|
|||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libloading = "=0.8.9"
|
||||
gtk-sys = "=0.18.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "=0.62.2", features = [
|
||||
|
|
|
|||
|
|
@ -98,6 +98,11 @@ impl App for OneClientApp {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(target_os = "linux")]
|
||||
unsafe {
|
||||
gtk_sys::gtk_disable_setlocale();
|
||||
}
|
||||
|
||||
let cli = cli::parse();
|
||||
|
||||
let mut builder = Builder::new_multi_thread();
|
||||
|
|
|
|||
|
|
@ -108,6 +108,28 @@ fn has_allowed_scheme(url: &str) -> bool {
|
|||
.any(|allowed| scheme.eq_ignore_ascii_case(allowed))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn open_target(target: &str) {
|
||||
use std::process::Stdio;
|
||||
|
||||
let spawned = open::commands(target).into_iter().find_map(|mut command| {
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.ok()
|
||||
});
|
||||
|
||||
match spawned {
|
||||
Some(mut child) => {
|
||||
std::thread::spawn(move || child.wait());
|
||||
}
|
||||
None => tracing::warn!("failed to open {target}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn open_target(target: &str) {
|
||||
if let Err(err) = open::that_detached(target) {
|
||||
tracing::warn!("failed to open {target}: {err}");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use freya::prelude::*;
|
||||
use oneclient_common::Patch;
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
use oneclient_core::settings::SettingsOsExtra;
|
||||
use oneclient_core::settings::{GameSettingsProfile, PackageUpdateMode, ProfileUpdate, Resolution};
|
||||
use oneclient_core::settings::{
|
||||
GameSettingsProfile, PackageUpdateMode, ProfileUpdate, Resolution, SettingsOsExtra,
|
||||
};
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{
|
||||
|
|
@ -65,11 +65,11 @@ impl Component for SettingsMinecraft {
|
|||
move || v
|
||||
});
|
||||
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
let discrete_gpu = use_state({
|
||||
let v = profile.use_discrete_gpu();
|
||||
let gpu = use_state({
|
||||
let v = profile.gpu().map(str::to_owned);
|
||||
move || v
|
||||
});
|
||||
let gpus = use_state(oneclient_core::game::gpu::detect);
|
||||
|
||||
let mut first = use_state(|| true);
|
||||
use_side_effect(move || {
|
||||
|
|
@ -84,22 +84,17 @@ impl Component for SettingsMinecraft {
|
|||
&post_exit_command.read(),
|
||||
*update_mode.read(),
|
||||
);
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
let gpu = *discrete_gpu.read();
|
||||
let selected_gpu = gpu.read().clone();
|
||||
if *first.peek() {
|
||||
first.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
let update = with_discrete_gpu(update, gpu);
|
||||
let update = with_gpu(update, selected_gpu);
|
||||
|
||||
dispatch.update_global_profile(update);
|
||||
});
|
||||
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
let discrete_gpu_default = SettingsOsExtra::default().use_discrete_gpu.unwrap_or(true);
|
||||
|
||||
let page = settings_page()
|
||||
.child(section_header("GAME"))
|
||||
.child(settings_row(
|
||||
|
|
@ -211,34 +206,56 @@ impl Component for SettingsMinecraft {
|
|||
),
|
||||
));
|
||||
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
let page = page
|
||||
.child(section_header("GRAPHICS"))
|
||||
.child(settings_row(
|
||||
let page = if gpus.read().is_empty() {
|
||||
page
|
||||
} else {
|
||||
page.child(section_header("GRAPHICS")).child(settings_row(
|
||||
IconType::Rocket02,
|
||||
"Use Discrete GPU",
|
||||
"Render the game on the dedicated graphics card. Does nothing on a machine with only one GPU, and draws noticeably more power on a laptop.",
|
||||
resettable(toggle(discrete_gpu), discrete_gpu, discrete_gpu_default),
|
||||
));
|
||||
"GPU",
|
||||
"The graphics card the game renders on. System default leaves the choice to your drivers.",
|
||||
resettable(gpu_field(gpu, &gpus.read()), gpu, None),
|
||||
))
|
||||
};
|
||||
|
||||
page.into_element()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
fn with_discrete_gpu(mut update: ProfileUpdate, on: bool) -> ProfileUpdate {
|
||||
fn with_gpu(mut update: ProfileUpdate, gpu: Option<String>) -> ProfileUpdate {
|
||||
let base = crate::launcher::state()
|
||||
.ok()
|
||||
.and_then(|state| state.settings.read().global_game_settings.os_extra.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
update.os_extra = Patch::Set(SettingsOsExtra {
|
||||
use_discrete_gpu: Some(on),
|
||||
..base
|
||||
});
|
||||
update.os_extra = Patch::Set(SettingsOsExtra { gpu, ..base });
|
||||
update
|
||||
}
|
||||
|
||||
fn gpu_field(
|
||||
mut selected: State<Option<String>>,
|
||||
gpus: &[oneclient_core::game::gpu::Gpu],
|
||||
) -> impl IntoElement {
|
||||
const SYSTEM_DEFAULT: &str = "System default";
|
||||
|
||||
let current = selected
|
||||
.read()
|
||||
.as_deref()
|
||||
.and_then(|id| gpus.iter().find(|gpu| gpu.id == id))
|
||||
.map_or(SYSTEM_DEFAULT.to_string(), |gpu| gpu.name.clone());
|
||||
|
||||
let options = std::iter::once(SYSTEM_DEFAULT.to_string())
|
||||
.chain(gpus.iter().map(|gpu| gpu.name.clone()))
|
||||
.collect();
|
||||
let ids: Vec<String> = gpus.iter().map(|gpu| gpu.id.clone()).collect();
|
||||
|
||||
Dropdown::new(current, options)
|
||||
.width(Size::px(220.))
|
||||
.height(Size::px(34.))
|
||||
.on_select(move |idx: usize| {
|
||||
selected.set(idx.checked_sub(1).and_then(|i| ids.get(i).cloned()));
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_update(
|
||||
fullscreen: bool,
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ pub use oneclient_common::domain::PackageUpdateMode;
|
|||
pub struct SettingsOsExtra {
|
||||
#[cfg(target_os = "linux")]
|
||||
pub enable_gamemode: Option<bool>,
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
pub use_discrete_gpu: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gpu: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub unknown: serde_json::Map<String, serde_json::Value>,
|
||||
}
|
||||
|
|
@ -42,8 +42,7 @@ impl Default for SettingsOsExtra {
|
|||
Self {
|
||||
#[cfg(target_os = "linux")]
|
||||
enable_gamemode: Some(true),
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
use_discrete_gpu: Some(true),
|
||||
gpu: None,
|
||||
unknown: serde_json::Map::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -67,15 +66,8 @@ impl GameSettingsProfile {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn use_discrete_gpu(&self) -> bool {
|
||||
cfg_select! {
|
||||
any(target_os = "linux", target_os = "windows") => self
|
||||
.os_extra
|
||||
.as_ref()
|
||||
.and_then(|extra| extra.use_discrete_gpu)
|
||||
.unwrap_or(true),
|
||||
_ => false
|
||||
}
|
||||
pub fn gpu(&self) -> Option<&str> {
|
||||
self.os_extra.as_ref()?.gpu.as_deref()
|
||||
}
|
||||
|
||||
pub fn is_global(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
const INTEL_INTEGRATED: &str = "0000:00:02.0";
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Driver {
|
||||
|
|
@ -41,53 +39,25 @@ impl Driver {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Gpu {
|
||||
pub struct Card {
|
||||
pub pci_address: String,
|
||||
pub boot_vga: Option<bool>,
|
||||
pub name: String,
|
||||
pub driver: Driver,
|
||||
pub render_node: Option<PathBuf>,
|
||||
pub discrete: bool,
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
fn nvidia_proprietary(&self) -> bool {
|
||||
self.driver == Driver::Nvidia
|
||||
}
|
||||
|
||||
fn can_render(&self) -> bool {
|
||||
self.render_node.is_some() || self.nvidia_proprietary()
|
||||
}
|
||||
|
||||
fn priority(&self) -> u32 {
|
||||
u32::from(self.discrete) * 2 + u32::from(self.boot_vga == Some(true))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offload_env(gpus: &[Gpu]) -> Vec<(&'static str, String)> {
|
||||
let Some(best) = gpus
|
||||
.iter()
|
||||
.filter(|gpu| gpu.can_render())
|
||||
.max_by_key(|gpu| gpu.priority())
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
if !best.discrete || best.boot_vga == Some(true) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut env = if best.nvidia_proprietary() {
|
||||
pub fn offload_env(card: &Card) -> Vec<(&'static str, String)> {
|
||||
let mut env = if card.driver == Driver::Nvidia {
|
||||
vec![
|
||||
("__NV_PRIME_RENDER_OFFLOAD", "1".to_string()),
|
||||
("__VK_LAYER_NV_optimus", "NVIDIA_only".to_string()),
|
||||
("__GLX_VENDOR_LIBRARY_NAME", "nvidia".to_string()),
|
||||
]
|
||||
} else {
|
||||
let prime = pci_tag(&best.pci_address).unwrap_or_else(|| "1".to_string());
|
||||
let prime = format!("pci-{}", card.pci_address.replace([':', '.'], "_"));
|
||||
vec![("DRI_PRIME", prime)]
|
||||
};
|
||||
|
||||
if let Some(icd) = best.driver.vulkan_icd() {
|
||||
if let Some(icd) = card.driver.vulkan_icd() {
|
||||
env.push(("VK_LOADER_DRIVERS_SELECT", icd.to_string()));
|
||||
}
|
||||
|
||||
|
|
@ -95,22 +65,25 @@ pub fn offload_env(gpus: &[Gpu]) -> Vec<(&'static str, String)> {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn detect() -> Vec<Gpu> {
|
||||
let mut gpus = read_pci_devices(Path::new("/sys/bus/pci/devices"));
|
||||
pub fn detect() -> Vec<Card> {
|
||||
let ids = [
|
||||
"/usr/share/hwdata/pci.ids",
|
||||
"/usr/share/misc/pci.ids",
|
||||
"/usr/share/pci.ids",
|
||||
]
|
||||
.iter()
|
||||
.find_map(|path| std::fs::read_to_string(path).ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
for gpu in &mut gpus {
|
||||
gpu.discrete = probe_discrete(gpu);
|
||||
}
|
||||
|
||||
gpus
|
||||
read_pci_devices(Path::new("/sys/bus/pci/devices"), &ids)
|
||||
}
|
||||
|
||||
fn read_pci_devices(root: &Path) -> Vec<Gpu> {
|
||||
fn read_pci_devices(root: &Path, ids: &str) -> Vec<Card> {
|
||||
let Ok(entries) = std::fs::read_dir(root) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut gpus = Vec::new();
|
||||
let mut cards = Vec::new();
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
|
|
@ -120,28 +93,30 @@ fn read_pci_devices(root: &Path) -> Vec<Gpu> {
|
|||
|
||||
let device = entry.path();
|
||||
|
||||
if read_class(&device.join("class")).is_none_or(|class| class >> 16 != 0x03) {
|
||||
if read_hex(&device.join("class")).is_none_or(|class| class >> 16 != 0x03) {
|
||||
continue;
|
||||
}
|
||||
|
||||
gpus.push(Gpu {
|
||||
let driver = Driver::from_name(read_link_name(&device.join("driver")).as_deref());
|
||||
if driver != Driver::Nvidia && !has_render_node(&device.join("drm")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let vendor = read_hex(&device.join("vendor")).unwrap_or(0);
|
||||
let model = read_hex(&device.join("device")).unwrap_or(0);
|
||||
|
||||
cards.push(Card {
|
||||
pci_address: address.to_string(),
|
||||
boot_vga: read_flag(&device.join("boot_vga")),
|
||||
driver: Driver::from_name(read_link_name(&device.join("driver")).as_deref()),
|
||||
render_node: render_node(&device.join("drm")),
|
||||
discrete: false,
|
||||
name: display_name(ids, vendor, model, address),
|
||||
driver,
|
||||
});
|
||||
}
|
||||
|
||||
gpus.sort_by(|a, b| a.pci_address.cmp(&b.pci_address));
|
||||
gpus
|
||||
cards.sort_by(|a, b| a.pci_address.cmp(&b.pci_address));
|
||||
cards
|
||||
}
|
||||
|
||||
fn read_flag(path: &Path) -> Option<bool> {
|
||||
Some(std::fs::read_to_string(path).ok()?.trim() == "1")
|
||||
}
|
||||
|
||||
fn read_class(path: &Path) -> Option<u32> {
|
||||
fn read_hex(path: &Path) -> Option<u32> {
|
||||
let raw = std::fs::read_to_string(path).ok()?;
|
||||
let raw = raw.trim();
|
||||
u32::from_str_radix(raw.strip_prefix("0x").unwrap_or(raw), 16).ok()
|
||||
|
|
@ -152,410 +127,95 @@ fn read_link_name(path: &Path) -> Option<String> {
|
|||
Some(link.file_name()?.to_str()?.to_string())
|
||||
}
|
||||
|
||||
fn render_node(drm: &Path) -> Option<PathBuf> {
|
||||
let entries = std::fs::read_dir(drm).ok()?;
|
||||
|
||||
entries.flatten().find_map(|entry| {
|
||||
let name = entry.file_name();
|
||||
name.to_str()?
|
||||
.starts_with("renderD")
|
||||
.then(|| Path::new("/dev/dri").join(&name))
|
||||
fn has_render_node(drm: &Path) -> bool {
|
||||
std::fs::read_dir(drm).is_ok_and(|entries| {
|
||||
entries
|
||||
.flatten()
|
||||
.any(|entry| entry.file_name().to_string_lossy().starts_with("renderD"))
|
||||
})
|
||||
}
|
||||
|
||||
fn pci_tag(address: &str) -> Option<String> {
|
||||
let (domain, rest) = address.split_once(':')?;
|
||||
let (bus, rest) = rest.split_once(':')?;
|
||||
let (device, function) = rest.split_once('.')?;
|
||||
fn display_name(ids: &str, vendor: u32, model: u32, address: &str) -> String {
|
||||
let brand = match vendor {
|
||||
0x10de => "NVIDIA",
|
||||
0x1002 => "AMD",
|
||||
0x8086 => "Intel",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
let widths = [(domain, 4), (bus, 2), (device, 2), (function, 1)];
|
||||
if widths
|
||||
.iter()
|
||||
.any(|(part, width)| part.len() != *width || !part.bytes().all(|b| b.is_ascii_hexdigit()))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let model = pci_ids_model(ids, vendor, model).map_or_else(
|
||||
|| format!("GPU {address}"),
|
||||
|name| {
|
||||
name.rsplit_once('[')
|
||||
.and_then(|(_, marketing)| marketing.strip_suffix(']'))
|
||||
.unwrap_or(name)
|
||||
.to_string()
|
||||
},
|
||||
);
|
||||
|
||||
Some(format!("pci-{domain}_{bus}_{device}_{function}"))
|
||||
}
|
||||
|
||||
fn probe_discrete(gpu: &Gpu) -> bool {
|
||||
match gpu.driver {
|
||||
Driver::Nvidia => true,
|
||||
Driver::AmdGpu => answered(gpu, drm::amdgpu_is_discrete(gpu)),
|
||||
Driver::Xe => answered(gpu, drm::xe_is_discrete(gpu)),
|
||||
Driver::I915 => gpu.pci_address != INTEL_INTEGRATED,
|
||||
Driver::Nouveau | Driver::Radeon | Driver::Other => gpu.boot_vga == Some(false),
|
||||
if brand.is_empty() {
|
||||
model
|
||||
} else {
|
||||
format!("{brand} {model}")
|
||||
}
|
||||
}
|
||||
|
||||
fn answered(gpu: &Gpu, probe: std::io::Result<bool>) -> bool {
|
||||
probe.unwrap_or_else(|error| {
|
||||
tracing::warn!(
|
||||
card = gpu.pci_address,
|
||||
?error,
|
||||
"no discreteness answer; assuming integrated"
|
||||
);
|
||||
false
|
||||
})
|
||||
}
|
||||
fn pci_ids_model(ids: &str, vendor: u32, model: u32) -> Option<&str> {
|
||||
let vendor = format!("{vendor:04x} ");
|
||||
let model = format!("\t{model:04x} ");
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
mod drm {
|
||||
use super::Gpu;
|
||||
|
||||
fn unavailable() -> std::io::Result<bool> {
|
||||
Err(std::io::Error::from(std::io::ErrorKind::Unsupported))
|
||||
}
|
||||
|
||||
pub fn amdgpu_is_discrete(_gpu: &Gpu) -> std::io::Result<bool> {
|
||||
unavailable()
|
||||
}
|
||||
|
||||
pub fn xe_is_discrete(_gpu: &Gpu) -> std::io::Result<bool> {
|
||||
unavailable()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod drm {
|
||||
use std::fs::File;
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use super::Gpu;
|
||||
|
||||
const DRM_IOCTL_BASE: u32 = b'd' as u32;
|
||||
const DRM_COMMAND_BASE: u32 = 0x40;
|
||||
|
||||
fn open(gpu: &Gpu) -> std::io::Result<Option<File>> {
|
||||
let Some(node) = &gpu.render_node else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(node)
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn check(result: i32) -> std::io::Result<()> {
|
||||
if result == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn command_write<T>(file: &File, index: u32, data: &T) -> std::io::Result<()> {
|
||||
let request = libc::_IOW::<T>(DRM_IOCTL_BASE, DRM_COMMAND_BASE + index);
|
||||
check(unsafe { libc::ioctl(file.as_raw_fd(), request, std::ptr::from_ref(data)) })
|
||||
}
|
||||
|
||||
fn command_read_write<T>(file: &File, index: u32, data: &mut T) -> std::io::Result<()> {
|
||||
let request = libc::_IOWR::<T>(DRM_IOCTL_BASE, DRM_COMMAND_BASE + index);
|
||||
check(unsafe { libc::ioctl(file.as_raw_fd(), request, std::ptr::from_mut(data)) })
|
||||
}
|
||||
|
||||
pub fn amdgpu_is_discrete(gpu: &Gpu) -> std::io::Result<bool> {
|
||||
let Some(file) = open(gpu)? else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
struct InfoDevice {
|
||||
_before_ids_flags: [u32; 34],
|
||||
ids_flags: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Info {
|
||||
return_pointer: u64,
|
||||
return_size: u32,
|
||||
query: u32,
|
||||
_query_args: [u32; 4],
|
||||
}
|
||||
|
||||
let mut result = InfoDevice {
|
||||
_before_ids_flags: [0; 34],
|
||||
ids_flags: 0,
|
||||
};
|
||||
|
||||
const AMDGPU_INFO_DEV_INFO: u32 = 0x16;
|
||||
let query = Info {
|
||||
return_pointer: std::ptr::from_mut(&mut result) as u64,
|
||||
return_size: size_of_val(&result) as u32,
|
||||
query: AMDGPU_INFO_DEV_INFO,
|
||||
_query_args: [0; 4],
|
||||
};
|
||||
|
||||
const DRM_AMDGPU_INFO: u32 = 0x05;
|
||||
command_write(&file, DRM_AMDGPU_INFO, &query)?;
|
||||
|
||||
const AMDGPU_IDS_FLAGS_FUSION: u64 = 0x1;
|
||||
Ok(result.ids_flags & AMDGPU_IDS_FLAGS_FUSION == 0)
|
||||
}
|
||||
|
||||
pub fn xe_is_discrete(gpu: &Gpu) -> std::io::Result<bool> {
|
||||
let Some(file) = open(gpu)? else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
struct DeviceQuery {
|
||||
extensions: u64,
|
||||
query: u32,
|
||||
size: u32,
|
||||
data: u64,
|
||||
_reserved: [u64; 2],
|
||||
}
|
||||
|
||||
const DRM_XE_DEVICE_QUERY: u32 = 0x00;
|
||||
const DRM_XE_DEVICE_QUERY_CONFIG: u32 = 0x2;
|
||||
|
||||
let mut query = DeviceQuery {
|
||||
extensions: 0,
|
||||
query: DRM_XE_DEVICE_QUERY_CONFIG,
|
||||
size: 0,
|
||||
data: 0,
|
||||
_reserved: [0; 2],
|
||||
};
|
||||
|
||||
command_read_write(&file, DRM_XE_DEVICE_QUERY, &mut query)?;
|
||||
|
||||
const INFO_OFFSET: usize = 1;
|
||||
const CONFIG_FLAGS: usize = 1;
|
||||
let words = (query.size as usize).div_ceil(size_of::<u64>());
|
||||
|
||||
if words <= INFO_OFFSET + CONFIG_FLAGS {
|
||||
tracing::warn!(size = query.size, "xe config too short to hold its flags");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut data = vec![0_u64; words];
|
||||
query.data = data.as_mut_ptr() as u64;
|
||||
|
||||
command_read_write(&file, DRM_XE_DEVICE_QUERY, &mut query)?;
|
||||
|
||||
const DRM_XE_QUERY_CONFIG_FLAG_HAS_VRAM: u64 = 1 << 0;
|
||||
Ok(data[INFO_OFFSET + CONFIG_FLAGS] & DRM_XE_QUERY_CONFIG_FLAG_HAS_VRAM != 0)
|
||||
}
|
||||
ids.lines()
|
||||
.skip_while(|line| !line.starts_with(&vendor))
|
||||
.skip(1)
|
||||
.take_while(|line| line.starts_with('\t') || line.starts_with('#'))
|
||||
.find_map(|line| line.strip_prefix(&model))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const AMD_INTEGRATED: &str = "0000:0c:00.0";
|
||||
const IDS: &str = "\
|
||||
# comment
|
||||
10de NVIDIA Corporation
|
||||
\t25a2 GA107M [GeForce RTX 3050 Mobile]
|
||||
\t\t1043 1e1f GA107M [GeForce RTX 3050 Mobile]
|
||||
\t1234 Plain Name
|
||||
1002 Advanced Micro Devices, Inc. [AMD/ATI]
|
||||
\t25a2 Not The Nvidia Card
|
||||
8086 Intel Corporation
|
||||
";
|
||||
|
||||
fn gpu(address: &str, driver: Driver, boot_vga: Option<bool>) -> Gpu {
|
||||
let discrete = match driver {
|
||||
Driver::Nvidia => true,
|
||||
Driver::I915 => address != INTEL_INTEGRATED,
|
||||
_ => boot_vga == Some(false),
|
||||
};
|
||||
|
||||
Gpu {
|
||||
fn card(address: &str, driver: Driver) -> Card {
|
||||
Card {
|
||||
pci_address: address.to_string(),
|
||||
boot_vga,
|
||||
name: String::new(),
|
||||
driver,
|
||||
render_node: Some(PathBuf::from("/dev/dri/renderD128")),
|
||||
discrete,
|
||||
}
|
||||
}
|
||||
|
||||
fn nvidia_offload() -> Vec<(&'static str, String)> {
|
||||
vec![
|
||||
("__NV_PRIME_RENDER_OFFLOAD", "1".to_string()),
|
||||
("__VK_LAYER_NV_optimus", "NVIDIA_only".to_string()),
|
||||
("__GLX_VENDOR_LIBRARY_NAME", "nvidia".to_string()),
|
||||
("VK_LOADER_DRIVERS_SELECT", "*nvidia*".to_string()),
|
||||
]
|
||||
}
|
||||
|
||||
fn dri_prime(card: &str, icd: &str) -> Vec<(&'static str, String)> {
|
||||
vec![
|
||||
("DRI_PRIME", card.to_string()),
|
||||
("VK_LOADER_DRIVERS_SELECT", icd.to_string()),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offload_env_names_the_right_card_for_every_pairing() {
|
||||
let igpu = || gpu(INTEL_INTEGRATED, Driver::I915, Some(true));
|
||||
let dgpu = || gpu("0000:01:00.0", Driver::Nvidia, Some(false));
|
||||
|
||||
type Case = (&'static str, Vec<Gpu>, Vec<(&'static str, String)>);
|
||||
let amd = "*radeon*,*amd*";
|
||||
|
||||
let cases: Vec<Case> = vec![
|
||||
("no cards at all", vec![], vec![]),
|
||||
(
|
||||
"a lone amd card",
|
||||
vec![gpu("0000:01:00.0", Driver::AmdGpu, Some(true))],
|
||||
vec![],
|
||||
),
|
||||
(
|
||||
"a lone nvidia card",
|
||||
vec![gpu("0000:01:00.0", Driver::Nvidia, Some(true))],
|
||||
vec![],
|
||||
),
|
||||
("a lone intel card", vec![igpu()], vec![]),
|
||||
(
|
||||
"an all-amd hybrid never names nvidia",
|
||||
vec![
|
||||
gpu(AMD_INTEGRATED, Driver::AmdGpu, Some(true)),
|
||||
gpu("0000:01:00.0", Driver::AmdGpu, Some(false)),
|
||||
],
|
||||
dri_prime("pci-0000_01_00_0", amd),
|
||||
),
|
||||
(
|
||||
"an intel + amd hybrid never names nvidia",
|
||||
vec![igpu(), gpu("0000:01:00.0", Driver::AmdGpu, Some(false))],
|
||||
dri_prime("pci-0000_01_00_0", amd),
|
||||
),
|
||||
(
|
||||
"an optimus laptop gets the nvidia variables and no DRI_PRIME",
|
||||
vec![igpu(), dgpu()],
|
||||
nvidia_offload(),
|
||||
),
|
||||
(
|
||||
"the same laptop on nouveau gets DRI_PRIME and no ICD filter",
|
||||
vec![
|
||||
igpu(),
|
||||
Gpu {
|
||||
driver: Driver::Nouveau,
|
||||
..dgpu()
|
||||
},
|
||||
],
|
||||
vec![("DRI_PRIME", "pci-0000_01_00_0".to_string())],
|
||||
),
|
||||
(
|
||||
"an nvidia card without a render node is still a target",
|
||||
vec![
|
||||
igpu(),
|
||||
Gpu {
|
||||
render_node: None,
|
||||
..dgpu()
|
||||
},
|
||||
],
|
||||
nvidia_offload(),
|
||||
),
|
||||
(
|
||||
"an unbound card is never the target",
|
||||
vec![
|
||||
igpu(),
|
||||
Gpu {
|
||||
driver: Driver::Other,
|
||||
render_node: None,
|
||||
..dgpu()
|
||||
},
|
||||
],
|
||||
vec![],
|
||||
),
|
||||
(
|
||||
"a bmc display adapter is skipped for the real card behind it",
|
||||
vec![
|
||||
igpu(),
|
||||
Gpu {
|
||||
render_node: None,
|
||||
..gpu("0000:03:00.0", Driver::Other, Some(false))
|
||||
},
|
||||
gpu("0000:c1:00.0", Driver::AmdGpu, Some(false)),
|
||||
],
|
||||
dri_prime("pci-0000_c1_00_0", amd),
|
||||
),
|
||||
(
|
||||
"the discrete card wins over a renderable card at a lower address",
|
||||
vec![
|
||||
igpu(),
|
||||
Gpu {
|
||||
discrete: false,
|
||||
..gpu("0000:03:00.0", Driver::AmdGpu, Some(false))
|
||||
},
|
||||
gpu("0000:c1:00.0", Driver::AmdGpu, Some(false)),
|
||||
],
|
||||
dri_prime("pci-0000_c1_00_0", amd),
|
||||
),
|
||||
(
|
||||
"nothing happens when the discrete card already drives the display",
|
||||
vec![
|
||||
gpu("0000:01:00.0", Driver::Nvidia, Some(true)),
|
||||
gpu(INTEL_INTEGRATED, Driver::I915, Some(false)),
|
||||
],
|
||||
vec![],
|
||||
),
|
||||
(
|
||||
"the driver still finds the discrete card when nothing publishes boot_vga",
|
||||
vec![
|
||||
gpu(INTEL_INTEGRATED, Driver::I915, None),
|
||||
gpu("0000:01:00.0", Driver::Nvidia, None),
|
||||
],
|
||||
nvidia_offload(),
|
||||
),
|
||||
(
|
||||
"a malformed pci address falls back to the ordinal",
|
||||
vec![
|
||||
gpu(AMD_INTEGRATED, Driver::AmdGpu, Some(true)),
|
||||
gpu("not-an-address", Driver::AmdGpu, Some(false)),
|
||||
],
|
||||
dri_prime("1", amd),
|
||||
),
|
||||
];
|
||||
|
||||
for (name, gpus, expected) in cases {
|
||||
assert_eq!(offload_env(&gpus), expected, "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pci_tags_match_the_shape_mesa_builds() {
|
||||
assert_eq!(pci_tag("0000:01:00.0").as_deref(), Some("pci-0000_01_00_0"));
|
||||
fn offload_env_names_the_chosen_card() {
|
||||
assert_eq!(
|
||||
pci_tag("10000:af:1f.7").as_deref(),
|
||||
None,
|
||||
"domain is 4 wide"
|
||||
offload_env(&card("0000:01:00.0", Driver::Nvidia)),
|
||||
vec![
|
||||
("__NV_PRIME_RENDER_OFFLOAD", "1".to_string()),
|
||||
("__VK_LAYER_NV_optimus", "NVIDIA_only".to_string()),
|
||||
("__GLX_VENDOR_LIBRARY_NAME", "nvidia".to_string()),
|
||||
("VK_LOADER_DRIVERS_SELECT", "*nvidia*".to_string()),
|
||||
],
|
||||
);
|
||||
assert_eq!(pci_tag("0000:1:00.0"), None, "bus is 2 wide");
|
||||
assert_eq!(pci_tag("0000:01:00"), None, "no function");
|
||||
assert_eq!(pci_tag("0000:0g:00.0"), None, "not hex");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_driver_that_will_not_answer_is_never_called_discrete() {
|
||||
let mut apu = gpu(AMD_INTEGRATED, Driver::AmdGpu, Some(false));
|
||||
apu.discrete = probe_discrete(&apu);
|
||||
assert!(!apu.discrete);
|
||||
|
||||
let mut dgpu = gpu("0000:03:00.0", Driver::AmdGpu, Some(true));
|
||||
dgpu.discrete = probe_discrete(&dgpu);
|
||||
assert!(!dgpu.discrete);
|
||||
|
||||
assert!(
|
||||
offload_env(&[apu, dgpu]).is_empty(),
|
||||
"knowing nothing, leave the renderer alone"
|
||||
assert_eq!(
|
||||
offload_env(&card("0000:c1:00.0", Driver::AmdGpu)),
|
||||
vec![
|
||||
("DRI_PRIME", "pci-0000_c1_00_0".to_string()),
|
||||
("VK_LOADER_DRIVERS_SELECT", "*radeon*,*amd*".to_string()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_cards_we_can_place_without_asking_the_driver() {
|
||||
assert!(probe_discrete(&gpu(
|
||||
"0000:01:00.0",
|
||||
Driver::Nvidia,
|
||||
Some(false)
|
||||
)));
|
||||
assert!(!probe_discrete(&gpu(
|
||||
INTEL_INTEGRATED,
|
||||
Driver::I915,
|
||||
Some(true)
|
||||
)));
|
||||
assert!(
|
||||
probe_discrete(&gpu("0000:03:00.0", Driver::I915, Some(false))),
|
||||
"an arc board does not sit at the integrated address"
|
||||
);
|
||||
assert!(
|
||||
probe_discrete(&gpu("0000:01:00.0", Driver::Nouveau, Some(false))),
|
||||
"no query, so boot_vga is all we have"
|
||||
assert_eq!(
|
||||
offload_env(&card("0000:01:00.0", Driver::Nouveau)),
|
||||
vec![("DRI_PRIME", "pci-0000_01_00_0".to_string())],
|
||||
"nouveau gets no ICD filter",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -570,10 +230,11 @@ mod tests {
|
|||
std::fs::write(path, contents).unwrap();
|
||||
}
|
||||
|
||||
fn write_card(bus: &Path, address: &str, boot_vga: &str, driver: &str) {
|
||||
fn write_card(bus: &Path, address: &str, vendor: &str, model: &str, driver: &str) {
|
||||
let device = bus.join(address);
|
||||
write(&device.join("boot_vga"), &format!("{boot_vga}\n"));
|
||||
write(&device.join("class"), "0x030000\n");
|
||||
write(&device.join("vendor"), &format!("0x{vendor}\n"));
|
||||
write(&device.join("device"), &format!("0x{model}\n"));
|
||||
|
||||
let target = bus.join("drivers").join(driver);
|
||||
std::fs::create_dir_all(&target).unwrap();
|
||||
|
|
@ -581,58 +242,41 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn reads_boot_flag_driver_and_render_node_out_of_a_sysfs_tree() {
|
||||
fn lists_the_cards_that_can_render_out_of_a_sysfs_tree() {
|
||||
let scratch = polyio::testing::ScratchDir::new("gpu-sysfs");
|
||||
let bus = scratch.join("devices");
|
||||
|
||||
write_card(&bus, INTEL_INTEGRATED, "1", "i915");
|
||||
write_card(&bus, "0000:00:02.0", "8086", "9a49", "i915");
|
||||
write(
|
||||
&bus.join(INTEL_INTEGRATED)
|
||||
&bus.join("0000:00:02.0")
|
||||
.join("drm")
|
||||
.join("renderD128")
|
||||
.join("dev"),
|
||||
"226:128\n",
|
||||
);
|
||||
write_card(&bus, "0000:01:00.0", "0", "nvidia");
|
||||
|
||||
let gpus = read_pci_devices(&bus);
|
||||
|
||||
assert_eq!(gpus.len(), 2);
|
||||
assert_eq!(gpus[0].pci_address, INTEL_INTEGRATED);
|
||||
assert_eq!(gpus[0].boot_vga, Some(true));
|
||||
assert_eq!(gpus[0].driver, Driver::I915);
|
||||
assert_eq!(
|
||||
gpus[0].render_node,
|
||||
Some(PathBuf::from("/dev/dri/renderD128")),
|
||||
"the node the ioctl probes open"
|
||||
);
|
||||
assert_eq!(gpus[1].boot_vga, Some(false));
|
||||
assert_eq!(gpus[1].driver, Driver::Nvidia);
|
||||
assert_eq!(gpus[1].render_node, None, "no nvidia-drm in this tree");
|
||||
|
||||
assert!(gpus.iter().all(|gpu| !gpu.discrete));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_everything_on_the_bus_that_is_not_a_display_controller() {
|
||||
let scratch = polyio::testing::ScratchDir::new("gpu-bus");
|
||||
let bus = scratch.join("devices");
|
||||
|
||||
write_card(&bus, "0000:01:00.0", "1", "amdgpu");
|
||||
write_card(&bus, "0000:01:00.0", "10de", "25a2", "nvidia");
|
||||
write_card(&bus, "0000:03:00.0", "1a03", "2000", "ast");
|
||||
write(&bus.join("0000:01:00.1").join("class"), "0x040300\n");
|
||||
write(&bus.join("0000:02:00.0").join("class"), "0x020000\n");
|
||||
std::fs::create_dir_all(bus.join("0000:03:00.0")).unwrap();
|
||||
std::fs::create_dir_all(bus.join("0000:04:00.0")).unwrap();
|
||||
|
||||
let gpus = read_pci_devices(&bus);
|
||||
let gpus = read_pci_devices(&bus, IDS);
|
||||
|
||||
assert_eq!(gpus.len(), 1, "one card, counted once");
|
||||
assert_eq!(gpus[0].driver, Driver::AmdGpu);
|
||||
assert_eq!(
|
||||
gpus.len(),
|
||||
2,
|
||||
"no bmc adapter, audio, nic or classless device"
|
||||
);
|
||||
assert_eq!(gpus[0].pci_address, "0000:00:02.0");
|
||||
assert_eq!(gpus[0].driver, Driver::I915);
|
||||
assert_eq!(gpus[1].driver, Driver::Nvidia, "needs no render node");
|
||||
assert_eq!(gpus[1].name, "NVIDIA GeForce RTX 3050 Mobile");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_missing_pci_tree_is_not_an_error() {
|
||||
let scratch = polyio::testing::ScratchDir::new("gpu-missing");
|
||||
|
||||
assert!(read_pci_devices(&scratch.join("nope")).is_empty());
|
||||
assert!(read_pci_devices(&scratch.join("nope"), "").is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,69 @@
|
|||
#[cfg(any(target_os = "linux", test))]
|
||||
mod linux;
|
||||
|
||||
use tokio::process::Command;
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Gpu {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
const WINDOWS_PREFERENCES: [(&str, &str, u8); 2] = [
|
||||
("integrated", "Integrated GPU", 1),
|
||||
("dedicated", "Dedicated GPU", 2),
|
||||
];
|
||||
|
||||
pub fn detect() -> Vec<Gpu> {
|
||||
cfg_select! {
|
||||
target_os = "linux" => linux::detect()
|
||||
.into_iter()
|
||||
.map(|card| Gpu {
|
||||
id: card.pci_address,
|
||||
name: card.name,
|
||||
})
|
||||
.collect(),
|
||||
windows => WINDOWS_PREFERENCES
|
||||
.iter()
|
||||
.map(|(id, name, _)| Gpu {
|
||||
id: (*id).to_string(),
|
||||
name: (*name).to_string(),
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub async fn prefer_discrete(command: &mut Command, java_path: &str) {
|
||||
pub async fn select(command: &mut tokio::process::Command, java_path: &str, id: &str) {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let gpus = linux::detect();
|
||||
let env = linux::offload_env(&gpus);
|
||||
|
||||
if env.is_empty() {
|
||||
tracing::info!(
|
||||
gpus = gpus.len(),
|
||||
"discrete GPU was requested but nothing here is a valid offload target; \
|
||||
leaving the renderer alone"
|
||||
);
|
||||
let cards = tokio::task::spawn_blocking(linux::detect)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let Some(card) = cards.into_iter().find(|card| card.pci_address == id) else {
|
||||
tracing::warn!(id, "the selected GPU is gone; leaving the renderer alone");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for (key, value) in env {
|
||||
tracing::debug!(key, value, "offloading the game to the discrete GPU");
|
||||
for (key, value) in linux::offload_env(&card) {
|
||||
tracing::debug!(
|
||||
key,
|
||||
value,
|
||||
gpu = card.name,
|
||||
"rendering the game on the selected GPU"
|
||||
);
|
||||
command.env(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
oneclient_java::prefer_dedicated_gpu(std::path::Path::new(java_path)).await;
|
||||
{
|
||||
let Some((_, _, preference)) = WINDOWS_PREFERENCES.iter().find(|(known, ..)| *known == id)
|
||||
else {
|
||||
tracing::warn!(id, "unknown GPU preference; leaving the renderer alone");
|
||||
return;
|
||||
};
|
||||
|
||||
oneclient_java::set_gpu_preference(std::path::Path::new(java_path), *preference).await;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -382,8 +382,8 @@ async fn start(
|
|||
|
||||
let (mut command, wrapper) = base_command(&profile, &java.absolute_path);
|
||||
|
||||
if profile.use_discrete_gpu() {
|
||||
crate::game::gpu::prefer_discrete(&mut command, &java.absolute_path).await;
|
||||
if let Some(gpu) = profile.gpu() {
|
||||
crate::game::gpu::select(&mut command, &java.absolute_path, gpu).await;
|
||||
}
|
||||
|
||||
apply_env(&mut command, &profile);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
mod analytics;
|
||||
mod error;
|
||||
pub mod fabric;
|
||||
mod gpu;
|
||||
pub mod gpu;
|
||||
mod heal;
|
||||
mod launch;
|
||||
mod log_replay;
|
||||
|
|
|
|||
|
|
@ -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, prefer_dedicated_gpu};
|
||||
pub use platform::{HostArch, HostOs, HostTarget, set_gpu_preference};
|
||||
pub use service::{
|
||||
AvailableJava, INSTALLABLE_MAJORS, JAVA_CHOICE_DOWNLOAD, JAVA_CHOICE_FOLDER, JAVA_VENDOR_HINT,
|
||||
JavaService,
|
||||
|
|
|
|||
|
|
@ -110,17 +110,8 @@ fn registered_value<'a>(query: &'a str, name: &str) -> Option<&'a str> {
|
|||
})
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
pub async fn set_gpu_preference(java_path: &std::path::Path, preference: u8) {
|
||||
let Some(dir) = java_path.parent() else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -150,21 +141,22 @@ pub async fn prefer_dedicated_gpu(java_path: &std::path::Path) {
|
|||
}
|
||||
};
|
||||
|
||||
let data = format!("GpuPreference={preference};");
|
||||
for path in targets {
|
||||
if registered_value(&query, &path.to_string_lossy()).is_some_and(has_explicit_preference) {
|
||||
if registered_value(&query, &path.to_string_lossy()) == Some(data.as_str()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let added = reg_command()
|
||||
.args(["add", GPU_PREFERENCES_KEY, "/v"])
|
||||
.arg(&path)
|
||||
.args(["/t", "REG_SZ", "/d", "GpuPreference=2;", "/f"])
|
||||
.args(["/t", "REG_SZ", "/d", data.as_str(), "/f"])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
match added {
|
||||
Ok(out) if out.status.success() => {
|
||||
tracing::info!(path = %path.display(), "registered JVM for high-performance GPU");
|
||||
tracing::info!(path = %path.display(), preference, "registered JVM GPU preference");
|
||||
}
|
||||
Ok(out) => tracing::warn!(
|
||||
path = %path.display(),
|
||||
|
|
@ -205,7 +197,7 @@ pub async fn forget_dedicated_gpu(java_path: &std::path::Path) {
|
|||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub async fn prefer_dedicated_gpu(_java_path: &std::path::Path) {}
|
||||
pub async fn set_gpu_preference(_java_path: &std::path::Path, _preference: u8) {}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub async fn forget_dedicated_gpu(_java_path: &std::path::Path) {}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue