diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/DisplayListManager.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/DisplayListManager.java index 40ffd6200..256610d19 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/DisplayListManager.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/DisplayListManager.java @@ -877,9 +877,14 @@ public class DisplayListManager { // Empty display list - per OpenGL spec, still valid after glNewList/glEndList compiled = CompiledDisplayList.EMPTY; } - final CompiledDisplayList previous = displayListCache.put(glListId, compiled); - if (previous != null && previous != CompiledDisplayList.EMPTY && previous != compiled) { - previous.delete(); + final boolean locked = GLStateManager.acquireDrawLock(); + try { + final CompiledDisplayList previous = displayListCache.put(glListId, compiled); + if (previous != null && previous != CompiledDisplayList.EMPTY && previous != compiled) { + previous.delete(); + } + } finally { + if (locked) GLStateManager.releaseDrawLock(); } // Log compilation details if enabled (before context restoration changes glListId) @@ -1012,33 +1017,43 @@ public class DisplayListManager { } private static void executeDisplayList(int list) { - final CompiledDisplayList compiled = displayListCache.get(list); - if (compiled != null) { - compiled.render(list); - return; - } - - // Fall back to VBOManager or legacy display list - if (list < 0) { - // Negative IDs are VBOManager space (entity models, etc.) - final VertexBuffer vbo = VBOManager.get(list); - if (vbo != null) { - vbo.render(); + final boolean locked = GLStateManager.acquireDrawLock(); + try { + final CompiledDisplayList compiled = displayListCache.get(list); + if (compiled != null) { + compiled.render(list); + return; } - // Per OpenGL spec: if list is undefined, glCallList has no effect + + // Fall back to VBOManager or legacy display list + if (list < 0) { + // Negative IDs are VBOManager space (entity models, etc.) + final VertexBuffer vbo = VBOManager.get(list); + if (vbo != null) { + vbo.render(); + } + // Per OpenGL spec: if list is undefined, glCallList has no effect + } + // An uncached positive-ID list = no-op (per OpenGL spec) + } finally { + if (locked) GLStateManager.releaseDrawLock(); } - // An uncached positive-ID list = no-op (per OpenGL spec) } /** * Delete display lists and free their VBO resources. */ public static void glDeleteLists(int list, int range) { - for (int i = list; i < list + range; i++) { - CompiledDisplayList compiled = displayListCache.remove(i); - if (compiled != null) { - compiled.delete(); + final boolean locked = GLStateManager.acquireDrawLock(); + try { + for (int i = list; i < list + range; i++) { + CompiledDisplayList compiled = displayListCache.remove(i); + if (compiled != null) { + compiled.delete(); + } } + } finally { + if (locked) GLStateManager.releaseDrawLock(); } } diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java index 5c77ccbf0..0900753f5 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java @@ -97,6 +97,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.IntSupplier; import static com.gtnewhorizons.angelica.glsm.Vendor.AMD; @@ -252,6 +253,18 @@ public class GLStateManager { // Reference to DrawableGL (main display context) - works with both FML and BLS splash @Setter private static Drawable drawableGL = null; + private static final ReentrantLock DRAW_LOCK = new ReentrantLock(); + + public static boolean acquireDrawLock() { + if (splashComplete) return false; + DRAW_LOCK.lock(); + return true; + } + + public static void releaseDrawLock() { + DRAW_LOCK.unlock(); + } + public static boolean isCachingEnabled() { if (splashComplete) return true; return Thread.currentThread() == drawableGLHolder; @@ -2244,15 +2257,25 @@ public class GLStateManager { } public static void preDraw(int drawMode) { - prepareWideLineEmulation(drawMode); - ShaderManager.getInstance().preDraw(); - prepareClientArrays(); + final boolean locked = acquireDrawLock(); + try { + prepareWideLineEmulation(drawMode); + ShaderManager.getInstance().preDraw(); + prepareClientArrays(); + } finally { + if (locked) releaseDrawLock(); + } } public static void preDraw() { - disableWideLineEmulation(); - ShaderManager.getInstance().preDraw(); - prepareClientArrays(); + final boolean locked = acquireDrawLock(); + try { + disableWideLineEmulation(); + ShaderManager.getInstance().preDraw(); + prepareClientArrays(); + } finally { + if (locked) releaseDrawLock(); + } } public static void glDrawElements(int mode, ByteBuffer indices) { @@ -3246,6 +3269,7 @@ public class GLStateManager { splashComplete = true; drawableGLHolder = null; drawableGL = null; + ImmediateModeRecorder.cleanupOrphanTessellators(); } public static void glNewList(int list, int mode) { diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/QuadConverter.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/QuadConverter.java index 336c8e307..79fc36b8b 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/QuadConverter.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/QuadConverter.java @@ -91,21 +91,26 @@ public final class QuadConverter { } private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) { - GLStateManager.preDraw(); - assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4"; - assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4"; - final int quadCount = vertexCount / 4; - final int prevEbo = GLStateManager.getBoundEBO(); - ensureCapacity(first / 4 + quadCount); - RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId); - // Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int - final long indexOffset = (long) (first / 4) * 6 * 4; - if (instanced) { - RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount); - } else { - RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset); + final boolean locked = GLStateManager.acquireDrawLock(); + try { + GLStateManager.preDraw(); + assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4"; + assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4"; + final int quadCount = vertexCount / 4; + final int prevEbo = GLStateManager.getBoundEBO(); + ensureCapacity(first / 4 + quadCount); + RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId); + // Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int + final long indexOffset = (long) (first / 4) * 6 * 4; + if (instanced) { + RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount); + } else { + RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset); + } + RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo); + } finally { + if (locked) GLStateManager.releaseDrawLock(); } - RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo); } /** @@ -123,32 +128,37 @@ public final class QuadConverter { } private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) { - GLStateManager.preDraw(); - final int needed = triIndexCount * bytesPerIndex; - final int prevEbo = GLStateManager.getBoundEBO(); + final boolean locked = GLStateManager.acquireDrawLock(); + try { + GLStateManager.preDraw(); + final int needed = triIndexCount * bytesPerIndex; + final int prevEbo = GLStateManager.getBoundEBO(); - if (scratchEboId == 0) { - scratchEboId = RENDER_BACKEND.genBuffers(); + if (scratchEboId == 0) { + scratchEboId = RENDER_BACKEND.genBuffers(); + } + + RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId); + + if (needed > scratchEboCapacity) { + // Power-of-2 growth -- allocate full capacity, upload actual data + int newCap = Math.max(4096, scratchEboCapacity); + while (newCap < needed) newCap *= 2; + RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW); + scratchEboCapacity = newCap; + } + RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst); + + if (instanced) { + RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount); + } else { + RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L); + } + + RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo); + } finally { + if (locked) GLStateManager.releaseDrawLock(); } - - RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId); - - if (needed > scratchEboCapacity) { - // Power-of-2 growth — allocate full capacity, upload actual data - int newCap = Math.max(4096, scratchEboCapacity); - while (newCap < needed) newCap *= 2; - RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW); - scratchEboCapacity = newCap; - } - RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst); - - if (instanced) { - RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount); - } else { - RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L); - } - - RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo); memFree(dst); } diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/ShaderManager.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/ShaderManager.java index 1eabde974..1c609edb7 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/ShaderManager.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/ShaderManager.java @@ -6,8 +6,10 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler; import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks; import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig; +import com.gtnewhorizons.angelica.glsm.QuadConverter; import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack; import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack; +import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer; import lombok.Getter; import org.joml.Vector3f; import org.joml.Vector4f; @@ -66,11 +68,33 @@ public final class ShaderManager { } public void enable() { + warmUp(); enabled = true; GLStateManager.LOGGER.info("FFP shader emulation enabled"); } + // Force loading of classes before the SplashThread kicks off + // Ensure it's GL free + private static void warmUp() { + try { + final long[] fkScratch = new long[FragmentKey.MAX_UNITS]; + final int fkLen = FragmentKey.packFromState(fkScratch); + final int fragMask = FragmentKey.unitMaskFromPacked(fkScratch, fkLen); + final long vkPacked = VertexKey.packFromState(true, true, true, true, fragMask); + final VertexKey vk = VertexKey.fromPacked(vkPacked); + VertexShaderGenerator.generate(vk); + FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen)); + GeometryShaderGenerator.generate(vk); + final Class[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class }; + for (Class c : touched) { + c.getName(); + } + } catch (Throwable t) { + GLStateManager.LOGGER.warn("FFP warmup failed; draw-path classes will resolve lazily", t); + } + } + public void disable() { enabled = false; } diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/recording/ImmediateModeRecorder.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/recording/ImmediateModeRecorder.java index 4b8cbca88..270ed2cf2 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/recording/ImmediateModeRecorder.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/recording/ImmediateModeRecorder.java @@ -10,6 +10,8 @@ import org.lwjgl.opengl.GL11; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; /** * Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}. @@ -18,28 +20,37 @@ import java.util.Arrays; public final class ImmediateModeRecorder { private static final CallbackTessellator tessellator = new CallbackTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE); - /** Separate tessellator for the splash thread. Nulled after splash completes. */ - private static DirectTessellator splashTessellator; + private record ThreadTessellator(Thread thread, DirectTessellator tessellator) {} + + private static final Queue threadTessellators = new ConcurrentLinkedQueue<>(); + private static final ThreadLocal threadTessellator = ThreadLocal.withInitial(() -> { + final DirectTessellator t = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE); + threadTessellators.add(new ThreadTessellator(Thread.currentThread(), t)); + cleanupOrphanTessellators(); + return t; + }); private ImmediateModeRecorder() { } - public static void initSplashTessellator() { - splashTessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE); + public static int getThreadTessellatorCount() { + return threadTessellators.size(); } - public static void destroySplashTessellator() { - splashTessellator.delete(); - splashTessellator = null; + public static synchronized void cleanupOrphanTessellators() { + threadTessellators.removeIf(tt -> { + if (tt.thread().isAlive()) return false; + tt.tessellator().delete(); + return true; + }); } private static DirectTessellator tessellator() { - final DirectTessellator splash = splashTessellator; - if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) { - return splash; + if (Thread.currentThread() == GLStateManager.getMainThread()) { + return tessellator; } - return tessellator; + return threadTessellator.get(); } public static DirectTessellator getInternalTessellator() { diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/streaming/TessellatorStreamingDrawer.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/streaming/TessellatorStreamingDrawer.java index 315ed142a..2f69ab7d9 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/streaming/TessellatorStreamingDrawer.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/streaming/TessellatorStreamingDrawer.java @@ -88,13 +88,18 @@ public class TessellatorStreamingDrawer { final int vertexSize = format.getVertexSize(); final int requiredBytes = vertexCount * vertexSize; - ensureRepackCapacity(requiredBytes); + final boolean locked = GLStateManager.acquireDrawLock(); + try { + ensureRepackCapacity(requiredBytes); - final long writePtr = format.writeToBuffer0(repackAddress, tess.rawBuffer, tess.rawBufferIndex); - repackBuffer.position(0); - repackBuffer.limit((int)(writePtr - repackAddress)); + final long writePtr = format.writeToBuffer0(repackAddress, tess.rawBuffer, tess.rawBufferIndex); + repackBuffer.position(0); + repackBuffer.limit((int)(writePtr - repackAddress)); - uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount); + uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount); + } finally { + if (locked) GLStateManager.releaseDrawLock(); + } // Shrink rawBuffer if oversized if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize << 3)) { @@ -187,24 +192,29 @@ public class TessellatorStreamingDrawer { * Tries the persistent ring buffer first, falls back to orphan buffer on overflow. */ private static void uploadAndDraw(ByteBuffer packed, int flags, VertexFormat format, int vertexSize, int drawMode, int vertexCount) { - ensureVAO(flags, format); + final boolean locked = GLStateManager.acquireDrawLock(); + try { + ensureVAO(flags, format); - int firstVertex = -1; + int firstVertex = -1; - if (persistentBuffer != null) { - firstVertex = persistentBuffer.upload(packed, vertexSize); + if (persistentBuffer != null) { + firstVertex = persistentBuffer.upload(packed, vertexSize); + } + + if (firstVertex >= 0) { + GLStateManager.glBindVertexArray(persistentVAOs[flags]); + } else { + GLStateManager.glBindVertexArray(orphanVAOs[flags]); + orphanBuffers[flags].upload(packed); + firstVertex = 0; + } + + drawWithQuadConversion(drawMode, firstVertex, vertexCount); + GLStateManager.glBindVertexArray(0); + } finally { + if (locked) GLStateManager.releaseDrawLock(); } - - if (firstVertex >= 0) { - GLStateManager.glBindVertexArray(persistentVAOs[flags]); - } else { - GLStateManager.glBindVertexArray(orphanVAOs[flags]); - orphanBuffers[flags].upload(packed); - firstVertex = 0; - } - - drawWithQuadConversion(drawMode, firstVertex, vertexCount); - GLStateManager.glBindVertexArray(0); } private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) { diff --git a/glsm/src/test/java/com/gtnewhorizons/angelica/glsm/ImmediateModeThreadIsolationTest.java b/glsm/src/test/java/com/gtnewhorizons/angelica/glsm/ImmediateModeThreadIsolationTest.java new file mode 100644 index 000000000..ed7433d85 --- /dev/null +++ b/glsm/src/test/java/com/gtnewhorizons/angelica/glsm/ImmediateModeThreadIsolationTest.java @@ -0,0 +1,125 @@ +package com.gtnewhorizons.angelica.glsm; + +import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator; +import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList; +import com.gtnewhorizons.angelica.glsm.recording.GLCommand; +import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.lwjgl.opengl.GL11; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(GLSMCoreExtension.class) +class ImmediateModeThreadIsolationTest { + + @Test + void backgroundThreadsGetDistinctTessellators() throws Exception { + final DirectTessellator mainTess = ImmediateModeRecorder.getInternalTessellator(); + final AtomicReference bg1 = new AtomicReference<>(); + final AtomicReference bg2 = new AtomicReference<>(); + + final Thread t1 = new Thread(() -> bg1.set(ImmediateModeRecorder.getInternalTessellator()), "iso-test-1"); + final Thread t2 = new Thread(() -> bg2.set(ImmediateModeRecorder.getInternalTessellator()), "iso-test-2"); + t1.start(); t2.start(); + t1.join(); t2.join(); + + assertNotNull(bg1.get()); + assertNotNull(bg2.get()); + assertNotSame(mainTess, bg1.get(), "background thread must not share the main-thread tessellator"); + assertNotSame(mainTess, bg2.get(), "background thread must not share the main-thread tessellator"); + assertNotSame(bg1.get(), bg2.get(), "background threads must not share a tessellator"); + } + + @Test + void concurrentImmediateModeDoesNotCorruptRecording() throws Exception { + final List failures = new CopyOnWriteArrayList<>(); + final CountDownLatch start = new CountDownLatch(1); + final AtomicBoolean stop = new AtomicBoolean(false); + + final Runnable spam = () -> { + try { + start.await(); + while (!stop.get()) { + GLStateManager.glBegin(GL11.GL_QUADS); + GLStateManager.glVertex3f(0, 0, 0); + GLStateManager.glVertex3f(1, 0, 0); + GLStateManager.glVertex3f(1, 1, 0); + GLStateManager.glVertex3f(0, 1, 0); + GLStateManager.glEnd(); + } + } catch (Throwable t) { + failures.add(t); + } + }; + final Thread t1 = new Thread(spam, "iso-spam-1"); + final Thread t2 = new Thread(spam, "iso-spam-2"); + t1.start(); t2.start(); + start.countDown(); + + final List lists = new ArrayList<>(); + try { + for (int i = 0; i < 50; i++) { + final int list = GLStateManager.glGenLists(1); + GLStateManager.glNewList(list, GL11.GL_COMPILE); + GLStateManager.glBegin(GL11.GL_QUADS); + GLStateManager.glVertex3f(0, 0, 0); + GLStateManager.glVertex3f(1, 0, 0); + GLStateManager.glVertex3f(1, 1, 0); + GLStateManager.glVertex3f(0, 1, 0); + GLStateManager.glEnd(); + GLStateManager.glEndList(); + lists.add(list); + } + } finally { + stop.set(true); + t1.join(5000); t2.join(5000); + } + + assertTrue(failures.isEmpty(), "background immediate mode threw: " + failures); + for (int list : lists) { + final CompiledDisplayList compiled = DisplayListManager.getDisplayList(list); + assertNotNull(compiled, "list " + list + " should be compiled"); + assertEquals(1, compiled.getCommandCounts().getOrDefault(GLCommand.DRAW_RANGE, 0), "list " + list + " should contain exactly the main thread's draw"); + GLStateManager.glDeleteLists(list, 1); + } + } + + @Test + void sweepFreesDeadThreadTessellators() throws Exception { + final Thread t = new Thread(() -> ImmediateModeRecorder.getInternalTessellator(), "iso-sweep"); + t.start(); + t.join(); + final int before = ImmediateModeRecorder.getThreadTessellatorCount(); + assertTrue(before >= 1); + ImmediateModeRecorder.cleanupOrphanTessellators(); + assertTrue(ImmediateModeRecorder.getThreadTessellatorCount() < before, "dead thread's tessellator should be swept"); + } + + @Test + void drawLockGatesOnSplashComplete() throws Exception { + assertFalse(GLStateManager.acquireDrawLock(), "post-splash acquire must be a no-op"); + + final Field f = GLStateManager.class.getDeclaredField("splashComplete"); + f.setAccessible(true); + f.set(null, false); + try { + assertTrue(GLStateManager.acquireDrawLock(), "during splash the lock must engage"); + GLStateManager.releaseDrawLock(); + } finally { + f.set(null, true); + } + } +} diff --git a/src/main/java/com/gtnewhorizons/angelica/client/font/BatchingFontRenderer.java b/src/main/java/com/gtnewhorizons/angelica/client/font/BatchingFontRenderer.java index a328d2a05..db54b933f 100644 --- a/src/main/java/com/gtnewhorizons/angelica/client/font/BatchingFontRenderer.java +++ b/src/main/java/com/gtnewhorizons/angelica/client/font/BatchingFontRenderer.java @@ -327,6 +327,15 @@ public class BatchingFontRenderer { private int fontAAStrengthLast = -1; private void flushBatch() { + final boolean locked = GLStateManager.acquireDrawLock(); + try { + flushBatchInner(); + } finally { + if (locked) GLStateManager.releaseDrawLock(); + } + } + + private void flushBatchInner() { if (vertexDataPos == 0) { clearBatch(); return; diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/glsm/MixinSplashProgressCaching.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/glsm/MixinSplashProgressCaching.java index 0236d4851..8101722e4 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/glsm/MixinSplashProgressCaching.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/glsm/MixinSplashProgressCaching.java @@ -1,7 +1,6 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.glsm; import com.gtnewhorizons.angelica.glsm.GLStateManager; -import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.spongepowered.asm.mixin.Mixin; @@ -27,14 +26,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public class MixinSplashProgressCaching { private static final Logger LOGGER = LogManager.getLogger("Angelica"); - /** - * Before splash starts, create a separate DirectTessellator for the splash thread. - */ - @Inject(method = "start", at = @At("HEAD")) - private static void angelica$initSplashTessellator(CallbackInfo ci) { - ImmediateModeRecorder.initSplashTessellator(); - } - // VAOs aren't shared across GL contexts; the SharedDrawable the client thread just swapped // onto has none. Core profile (macOS) rejects glValidateProgram without one. @Inject(method = "start", at = @At("RETURN")) @@ -42,12 +33,8 @@ public class MixinSplashProgressCaching { GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays()); } - /** - * On return from finish() - destroy splash tessellator and mark splash complete - */ @Inject(method = "finish", at = @At("RETURN")) private static void angelica$enableCachingOnFinish(CallbackInfo ci) { - ImmediateModeRecorder.destroySplashTessellator(); GLStateManager.markSplashComplete(); LOGGER.info("Splash Complete"); }