Skip to content

feat: [Windows] 支持自动使用高性能显卡#5420

Open
CiiLu wants to merge 2 commits intoHMCL-dev:mainfrom
CiiLu:soms
Open

feat: [Windows] 支持自动使用高性能显卡#5420
CiiLu wants to merge 2 commits intoHMCL-dev:mainfrom
CiiLu:soms

Conversation

@CiiLu
Copy link
Contributor

@CiiLu CiiLu commented Feb 4, 2026

No description provided.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional Windows-only launch behavior to prefer the high-performance GPU by temporarily writing UserGpuPreferences registry values, exposed via a new launcher setting.

Changes:

  • Extend the Windows registry helper (WinReg/JNA) to support setting and deleting REG_SZ values.
  • Add a new persisted config flag and settings UI toggle for “high-performance GPU” on Windows.
  • Hook game launch flow to create (and later remove) the relevant UserGpuPreferences registry entry.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java Adds setValue / deleteValue APIs and JNA implementations for registry writes.
HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinConstants.java Adds registry access/constants needed for write/delete operations.
HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/Advapi32.java Adds JNA signatures for RegCreateKeyExW / RegSetValueExW / RegDeleteValueW.
HMCL/src/main/resources/assets/lang/I18N*.properties Adds i18n strings for the new Windows GPU preference setting.
HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java Adds a new toggle to the settings UI bound to the new config property.
HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java Persists the new windowsHighPerformance flag.
HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java Writes/deletes the GPU preference registry value around the launch lifecycle.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

windowsHighPerformance.setTitle(i18n("settings.launcher.windows_gpu_preferences"));
windowsHighPerformance.selectedProperty().bindBidirectional(config().windowsHighPerformanceProperty());

settingsPane.getContent().add(windowsHighPerformance);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Windows-specific toggle is currently shown on all platforms. Since enabling it on non-Windows can’t have an effect (and currently can even trigger Windows-registry code paths during launch), consider only adding this control when OperatingSystem.CURRENT_OS == WINDOWS, or disabling it with an explanatory subtitle when not on Windows.

Suggested change
settingsPane.getContent().add(windowsHighPerformance);
if (System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("windows")) {
settingsPane.getContent().add(windowsHighPerformance);
}

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +93
public abstract boolean setValue(HKEY root, String key, String valueName, String value);

public abstract boolean deleteValue(HKEY root, String key, String valueName);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New WinReg mutation APIs (setValue/deleteValue) are introduced, but the existing WinRegTest only covers querying. Please add JUnit coverage for setting a REG_SZ value and deleting it (including verifying behavior when the value/key does not exist), so regressions in JNA signatures/encoding are caught.

Copilot uses AI. Check for mistakes.
Comment on lines +214 to +227
Object current = WinReg.INSTANCE.queryValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
FileUtils.getAbsolutePath(javaVersionRef.get().getBinary())
);
if (current instanceof String) {
gpuRegAlreadyExistsed = true;
} else {
WinReg.INSTANCE.setValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
FileUtils.getAbsolutePath(javaVersionRef.get().getBinary()),
"GpuPreference=2;"
);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WinReg.INSTANCE can be null (e.g., non-Windows or JNA disabled). This code calls WinReg.INSTANCE.queryValue/setValue without checking for null or OperatingSystem.WINDOWS, which can cause an immediate NPE when the setting is enabled. Please guard this block with an OS check and a WinReg reg = WinReg.INSTANCE; if (reg == null) { ... } fallback (and consider logging if the registry write is unavailable).

