diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 6191a3ecdc..40446100a3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -173,11 +173,10 @@ private void launch0() { } }), 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 @@ private void launch0() { 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) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java index 27f51ac250..389c5ca363 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java @@ -836,7 +836,7 @@ public JsonElement serialize(VersionSetting src, Type typeOfSrc, JsonSerializati 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 VersionSetting deserialize(JsonElement json, Type typeOfT, JsonDeserializ } 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 VersionSetting deserialize(JsonElement json, Type typeOfT, JsonDeserializ } catch (IllegalArgumentException ignored) { return Optional.empty(); } - }).orElseGet(() -> vs.getRenderer().getApi())); + }).orElseGet(() -> vs.getRenderer() instanceof Renderer.Driver renderer ? renderer.api() : GraphicsAPI.DEFAULT)); return vs; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AdvancedVersionSettingPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AdvancedVersionSettingPage.java index 0b2d2bbeb5..8c11c68ba2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AdvancedVersionSettingPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AdvancedVersionSettingPage.java @@ -219,22 +219,20 @@ else if (gameVersion.compareTo("26.2-snapshot-2") < 0) 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); - } } }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java index 8c72acd6ad..c10c6a01b5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java @@ -197,7 +197,7 @@ public static Version patchNative(DefaultGameRepository repository, 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; diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 02d918b457..6c12a0ffb3 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -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 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 2bcfec2041..69234dc765 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -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=伺服器位址 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index ba95ad274e..46827e9ae1 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -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=服务器地址 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Renderer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Renderer.java index a0b0c68db6..146747d646 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Renderer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Renderer.java @@ -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), - - LAVAPIPE(GraphicsAPI.VULKAN, "lavapipe", "lvp"), +public sealed interface Renderer permits Renderer.Default, Renderer.Driver, Renderer.Unknown { - // 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"), + /// Default renderer. + Default DEFAULT = new Default(); - LLVMPIPE(GraphicsAPI.OPENGL, "llvmpipe", null), - ZINK(GraphicsAPI.OPENGL, "zink", null), - D3D12(GraphicsAPI.OPENGL, "d3d12", null), - ; + /// Parse a renderer from a string. + static Renderer of(String name) { + if ("DEFAULT".equalsIgnoreCase(name) || name.isBlank()) + return DEFAULT; - /// Map the graphics API to supported renderers. - public static final Map> SUPPORTED; + String upper = name.toUpperCase(Locale.ROOT).trim(); - static { - var map = new EnumMap>(GraphicsAPI.class); + try { + return OpenGL.valueOf(upper); + } catch (IllegalArgumentException ignored) { + } - for (var api : GraphicsAPI.values()) { - map.put(api, Stream.of(values()).filter(it -> it.isSupported(api)).toList()); + try { + return Vulkan.valueOf(upper); + } catch (IllegalArgumentException ignored) { } - SUPPORTED = map; + return new Unknown(name); + } + + /// Get all supported renderers for a given graphics API. + static @Unmodifiable List getSupported(GraphicsAPI api) { + return switch (api) { + case DEFAULT -> List.of(DEFAULT); + case VULKAN -> Vulkan.Holder.SUPPORTED; + case OPENGL -> OpenGL.SUPPORTED; + }; } - private final GraphicsAPI api; + String name(); - private final @Nullable String loaderName; - private final @Nullable String icdName; + final class Default implements Renderer { + private Default() { + } - Renderer(GraphicsAPI api, @Nullable String loaderName, @Nullable String icdName) { - this.api = api; - this.loaderName = loaderName; - this.icdName = icdName; + @Override + public String name() { + return "DEFAULT"; + } } - /// Get the Graphics API used by this renderer. - public GraphicsAPI getApi() { - return api; - } + sealed interface Driver extends Renderer permits Vulkan, OpenGL { - public boolean isSupported(GraphicsAPI api) { - return this.api == api || this.api == GraphicsAPI.DEFAULT; + /// 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 LLVMpipe - The Mesa 3D Graphics Library + 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 cards) { + return platform.os() == OperatingSystem.WINDOWS; + } + + @Override + public String mesaDriverName() { + return "dzn"; + } + }, + + /// NVIDIA Vulkan driver. + /// + /// It is a Vulkan driver for NVIDIA GPUs. + /// + /// @see Vulkan Open Standard Modern GPU API | NVIDIA Developer + NVIDIA_VULKAN("nvidia") { + @Override + public boolean isSupported(Platform platform, @Nullable List cards) { + return Vulkan.hasCard(cards, HardwareVendor.NVIDIA); + } + }, + + /// Mesa NVK driver. + /// + /// It is a Vulkan driver for NVIDIA GPUs. + /// + /// @see NVK - The Mesa 3D Graphics Library + NVIDIA_NVK("nouveau") { + @Override + public boolean isSupported(Platform platform, @Nullable List 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 GPUOpen-Drivers/AMDVLK - GitHub + AMDVLK("amd") { + @Override + public boolean isSupported(Platform platform, @Nullable List 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 RADV - The Mesa 3D Graphics Library + AMD_RADV("radeon") { + @Override + public boolean isSupported(Platform platform, @Nullable List 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 cards) { + return platform.os() == OperatingSystem.WINDOWS && Vulkan.hasCard(cards, HardwareVendor.INTEL); + } + }, + + /// Mesa ANV driver. + /// + /// It is a Vulkan driver for Intel GPUs. + /// + /// @see ANV - The Mesa 3D Graphics Library + INTEL_ANV("intel") { + @Override + public boolean isSupported(Platform platform, @Nullable List 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 intel: split vulkan driver between gfx7/8 and above + INTEL_HASVK("intel_hasvk") { + @Override + public boolean isSupported(Platform platform, @Nullable List 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 cards) { + return platform.equals(Platform.WINDOWS_ARM64); + } + }, + + /// Mesa Turnip driver. + /// + /// It is a Vulkan driver for Qualcomm Adreno GPUs. + /// + /// @see Freedreno - The Mesa 3D Graphics Library + TURNIP("freedreno") { + @Override + public boolean isSupported(Platform platform, @Nullable List cards) { + return platform.os() != OperatingSystem.WINDOWS && platform.arch().isArm(); + } + }, + + /// MoltenVK driver. + /// + /// It is a Vulkan driver for macOS, iOS, tvOS, and visionOS. + /// + /// @see KhronosGroup/MoltenVK - GitHub + MOLTENVK("MoltenVK") { + @Override + public boolean isSupported(Platform platform, @Nullable List cards) { + return platform.os() == OperatingSystem.MACOS; + } + }, + + /// Mesa KosmicKrisp driver. + /// + /// It is a Vulkan driver for Apple Silicon hardware. + /// + /// @see KosmicKrisp - The Mesa 3D Graphics Library + KOSMICKRISP("kosmickrisp_mesa") { + @Override + public boolean isSupported(Platform platform, @Nullable List cards) { + return platform.os() == OperatingSystem.MACOS && platform.arch() == Architecture.ARM64; + } + }, + + /// Mesa PowerVR driver. + /// + /// It is a Vulkan driver for Imagination Technologies PowerVR GPUs. + /// + /// @see PowerVR - The Mesa 3D Graphics Library + POWERVR("powervr") { + @Override + public boolean isSupported(Platform platform, @Nullable List cards) { + return platform.os() == OperatingSystem.LINUX && Vulkan.hasCard(cards, HardwareVendor.IMG); + } + }, + + /// Mesa PanVK driver. + /// + /// It is a Vulkan driver for ARM Mali GPUs. + /// + /// @see Panfrost - The Mesa 3D Graphics Library + PANVK("panfrost") { + @Override + public boolean isSupported(Platform platform, @Nullable List 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 V3DV - The Mesa 3D Graphics Library + V3DV("broadcom") { + @Override + public boolean isSupported(Platform platform, @Nullable List cards) { + return platform.os() == OperatingSystem.LINUX && platform.arch().isArm() + && Vulkan.hasCard(cards, HardwareVendor.BROADCOM); + } + }; + + private static final class Holder { + static final List SUPPORTED; + static final Map DRIVER_TO_ICD_FILE; + + static { + var driverToIcdFile = new EnumMap(Vulkan.class); + var supported = new LinkedHashSet(); + + + // 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("(?[a-zA-Z0-9_-]+)_icd(?:\\." + Pattern.quote(archName) + ")?\\.json"); + Map 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 graphicsCards = SystemInfo.getGraphicsCards(); + if (graphicsCards != null) { + EnumSet 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 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 foundSupported = EnumSet.noneOf(Vulkan.class); + for (Path icdDir : icdDirs) { + if (!Files.isDirectory(icdDir)) + continue; + try (Stream 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 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 LLVMpipe - The Mesa 3D Graphics Library + LLVMPIPE { + @Override + public String mesaDriverName() { + return "llvmpipe"; + } + }, + ZINK { + @Override + public String mesaDriverName() { + return "zink"; + } + }, + D3D12 { + @Override + public boolean isSupported(Platform platform, @Nullable List cards) { + return platform.os() == OperatingSystem.WINDOWS; + } + + @Override + public String mesaDriverName() { + return "d3d12"; + } + }; + + static final List 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 cards) { + return true; + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 0ce41792bc..91103b786a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -27,6 +27,7 @@ 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 @@ private Command generateCommandLine(Path nativeFolder) throws IOException { } 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 classpath = repository.getClasspath(version); if (analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)) { @@ -602,28 +612,6 @@ public ManagedProcess launch() throws IOException, InterruptedException { return p; } - private @Nullable Path findVulkanDescriptorFile(List 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 getEnvVars(Path nativeFolder) { String versionName = Optional.ofNullable(options.getVersionName()).orElse(version.getId()); @@ -634,28 +622,31 @@ private Map getEnvVars(Path nativeFolder) { 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 @@ private Map getEnvVars(Path nativeFolder) { * 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); + } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Architecture.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Architecture.java index 7815131f39..a319227841 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Architecture.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Architecture.java @@ -86,6 +86,10 @@ public boolean isX86() { 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; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCard.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCard.java index d7323836c7..1eca335718 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCard.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCard.java @@ -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 static Builder builder() { private final @Nullable Type type; private final @Nullable String driver; private final @Nullable String driverVersion; + private final @Unmodifiable List 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 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 String getName() { return driverVersion; } + public List getVulkanDriverFiles() { + return vulkanDriverFiles; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(name); @@ -96,11 +106,12 @@ public enum Type { } 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 vulkanDriverFiles; public GraphicsCard build() { String name = this.name; @@ -111,10 +122,12 @@ public GraphicsCard build() { name = "Unknown"; } - return new GraphicsCard(name, vendor, type, driver, driverVersion); + List 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 Builder setName(String name) { return this; } - public HardwareVendor getVendor() { + public @Nullable HardwareVendor getVendor() { return vendor; } @@ -132,7 +145,7 @@ public Builder setVendor(HardwareVendor vendor) { return this; } - public Type getType() { + public @Nullable Type getType() { return type; } @@ -141,7 +154,7 @@ public Builder setType(Type type) { return this; } - public String getDriver() { + public @Nullable String getDriver() { return driver; } @@ -150,7 +163,7 @@ public Builder setDriver(String driver) { return this; } - public String getDriverVersion() { + public @Nullable String getDriverVersion() { return driverVersion; } @@ -158,5 +171,14 @@ public Builder setDriverVersion(String driverVersion) { this.driverVersion = driverVersion; return this; } + + public @Nullable List getVulkanDriverFiles() { + return vulkanDriverFiles; + } + + public Builder setVulkanDriverFiles(@Nullable List files) { + this.vulkanDriverFiles = files; + return this; + } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/macos/HomebrewUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/macos/HomebrewUtils.java new file mode 100644 index 0000000000..343668e6df --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/macos/HomebrewUtils.java @@ -0,0 +1,60 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +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() { + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsGPUDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsGPUDetector.java index b94c21873d..6a8c479990 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsGPUDetector.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsGPUDetector.java @@ -28,8 +28,11 @@ 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 @@ private static GraphicsCard.Type fromDacType(String adapterDACType) { } } + private static @Nullable List 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 detectByCim() { try { String getCimInstance = OperatingSystem.SYSTEM_VERSION.getVersion().startsWith("6.1") @@ -116,6 +138,7 @@ private static List detectByRegistry(WinReg reg) { 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 @@ private static List detectByRegistry(WinReg reg) { builder.setDriverVersion(driverVersion); if (dacType != null) builder.setType(fromDacType(dacType)); + if (vulkanDriverName != null) + builder.setVulkanDriverFiles(parseVulkanDriverFiles(vulkanDriverName)); result.add(builder.build()); } return result;