Compare commits

..
Author SHA1 Message Date
Glavo 101a9b5d5e 禁止在 LogWindow 中显示横向滚动条 (#5935) 2026-04-11 00:43:35 +08:00
Glavo 7cd79a4e2a Update versions.txt to 26.1.2 (#5934) 2026-04-10 22:49:40 +08:00
Glavoand3gf8jv4dv 30c3acfb23 增强对 Vulkan 的支持 (#5930)
1. 优化渲染器选项描述;
2. 添加 Vulkan 相关的渲染器选项。

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2026-04-10 22:19:51 +08:00
Glavo 31f04db555 支持将新格式快照版本号翻译为文言 (#5931) 2026-04-09 00:16:57 +08:00
23 changed files with 361 additions and 96 deletions

No files matched your search

@@ -430,6 +430,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
.setNativesDirType(vs.getNativesDirType())
.setNativesDir(vs.getNativesDir())
.setProcessPriority(vs.getProcessPriority())
.setGraphicsBackend(vs.getGraphicsBackend())
.setRenderer(vs.getRenderer())
.setEnableDebugLogOutput(vs.isEnableDebugLogOutput())
.setUseNativeGLFW(vs.isUseNativeGLFW())
@@ -174,26 +174,27 @@ public final class LauncherHelper {
}),
Task.composeAsync(() -> {
Renderer renderer = setting.getRenderer();
if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION);
if (lib == null)
return null;
Path file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
if (file.toAbsolutePath().toString().indexOf('=') >= 0) {
LOG.warning("Invalid character '=' in the libraries directory path, unable to attach software renderer loader");
return null;
}
if (renderer == null || renderer.getMesaDriverName() == null
|| OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) {
return null;
}
String agent = FileUtils.getAbsolutePath(file) + "=" + renderer.name().toLowerCase(Locale.ROOT);
Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION);
if (lib == null)
return null;
Path file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
if (file.toAbsolutePath().toString().indexOf('=') >= 0) {
LOG.warning("Invalid character '=' in the libraries directory path, unable to attach software renderer loader");
return null;
}
if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {
return new LibraryDownloadTask(dependencyManager, file, lib)
.thenRunAsync(() -> javaAgents.add(agent));
} else {
javaAgents.add(agent);
return null;
}
String agent = FileUtils.getAbsolutePath(file) + "=" + renderer.getMesaDriverName();
if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {
return new LibraryDownloadTask(dependencyManager, file, lib)
.thenRunAsync(() -> javaAgents.add(agent));
} else {
javaAgents.add(agent);
return null;
}
})
@@ -232,6 +233,7 @@ public final class LauncherHelper {
if (quickPlayOption != null) {
launchOptionsBuilder.setQuickPlayOption(quickPlayOption);
}
LaunchOptions launchOptions = launchOptionsBuilder.create();
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModsDirectory(selectedVersion), 10));
@@ -603,6 +603,20 @@ public final class VersionSetting implements Cloneable, Observable {
processPriorityProperty.set(processPriority);
}
private final ObjectProperty<GraphicsAPI> graphicsBackend = new SimpleObjectProperty<>(this, "graphicsBackend", GraphicsAPI.DEFAULT);
public ObjectProperty<GraphicsAPI> graphicsBackendProperty() {
return graphicsBackend;
}
public GraphicsAPI getGraphicsBackend() {
return graphicsBackendProperty().get();
}
public void setGraphicsBackend(GraphicsAPI api) {
graphicsBackendProperty().set(api);
}
private final ObjectProperty<Renderer> rendererProperty = new SimpleObjectProperty<>(this, "renderer", Renderer.DEFAULT);
public Renderer getRenderer() {
@@ -820,6 +834,7 @@ public final class VersionSetting implements Cloneable, Observable {
}
obj.addProperty("java", java);
obj.addProperty("graphicsBackend", src.getGraphicsBackend().name());
obj.addProperty("renderer", src.getRenderer().name());
if (src.getRenderer() == Renderer.LLVMPIPE)
obj.addProperty("useSoftwareRenderer", true);
@@ -905,6 +920,15 @@ public final class VersionSetting implements Cloneable, Observable {
return useSoftwareRenderer ? Renderer.LLVMPIPE : Renderer.DEFAULT;
}));
vs.setGraphicsBackend(Optional.ofNullable(obj.get("graphicsBackend")).map(JsonElement::getAsString)
.flatMap(name -> {
try {
return Optional.of(GraphicsAPI.valueOf(name.toUpperCase(Locale.ROOT)));
} catch (IllegalArgumentException ignored) {
return Optional.empty();
}
}).orElseGet(() -> vs.getRenderer().getApi()));
return vs;
}
@@ -170,6 +170,7 @@ public final class LogWindow extends Stage {
LogWindowImpl() {
getStyleClass().add("log-window");
listView.getStyleClass().add("no-horizontal-scrollbar");
listView.getProperties().put("no-smooth-scrolling", true);
listView.setItems(FXCollections.observableList(new CircularArrayList<>(logs.size())));
@@ -28,6 +28,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.input.*;
import javafx.scene.layout.StackPane;
@@ -49,6 +50,8 @@ public class LineSelectButton<T> extends LineButton {
private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
private JFXPopup popup;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private ObservableList<Node> popupItems; // keep a reference
public LineSelectButton() {
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
@@ -87,7 +90,7 @@ public class LineSelectButton<T> extends LineButton {
ripplerContainer.addEventFilter(ScrollEvent.ANY, ignored -> popup.hide());
Bindings.bindContent(popupMenu.getContent(), MappedObservableList.create(itemsProperty(), item -> {
Bindings.bindContent(popupMenu.getContent(), popupItems = MappedObservableList.create(itemsProperty(), item -> {
VBox vbox = new VBox();
var itemTitleLabel = new Label();
@@ -25,6 +25,7 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.game.GraphicsAPI;
import org.jackhuang.hmcl.game.NativesDirectoryType;
import org.jackhuang.hmcl.game.Renderer;
import org.jackhuang.hmcl.setting.Profile;
@@ -36,6 +37,7 @@ import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.Platform;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jetbrains.annotations.Nullable;
import java.nio.file.FileSystems;
@@ -69,6 +71,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
private final ComponentSublist nativesDirSublist;
private final MultiFileItem<NativesDirectoryType> nativesDirItem;
private final MultiFileItem.FileOption<NativesDirectoryType> nativesDirCustomOption;
private final LineSelectButton<GraphicsAPI> graphicsBackendPane;
private final LineSelectButton<Renderer> rendererPane;
public AdvancedVersionSettingPage(Profile profile, @Nullable String versionId, VersionSetting versionSetting) {
@@ -79,6 +82,10 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
versionId == null ? i18n("settings.advanced") : i18n("settings.advanced.title", versionId)
));
@Nullable GameVersionNumber gameVersion = versionId != null
? GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(versionId))
: null;
this.getStyleClass().add("gray-background");
ScrollPane scrollPane = new ScrollPane();
@@ -185,6 +192,25 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
nativesDirHint.setText(i18n("settings.advanced.natives_directory.hint"));
nativesDirItem.getChildren().add(nativesDirHint);
graphicsBackendPane = new LineSelectButton<>();
graphicsBackendPane.setTitle(i18n("settings.advanced.graphics_backend"));
graphicsBackendPane.setConverter(backend -> i18n("settings.advanced.graphics_backend." + backend.name().toLowerCase(Locale.ROOT)));
graphicsBackendPane.setDescriptionConverter(backend -> switch (backend) {
case DEFAULT -> i18n("settings.advanced.graphics_backend.default.desc");
case OPENGL -> i18n("settings.advanced.graphics_backend.opengl.desc");
case VULKAN -> {
if (gameVersion == null)
yield i18n("settings.advanced.graphics_backend.vulkan.desc.global");
else if (gameVersion.compareTo("26.2-snapshot-2") < 0)
yield i18n("settings.advanced.graphics_backend.vulkan.desc.unsupported");
else
yield i18n("settings.advanced.graphics_backend.vulkan.desc");
}
default -> null;
});
graphicsBackendPane.setValue(GraphicsAPI.DEFAULT);
graphicsBackendPane.setItems(GraphicsAPI.values());
rendererPane = new LineSelectButton<>();
rendererPane.setTitle(i18n("settings.advanced.renderer"));
rendererPane.setConverter(e -> i18n("settings.advanced.renderer." + e.name().toLowerCase(Locale.ROOT)));
@@ -192,7 +218,25 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
String bundleKey = "settings.advanced.renderer." + e.name().toLowerCase(Locale.ROOT) + ".desc";
return I18n.hasKey(bundleKey) ? i18n(bundleKey) : null;
});
rendererPane.setItems(Renderer.values());
rendererPane.setValue(Renderer.DEFAULT);
rendererPane.setItems(Renderer.DEFAULT);
FXUtils.onChangeAndOperate(graphicsBackendPane.valueProperty(), backend -> {
if (backend == null) { // unbind
return;
}
rendererPane.setItems(Renderer.SUPPORTED.get(backend));
if (backend == GraphicsAPI.DEFAULT) {
rendererPane.setDisable(true);
rendererPane.setValue(Renderer.DEFAULT);
} else {
rendererPane.setDisable(false);
if (rendererPane.getValue() == null || !rendererPane.getValue().isSupported(backend)) {
rendererPane.setValue(Renderer.DEFAULT);
}
}
});
noJVMArgsPane = new LineToggleButton();
noJVMArgsPane.setTitle(i18n("settings.advanced.no_jvm_args"));
@@ -219,7 +263,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
useNativeOpenALPane.setSubtitle(i18n("settings.advanced.linux_freebsd_only"));
workaroundPane.getContent().setAll(
nativesDirSublist, rendererPane, noJVMArgsPane, noOptimizingJVMArgsPane, noGameCheckPane,
nativesDirSublist, graphicsBackendPane, rendererPane, noJVMArgsPane, noOptimizingJVMArgsPane, noGameCheckPane,
noJVMCheckPane, noNativesPatchPane
);
@@ -252,6 +296,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
FXUtils.bindString(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
FXUtils.bindString(txtPostExitCommand, versionSetting.postExitCommandProperty());
rendererPane.valueProperty().bindBidirectional(versionSetting.rendererProperty());
graphicsBackendPane.valueProperty().bindBidirectional(versionSetting.graphicsBackendProperty());
noGameCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckGameProperty());
noJVMCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckJVMProperty());
noJVMArgsPane.selectedProperty().bindBidirectional(versionSetting.noJVMArgsProperty());
@@ -294,6 +339,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
rendererPane.valueProperty().unbindBidirectional(versionSetting.rendererProperty());
graphicsBackendPane.valueProperty().unbindBidirectional(versionSetting.graphicsBackendProperty());
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
@@ -189,15 +189,16 @@ public final class NativePatcher {
return version.setLibraries(newLibraries);
}
public static @Nullable Library getWindowsMesaLoader(@NotNull JavaRuntime javaVersion, @NotNull Renderer renderer, @NotNull OSVersion windowsVersion) {
/// @see <a href="https://github.com/HMCL-dev/mesa-loader-windows">Java Mesa Loader for Windows</a>
public static @Nullable Library getWindowsMesaLoader(@NotNull JavaRuntime java, @NotNull Renderer renderer, @NotNull OSVersion windowsVersion) {
if (renderer == Renderer.DEFAULT)
return null;
if (windowsVersion.isAtLeast(OSVersion.WINDOWS_10)) {
return getNatives(javaVersion.getPlatform()).get("mesa-loader");
return getNatives(java.getPlatform()).get("mesa-loader");
} else if (windowsVersion.isAtLeast(OSVersion.WINDOWS_7)) {
if (renderer == Renderer.LLVMPIPE)
return getNatives(javaVersion.getPlatform()).get("software-renderer-loader");
return getNatives(java.getPlatform()).get("software-renderer-loader");
else
return null;
} else {
@@ -96,6 +96,10 @@ public class Translator_lzh extends Translator {
case GA -> {
// do nothing
}
case SNAPSHOT -> {
builder.append("之快照");
appendDigitByDigit(builder, release.getEaVersion().toString());
}
case PRE_RELEASE -> {
builder.append("之預");
appendDigitByDigit(builder, release.getEaVersion().toString());
@@ -1337,6 +1337,15 @@ settings.advanced.dont_patch_natives=Do not attempt to automatically replace nat
settings.advanced.environment_variables=Environment Variables
settings.advanced.game_dir.default=Default (".minecraft/")
settings.advanced.game_dir.independent=Isolated (".minecraft/versions/<instance name>/", except for assets and libraries)
settings.advanced.graphics_backend=Graphics API
settings.advanced.graphics_backend.default=Default
settings.advanced.graphics_backend.default.desc=Follow the game's settings
settings.advanced.graphics_backend.opengl=OpenGL
settings.advanced.graphics_backend.opengl.desc=Use OpenGL for rendering
settings.advanced.graphics_backend.vulkan=Vulkan
settings.advanced.graphics_backend.vulkan.desc=Use Vulkan for rendering
settings.advanced.graphics_backend.vulkan.desc.global=Use Vulkan for rendering (Since Minecraft 26.2)
settings.advanced.graphics_backend.vulkan.desc.unsupported=Use Vulkan for rendering (not supported for the current game version)
settings.advanced.java_permanent_generation_space=PermGen Space
settings.advanced.java_permanent_generation_space.prompt=in MiB
settings.advanced.jvm=JVM Options
@@ -1379,13 +1388,17 @@ settings.advanced.post_exit_command=Post-exit Command
settings.advanced.post_exit_command.prompt=Commands to execute after the game exits
settings.advanced.renderer=Renderer
settings.advanced.renderer.default=Default
settings.advanced.renderer.default.desc=OpenGL
settings.advanced.renderer.default.desc=Use System Default
settings.advanced.renderer.d3d12=Mesa D3D12
settings.advanced.renderer.d3d12.desc=DirectX 12 (Poor performance and compatibility)
settings.advanced.renderer.d3d12.desc=OpenGL renderer based on DirectX 12
settings.advanced.renderer.lavapipe=Mesa Lavapipe
settings.advanced.renderer.lavapipe.desc=Software Vulkan Renderer
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
settings.advanced.renderer.llvmpipe.desc=Software (Poor performance, best compatibility)
settings.advanced.renderer.llvmpipe.desc=Software OpenGL Renderer
settings.advanced.renderer.dozen=Mesa Dozen
settings.advanced.renderer.dozen.desc=Vulkan renderer based on DirectX 12 (Experimental)
settings.advanced.renderer.zink=Mesa Zink
settings.advanced.renderer.zink.desc=Vulkan (Best performance, poor compatibility)
settings.advanced.renderer.zink.desc=OpenGL renderer based on Vulkan
settings.advanced.server_ip=Server Address
settings.advanced.server_ip.prompt=Automatically join after launching the game
settings.advanced.unsupported_system_options=Settings not applicable to the current system
@@ -1364,13 +1364,9 @@ settings.advanced.post_exit_command=أمر ما بعد الإغلاق
settings.advanced.post_exit_command.prompt=أوامر تُنفَّذ بعد إغلاق اللعبة
settings.advanced.renderer=المُصيِّر
settings.advanced.renderer.default=افتراضي
settings.advanced.renderer.default.desc=OpenGL
settings.advanced.renderer.d3d12=Mesa D3D12
settings.advanced.renderer.d3d12.desc=DirectX 12 (أداء وتوافق ضعيفان)
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
settings.advanced.renderer.llvmpipe.desc=برمجي (أداء ضعيف، توافق أفضل)
settings.advanced.renderer.zink=Mesa Zink
settings.advanced.renderer.zink.desc=Vulkan (أفضل أداء، توافق ضعيف)
settings.advanced.server_ip=عنوان الخادم
settings.advanced.server_ip.prompt=الانضمام تلقائياً بعد تشغيل اللعبة
settings.advanced.unsupported_system_options=إعدادات غير مطبّقة على النظام الحالي
@@ -1268,13 +1268,9 @@ settings.advanced.post_exit_command=Comando post-salida
settings.advanced.post_exit_command.prompt=El comando se ejecuta después de que el juego se detenga
settings.advanced.renderer=Renderizador
settings.advanced.renderer.default=Por defecto
settings.advanced.renderer.default.desc=OpenGL
settings.advanced.renderer.d3d12=Mesa D3D12
settings.advanced.renderer.d3d12.desc=DirectX 12 (Rendimiento y compatibilidad deficientes)
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
settings.advanced.renderer.llvmpipe.desc=Software (Bajo rendimiento, máxima compatibilidad)
settings.advanced.renderer.zink=Mesa Zink
settings.advanced.renderer.zink.desc=Vulkan (Máximo rendimiento, baja compatibilidad)
settings.advanced.server_ip=Dirección del servidor
settings.advanced.server_ip.prompt=Entrar automáticamente después de ejecutar el juego
settings.advanced.unsupported_system_options=Configuración no aplicable al sistema actual
@@ -792,13 +792,9 @@ settings.advanced.post_exit_command=終了後のコマンド
settings.advanced.post_exit_command.prompt=ゲーム終了後に実行されます
settings.advanced.renderer=レンダラー
settings.advanced.renderer.default=デフォルト
settings.advanced.renderer.default.desc=OpenGL
settings.advanced.renderer.d3d12=Mesa D3D12
settings.advanced.renderer.d3d12.desc=DirectX 12 (低パフォーマンス、低互換性)
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
settings.advanced.renderer.llvmpipe.desc=ソフトウェア (低速だが、互換性は最高)
settings.advanced.renderer.zink=Mesa Zink
settings.advanced.renderer.zink.desc=Vulkan (最高パフォーマンス、低互換性)
settings.advanced.server_ip=サーバーアドレス
settings.advanced.server_ip.prompt=ゲームの起動時にサーバーに参加する
settings.advanced.unsupported_system_options=サポートされていないシステムオプション
@@ -1085,13 +1085,9 @@ settings.advanced.post_exit_command=令於戲訖
settings.advanced.post_exit_command.prompt=將後戲訖而行
settings.advanced.renderer=繪器
settings.advanced.renderer.default=本
settings.advanced.renderer.default.desc=開圖庫
settings.advanced.renderer.d3d12=Mesa D3D12
settings.advanced.renderer.d3d12.desc=DirectX 12 (效與所適皆差,勘誤之用)
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
settings.advanced.renderer.llvmpipe.desc=軟繪器 (效差,所適至)
settings.advanced.renderer.zink=Mesa Zink
settings.advanced.renderer.zink.desc=Vulkan (效至,所適差)
settings.advanced.server_ip=伺服器之址
settings.advanced.server_ip.prompt=本之址,啟訖徑入是伺服器
settings.advanced.unsupported_system_options=不逮今之械網所選
@@ -1259,13 +1259,9 @@ settings.advanced.post_exit_command=Команда после выхода
settings.advanced.post_exit_command.prompt=Команды, которые необходимо выполнить после выхода из игры
settings.advanced.renderer=Рендерер
settings.advanced.renderer.default=По умолчанию
settings.advanced.renderer.default.desc=OpenGL
settings.advanced.renderer.d3d12=Mesa D3D12
settings.advanced.renderer.d3d12.desc=DirectX 12 (Низкая производительность и совместимость)
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
settings.advanced.renderer.llvmpipe.desc=ПО (Низкая производительность, лучшая совместимость)
settings.advanced.renderer.zink=Mesa Zink
settings.advanced.renderer.zink.desc=Vulkan (Лучшая производительность, низкая совместимость)
settings.advanced.server_ip=Адрес сервера
settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры
settings.advanced.unsupported_system_options=Настройки, не применимые к текущей системе
@@ -1206,13 +1206,9 @@ settings.advanced.post_exit_command=Команда після виходу
settings.advanced.post_exit_command.prompt=Команди для виконання після виходу з гри
settings.advanced.renderer=Рендерер
settings.advanced.renderer.default=По умолчанию
settings.advanced.renderer.default.desc=OpenGL
settings.advanced.renderer.d3d12=Mesa D3D12
settings.advanced.renderer.d3d12.desc=DirectX 12 (Погана продуктивність та сумісність)
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
settings.advanced.renderer.llvmpipe.desc=Програмний (Погана продуктивність, найкраща сумісність)
settings.advanced.renderer.zink=Mesa Zink
settings.advanced.renderer.zink.desc=Vulkan (Найкраща продуктивність, погана сумісність)
settings.advanced.server_ip=Адреса сервера
settings.advanced.server_ip.prompt=Автоматично приєднатися після запуску гри
settings.advanced.unsupported_system_options=Налаштування, що не застосовуються до поточної системи
@@ -1127,6 +1127,15 @@ settings.advanced.dont_patch_natives=不嘗試自動取代本機庫
settings.advanced.environment_variables=環境變數
settings.advanced.game_dir.default=預設 (".minecraft/")
settings.advanced.game_dir.independent=各實例獨立 (".minecraft/versions/<實例名>/",除 assets、libraries 外)
settings.advanced.graphics_backend=圖形 API
settings.advanced.graphics_backend.default=預設
settings.advanced.graphics_backend.default.desc=遵循遊戲設定
settings.advanced.graphics_backend.opengl=OpenGL
settings.advanced.graphics_backend.opengl.desc=使用 OpenGL 渲染
settings.advanced.graphics_backend.vulkan=Vulkan
settings.advanced.graphics_backend.vulkan.desc=使用 Vulkan 渲染
settings.advanced.graphics_backend.vulkan.desc.global=使用 Vulkan 渲染 (適用於 Minecraft 26.2 及更高版本)
settings.advanced.graphics_backend.vulkan.desc.unsupported=使用 Vulkan 渲染 (不適用於當前遊戲版本)
settings.advanced.java_permanent_generation_space=記憶體永久儲存區域
settings.advanced.java_permanent_generation_space.prompt=單位 MiB
settings.advanced.jvm=Java 虛擬機設定
@@ -1166,13 +1175,17 @@ settings.advanced.post_exit_command=遊戲結束後執行指令
settings.advanced.post_exit_command.prompt=將在遊戲結束後呼叫使用
settings.advanced.renderer=繪製器
settings.advanced.renderer.default=預設
settings.advanced.renderer.default.desc=OpenGL
settings.advanced.renderer.default.desc=使用系統預設繪製器
settings.advanced.renderer.d3d12=Mesa D3D12
settings.advanced.renderer.d3d12.desc=DirectX 12 (效能與相容性較差,用於除錯)
settings.advanced.renderer.d3d12.desc=基於 DirectX 12 的 OpenGL 繪製器
settings.advanced.renderer.lavapipe=Mesa Lavapipe
settings.advanced.renderer.lavapipe.desc=軟體 Vulkan 繪製器
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
settings.advanced.renderer.llvmpipe.desc=軟繪製器 (效能較差,相容性最好)
settings.advanced.renderer.llvmpipe.desc=軟體 OpenGL 繪製器
settings.advanced.renderer.dozen=Mesa Dozen
settings.advanced.renderer.dozen.desc=基於 DirectX 12 的 Vulkan 繪製器 (實驗性)
settings.advanced.renderer.zink=Mesa Zink
settings.advanced.renderer.zink.desc=Vulkan (效能最好,相容性較差)
settings.advanced.renderer.zink.desc=基於 Vulkan 的 OpenGL 繪製器
settings.advanced.server_ip=伺服器位址
settings.advanced.server_ip.prompt=預設,啟動遊戲後直接進入對應伺服器
settings.advanced.unsupported_system_options=不適用於目前系統的選項
@@ -1132,6 +1132,15 @@ settings.advanced.dont_patch_natives=不尝试自动替换本地库
settings.advanced.environment_variables=环境变量
settings.advanced.game_dir.default=默认 (".minecraft/")
settings.advanced.game_dir.independent=各实例独立 (存放在 ".minecraft/versions/<实例名>/",除 assets、libraries 外)
settings.advanced.graphics_backend=图形 API
settings.advanced.graphics_backend.default=默认
settings.advanced.graphics_backend.default.desc=遵循游戏设置
settings.advanced.graphics_backend.opengl=OpenGL
settings.advanced.graphics_backend.opengl.desc=使用 OpenGL 渲染
settings.advanced.graphics_backend.vulkan=Vulkan
settings.advanced.graphics_backend.vulkan.desc=使用 Vulkan 渲染
settings.advanced.graphics_backend.vulkan.desc.global=使用 Vulkan 渲染 (适用于 Minecraft 26.2 及更高版本)
settings.advanced.graphics_backend.vulkan.desc.unsupported=使用 Vulkan 渲染 (不适用于当前游戏版本)
settings.advanced.java_permanent_generation_space=内存永久保存区域
settings.advanced.java_permanent_generation_space.prompt=单位 MiB
settings.advanced.jvm=Java 虚拟机设置
@@ -1171,13 +1180,17 @@ settings.advanced.post_exit_command=游戏结束后执行命令
settings.advanced.post_exit_command.prompt=将在游戏结束后调用
settings.advanced.renderer=渲染器
settings.advanced.renderer.default=默认
settings.advanced.renderer.default.desc=OpenGL
settings.advanced.renderer.default.desc=使用系统默认渲染器
settings.advanced.renderer.d3d12=Mesa D3D12
settings.advanced.renderer.d3d12.desc=DirectX 12 (性能与兼容性较差,用于调试)
settings.advanced.renderer.d3d12.desc=基于 DirectX 12 的 OpenGL 渲染器
settings.advanced.renderer.dozen=Mesa Dozen
settings.advanced.renderer.dozen.desc=基于 DirectX 12 的 Vulkan 渲染器 (实验性)
settings.advanced.renderer.lavapipe=Mesa Lavapipe
settings.advanced.renderer.lavapipe.desc=软件 Vulkan 渲染器
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
settings.advanced.renderer.llvmpipe.desc=软渲染器 (性能较差,兼容性最好)
settings.advanced.renderer.llvmpipe.desc=软件 OpenGL 渲染器
settings.advanced.renderer.zink=Mesa Zink
settings.advanced.renderer.zink.desc=Vulkan (性能最好,兼容性较差)
settings.advanced.renderer.zink.desc=基于 Vulkan 的 OpenGL 渲染器
settings.advanced.server_ip=服务器地址
settings.advanced.server_ip.prompt=默认,启动游戏后可以直接进入对应服务器
settings.advanced.unsupported_system_options=不适用于当前系统的选项
+15 -15
View File
@@ -4403,13 +4403,13 @@
},
"windows-x86_64": {
"mesa-loader": {
"name": "org.glavo:mesa-loader-windows:25.0.3:x64",
"name": "org.glavo:mesa-loader-windows:26.0.4:x64",
"downloads": {
"artifact": {
"path": "org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x64.jar",
"sha1": "b2552fcc8c98e95b4559c39f2ff87cdb3aaaa513",
"size": 27971214
"path": "org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-x64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-x64.jar",
"sha1": "f8da709c59ef61f531c91434ca0e3b4f39202981",
"size": 49873846
}
}
},
@@ -4427,13 +4427,13 @@
},
"windows-x86": {
"mesa-loader": {
"name": "org.glavo:mesa-loader-windows:25.0.3:x86",
"name": "org.glavo:mesa-loader-windows:26.0.4:x86",
"downloads": {
"artifact": {
"path": "org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x86.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-x86.jar",
"sha1": "6fd0fd7da88ca6636f735e1e0f6feb7f62f59715",
"size": 23311299
"path": "org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-x86.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-x86.jar",
"sha1": "ac6afaa8baa7c17468267c09e77e1296ee92d5ed",
"size": 41742113
}
}
}
@@ -4816,13 +4816,13 @@
"com.mojang:text2speech:1.12.4:natives": null,
"com.mojang:text2speech:1.13.9:natives-windows": null,
"mesa-loader": {
"name": "org.glavo:mesa-loader-windows:25.0.3:arm64",
"name": "org.glavo:mesa-loader-windows:26.0.4:arm64",
"downloads": {
"artifact": {
"path": "org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/25.0.3/mesa-loader-windows-25.0.3-arm64.jar",
"sha1": "40ca5de3351bf48b7b9efa5d2598d5b6b4d1ab8e",
"size": 24905270
"path": "org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/26.0.4/mesa-loader-windows-26.0.4-arm64.jar",
"sha1": "5c761a344700a07eaded51c5cf0cde36ee614706",
"size": 43504189
}
}
}
@@ -0,0 +1,33 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import java.util.Locale;
/// The Graphics API.
public enum GraphicsAPI {
DEFAULT,
OPENGL,
VULKAN;
private final String minecraftArg = name().toLowerCase(Locale.ROOT);
public String getMinecraftArg() {
return minecraftArg;
}
}
@@ -56,6 +56,7 @@ public class LaunchOptions implements Serializable {
private NativesDirectoryType nativesDirType;
private String nativesDir;
private ProcessPriority processPriority = ProcessPriority.NORMAL;
private GraphicsAPI graphicsBackend = GraphicsAPI.DEFAULT;
private Renderer renderer = Renderer.DEFAULT;
private boolean useNativeGLFW;
private boolean useNativeOpenAL;
@@ -244,7 +245,11 @@ public class LaunchOptions implements Serializable {
return processPriority;
}
public Renderer getRenderer() {
public @NotNull GraphicsAPI getGraphicsBackend() {
return graphicsBackend;
}
public @NotNull Renderer getRenderer() {
return renderer;
}
@@ -435,8 +440,13 @@ public class LaunchOptions implements Serializable {
return this;
}
public Builder setRenderer(@NotNull Renderer renderer) {
options.renderer = renderer;
public Builder setGraphicsBackend(GraphicsAPI backend) {
options.graphicsBackend = Objects.requireNonNullElse(backend, GraphicsAPI.DEFAULT);
return this;
}
public Builder setRenderer(Renderer renderer) {
options.renderer = Objects.requireNonNullElse(renderer, Renderer.DEFAULT);
return this;
}
@@ -17,9 +17,73 @@
*/
package org.jackhuang.hmcl.game;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
/// @author Glavo
@NotNullByDefault
public enum Renderer {
DEFAULT,
ZINK,
LLVMPIPE,
D3D12
DEFAULT(GraphicsAPI.DEFAULT, null, null),
LAVAPIPE(GraphicsAPI.VULKAN, "lavapipe", "lvp"),
// Currently, Dozen does not support the VK_KHR_push_descriptor feature, so it cannot launch Minecraft 26.2
// Using Dozen can run Minecraft 1.21.11 + VulkanMod, but it will cause the game to crash after playing for a while
DOZEN(GraphicsAPI.VULKAN, "dzn", "dzn"),
LLVMPIPE(GraphicsAPI.OPENGL, "llvmpipe", null),
ZINK(GraphicsAPI.OPENGL, "zink", null),
D3D12(GraphicsAPI.OPENGL, "d3d12", null),
;
/// Map the graphics API to supported renderers.
public static final Map<GraphicsAPI, List<Renderer>> SUPPORTED;
static {
var map = new EnumMap<GraphicsAPI, List<Renderer>>(GraphicsAPI.class);
for (var api : GraphicsAPI.values()) {
map.put(api, Stream.of(values()).filter(it -> it.isSupported(api)).toList());
}
SUPPORTED = map;
}
private final GraphicsAPI api;
private final @Nullable String loaderName;
private final @Nullable String icdName;
Renderer(GraphicsAPI api, @Nullable String loaderName, @Nullable String icdName) {
this.api = api;
this.loaderName = loaderName;
this.icdName = icdName;
}
/// Get the Graphics API used by this renderer.
public GraphicsAPI getApi() {
return api;
}
public boolean isSupported(GraphicsAPI api) {
return this.api == api || this.api == GraphicsAPI.DEFAULT;
}
public @Nullable String getMesaDriverName() {
return loaderName;
}
public @Nullable String getIcdName() {
return icdName;
}
public @Nullable String getIcdFileName() {
return icdName != null ? icdName + "_icd.json" : null;
}
}
@@ -104,7 +104,7 @@ public class DefaultLauncher extends Launcher {
// Executable
if (StringUtils.isNotBlank(options.getWrapper()))
res.addAllWithoutParsing(StringUtils.tokenize(options.getWrapper(), getEnvVars()));
res.addAllWithoutParsing(StringUtils.tokenize(options.getWrapper(), getEnvVars(nativeFolder)));
res.add(options.getJava().getBinary().toString());
@@ -265,6 +265,12 @@ public class DefaultLauncher extends Launcher {
res.addDefault("-Dfml.ignorePatchDiscrepancies=", "true");
}
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
&& options.getRenderer() != null
&& options.getRenderer().getMesaDriverName() != null) {
res.addDefault("-Dorg.glavo.mesa.loader.nativeDir=", FileUtils.getAbsolutePath(nativeFolder.resolve("mesa-loader")));
}
Set<String> classpath = repository.getClasspath(version);
if (analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)) {
@@ -395,6 +401,12 @@ public class DefaultLauncher extends Launcher {
}
}
if (options.getGraphicsBackend() != GraphicsAPI.DEFAULT
&& gameVersion.isPresent() && GameVersionNumber.compare(gameVersion.get(), "26.2-snapshot-2") >= 0) {
res.add("--graphicsBackend");
res.add(options.getGraphicsBackend().getMinecraftArg());
}
res.addAllWithoutParsing(Arguments.parseStringArguments(options.getGameArguments(), configuration));
res.removeIf(it -> getForbiddens().containsKey(it) && getForbiddens().get(it).get());
@@ -564,8 +576,8 @@ public class DefaultLauncher extends Launcher {
Path runDirectory = repository.getRunDirectory(version.getId());
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPreLaunchCommand(), getEnvVars())).directory(runDirectory.toFile());
builder.environment().putAll(getEnvVars());
ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPreLaunchCommand(), getEnvVars(nativeFolder))).directory(runDirectory.toFile());
builder.environment().putAll(getEnvVars(nativeFolder));
SystemUtils.callExternalProcess(builder);
}
@@ -578,7 +590,7 @@ public class DefaultLauncher extends Launcher {
Path appdata = options.getGameDir().toAbsolutePath().getParent();
if (appdata != null) builder.environment().put("APPDATA", appdata.toString());
builder.environment().putAll(getEnvVars());
builder.environment().putAll(getEnvVars(nativeFolder));
process = builder.start();
} catch (IOException e) {
throw new ProcessCreationException(e);
@@ -586,12 +598,35 @@ public class DefaultLauncher extends Launcher {
ManagedProcess p = new ManagedProcess(process, rawCommandLine);
if (listener != null)
startMonitors(p, listener, command.encoding, daemon);
startMonitors(p, nativeFolder, listener, command.encoding, daemon);
return p;
}
private Map<String, String> getEnvVars() {
private @Nullable Path findVulkanDescriptorFile(List<Path> dirs, String driverNameBase) {
String archName = switch (options.getJava().getArchitecture()) {
case X86 -> "i686";
case X86_64 -> "x86_64";
default -> options.getJava().getArchitecture().getCheckedName();
};
for (Path dir : dirs) {
if (Files.isDirectory(dir)) {
Path file = dir.resolve(driverNameBase + "." + archName + ".json");
if (Files.isRegularFile(file))
return file;
file = dir.resolve(driverNameBase + ".json");
if (Files.isRegularFile(file))
return file;
}
}
return null;
}
private Map<String, String> getEnvVars(Path nativeFolder) {
String versionName = Optional.ofNullable(options.getVersionName()).orElse(version.getId());
Map<String, String> env = new LinkedHashMap<>();
env.put("INST_NAME", versionName);
env.put("INST_ID", versionName);
@@ -602,15 +637,26 @@ public class DefaultLauncher extends Launcher {
Renderer renderer = options.getRenderer();
if (renderer != Renderer.DEFAULT) {
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
if (renderer != Renderer.LLVMPIPE)
env.put("GALLIUM_DRIVER", renderer.name().toLowerCase(Locale.ROOT));
if (renderer.getMesaDriverName() != null) {
if (renderer.getApi() == GraphicsAPI.OPENGL && renderer != Renderer.LLVMPIPE)
env.put("GALLIUM_DRIVER", renderer.getMesaDriverName());
if (renderer.getApi() == GraphicsAPI.VULKAN) {
String icdFile = FileUtils.getAbsolutePath(nativeFolder.resolve("mesa-loader/" + renderer.getIcdFileName()));
env.put("VK_ICD_FILENAMES", icdFile);
env.put("VK_DRIVER_FILES", icdFile);
}
}
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) {
env.put("__GLX_VENDOR_LIBRARY_NAME", "mesa");
switch (renderer) {
case LLVMPIPE:
case LLVMPIPE: {
env.put("__GLX_VENDOR_LIBRARY_NAME", "mesa");
env.put("LIBGL_ALWAYS_SOFTWARE", "1");
break;
case ZINK:
}
case ZINK: {
env.put("__GLX_VENDOR_LIBRARY_NAME", "mesa");
env.put("MESA_LOADER_DRIVER_OVERRIDE", "zink");
/*
* The amdgpu DDX is missing support for modifiers, causing Zink to fail.
@@ -620,6 +666,19 @@ public class DefaultLauncher extends Launcher {
*/
env.put("LIBGL_KOPPER_DRI2", "1");
break;
}
case LAVAPIPE: {
Path lvp = findVulkanDescriptorFile(
List.of(Path.of("/usr/share/vulkan/icd.d"), Path.of("/etc/vulkan/icd.d")),
"lvp_icd"
);
if (lvp != null) {
env.put("VK_ICD_FILENAMES", FileUtils.getAbsolutePath(lvp));
env.put("VK_DRIVER_FILES", FileUtils.getAbsolutePath(lvp));
}
break;
}
}
}
}
@@ -677,7 +736,7 @@ public class DefaultLauncher extends Launcher {
final Command commandLine = generateCommandLine(nativeFolder);
final String command = usePowerShell ? null : commandLine.commandLine.toString();
Map<String, String> envVars = getEnvVars();
Map<String, String> envVars = getEnvVars(nativeFolder);
if (isWindows && !usePowerShell) {
// https://stackoverflow.com/a/28452546
@@ -820,7 +879,7 @@ public class DefaultLauncher extends Launcher {
throw new ExecutionPolicyLimitException();
}
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, Charset encoding, boolean isDaemon) {
private void startMonitors(ManagedProcess managedProcess, Path nativeFolder, ProcessListener processListener, Charset encoding, boolean isDaemon) {
processListener.setProcess(managedProcess);
Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> {
processListener.onLog(it, false);
@@ -837,8 +896,8 @@ public class DefaultLauncher extends Launcher {
if (StringUtils.isNotBlank(options.getPostExitCommand())) {
try {
ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPostExitCommand(), getEnvVars())).directory(options.getGameDir().toFile());
builder.environment().putAll(getEnvVars());
ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPostExitCommand(), getEnvVars(nativeFolder))).directory(options.getGameDir().toFile());
builder.environment().putAll(getEnvVars(nativeFolder));
SystemUtils.callExternalProcess(builder);
} catch (Throwable e) {
LOG.warning("An Exception happened while running exit command.", e);
@@ -914,4 +914,6 @@
26.1
26.1.1-rc-1
26.1.1
26.1.2-rc-1
26.1.2
26w14a