Suggested change
Object current = WinReg.INSTANCE.queryValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
FileUtils.getAbsolutePath(javaVersionRef.get().getBinary())
);
if (current instanceof String) {
gpuRegAlreadyExistsed = true;
} else {
WinReg.INSTANCE.setValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
FileUtils.getAbsolutePath(javaVersionRef.get().getBinary()),
"GpuPreference=2;"
);
if (!SYSTEM_PLATFORM.isWindows()) {
LOG.warning("Windows high performance GPU setting is enabled, but current platform is not Windows; skipping registry configuration.");
} else {
WinReg reg = WinReg.INSTANCE;
if (reg == null) {
LOG.warning("Windows registry access (WinReg) is unavailable; cannot apply high performance GPU preference.");
} else {
Object current = reg.queryValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
FileUtils.getAbsolutePath(javaVersionRef.get().getBinary())
);
if (current instanceof String) {
gpuRegAlreadyExistsed = true;
} else {
reg.setValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
FileUtils.getAbsolutePath(javaVersionRef.get().getBinary()),
"GpuPreference=2;"
);
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +850 to +854
WinReg.INSTANCE.deleteValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
process.getProcess().info().command().orElseThrow()
);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The registry value name used for cleanup should match exactly the value name used when setting it. Here the value name is derived from javaVersionRef.get().getBinary() during setValue, but cleanup uses process.getProcess().info().command().orElseThrow(), which may differ (quoting/canonicalization) or be empty and throw, leaving the registry entry behind or breaking finishLaunch. Store the exact value name string you used for setValue and reuse it for deletion, and avoid orElseThrow() here.

Suggested change
WinReg.INSTANCE.deleteValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
process.getProcess().info().command().orElseThrow()
);
String valueName = null;
JavaRuntime javaRuntime = javaVersionRef.get();
if (javaRuntime != null && javaRuntime.getBinary() != null) {
valueName = javaRuntime.getBinary().toString();
}
if (valueName != null) {
WinReg.INSTANCE.deleteValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
valueName
);
}

Copilot uses AI. Check for mistakes.
Comment on lines +847 to +854
try {
Thread.sleep(5000L);
} catch (InterruptedException ignored) {}
WinReg.INSTANCE.deleteValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
process.getProcess().info().command().orElseThrow()
);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

finishLaunch() performs a 5s Thread.sleep and ignores interrupts. This method is invoked from onLog() while holding lock (see onLog around the finishLaunch() call), so sleeping here can block log processing and hold the lock for 5 seconds. Consider scheduling the delayed registry cleanup asynchronously (after releasing the lock), and if catching InterruptedException, restore the interrupt flag with Thread.currentThread().interrupt().

Suggested change
try {
Thread.sleep(5000L);
} catch (InterruptedException ignored) {}
WinReg.INSTANCE.deleteValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
process.getProcess().info().command().orElseThrow()
);
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
WinReg.INSTANCE.deleteValue(
WinReg.HKEY.HKEY_CURRENT_USER,
"Software\\Microsoft\\DirectX\\UserGpuPreferences",
process.getProcess().info().command().orElseThrow()
);
});

Copilot uses AI. Check for mistakes.
private boolean showLogs;
private QuickPlayOption quickPlayOption;
private boolean disableOfflineSkin = false;
private boolean gpuRegAlreadyExistsed = false;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gpuRegAlreadyExistsed is written during the async launch task and read later from the process listener thread; without any synchronization it may not be safely published between threads. Make this flag volatile/AtomicBoolean, or move it (and any related state like the registry value name) into the HMCLProcessListener instance where it’s used.

Suggested change
private boolean gpuRegAlreadyExistsed = false;
private volatile boolean gpuRegAlreadyExistsed = false;

Copilot uses AI. Check for mistakes.
private boolean showLogs;
private QuickPlayOption quickPlayOption;
private boolean disableOfflineSkin = false;
private boolean gpuRegAlreadyExistsed = false;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in new field name: gpuRegAlreadyExistsed is grammatically incorrect and makes the intent harder to read. Rename to something like gpuRegAlreadyExisted or gpuRegValueAlreadyExists.

Suggested change
private boolean gpuRegAlreadyExistsed = false;
private boolean gpuRegValueAlreadyExists = false;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant