diff --git a/common/build.gradle.kts b/common/build.gradle.kts index cec4b9fdeb..c56c417140 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -69,9 +69,8 @@ dependencies { // We need to be careful during pre-launch that we don't touch any Minecraft classes, since other mods // will not yet have an opportunity to apply transformations. - configurationPreLaunch("org.apache.commons:commons-lang3:3.14.0") - configurationPreLaunch("commons-io:commons-io:2.15.1") configurationPreLaunch("org.lwjgl:lwjgl:3.3.3") + configurationPreLaunch("org.lwjgl:lwjgl-opengl:3.3.3") configurationPreLaunch("net.java.dev.jna:jna:5.14.0") configurationPreLaunch("net.java.dev.jna:jna-platform:5.14.0") configurationPreLaunch("org.slf4j:slf4j-api:2.0.9") diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/GlContextInfo.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/GlContextInfo.java deleted file mode 100644 index eef8034dbc..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/GlContextInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gl; - -import org.jetbrains.annotations.Nullable; -import org.lwjgl.opengl.GL11C; - -public record GlContextInfo(String vendor, String renderer, String version) { - @Nullable - public static GlContextInfo create() { - String vendor = GL11C.glGetString(GL11C.GL_VENDOR); - String renderer = GL11C.glGetString(GL11C.GL_RENDERER); - String version = GL11C.glGetString(GL11C.GL_VERSION); - - if (vendor == null || renderer == null || version == null) { - return null; - } - - return new GlContextInfo(vendor, renderer, version); - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java index fc8592bddf..78792f3971 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java @@ -22,9 +22,12 @@ public class WindowMixin implements NativeWindowHandle { @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwCreateWindow(IILjava/lang/CharSequence;JJ)J"), require = 0) public long setAdditionalWindowHints(int titleEncoded, int width, CharSequence height, long title, long monitor, Operation original) { - if (!PlatformRuntimeInformation.getInstance().platformHasEarlyLoadingScreen() && SodiumClientMod.options().performance.useNoErrorGLContext && - !Workarounds.isWorkaroundEnabled(Workarounds.Reference.NO_ERROR_CONTEXT_UNSUPPORTED)) { - GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_NO_ERROR, GLFW.GLFW_TRUE); + if (!PlatformRuntimeInformation.getInstance().platformHasEarlyLoadingScreen()) { + if (SodiumClientMod.options().performance.useNoErrorGLContext) { + if (!Workarounds.isWorkaroundEnabled(Workarounds.Reference.NO_ERROR_CONTEXT_UNSUPPORTED)) { + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_NO_ERROR, GLFW.GLFW_TRUE); + } + } } return original.call(titleEncoded, width, height, title, monitor); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java index 95c55b4488..0d7611e9a5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java @@ -6,11 +6,10 @@ import com.mojang.blaze3d.platform.ScreenManager; import com.mojang.blaze3d.platform.Window; import com.mojang.blaze3d.platform.WindowEventHandler; -import net.caffeinemc.mods.sodium.client.compatibility.checks.PostLaunchChecks; import net.caffeinemc.mods.sodium.client.compatibility.checks.ModuleScanner; -import net.caffeinemc.mods.sodium.client.gl.GlContextInfo; -import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; +import net.caffeinemc.mods.sodium.client.compatibility.checks.PostLaunchChecks; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; +import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import net.minecraft.Util; @@ -38,59 +37,46 @@ public class WindowMixin { @Final private static Logger LOGGER; - @Shadow - @Final - private long window; - @Unique private long wglPrevContext = MemoryUtil.NULL; @Redirect(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwCreateWindow(IILjava/lang/CharSequence;JJ)J"), expect = 0, require = 0) private long wrapGlfwCreateWindow(int width, int height, CharSequence title, long monitor, long share) { - final boolean applyNvidiaWorkarounds = Workarounds.isWorkaroundEnabled(Workarounds.Reference.NVIDIA_THREADED_OPTIMIZATIONS); - - if (applyNvidiaWorkarounds) { - NvidiaWorkarounds.install(); - } + NvidiaWorkarounds.applyEnvironmentChanges(); try { return GLFW.glfwCreateWindow(width, height, title, monitor, share); } finally { - if (applyNvidiaWorkarounds) { - NvidiaWorkarounds.uninstall(); - } + NvidiaWorkarounds.undoEnvironmentChanges(); } } @SuppressWarnings("all") @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Lnet/neoforged/fml/loading/ImmediateWindowHandler;setupMinecraftWindow(Ljava/util/function/IntSupplier;Ljava/util/function/IntSupplier;Ljava/util/function/Supplier;Ljava/util/function/LongSupplier;)J"), expect = 0, require = 0) private long wrapGlfwCreateWindowForge(final IntSupplier width, final IntSupplier height, final Supplier title, final LongSupplier monitor, Operation op) { - final boolean applyNvidiaWorkarounds = Workarounds.isWorkaroundEnabled(Workarounds.Reference.NVIDIA_THREADED_OPTIMIZATIONS); + boolean applyWorkaroundsLate = !PlatformRuntimeInformation.getInstance() + .platformHasEarlyLoadingScreen(); - if (applyNvidiaWorkarounds && !PlatformRuntimeInformation.getInstance().platformHasEarlyLoadingScreen()) { - NvidiaWorkarounds.install(); + if (applyWorkaroundsLate) { + NvidiaWorkarounds.applyEnvironmentChanges(); } try { return op.call(width, height, title, monitor); } finally { - if (applyNvidiaWorkarounds) { - NvidiaWorkarounds.uninstall(); + if (applyWorkaroundsLate) { + NvidiaWorkarounds.undoEnvironmentChanges(); } } } + @Inject(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL;createCapabilities()Lorg/lwjgl/opengl/GLCapabilities;", shift = At.Shift.AFTER)) private void postContextReady(WindowEventHandler eventHandler, ScreenManager monitorTracker, DisplayData settings, String videoMode, String title, CallbackInfo ci) { GlContextInfo driver = GlContextInfo.create(); - - if (driver == null) { - LOGGER.warn("Could not retrieve identifying strings for OpenGL implementation"); - } else { - LOGGER.info("OpenGL Vendor: {}", driver.vendor()); - LOGGER.info("OpenGL Renderer: {}", driver.renderer()); - LOGGER.info("OpenGL Version: {}", driver.version()); - } + LOGGER.info("OpenGL Vendor: {}", driver.vendor()); + LOGGER.info("OpenGL Renderer: {}", driver.renderer()); + LOGGER.info("OpenGL Version: {}", driver.version()); // Capture the current WGL context so that we can detect it being replaced later. if (Util.getPlatform() == Util.OS.WINDOWS) { @@ -99,6 +85,7 @@ private void postContextReady(WindowEventHandler eventHandler, ScreenManager mon this.wglPrevContext = MemoryUtil.NULL; } + NvidiaWorkarounds.applyContextChanges(driver); PostLaunchChecks.onContextInitialized(); ModuleScanner.checkModules((NativeWindowHandle) this); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java index cd41ca489c..170f329ff4 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java @@ -1,19 +1,13 @@ package net.caffeinemc.mods.sodium.client.compatibility.checks; -import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; -import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; -import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterVendor; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.intel.IntelWorkarounds; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaDriverVersion; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; import net.caffeinemc.mods.sodium.client.platform.MessageBox; -import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; -import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; -import org.jetbrains.annotations.Nullable; import org.lwjgl.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; - /** * Performs OpenGL driver validation before the game creates an OpenGL context. This runs during the earliest possible * opportunity at game startup, and uses a custom hardware prober to search for problematic drivers. @@ -21,19 +15,23 @@ public class PreLaunchChecks { private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-EarlyDriverScanner"); - // This string should be determined at compile time, so it can be checked against the runtime version. - private static final String REQUIRED_LWJGL_VERSION = Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION; + // These version constants are inlined at compile time. + private static final String REQUIRED_LWJGL_VERSION = + Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION; - private static final String normalMessage = "You must change the LWJGL version in your launcher to continue. This is usually controlled by the settings for a profile or instance in your launcher."; + private static final String normalMessage = "You must change the LWJGL version in your launcher to continue. " + + "This is usually controlled by the settings for a profile or instance in your launcher."; - private static final String prismMessage = "It appears you are using Prism Launcher to start the game. You can likely fix this problem by opening your instance settings and navigating to the Version section in the sidebar."; + private static final String prismMessage = "It appears you are using Prism Launcher to start the game. You can " + + "likely fix this problem by opening your instance settings and navigating to the Version section in the " + + "sidebar."; public static void beforeLWJGLInit() { if (BugChecks.ISSUE_2561) { - if (!Version.getVersion().startsWith(REQUIRED_LWJGL_VERSION)) { + if (!isUsingKnownCompatibleLwjglVersion()) { String message = normalMessage; - if (System.getProperty("minecraft.launcher.brand", "unknown").equalsIgnoreCase("PrismLauncher")) { + if (isUsingPrismLauncher()) { message = prismMessage; } @@ -46,19 +44,20 @@ public static void beforeLWJGLInit() { Required version: ###REQUIRED_VERSION### """ + message) - .replace("###CURRENT_VERSION###", org.lwjgl.Version.getVersion()) + .replace("###CURRENT_VERSION###", Version.getVersion()) .replace("###REQUIRED_VERSION###", REQUIRED_LWJGL_VERSION), "https://github.com/CaffeineMC/sodium/wiki/LWJGL-Compatibility"); - } } } public static void onGameInit() { if (BugChecks.ISSUE_899) { - var installedVersion = findIntelDriverMatchingBug899(); + var installedVersion = IntelWorkarounds.findIntelDriverMatchingBug899(); if (installedVersion != null) { + var installedVersionString = installedVersion.toString(); + showCriticalErrorAndClose("Sodium Renderer - Unsupported Driver", """ The game failed to start because the currently installed Intel Graphics Driver is not \ @@ -68,15 +67,18 @@ public static void onGameInit() { Required version: 10.18.10.5161 (or newer) You must update your graphics card driver in order to continue.""" - .replace("###CURRENT_DRIVER###", installedVersion.toString()), + .replace("###CURRENT_DRIVER###", installedVersionString), "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#windows-intel-gen7"); } } if (BugChecks.ISSUE_1486) { - var installedVersion = findNvidiaDriverMatchingBug1486(); + var installedVersion = NvidiaWorkarounds.findNvidiaDriverMatchingBug1486(); if (installedVersion != null) { + var installedVersionString = NvidiaDriverVersion.parse(installedVersion) + .toString(); + showCriticalErrorAndClose("Sodium Renderer - Unsupported Driver", """ The game failed to start because the currently installed NVIDIA Graphics Driver is not \ @@ -86,7 +88,7 @@ public static void onGameInit() { Required version: 536.23 (or newer) You must update your graphics card driver in order to continue.""" - .replace("###CURRENT_DRIVER###", NvidiaDriverVersion.parse(installedVersion).toString()), + .replace("###CURRENT_DRIVER###", installedVersionString), "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#nvidia-gpus"); } @@ -107,67 +109,13 @@ private static void showCriticalErrorAndClose(String title, String message, Stri System.exit(1 /* failure code */); } - // https://github.com/CaffeineMC/sodium/issues/899 - private static @Nullable WindowsFileVersion findIntelDriverMatchingBug899() { - if (OsUtils.getOs() != OsUtils.OperatingSystem.WIN) { - return null; - } - - for (var adapter : GraphicsAdapterProbe.getAdapters()) { - if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { - @Nullable var driverName = wddmAdapterInfo.getOpenGlIcdName(); - - if (driverName == null) { - continue; - } - - var driverVersion = wddmAdapterInfo.openglIcdVersion(); - - // Intel OpenGL ICD for Generation 7 GPUs - if (driverName.matches("ig7icd(32|64)")) { - // https://www.intel.com/content/www/us/en/support/articles/000005654/graphics.html - // Anything which matches the 15.33 driver scheme (WDDM x.y.10.w) should be checked - // Drivers before build 5161 are assumed to have bugs with synchronization primitives - if (driverVersion.z() == 10 && driverVersion.w() < 5161) { - return driverVersion; - } - } - } - } - - return null; + private static boolean isUsingKnownCompatibleLwjglVersion() { + return Version.getVersion() + .startsWith(REQUIRED_LWJGL_VERSION); } - - // https://github.com/CaffeineMC/sodium/issues/1486 - // The way which NVIDIA tries to detect the Minecraft process could not be circumvented until fairly recently - // So we require that an up-to-date graphics driver is installed so that our workarounds can disable the Threaded - // Optimizations driver hack. - private static @Nullable WindowsFileVersion findNvidiaDriverMatchingBug1486() { - // The Linux driver has two separate branches which have overlapping version numbers, despite also having - // different feature sets. As a result, we can't reliably determine which Linux drivers are broken... - if (OsUtils.getOs() != OsUtils.OperatingSystem.WIN) { - return null; - } - - for (var adapter : GraphicsAdapterProbe.getAdapters()) { - if (adapter.vendor() != GraphicsAdapterVendor.NVIDIA) { - continue; - } - - if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { - var driverVersion = wddmAdapterInfo.openglIcdVersion(); - - if (driverVersion.z() == 15) { // Only match 5XX.XX drivers - // Broken in x.y.15.2647 (526.47) - // Fixed in x.y.15.3623 (536.23) - if (driverVersion.w() >= 2647 && driverVersion.w() < 3623) { - return driverVersion; - } - } - } - } - - return null; + private static boolean isUsingPrismLauncher() { + return System.getProperty("minecraft.launcher.brand", "unknown") + .equalsIgnoreCase("PrismLauncher"); } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java new file mode 100644 index 0000000000..7e8f484385 --- /dev/null +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java @@ -0,0 +1,19 @@ +package net.caffeinemc.mods.sodium.client.compatibility.environment; + +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL11C; + +import java.util.Objects; + +public record GlContextInfo(String vendor, String renderer, String version) { + public static GlContextInfo create() { + String vendor = Objects.requireNonNull(GL11C.glGetString(GL11C.GL_VENDOR), + "GL_VENDOR is NULL"); + String renderer = Objects.requireNonNull(GL11C.glGetString(GL11C.GL_RENDERER), + "GL_RENDERER is NULL"); + String version = Objects.requireNonNull(GL11C.glGetString(GL11C.GL_VERSION), + "GL_VERSION is NULL"); + + return new GlContextInfo(vendor, renderer, version); + } +} diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java index 4f39eb9ce0..c232c0faa9 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java @@ -1,21 +1,32 @@ package net.caffeinemc.mods.sodium.client.compatibility.environment; -import org.apache.commons.lang3.SystemUtils; +import java.util.Locale; public class OsUtils { + private static final OperatingSystem OS = determineOs(); - public static OperatingSystem getOs() { - if (SystemUtils.IS_OS_WINDOWS) { - return OperatingSystem.WIN; - } else if (SystemUtils.IS_OS_MAC) { - return OperatingSystem.MAC; - } else if (SystemUtils.IS_OS_LINUX) { - return OperatingSystem.LINUX; + public static OperatingSystem determineOs() { + var name = System.getProperty("os.name"); + + if (name != null) { + var normalized = name.toLowerCase(Locale.ROOT); + + if (normalized.startsWith("windows")) { + return OperatingSystem.WIN; + } else if (normalized.startsWith("mac")) { + return OperatingSystem.MAC; + } else if (normalized.startsWith("linux")) { + return OperatingSystem.LINUX; + } } return OperatingSystem.UNKNOWN; } + public static OperatingSystem getOs() { + return OS; + } + public enum OperatingSystem { WIN, MAC, diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java index 0026148242..661f817ac9 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java @@ -2,12 +2,32 @@ import org.jetbrains.annotations.NotNull; +import java.util.regex.Pattern; + public enum GraphicsAdapterVendor { NVIDIA, AMD, INTEL, UNKNOWN; + // Intel Gen 4, 5, 6 - ig4icd + // Intel Gen 7 - ig7icd + // Intel Gen 7.5 - ig75icd + // Intel Gen 8 - ig8icd + // Intel Gen 9, 9.5 - ig9icd + // Intel Gen 11 - ig11icd + // Intel Gen 12 - ig12icd (UHD Graphics, with early drivers) + // igxelpicd (Xe-LP; integrated) + // igxehpicd (Xe-HP; dedicated) + private static final Pattern INTEL_ICD_PATTERN = + Pattern.compile("ig(4|7|75|8|9|11|12|xelp|xehp)icd(32|64)\\.dll", Pattern.CASE_INSENSITIVE); + + private static final Pattern NVIDIA_ICD_PATTERN = + Pattern.compile("nvoglv(32|64)\\.dll", Pattern.CASE_INSENSITIVE); + + private static final Pattern AMD_ICD_PATTERN = + Pattern.compile("(atiglpxx|atig6pxx)\\.dll", Pattern.CASE_INSENSITIVE); + @NotNull static GraphicsAdapterVendor fromPciVendorId(String vendor) { if (vendor.contains("0x1002")) { @@ -22,27 +42,19 @@ static GraphicsAdapterVendor fromPciVendorId(String vendor) { } public static GraphicsAdapterVendor fromIcdName(String name) { - // Intel Gen 4, 5, 6 - ig4icd - // Intel Gen 7 - ig7icd - // Intel Gen 7.5 - ig75icd - // Intel Gen 8 - ig8icd - // Intel Gen 9, 9.5 - ig9icd - // Intel Gen 11 - ig11icd - // Intel Gen 12 - ig12icd (UHD Graphics, with early drivers) - // igxelpicd (Xe-LP; integrated) - // igxehpicd (Xe-HP; dedicated) - if (name.matches("ig(4|7|75|8|9|11|12|xelp|xehp)icd(32|64)")) { + if (matchesPattern(INTEL_ICD_PATTERN, name)) { return INTEL; - } - - if (name.matches("nvoglv(32|64)")) { + } else if (matchesPattern(NVIDIA_ICD_PATTERN, name)) { return NVIDIA; - } - - if (name.matches("atiglpxx|atig6pxx")) { + } else if (matchesPattern(AMD_ICD_PATTERN, name)) { return AMD; + } else { + return UNKNOWN; } + } - return UNKNOWN; + private static boolean matchesPattern(Pattern pattern, String name) { + return pattern.matcher(name) + .matches(); } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java index 1e6afc0cad..98e286d03d 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java @@ -1,15 +1,17 @@ package net.caffeinemc.mods.sodium.client.compatibility.workarounds; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; -import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterInfo; import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; -import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterVendor; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -37,10 +39,8 @@ private static Set findNecessaryWorkarounds() { var workarounds = EnumSet.noneOf(Reference.class); var operatingSystem = OsUtils.getOs(); - var graphicsAdapters = GraphicsAdapterProbe.getAdapters(); - - if (isUsingNvidiaGraphicsCard(operatingSystem, graphicsAdapters)) { - workarounds.add(Reference.NVIDIA_THREADED_OPTIMIZATIONS); + if (NvidiaWorkarounds.isUsingNvidiaGraphicsCard()) { + workarounds.add(Reference.NVIDIA_THREADED_OPTIMIZATIONS_BROKEN); } if (isUsingIntelGen8OrOlder()) { @@ -84,12 +84,6 @@ private static boolean isUsingIntelGen8OrOlder() { return false; } - private static boolean isUsingNvidiaGraphicsCard(OsUtils.OperatingSystem operatingSystem, Collection adapters) { - - return (operatingSystem == OsUtils.OperatingSystem.WIN || operatingSystem == OsUtils.OperatingSystem.LINUX) && - adapters.stream().anyMatch(adapter -> adapter.vendor() == GraphicsAdapterVendor.NVIDIA); - } - public static boolean isWorkaroundEnabled(Reference id) { return ACTIVE_WORKAROUNDS.get() .contains(id); @@ -101,7 +95,7 @@ public enum Reference { * performance issues and crashes. * GitHub Issue */ - NVIDIA_THREADED_OPTIMIZATIONS, + NVIDIA_THREADED_OPTIMIZATIONS_BROKEN, /** * Requesting a No Error Context causes a crash at startup when using a Wayland session. diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java new file mode 100644 index 0000000000..97ab3a1aae --- /dev/null +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java @@ -0,0 +1,40 @@ +package net.caffeinemc.mods.sodium.client.compatibility.workarounds.intel; + +import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; +import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; +import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; +import org.jetbrains.annotations.Nullable; + +public class IntelWorkarounds { + // https://github.com/CaffeineMC/sodium/issues/899 + public static @Nullable WindowsFileVersion findIntelDriverMatchingBug899() { + if (OsUtils.getOs() != OsUtils.OperatingSystem.WIN) { + return null; + } + + for (var adapter : GraphicsAdapterProbe.getAdapters()) { + if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { + @Nullable var driverName = wddmAdapterInfo.getOpenGlIcdName(); + + if (driverName == null) { + continue; + } + + var driverVersion = wddmAdapterInfo.openglIcdVersion(); + + // Intel OpenGL ICD for Generation 7 GPUs + if (driverName.matches("ig7icd(32|64)")) { + // https://www.intel.com/content/www/us/en/support/articles/000005654/graphics.html + // Anything which matches the 15.33 driver scheme (WDDM x.y.10.w) should be checked + // Drivers before build 5161 are assumed to have bugs with synchronization primitives + if (driverVersion.z() == 10 && driverVersion.w() < 5161) { + return driverVersion; + } + } + } + } + + return null; + } +} diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java index 5f54ec5174..49bc336a0c 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java @@ -1,49 +1,154 @@ package net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia; +import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils.OperatingSystem; +import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; +import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterVendor; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; import net.caffeinemc.mods.sodium.client.platform.unix.Libc; -import net.caffeinemc.mods.sodium.client.platform.windows.api.Kernel32; import net.caffeinemc.mods.sodium.client.platform.windows.WindowsCommandLine; +import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; +import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL32C; +import org.lwjgl.opengl.KHRDebug; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Locale; + public class NvidiaWorkarounds { private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-NvidiaWorkarounds"); - public static void install() { - LOGGER.warn("Applying workaround: Prevent the NVIDIA OpenGL driver from using broken optimizations (NVIDIA_THREADED_OPTIMIZATIONS)"); + public static boolean isUsingNvidiaGraphicsCard() { + return GraphicsAdapterProbe.getAdapters() + .stream() + .anyMatch(adapter -> adapter.vendor() == GraphicsAdapterVendor.NVIDIA); + } - try { - switch (OsUtils.getOs()) { - case WIN -> { - // The NVIDIA drivers rely on parsing the command line arguments to detect Minecraft. If we destroy those, - // then it shouldn't be able to detect us anymore. - WindowsCommandLine.setCommandLine("net.caffeinemc.sodium"); - - // Ensures that Minecraft will run on the dedicated GPU, since the drivers can no longer detect it - Kernel32.setEnvironmentVariable("SHIM_MCCOMPAT", "0x800000001"); - } - case LINUX -> { - // Unlike Windows, we don't need to hide ourselves from the driver. We can just request that - // it not use threaded optimizations instead. - Libc.setEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", "0"); + // https://github.com/CaffeineMC/sodium/issues/1486 + // The way which NVIDIA tries to detect the Minecraft process could not be circumvented until fairly recently + // So we require that an up-to-date graphics driver is installed so that our workarounds can disable the Threaded + // Optimizations driver hack. + public static @Nullable WindowsFileVersion findNvidiaDriverMatchingBug1486() { + // The Linux driver has two separate branches which have overlapping version numbers, despite also having + // different feature sets. As a result, we can't reliably determine which Linux drivers are broken... + if (OsUtils.getOs() != OperatingSystem.WIN) { + return null; + } + + for (var adapter : GraphicsAdapterProbe.getAdapters()) { + if (adapter.vendor() != GraphicsAdapterVendor.NVIDIA) { + continue; + } + + if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { + var driverVersion = wddmAdapterInfo.openglIcdVersion(); + + if (driverVersion.z() == 15) { // Only match 5XX.XX drivers + // Broken in x.y.15.2647 (526.47) + // Fixed in x.y.15.3623 (536.23) + if (driverVersion.w() >= 2647 && driverVersion.w() < 3623) { + return driverVersion; + } } } + } + + return null; + } + + public static void applyEnvironmentChanges() { + // We can't know if the OpenGL context will actually be initialized using the NVIDIA ICD, but we need to + // modify the process environment *now* otherwise the driver will initialize with bad settings. For non-NVIDIA + // drivers, these workarounds are not likely to cause issues. + if (!isUsingNvidiaGraphicsCard()) { + return; + } + + LOGGER.warn("Modifying process environment to apply workarounds for the NVIDIA graphics driver..."); + + try { + if (OsUtils.getOs() == OperatingSystem.WIN) { + applyEnvironmentChanges$Windows(); + } else if (OsUtils.getOs() == OperatingSystem.LINUX) { + applyEnvironmentChanges$Linux(); + } } catch (Throwable t) { - LOGGER.error("Failure while applying workarounds", t); + LOGGER.error("Failed to modify the process environment", t); + logWarning(); + } + } + - LOGGER.error("READ ME! The workarounds for the NVIDIA Graphics Driver did not apply correctly!"); - LOGGER.error("READ ME! You are very likely going to run into unexplained crashes and severe performance issues!"); - LOGGER.error("READ ME! Please see this issue for more information: https://github.com/CaffeineMC/sodium/issues/1816"); + private static void applyEnvironmentChanges$Windows() { + // The NVIDIA drivers rely on parsing the command line arguments to detect Minecraft. We need to + // make sure that it detects the game so that *some* important optimizations are applied. Later, + // we will try to enable GL_DEBUG_OUTPUT_SYNCHRONOUS so that "Threaded Optimizations" cannot + // be enabled. + WindowsCommandLine.setCommandLine("net.caffeinemc.sodium / net.minecraft.client.main.Main /"); + } + + private static void applyEnvironmentChanges$Linux() { + // Unlike Windows, we can just request that it not use threaded optimizations instead. + Libc.setEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", "0"); + } + + public static void undoEnvironmentChanges() { + if (OsUtils.getOs() == OperatingSystem.WIN) { + undoEnvironmentChanges$Windows(); } } - public static void uninstall() { - switch (OsUtils.getOs()) { - case WIN -> { - WindowsCommandLine.resetCommandLine(); + private static void undoEnvironmentChanges$Windows() { + WindowsCommandLine.resetCommandLine(); + } + + public static void applyContextChanges(GlContextInfo driver) { + var normalizedVendorName = driver.vendor() + .toLowerCase(Locale.ROOT); + + // The context may not have been initialized with the NVIDIA ICD, even if we think there is an NVIDIA + // graphics adapter in use. Because enabling these workarounds have the potential to severely hurt performance + // on other drivers, make sure we exit now. + if (!normalizedVendorName.startsWith("nvidia")) { + return; + } + + LOGGER.warn("Modifying OpenGL context to apply workarounds for the NVIDIA graphics driver..."); + + if (Workarounds.isWorkaroundEnabled(Workarounds.Reference.NVIDIA_THREADED_OPTIMIZATIONS_BROKEN)) { + if (OsUtils.getOs() == OperatingSystem.WIN) { + applyContextChanges$Windows(); } - case LINUX -> { } } } + + private static void applyContextChanges$Windows() { + // On Windows, the NVIDIA drivers do not have any environment variable to control whether + // "Threaded Optimizations" are enabled. But we can enable the "GL_DEBUG_OUTPUT_SYNCHRONOUS" option to + // achieve the same effect. + var capabilities = GL.getCapabilities(); + + if (capabilities.GL_KHR_debug) { + LOGGER.warn("Using GL_DEBUG_OUTPUT_SYNCHRONOUS to force the NVIDIA driver to disable threaded" + + "command submission"); + GL32C.glEnable(KHRDebug.GL_DEBUG_OUTPUT_SYNCHRONOUS); + } else { + LOGGER.error("GL_KHR_debug does not appear to be supported on this system, unable to " + + "disable threaded command submission for NVIDIA graphics driver! You are likely to " + + "experience severe performance issues and frequent crashes."); + logWarning(); + } + } + + private static void logWarning() { + LOGGER.error("READ ME! The workarounds for the NVIDIA Graphics Driver did not apply correctly!"); + LOGGER.error("READ ME! You are very likely going to run into unexplained crashes and severe performance issues."); + LOGGER.error("READ ME! More information about what went wrong can be found above this message."); + LOGGER.error("READ ME! Please help us understand why this problem occurred by opening a bug report on our issue tracker:"); + LOGGER.error("READ ME! https://github.com/CaffeineMC/sodium/issues"); + } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java index 3341963a38..680c071807 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java @@ -1,6 +1,5 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api; -import org.apache.commons.lang3.Validate; import org.lwjgl.system.JNI; import org.lwjgl.system.SharedLibrary; @@ -21,19 +20,24 @@ public static boolean isD3DKMTSupported() { // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/nf-d3dkmthk-d3dkmtqueryadapterinfo public static int /* NTSTATUS */ nd3dKmtQueryAdapterInfo(long ptr /* D3DKMT_QUERYADAPTERINFO */) { - Validate.isTrue(PFN_D3DKMTQueryAdapterInfo != NULL); - return JNI.callPI(ptr, PFN_D3DKMTQueryAdapterInfo); + return JNI.callPI(ptr, checkPfn(PFN_D3DKMTQueryAdapterInfo)); } // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/nf-d3dkmthk-d3dkmtenumadapters2 public static int /* NTSTATUS */ nD3DKMTEnumAdapters(long ptr /* D3DKMT_ENUMADAPTERS */) { - Validate.isTrue(PFN_D3DKMTEnumAdapters != NULL); - return JNI.callPI(ptr, PFN_D3DKMTEnumAdapters); + return JNI.callPI(ptr, checkPfn(PFN_D3DKMTEnumAdapters)); } // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/nf-d3dkmthk-d3dkmtcloseadapter public static int /* NTSTATUS */ nD3DKMTCloseAdapter(long ptr /* D3DKMT_CLOSEADAPTER */) { - Validate.isTrue(PFN_D3DKMTCloseAdapter != NULL); - return JNI.callPI(ptr, PFN_D3DKMTCloseAdapter); + return JNI.callPI(ptr, checkPfn(PFN_D3DKMTCloseAdapter)); + } + + private static long checkPfn(long pfn) { + if (pfn == NULL) { + throw new NullPointerException("Function pointer not available"); + } + + return pfn; } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java index 102fe183cf..6881b10ea7 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java @@ -5,7 +5,6 @@ import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.Gdi32; import net.caffeinemc.mods.sodium.client.platform.windows.api.version.Version; -import org.apache.commons.io.FilenameUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryStack; @@ -13,7 +12,7 @@ import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; -import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -201,8 +200,18 @@ public String toString() { } } - // Returns (null) if input is (null). - private static String getOpenGlIcdName(String path) { - return FilenameUtils.removeExtension(FilenameUtils.getName(path)); + private static String getOpenGlIcdName(@Nullable String filePath) { + if (filePath == null) { + return null; + } + + var fileName = Paths.get(filePath) + .getFileName(); + + if (fileName == null) { + return null; + } + + return fileName.toString(); } } diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/NeoForgeRuntimeInformation.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/NeoForgeRuntimeInformation.java index 39a43b2cdc..3e043843c7 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/NeoForgeRuntimeInformation.java +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/NeoForgeRuntimeInformation.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.neoforge; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; -import net.neoforged.fml.loading.FMLConfig; import net.neoforged.fml.loading.FMLLoader; import net.neoforged.fml.loading.FMLPaths; import net.neoforged.fml.loading.LoadingModList; diff --git a/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java b/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java index 1d6876b8e2..8dab82a896 100644 --- a/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java +++ b/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java @@ -18,11 +18,8 @@ public void bootstrap(String[] arguments) { GraphicsAdapterProbe.findAdapters(); PreLaunchChecks.onGameInit(); Workarounds.init(); - final boolean applyNvidiaWorkarounds = Workarounds.isWorkaroundEnabled(Workarounds.Reference.NVIDIA_THREADED_OPTIMIZATIONS); - if (applyNvidiaWorkarounds) { - System.out.println("[Sodium] Applying NVIDIA workarounds earlier on Forge."); - NvidiaWorkarounds.install(); - } + // Context creation happens earlier on NeoForge, so we need to apply this now + NvidiaWorkarounds.applyEnvironmentChanges(); } }