Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9b1cfec5a | ||
|
|
82460ebbcb | ||
|
|
3e8a9bf1c4 | ||
|
|
a2db925e86 | ||
|
|
fe70cc3daf | ||
|
|
5991112cd8 | ||
|
|
084d91c62a | ||
|
|
4963f7a5f0 | ||
|
|
f42d5219c2 |
No files matched your search
+5
-1
@@ -64,4 +64,8 @@ language-subtag-registry
|
||||
# test
|
||||
/hmcl.json
|
||||
/.hmcl.json
|
||||
/.hmcl/
|
||||
/.hmcl/
|
||||
|
||||
# AI
|
||||
/.gemini/
|
||||
!/.gemini/config.yaml
|
||||
@@ -173,11 +173,10 @@ public final class LauncherHelper {
|
||||
}
|
||||
}),
|
||||
Task.composeAsync(() -> {
|
||||
Renderer renderer = setting.getRenderer();
|
||||
if (renderer == null || renderer.getMesaDriverName() == null
|
||||
|| OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) {
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS
|
||||
|| !(setting.getRenderer() instanceof Renderer.Driver renderer)
|
||||
|| renderer.mesaDriverName() == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION);
|
||||
if (lib == null)
|
||||
@@ -188,7 +187,7 @@ public final class LauncherHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
String agent = FileUtils.getAbsolutePath(file) + "=" + renderer.getMesaDriverName();
|
||||
String agent = FileUtils.getAbsolutePath(file) + "=" + renderer.mesaDriverName();
|
||||
|
||||
if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {
|
||||
return new LibraryDownloadTask(dependencyManager, file, lib)
|
||||
|
||||
@@ -836,7 +836,7 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
|
||||
obj.addProperty("graphicsBackend", src.getGraphicsBackend().name());
|
||||
obj.addProperty("renderer", src.getRenderer().name());
|
||||
if (src.getRenderer() == Renderer.LLVMPIPE)
|
||||
if (src.getRenderer() == Renderer.OpenGL.LLVMPIPE)
|
||||
obj.addProperty("useSoftwareRenderer", true);
|
||||
|
||||
return obj;
|
||||
@@ -909,15 +909,9 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
}
|
||||
|
||||
vs.setRenderer(Optional.ofNullable(obj.get("renderer")).map(JsonElement::getAsString)
|
||||
.flatMap(name -> {
|
||||
try {
|
||||
return Optional.of(Renderer.valueOf(name.toUpperCase(Locale.ROOT)));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}).orElseGet(() -> {
|
||||
.map(Renderer::of).orElseGet(() -> {
|
||||
boolean useSoftwareRenderer = Optional.ofNullable(obj.get("useSoftwareRenderer")).map(JsonElement::getAsBoolean).orElse(false);
|
||||
return useSoftwareRenderer ? Renderer.LLVMPIPE : Renderer.DEFAULT;
|
||||
return useSoftwareRenderer ? Renderer.OpenGL.LLVMPIPE : Renderer.DEFAULT;
|
||||
}));
|
||||
|
||||
vs.setGraphicsBackend(Optional.ofNullable(obj.get("graphicsBackend")).map(JsonElement::getAsString)
|
||||
@@ -927,7 +921,7 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}).orElseGet(() -> vs.getRenderer().getApi()));
|
||||
}).orElseGet(() -> vs.getRenderer() instanceof Renderer.Driver renderer ? renderer.api() : GraphicsAPI.DEFAULT));
|
||||
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -164,6 +164,9 @@ public class AccountListItem extends RadioButton {
|
||||
if (skinImg.isError()) {
|
||||
throw new InvalidSkinException("Failed to read skin image", skinImg.getException());
|
||||
}
|
||||
if (skinImg.getWidth() != 64 || (skinImg.getHeight() != 32 && skinImg.getHeight() != 64)) {
|
||||
throw new InvalidSkinException("Invalid skin size");
|
||||
}
|
||||
NormalizedSkin skin = new NormalizedSkin(skinImg);
|
||||
String model = skin.isSlim() ? "slim" : "";
|
||||
LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]");
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
|
||||
public class MenuSeparator extends StackPane {
|
||||
|
||||
@@ -28,7 +28,7 @@ public class MenuSeparator extends StackPane {
|
||||
Rectangle rect = new Rectangle();
|
||||
rect.widthProperty().bind(widthProperty().add(-14));
|
||||
rect.setHeight(1);
|
||||
rect.setFill(Color.GRAY);
|
||||
rect.fillProperty().bind(Themes.colorSchemeProperty().getOutlineVariant());
|
||||
maxHeightProperty().set(10);
|
||||
setPadding(new Insets(3));
|
||||
getChildren().setAll(rect);
|
||||
|
||||
@@ -147,6 +147,10 @@ public class TwoLineListItem extends VBox {
|
||||
subtitleProperty().set(subtitle);
|
||||
}
|
||||
|
||||
public HBox getFirstLine() {
|
||||
return firstLine;
|
||||
}
|
||||
|
||||
public Label getTitleLabel() {
|
||||
return lblTitle;
|
||||
}
|
||||
@@ -168,6 +172,7 @@ public class TwoLineListItem extends VBox {
|
||||
Bindings.bindContent(tagsBox.getChildren(), tags);
|
||||
|
||||
var scrollPane = new ScrollPane(tagsBox);
|
||||
scrollPane.setMinSize(0, 0);
|
||||
HBox.setHgrow(scrollPane, Priority.ALWAYS);
|
||||
lblTitle.setMinWidth(Label.USE_PREF_SIZE);
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
|
||||
@@ -353,12 +353,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||
skinnable.forbidDraggingWindow(refreshNavButton);
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||
separator.heightProperty().bind(navBar.heightProperty());
|
||||
separator.setFill(Color.GRAY);
|
||||
|
||||
navRight.getChildren().setAll(refreshNavButton, separator);
|
||||
navRight.getChildren().setAll(refreshNavButton);
|
||||
navBar.setRight(navRight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,22 +219,20 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
return I18n.hasKey(bundleKey) ? i18n(bundleKey) : null;
|
||||
});
|
||||
rendererPane.setValue(Renderer.DEFAULT);
|
||||
rendererPane.setItems(Renderer.DEFAULT);
|
||||
|
||||
FXUtils.onChangeAndOperate(graphicsBackendPane.valueProperty(), backend -> {
|
||||
if (backend == null) { // unbind
|
||||
return;
|
||||
}
|
||||
|
||||
rendererPane.setItems(Renderer.SUPPORTED.get(backend));
|
||||
rendererPane.setItems(Renderer.getSupported(backend));
|
||||
if (backend == GraphicsAPI.DEFAULT) {
|
||||
rendererPane.setDisable(true);
|
||||
rendererPane.setValue(Renderer.DEFAULT);
|
||||
} else {
|
||||
rendererPane.setDisable(false);
|
||||
if (rendererPane.getValue() == null || !rendererPane.getValue().isSupported(backend)) {
|
||||
if (!(rendererPane.getValue() instanceof Renderer.Driver driver) || driver.api() != backend)
|
||||
rendererPane.setValue(Renderer.DEFAULT);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -225,8 +225,10 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
content.setSubtitle(getSkinnable().addon.getDescription());
|
||||
content.getSubtitleLabel().setWrapText(true);
|
||||
getSkinnable().addon.getCategories().stream()
|
||||
.filter(category -> getSkinnable().page.shouldDisplayCategory(category))
|
||||
.map(category -> getSkinnable().page.getLocalizedCategory(category))
|
||||
.forEach(content::addTag);
|
||||
content.getFirstLine().setMinWidth(0);
|
||||
descriptionPane.getChildren().add(content);
|
||||
|
||||
if (getSkinnable().mod != null) {
|
||||
|
||||
@@ -197,7 +197,7 @@ public final class NativePatcher {
|
||||
if (windowsVersion.isAtLeast(OSVersion.WINDOWS_10)) {
|
||||
return getNatives(java.getPlatform()).get("mesa-loader");
|
||||
} else if (windowsVersion.isAtLeast(OSVersion.WINDOWS_7)) {
|
||||
if (renderer == Renderer.LLVMPIPE)
|
||||
if (renderer == Renderer.OpenGL.LLVMPIPE)
|
||||
return getNatives(java.getPlatform()).get("software-renderer-loader");
|
||||
else
|
||||
return null;
|
||||
|
||||
@@ -414,7 +414,7 @@
|
||||
}
|
||||
|
||||
.installer-item-wrapper .installer-item:list-item > .installer-item-name {
|
||||
-fx-pref-width: 80px;
|
||||
-fx-pref-width: 100px;
|
||||
}
|
||||
|
||||
.installer-item-wrapper .installer-item:list-item > .installer-item-status {
|
||||
|
||||
@@ -1389,14 +1389,44 @@ settings.advanced.post_exit_command.prompt=Commands to execute after the game ex
|
||||
settings.advanced.renderer=Renderer
|
||||
settings.advanced.renderer.default=Default
|
||||
settings.advanced.renderer.default.desc=Use System Default
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=OpenGL renderer based on DirectX 12
|
||||
# Vulkan Renderers
|
||||
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 OpenGL Renderer
|
||||
settings.advanced.renderer.dozen=Mesa Dozen
|
||||
settings.advanced.renderer.dozen.desc=Vulkan renderer based on DirectX 12 (Experimental)
|
||||
settings.advanced.renderer.nvidia_vulkan=NVIDIA
|
||||
settings.advanced.renderer.nvidia_vulkan.desc=Official Vulkan driver for NVIDIA GPUs
|
||||
settings.advanced.renderer.nvidia_nvk=NVIDIA (NVK)
|
||||
settings.advanced.renderer.nvidia_nvk.desc=Open-source Vulkan driver for NVIDIA GPUs
|
||||
settings.advanced.renderer.amdvlk=AMD
|
||||
settings.advanced.renderer.amdvlk.desc=Official Vulkan driver for AMD GPUs
|
||||
settings.advanced.renderer.amd_radv=AMD (RADV)
|
||||
settings.advanced.renderer.amd_radv.desc=Open-source Vulkan driver for AMD GPUs
|
||||
settings.advanced.renderer.intel_vulkan=Intel
|
||||
settings.advanced.renderer.intel_vulkan.desc=Official Vulkan driver for Intel GPUs
|
||||
settings.advanced.renderer.intel_anv=Intel (ANV)
|
||||
settings.advanced.renderer.intel_anv.desc=Open-source Vulkan driver for Intel GPUs
|
||||
settings.advanced.renderer.intel_hasvk=Intel (HASVK)
|
||||
settings.advanced.renderer.intel_hasvk.desc=Open-source Vulkan driver for older Intel GPUs
|
||||
settings.advanced.renderer.qualcomm=Qualcomm
|
||||
settings.advanced.renderer.qualcomm.desc=Official Vulkan driver for Qualcomm Adreno GPUs
|
||||
settings.advanced.renderer.turnip=Qualcomm (Turnip)
|
||||
settings.advanced.renderer.turnip.desc=Open-source Vulkan driver for Qualcomm Adreno GPUs
|
||||
settings.advanced.renderer.moltenvk=MoltenVK
|
||||
settings.advanced.renderer.moltenvk.desc=Vulkan renderer based on Metal
|
||||
settings.advanced.renderer.kosmickrisp=KosmicKrisp
|
||||
settings.advanced.renderer.kosmickrisp.desc=Vulkan renderer based on Metal 4 (Experimental)
|
||||
settings.advanced.renderer.powervr=PowerVR
|
||||
settings.advanced.renderer.powervr.desc=Open-source Vulkan driver for PowerVR GPUs (Experimental)
|
||||
settings.advanced.renderer.panvk=PanVK
|
||||
settings.advanced.renderer.panvk.desc=Open-source Vulkan driver for Arm Mali GPUs (Experimental)
|
||||
settings.advanced.renderer.v3dv=V3DV
|
||||
settings.advanced.renderer.v3dv.desc=Open-source Vulkan driver for Raspberry Pi 4/5 GPUs (Experimental)
|
||||
# OpenGL Renderers
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=Software OpenGL renderer
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=OpenGL renderer based on DirectX 12
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=OpenGL renderer based on Vulkan
|
||||
settings.advanced.server_ip=Server Address
|
||||
|
||||
@@ -1173,17 +1173,47 @@ settings.advanced.process_priority.high=高
|
||||
settings.advanced.process_priority.high.desc=優先保證遊戲執行,但可能會導致其他程式卡頓
|
||||
settings.advanced.post_exit_command=遊戲結束後執行指令
|
||||
settings.advanced.post_exit_command.prompt=將在遊戲結束後呼叫使用
|
||||
settings.advanced.renderer=繪製器
|
||||
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 的 OpenGL 繪製器
|
||||
# Vulkan Renderers
|
||||
settings.advanced.renderer.lavapipe=Mesa Lavapipe
|
||||
settings.advanced.renderer.lavapipe.desc=軟體 Vulkan 繪製器
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=軟體 OpenGL 繪製器
|
||||
settings.advanced.renderer.dozen=Mesa Dozen
|
||||
settings.advanced.renderer.dozen.desc=基於 DirectX 12 的 Vulkan 繪製器 (實驗性)
|
||||
settings.advanced.renderer.nvidia_vulkan=NVIDIA
|
||||
settings.advanced.renderer.nvidia_vulkan.desc=NVIDIA GPU 的官方 Vulkan 驅動
|
||||
settings.advanced.renderer.nvidia_nvk=NVIDIA (NVK)
|
||||
settings.advanced.renderer.nvidia_nvk.desc=NVIDIA GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.amdvlk=AMD
|
||||
settings.advanced.renderer.amdvlk.desc=AMD GPU 的官方 Vulkan 驅動
|
||||
settings.advanced.renderer.amd_radv=AMD (RADV)
|
||||
settings.advanced.renderer.amd_radv.desc=AMD GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.intel_vulkan=Intel
|
||||
settings.advanced.renderer.intel_vulkan.desc=Intel GPU 的官方 Vulkan 驅動
|
||||
settings.advanced.renderer.intel_anv=Intel (ANV)
|
||||
settings.advanced.renderer.intel_anv.desc=Intel GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.intel_hasvk=Intel (HASVK)
|
||||
settings.advanced.renderer.intel_hasvk.desc=Intel 舊 GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.qualcomm=高通
|
||||
settings.advanced.renderer.qualcomm.desc=高通 Adreno GPU 的官方 Vulkan 驅動
|
||||
settings.advanced.renderer.turnip=高通 (Turnip)
|
||||
settings.advanced.renderer.turnip.desc=高通 Adreno GPU 的開源 Vulkan 驅動
|
||||
settings.advanced.renderer.moltenvk=MoltenVK
|
||||
settings.advanced.renderer.moltenvk.desc=基於 Metal 的 Vulkan 繪製器
|
||||
settings.advanced.renderer.kosmickrisp=KosmicKrisp
|
||||
settings.advanced.renderer.kosmickrisp.desc=基於 Metal 4 的 Vulkan 繪製器 (實驗性)
|
||||
settings.advanced.renderer.powervr=PowerVR
|
||||
settings.advanced.renderer.powervr.desc=PowerVR GPU 的開源 Vulkan 驅動 (實驗性)
|
||||
settings.advanced.renderer.panvk=PanVK
|
||||
settings.advanced.renderer.panvk.desc=Arm Mali GPU 的開源 Vulkan 驅動 (實驗性)
|
||||
settings.advanced.renderer.v3dv=V3DV
|
||||
settings.advanced.renderer.v3dv.desc=樹莓派 4/5 GPU 的開源 Vulkan 驅動 (實驗性)
|
||||
# OpenGL Renderers
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=軟體 OpenGL 繪製器
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=基於 DirectX 12 的 OpenGL 繪製器
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=基於 Vulkan 的 OpenGL 繪製器
|
||||
settings.advanced.server_ip=伺服器位址
|
||||
|
||||
@@ -1178,17 +1178,47 @@ settings.advanced.process_priority.high=高
|
||||
settings.advanced.process_priority.high.desc=优先保证游戏运行,但可能会导致其他程序卡顿
|
||||
settings.advanced.post_exit_command=游戏结束后执行命令
|
||||
settings.advanced.post_exit_command.prompt=将在游戏结束后调用
|
||||
settings.advanced.renderer=渲染器
|
||||
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 的 OpenGL 渲染器
|
||||
settings.advanced.renderer.dozen=Mesa Dozen
|
||||
settings.advanced.renderer.dozen.desc=基于 DirectX 12 的 Vulkan 渲染器 (实验性)
|
||||
# Vulkan Renderers
|
||||
settings.advanced.renderer.lavapipe=Mesa Lavapipe
|
||||
settings.advanced.renderer.lavapipe.desc=软件 Vulkan 渲染器
|
||||
settings.advanced.renderer.dozen=Mesa Dozen
|
||||
settings.advanced.renderer.dozen.desc=基于 DirectX 12 的 Vulkan 渲染器 (实验性)
|
||||
settings.advanced.renderer.nvidia_vulkan=NVIDIA
|
||||
settings.advanced.renderer.nvidia_vulkan.desc=NVIDIA GPU 的官方 Vulkan 驱动
|
||||
settings.advanced.renderer.nvidia_nvk=NVIDIA (NVK)
|
||||
settings.advanced.renderer.nvidia_nvk.desc=NVIDIA GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.amdvlk=AMD
|
||||
settings.advanced.renderer.amdvlk.desc=AMD GPU 的官方 Vulkan 驱动
|
||||
settings.advanced.renderer.amd_radv=AMD (RADV)
|
||||
settings.advanced.renderer.amd_radv.desc=AMD GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.intel_vulkan=Intel
|
||||
settings.advanced.renderer.intel_vulkan.desc=Intel GPU 的官方 Vulkan 驱动
|
||||
settings.advanced.renderer.intel_anv=Intel (ANV)
|
||||
settings.advanced.renderer.intel_anv.desc=Intel GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.intel_hasvk=Intel (HASVK)
|
||||
settings.advanced.renderer.intel_hasvk.desc=Intel 旧 GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.qualcomm=高通
|
||||
settings.advanced.renderer.qualcomm.desc=高通 Adreno GPU 的官方 Vulkan 驱动
|
||||
settings.advanced.renderer.turnip=高通 (Turnip)
|
||||
settings.advanced.renderer.turnip.desc=高通 Adreno GPU 的开源 Vulkan 驱动
|
||||
settings.advanced.renderer.moltenvk=MoltenVK
|
||||
settings.advanced.renderer.moltenvk.desc=基于 Metal 的 Vulkan 渲染器
|
||||
settings.advanced.renderer.kosmickrisp=KosmicKrisp
|
||||
settings.advanced.renderer.kosmickrisp.desc=基于 Metal 4 的 Vulkan 渲染器 (实验性)
|
||||
settings.advanced.renderer.powervr=PowerVR
|
||||
settings.advanced.renderer.powervr.desc=PowerVR GPU 的开源 Vulkan 驱动 (实验性)
|
||||
settings.advanced.renderer.panvk=PanVK
|
||||
settings.advanced.renderer.panvk.desc=Arm Mali GPU 的开源 Vulkan 驱动 (实验性)
|
||||
settings.advanced.renderer.v3dv=V3DV
|
||||
settings.advanced.renderer.v3dv.desc=树莓派 4/5 GPU 的开源 Vulkan 驱动 (实验性)
|
||||
# OpenGL Renderers
|
||||
settings.advanced.renderer.llvmpipe=Mesa LLVMpipe
|
||||
settings.advanced.renderer.llvmpipe.desc=软件 OpenGL 渲染器
|
||||
settings.advanced.renderer.d3d12=Mesa D3D12
|
||||
settings.advanced.renderer.d3d12.desc=基于 DirectX 12 的 OpenGL 渲染器
|
||||
settings.advanced.renderer.zink=Mesa Zink
|
||||
settings.advanced.renderer.zink.desc=基于 Vulkan 的 OpenGL 渲染器
|
||||
settings.advanced.server_ip=服务器地址
|
||||
|
||||
@@ -17,73 +17,529 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;
|
||||
import org.jackhuang.hmcl.util.platform.macos.HomebrewUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/// @author Glavo
|
||||
@NotNullByDefault
|
||||
public enum Renderer {
|
||||
DEFAULT(GraphicsAPI.DEFAULT, null, null),
|
||||
public sealed interface Renderer permits Renderer.Default, Renderer.Driver, Renderer.Unknown {
|
||||
|
||||
LAVAPIPE(GraphicsAPI.VULKAN, "lavapipe", "lvp"),
|
||||
/// Default renderer.
|
||||
Default DEFAULT = new Default();
|
||||
|
||||
// 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"),
|
||||
/// Parse a renderer from a string.
|
||||
static Renderer of(String name) {
|
||||
if ("DEFAULT".equalsIgnoreCase(name) || name.isBlank())
|
||||
return DEFAULT;
|
||||
|
||||
LLVMPIPE(GraphicsAPI.OPENGL, "llvmpipe", null),
|
||||
ZINK(GraphicsAPI.OPENGL, "zink", null),
|
||||
D3D12(GraphicsAPI.OPENGL, "d3d12", null),
|
||||
;
|
||||
String upper = name.toUpperCase(Locale.ROOT).trim();
|
||||
|
||||
/// 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());
|
||||
try {
|
||||
return OpenGL.valueOf(upper);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
|
||||
SUPPORTED = map;
|
||||
try {
|
||||
return Vulkan.valueOf(upper);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
|
||||
return new Unknown(name);
|
||||
}
|
||||
|
||||
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 all supported renderers for a given graphics API.
|
||||
static @Unmodifiable List<Renderer> getSupported(GraphicsAPI api) {
|
||||
return switch (api) {
|
||||
case DEFAULT -> List.of(DEFAULT);
|
||||
case VULKAN -> Vulkan.Holder.SUPPORTED;
|
||||
case OPENGL -> OpenGL.SUPPORTED;
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the Graphics API used by this renderer.
|
||||
public GraphicsAPI getApi() {
|
||||
return api;
|
||||
String name();
|
||||
|
||||
final class Default implements Renderer {
|
||||
private Default() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "DEFAULT";
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSupported(GraphicsAPI api) {
|
||||
return this.api == api || this.api == GraphicsAPI.DEFAULT;
|
||||
sealed interface Driver extends Renderer permits Vulkan, OpenGL {
|
||||
|
||||
/// The graphics API that this driver belongs to.
|
||||
GraphicsAPI api();
|
||||
|
||||
/// If this driver can be loaded via [mesa-loader-windows](https://github.com/HMCL-dev/mesa-loader-windows),
|
||||
/// return the driver name, otherwise return `null`.
|
||||
@Contract(pure = true)
|
||||
default @Nullable String mesaDriverName() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getMesaDriverName() {
|
||||
return loaderName;
|
||||
enum Vulkan implements Driver {
|
||||
/// Mesa Lavapipe driver.
|
||||
///
|
||||
/// It is a software rasterizer.
|
||||
///
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/llvmpipe.html">LLVMpipe - The Mesa 3D Graphics Library</a>
|
||||
LAVAPIPE("lvp") {
|
||||
@Override
|
||||
public String mesaDriverName() {
|
||||
return "lavapipe";
|
||||
}
|
||||
},
|
||||
|
||||
/// Mesa Dozen driver.
|
||||
///
|
||||
/// It is a Vulkan driver based on DirectX 12.
|
||||
///
|
||||
/// ## Note
|
||||
/// 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("dzn") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.WINDOWS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mesaDriverName() {
|
||||
return "dzn";
|
||||
}
|
||||
},
|
||||
|
||||
/// NVIDIA Vulkan driver.
|
||||
///
|
||||
/// It is a Vulkan driver for NVIDIA GPUs.
|
||||
///
|
||||
/// @see <a href="https://developer.nvidia.com/vulkan">Vulkan Open Standard Modern GPU API | NVIDIA Developer</a>
|
||||
NVIDIA_VULKAN("nvidia") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return Vulkan.hasCard(cards, HardwareVendor.NVIDIA);
|
||||
}
|
||||
},
|
||||
|
||||
/// Mesa NVK driver.
|
||||
///
|
||||
/// It is a Vulkan driver for NVIDIA GPUs.
|
||||
///
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/nvk.html">NVK - The Mesa 3D Graphics Library</a>
|
||||
NVIDIA_NVK("nouveau") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.LINUX && Vulkan.hasCard(cards, HardwareVendor.NVIDIA);
|
||||
}
|
||||
},
|
||||
|
||||
/// AMD Open Source Driver for Vulkan
|
||||
///
|
||||
/// It is a Vulkan driver for AMD GPUs.
|
||||
///
|
||||
/// @see <a href="https://github.com/GPUOpen-Drivers/AMDVLK">GPUOpen-Drivers/AMDVLK - GitHub</a>
|
||||
AMDVLK("amd") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.LINUX && Vulkan.hasCard(cards, HardwareVendor.AMD);
|
||||
}
|
||||
},
|
||||
|
||||
/// Mesa RADV driver.
|
||||
///
|
||||
/// It is a Vulkan driver for AMD GCN/RDNA GPUs.
|
||||
///
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/radv.html">RADV - The Mesa 3D Graphics Library</a>
|
||||
AMD_RADV("radeon") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return Vulkan.hasCard(cards, HardwareVendor.AMD);
|
||||
}
|
||||
},
|
||||
|
||||
/// Intel Vulkan driver.
|
||||
///
|
||||
/// It is a Vulkan driver for Intel GPUs.
|
||||
INTEL_VULKAN("ig") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.WINDOWS && Vulkan.hasCard(cards, HardwareVendor.INTEL);
|
||||
}
|
||||
},
|
||||
|
||||
/// Mesa ANV driver.
|
||||
///
|
||||
/// It is a Vulkan driver for Intel GPUs.
|
||||
///
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/anv.html">ANV - The Mesa 3D Graphics Library</a>
|
||||
INTEL_ANV("intel") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() != OperatingSystem.WINDOWS && Vulkan.hasCard(cards, HardwareVendor.INTEL);
|
||||
}
|
||||
},
|
||||
|
||||
/// Intel HASVK driver.
|
||||
///
|
||||
/// It is a Vulkan driver for Intel Gen7 (Ivy Bridge / Haswell) and Gen8 (Broadwell) graphics.
|
||||
///
|
||||
/// @see <a href="https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/18208">intel: split vulkan driver between gfx7/8 and above</a>
|
||||
INTEL_HASVK("intel_hasvk") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() != OperatingSystem.WINDOWS
|
||||
&& cards != null
|
||||
&& cards.stream().anyMatch(card -> card.getVendor() == HardwareVendor.INTEL && card.getName().startsWith("Intel HD Graphics "));
|
||||
}
|
||||
},
|
||||
|
||||
/// Qualcomm Vulkan driver.
|
||||
///
|
||||
/// It is a Vulkan driver for Qualcomm Adreno GPUs.
|
||||
QUALCOMM("qc") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.equals(Platform.WINDOWS_ARM64);
|
||||
}
|
||||
},
|
||||
|
||||
/// Mesa Turnip driver.
|
||||
///
|
||||
/// It is a Vulkan driver for Qualcomm Adreno GPUs.
|
||||
///
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/freedreno.html">Freedreno - The Mesa 3D Graphics Library</a>
|
||||
TURNIP("freedreno") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() != OperatingSystem.WINDOWS && platform.arch().isArm();
|
||||
}
|
||||
},
|
||||
|
||||
/// MoltenVK driver.
|
||||
///
|
||||
/// It is a Vulkan driver for macOS, iOS, tvOS, and visionOS.
|
||||
///
|
||||
/// @see <a href="https://github.com/KhronosGroup/MoltenVK">KhronosGroup/MoltenVK - GitHub</a>
|
||||
MOLTENVK("MoltenVK") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.MACOS;
|
||||
}
|
||||
},
|
||||
|
||||
/// Mesa KosmicKrisp driver.
|
||||
///
|
||||
/// It is a Vulkan driver for Apple Silicon hardware.
|
||||
///
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/kosmickrisp.html">KosmicKrisp - The Mesa 3D Graphics Library</a>
|
||||
KOSMICKRISP("kosmickrisp_mesa") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.MACOS && platform.arch() == Architecture.ARM64;
|
||||
}
|
||||
},
|
||||
|
||||
/// Mesa PowerVR driver.
|
||||
///
|
||||
/// It is a Vulkan driver for Imagination Technologies PowerVR GPUs.
|
||||
///
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/powervr.html">PowerVR - The Mesa 3D Graphics Library</a>
|
||||
POWERVR("powervr") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.LINUX && Vulkan.hasCard(cards, HardwareVendor.IMG);
|
||||
}
|
||||
},
|
||||
|
||||
/// Mesa PanVK driver.
|
||||
///
|
||||
/// It is a Vulkan driver for ARM Mali GPUs.
|
||||
///
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/panfrost.html">Panfrost - The Mesa 3D Graphics Library</a>
|
||||
PANVK("panfrost") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.LINUX && platform.arch().isArm();
|
||||
}
|
||||
},
|
||||
|
||||
/// Mesa V3DV driver.
|
||||
///
|
||||
/// It is a Vulkan driver for the Raspberry Pi 4 and Raspberry Pi 5.
|
||||
///
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/v3dv.html">V3DV - The Mesa 3D Graphics Library</a>
|
||||
V3DV("broadcom") {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.LINUX && platform.arch().isArm()
|
||||
&& Vulkan.hasCard(cards, HardwareVendor.BROADCOM);
|
||||
}
|
||||
};
|
||||
|
||||
private static final class Holder {
|
||||
static final List<Renderer> SUPPORTED;
|
||||
static final Map<Vulkan, Path> DRIVER_TO_ICD_FILE;
|
||||
|
||||
static {
|
||||
var driverToIcdFile = new EnumMap<Vulkan, Path>(Vulkan.class);
|
||||
var supported = new LinkedHashSet<Renderer>();
|
||||
|
||||
|
||||
// Helper
|
||||
String archName = switch (Architecture.SYSTEM_ARCH) {
|
||||
case X86 -> "i686";
|
||||
case X86_64 -> "x86_64";
|
||||
case ARM64 -> "aarch64";
|
||||
default -> Architecture.SYSTEM_ARCH.getCheckedName();
|
||||
};
|
||||
|
||||
var icdFileNamePattern = Pattern.compile("(?<name>[a-zA-Z0-9_-]+)_icd(?:\\." + Pattern.quote(archName) + ")?\\.json");
|
||||
Map<String, Vulkan> icdNameToDriver = Stream.of(values()).collect(Collectors.toMap(Vulkan::icdName, Function.identity()));
|
||||
|
||||
supported.add(DEFAULT);
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
supported.addAll(List.of(LAVAPIPE, DOZEN));
|
||||
|
||||
List<GraphicsCard> graphicsCards = SystemInfo.getGraphicsCards();
|
||||
if (graphicsCards != null) {
|
||||
EnumSet<Vulkan> foundSupported = EnumSet.noneOf(Vulkan.class);
|
||||
for (GraphicsCard card : graphicsCards) {
|
||||
if (!card.getVulkanDriverFiles().isEmpty()) {
|
||||
for (Path icdFile : card.getVulkanDriverFiles()) {
|
||||
String fileName = FileUtils.getName(icdFile);
|
||||
if (!fileName.endsWith(".json"))
|
||||
continue;
|
||||
|
||||
Vulkan driver;
|
||||
|
||||
Matcher matcher = icdFileNamePattern.matcher(fileName);
|
||||
if (matcher.matches()) {
|
||||
String icdName = matcher.group("name");
|
||||
driver = icdNameToDriver.get(icdName);
|
||||
} else {
|
||||
switch (fileName.substring(0, fileName.length() - ".json".length())) {
|
||||
case "igvk64" -> {
|
||||
if (Architecture.SYSTEM_ARCH.getBits() == Bits.BIT_64)
|
||||
driver = INTEL_VULKAN;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
case "igvk32" -> {
|
||||
if (Architecture.SYSTEM_ARCH.getBits() == Bits.BIT_32)
|
||||
driver = INTEL_VULKAN;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
case "nv-vk64" -> {
|
||||
if (Architecture.SYSTEM_ARCH.getBits() == Bits.BIT_64)
|
||||
driver = NVIDIA_VULKAN;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
case "nv-vk32" -> {
|
||||
if (Architecture.SYSTEM_ARCH.getBits() == Bits.BIT_32)
|
||||
driver = NVIDIA_VULKAN;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
case "amd-vulkan64" -> {
|
||||
if (Architecture.SYSTEM_ARCH.getBits() == Bits.BIT_64)
|
||||
driver = AMDVLK;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
case "amd-vulkan32" -> {
|
||||
if (Architecture.SYSTEM_ARCH.getBits() == Bits.BIT_32)
|
||||
driver = AMDVLK;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
case "qcvk_icd_arm64x" -> {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64)
|
||||
driver = QUALCOMM;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
default -> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
driverToIcdFile.putIfAbsent(driver, icdFile);
|
||||
foundSupported.add(driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
supported.addAll(foundSupported);
|
||||
}
|
||||
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
// LWJGL integrates MoltenVK, so it is always available
|
||||
supported.add(MOLTENVK);
|
||||
|
||||
// We need libvulkan.1.dylib to load custom Vulkan drivers
|
||||
if (Files.isRegularFile(HomebrewUtils.LIB_VULKAN)) {
|
||||
Path kosmickrispIcd = HomebrewUtils.HOMEBREW_PREFIX.resolve("share/vulkan/icd.d/kosmickrisp_mesa_icd." + archName + ".json");
|
||||
if (Files.isRegularFile(kosmickrispIcd)) {
|
||||
driverToIcdFile.put(KOSMICKRISP, kosmickrispIcd);
|
||||
supported.add(KOSMICKRISP);
|
||||
}
|
||||
|
||||
Path lvpIcd = HomebrewUtils.HOMEBREW_PREFIX.resolve("share/vulkan/icd.d/lvp_icd." + archName + ".json");
|
||||
if (Files.isRegularFile(lvpIcd)) {
|
||||
driverToIcdFile.put(LAVAPIPE, lvpIcd);
|
||||
supported.add(LAVAPIPE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<Path> icdDirs = switch (OperatingSystem.CURRENT_OS) {
|
||||
case LINUX -> List.of(
|
||||
Path.of("/usr/share/vulkan/icd.d"),
|
||||
Path.of("/etc/vulkan/icd.d")
|
||||
);
|
||||
case FREEBSD -> List.of(Path.of("/usr/local/share/vulkan/icd.d"));
|
||||
default -> List.of();
|
||||
};
|
||||
|
||||
EnumSet<Vulkan> foundSupported = EnumSet.noneOf(Vulkan.class);
|
||||
for (Path icdDir : icdDirs) {
|
||||
if (!Files.isDirectory(icdDir))
|
||||
continue;
|
||||
try (Stream<Path> stream = Files.list(icdDir)) {
|
||||
for (Path icdFile : Lang.toIterable(stream)) {
|
||||
String fileName = icdFile.getFileName().toString();
|
||||
|
||||
Matcher matcher = icdFileNamePattern.matcher(fileName);
|
||||
if (matcher.matches()) {
|
||||
String icdName = matcher.group("name");
|
||||
|
||||
Vulkan driver = icdNameToDriver.get(icdName);
|
||||
if (driver != null) {
|
||||
driverToIcdFile.put(driver, icdFile);
|
||||
|
||||
if (driver.isSupported(Platform.CURRENT_PLATFORM, SystemInfo.getGraphicsCards())) {
|
||||
foundSupported.add(driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read Vulkan ICD files in " + icdDir, e);
|
||||
}
|
||||
}
|
||||
|
||||
supported.addAll(foundSupported);
|
||||
}
|
||||
|
||||
SUPPORTED = List.copyOf(supported);
|
||||
DRIVER_TO_ICD_FILE = Collections.unmodifiableMap(driverToIcdFile);
|
||||
}
|
||||
}
|
||||
|
||||
private final String icdName;
|
||||
|
||||
Vulkan(String icdName) {
|
||||
this.icdName = icdName;
|
||||
}
|
||||
|
||||
private static boolean hasCard(@Nullable List<GraphicsCard> cards, HardwareVendor vendor) {
|
||||
return cards != null && cards.stream().anyMatch(card -> card.getVendor() == vendor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphicsAPI api() {
|
||||
return GraphicsAPI.VULKAN;
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public String icdName() {
|
||||
return icdName;
|
||||
}
|
||||
|
||||
/// Get the path to the ICD file for this driver.
|
||||
///
|
||||
/// If the ICD file does not exist, return `null`.
|
||||
@Contract(pure = true)
|
||||
public @Nullable Path icdFile() {
|
||||
return Holder.DRIVER_TO_ICD_FILE.get(this);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getIcdName() {
|
||||
return icdName;
|
||||
enum OpenGL implements Driver {
|
||||
/// @see <a href="https://docs.mesa3d.org/drivers/llvmpipe.html">LLVMpipe - The Mesa 3D Graphics Library</a>
|
||||
LLVMPIPE {
|
||||
@Override
|
||||
public String mesaDriverName() {
|
||||
return "llvmpipe";
|
||||
}
|
||||
},
|
||||
ZINK {
|
||||
@Override
|
||||
public String mesaDriverName() {
|
||||
return "zink";
|
||||
}
|
||||
},
|
||||
D3D12 {
|
||||
@Override
|
||||
public boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return platform.os() == OperatingSystem.WINDOWS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mesaDriverName() {
|
||||
return "d3d12";
|
||||
}
|
||||
};
|
||||
|
||||
static final List<Renderer> SUPPORTED =
|
||||
Stream.concat(
|
||||
Stream.of(DEFAULT),
|
||||
Stream.of(OpenGL.values()).filter(it -> it.isSupported(
|
||||
Platform.CURRENT_PLATFORM,
|
||||
null // We don't need to pass graphics cards for OpenGL yet
|
||||
))
|
||||
).toList();
|
||||
|
||||
@Override
|
||||
public GraphicsAPI api() {
|
||||
return GraphicsAPI.OPENGL;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getIcdFileName() {
|
||||
return icdName != null ? icdName + "_icd.json" : null;
|
||||
/// Unknown renderer.
|
||||
record Unknown(String name) implements Renderer {
|
||||
}
|
||||
|
||||
/// Whether this renderer is supported on the given platform and graphics cards.
|
||||
///
|
||||
/// If `cards` is `null`, it means that the graphics cards are unknown.
|
||||
default boolean isSupported(Platform platform, @Nullable List<GraphicsCard> cards) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.Unzipper;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.platform.macos.HomebrewUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -266,11 +267,20 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||
&& options.getRenderer() != null
|
||||
&& options.getRenderer().getMesaDriverName() != null) {
|
||||
&& options.getRenderer() instanceof Renderer.Driver renderer
|
||||
&& renderer.mesaDriverName() != null) {
|
||||
res.addDefault("-Dorg.glavo.mesa.loader.nativeDir=", FileUtils.getAbsolutePath(nativeFolder.resolve("mesa-loader")));
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS
|
||||
&& options.getJava().getArchitecture() == Architecture.SYSTEM_ARCH
|
||||
&& options.getRenderer() instanceof Renderer.Vulkan vulkanDriver
|
||||
&& vulkanDriver.icdFile() != null) {
|
||||
if (Files.isRegularFile(HomebrewUtils.LIB_VULKAN)) {
|
||||
res.addDefault("-Dorg.lwjgl.vulkan.libname=", FileUtils.getAbsolutePath(HomebrewUtils.LIB_VULKAN));
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> classpath = repository.getClasspath(version);
|
||||
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)) {
|
||||
@@ -602,28 +612,6 @@ public class DefaultLauncher extends Launcher {
|
||||
return p;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
@@ -634,28 +622,31 @@ public class DefaultLauncher extends Launcher {
|
||||
env.put("INST_MC_DIR", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId())));
|
||||
env.put("INST_JAVA", options.getJava().getBinary().toString());
|
||||
|
||||
Renderer renderer = options.getRenderer();
|
||||
if (renderer != Renderer.DEFAULT) {
|
||||
if (options.getRenderer() instanceof Renderer.Driver driver) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
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()));
|
||||
if (driver.mesaDriverName() != null) {
|
||||
if (driver instanceof Renderer.OpenGL && driver != Renderer.OpenGL.LLVMPIPE)
|
||||
env.put("GALLIUM_DRIVER", driver.mesaDriverName());
|
||||
else if (driver instanceof Renderer.Vulkan vulkanDriver) {
|
||||
String icdFile = FileUtils.getAbsolutePath(nativeFolder.resolve("mesa-loader/" + vulkanDriver.icdName() + "_icd.json"));
|
||||
|
||||
env.put("VK_ICD_FILENAMES", icdFile);
|
||||
env.put("VK_DRIVER_FILES", icdFile);
|
||||
}
|
||||
} else if (driver instanceof Renderer.Vulkan vulkanDriver
|
||||
&& vulkanDriver.icdFile() != null
|
||||
&& options.getJava().getArchitecture() == Architecture.SYSTEM_ARCH) {
|
||||
String icdFile = FileUtils.getAbsolutePath(vulkanDriver.icdFile());
|
||||
|
||||
env.put("VK_ICD_FILENAMES", icdFile);
|
||||
env.put("VK_DRIVER_FILES", icdFile);
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) {
|
||||
switch (renderer) {
|
||||
case LLVMPIPE: {
|
||||
} else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
|
||||
if (driver instanceof Renderer.OpenGL oglDriver) {
|
||||
if (oglDriver == Renderer.OpenGL.LLVMPIPE) {
|
||||
env.put("__GLX_VENDOR_LIBRARY_NAME", "mesa");
|
||||
env.put("LIBGL_ALWAYS_SOFTWARE", "1");
|
||||
break;
|
||||
}
|
||||
case ZINK: {
|
||||
} else if (oglDriver == Renderer.OpenGL.ZINK) {
|
||||
env.put("__GLX_VENDOR_LIBRARY_NAME", "mesa");
|
||||
env.put("MESA_LOADER_DRIVER_OVERRIDE", "zink");
|
||||
/*
|
||||
@@ -665,21 +656,24 @@ public class DefaultLauncher extends Launcher {
|
||||
* Link: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10093
|
||||
*/
|
||||
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;
|
||||
} else if (driver instanceof Renderer.Vulkan vulkanDriver
|
||||
&& options.getJava().getArchitecture() == Architecture.SYSTEM_ARCH) {
|
||||
if (vulkanDriver.icdFile() != null) {
|
||||
String absolutePath = FileUtils.getAbsolutePath(vulkanDriver.icdFile());
|
||||
env.put("VK_ICD_FILENAMES", absolutePath);
|
||||
env.put("VK_DRIVER_FILES", absolutePath);
|
||||
}
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS
|
||||
&& options.getJava().getArchitecture() == Architecture.SYSTEM_ARCH) {
|
||||
if (driver instanceof Renderer.Vulkan vulkanDriver
|
||||
&& vulkanDriver != Renderer.Vulkan.MOLTENVK
|
||||
&& vulkanDriver.icdFile() != null) {
|
||||
String absolutePath = FileUtils.getAbsolutePath(vulkanDriver.icdFile());
|
||||
env.put("VK_ICD_FILENAMES", absolutePath);
|
||||
env.put("VK_DRIVER_FILES", absolutePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -585,7 +586,7 @@ public class CurseAddon implements RemoteMod.IMod {
|
||||
}
|
||||
return RemoteMod.Dependency.ofGeneral(RELATION_TYPE.get(dependency.getRelationType()), CurseForgeRemoteModRepository.MODS, Integer.toString(dependency.getModId()));
|
||||
}).distinct().filter(Objects::nonNull).collect(Collectors.toList()),
|
||||
gameVersions.stream().filter(ver -> ver.startsWith("1.") || ver.contains("w")).collect(Collectors.toList()),
|
||||
gameVersions.stream().filter(GameVersionNumber::isKnown).toList(),
|
||||
gameVersions.stream().flatMap(version -> {
|
||||
if ("fabric".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FABRIC);
|
||||
else if ("forge".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FORGE);
|
||||
|
||||
@@ -86,6 +86,10 @@ public enum Architecture {
|
||||
return this == X86 || this == X86_64;
|
||||
}
|
||||
|
||||
public boolean isArm() {
|
||||
return this == ARM32 || this == ARM64;
|
||||
}
|
||||
|
||||
public static final Architecture CURRENT_ARCH;
|
||||
public static final Architecture SYSTEM_ARCH;
|
||||
|
||||
|
||||
+38
-16
@@ -18,18 +18,22 @@
|
||||
package org.jackhuang.hmcl.util.platform.hardware;
|
||||
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
/// @author Glavo
|
||||
@NotNullByDefault
|
||||
public final class GraphicsCard {
|
||||
|
||||
public static String cleanName(String name) {
|
||||
@UnknownNullability
|
||||
public static String cleanName(@UnknownNullability String name) {
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
@@ -58,13 +62,15 @@ public final class GraphicsCard {
|
||||
private final @Nullable Type type;
|
||||
private final @Nullable String driver;
|
||||
private final @Nullable String driverVersion;
|
||||
private final @Unmodifiable List<Path> vulkanDriverFiles;
|
||||
|
||||
private GraphicsCard(String name, @Nullable HardwareVendor vendor, @Nullable Type type, @Nullable String driver, @Nullable String driverVersion) {
|
||||
private GraphicsCard(String name, @Nullable HardwareVendor vendor, @Nullable Type type, @Nullable String driver, @Nullable String driverVersion, List<Path> vulkanDriverFiles) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.vendor = vendor;
|
||||
this.type = type;
|
||||
this.driver = driver;
|
||||
this.driverVersion = driverVersion;
|
||||
this.vulkanDriverFiles = vulkanDriverFiles;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -79,6 +85,10 @@ public final class GraphicsCard {
|
||||
return driverVersion;
|
||||
}
|
||||
|
||||
public List<Path> getVulkanDriverFiles() {
|
||||
return vulkanDriverFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder(name);
|
||||
@@ -96,11 +106,12 @@ public final class GraphicsCard {
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private String name;
|
||||
private HardwareVendor vendor;
|
||||
private Type type;
|
||||
private String driver;
|
||||
private String driverVersion;
|
||||
private @Nullable String name;
|
||||
private @Nullable HardwareVendor vendor;
|
||||
private @Nullable Type type;
|
||||
private @Nullable String driver;
|
||||
private @Nullable String driverVersion;
|
||||
private @Nullable List<Path> vulkanDriverFiles;
|
||||
|
||||
public GraphicsCard build() {
|
||||
String name = this.name;
|
||||
@@ -111,10 +122,12 @@ public final class GraphicsCard {
|
||||
name = "Unknown";
|
||||
}
|
||||
|
||||
return new GraphicsCard(name, vendor, type, driver, driverVersion);
|
||||
List<Path> vulkanDriverFiles = Objects.requireNonNullElse(this.vulkanDriverFiles, List.of());
|
||||
|
||||
return new GraphicsCard(name, vendor, type, driver, driverVersion, List.copyOf(vulkanDriverFiles));
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
public @Nullable String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -123,7 +136,7 @@ public final class GraphicsCard {
|
||||
return this;
|
||||
}
|
||||
|
||||
public HardwareVendor getVendor() {
|
||||
public @Nullable HardwareVendor getVendor() {
|
||||
return vendor;
|
||||
}
|
||||
|
||||
@@ -132,7 +145,7 @@ public final class GraphicsCard {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
public @Nullable Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -141,7 +154,7 @@ public final class GraphicsCard {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDriver() {
|
||||
public @Nullable String getDriver() {
|
||||
return driver;
|
||||
}
|
||||
|
||||
@@ -150,7 +163,7 @@ public final class GraphicsCard {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDriverVersion() {
|
||||
public @Nullable String getDriverVersion() {
|
||||
return driverVersion;
|
||||
}
|
||||
|
||||
@@ -158,5 +171,14 @@ public final class GraphicsCard {
|
||||
this.driverVersion = driverVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @Nullable List<Path> getVulkanDriverFiles() {
|
||||
return vulkanDriverFiles;
|
||||
}
|
||||
|
||||
public Builder setVulkanDriverFiles(@Nullable List<Path> files) {
|
||||
this.vulkanDriverFiles = files;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.util.platform.macos;
|
||||
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/// @author Glavo
|
||||
@NotNullByDefault
|
||||
public final class HomebrewUtils {
|
||||
|
||||
/// The path to Homebrew prefix.
|
||||
///
|
||||
/// - For macOS x86_64, it is `/usr/local`;
|
||||
/// - For macOS ARM64, it is `/opt/homebrew`;
|
||||
/// - For other operating systems, it is undefined.
|
||||
public static final Path HOMEBREW_PREFIX;
|
||||
|
||||
/// The path to `libvulkan.1.dylib`.
|
||||
///
|
||||
/// For non-macOS operating systems, it is undefined.
|
||||
public static final Path LIB_VULKAN;
|
||||
|
||||
static {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
// TODO: custom Homebrew prefix for macOS
|
||||
HOMEBREW_PREFIX = Architecture.SYSTEM_ARCH.isX86()
|
||||
? Path.of("/usr/local")
|
||||
: Path.of("/opt/homebrew");
|
||||
|
||||
LIB_VULKAN = HOMEBREW_PREFIX.resolve("lib/libvulkan.1.dylib");
|
||||
} else {
|
||||
// For other operating systems, we don't need Homebrew.
|
||||
var placeholder = Path.of("");
|
||||
HOMEBREW_PREFIX = placeholder;
|
||||
LIB_VULKAN = placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
private HomebrewUtils() {
|
||||
}
|
||||
}
|
||||
+25
@@ -28,8 +28,11 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -48,6 +51,25 @@ final class WindowsGPUDetector {
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable List<Path> parseVulkanDriverFiles(Object vulkanDriverFiles) {
|
||||
if (vulkanDriverFiles instanceof String file) {
|
||||
try {
|
||||
return List.of(Path.of(file));
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.warning("Failed to parse Vulkan ICD file path: " + file, e);
|
||||
return null;
|
||||
}
|
||||
} else if (vulkanDriverFiles instanceof String[] files) {
|
||||
try {
|
||||
return Stream.of(files).map(Path::of).toList();
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.warning("Failed to parse Vulkan ICD file paths: " + Arrays.toString(files), e);
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<GraphicsCard> detectByCim() {
|
||||
try {
|
||||
String getCimInstance = OperatingSystem.SYSTEM_VERSION.getVersion().startsWith("6.1")
|
||||
@@ -116,6 +138,7 @@ final class WindowsGPUDetector {
|
||||
String vendor = regValueToString(reg.queryValue(hkey, subkey, "ProviderName"));
|
||||
String driverVersion = regValueToString(reg.queryValue(hkey, subkey, "DriverVersion"));
|
||||
String dacType = regValueToString(reg.queryValue(hkey, subkey, "HardwareInformation.DacType"));
|
||||
Object vulkanDriverName = reg.queryValue(hkey, subkey, "VulkanDriverName");
|
||||
|
||||
GraphicsCard.Builder builder = GraphicsCard.builder();
|
||||
if (name != null)
|
||||
@@ -126,6 +149,8 @@ final class WindowsGPUDetector {
|
||||
builder.setDriverVersion(driverVersion);
|
||||
if (dacType != null)
|
||||
builder.setType(fromDacType(dacType));
|
||||
if (vulkanDriverName != null)
|
||||
builder.setVulkanDriverFiles(parseVulkanDriverFiles(vulkanDriverName));
|
||||
result.add(builder.build());
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -95,6 +95,18 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
return VersionRange.atMost(asGameVersion(maximum));
|
||||
}
|
||||
|
||||
/// Determines whether the given version string corresponds to a known Minecraft version.
|
||||
///
|
||||
/// If the version string cannot be parsed as any known version type (release, snapshot, pre-release, etc.)
|
||||
/// and is not a known April Fools snapshot, it is considered an unknown version.
|
||||
///
|
||||
/// @param version the version string to check, e.g. `"1.21.4"`, `"25w14craftmine"`, etc.
|
||||
/// @return {@code true} if the version string corresponds to a known Minecraft version, {@code false} otherwise
|
||||
/// @see #asGameVersion(String)
|
||||
public static boolean isKnown(String version) {
|
||||
return !(asGameVersion(version) instanceof Special special && special.prev == null);
|
||||
}
|
||||
|
||||
final String value;
|
||||
final String normalized;
|
||||
|
||||
@@ -151,7 +163,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
///
|
||||
/// ```java
|
||||
/// GameVersionNumber.asVersion("...").isAtLeast("1.13", "17w43a");
|
||||
///```
|
||||
/// ```
|
||||
///
|
||||
/// @param strictReleaseVersion When `strictReleaseVersion` is `false`, `releaseVersion` is considered less than
|
||||
/// its corresponding pre/rc versions.
|
||||
|
||||
Reference in new issue
Block a user