Skip to content

Commit

Permalink
Use alternative workaround for NVIDIA drivers
Browse files Browse the repository at this point in the history
The NVIDIA driver enables a driver feature called
"Threaded Optimizations" when it finds Minecraft,
which causes severe performance issues and sometimes
even crashes.

Newer versions of the driver seem to use a slightly
different heuristic which our workaround doesn't
address.

So, instead, use an alternative method that enables
GL_DEBUG_OUTPUT_SYNCHRONOUS. This seems to reliably
disable the functionality *even if* the user has
configured it otherwise in their driver settings.

Additionally, on Windows, we now always indicate to
the driver that Minecraft is running, so that users
with hybrid graphics don't see regressed performance.
  • Loading branch information
jellysquid3 committed Nov 19, 2024
1 parent 990cf64 commit cb9d728
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 215 deletions.
3 changes: 1 addition & 2 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ public class WindowMixin implements NativeWindowHandle {

@WrapOperation(method = "<init>", 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<Long> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = "<init>", 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 = "<init>", 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<String> title, final LongSupplier monitor, Operation<Long> 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 = "<init>", 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) {
Expand All @@ -99,6 +85,7 @@ private void postContextReady(WindowEventHandler eventHandler, ScreenManager mon
this.wglPrevContext = MemoryUtil.NULL;
}

NvidiaWorkarounds.applyContextChanges(driver);
PostLaunchChecks.onContextInitialized();
ModuleScanner.checkModules((NativeWindowHandle) this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,37 @@
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.
*/
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;
}

Expand All @@ -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 \
Expand All @@ -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 \
Expand All @@ -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");

}
Expand All @@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Loading

1 comment on commit cb9d728

@Niterux
Copy link
Contributor

@Niterux Niterux commented on cb9d728 Nov 21, 2024

Choose a reason for hiding this comment

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

It's really sad that NVIDIA is trying their hardest to break Sodium's workarounds :/ hopefully, they cut it out eventually
Does this GL_DEBUG_OUTPUT_SYNCHRONOUS have any negative performance implications?
What does NVIDIA even have to gain by repeatedly targeting Sodium's workarounds for this and making it impossible for Sodium to disable this behavior in any official manner? Do they WANT their drivers to be associated with instability and bad performance?
Sorry if this comment brings a harmful tone, I am genuinely curious 👍 ❤

Please sign in to comment.