Give each GL drawable its own GLSM context (#2142)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-09-26 22:31:07 -07:00
1 parent 7c28a69bfe
commit a8c29fa3f9
67 files changed
+1817 -1009

No files matched your search

@@ -117,6 +117,7 @@ public class CompatUniformManager {
private static final class ProgramUniforms {
final int[] locs;
GLContextState owner;
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
ProgramUniforms(int[] locs) { this.locs = locs; }
@@ -161,7 +162,13 @@ public class CompatUniformManager {
}
if (hasAny) {
programUniforms.put(program, new ProgramUniforms(locs));
final ProgramUniforms uniforms = new ProgramUniforms(locs);
final boolean locked = GLStateManager.acquireDrawLock();
try {
programUniforms.put(program, uniforms);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
@@ -171,16 +178,21 @@ public class CompatUniformManager {
*
* @return Whether the program needs emulation (FFP or Iris Program)
*/
public static boolean refreshCompatUniforms(int program) {
public static boolean refreshCompatUniforms(int program, GLContextState glCtx) {
final ProgramUniforms pu = programUniforms.get(program);
if (pu == null) return false;
final int[] locs = pu.locs;
if (pu.owner != glCtx) {
pu.owner = glCtx;
pu.lastMvGen = pu.lastMvLinearGen = pu.lastProjGen = pu.lastTexMatGen = -1;
pu.lastFragmentGen = pu.lastLightingGen = pu.lastClipPlaneGen = -1;
}
// Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.getMvGeneration();
final int mvLinearGen = GLStateManager.getMvLinearGeneration();
final int projGen = GLStateManager.getProjGeneration();
final int texMatGen = GLStateManager.getTexMatrixGeneration();
final int mvGen = glCtx.mvGeneration;
final int mvLinearGen = glCtx.mvLinearGeneration;
final int projGen = glCtx.projGeneration;
final int texMatGen = glCtx.texMatrixGeneration;
final boolean mvChanged = mvGen != pu.lastMvGen;
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
final boolean projChanged = projGen != pu.lastProjGen;
@@ -194,14 +206,14 @@ public class CompatUniformManager {
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.getFragmentGeneration();
final int fragGen = glCtx.fragmentGeneration;
if (fragGen != pu.lastFragmentGen) {
pu.lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.getLightingGeneration();
final int litGen = glCtx.lightingGeneration;
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
@@ -211,7 +223,7 @@ public class CompatUniformManager {
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.getClipPlaneGeneration();
final int cpGen = glCtx.clipPlaneGeneration;
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
@@ -438,7 +450,12 @@ public class CompatUniformManager {
}
public static void onDeleteProgram(int program) {
programUniforms.remove(program);
final boolean locked = GLStateManager.acquireDrawLock();
try {
programUniforms.remove(program);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
}
public static boolean hasProgram(int program) {
@@ -59,7 +59,7 @@ public class DisplayListIDAllocator {
numSetElements = 1;
}
public int allocRange(int count) {
public synchronized int allocRange(int count) {
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
if (count == 1) return allocSingle();
return allocMultiple(count);
@@ -132,7 +132,7 @@ public class DisplayListIDAllocator {
return numElements;
}
public void free(int id) {
public synchronized void free(int id) {
if (id <= 0) return; // ID 0 is reserved
int idx = id / 32;
if (idx >= numElements) return;
@@ -147,7 +147,7 @@ public class DisplayListIDAllocator {
}
}
public void freeRange(int base, int count) {
public synchronized void freeRange(int base, int count) {
if (count <= 0 || base <= 0) return;
int startIdx = base / 32;
@@ -166,7 +166,7 @@ public class DisplayListIDAllocator {
}
}
public boolean isAllocated(int id) {
public synchronized boolean isAllocated(int id) {
if (id <= 0) return false;
int idx = id / 32;
if (idx >= numSetElements) return false;
@@ -144,7 +144,7 @@ public class DisplayListManager {
}
public static String describeCurrentCompilation() {
if (currentRecorder == null) {
if (!isRecording()) {
return "(not compiling)";
}
final String mode = glListMode == GL11.GL_COMPILE_AND_EXECUTE ? "GL_COMPILE_AND_EXECUTE" : "GL_COMPILE";
@@ -153,16 +153,20 @@ public class DisplayListManager {
}
public static boolean isCompileAndExecute() {
return glListMode == GL11.GL_COMPILE_AND_EXECUTE;
return isRecording() && glListMode == GL11.GL_COMPILE_AND_EXECUTE;
}
public static CommandRecorder pauseRecording() {
if (Thread.currentThread() != recordingThread) return null;
final CommandRecorder r = currentRecorder;
currentRecorder = null;
return r;
}
public static void resumeRecording(CommandRecorder r) {
if (Thread.currentThread() != recordingThread) {
throw new IllegalStateException("Cannot resume another thread's display list compilation");
}
currentRecorder = r;
}
@@ -229,15 +233,15 @@ public class DisplayListManager {
}
public static int getRecordingListId() {
return glListId;
return Thread.currentThread() == recordingThread ? glListId : -1;
}
public static int getListMode() {
return glListMode;
return Thread.currentThread() == recordingThread ? glListMode : 0;
}
public static void trackDrawRangeSource(String source) {
if (drawRangeSources != null) {
if (Thread.currentThread() == recordingThread && drawRangeSources != null) {
drawRangeSources.add(source);
}
}
@@ -761,8 +765,11 @@ public class DisplayListManager {
* @return true if the display list exists, false otherwise
*/
public static boolean displayListExists(int list) {
if (displayListCache.containsKey(list)) {
return true;
final boolean locked = GLStateManager.acquireDrawLock();
try {
if (displayListCache.containsKey(list)) return true;
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
// Check VBOManager for GTNHLib compatibility (negative IDs)
if (list < -1) {
@@ -778,7 +785,12 @@ public class DisplayListManager {
* @return The CompiledDisplayList, or null if not found
*/
public static CompiledDisplayList getDisplayList(int list) {
return displayListCache.get(list);
final boolean locked = GLStateManager.acquireDrawLock();
try {
return displayListCache.get(list);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
}
/**
@@ -787,73 +799,100 @@ public class DisplayListManager {
* GL_COMPILE: Commands are recorded only (not executed), GLSM cache unchanged
* GL_COMPILE_AND_EXECUTE: Commands are recorded AND executed (GLSM cache updated)
*/
public static void glNewList(int list, int mode) {
// Handle nested compilation - push current context onto stack
public static synchronized void glNewList(int list, int mode) {
final Thread recorder = recordingThread;
if (recorder != null && recorder != Thread.currentThread()) {
throw new IllegalStateException("glNewList(" + list + ") on " + Thread.currentThread().getName() + " while " + recorder.getName() + " is compiling a display list");
}
final boolean isNested = glListMode > 0;
final int captureDepth = TessellatorManager.getDirectCaptureDepth();
boolean pushed = false;
if (!isNested) recordingThread = Thread.currentThread();
try {
if (isNested) {
flushAll();
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, recordedStatePushes, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
pushed = true;
}
if (isNested) {
flushAll();
// Nested display list compilation violates OpenGL spec, but some of our optimizations require it
// Save current compilation context and start fresh for nested list
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, recordedStatePushes, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
glListId = list;
glListMode = mode;
currentRecorder = new CommandRecorder();
accumulatedDraws = new ArrayList<>(8);
transformCallback = new DisplayListCallback();
recordedStatePushes = 0;
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
pendingTransformOps = new ArrayList<>();
multMatrixSources = new ArrayList<>();
drawRangeSources = new ArrayList<>();
} else {
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
}
TessellatorManager.startCapturingDirect(transformCallback);
} catch (RuntimeException | Error failure) {
try {
if (TessellatorManager.getDirectCaptureDepth() > captureDepth) TessellatorManager.stopCapturingDirect();
} catch (RuntimeException | Error cleanup) {
failure.addSuppressed(cleanup);
}
try {
if (pushed) {
final CommandRecorder parent = compilationStack.peek().recorder();
try {
if (currentRecorder != null && currentRecorder != parent) currentRecorder.delete();
} finally {
popCompilationContext();
}
} else if (!isNested) {
try {
if (currentRecorder != null) currentRecorder.delete();
} finally {
resetCompilationState();
}
}
} catch (RuntimeException | Error cleanup) {
failure.addSuppressed(cleanup);
}
throw failure;
}
// Initialize fresh context for this (possibly nested) list
glListId = list;
glListMode = mode;
recordingThread = Thread.currentThread(); // Track which thread is recording
currentRecorder = new CommandRecorder(); // Create command recorder
accumulatedDraws = new ArrayList<>(8); // Fewer draws than commands typically
transformCallback = new DisplayListCallback();
recordedStatePushes = 0;
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
// Initialize debug logging fields (only when logging enabled)
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
pendingTransformOps = new ArrayList<>();
multMatrixSources = new ArrayList<>();
drawRangeSources = new ArrayList<>();
} else {
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
}
TessellatorManager.startCapturingDirect(transformCallback);
}
/**
* End display list compilation and build optimized/unoptimized versions.
*/
public static void glEndList() {
public static synchronized void glEndList() {
final Thread recorder = recordingThread;
if (recorder != null && recorder != Thread.currentThread()) {
throw new IllegalStateException("glEndList on " + Thread.currentThread().getName() + " while " + recorder.getName() + " is compiling a display list");
}
if (glListMode == 0) {
GLStateManager.warnOnce("endlist-outside", "glEndList called outside of a display list!");
return;
}
if (Tracy.ENABLED) Tracy.beginZone(Z_GL_LIST_COMPILE);
try {
TessellatorManager.stopCapturingDirect();
finishCurrentList();
} finally {
if (Tracy.ENABLED) Tracy.endZone();
// Non-null here means finishCurrentList threw before transferring or releasing
// ownership of the recorder.
if (currentRecorder != null) {
currentRecorder.delete();
try {
if (currentRecorder != null) currentRecorder.delete();
} finally {
popCompilationContext();
}
// Must always run, even when compilation fails - a leaked context leaves
// isRecording() true forever and every guarded GL call throws afterward.
popCompilationContext();
}
}
private static void finishCurrentList() {
// Stop compiling mode (works for both root and nested lists now)
TessellatorManager.stopCapturingDirect();
boolean unbalancedStatePush = false;
while (recordPopStateIfPending()) unbalancedStatePush = true;
if (unbalancedStatePush) {
@@ -966,7 +1005,6 @@ public class DisplayListManager {
/** Clear all root-level compilation state back to "not recording". */
private static void resetCompilationState() {
currentRecorder = null;
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
@@ -977,9 +1015,13 @@ public class DisplayListManager {
glListId = -1;
glListMode = 0;
recordedStatePushes = 0;
recordingThread = null;
}
public static void abortCompilation() {
public static synchronized void abortCompilation() {
if (recordingThread != null && recordingThread != Thread.currentThread()) {
throw new IllegalStateException("Cannot abort another thread's display list compilation");
}
if (glListMode == 0 && compilationStack.isEmpty()) {
return;
}
@@ -1007,7 +1049,7 @@ public class DisplayListManager {
}
public static void abortIfLeaked() {
if (glListMode != 0 || !compilationStack.isEmpty()) {
if (Thread.currentThread() == recordingThread && (glListMode != 0 || !compilationStack.isEmpty())) {
GLStateManager.warnOnce("frame-leak", "Display list {} left open across a frame boundary - aborting to recover", glListId);
abortCompilation();
}
@@ -1043,7 +1085,7 @@ public class DisplayListManager {
* Execute a compiled display list.
*/
public static void glCallList(int list) {
if (currentRecorder != null) {
if (isRecording()) {
recordCallList(list);
if (getListMode() != GL11.GL_COMPILE) {
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
@@ -130,30 +129,32 @@ public class FeedbackManager {
feedbackVertex(winX, winY, winZ, winW);
}
private static int positionStride() {
final int s = VAOManager.getAttribEffectiveStride(0);
private static int positionStride(GLContextState glCtx) {
final int s = glCtx.vaos.attribEffectiveStride(0);
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VAOManager.getAttribOffset(0);
private static long positionOffset(GLContextState glCtx) {
return glCtx.vaos.attribOffset(0);
}
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
final int stride = positionStride();
final long posOffset = positionOffset();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
final long posOffset = positionOffset(glCtx);
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) first * stride + posOffset, readbackBuf);
readPositionBuffer((long) first * stride + posOffset, readbackBuf, glCtx);
emitAllPrimitives(mode, count, i -> i, stride);
}
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
final int stride = positionStride();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
final int elementSize = GLTypes.sizeBytes(type);
final int indexByteCount = indexCount * elementSize;
@@ -169,12 +170,12 @@ public class FeedbackManager {
if (idx > maxIdx) maxIdx = idx;
}
final long posOffset = positionOffset();
final long posOffset = positionOffset(glCtx);
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
@@ -208,7 +209,8 @@ public class FeedbackManager {
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final int stride = positionStride();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
@@ -217,7 +219,7 @@ public class FeedbackManager {
if (idx > maxIdx) maxIdx = idx;
}
readVerticesFromVBO(minIdx, maxIdx, stride);
readVerticesFromVBO(minIdx, maxIdx, stride, glCtx);
final int base = minIdx;
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
}
@@ -249,18 +251,18 @@ public class FeedbackManager {
}
}
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
final long posOffset = positionOffset();
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride, GLContextState glCtx) {
final long posOffset = positionOffset(glCtx);
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
}
private static void readPositionBuffer(long offset, ByteBuffer destination) {
final int positionVbo = VAOManager.getAttribVBO(0);
final int previousVbo = GLStateManager.getBoundVBO();
private static void readPositionBuffer(long offset, ByteBuffer destination, GLContextState glCtx) {
final int positionVbo = glCtx.vaos.attribVBO(0);
final int previousVbo = glCtx.boundVBO;
if (positionVbo != 0 && positionVbo != previousVbo) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, positionVbo);
@@ -3,8 +3,6 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
@@ -35,8 +33,14 @@ import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingVaos;
import org.joml.Matrix4d;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -46,6 +50,7 @@ import org.lwjgl.opengl.GL30;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -214,7 +219,6 @@ public class GLContextState {
public int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
public final IntSet writeMappedBuffers = new IntOpenHashSet();
public PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
@@ -227,6 +231,31 @@ public class GLContextState {
public int drawFramebuffer = 0;
public int readFramebuffer = 0;
public int texGenGeneration;
public int defaultVAO;
public final VAOManager vaos = new VAOManager();
public final StreamingVaos streamingVaos = new StreamingVaos();
public int fontVao;
public boolean lockBindCallback;
public int foreignDrawDepth;
public int internalGlDepth;
public int internalDrawDepth;
public int savedFilterTextureId;
public boolean wideLineEmulationActive = false;
public boolean lineStippleActive = false;
public Instancing ffpInstancing = Instancing.NONE;
public float lastBrightnessX;
public float lastBrightnessY;
public final Matrix4f multMatrix = new Matrix4f();
public final Matrix4d conversionMatrix4d = new Matrix4d();
public final Matrix4f conversionMatrix4f = new Matrix4f();
public final Matrix4f gluMatrix = new Matrix4f();
public final Matrix4f gluMatrix2 = new Matrix4f();
public final Vector4f gluVec4 = new Vector4f();
public final FloatBuffer gluBuffer = BufferUtils.createFloatBuffer(16);
public final float[] texGenTempPlane = new float[4];
public final IntBuffer shaderBuf = BufferUtils.createIntBuffer(8);
public final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
public final ShaderManager ffp = new ShaderManager();
GLContextState() {
allStacksBuilder.addAll(textures.idStacks());
@@ -242,11 +271,11 @@ public class GLContextState {
for (int i = 0; i < lightDataStates.length; i++) {
lightDataStates[i] = member(new LightStateStack(GL11.GL_LIGHT0 + i, StackIdAllocator.nextId()));
}
allStacksBuilder.add(ShaderManager.getNormalStack());
allStacksBuilder.add(ShaderManager.getTexCoordStack());
allStacksBuilder.add(ffp.normalStack);
allStacksBuilder.add(ffp.texCoordStack);
allStacks = allStacksBuilder.toArray(new IStateStack<?>[0]);
final int capacity = StackIdAllocator.capacity();
final int capacity = StackIdAllocator.allocated();
stackById = new CowStateStack<?>[capacity];
depthsById = new CowDepths[capacity];
restoreBitById = new int[capacity];
@@ -257,6 +286,7 @@ public class GLContextState {
final int id = s.stackId();
stackById[id] = s;
depthsById[id] = s.cowDepths();
depthsById[id].owner = this;
restoreBitById[id] = s.restoreBit();
restoreUnitById[id] = s.restoreUnit();
kindById[id] = CowDispatch.kindOf(s);
File diff suppressed because it is too large. Load diff
@@ -82,17 +82,17 @@ public final class QuadConverter {
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
drawSharedQuadEbo(first, vertexCount, 1, false);
public static void drawQuadsAsTriangles(GLContextState glCtx, int first, int vertexCount) {
drawSharedQuadEbo(glCtx, first, vertexCount, 1, false);
}
public static void drawQuadsAsTrianglesInstanced(int first, int vertexCount, int primcount) {
drawSharedQuadEbo(first, vertexCount, primcount, true);
public static void drawQuadsAsTrianglesInstanced(GLContextState glCtx, int first, int vertexCount, int primcount) {
drawSharedQuadEbo(glCtx, first, vertexCount, primcount, true);
}
private static int misalignedDraws;
private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) {
private static void drawSharedQuadEbo(GLContextState glCtx, int first, int vertexCount, int primcount, boolean instanced) {
final int quadCount = vertexCount / 4;
if ((first & 3) != 0) {
misalignedDraws++;
@@ -111,26 +111,21 @@ public final class QuadConverter {
memPutInt(ptr + 20, base + 3);
ptr += 24;
}
uploadAndDraw(dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
uploadAndDraw(glCtx, dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
return;
}
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
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();
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
final int prevEbo = glCtx.vaos.boundEBO;
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);
}
/**
@@ -143,42 +138,37 @@ public final class QuadConverter {
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
uploadAndDraw(dst, triIndexCount, indexType, bytesPerIndex, 1, false);
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
uploadAndDraw(glCtx, dst, triIndexCount, indexType, bytesPerIndex, 1, false);
}
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = glCtx.vaos.boundEBO;
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();
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);
memFree(dst);
}
@@ -190,15 +180,15 @@ public final class QuadConverter {
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
* @param offset byte offset into the currently bound EBO
*/
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
drawQuadElementsFromEbo(indexCount, type, offset, 1, false);
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int indexCount, int type, long offset) {
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, 1, false);
}
public static void drawQuadElementsAsTrianglesInstanced(int indexCount, int type, long offset, int primcount) {
drawQuadElementsFromEbo(indexCount, type, offset, primcount, true);
public static void drawQuadElementsAsTrianglesInstanced(GLContextState glCtx, int indexCount, int type, long offset, int primcount) {
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, primcount, true);
}
private static void drawQuadElementsFromEbo(int indexCount, int type, long offset, int primcount, boolean instanced) {
private static void drawQuadElementsFromEbo(GLContextState glCtx, int indexCount, int type, long offset, int primcount, boolean instanced) {
if (indexCount == 0) return;
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
final int quadCount = indexCount / 4;
@@ -213,13 +203,13 @@ public final class QuadConverter {
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
memFree(src);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
}
/**
* Convert a client-side IntBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
public static void drawQuadElementsAsTriangles(GLContextState glCtx, IntBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
@@ -230,13 +220,13 @@ public final class QuadConverter {
final long srcAddr = memAddress0(indices) + (long) indices.position() * 4;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Convert a client-side ShortBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
public static void drawQuadElementsAsTriangles(GLContextState glCtx, ShortBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
@@ -247,13 +237,13 @@ public final class QuadConverter {
final long srcAddr = memAddress0(indices) + (long) indices.position() * 2;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
}
/**
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
*/
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int count, int type, ByteBuffer indices) {
if (count == 0) return;
assert count % 4 == 0;
final int quadCount = count / 4;
@@ -263,23 +253,7 @@ public final class QuadConverter {
final ByteBuffer dst = memAlloc(triIndexCount * 4);
triangulateQuads(srcAddr, type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Clean up the shared EBO.
*/
public static void destroy() {
if (eboId != 0) {
GLStateManager.glDeleteBuffers(eboId);
eboId = 0;
maxQuads = 0;
}
if (scratchEboId != 0) {
GLStateManager.glDeleteBuffers(scratchEboId);
scratchEboId = 0;
scratchEboCapacity = 0;
}
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
@@ -169,13 +169,13 @@ public class RenderSystem {
if (isGLES) {
GLStateManager.LOGGER.info("GL ES context detected, enabling shader transformer.");
ShaderManager.getInstance().enable();
ShaderManager.enable();
} else if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
coreProfile = true;
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
ShaderManager.enable();
} else {
throw new IllegalStateException("Non-core GL context (profile mask 0x" + Integer.toHexString(profileMask)
+ "); FFP emulation requires a core profile and nothing would render. Context creation should have rejected this.");
@@ -503,13 +503,12 @@ public class RenderSystem {
dsaState.bindTextureToUnit(target, unit, texture);
}
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
public static void setupProjectionMatrix(Matrix4f matrix) {
final FloatBuffer projectionMatrixBuffer = GLStateManager.ctx().projectionMatrixBuffer;
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
matrix.get(0, PROJECTION_MATRIX_BUFFER);
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
matrix.get(0, projectionMatrixBuffer);
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
@@ -59,7 +60,6 @@ public final class FfpExtendedAttribs {
private static final HashMap<Key, ExtBuffer> cache = new HashMap<>();
private static final Key lookup = new Key();
private static int internalGlDepth = 0;
private static final HashMap<EboKey, Boolean> eboPatternCache = new HashMap<>();
private static final EboKey eboLookup = new EboKey();
@@ -79,15 +79,19 @@ public final class FfpExtendedAttribs {
return cache.isEmpty() && eboPatternCache.isEmpty();
}
private static int internalDrawDepth = 0;
public static void beginInternalDraw() { GLStateManager.ctx().internalDrawDepth++; }
public static void beginInternalDraw() { internalDrawDepth++; }
public static void endInternalDraw() {
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.internalDrawDepth > 0) glCtx.internalDrawDepth--;
}
public static void endInternalDraw() { if (internalDrawDepth > 0) internalDrawDepth--; }
private static void beginInternalGl() { GLStateManager.ctx().internalGlDepth++; }
private static void beginInternalGl() { internalGlDepth++; }
private static void endInternalGl() { if (internalGlDepth > 0) internalGlDepth--; }
private static void endInternalGl() {
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.internalGlDepth > 0) glCtx.internalGlDepth--;
}
private static boolean isNotConventionalTexturedArray(VAOManager.Attrib a) {
return a == null || !a.enabled || a.genericPointer || a.vboId == 0 || a.size < 2;
@@ -104,20 +108,20 @@ public final class FfpExtendedAttribs {
return true;
}
public static boolean maybeBind(int mode, int first, int count) {
public static boolean maybeBind(GLContextState glCtx, int mode, int first, int count) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (internalDrawDepth > 0) return false;
if (glCtx.internalDrawDepth > 0) return false;
final int extPrim = ImmediateExtendedAttribHandler.extPrimVerts(mode, count);
if (extPrim == 0 || first < 0) return false;
if (!h.wantsExtended()) return false;
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId)) return false;
VAOManager.Attrib normal = extPrim == 3 ? VAOManager.get(NORMAL_LOC) : null;
VAOManager.Attrib normal = extPrim == 3 ? glCtx.vaos.attrib(NORMAL_LOC) : null;
if (normal != null && (!normal.enabled || normal.genericPointer || normal.vboId == 0 || normal.size < 3
|| isStreaming(normal.vboId))) {
normal = null;
@@ -126,17 +130,17 @@ public final class FfpExtendedAttribs {
return bindExt(h, mode, first, count, extPrim, pos, uv, normal);
}
public static boolean maybeBindIndexed(int mode, int indexCount, int indexType, long indicesOffset) {
public static boolean maybeBindIndexed(GLContextState glCtx, int mode, int indexCount, int indexType, long indicesOffset) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (internalDrawDepth > 0) return false;
if (glCtx.internalDrawDepth > 0) return false;
if (mode != GL11.GL_TRIANGLES || indexCount <= 0 || indexCount % 6 != 0) return false;
if (!h.wantsExtended()) return false;
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(VAOManager.boundEBO)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(glCtx.vaos.boundEBO)) return false;
final int indexSize = indexTypeBytes(indexType);
if (indexSize == 0) return false;
@@ -144,7 +148,7 @@ public final class FfpExtendedAttribs {
final int first = (int) (indicesOffset / indexSize) / 6 * 4;
final int count = indexCount / 6 * 4;
if (!isQuadPatternEbo(VAOManager.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
if (!isQuadPatternEbo(glCtx.vaos.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
// Build/bind as GL_QUADS (the real primitive): face-normal tangents, 4-vertex tiles, no per-vertex normal.
return bindExt(h, GL11.GL_QUADS, first, count, 4, pos, uv, null);
@@ -226,8 +230,8 @@ public final class FfpExtendedAttribs {
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
}
public static void onDeleteBuffer(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
public static void onDeleteBuffer(GLContextState glCtx, int vboId) {
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
dropDerivedFrom(vboId);
respecCounts.remove(vboId);
respecFrames.remove(vboId);
@@ -236,8 +240,8 @@ public final class FfpExtendedAttribs {
persistentSources.remove(vboId);
}
public static void onBufferRespecified(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
public static void onBufferRespecified(GLContextState glCtx, int vboId) {
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
if (streamingSources.contains(vboId)) {
respecFrames.put(vboId, frame);
return;
@@ -268,16 +272,15 @@ public final class FfpExtendedAttribs {
respecFrames.put(vboId, frame);
}
public static void markStreaming(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
private static void markStreaming(int vboId) {
respecCounts.remove(vboId);
respecRunStart.remove(vboId);
respecFrames.put(vboId, frame);
if (streamingSources.add(vboId)) dropDerivedFrom(vboId);
}
public static void markPersistent(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
public static void markPersistent(GLContextState glCtx, int vboId) {
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
markStreaming(vboId);
persistentSources.add(vboId);
}
@@ -134,13 +134,13 @@ public final class FragmentKey {
}
// Damage overlay
if (GLStateManager.ffpInstancing.hasInstanceHead()) {
if (glCtx.ffpInstancing.hasInstanceHead()) {
global |= (1L << BIT_OVERLAY_INSTANCED);
} else if (glCtx.overlayA != 0.0f) {
global |= (1L << BIT_OVERLAY_ENABLED);
}
if (GLStateManager.lineStippleActive) {
if (glCtx.lineStippleActive) {
global |= (1L << BIT_LINE_STIPPLE);
}
@@ -9,7 +9,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.IdentityHashMap;
@@ -271,25 +270,6 @@ public class ShaderCache {
}
}
/** Destroy all cached programs and clear caches. */
public void destroy() {
for (int i = 0; i < capacity; i++) {
if (programs[i] != null) {
programs[i].destroy();
}
}
Arrays.fill(keys, 0);
Arrays.fill(programs, null);
Arrays.fill(fragSources, null);
count = 0;
uniqueFragCount = 0;
vertexSourceCache.clear();
geometrySourceCache.clear();
lastProgram = null;
lastVK = Long.MIN_VALUE;
GLStateManager.LOGGER.debug("FFP shader cache cleared");
}
public int getProgramCount() {
return count;
}
@@ -8,7 +8,6 @@ import com.gtnewhorizons.angelica.glsm.GLContextState;
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.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
@@ -28,45 +27,42 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
*/
public final class ShaderManager {
private static final class Holder {
static final ShaderManager INSTANCE = new ShaderManager();
}
private final ShaderCache cache = new ShaderCache();
private static final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
@Getter
private boolean active = false;
private boolean active;
private Program currentProgram = null;
private int lastBoundProgramId = -1;
private int lastBoundProgramId;
private long currentVertexKeyPacked = Long.MIN_VALUE;
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
private int currentFKLen = 0;
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
private static final Vector4f[] currentTexCoords = {
public final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
public final Vector4f[] currentTexCoords = {
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
};
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal, StackIdAllocator.nextStaticId());
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0], StackIdAllocator.nextStaticId());
@Getter private static int normalGeneration;
@Getter private static int texCoordGeneration;
public final Vec3fStack normalStack = new Vec3fStack(currentNormal, StackIdAllocator.nextId());
public final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0], StackIdAllocator.nextId());
public int normalGeneration;
public int texCoordGeneration;
private int preDrawCalls;
private int lastFramePreDrawCalls;
public static long variantSwitches;
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
public static Vector3f getCurrentNormal() { return GLStateManager.ctx().ffp.currentNormal; }
public static Vector4f getCurrentTexCoord() { return GLStateManager.ctx().ffp.currentTexCoords[0]; }
public static Vec3fStack getNormalStack() { return GLStateManager.ctx().ffp.normalStack; }
public static Vec4fStack getTexCoordStack() { return GLStateManager.ctx().ffp.texCoordStack; }
@Getter private static boolean enabled = false;
private ShaderManager() {
static {
cache.setDumpDir(SystemProperties.shaderDumpDir("ffp"));
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
VAOManager.setCurrentVertexFlags(format.getVertexFlags());
@@ -74,18 +70,20 @@ public final class ShaderManager {
});
}
public static ShaderManager getInstance() {
return Holder.INSTANCE;
public ShaderManager() {
active = enabled;
lastBoundProgramId = enabled ? 0 : -1;
}
public void enable() {
public static ShaderManager getInstance() {
return GLStateManager.ctx().ffp;
}
public static void enable() {
warmUp();
enabled = true;
if (GLStateManager.getActiveProgram() == 0) {
activate();
}
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.activeProgram == 0) glCtx.ffp.activate();
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
@@ -112,7 +110,7 @@ public final class ShaderManager {
}
}
public void disable() {
public static void disable() {
enabled = false;
}
@@ -121,10 +119,11 @@ public final class ShaderManager {
lastBoundProgramId = GLStateManager.getActiveProgram();
}
public static void invalidateBoundProgram() {
final ShaderManager sm = Holder.INSTANCE;
sm.lastBoundProgramId = -1;
sm.currentProgram = null;
public void invalidateProgram() {
lastBoundProgramId = -1;
currentProgram = null;
currentVertexKeyPacked = Long.MIN_VALUE;
uniforms.invalidateBinding();
}
public void deactivate() {
@@ -136,30 +135,29 @@ public final class ShaderManager {
GLStateManager.forceAttribDefaultsDirty();
}
public void preDraw() {
public void preDraw(GLContextState glCtx) {
GLSMHooks.resolvePendingProgram();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
// Handle FFP & Iris uniforms
final int currentProgramId = GLStateManager.getActiveProgram();
final int currentProgramId = glCtx.activeProgram;
if (currentProgramId != 0) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId)) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId, glCtx)) {
return; // Don't emulate FFP on non-iris core shaders
}
}
final int vertexFlags = VAOManager.getCurrentVertexFlags();
final int vertexFlags = glCtx.vaos.getVertexFlags();
final boolean hasColor = (vertexFlags & VertexFlags.COLOR_BIT) != 0;
final boolean hasNormal = (vertexFlags & VertexFlags.NORMAL_BIT) != 0;
final boolean hasTexCoord = (vertexFlags & VertexFlags.TEXTURE_BIT) != 0;
final boolean hasLightmap = (vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0;
GLStateManager.flushDeferredVertexAttribs(hasColor, hasNormal, hasTexCoord, hasLightmap);
GLStateManager.flushDeferredVertexAttribs(glCtx, hasColor, hasNormal, hasTexCoord, hasLightmap);
if (!active) return;
preDrawCalls++;
final GLContextState glCtx = GLStateManager.ctx();
final int fkLen = FragmentKey.packFromState(currentFKScratch, glCtx);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask, glCtx);
@@ -191,44 +189,31 @@ public final class ShaderManager {
}
public static void endFrame() {
final ShaderManager sm = Holder.INSTANCE;
final ShaderManager sm = GLStateManager.ctx().ffp;
sm.lastFramePreDrawCalls = sm.preDrawCalls;
sm.preDrawCalls = 0;
sm.uniforms.endFrame();
FfpExtendedAttribs.endFrame();
}
public static void setCurrentNormal(float x, float y, float z) {
public void setNormal(float x, float y, float z) {
normalStack.beforeModify();
currentNormal.set(x, y, z);
normalGeneration++;
}
public static void setCurrentTexCoord(float s, float t, float r, float q) {
public void setTexCoord(float s, float t, float r, float q) {
texCoordStack.beforeModify();
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
public void setTexCoord(int unit, float s, float t, float r, float q) {
if (unit == 0) texCoordStack.beforeModify();
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
}
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void destroy() {
cache.destroy();
uniforms.destroy();
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
QuadConverter.destroy();
active = false;
currentProgram = null;
}
public String getDebugInfo() {
return String.format("FFP: %d programs (%d vert, %d frag variants) | UBO: %d/%d draws wrote (mat %d, light %d, frag %d, misc %d)",
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount(),
@@ -11,7 +11,6 @@ import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
@@ -125,25 +124,26 @@ public class Uniforms {
stagedColor++;
dirty = true;
}
final int normG = ShaderManager.getNormalGeneration();
final ShaderManager ffp = glCtx.ffp;
final int normG = ffp.normalGeneration;
if (normG != normalGen) {
final Vector3f normal = ShaderManager.getCurrentNormal();
final Vector3f normal = ffp.currentNormal;
putVec3(FFPUniformBlock.CURRENT_NORMAL, normal.x, normal.y, normal.z);
normalGen = normG;
stagedNormal++;
dirty = true;
}
final int tcG = ShaderManager.getTexCoordGeneration();
final int tcG = ffp.texCoordGeneration;
if (tcG != texCoordGen) {
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ShaderManager.getCurrentTexCoord(0));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ShaderManager.getCurrentTexCoord(2));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ShaderManager.getCurrentTexCoord(3));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ffp.currentTexCoords[0]);
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ffp.currentTexCoords[2]);
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ffp.currentTexCoords[3]);
texCoordGen = tcG;
stagedTexCoord++;
dirty = true;
}
final float brightX = GLSMConfig.lastBrightnessX;
final float brightY = GLSMConfig.lastBrightnessY;
final float brightX = glCtx.lastBrightnessX;
final float brightY = glCtx.lastBrightnessY;
if (brightX != lightmapX || brightY != lightmapY) {
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD, brightX);
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD + 4, brightY);
@@ -446,6 +446,10 @@ public class Uniforms {
stagedMisc = 0;
}
void invalidateBinding() {
bound = false;
}
UniformRingBuffer getRing() {
return ring;
}
@@ -14,30 +14,34 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
public final class VAOManager {
public static final int MAX_ATTRIBS = 16;
public static final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
public final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
private static VAOData current = null;
private static int currentVertexFlags = 0;
private static Attrib[] currentAttribs = null;
public static int boundEBO = 0;
private VAOData current = null;
private int currentVertexFlags = 0;
private Attrib[] currentAttribs = null;
public int boundEBO = 0;
private static int clientSideEnabledCount = 0;
private int clientSideEnabledCount = 0;
public static void init(int defaultVAO) {
public void init(int defaultVAO) {
final VAOData data = new VAOData();
current = data;
currentAttribs = data.attribs;
vaoMap.put(defaultVAO, data);
}
public static void onBindEBO(int ebo) {
public boolean isInitialized() {
return current != null;
}
public void onBindEBO(int ebo) {
boundEBO = ebo;
}
public static void onBindVertexArrayPre(int vaoId) {
public void onBindVertexArrayPre(int vaoId) {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
@@ -62,14 +66,14 @@ public final class VAOManager {
clientSideEnabledCount = data.clientSideEnabledCount;
}
public static void onDeleteVertexArray(int vaoId) {
public void onDeleteVertexArray(int vaoId) {
final VAOData data = vaoMap.remove(vaoId);
if (data == null) return;
removeEboOwner(data.ebo, data);
if (data == current) current = null;
}
public static void onDeleteBuffer(int buffer) {
public void onDeleteBuffer(int buffer) {
if (buffer == 0) return;
VAOData owner = eboOwners.remove(buffer);
while (owner != null) {
@@ -80,12 +84,12 @@ public final class VAOManager {
}
}
private static void addEboOwner(int ebo, VAOData data) {
private void addEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
data.nextEboOwner = eboOwners.put(ebo, data);
}
private static void removeEboOwner(int ebo, VAOData data) {
private void removeEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
VAOData node = eboOwners.get(ebo);
if (node == data) {
@@ -105,10 +109,11 @@ public final class VAOManager {
}
}
public static void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public static void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public static int getCurrentVertexFlags() { return currentVertexFlags; }
public static void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
public void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public int getVertexFlags() { return currentVertexFlags; }
public void setVertexFlags(int flags) { currentVertexFlags = flags; }
public static void setCurrentVertexFlags(int flags) { GLStateManager.ctx().vaos.currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
@@ -122,7 +127,7 @@ public final class VAOManager {
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
public void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
@@ -139,7 +144,7 @@ public final class VAOManager {
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
public void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
@@ -156,19 +161,19 @@ public final class VAOManager {
}
}
public static boolean isGenericPointerEnabled(int index) {
public boolean isGenericPointerEnabled(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib a = currentAttribs[index];
return a != null && a.genericPointer && a.enabled;
}
public static void markConventional(int index) {
public void markConventional(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = currentAttribs[index];
if (a != null) a.genericPointer = false;
}
public static void setEnabled(int index, boolean enabled) {
public void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled != enabled) {
@@ -177,7 +182,7 @@ public final class VAOManager {
a.enabled = enabled;
}
public static void enableAttribute(int index) {
public void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
@@ -186,7 +191,7 @@ public final class VAOManager {
a.enabled = true;
}
public static void disableAttribute(int index) {
public void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
@@ -196,7 +201,7 @@ public final class VAOManager {
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private static Attrib getAttrib(int index) {
private Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
@@ -205,28 +210,32 @@ public final class VAOManager {
return a;
}
public static boolean isClientSideAttrib(int index) {
public boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
}
public static int getAttribEffectiveStride(int index) {
public Attrib attrib(int index) {
return currentAttribs[index];
}
public int attribEffectiveStride(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.effectiveStride() : 0;
}
public static long getAttribOffset(int index) {
public long attribOffset(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.offset : 0;
}
public static int getAttribVBO(int index) {
public int attribVBO(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.vboId : 0;
}
public static @Nullable Attrib get(int index) {
return currentAttribs[index];
return GLStateManager.ctx().vaos.attrib(index);
}
@@ -236,7 +245,7 @@ public final class VAOManager {
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
public boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
@@ -244,15 +253,15 @@ public final class VAOManager {
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
* attribs into a shared stream VBO so the draw succeeds under core profile.
*/
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
private int clientArraysVBO = 0;
private int clientArraysVBOCapacity = 0;
private final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
public static void uploadClientArraysToVBO() {
public void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = currentAttribs[i];
if (a == null || !a.enabled || a.clientPointer == null) continue;
clientArraysVBOOffsets[i] = totalBytes;
totalBytes += a.clientPointer.remaining();
@@ -275,7 +284,7 @@ public final class VAOManager {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = currentAttribs[i];
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
@@ -238,15 +238,15 @@ public final class VertexKey {
bits |= (1L << BIT_CLIP_PLANES);
}
if (GLStateManager.wideLineEmulationActive) {
if (glCtx.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
if (GLStateManager.lineStippleActive) {
if (glCtx.lineStippleActive) {
bits |= (1L << BIT_LINE_STIPPLE);
}
bits |= (long) GLStateManager.ffpInstancing.ordinal() << BIT_INSTANCING;
bits |= (long) glCtx.ffpInstancing.ordinal() << BIT_INSTANCING;
return bits;
}
@@ -41,10 +41,6 @@ public final class FrameHooks {
BackendManager.shutdown();
}
public static void bindSplashVao() {
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
}
public static void splashFinished() {
GLStateManager.markSplashComplete("SplashProgress.finish");
}
@@ -1,17 +1,17 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public final class GLSMConfig {
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
// Lightmap (replaces OpenGlHelper.lastBrightnessX/Y)
public static float lastBrightnessX;
public static float lastBrightnessY;
public final class GLSMConfig {
public static int packBrightness(float x, float y) {
return ((int) y << 16) | ((int) x & 0xFFFF);
}
public static int packedLastBrightness() {
return packBrightness(lastBrightnessX, lastBrightnessY);
final GLContextState glCtx = GLStateManager.ctx();
return packBrightness(glCtx.lastBrightnessX, glCtx.lastBrightnessY);
}
public static boolean hudCacheOverride;
@@ -12,7 +12,6 @@ public final class GLSMInitConfig {
@Getter private final boolean lwjglDebug;
@Getter private final StreamingUploader.UploadStrategy streamingUploadStrategy;
@Getter private final Consumer<DirectTessellator> directDrawer;
@Getter private final Runnable streamingDrawerDestroy;
@Getter private final int displayWidth;
@Getter private final int displayHeight;
@Getter private final Runnable postInitCallback;
@@ -24,7 +23,6 @@ public final class GLSMInitConfig {
this.streamingUploadStrategy = builder.streamingUploadStrategy;
this.noErrorChecks = builder.noErrorChecks;
this.directDrawer = builder.directDrawer;
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
@@ -43,7 +41,6 @@ public final class GLSMInitConfig {
private boolean lwjglDebug = SystemProperties.LWJGL_DEBUG;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private Consumer<DirectTessellator> directDrawer = TessellatorStreamingDrawer::drawDirect;
private Runnable streamingDrawerDestroy = TessellatorStreamingDrawer::destroy;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
@@ -72,11 +69,6 @@ public final class GLSMInitConfig {
return this;
}
public Builder streamingDrawerDestroy(Runnable streamingDrawerDestroy) {
this.streamingDrawerDestroy = streamingDrawerDestroy;
return this;
}
public Builder displaySize(int width, int height) {
this.displayWidth = width;
this.displayHeight = height;
@@ -11,22 +11,20 @@ public final class TextureUtilHooks {
private TextureUtilHooks() {}
private static int savedFilterTextureId;
public static void setMaxLevel(int mipmapLevels) {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
}
public static void captureBoundFilterTexture() {
savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
GLStateManager.ctx().savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
}
public static void clearSavedFilterTexture() {
savedFilterTextureId = 0;
GLStateManager.ctx().savedFilterTextureId = 0;
}
public static void restoreFilter(int min, int mag) {
final int id = savedFilterTextureId;
final int id = GLStateManager.ctx().savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
@@ -37,7 +35,7 @@ public final class TextureUtilHooks {
}
public static void restoreAniso(float aniso) {
final int id = savedFilterTextureId;
final int id = GLStateManager.ctx().savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
} else {
@@ -68,6 +68,7 @@ public final class AttribSnapshot {
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
final VAOManager vaos = GLStateManager.ctx().vaos;
boolean success = false;
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
@@ -78,7 +79,7 @@ public final class AttribSnapshot {
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = vaos.attrib(i);
if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * GLTypes.sizeBytes(a.type);
@@ -114,7 +115,7 @@ public final class AttribSnapshot {
try {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = vaos.attrib(i);
if (a == null || !a.enabled) continue;
if (a.vboId != 0) {
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
@@ -89,7 +90,8 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
@Override
public void setEnabled(boolean enabled) {
if (GLStateManager.getAttribDepth() > 0
final GLContextState o = cow.owner;
if (o != null && o.attribDepth > 0
&& (stateUnknown || !GLStateManager.isCachingEnabled() || enabled != vanillaValue())) {
beforeModify();
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
/**
@@ -14,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
public final class CowDepths {
public int id = -1;
public GLContextState owner;
public final int restoreBit;
public final int restoreUnit;
private final int[] slotDepth = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
/**
@@ -41,15 +42,19 @@ public interface CowStateStack<T> extends IStateStack<T> {
@Override
default int pushDepth() {
return GLStateManager.getAttribDepth();
final GLContextState o = cowDepths().owner;
return o == null ? 0 : o.attribDepth;
}
@Override
default void beforeModify() {
final int s = cowDepths().claim(GLStateManager.getAttribDepth());
final CowDepths d = cowDepths();
final GLContextState o = d.owner;
if (o == null) return;
final int s = d.claim(o.attribDepth);
if (s >= 0) {
captureSlot(s);
GLStateManager.registerModifiedState(stackId());
GLStateManager.registerModifiedState(o, d.id);
}
}
@@ -4,12 +4,8 @@ public final class StackIdAllocator {
private StackIdAllocator() {}
private static final int STATIC_BASE = 4096;
private static final int STATIC_CAPACITY = 16;
private static int next = -1;
private static int perContext = -1;
private static int staticNext;
public static void beginContext() {
next = 0;
@@ -28,14 +24,7 @@ public final class StackIdAllocator {
return next >= 0 ? next++ : -1;
}
public static int nextStaticId() {
if (staticNext >= STATIC_CAPACITY) {
throw new IllegalStateException("static stack id capacity exceeded");
}
return STATIC_BASE + staticNext++;
}
public static int capacity() {
return STATIC_BASE + STATIC_CAPACITY;
public static int allocated() {
return next;
}
}
@@ -0,0 +1,13 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
public final class StreamingVaos {
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE;
final int[] persistentVAOs = new int[FORMAT_COUNT];
final int[] orphanVAOs = new int[FORMAT_COUNT];
final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
}
@@ -6,6 +6,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
@@ -47,11 +48,6 @@ public class TessellatorStreamingDrawer {
private static PersistentStreamingBuffer persistentBuffer;
private static final OrphanStreamingBuffer[] orphanBuffers = new OrphanStreamingBuffer[FORMAT_COUNT];
private static final int[] persistentVAOs = new int[FORMAT_COUNT];
private static final int[] orphanVAOs = new int[FORMAT_COUNT];
private static final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
private static final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
private static ByteBuffer extScratch;
private static long extScratchAddress;
private static int extScratchCapacity;
@@ -243,13 +239,18 @@ public class TessellatorStreamingDrawer {
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(srcBase, extScratchAddress, vertexCount, vertexSize, combinedStride);
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
final boolean locked = GLStateManager.acquireDrawLock();
try {
ensureExtScratch(combinedBytes);
interleaveBase(srcBase, extScratchAddress, vertexCount, vertexSize, combinedStride);
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
return true;
}
@@ -311,7 +312,9 @@ public class TessellatorStreamingDrawer {
Tracy.beginZone(Z_SDL_STREAM_DRAW);
final boolean locked = GLStateManager.acquireDrawLock();
try {
ensureVAO(flags, format);
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
if (Tracy.ENABLED) {
streamedBytes += packed.remaining();
@@ -326,10 +329,10 @@ public class TessellatorStreamingDrawer {
final boolean fromRing = firstVertex >= 0;
if (fromRing) {
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.persistentVAOs[flags]);
} else {
if (Tracy.ENABLED && persistentBuffer != null) orphanFallbacks++;
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.orphanVAOs[flags]);
orphanBuffers[flags].upload(packed);
firstVertex = 0;
}
@@ -340,7 +343,7 @@ public class TessellatorStreamingDrawer {
lastStreamDrawMode = drawMode;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
drawWithQuadConversion(glCtx, drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
} finally {
if (locked) GLStateManager.releaseDrawLock();
@@ -352,11 +355,11 @@ public class TessellatorStreamingDrawer {
return persistentBuffer == null ? 0L : persistentBuffer.getWraps();
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
private static void drawWithQuadConversion(GLContextState glCtx, int drawMode, int firstVertex, int vertexCount) {
FfpExtendedAttribs.beginInternalDraw();
try {
if (drawMode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
QuadConverter.drawQuadsAsTriangles(glCtx, firstVertex, vertexCount);
} else {
GLStateManager.glDrawArrays(drawMode, firstVertex, vertexCount);
}
@@ -366,8 +369,10 @@ public class TessellatorStreamingDrawer {
}
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
ensureVAO(flags, format);
ensureExtendedVAOs(flags, format);
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
ensureExtendedVAOs(vaos, flags, format);
int firstVertex = -1;
@@ -376,18 +381,20 @@ public class TessellatorStreamingDrawer {
}
if (firstVertex >= 0) {
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedPersistentVAOs[flags]);
} else {
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedOrphanVAOs[flags]);
orphanBuffers[flags].upload(combined);
firstVertex = 0;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
drawWithQuadConversion(glCtx, drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
}
private static void ensureExtendedVAOs(int flags, VertexFormat format) {
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format) {
final int[] extendedOrphanVAOs = vaos.extendedOrphanVAOs;
final int[] extendedPersistentVAOs = vaos.extendedPersistentVAOs;
if (extendedOrphanVAOs[flags] == 0) {
extendedOrphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
@@ -467,12 +474,13 @@ public class TessellatorStreamingDrawer {
return repackBuffer;
}
private static void ensureVAO(int flags, VertexFormat format) {
private static void ensureVAO(StreamingVaos vaos, int flags, VertexFormat format) {
init();
if (orphanBuffers[flags] == null) orphanBuffers[flags] = new OrphanStreamingBuffer();
final int[] orphanVAOs = vaos.orphanVAOs;
final int[] persistentVAOs = vaos.persistentVAOs;
if (orphanVAOs[flags] == 0) {
orphanBuffers[flags] = new OrphanStreamingBuffer();
orphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
@@ -489,33 +497,4 @@ public class TessellatorStreamingDrawer {
}
}
/**
* Clean up all VAOs, streaming buffers, and the repack buffer.
*/
public static void destroy() {
for (int i = 0; i < FORMAT_COUNT; i++) {
if (persistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(persistentVAOs[i]); persistentVAOs[i] = 0; }
if (orphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(orphanVAOs[i]); orphanVAOs[i] = 0; }
if (extendedPersistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedPersistentVAOs[i]); extendedPersistentVAOs[i] = 0; }
if (extendedOrphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedOrphanVAOs[i]); extendedOrphanVAOs[i] = 0; }
if (orphanBuffers[i] != null) { orphanBuffers[i].destroy(); orphanBuffers[i] = null; }
}
if (extScratch != null) {
memFree(extScratch);
extScratch = null;
extScratchAddress = 0;
extScratchCapacity = 0;
}
if (persistentBuffer != null) {
persistentBuffer.destroy();
persistentBuffer = null;
}
if (repackBuffer != null) {
memFree(repackBuffer);
repackBuffer = null;
repackAddress = 0;
repackCapacity = 0;
}
initialized = false;
}
}
@@ -68,7 +68,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
CompatUniformManager.refreshCompatUniforms(program, GLStateManager.ctx());
// Verify no GL errors
assertEquals(0, GL11.glGetError(), "GL error after uniform upload");
@@ -146,7 +146,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
CompatUniformManager.refreshCompatUniforms(program, GLStateManager.ctx());
assertEquals(0, GL11.glGetError(), "GL error after fog uniform upload");
}
@@ -1,7 +1,11 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -12,6 +16,28 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class DisplayListIdAllocatorTest {
@Test
void concurrentAllocationsRemainUnique() throws Exception {
final DisplayListIDAllocator alloc = new DisplayListIDAllocator();
final int[][] ids = new int[2][500];
final CountDownLatch start = new CountDownLatch(1);
final TestThreads.Worker[] workers = new TestThreads.Worker[2];
for (int t = 0; t < workers.length; t++) {
final int[] lane = ids[t];
workers[t] = TestThreads.start("DisplayListIdAllocator-" + t, () -> {
if (!start.await(5, TimeUnit.SECONDS)) throw new AssertionError("start timed out");
for (int i = 0; i < lane.length; i++) lane[i] = alloc.allocRange(1);
});
}
start.countDown();
for (TestThreads.Worker worker : workers) worker.join();
final Set<Integer> unique = new HashSet<>();
for (int[] lane : ids) {
for (int id : lane) assertTrue(unique.add(id));
}
assertEquals(1000, unique.size());
}
@Test
void zeroNeverReturned() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator(1);
@@ -0,0 +1,38 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.lwjgl.opengl.SharedDrawable;
import java.util.Arrays;
import java.util.Map;
final class DrawableContexts {
private DrawableContexts() {}
static TestThreads.Worker start(SharedDrawable drawable, String name, TestThreads.Body body) {
return TestThreads.start(name, () -> {
GLStateManager.makeCurrent(drawable);
try {
body.run();
} finally {
GLStateManager.releaseContext(drawable);
}
});
}
static void run(SharedDrawable drawable, String name, TestThreads.Body body) throws InterruptedException {
start(drawable, name, body).join();
}
static void destroy(SharedDrawable drawable) {
final Map<Object, GLContextState> contexts = Reflect.getStatic(GLStateManager.class, "drawableContexts");
final GLContextState ctx = contexts.remove(drawable);
if (ctx != null) {
final GLContextState[] states = Reflect.getStatic(GLStateManager.class, "drawableContextStates");
Reflect.setStatic(GLStateManager.class, "drawableContextStates", Arrays.stream(states).filter(s -> s != ctx).toArray(GLContextState[]::new));
}
drawable.destroy();
}
}
@@ -15,6 +15,7 @@ import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -37,7 +38,10 @@ public class GLSM_ServerSideState_UnitTest {
private Runnable pendingCommand;
private Throwable backgroundError;
private volatile boolean shutdownRequested;
private volatile GLContextState workerContext;
private volatile boolean workerCaching;
private boolean originalSplashComplete;
private Thread originalDrawableGLHolder;
private static void setSplashComplete(boolean value) {
SplashWindow.setSplashComplete(value);
@@ -50,6 +54,7 @@ public class GLSM_ServerSideState_UnitTest {
@BeforeEach
void setupSharedDrawable() throws Exception {
originalSplashComplete = getSplashComplete();
originalDrawableGLHolder = GLStateManager.getDrawableGLHolder();
// Reset splash state to simulate splash screen scenario
setSplashComplete(false);
@@ -67,6 +72,8 @@ public class GLSM_ServerSideState_UnitTest {
shutdownRequested = false;
backgroundError = null;
workerContext = null;
workerCaching = false;
initLatch = new CountDownLatch(1);
commandLatch = new CountDownLatch(1);
completeLatch = new CountDownLatch(1);
@@ -75,10 +82,8 @@ public class GLSM_ServerSideState_UnitTest {
backgroundThread = new Thread(() -> {
try {
GLStateManager.makeCurrent(sharedDrawable);
if (GLStateManager.isCachingEnabled()) {
throw new AssertionError("SharedDrawable thread should have caching DISABLED");
}
workerContext = GLStateManager.ctx();
workerCaching = GLStateManager.isCachingEnabled();
// Signal that initialization is complete
initLatch.countDown();
@@ -102,11 +107,12 @@ public class GLSM_ServerSideState_UnitTest {
}
} catch (InterruptedException e) {
// Normal shutdown
} catch (LWJGLException e) {
backgroundError = e;
} catch (Throwable t) {
backgroundError = t;
initLatch.countDown();
} finally {
try {
sharedDrawable.releaseContext();
GLStateManager.releaseContext(sharedDrawable);
} catch (LWJGLException e) {
// Ignore on cleanup
}
@@ -116,6 +122,11 @@ public class GLSM_ServerSideState_UnitTest {
// Wait for background thread to complete initialization
assertTrue(initLatch.await(5, TimeUnit.SECONDS), "Background thread failed to initialize");
if (backgroundError != null) {
fail("Background thread failed to make SharedDrawable current", backgroundError);
}
assertTrue(workerCaching, "SharedDrawable thread should cache into its own context");
assertNotSame(GLStateManager.ctx(), workerContext, "SharedDrawable thread must not share the main thread's context");
}
@AfterEach
@@ -132,11 +143,12 @@ public class GLSM_ServerSideState_UnitTest {
}
if (sharedDrawable != null) {
sharedDrawable.destroy();
DrawableContexts.destroy(sharedDrawable);
}
// Restore original state
GLStateManager.setDrawableGL(null);
GLStateManager.setDrawableGLHolder(originalDrawableGLHolder);
setSplashComplete(originalSplashComplete);
if (originalSplashComplete) {
GLStateManager.markSplashComplete("test");
@@ -328,7 +340,11 @@ public class GLSM_ServerSideState_UnitTest {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId1);
assertEquals(texId1, GLStateManager.getBoundTextureForServerState(), "Main thread cache should show texId1 bound");
executeOnSharedDrawable(() -> GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId2));
executeOnSharedDrawable(() -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId2);
assertEquals(texId2, GLStateManager.getBoundTextureForServerState(), "SharedDrawable cache should show texId2 bound");
assertEquals(texId2, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "SharedDrawable actual GL binding should be texId2");
});
assertEquals(texId1, GLStateManager.getBoundTextureForServerState(), "Main thread cache should still show texId1 (client-side state is per-context)");
@@ -436,38 +452,40 @@ public class GLSM_ServerSideState_UnitTest {
}
}
/**
* Test glDeleteTextures: SharedDrawable deletes texture, verify cache cleared.
*/
@Test
void testDeleteTexturesFromSharedDrawable() throws InterruptedException {
int texId = GL11.glGenTextures();
final int texId = GLStateManager.glGenTextures();
int flushGen = 0;
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, 32, 32, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
int cachedFilter = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
assertEquals(GL11.GL_LINEAR, cachedFilter, "Cache should have LINEAR before delete");
assertEquals(GL11.GL_LINEAR, GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER), "Cache should have LINEAR before delete");
assertTrue(GL11.glIsTexture(texId), "Texture should exist before delete");
// SharedDrawable: delete the texture (deferred — name stays valid)
executeOnSharedDrawable(() -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
GLStateManager.glDeleteTextures(texId);
assertEquals(0, GLStateManager.getBoundTextureForServerState(), "Delete should unbind from the deleting context");
assertTrue(GL11.glIsTexture(texId), "Texture name should still be valid (deferred delete)");
});
assertTrue(GL11.glIsTexture(texId), "Texture name should still be valid on main thread (deferred delete)");
assertEquals(texId, GLStateManager.getBoundTextureForServerState(), "Main thread binding should survive until the flush");
// glGenTextures flushes deferred deletes
int newTexId = GLStateManager.glGenTextures();
assertFalse(GL11.glIsTexture(texId), "Texture should be gone after flush via glGenTextures");
GL11.glDeleteTextures(newTexId);
final int deferredGen = GLStateManager.glGenTextures();
GL11.glDeleteTextures(deferredGen);
assertTrue(GL11.glIsTexture(texId), "Delete should stay deferred while the splash is not complete");
setSplashComplete(true);
flushGen = GLStateManager.glGenTextures();
assertFalse(GL11.glIsTexture(texId), "Texture should be gone after the post-splash flush");
assertEquals(0, GLStateManager.getBoundTextureForServerState(), "Flush should scrub the main thread binding");
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
if (flushGen != 0) GL11.glDeleteTextures(flushGen);
if (GL11.glIsTexture(texId)) {
GL11.glDeleteTextures(texId);
}
@@ -0,0 +1,302 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.SharedDrawable;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
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.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
public class GLSM_SharedDrawableContext_GLTest {
private SharedDrawable sharedDrawable;
@AfterEach
void cleanup() {
if (sharedDrawable != null) {
DrawableContexts.destroy(sharedDrawable);
sharedDrawable = null;
}
}
@Test
void sharedDrawableBindsItsOwnContext() throws Exception {
final GLContextState primary = GLStateManager.ctx();
sharedDrawable = new SharedDrawable(Display.getDrawable());
TestThreads.run("SharedDrawableContext-Bind-Thread", () -> {
GLStateManager.makeCurrent(sharedDrawable);
try {
final GLContextState workerCtx = GLStateManager.ctx();
assertNotSame(primary, workerCtx);
assertNotEquals(0, workerCtx.defaultVAO);
assertEquals(workerCtx.defaultVAO, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING));
final IntBuffer viewport = BufferUtils.createIntBuffer(16);
GL11.glGetInteger(GL11.GL_VIEWPORT, viewport);
assertEquals(0, viewport.get(0));
assertEquals(0, viewport.get(1));
assertEquals(Display.getWidth(), viewport.get(2));
assertEquals(Display.getHeight(), viewport.get(3));
} finally {
GLStateManager.releaseContext(sharedDrawable);
}
assertSame(primary, GLStateManager.ctx());
});
}
@Test
void failedContextSwitchRestoresPreviousBinding() throws Exception {
final GLContextState primary = GLStateManager.ctx();
sharedDrawable = new SharedDrawable(Display.getDrawable());
final CountDownLatch acquired = new CountDownLatch(1);
final CountDownLatch release = new CountDownLatch(1);
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-Owner-Thread", () -> {
acquired.countDown();
if (!release.await(5, TimeUnit.SECONDS)) throw new AssertionError("release timed out");
});
try {
assertTrue(acquired.await(5, TimeUnit.SECONDS));
assertThrows(IllegalStateException.class, () -> GLStateManager.makeCurrent(sharedDrawable));
assertSame(primary, GLStateManager.ctx());
assertEquals(primary.defaultVAO, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING));
} finally {
release.countDown();
worker.join();
}
GLStateManager.makeCurrent(sharedDrawable);
assertNotSame(primary, GLStateManager.ctx());
GLStateManager.releaseContext(sharedDrawable);
GLStateManager.makeCurrent(Display.getDrawable());
assertSame(primary, GLStateManager.ctx());
}
@Test
void foreignThreadCannotFinishOrRecordIntoActiveList() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
final int existing = GLStateManager.glGenLists(1);
final int active = GLStateManager.glGenLists(1);
final int rejected = GLStateManager.glGenLists(1);
GLStateManager.glNewList(existing, GL11.GL_COMPILE);
GLStateManager.enableBlend();
GLStateManager.glEndList();
GLStateManager.disableBlend();
final CountDownLatch started = new CountDownLatch(1);
final CountDownLatch finish = new CountDownLatch(1);
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-List-Thread", () -> {
GLStateManager.glNewList(active, GL11.GL_COMPILE);
started.countDown();
if (!finish.await(5, TimeUnit.SECONDS)) throw new AssertionError("finish timed out");
GLStateManager.glEndList();
});
try {
try {
assertTrue(started.await(5, TimeUnit.SECONDS));
assertThrows(IllegalStateException.class, () -> GLStateManager.glNewList(rejected, GL11.GL_COMPILE));
assertThrows(IllegalStateException.class, GLStateManager::glEndList);
assertEquals(0, GLStateManager.getListMode());
DisplayListManager.abortIfLeaked();
assertThrows(IllegalStateException.class, DisplayListManager::abortCompilation);
GLStateManager.glCallList(existing);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_BLEND));
assertEquals(0, GLStateManager.getListMode());
} finally {
finish.countDown();
worker.join();
}
assertTrue(DisplayListManager.displayListExists(active));
GLStateManager.glCallList(active);
} finally {
GLStateManager.glDeleteLists(existing, 1);
GLStateManager.glDeleteLists(active, 1);
GLStateManager.glDeleteLists(rejected, 1);
GLStateManager.disableBlend();
}
}
@Test
void simultaneousListStartsClaimOneOwner() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
final int first = GLStateManager.glGenLists(1);
final int second = GLStateManager.glGenLists(1);
final CyclicBarrier start = new CyclicBarrier(2);
final CountDownLatch attempted = new CountDownLatch(2);
final AtomicInteger completed = new AtomicInteger();
final AtomicInteger rejected = new AtomicInteger();
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-List-Race-Thread", () -> {
start.await();
try {
GLStateManager.glNewList(second, GL11.GL_COMPILE);
} catch (IllegalStateException expected) {
rejected.incrementAndGet();
attempted.countDown();
return;
}
attempted.countDown();
if (!attempted.await(5, TimeUnit.SECONDS)) throw new AssertionError("attempts timed out");
GLStateManager.glEndList();
completed.incrementAndGet();
});
try {
start.await();
try {
GLStateManager.glNewList(first, GL11.GL_COMPILE);
} catch (IllegalStateException expected) {
rejected.incrementAndGet();
attempted.countDown();
}
if (GLStateManager.getListMode() != 0) {
attempted.countDown();
assertTrue(attempted.await(5, TimeUnit.SECONDS));
GLStateManager.glEndList();
completed.incrementAndGet();
}
worker.join();
} finally {
GLStateManager.glDeleteLists(first, 1);
GLStateManager.glDeleteLists(second, 1);
}
assertEquals(1, completed.get());
assertEquals(1, rejected.get());
}
@Test
void reenteredContextRebindsFfpProgram() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
DrawableContexts.run(sharedDrawable, "SharedDrawableContext-Reentry-Thread", () -> {
final ShaderManager sm = ShaderManager.getInstance();
final boolean wasEnabled = ShaderManager.isEnabled();
ShaderManager.enable();
sm.activate();
try {
final FloatBuffer vertices = BufferUtils.createFloatBuffer(9);
vertices.put(new float[] { -0.5f, -0.5f, 0f, 0.5f, -0.5f, 0f, 0f, 0.5f, 0f });
vertices.flip();
final int vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, vertices, GL15.GL_STATIC_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 12, 0);
VAOManager.setCurrentVertexFlags(0);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
GLStateManager.releaseContext(sharedDrawable);
GLStateManager.makeCurrent(sharedDrawable);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
assertNotEquals(0, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM), "invalidateProgram must force a rebind after replay clears the program to 0");
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteBuffers(vbo);
} finally {
sm.deactivate();
if (!wasEnabled) ShaderManager.disable();
}
});
}
@Test
void deferredTextureDeletesWaitForSplashAndScrubDrawableContexts() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
final int t = GLStateManager.glGenTextures();
final AtomicReference<GLContextState> workerCtxRef = new AtomicReference<>();
DrawableContexts.run(sharedDrawable, "SharedDrawableContext-DeferredDelete-Thread", () -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t);
workerCtxRef.set(GLStateManager.ctx());
});
final GLContextState workerCtx = workerCtxRef.get();
int deferredGen = 0;
int flushGen = 0;
try {
SplashWindow.setSplashComplete(false);
GLStateManager.glDeleteTextures(t);
deferredGen = GLStateManager.glGenTextures();
assertTrue(GL11.glIsTexture(t), "delete must stay deferred while the splash is not complete");
assertEquals(t, workerCtx.textures.getTextureUnitBindings(0).getBinding(), "worker ctx binding must survive until the flush");
SplashWindow.setSplashComplete(true);
flushGen = GLStateManager.glGenTextures();
assertFalse(GL11.glIsTexture(t), "the flush must delete the texture once the splash is complete");
assertEquals(0, workerCtx.textures.getTextureUnitBindings(0).getBinding(), "the flush must scrub the drawable context's binding");
} finally {
SplashWindow.setSplashComplete(true);
if (deferredGen != 0) GL11.glDeleteTextures(deferredGen);
if (flushGen != 0) GL11.glDeleteTextures(flushGen);
}
}
@Test
void concurrentContextsDoNotShareAttribOrCurrentState() throws Exception {
final GLContextState primary = GLStateManager.ctx();
sharedDrawable = new SharedDrawable(Display.getDrawable());
final CyclicBarrier barrier = new CyclicBarrier(2);
final AtomicReference<GLContextState> workerCtxRef = new AtomicReference<>();
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-Concurrent-Thread", () -> {
workerCtxRef.set(GLStateManager.ctx());
barrier.await();
for (int i = 0; i < 20000; i++) {
final int t = GLStateManager.pushState(StateSet.FONT);
GLStateManager.glNormal3f(0.1f, 0.2f, 0.3f);
GLStateManager.popStateTo(t);
}
GLStateManager.glNormal3f(0.1f, 0.2f, 0.3f);
});
try {
barrier.await();
for (int i = 0; i < 20000; i++) {
final int t = GLStateManager.pushState(StateSet.FONT);
GLStateManager.glNormal3f(0.25f, 0.5f, 0.75f);
GLStateManager.popStateTo(t);
}
GLStateManager.glNormal3f(0.25f, 0.5f, 0.75f);
worker.join();
final GLContextState workerCtx = workerCtxRef.get();
assertNotSame(primary, workerCtx);
assertEquals(0, primary.attribDepth);
assertEquals(0, workerCtx.attribDepth);
assertEquals(0.25f, primary.ffp.currentNormal.x, 0.0f);
assertEquals(0.5f, primary.ffp.currentNormal.y, 0.0f);
assertEquals(0.75f, primary.ffp.currentNormal.z, 0.0f);
assertEquals(0.1f, workerCtx.ffp.currentNormal.x, 0.0f);
assertEquals(0.2f, workerCtx.ffp.currentNormal.y, 0.0f);
assertEquals(0.3f, workerCtx.ffp.currentNormal.z, 0.0f);
} finally {
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
}
}
}
@@ -1,9 +1,14 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.Display;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -63,4 +68,37 @@ public class GLSM_SplashCompleteLatch_UnitTest {
t.join();
assertTrue(cachingOffThread.get(), "after the latch caching is global - this is what the latch buys");
}
@Test
void pausedSplashThreadDoesNotCompleteUntilItExits() throws Exception {
final CountDownLatch released = new CountDownLatch(1);
final CountDownLatch resume = new CountDownLatch(1);
GLStateManager.releaseContext(Display.getDrawable());
try {
final TestThreads.Worker worker = TestThreads.start("SplashPause-Worker-Thread", () -> {
GLStateManager.makeCurrent(Display.getDrawable());
GLStateManager.releaseContext(Display.getDrawable());
released.countDown();
resume.await();
});
assertTrue(released.await(10, TimeUnit.SECONDS), "worker did not release the Display drawable in time");
GLStateManager.makeCurrent(Display.getDrawable());
assertFalse(GLStateManager.isSplashComplete());
resume.countDown();
worker.join();
GLStateManager.releaseContext(Display.getDrawable());
GLStateManager.makeCurrent(Display.getDrawable());
assertTrue(GLStateManager.isSplashComplete());
} finally {
resume.countDown();
Reflect.setStatic(GLStateManager.class, "stateSeedPending", false);
Reflect.setStatic(GLStateManager.class, "splashDisplayReleaser", null);
GLStateManager.makeCurrent(Display.getDrawable());
}
}
}
@@ -17,7 +17,7 @@ public class GLSM_VAO_UnitTest {
@Test
void testDefaultStates() {
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.getDefaultVAO(), "GL_VERTEX_ARRAY_BINDING Initial State");
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.ctx().defaultVAO, "GL_VERTEX_ARRAY_BINDING Initial State");
verifyState(GL13.GL_CLIENT_ACTIVE_TEXTURE, GL13.GL_TEXTURE0, "GL_CLIENT_ACTIVE_TEXTURE Initial State");
testClientStates();
}
@@ -84,7 +84,7 @@ public class GLSM_VAO_UnitTest {
// Check if everything got reset to default
glBindVertexArray(0);
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.getDefaultVAO(), "GL_VERTEX_ARRAY_BINDING - Unbound");
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.ctx().defaultVAO, "GL_VERTEX_ARRAY_BINDING - Unbound");
verifyState(GL15.GL_ARRAY_BUFFER_BINDING, 0, "GL_ARRAY_BUFFER_BINDING - Unbound");
verifyState(GL11.GL_VERTEX_ARRAY, defaultVertexEnabled, "GL_VERTEX_ARRAY - Reset");
@@ -192,9 +192,10 @@ public class GLSM_VAO_UnitTest {
}
private static void glBindVertexArray(int array) {
if (array == 0) array = GLStateManager.getDefaultVAO();
GLStateManager.ctx().boundVAO = array;
VAOManager.onBindVertexArrayPre(array);
final GLContextState glCtx = GLStateManager.ctx();
if (array == 0) array = glCtx.defaultVAO;
glCtx.boundVAO = array;
glCtx.vaos.onBindVertexArrayPre(array);
UniversalVAO.bindVertexArray(array);
}
}
@@ -28,7 +28,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
@@ -40,7 +40,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlNormal3fSetsVertexAttrib() {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
@@ -51,7 +51,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testSetLightmapTextureCoordsOnUnit1SetsVertexAttrib() {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
@@ -64,7 +64,7 @@ class VertexAttribDefaultBridgeTest {
void testGlTexCoord2fSetsVertexAttrib() {
// glTexCoord2f outside recording → sets default vertex attrib 2 (PRIMARY_UV)
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
@@ -77,7 +77,7 @@ class VertexAttribDefaultBridgeTest {
// Second call is a cache hit — changeColor not called.
// Vertex attrib should still hold the correct value from the first call.
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
@@ -89,7 +89,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testCompileMode_DoesNotSetVertexAttrib() {
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
int listId = GLStateManager.glGenLists(1);
GLStateManager.glNewList(listId, GL11.GL_COMPILE);
@@ -245,7 +245,7 @@ class FFPCombineShaderTest {
@Test
void testMultiTexCoordRoutesToShaderManager() {
// Not in immediate mode or display list recording, so should go to ShaderManager.currentTexCoord
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f); // reset
GLStateManager.ctx().ffp.setTexCoord(0.0f, 0.0f, 0.0f, 1.0f); // reset
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.5f, 0.75f);
var texCoord = ShaderManager.getCurrentTexCoord();
@@ -255,7 +255,7 @@ class FFPCombineShaderTest {
@Test
void testMultiTexCoord2dDelegatesToFloat() {
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.ctx().ffp.setTexCoord(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glMultiTexCoord2d(GL13.GL_TEXTURE0, 0.5, 0.75);
var texCoord = ShaderManager.getCurrentTexCoord();
@@ -69,7 +69,7 @@ class FFPVertexLightingGLTest {
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
sm.disable();
ShaderManager.disable();
GLStateManager.glBindVertexArray(0);
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
@@ -148,7 +148,7 @@ class FFPVertexLightingGLTest {
private float[] drawAndReadCenter() {
buildTriangle();
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
@@ -50,8 +49,8 @@ class FfpUniformBlockDedupTest {
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glTexCoord2f(0.0f, 0.0f);
GLSMConfig.lastBrightnessX = 0.0f;
GLSMConfig.lastBrightnessY = 0.0f;
GLStateManager.ctx().lastBrightnessX = 0.0f;
GLStateManager.ctx().lastBrightnessY = 0.0f;
GLStateManager.glFog(GL11.GL_FOG_COLOR, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 1.0f);
GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, floats(0.0f, 0.0f, 0.0f, 0.0f));
@@ -128,7 +127,7 @@ class FfpUniformBlockDedupTest {
addCategory(tests, "currentColor", () -> GLStateManager.glColor4f(0.25f, 0.5f, 0.75f, 1.0f));
addCategory(tests, "currentNormal", () -> GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f));
addCategory(tests, "currentTexCoord", () -> GLStateManager.glTexCoord2f(0.125f, 0.375f));
addCategory(tests, "lightmapCoord", () -> GLSMConfig.lastBrightnessX = 96.0f);
addCategory(tests, "lightmapCoord", () -> GLStateManager.ctx().lastBrightnessX = 96.0f);
addCategory(tests, "texGenPlane", () -> GLStateManager.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(0.5f, 0.0f, 0.0f, 0.25f)));
addCategory(tests, "clipPlane", () -> GLStateManager.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 1.0, 0.0, 7.0)));
addCategory(tests, "alphaRef", () -> GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.35f));
@@ -147,7 +147,7 @@ class InstancedFFPDrawGLTest {
CubeParityFixture.setupScene(lightmap, lit);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
@@ -172,7 +172,7 @@ class InstancedFFPDrawGLTest {
ParticleParityFixture.setupScene(true);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
@@ -222,9 +222,9 @@ class InstancedFFPDrawGLTest {
@Test
void instancedSourceUsesInstanceAttribs() {
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
final VertexKey key = VertexKey.fromState(true, true, false, false, 0b10);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
final String source = VertexShaderGenerator.generate(key);
assertTrue(source.contains("a_InstRow0"), "instance matrix attribs declared");
@@ -248,7 +248,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
@@ -278,7 +278,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
@@ -311,7 +311,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
@@ -351,7 +351,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
@@ -430,8 +430,8 @@ class InstancedFFPDrawGLTest {
}
private static void drawInstances() {
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
}
}
@@ -17,6 +17,7 @@ public class VAOManagerEboIndexTest {
private static final int DEFAULT_VAO = 0;
private static final int NO_VAO = -1;
private final VAOManager vaos = new VAOManager();
private final Int2IntOpenHashMap expectedEbo = new Int2IntOpenHashMap();
private int currentVao;
@@ -24,7 +25,7 @@ public class VAOManagerEboIndexTest {
void reset() {
drainManagerState();
expectedEbo.clear();
VAOManager.init(DEFAULT_VAO);
vaos.init(DEFAULT_VAO);
expectedEbo.put(DEFAULT_VAO, 0);
currentVao = DEFAULT_VAO;
}
@@ -34,23 +35,23 @@ public class VAOManagerEboIndexTest {
drainManagerState();
}
private static void drainManagerState() {
for (int vao : VAOManager.vaoMap.keySet().toIntArray()) {
VAOManager.onDeleteVertexArray(vao);
private void drainManagerState() {
for (int vao : vaos.vaoMap.keySet().toIntArray()) {
vaos.onDeleteVertexArray(vao);
}
VAOManager.boundEBO = 0;
assertEquals(0, VAOManager.eboOwners.size(), "draining every VAO left entries in the index");
vaos.boundEBO = 0;
assertEquals(0, vaos.eboOwners.size(), "draining every VAO left entries in the index");
}
private void bindVao(int vao) {
if (currentVao != NO_VAO) expectedEbo.put(currentVao, VAOManager.boundEBO);
VAOManager.onBindVertexArrayPre(vao);
if (currentVao != NO_VAO) expectedEbo.put(currentVao, vaos.boundEBO);
vaos.onBindVertexArrayPre(vao);
expectedEbo.putIfAbsent(vao, 0);
currentVao = vao;
}
private void bindEbo(int ebo) {
VAOManager.onBindEBO(ebo);
vaos.onBindEBO(ebo);
}
private int countOwners(int buffer) {
@@ -62,20 +63,20 @@ public class VAOManagerEboIndexTest {
}
private void deleteBuffer(int buffer) {
VAOManager.onDeleteBuffer(buffer);
vaos.onDeleteBuffer(buffer);
for (int vao : expectedEbo.keySet().toIntArray()) {
if (expectedEbo.get(vao) == buffer) expectedEbo.put(vao, 0);
}
}
private void deleteVao(int vao) {
VAOManager.onDeleteVertexArray(vao);
vaos.onDeleteVertexArray(vao);
expectedEbo.remove(vao);
if (vao == currentVao) currentVao = NO_VAO;
}
private void assertExpectedEbos(int op) {
for (var entry : VAOManager.vaoMap.int2ObjectEntrySet()) {
for (var entry : vaos.vaoMap.int2ObjectEntrySet()) {
final int vao = entry.getIntKey();
if (vao == currentVao) continue;
assertEquals(expectedEbo.get(vao), entry.getValue().ebo, () -> "op " + op + ": vao " + vao + " ebo mismatch");
@@ -140,10 +141,10 @@ public class VAOManagerEboIndexTest {
deleteBuffer(ebo);
assertEquals(0, VAOManager.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
assertEquals(0, VAOManager.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
assertEquals(0, VAOManager.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
assertFalse(VAOManager.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
assertEquals(0, vaos.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
}
@Test
@@ -156,9 +157,9 @@ public class VAOManagerEboIndexTest {
bindVao(DEFAULT_VAO);
deleteVao(1);
assertTrue(VAOManager.eboOwners.containsKey(ebo), "index dropped a still-live owner");
assertTrue(vaos.eboOwners.containsKey(ebo), "index dropped a still-live owner");
deleteVao(2);
assertFalse(VAOManager.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
}
@Test
@@ -170,8 +171,8 @@ public class VAOManagerEboIndexTest {
deleteVao(1);
bindVao(DEFAULT_VAO);
assertFalse(VAOManager.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
assertEquals(0, VAOManager.eboOwners.size(), "index leaked an entry for a deleted VAO");
assertFalse(vaos.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
assertEquals(0, vaos.eboOwners.size(), "index leaked an entry for a deleted VAO");
}
@Test
@@ -184,6 +185,6 @@ public class VAOManagerEboIndexTest {
for (int vao = 1; vao <= 64; vao++) {
deleteVao(vao);
}
assertEquals(0, VAOManager.eboOwners.size(), "index leaked entries after every VAO was deleted");
assertEquals(0, vaos.eboOwners.size(), "index leaked entries after every VAO was deleted");
}
}
@@ -314,10 +314,11 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
final ByteBuffer colorSlice = clientData.duplicate().order(ByteOrder.nativeOrder());
colorSlice.position(8);
VAOManager.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
VAOManager.enableAttribute(0);
VAOManager.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
VAOManager.enableAttribute(1);
final VAOManager vaos = GLStateManager.ctx().vaos;
vaos.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
vaos.enableAttribute(0);
vaos.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
vaos.enableAttribute(1);
GLStateManager.glBindVertexArray(0);
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
@@ -593,8 +594,9 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
VAOManager.enableAttribute(0);
final VAOManager vaos = GLStateManager.ctx().vaos;
vaos.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
vaos.enableAttribute(0);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, indices);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
@@ -42,7 +42,6 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
GLStateManager.initialize(GLSMInitConfig.builder()
.displaySize(800, 600)
.directDrawer(t -> {})
.streamingDrawerDestroy(() -> {})
.enableDSA(false)
.build());
GLStateManager.setRunningSplash(false);
@@ -70,7 +70,6 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
final GLSMInitConfig config = GLSMInitConfig.builder()
.displaySize(displayMode.getWidth(), displayMode.getHeight())
.directDrawer(t -> {})
.streamingDrawerDestroy(() -> {})
.enableDSA(false)
.build();
setMainThread(Thread.currentThread());
@@ -144,7 +143,7 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
if (ffp.isActive()) {
ffp.deactivate();
}
ffp.disable();
ShaderManager.disable();
// Force-sync VAO binding. Tests may unbind via raw GL, desynchronizing GLSM's cache.
// Invalidate the cache so glBindVertexArray(0) actually issues the GL call.
@@ -272,9 +272,9 @@ public final class CubeParityFixture {
CubeInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.ffpInstancing = Instancing.CUBE;
GLStateManager.setFfpInstancing(Instancing.CUBE);
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, specs.length);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
}
@@ -244,10 +244,10 @@ public final class FfpFixture {
}
public static void resetFfpState() {
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
sm.disable();
ShaderManager.disable();
GLStateManager.glBindVertexArray(0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
@@ -184,9 +184,9 @@ public final class ParticleParityFixture {
ParticleInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.ffpInstancing = Instancing.PARTICLE;
GLStateManager.setFfpInstancing(Instancing.PARTICLE);
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, ParticleQuadMesh.VERTEX_COUNT, particles.length);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
}
@@ -0,0 +1,53 @@
package com.gtnewhorizons.angelica.glsm.testutil;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
public final class TestThreads {
private static final long JOIN_TIMEOUT_MS = 10_000;
private TestThreads() {}
@FunctionalInterface
public interface Body {
void run() throws Throwable;
}
public static final class Worker {
private final Thread thread;
private final AtomicReference<Throwable> error = new AtomicReference<>();
private Worker(String name, Body body) {
thread = new Thread(() -> {
try {
body.run();
} catch (Throwable t) {
error.set(t);
}
}, name);
thread.setDaemon(true);
thread.start();
}
public Throwable error() {
return error.get();
}
public void join() throws InterruptedException {
thread.join(JOIN_TIMEOUT_MS);
assertFalse(thread.isAlive(), thread.getName() + " did not finish");
if (error.get() != null) fail(error.get());
}
}
public static Worker start(String name, Body body) {
return new Worker(name, body);
}
public static void run(String name, Body body) throws InterruptedException {
start(name, body).join();
}
}
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.sdlgpu.compute.VoxelizationDispatcher;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
@@ -555,14 +556,34 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public boolean handleMakeCurrent(Object drawable) {
if (!isSDLManagedDrawable(drawable)) return false;
Lwjgl3GLCapabilitiesShim.installOnCurrentThread(advertisedCapabilities());
final OffscreenTarget splash = splashTarget;
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
s().boundFboId = splash.fboId();
frameManager.frame().swapchainUnavailable = true;
}
enterContext(drawable instanceof SDLDrawable);
return true;
}
private void enterContext(boolean displayDrawable) {
final ContextState st = s();
final GLContextState ctx = GLStateManager.ctx();
if (st.mirroredContext != ctx) {
st.invalidateMirror();
st.mirroredContext = ctx;
}
final OffscreenTarget splash = splashTarget;
final boolean splashContext = displayDrawable && splash != null && Thread.currentThread() != GLStateManager.getMainThread();
st.defaultFboId = splashContext ? splash.fboId() : 0;
if (splashContext && !frameManager.isFrameActive()) {
beginSplashFrame();
} else if (st.needsSeed) {
st.needsSeed = false;
GLStateManager.replayStateToBackend();
}
}
private void beginSplashFrame() {
frameManager.beginFrame();
beginFrameInit();
frameManager.frame().swapchainUnavailable = true;
}
@Override public boolean handleReleaseContext(Object drawable) {
if (!isSDLManagedDrawable(drawable)) return false;
if (Thread.currentThread() == GLStateManager.getMainThread()) return true;
@@ -583,9 +604,7 @@ public class SDLGPURenderBackend extends RenderBackend {
endFrameUploadFlush();
frameManager.endFrame();
SplashDispatcher.signalFrameReady((int) s().viewportW, (int) s().viewportH);
frameManager.beginFrame();
beginFrameInit();
frameManager.frame().swapchainUnavailable = true;
beginSplashFrame();
return true;
}
frameManager.presentFrame();
@@ -1726,13 +1745,16 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void deleteFramebuffers(int framebuffer) {
final ContextState cs = s();
final boolean drawBound = cs.boundFboId == framebuffer;
final boolean readBound = cs.boundReadFboId == framebuffer;
resourceManager.deleteFbo(framebuffer);
if (cs.boundFboId == framebuffer) cs.boundFboId = 0;
if (cs.boundReadFboId == framebuffer) cs.boundReadFboId = 0;
if (drawBound) bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
if (readBound) bindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
}
@Override public void bindFramebuffer(int target, int framebuffer) {
final ContextState cs = s();
if (framebuffer == 0) framebuffer = cs.defaultFboId;
final boolean isRead = target == GL30.GL_READ_FRAMEBUFFER;
final boolean isDraw = target == GL30.GL_DRAW_FRAMEBUFFER;
final boolean isBoth = !isRead && !isDraw;
@@ -1771,7 +1793,7 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
final ContextState cs = s();
if (cs.boundFboId == 0) return;
if (cs.boundFboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
if (fbo == null) return;
@@ -1852,7 +1874,7 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void drawBuffers(int buffer) {
final ContextState cs = s();
if (cs.boundFboId == 0) return;
if (cs.boundFboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
if (fbo == null) return;
@@ -1875,7 +1897,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void drawBuffers(IntBuffer bufs) {
final ContextState cs = s();
if (cs.boundFboId == 0) return;
if (cs.boundFboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
if (fbo == null) return;
applyDrawBuffersFromIntBuffer(fbo, bufs, true);
@@ -1911,8 +1933,9 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
@Override public void readBuffer(int mode) {
final int fboId = s().boundReadFboId;
if (fboId == 0) return;
final ContextState cs = s();
final int fboId = cs.boundReadFboId;
if (fboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(fboId);
if (fbo == null) return;
if (mode >= GL30.GL_COLOR_ATTACHMENT0 && mode < GL30.GL_COLOR_ATTACHMENT0 + ContextState.MAX_COLOR_ATTACHMENTS) {
@@ -2047,6 +2070,10 @@ public class SDLGPURenderBackend extends RenderBackend {
final int colorFormat = cachedSwapchainFormatArray != null ? cachedSwapchainFormatArray[0] : 0;
return defaultFramebufferAttachmentParameteri(attachment, pname, colorFormat, resourceManager.getSwapchainDepthStencilFormat());
}
if (fboId == cs.defaultFboId) {
final FboState fbo = resourceManager.getFbo(fboId);
return defaultFramebufferAttachmentParameteri(attachment, pname, fbo.colorFormats[0], fbo.depthFormat);
}
return fboAttachmentParameteri(resourceManager.getFbo(fboId), attachment, pname);
}
@@ -3720,8 +3747,8 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL43.GL_MAX_DEBUG_GROUP_STACK_DEPTH -> 64;
case GL43.GL_MAX_LABEL_LENGTH -> 256;
case GL43.GL_MAX_DEBUG_MESSAGE_LENGTH -> 1024;
case GL11.GL_DRAW_BUFFER -> drawBufferEnum(cs.boundFboId);
case GL11.GL_READ_BUFFER -> readBufferEnum(cs.boundReadFboId);
case GL11.GL_DRAW_BUFFER -> drawBufferEnum(cs.boundFboId, cs.defaultFboId);
case GL11.GL_READ_BUFFER -> readBufferEnum(cs.boundReadFboId, cs.defaultFboId);
case GL11.GL_TEXTURE_BINDING_2D -> boundTextureOf(cs.activeTextureUnit, cs.boundTextures);
case GL11.GL_DEPTH_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE);
case GL11.GL_STENCIL_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
@@ -3738,15 +3765,15 @@ public class SDLGPURenderBackend extends RenderBackend {
return GL30.GL_COLOR_ATTACHMENT0 + attachmentIndex;
}
private int drawBufferEnum(int fboId) {
if (fboId == 0) return bufferEnum(0, 0);
private int drawBufferEnum(int fboId, int defaultFboId) {
if (fboId == defaultFboId) return bufferEnum(0, 0);
final FboState fbo = resourceManager.getFbo(fboId);
final int idx = (fbo == null || fbo.drawBuffers.length == 0) ? -1 : fbo.drawBuffers[0];
return bufferEnum(fboId, idx);
}
private int readBufferEnum(int fboId) {
if (fboId == 0) return bufferEnum(0, 0);
private int readBufferEnum(int fboId, int defaultFboId) {
if (fboId == defaultFboId) return bufferEnum(0, 0);
final FboState fbo = resourceManager.getFbo(fboId);
return bufferEnum(fboId, fbo == null ? -1 : fbo.readBufferIndex);
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu.frame;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
@@ -328,6 +329,8 @@ public final class ContextState {
public int boundFboId;
public int boundReadFboId;
public int defaultFboId;
public GLContextState mirroredContext;
public final LongOpenHashSet clearedTexturesThisFrame = new LongOpenHashSet();
public final LongOpenHashSet clearedStencilTexturesThisFrame = new LongOpenHashSet();
@@ -53,7 +53,7 @@ public final class OffscreenTarget {
public void markContent() { hasContent = true; }
public boolean isFor(ContextState st) {
return fboGlId != 0 && st.boundFboId == fboGlId;
return fboGlId != 0 && st.defaultFboId == fboGlId;
}
public void destroy(ResourceManager rm) {
@@ -47,7 +47,7 @@ class GlsmSdlFfpTest {
@AfterEach
void cleanupInstancing() {
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
CubeParityFixture.deleteResources();
ParticleParityFixture.deleteResources();
FfpFixture.IntegerInstances.delete();
@@ -51,7 +51,6 @@ public final class GlsmSdlHeadlessRig {
GLStateManager.initialize(GLSMInitConfig.builder()
.displaySize(SIZE, SIZE)
.directDrawer(TessellatorStreamingDrawer::drawDirect)
.streamingDrawerDestroy(TessellatorStreamingDrawer::destroy)
.build());
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete("glsmSdlHeadlessRig");
@@ -92,7 +91,7 @@ public final class GlsmSdlHeadlessRig {
return bound.get(target);
}
private static int colorTargetSdlFormat() {
static int colorTargetSdlFormat() {
final ResourceManager rm = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
final ResourceManager.TextureMeta meta = rm.getTextureMeta(colorTexture);
return meta != null ? meta.sdlFormat() : 0;
@@ -0,0 +1,91 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.frame.OffscreenTarget;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.splash.SplashDispatcher;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL30;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlSplashTargetTest {
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
@Test
void seededSplashContextDrawsIntoSplashTarget() throws Exception {
final SDLGPURenderBackend backend = (SDLGPURenderBackend) BackendManager.RENDER_BACKEND;
final OffscreenTarget target = createSplashTarget(backend);
Reflect.set(backend, "splashTarget", target);
try {
TestThreads.run("SplashTarget-Seed-Thread", () -> {
Reflect.invoke(backend, "enterContext", new Class<?>[]{boolean.class}, true);
GlsmSdlHeadlessRig.solidQuad(0f, 1f, 0f);
backend.handleSwapBuffers();
backend.onRenderThreadReleased(Thread.currentThread());
});
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, target.fboId());
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), 0xFF00FF00, "splash target");
} finally {
releaseSplashTarget(backend, target);
}
}
@Test
void bindZeroOnSplashThreadTargetsSplash() throws Exception {
final SDLGPURenderBackend backend = (SDLGPURenderBackend) BackendManager.RENDER_BACKEND;
final OffscreenTarget target = createSplashTarget(backend);
Reflect.set(backend, "splashTarget", target);
try {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0f, 0f, 1f, 1f);
TestThreads.run("SplashTarget-BindZero-Thread", () -> {
Reflect.invoke(backend, "enterContext", new Class<?>[]{boolean.class}, true);
GlsmSdlHeadlessRig.bindTarget();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GlsmSdlHeadlessRig.clearTo(1f, 0f, 0f, 1f);
GlsmSdlHeadlessRig.solidQuad(0f, 1f, 0f);
backend.handleSwapBuffers();
backend.onRenderThreadReleased(Thread.currentThread());
});
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, target.fboId());
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), 0xFF00FF00, "splash target");
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), 0xFF0000FF, "rig target");
} finally {
releaseSplashTarget(backend, target);
}
}
private static OffscreenTarget createSplashTarget(SDLGPURenderBackend backend) {
final Device device = Reflect.get(backend, "device");
final ResourceManager resourceManager = Reflect.get(backend, "resourceManager");
final int sdlFormat = GlsmSdlHeadlessRig.colorTargetSdlFormat();
final OffscreenTarget target = new OffscreenTarget();
target.create(device, resourceManager, GlsmSdlHeadlessRig.SIZE, GlsmSdlHeadlessRig.SIZE, sdlFormat);
return target;
}
private static void releaseSplashTarget(SDLGPURenderBackend backend, OffscreenTarget target) {
Reflect.set(backend, "splashTarget", null);
SplashDispatcher.reset();
GlsmSdlHeadlessRig.bindTarget();
final ResourceManager resourceManager = Reflect.get(backend, "resourceManager");
target.destroy(resourceManager);
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
@@ -35,7 +34,6 @@ class AttachmentClearTest {
rig = SdlTestRig.acquireRealDevice();
rig.resourceManager.cachePreferredDepthFormats();
sm = new ShaderManager(rig.device);
VAOManager.init(0);
store = new PipelineStore(rig.device);
attachmentClear = new AttachmentClear(rig.frameManager, store, sm);
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -17,7 +16,6 @@ class PipelineCacheKeyTest {
@BeforeEach
void setUp() {
VAOManager.init(0);
PipelineCache.setSwapchainFormats(new int[]{SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM});
}
@@ -1,7 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import org.junit.jupiter.api.BeforeEach;
@@ -31,7 +30,6 @@ class PipelineCacheVertexInputTest {
@BeforeEach
void setUp() {
VAOManager.init(0);
PipelineCache.setSwapchainFormats(new int[]{SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM});
store = new PipelineStore(null);
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
@@ -69,7 +68,6 @@ class StencilMaskGpuTest {
rig = SdlTestRig.acquireRealDevice();
rig.resourceManager.cachePreferredDepthFormats();
sm = new ShaderManager(rig.device);
VAOManager.init(0);
store = new PipelineStore(rig.device);
colorTexture = rig.resourceManager.createTexture(9500, GL11.GL_TEXTURE_2D, GL11.GL_RGBA8, SIZE, SIZE, 1, 1);
@@ -7,6 +7,7 @@ import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.ffp.FFPVertexLighting;
@@ -159,7 +160,6 @@ public class BatchingFontRenderer {
// OpenGL objects (static, can be used between multiple BatchingFontRenderer)
private static int fontVAO = 0;
private static int vbo;
private static IndexBuffer ebo;
private static int lightmapSamplerProgram;
@@ -169,6 +169,7 @@ public class BatchingFontRenderer {
private static boolean streamingInitialized;
private static int pendingBuffer;
private static int pendingBase;
private static GLContextState vaoContext;
private static int vaoBuffer = -1;
private static int vaoBase = -1;
@@ -219,7 +220,8 @@ public class BatchingFontRenderer {
float v = 0.0f;
int texture = 0;
if (active) {
scratchLightmapUv.set(GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY, 0.0f, 1.0f).mul(GLStateManager.getTextures().getTextureUnitMatrix(LIGHTMAP_TEX_UNIT));
final GLContextState glCtx = GLStateManager.ctx();
scratchLightmapUv.set(glCtx.lastBrightnessX, glCtx.lastBrightnessY, 0.0f, 1.0f).mul(GLStateManager.getTextures().getTextureUnitMatrix(LIGHTMAP_TEX_UNIT));
u = scratchLightmapUv.x;
v = scratchLightmapUv.y;
texture = unitBinding;
@@ -620,11 +622,16 @@ public class BatchingFontRenderer {
return;
}
GLStateManager.beginForeignDraw();
final boolean locked = GLStateManager.acquireDrawLock();
try {
flushDeferredTextInner();
GLStateManager.beginForeignDraw();
try {
flushDeferredTextInner();
} finally {
GLStateManager.endForeignDraw();
}
} finally {
GLStateManager.endForeignDraw();
if (locked) GLStateManager.releaseDrawLock();
}
}
@@ -859,8 +866,15 @@ public class BatchingFontRenderer {
lightmapSamplerProgram = fontShaderId;
}
final GLContextState glCtx = GLStateManager.ctx();
if (vaoContext != glCtx) {
vaoContext = glCtx;
vaoBuffer = -1;
}
int fontVAO = glCtx.fontVao;
if (fontVAO == 0) {
fontVAO = GLStateManager.glGenVertexArrays();
glCtx.fontVao = fontVAO;
GLStateManager.glBindVertexArray(fontVAO);
ebo.bind();
@@ -308,9 +308,9 @@ public final class WeatherRenderer {
WeatherInstancedAttribs.pointInstanceAttribs((long) first * WeatherInstancedAttribs.STRIDE);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setCurrentVertexFlags(WeatherQuadMesh.VERTEX_FLAGS);
if (deferred == null) GLStateManager.ffpInstancing = Instancing.WEATHER;
if (deferred == null) GLStateManager.setFfpInstancing(Instancing.WEATHER);
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLE_FAN, 0, WeatherQuadMesh.VERTEX_COUNT, count);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
if (deferred != null) deferred.rebindCurrentPass();
@@ -333,7 +333,7 @@ public final class PlayerReflectionCapture {
GLStateManager.glColorMask(false, false, false, false);
GLStateManager.glDepthMask(false);
try {
QuadConverter.drawQuadsAsTriangles(0, VERTEX_COUNT);
QuadConverter.drawQuadsAsTriangles(GLStateManager.ctx(), 0, VERTEX_COUNT);
} finally {
GLStateManager.popStateTo(maskStateDepth);
GLStateManager.setModelViewMatrix(savedModelView);
@@ -345,11 +345,11 @@ public final class ParticleInstancer {
ParticleInstancedAttribs.pointInstanceAttribs(base);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
try {
if (ffp) GLStateManager.ffpInstancing = Instancing.PARTICLE;
if (ffp) GLStateManager.setFfpInstancing(Instancing.PARTICLE);
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, ParticleQuadMesh.VERTEX_COUNT, count);
draws++;
} finally {
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
}
}
@@ -112,7 +112,7 @@ final class InstancedTemplateRenderer {
int draws = 0;
long recordOffset = ringBase;
neutralizeExtendedAttribs();
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
for (int b = 0, n = liveBuckets.size(); b < n; b++) {
final Bucket bucket = liveBuckets.get(b);
final TemplateMesh mesh = bucket.mesh;
@@ -130,7 +130,7 @@ final class InstancedTemplateRenderer {
bucket.mesh = null;
bucketPool.add(bucket);
}
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
liveBuckets.clear();
return draws;
@@ -142,7 +142,7 @@ final class InstancedTemplateRenderer {
int redrawTemplates(TexRun base, int colorABGR) {
long recordOffset = base.uploadOffset;
neutralizeExtendedAttribs();
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
for (int b = 0, n = base.uploadMeshes.size(); b < n; b++) {
final TemplateMesh mesh = (TemplateMesh) base.uploadMeshes.get(b);
final int count = base.uploadCounts.getInt(b);
@@ -154,7 +154,7 @@ final class InstancedTemplateRenderer {
GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_COLOR);
recordOffset += (long) count * InstancedAttribs.STRIDE;
}
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
return base.uploadMeshes.size();
}
@@ -195,9 +195,9 @@ final class InstancedTemplateRenderer {
bindRingTo(UnitCubeMesh.vao(), UnitCubeMesh.VERTEX_FLAGS);
CubeInstancedAttribs.pointInstanceAttribs(ringBase);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.ffpInstancing = Instancing.CUBE;
GLStateManager.setFfpInstancing(Instancing.CUBE);
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, run.instances);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
return ringBase;
}
@@ -220,9 +220,9 @@ final class InstancedTemplateRenderer {
CubeInstancedAttribs.pointInstanceAttribs(offset);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
setConstantColor(colorABGR);
GLStateManager.ffpInstancing = Instancing.CUBE;
GLStateManager.setFfpInstancing(Instancing.CUBE);
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, instances);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_COLOR);
GLStateManager.glBindVertexArray(0);
}
@@ -10,11 +10,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = cpw.mods.fml.client.SplashProgress.class, remap = false)
public class MixinSplashProgressCaching {
@Inject(method = "start", at = @At("TAIL"))
private static void angelica$bindSharedDrawableVAO(CallbackInfo ci) {
FrameHooks.bindSplashVao();
}
@Inject(method = "finish", at = @At("RETURN"))
private static void angelica$enableCachingOnFinish(CallbackInfo ci) {
FrameHooks.splashFinished();
@@ -149,7 +149,7 @@ class PlayerReflectionSsboMappingTest {
GLStateManager.glUseProgram(program);
GLStateManager.glBindVertexArray(vao);
QuadConverter.drawQuadsAsTriangles(0, PlayerReflectionCapture.VERTEX_COUNT);
QuadConverter.drawQuadsAsTriangles(GLStateManager.ctx(), 0, PlayerReflectionCapture.VERTEX_COUNT);
final ByteBuffer ssboData = readback(ssbo, SSBO_BYTES);
final int floats = PlayerReflectionLayoutTest.FLOATS;
@@ -176,7 +176,7 @@ class PlayerReflectionSsboMappingTest {
GLStateManager.glUseProgram(program);
GLStateManager.glBindVertexArray(vao);
QuadConverter.drawQuadsAsTriangles(0, PlayerReflectionCapture.VERTEX_COUNT);
QuadConverter.drawQuadsAsTriangles(GLStateManager.ctx(), 0, PlayerReflectionCapture.VERTEX_COUNT);
final ByteBuffer visitedData = readback(visitedSsbo, VISITED_BYTES);
for (int vertexId = 0; vertexId < PlayerReflectionCapture.VERTEX_COUNT; vertexId++) {
@@ -98,7 +98,7 @@ class RetainedTesrGroupsGLTest {
GLStateManager.glColor4f(1f, 1f, 1f, 1f);
final ShaderManager ffp = ShaderManager.getInstance();
ffp.enable();
ShaderManager.enable();
ffp.activate();
source = new AngelicaBufferSource();
@@ -111,7 +111,7 @@ class RetainedTesrGroupsGLTest {
@AfterEach
void cleanup() {
skipClientArrays = false;
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
groups.clear();
@@ -119,7 +119,7 @@ class RetainedTesrGroupsGLTest {
ring.delete();
final ShaderManager ffp = ShaderManager.getInstance();
if (ffp.isActive()) ffp.deactivate();
ffp.disable();
ShaderManager.disable();
if (baseTexture != 0) GLStateManager.glDeleteTextures(baseTexture);
if (glintTexture != 0) GLStateManager.glDeleteTextures(glintTexture);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
@@ -58,7 +58,7 @@ class TesrLayerStateTest {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
final ShaderManager ffp = ShaderManager.getInstance();
if (ffp.isActive()) ffp.deactivate();
ffp.disable();
ShaderManager.disable();
GLStateManager.disableBlend();
GLStateManager.disableAlphaTest();
@@ -213,7 +213,7 @@ class TesrLayerStateTest {
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
final ShaderManager ffp = ShaderManager.getInstance();
ffp.enable();
ShaderManager.enable();
ffp.activate();
assertTrue(unitTexIdentity(), "a freshly loaded identity unit-0 matrix must read as identity");
@@ -176,7 +176,7 @@ class IrisInstancingGLTest {
GLSMHooks.pendingProgramSelection = null;
Reflect.setStatic(BatchEligibility.class, "depth", 0);
Reflect.setStatic(BatchEligibility.class, "allowed", false);
Reflect.setStatic(GLStateManager.class, "foreignDrawDepth", 0);
GLStateManager.ctx().foreignDrawDepth = 0;
}
private static DeferredWorldRenderingPipeline newPipeline(WorldRenderingPhase phase, boolean isMainBound) {
@@ -10,11 +10,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = cpw.mods.fml.client.SplashProgress.class, remap = false)
public class MixinSplashProgressCaching {
@Inject(method = "start", at = @At("TAIL"))
private static void umbra$bindSharedDrawableVAO(CallbackInfo ci) {
FrameHooks.bindSplashVao();
}
@Inject(method = "finish", at = @At("RETURN"))
private static void umbra$enableCachingOnFinish(CallbackInfo ci) {
FrameHooks.splashFinished();