diff --git a/src/main/java/me/jellysquid/mods/sodium/client/compatibility/workarounds/amd/AmdWorkarounds.java b/src/main/java/me/jellysquid/mods/sodium/client/compatibility/workarounds/amd/AmdWorkarounds.java new file mode 100644 index 000000000..2e6fb79d8 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/sodium/client/compatibility/workarounds/amd/AmdWorkarounds.java @@ -0,0 +1,55 @@ +package me.jellysquid.mods.sodium.client.compatibility.workarounds.amd; + +import me.jellysquid.mods.sodium.client.platform.windows.WindowsCommandLine; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import net.minecraft.Util; + +public class AmdWorkarounds { + private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-AmdWorkarounds"); + + public static void undoEnvironmentChanges() { + if (Util.getPlatform() == Util.OS.WINDOWS) { + undoEnvironmentChanges$Windows(); + } + } + + private static void undoEnvironmentChanges$Windows() { + WindowsCommandLine.resetCommandLine(); + } + + public static void applyEnvironmentChanges() { + // We can't know if the OpenGL context will actually be initialized using the AMD ICD, but we need to + // modify the process environment *now* otherwise the driver will initialize with bad settings. For non-AMD + // drivers, these workarounds are not likely to cause issues. + LOGGER.info("Modifying process environment to apply workarounds for the AMD graphics driver..."); + + try { + if (Util.getPlatform() == Util.OS.WINDOWS) { + applyEnvironmentChanges$Windows(); + } + } catch (Throwable t) { + LOGGER.error("Failed to modify the process environment", t); + logWarning(); + } + } + + + private static void applyEnvironmentChanges$Windows() { + // The new AMD drivers rely on parsing the command line arguments to detect Minecraft. + // When they do they apply an optimization that is broken with sodium present + // This stops AMD drivers from detecting the game + WindowsCommandLine.setCommandLine("net.caffeinemc.sodium / net.minecraft.client.main.Main /"); + } + + private static void logWarning() { + LOGGER.error("READ ME!"); + LOGGER.error("READ ME! The workarounds for the AMD Graphics Driver did not apply correctly!"); + LOGGER.error("READ ME! You may run into unexplained graphical issues."); + LOGGER.error("READ ME! More information about what went wrong can be found above this message."); + LOGGER.error("READ ME!"); + 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"); + LOGGER.error("READ ME!"); + } +} diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gl/device/GLRenderDevice.java b/src/main/java/me/jellysquid/mods/sodium/client/gl/device/GLRenderDevice.java index 66a043393..3f48c6e8a 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/gl/device/GLRenderDevice.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/gl/device/GLRenderDevice.java @@ -269,13 +269,16 @@ public ImmediateDrawCommandList() { @Override public void multiDrawElementsBaseVertex(MultiDrawBatch batch, GlIndexType indexType) { GlPrimitiveType primitiveType = GLRenderDevice.this.activeTessellation.getPrimitiveType(); + int mode = primitiveType.getId(); + int type = indexType.getFormatId(); - GL32C.nglMultiDrawElementsBaseVertex(primitiveType.getId(), - batch.pElementCount, - indexType.getFormatId(), - batch.pElementPointer, - batch.size(), - batch.pBaseVertex); + for (int i = 0; i < batch.size(); i++) { + int count = org.lwjgl.system.MemoryUtil.memGetInt(batch.pElementCount + ((long) i * Integer.BYTES)); + long indices = org.lwjgl.system.MemoryUtil.memGetAddress(batch.pElementPointer + ((long) i * org.lwjgl.system.Pointer.POINTER_SIZE)); + int baseVertex = org.lwjgl.system.MemoryUtil.memGetInt(batch.pBaseVertex + ((long) i * Integer.BYTES)); + + GL32C.nglDrawElementsBaseVertex(mode, count, type, indices, baseVertex); + } } @Override diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java index c2b1675f7..f4bd30df5 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java @@ -47,17 +47,17 @@ public static class PerformanceSettings { public boolean animateOnlyVisibleTextures = true; public boolean useEntityCulling = true; public boolean useFogOcclusion = true; - public boolean useBlockFaceCulling = true; - public boolean useCompactVertexFormat = true; + public boolean useBlockFaceCulling = false; + public boolean useCompactVertexFormat = false; @SerializedName("use_translucent_face_sorting_v2") public boolean useTranslucentFaceSorting = true; public boolean useRenderPassOptimization = true; - public boolean useNoErrorGLContext = true; + public boolean useNoErrorGLContext = false; } public static class AdvancedSettings { public boolean enableMemoryTracing = false; - public boolean useAdvancedStagingBuffers = true; + public boolean useAdvancedStagingBuffers = false; public boolean disableIncompatibleModWarnings = false; public int cpuRenderAheadLimit = 3; diff --git a/src/main/java/me/jellysquid/mods/sodium/client/platform/windows/WindowsCommandLine.java b/src/main/java/me/jellysquid/mods/sodium/client/platform/windows/WindowsCommandLine.java index 9d223b2e1..8ad19dd5a 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/platform/windows/WindowsCommandLine.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/platform/windows/WindowsCommandLine.java @@ -2,6 +2,7 @@ import me.jellysquid.mods.sodium.client.platform.windows.api.Kernel32; import org.lwjgl.system.MemoryUtil; + import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.Objects; @@ -17,11 +18,15 @@ public static void setCommandLine(String modifiedCmdline) { // Pointer into the command-line arguments stored within the Windows process structure // We do not own this memory, and it should not be freed. var pCmdline = Kernel32.getCommandLine(); + var pCmdlineA = Kernel32.getCommandLineA(); // The original command-line the process was started with. var cmdline = MemoryUtil.memUTF16(pCmdline); var cmdlineLen = MemoryUtil.memLengthUTF16(cmdline, true); + var cmdlineA = MemoryUtil.memASCII(pCmdlineA); + var cmdLineLenA = MemoryUtil.memLengthASCII(cmdlineA, true); + if (MemoryUtil.memLengthUTF16(modifiedCmdline, true) > cmdlineLen) { // We can never write a string which is larger than what we were given, as there // may not be enough space remaining. Realistically, this should never happen, since @@ -30,12 +35,18 @@ public static void setCommandLine(String modifiedCmdline) { throw new BufferOverflowException(); } + if (MemoryUtil.memLengthASCII(modifiedCmdline, true) > cmdLineLenA) { + throw new BufferOverflowException(); + } + ByteBuffer buffer = MemoryUtil.memByteBuffer(pCmdline, cmdlineLen); + ByteBuffer bufferA = MemoryUtil.memByteBuffer(pCmdlineA, cmdLineLenA); // Write the new command line arguments into the process structure. // The Windows API documentation explicitly says this is forbidden, but it *does* give us a pointer // directly into the PEB structure, so... MemoryUtil.memUTF16(modifiedCmdline, true, buffer); + MemoryUtil.memASCII(modifiedCmdline, true, bufferA); // Make sure we can actually see our changes in the process structure // We don't know if this could ever actually happen, but since we're doing something pretty hacky @@ -43,8 +54,11 @@ public static void setCommandLine(String modifiedCmdline) { if (!Objects.equals(modifiedCmdline, MemoryUtil.memUTF16(pCmdline))) { throw new RuntimeException("Sanity check failed, the command line arguments did not appear to change"); } + if (!Objects.equals(modifiedCmdline, MemoryUtil.memASCII(pCmdlineA))) { + throw new RuntimeException("Sanity check failed, the command line arguments did not appear to change"); + } - ACTIVE_COMMAND_LINE_HOOK = new CommandLineHook(cmdline, buffer); + ACTIVE_COMMAND_LINE_HOOK = new CommandLineHook(cmdline, cmdlineA, buffer, bufferA); } public static void resetCommandLine() { @@ -56,13 +70,17 @@ public static void resetCommandLine() { private static class CommandLineHook { private final String cmdline; + private final String cmdlineA; private final ByteBuffer cmdlineBuf; + private final ByteBuffer cmdlineBufA; private boolean active = true; - private CommandLineHook(String cmdline, ByteBuffer cmdlineBuf) { + private CommandLineHook(String cmdline, String cmdlineA, ByteBuffer cmdlineBuf, ByteBuffer cmdlineBufA) { this.cmdline = cmdline; + this.cmdlineA = cmdlineA; this.cmdlineBuf = cmdlineBuf; + this.cmdlineBufA = cmdlineBufA; } public void uninstall() { @@ -73,6 +91,7 @@ public void uninstall() { // Restore the original value of the command line arguments // Must be null-terminated (as it was given to us) MemoryUtil.memUTF16(this.cmdline, true, this.cmdlineBuf); + MemoryUtil.memASCII(this.cmdlineA, true, this.cmdlineBufA); this.active = false; } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/platform/windows/api/Kernel32.java b/src/main/java/me/jellysquid/mods/sodium/client/platform/windows/api/Kernel32.java index d85c4852b..f31a8c2cc 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/platform/windows/api/Kernel32.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/platform/windows/api/Kernel32.java @@ -16,6 +16,7 @@ public class Kernel32 { private static final int GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 1 << 2; private static final long PFN_GetCommandLineW; + private static final long PFN_GetCommandLineA; private static final long PFN_SetEnvironmentVariableW; private static final long PFN_GetModuleHandleExW; @@ -26,6 +27,7 @@ public class Kernel32 { static { PFN_GetCommandLineW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetCommandLineW"); + PFN_GetCommandLineA = APIUtil.apiGetFunctionAddress(LIBRARY, "GetCommandLineA"); PFN_SetEnvironmentVariableW = APIUtil.apiGetFunctionAddress(LIBRARY, "SetEnvironmentVariableW"); PFN_GetModuleHandleExW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetModuleHandleExW"); PFN_GetLastError = APIUtil.apiGetFunctionAddress(LIBRARY, "GetLastError"); @@ -52,6 +54,10 @@ public static long getCommandLine() { return JNI.callP(PFN_GetCommandLineW); } + public static long getCommandLineA() { + return JNI.callP(PFN_GetCommandLineA); + } + public static long getModuleHandleByNames(String[] names) { for (String name : names) { var handle = getModuleHandleByName(name); diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java index 4c0036a23..c79851935 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java @@ -45,12 +45,19 @@ private int getNextSize(int primitiveCount) { private void grow(CommandList commandList, int primitiveCount) { var bufferSize = primitiveCount * this.indexType.getBytesPerElement() * ELEMENTS_PER_PRIMITIVE; - commandList.allocateStorage(this.buffer, bufferSize, GlBufferUsage.STATIC_DRAW); - - var mapped = commandList.mapBuffer(this.buffer, 0, bufferSize, EnumBitField.of(GlBufferMapFlags.INVALIDATE_BUFFER, GlBufferMapFlags.WRITE, GlBufferMapFlags.UNSYNCHRONIZED)); - this.indexType.createIndexBuffer(mapped.getMemoryBuffer(), primitiveCount); - - commandList.unmap(mapped); + // [AMD RDNA WORKAROUND]: Use safe RAM allocations and bulk copy (glBufferData) + // instead of mapping (glMapBufferRange). AMD drivers frequently fail mapped + // synchronizations for index buffers, causing severe geometry corruption. + java.nio.ByteBuffer tempBuffer = org.lwjgl.system.MemoryUtil.memAlloc(bufferSize); + try { + this.indexType.createIndexBuffer(tempBuffer, primitiveCount); + // TempBuffer is already at position 0 with limit=bufferSize. + // The writer array modifies it locally with absolute indices, + // so we don't need to flip() here. + commandList.uploadData(this.buffer, tempBuffer, GlBufferUsage.STATIC_DRAW); + } finally { + org.lwjgl.system.MemoryUtil.memFree(tempBuffer); + } this.maxPrimitives = primitiveCount; } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/region/RenderRegionManager.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/region/RenderRegionManager.java index 4ab96cb73..2a6e80558 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/region/RenderRegionManager.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/region/RenderRegionManager.java @@ -207,10 +207,8 @@ private record PendingResortUpload(RenderSection section, BuiltSectionMeshParts private static StagingBuffer createStagingBuffer(CommandList commandList) { - if (SodiumClientMod.options().advanced.useAdvancedStagingBuffers && MappedStagingBuffer.isSupported(RenderDevice.INSTANCE)) { - return new MappedStagingBuffer(commandList); - } - + // [AMD RDNA WORKAROUND]: Force FallbackStagingBuffer to prevent glMapBufferRange crashes on Windows Drivers. + // This ensures compatibility with Adrenalin drivers even if the JSON option is left enabled. return new FallbackStagingBuffer(commandList); } } diff --git a/src/main/java/me/jellysquid/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java b/src/main/java/me/jellysquid/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java index b5f0d2a27..fde13f344 100644 --- a/src/main/java/me/jellysquid/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java +++ b/src/main/java/me/jellysquid/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java @@ -5,6 +5,7 @@ import me.jellysquid.mods.sodium.client.compatibility.checks.LateDriverScanner; import me.jellysquid.mods.sodium.client.compatibility.workarounds.Workarounds; import me.jellysquid.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; +import me.jellysquid.mods.sodium.client.compatibility.workarounds.amd.AmdWorkarounds; import net.minecraft.Util; import net.minecraftforge.fml.loading.FMLConfig; import net.minecraftforge.fml.loading.ImmediateWindowHandler; @@ -51,6 +52,8 @@ private long wrapGlfwCreateWindow(IntSupplier width, IntSupplier height, Supplie if (applyNvidiaWorkarounds) { NvidiaWorkarounds.install(); } + + AmdWorkarounds.applyEnvironmentChanges(); /** * @author Asek3 @@ -73,6 +76,7 @@ private long wrapGlfwCreateWindow(IntSupplier width, IntSupplier height, Supplie if (applyNvidiaWorkarounds) { NvidiaWorkarounds.uninstall(); } + AmdWorkarounds.undoEnvironmentChanges(); } }