Compare commits

..
6 Commits
Author SHA1 Message Date
Jason Mitchell a8c29fa3f9 Give each GL drawable its own GLSM context (#2142) 2026-09-26 22:31:07 -07:00
Eclipse-Sol 7c28a69bfe Optimize Enchant Glint Rendering (#2140)
* initial commit for enchant-glint-optimizations

* Fix spacing issues and remove GlintCompatibility.java
2026-09-26 19:21:21 -04:00
Eclipse-Sol 7f3fb35ebb Use actual camera when doing cloud related culling and fuctions (#2139) 2026-09-26 14:58:03 -07:00
Jason Mitchell 44c930f4ab Fix scissor clears wiping the whole screen on SDL-GPU (#2138) 2026-09-26 13:54:10 -07:00
Jason Mitchell b76284048a Fix shader packs in modded dimensions (#2137)
* Use world0 for unmapped dimensions
* Fix iris warnings for shaderpacks

Fixes https://github.com/GTNewHorizons/Angelica/issues/2128
2026-09-26 13:18:17 -04:00
Jason Mitchell 762c5661eb Stop blocking chunk workers on deferred ISBRH blocks (#2136) 2026-09-26 08:44:22 -07:00
129 changed files with 5112 additions and 1628 deletions

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) {
@@ -302,7 +302,7 @@ public class Feature {
));
attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of(
GLStateManager.getScissorTest()
// Scissor box
, GLStateManager.getScissorState()
));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
GLStateManager.getStencilTest()
@@ -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;
@@ -48,6 +47,10 @@ public class FeedbackManager {
return renderMode == GL11.GL_FEEDBACK;
}
public static int getRenderMode() {
return renderMode;
}
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
if (renderMode == GL11.GL_FEEDBACK) {
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
@@ -126,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;
@@ -165,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);
@@ -204,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++) {
@@ -213,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);
}
@@ -245,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;
@@ -23,6 +21,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
@@ -34,16 +33,24 @@ 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;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
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;
@@ -125,6 +132,7 @@ public class GLContextState {
public final BlendStateStack blendState = member(new BlendStateStack(StackIdAllocator.nextId()));
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
public final BooleanStateStack rasterizerDiscard = track(new BooleanStateStack(GL30.GL_RASTERIZER_DISCARD));
public final DepthStateStack depthState = member(new DepthStateStack(StackIdAllocator.nextId()));
public final BooleanStateStack depthTest = track(new BooleanStateStack(GL11.GL_DEPTH_TEST));
public final FogStateStack fogState = member(new FogStateStack(StackIdAllocator.nextId()));
@@ -200,6 +208,7 @@ public class GLContextState {
public final MaterialStateStack frontMaterial = member(new MaterialStateStack(GL11.GL_FRONT, StackIdAllocator.nextId()));
public final MaterialStateStack backMaterial = member(new MaterialStateStack(GL11.GL_BACK, StackIdAllocator.nextId()));
public final ViewPortStateStack viewportState = member(new ViewPortStateStack(StackIdAllocator.nextId()));
public final ScissorStateStack scissorState = member(new ScissorStateStack(StackIdAllocator.nextId()));
public int activeProgram = 0;
public final IntegerStateStack programStack = member(new IntegerStateStack(0, StackIdAllocator.nextId()));
public int listBase = 0;
@@ -210,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];
@@ -223,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());
@@ -238,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];
@@ -253,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);
}
@@ -23,6 +23,7 @@ public final class StateSet {
public static final int R_TEXTURE = 1 << 12;
public static final int R_ACTIVE_UNIT = 1 << 13;
public static final int R_PROGRAM = 1 << 14;
public static final int R_SCISSOR = 1 << 15;
public static final int B_FRAG_COLORBUF = 1;
public static final int B_FRAG_TEXTURE = 1 << 1;
@@ -205,6 +206,9 @@ public final class StateSet {
if ((mask & GL11.GL_VIEWPORT_BIT) != 0) {
restore |= R_VIEWPORT;
}
if ((mask & GL11.GL_SCISSOR_BIT) != 0) {
restore |= R_SCISSOR;
}
if ((mask & GL11.GL_LINE_BIT) != 0) {
restore |= R_LINE;
}
@@ -0,0 +1,55 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
public final class CombinedGlint {
private static final Matrix4f savedMatrix = new Matrix4f();
private static final Matrix4f inverse = new Matrix4f();
private static final Matrix4f relative = new Matrix4f();
private static boolean active;
private static boolean replaceAlpha;
private CombinedGlint() {}
public static boolean isActive() {
return active;
}
public static boolean replacesAlpha() {
return active && replaceAlpha;
}
public static boolean begin(Matrix4fc secondTextureMatrix) {
if (active || GLStateManager.getActiveProgram() != 0 || GLStateManager.getLightingState().isEnabled() || GLStateManager.getColorSumState().isEnabled() || !GLStateManager.glIsEnabled(GL11.GL_BLEND)) return false;
final var blend = GLStateManager.getBlendState();
final boolean lastAlpha = blend.getSrcAlpha() == GL11.GL_ONE && blend.getDstAlpha() == GL11.GL_ZERO && blend.getEquationAlpha() == GL14.GL_FUNC_ADD;
final boolean preserveAlpha = blend.getSrcAlpha() == GL11.GL_ZERO && blend.getDstAlpha() == GL11.GL_ONE && (blend.getEquationAlpha() == GL14.GL_FUNC_ADD || blend.getEquationAlpha() == GL14.GL_FUNC_REVERSE_SUBTRACT);
if (blend.getSrcRgb() != GL11.GL_SRC_COLOR || blend.getDstRgb() != GL11.GL_ONE
|| (!lastAlpha && !preserveAlpha && (blend.getSrcAlpha() != GL11.GL_SRC_COLOR || blend.getDstAlpha() != GL11.GL_ONE || blend.getEquationAlpha() != blend.getEquationRgb()))
|| (blend.getEquationRgb() != GL14.GL_FUNC_ADD && blend.getEquationRgb() != GL14.GL_FUNC_REVERSE_SUBTRACT)) return false;
final var textures = GLStateManager.getTextures();
if (!textures.getTextureUnitStates(0).isEnabled() || textures.getTextureUnitBindings(0).getBinding() == 0 || textures.getTexEnvState(0).mode != GL11.GL_MODULATE) return false;
if (textures.getTextureUnitStates(1).isEnabled() && textures.getTextureUnitBindings(1).getBinding() != 0 && textures.getTexEnvState(1).mode != GL11.GL_MODULATE) return false;
for (int unit = 2; unit < 4; unit++) {
if (textures.getTextureUnitStates(unit).isEnabled() && textures.getTextureUnitBindings(unit).getBinding() != 0) return false;
}
inverse.set(textures.getTextureUnitMatrix(0)).invert();
relative.set(secondTextureMatrix).mul(inverse);
if (!relative.isFinite()) return false;
savedMatrix.set(textures.getTextureUnitMatrix(3));
GLStateManager.setTextureMatrix(3, relative);
replaceAlpha = lastAlpha;
active = true;
return true;
}
public static void end() {
if (!active) return;
active = false;
GLStateManager.setTextureMatrix(3, savedMatrix);
}
}
@@ -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);
}
@@ -66,6 +66,8 @@ public final class FragmentKey {
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 14;
private static final int BIT_COMBINED_GLINT = 61;
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
@@ -111,6 +113,8 @@ public final class FragmentKey {
public static int packFromState(long[] out, GLContextState glCtx) {
long global = 0;
if (CombinedGlint.isActive()) global |= 1L << BIT_COMBINED_GLINT;
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
// Fog
if (glCtx.fogMode.isEnabled()) {
@@ -130,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);
}
@@ -248,6 +252,8 @@ public final class FragmentKey {
}
public int fogMode() { return (int) (packed[0] & 0x3); }
public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; }
public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; }
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
@@ -389,7 +395,9 @@ public final class FragmentKey {
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(), alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(), overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(),
overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import org.lwjgl.opengl.GL11;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
/**
* Generates GLSL 330 core fragment shaders for FFP emulation.
@@ -17,6 +18,15 @@ public final class FragmentShaderGenerator {
emitInputs(sb, key);
emitUniforms(sb, key);
if (key.combinedGlint()) {
sb.append("#define GLINT_REPLACE_ALPHA ").append(key.glintReplaceAlpha() ? 1 : 0).append('\n');
sb.append("#define GLINT_FOG_MODE ").append(key.fogMode()).append('\n');
sb.append("#define GLINT_ALPHA_FUNC ").append(key.alphaTestEnabled() ? key.alphaTestFunc() : 7).append('\n');
sb.append("#define GLINT_OVERLAY ").append(key.overlayInstanced() ? 2 : key.overlayEnabled() ? 1 : 0).append('\n');
sb.append("#define GLINT_LIGHTMAP ").append(key.nrEnabledUnits() > 1 && key.unitEnabled(1) ? 1 : 0).append('\n');
sb.append(ShaderLoader.getShaderSource("angelica:combined_glint.frag.glsl"));
return sb.toString();
}
sb.append("out vec4 fragColor;\n\n");
sb.append("void main() {\n");
@@ -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];
@@ -40,6 +40,7 @@ public final class CowDispatch {
public static final byte LINE = 20;
public static final byte TEXTURE_BINDING = 21;
public static final byte TEX_ENV = 22;
public static final byte SCISSOR = 23;
public static byte kindOf(CowStateStack<?> s) {
final Class<?> c = s.getClass();
@@ -66,6 +67,7 @@ public final class CowDispatch {
if (c == LineStateStack.class) return LINE;
if (c == TextureBindingStack.class) return TEXTURE_BINDING;
if (c == TexEnvState.class) return TEX_ENV;
if (c == ScissorStateStack.class) return SCISSOR;
throw new IllegalStateException("Unknown CowStateStack class: " + c.getName());
}
@@ -94,6 +96,7 @@ public final class CowDispatch {
case LINE -> ((LineStateStack) s).topSlotChanged();
case TEXTURE_BINDING -> ((TextureBindingStack) s).topSlotChanged();
case TEX_ENV -> ((TexEnvState) s).topSlotChanged();
case SCISSOR -> ((ScissorStateStack) s).topSlotChanged();
default -> throw new IllegalStateException("Unknown kind: " + kind);
};
}
@@ -123,6 +126,7 @@ public final class CowDispatch {
case LINE -> ((LineStateStack) s).restoreSlot(slot);
case TEXTURE_BINDING -> ((TextureBindingStack) s).restoreSlot(slot);
case TEX_ENV -> ((TexEnvState) s).restoreSlot(slot);
case SCISSOR -> ((ScissorStateStack) s).restoreSlot(slot);
default -> throw new IllegalStateException("Unknown kind: " + kind);
}
}
@@ -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);
}
}
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.ScissorState;
public final class ScissorStateStack extends ScissorState implements CowStateStack<ScissorStateStack> {
protected final ScissorState[] stack;
private final CowDepths cow = new CowDepths(StateSet.R_SCISSOR);
public ScissorStateStack(int id) {
cow.id = id;
stack = new ScissorState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new ScissorState();
}
}
@Override
public CowDepths cowDepths() {
return cow;
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.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,54 @@
package com.gtnewhorizons.angelica.glsm.states;
import java.nio.IntBuffer;
public class ScissorState implements ISettableState<ScissorState> {
public int x;
public int y;
public int width;
public int height;
public void setScissor(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void get(IntBuffer params) {
params.put(0, x);
params.put(1, y);
params.put(2, width);
params.put(3, height);
}
public void get(int[] params) {
params[0] = x;
params[1] = y;
params[2] = width;
params[3] = height;
}
@Override
public ScissorState set(ScissorState state) {
this.x = state.x;
this.y = state.y;
this.width = state.width;
this.height = state.height;
return this;
}
@Override
public boolean sameAs(Object state) {
if (this == state) return true;
if (!(state instanceof ScissorState scissorState)) return false;
return x == scissorState.x && y == scissorState.y && width == scissorState.width && height == scissorState.height;
}
@Override
public ScissorState copy() {
return new ScissorState().set(this);
}
}
@@ -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;
}
}
@@ -0,0 +1,56 @@
out vec4 fragColor;
vec4 glintLayer(vec4 coord, out bool visible) {
vec4 color = textureProj(u_Sampler0, coord) * v_Color;
#if GLINT_OVERLAY == 2
color.rgb = mix(color.rgb, v_Overlay.rgb, v_Overlay.a);
#elif GLINT_OVERLAY == 1
color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);
#endif
#if GLINT_LIGHTMAP
color *= textureProj(u_Sampler1, v_TexCoord1);
#endif
float qa = round(color.a * 255.0);
float ref = u_AlphaRef * 255.0;
#if GLINT_ALPHA_FUNC == 0
visible = false;
#elif GLINT_ALPHA_FUNC == 1
visible = qa < ref;
#elif GLINT_ALPHA_FUNC == 2
visible = qa == round(ref);
#elif GLINT_ALPHA_FUNC == 3
visible = qa <= ref;
#elif GLINT_ALPHA_FUNC == 4
visible = qa > ref;
#elif GLINT_ALPHA_FUNC == 5
visible = qa != round(ref);
#elif GLINT_ALPHA_FUNC == 6
visible = qa >= ref;
#else
visible = true;
#endif
if (!visible) return vec4(0.0);
#if GLINT_FOG_MODE == 1
float fog = v_FogCoord * u_FogParams.x + u_FogParams.y;
#elif GLINT_FOG_MODE == 2
float fog = exp2(-(v_FogCoord * u_FogParams.z));
#elif GLINT_FOG_MODE == 3
float fogDistance = v_FogCoord * u_FogParams.w;
float fog = exp2(-(fogDistance * fogDistance));
#endif
#if GLINT_FOG_MODE != 0
color.rgb = mix(u_FogColor.rgb, color.rgb, clamp(fog, 0.0, 1.0));
#endif
return color;
}
void main() {
bool visible0, visible1;
vec4 a = glintLayer(v_TexCoord0, visible0);
vec4 b = glintLayer(u_TextureMatrix3 * v_TexCoord0, visible1);
if (!visible0 && !visible1) discard;
fragColor = sqrt(a * a + b * b);
#if GLINT_REPLACE_ALPHA
fragColor.a = visible1 ? b.a : a.a;
#endif
}
@@ -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();
}
}
@@ -3,8 +3,10 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
@@ -170,6 +172,66 @@ public class GLSM_PopAttribMaskFidelity_GLTest {
}
}
@Test
void scissorBoxFollowsScissorBitOnly() {
final int[] boxA = { 10, 20, 100, 200 };
final int[] boxB = { 30, 40, 50, 60 };
try {
GLStateManager.glScissor(boxA[0], boxA[1], boxA[2], boxA[3]);
GLStateManager.disableScissorTest();
verifyState(GL11.GL_SCISSOR_BOX, boxA, "Scissor box - Initial");
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - Initial");
GLStateManager.glPushAttrib(GL11.GL_SCISSOR_BIT);
GLStateManager.enableScissorTest();
GLStateManager.glScissor(boxB[0], boxB[1], boxB[2], boxB[3]);
GLStateManager.glPopAttrib();
verifyState(GL11.GL_SCISSOR_BOX, boxA, "Scissor box - GL_SCISSOR_BIT member, must restore");
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - GL_SCISSOR_BIT member, must restore");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.enableScissorTest();
GLStateManager.glScissor(boxB[0], boxB[1], boxB[2], boxB[3]);
GLStateManager.glPopAttrib();
verifyState(GL11.GL_SCISSOR_BOX, boxB, "Scissor box - Not a GL_ENABLE_BIT member, must stay changed");
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - GL_ENABLE_BIT member, must restore");
} finally {
GLStateManager.disableScissorTest();
GLStateManager.glScissor(0, 0, 800, 600);
}
}
@Test
void rasterizerDiscardIsNotAnEnableBitMember() {
try {
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - Initial");
GLStateManager.glEnable(GL30.GL_RASTERIZER_DISCARD);
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, true, "RasterizerDiscard - After enable");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - After disable inside GL_ENABLE_BIT");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - Not a GL_ENABLE_BIT member, must stay disabled");
} finally {
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
}
}
@Test
void clearDepthIsClamped() {
try {
GLStateManager.glClearDepth(2.0);
verifyState(GL11.GL_DEPTH_CLEAR_VALUE, 1.0f, "Depth clear value - glClearDepth(2.0) must clamp to 1.0");
} finally {
GLStateManager.glClearDepth(1.0);
}
}
@Test
void colorBufferBitPopDoesNotRestoreClipPlaneButMaskStaysFaithful() {
try {
@@ -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);
}
}
@@ -45,7 +45,7 @@ public class GLSM_ValueStackCow_GLTest {
"glAlphaFunc", "glBlendFunc", "tryBlendFuncSeparate", "glBlendEquation", "glBlendEquationSeparate",
"glBlendColor", "glColorMask", "glClearColor", "glDrawBuffer", "glLogicOp",
"glDepthFunc", "glDepthMask", "glClearDepth", "glStencilFunc", "glStencilOp",
"glStencilMask", "glClearStencil", "glViewport", "glDepthRange", "glLineWidth",
"glStencilMask", "glClearStencil", "glViewport", "glScissor", "glDepthRange", "glLineWidth",
"glLineStipple", "glPointSize", "glPolygonMode", "glCullFace", "glFrontFace",
"glPolygonOffset", "fogColor", "glFogf", "glFogi", "glLightf",
"glLighti", "glLight", "glLightModelf", "glLightModeli", "glLightModel",
@@ -74,6 +74,7 @@ public class GLSM_ValueStackCow_GLTest {
() -> GLStateManager.glStencilMask(0x0F),
() -> GLStateManager.glClearStencil(9),
() -> GLStateManager.glViewport(1, 2, 3, 4),
() -> GLStateManager.glScissor(1, 2, 3, 4),
() -> GLStateManager.glDepthRange(0.2, 0.8),
() -> GLStateManager.glLineWidth(3.0f),
() -> GLStateManager.glLineStipple(3, (short) 0x00FF),
@@ -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;
@@ -162,6 +163,7 @@ public class SDLGPURenderBackend extends RenderBackend {
private boolean droppedSsboWriteWarned;
private boolean depthClearSuppressedWarned;
private boolean stencilClearSuppressedWarned;
private boolean partialClearFallbackWarned;
private final DeferredCopyOp deferredCopyOp = new DeferredCopyOp();
private TransferThread transferThread;
@@ -554,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;
@@ -582,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();
@@ -814,6 +834,7 @@ public class SDLGPURenderBackend extends RenderBackend {
st.scissorDirty = true;
}
case GL31.GL_PRIMITIVE_RESTART -> cs.primitiveRestartEnabled = on;
case GL30.GL_RASTERIZER_DISCARD -> cs.rasterizerDiscard = on;
}
}
@@ -954,28 +975,78 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void clearStencil(int s) { s().stencilClearValue = s & 0xFF; }
@Override public void clear(int mask) {
final ContextState cs = s();
final ContextState st = s();
if (!frameManager.isFrameActive()) return;
final boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
boolean wantDepth = (mask & GL11.GL_DEPTH_BUFFER_BIT) != 0;
boolean wantStencil = (mask & GL11.GL_STENCIL_BUFFER_BIT) != 0;
if (wantDepth && !cs.pipeline.depthWriteEnabled) {
if (wantDepth && !st.pipeline.depthWriteEnabled) {
wantDepth = false;
if (!depthClearSuppressedWarned) {
depthClearSuppressedWarned = true;
LOG.warn("clear: GL_DEPTH_BUFFER_BIT requested with glDepthMask(false); clear suppressed per GL semantics");
}
}
if (wantStencil && cs.pipeline.stencilWriteMask == 0) {
if (wantStencil && st.pipeline.stencilWriteMask == 0) {
wantStencil = false;
if (!stencilClearSuppressedWarned) {
stencilClearSuppressedWarned = true;
LOG.warn("clear: GL_STENCIL_BUFFER_BIT requested with a zero glStencilMask; clear suppressed per GL semantics");
}
}
if (wantColor && (st.pipeline.colorWriteMask & 0xF) == 0) {
wantColor = false;
}
if (!wantColor && !wantDepth && !wantStencil) return;
final ContextState st = cs;
final FboState fbo;
if (st.boundFboId == 0) {
fbo = null;
} else {
fbo = resourceManager.getFbo(st.boundFboId);
if (fbo == null) return;
if (fbo.targetsDirty) fbo.recomputeTargets();
if (fbo.cachedFormatsDirty && fbo.hasAnyColor) fboClearTracker.updatePipelineCacheColorFormats(st, fbo);
}
final AttachmentClear.Target target = fbo != null ? attachmentClear.forFbo(fbo) : attachmentClear.forFbo0(cachedSwapchainFormatArray, frameManager.getFbo0Width(), frameManager.getFbo0Height());
int skipSlots = 0;
boolean colorPartial = false;
if (wantColor) {
final int colorWriteMask = st.pipeline.colorWriteMask;
final int[] formats = target.colorFormats;
final int slotCount = formats != null ? formats.length : 0;
boolean anyLive = false;
for (int i = 0; i < slotCount; i++) {
final boolean present;
if (fbo != null) {
final int db = target.drawBuffers[i];
present = db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS && fbo.colorTextures[db] != 0;
} else {
present = i == 0;
}
if (!present) continue;
final int fm = PixelOps.colorComponentMask(formats[i]);
final int m = colorWriteMask & fm;
if (m == 0) {
skipSlots |= 1 << i;
} else {
anyLive = true;
if (m != fm) colorPartial = true;
}
}
wantColor = anyLive;
}
if (target.width > 0 && target.height > 0) {
final int coverage = attachmentClear.coverage(st, target);
if (coverage == AttachmentClear.EMPTY) return;
final boolean partial = coverage == AttachmentClear.PARTIAL || colorPartial || (wantStencil && (st.pipeline.stencilWriteMask & 0xFF) != 0xFF);
if (partial && !(wantColor && attachmentClear.hasIntegerColorTarget(target)) && clearPartial(st, fbo, target, wantColor, wantDepth, wantStencil)) {
return;
}
}
// FBO0 path: just record pending state; ensureFbo0RenderPass consumes it lazily.
if (st.boundFboId == 0) {
@@ -998,15 +1069,9 @@ public class SDLGPURenderBackend extends RenderBackend {
return;
}
final FboState fbo = resourceManager.getFbo(st.boundFboId);
if (fbo == null) return;
if (!wantColor && (wantDepth || wantStencil)) {
final FrameState f = frameManager.frame();
if (attachmentClear.eligible(st, f, fbo, wantDepth, wantStencil)
&& attachmentClear.clearInPass(st, f, fbo, wantDepth, wantStencil)) {
return;
}
final FrameState f = frameManager.frame();
if (!wantColor && !SystemProperties.SDL_DISABLE_IN_PASS_CLEAR && f.renderPass != 0 && f.activeLayoutHash == fbo.structuralLayoutHash && !FBOClearTracker.fboHasPendingClear(st, fbo) && clearPartial(st, fbo, target, false, wantDepth, wantStencil)) {
return;
}
boolean affectsActivePass = false;
@@ -1015,7 +1080,10 @@ public class SDLGPURenderBackend extends RenderBackend {
final long activeDepth = passActive ? frameManager.getCurrentDepthTarget() : 0;
if (wantColor) {
for (int db : fbo.drawBuffers) {
final int[] drawBuffers = fbo.drawBuffers;
for (int i = 0; i < drawBuffers.length; i++) {
if ((skipSlots & (1 << i)) != 0) continue;
final int db = drawBuffers[i];
if (db < 0 || db >= ContextState.MAX_COLOR_ATTACHMENTS) continue;
final long tex = fbo.colorTextures[db];
if (tex == 0) continue;
@@ -1024,11 +1092,11 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
if (wantDepth && fbo.depthTexture != 0) {
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, cs.depthClearValue);
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, st.depthClearValue);
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
}
if (wantStencil && fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat)) {
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, cs.stencilClearValue);
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, st.stencilClearValue);
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
}
@@ -1037,6 +1105,33 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
private boolean clearPartial(ContextState st, FboState fbo, AttachmentClear.Target t, boolean color, boolean depth, boolean stencil) {
final FrameState f = frameManager.frame();
pipelineApplier.ensureRenderPass(st, f);
final long expectedLayout = fbo == null ? FrameManager.FBO0_LAYOUT_HASH : fbo.structuralLayoutHash;
if (f.renderPass == 0 || f.activeLayoutHash != expectedLayout) {
return warnPartialClearFallback();
}
if (fbo == null) {
t.depthTexture = f.currentDepthTarget;
t.depthFormat = t.depthTexture != 0 ? resourceManager.getSwapchainDepthStencilFormat() : 0;
}
color &= t.colorFormats != null && (fbo == null || fbo.hasAnyColor);
depth &= t.depthTexture != 0;
stencil &= PixelOps.isDepthStencilFormat(t.depthFormat);
if (!color && !depth && !stencil) return true;
if (attachmentClear.clearInPass(st, f, t, color, depth, stencil)) return true;
return warnPartialClearFallback();
}
private boolean warnPartialClearFallback() {
if (!partialClearFallbackWarned) {
partialClearFallbackWarned = true;
LOG.error("clear: in-pass partial clear unavailable; falling back to the legacy clear path");
}
return false;
}
@Override public void lineWidth(float width) { /* no-op */ }
@Override public void pointSize(float size) { /* no-op */ }
@Override public void logicOp(int opcode) { /* no-op */ }
@@ -1051,9 +1146,10 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void drawArrays(int mode, int first, int count) {
if (count <= 0) return;
final ContextState st = s();
if (st.rasterizerDiscard) return;
final FrameState f = frameManager.frame();
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
final ContextState st = s();
if (mode == GL11.GL_TRIANGLE_FAN && count >= 3) {
if (!drawDispatch.drawTriangleFanAsTriangleList(st, first, count)) { f.droppedDrawsThisFrame++; return; }
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysFan", mode, count, 0, first, 0);
@@ -1096,6 +1192,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private long prepareIndexedDrawBind(ContextState st, int mode, int type, String opName) {
if (st.rasterizerDiscard) return 0;
if (!drawDispatch.prepareIndexedDraw(st, mode, type)) return 0;
final int ebo = st.currentVao.elementBuffer;
final long eboHandle = resourceManager.getBufferHandle(ebo);
@@ -1648,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;
@@ -1693,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;
@@ -1774,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;
@@ -1797,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);
@@ -1833,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) {
@@ -1868,6 +1969,7 @@ public class SDLGPURenderBackend extends RenderBackend {
srcWidth = meta != null ? meta.width() : fbo.width;
srcHeight = meta != null ? meta.height() : fbo.height;
srcY = y;
if (frameManager.isFrameActive()) fboClearTracker.materializePendingClearForTexture(cs, texHandle);
}
if (texHandle == 0 || srcWidth <= 0 || srcHeight <= 0) return;
@@ -1968,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);
}
@@ -3641,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);
@@ -3659,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);
}
@@ -3747,6 +3853,7 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL11.GL_SCISSOR_TEST -> cs.scissorEnabled;
case GL11.GL_STENCIL_TEST -> cs.pipeline.stencilTestEnabled;
case GL11.GL_DEPTH_WRITEMASK -> cs.pipeline.depthWriteEnabled;
case GL30.GL_RASTERIZER_DISCARD -> cs.rasterizerDiscard;
default -> false;
};
}
@@ -4024,8 +4131,9 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
if (count <= 0 || primcount <= 0) return;
if (!frameManager.isFrameActive()) return;
final ContextState st = s();
if (st.rasterizerDiscard) return;
if (!frameManager.isFrameActive()) return;
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
pipelineApplier.ensureRenderPass(st);
if (!frameManager.isRenderPassActive()) return;
@@ -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;
@@ -113,6 +114,7 @@ public final class ContextState {
public float viewportDepthFar = 1.0f;
public int scissorX, scissorY, scissorW, scissorH;
public boolean scissorEnabled;
public boolean rasterizerDiscard;
public int activeTextureUnit;
public int boundProgram;
public int autoPushedProgram;
@@ -327,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) {
@@ -1,10 +1,9 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
@@ -23,7 +22,10 @@ import org.lwjgl.sdl.SDL_GPURasterizerState;
import org.lwjgl.sdl.SDL_GPUVertexInputState;
import org.lwjgl.sdl.SDL_GPUViewport;
import org.lwjgl.sdl.SDL_Rect;
import org.lwjgl.system.MemoryStack;
import org.taumc.glsl.Transformer;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.function.LongSupplier;
@@ -38,15 +40,33 @@ public final class AttachmentClear {
private static final String FRAGMENT_SOURCE = "angelica:sdlgpu/clear_attachment.fsh";
private static final long KEY_SEED = 0x5B1D2A7C4E9F3061L;
private static final int CACHE_SLOTS = 4;
private static final int MAX_COLOR_TARGETS = ContextState.MAX_COLOR_ATTACHMENTS;
public static final int EMPTY = 0;
public static final int PARTIAL = 1;
public static final int FULL = 2;
public static final class Target {
public int[] colorFormats;
public int[] drawBuffers;
public int depthFormat;
public long depthTexture;
public int width;
public int height;
public boolean flipY;
}
private final FrameManager frameManager;
private final PipelineStore pipelineStore;
private final ShaderManager shaderManager;
private int program;
private final Target fboTarget = new Target();
private final Target fbo0Target = new Target();
private int vertexShaderId;
private long sdlVertexShader;
private long sdlFragmentShader;
private boolean failed;
private final long[] sdlFragmentShaders = new long[MAX_COLOR_TARGETS + 1];
private final boolean[] fragmentFailed = new boolean[MAX_COLOR_TARGETS + 1];
private final long[] cacheKeys = new long[CACHE_SLOTS];
private final long[] cachePipelines = new long[CACHE_SLOTS];
@@ -55,12 +75,12 @@ public final class AttachmentClear {
private final int[] scissorRect = new int[4];
private final Builder builder = new Builder();
private int[] buildColorFormats;
private int buildColorCount;
private int buildDepthFormat;
private Target buildTarget;
private int buildColorTargets;
private boolean buildDepthFlag;
private boolean buildStencilFlag;
private int buildWriteMask;
private int buildStencilMask;
private int buildColorMask;
public AttachmentClear(FrameManager frameManager, PipelineStore pipelineStore, ShaderManager shaderManager) {
this.frameManager = frameManager;
@@ -72,40 +92,69 @@ public final class AttachmentClear {
@Override public long getAsLong() { return createPipeline(); }
}
public boolean eligible(ContextState st, FrameState f, FboState fbo, boolean wantDepth, boolean wantStencil) {
if (SystemProperties.SDL_DISABLE_IN_PASS_CLEAR) return false;
if (st.boundFboId == 0) return false;
if (f.renderPass == 0) return false;
if (fbo.depthTexture == 0) return false;
if (f.currentDepthTarget != fbo.depthTexture) return false;
if (f.activeLayoutHash != fbo.structuralLayoutHash || f.currentColorTarget != fbo.primaryTarget) return false;
if (FBOClearTracker.fboHasPendingClear(st, fbo)) return false;
if (wantStencil && !PixelOps.isDepthStencilFormat(fbo.depthFormat)) return false;
if (fbo.width <= 0 || fbo.height <= 0) return false;
return computeScissor(st, fbo);
public Target forFbo(FboState fbo) {
final Target t = fboTarget;
t.colorFormats = fbo.cachedColorFormats;
t.drawBuffers = fbo.drawBuffers;
t.depthFormat = fbo.depthTexture != 0 ? fbo.depthFormat : 0;
t.depthTexture = fbo.depthTexture;
t.width = fbo.width;
t.height = fbo.height;
t.flipY = false;
return t;
}
private boolean computeScissor(ContextState st, FboState fbo) {
public Target forFbo0(int[] swapchainFormats, int w, int h) {
final Target t = fbo0Target;
t.colorFormats = swapchainFormats;
t.drawBuffers = null;
t.depthFormat = 0;
t.depthTexture = 0;
t.width = w;
t.height = h;
t.flipY = true;
return t;
}
static boolean enabled(Target t, int i) {
return t.drawBuffers == null || t.drawBuffers[i] >= 0;
}
public boolean hasIntegerColorTarget(Target t) {
final int[] formats = t.colorFormats;
if (formats == null) return false;
for (int i = 0; i < formats.length; i++) {
if (enabled(t, i) && PixelOps.isSdlFormatInteger(formats[i])) return true;
}
return false;
}
public int coverage(ContextState st, Target t) {
final int sx, sy, sw, sh;
if (st.scissorEnabled) {
sx = st.scissorX;
sy = st.scissorY;
sy = t.flipY ? t.height - st.scissorY - st.scissorH : st.scissorY;
sw = st.scissorW;
sh = st.scissorH;
} else {
sx = 0;
sy = 0;
sw = fbo.width;
sh = fbo.height;
sw = t.width;
sh = t.height;
}
return ScissorClamp.clamp(sx, sy, sw, sh, fbo.width, fbo.height, scissorRect);
if (!ScissorClamp.clamp(sx, sy, sw, sh, t.width, t.height, scissorRect)) return EMPTY;
if (scissorRect[ScissorClamp.X] == 0 && scissorRect[ScissorClamp.Y] == 0
&& scissorRect[ScissorClamp.W] == t.width && scissorRect[ScissorClamp.H] == t.height) return FULL;
return PARTIAL;
}
public boolean clearInPass(ContextState st, FrameState f, FboState fbo, boolean depth, boolean stencil) {
final int writeMask = stencil ? (st.pipeline.stencilWriteMask & 0xFF) : 0;
final long pipeline = getOrCreatePipeline(fbo, depth, stencil, writeMask);
public boolean clearInPass(ContextState st, FrameState f, Target t, boolean color, boolean depth, boolean stencil) {
final int colorTargets = color ? t.colorFormats.length : 0;
final int stencilMask = stencil ? (st.pipeline.stencilWriteMask & 0xFF) : 0;
final int colorMask = color ? st.pipeline.colorWriteMask : 0;
final long pipeline = getOrCreatePipeline(t, colorTargets, depth, stencil, stencilMask, colorMask);
if (pipeline == 0) return false;
if (!computeScissor(st, fbo)) return false;
if (coverage(st, t) == EMPTY) return false;
final long rp = f.renderPass;
SDL_BindGPUGraphicsPipeline(rp, pipeline);
@@ -114,8 +163,8 @@ public final class AttachmentClear {
final long vpAddr = st.cachedViewport.address();
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.X, 0f);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.Y, 0f);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.W, fbo.width);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.H, fbo.height);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.W, t.width);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.H, t.height);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.MIN_DEPTH, clearDepth);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.MAX_DEPTH, clearDepth);
SDL_SetGPUViewport(rp, st.cachedViewport);
@@ -131,6 +180,18 @@ public final class AttachmentClear {
SDL_SetGPUStencilReference(rp, (byte) st.stencilClearValue);
}
if (colorTargets > 0) {
try (MemoryStack stack = stackPush()) {
final ByteBuffer clearColor = stack.malloc(16);
clearColor.putFloat(0, st.clearR);
clearColor.putFloat(4, st.clearG);
clearColor.putFloat(8, st.clearB);
clearColor.putFloat(12, st.clearA);
SDL_PushGPUFragmentUniformData(frameManager.getCommandBuffer(f), 0, clearColor);
}
st.lastPushedProgramFs = -1;
}
SDL_DrawGPUPrimitives(rp, 3, 1, 0, 0);
st.lastBoundPipeline = 0;
@@ -139,34 +200,40 @@ public final class AttachmentClear {
st.lastAppliedStencilRef = Integer.MIN_VALUE;
st.lastAppliedVboBindCb = 0;
if (depth) st.clearedTexturesThisFrame.add(fbo.depthTexture);
if (stencil) st.clearedStencilTexturesThisFrame.add(fbo.depthTexture);
if (t.depthTexture != 0) {
if (depth) st.clearedTexturesThisFrame.add(t.depthTexture);
if (stencil) st.clearedStencilTexturesThisFrame.add(t.depthTexture);
}
frameManager.noteInPassClear();
return true;
}
long getOrCreatePipeline(FboState fbo, boolean depthFlag, boolean stencilFlag, int writeMask) {
if (!ensureShaders()) return 0;
long getOrCreatePipeline(Target t, int colorTargets, boolean depth, boolean stencil, int stencilMask, int colorMask) {
if (!ensureShaders(colorTargets)) return 0;
final int[] formats = fbo.cachedColorFormats;
final int[] formats = t.colorFormats;
final int count = formats != null ? formats.length : 0;
long key = KEY_SEED;
key = Hashing.fmix64(key, count);
for (int i = 0; i < count; i++) key = Hashing.fmix64(key, formats[i]);
key = Hashing.fmix64(key, fbo.depthFormat);
key = Hashing.fmix64(key, (depthFlag ? 1 : 0) | ((stencilFlag ? 1 : 0) << 1) | (writeMask << 8));
for (int i = 0; i < count; i++) {
key = Hashing.fmix64(key, formats[i]);
key = Hashing.fmix64(key, enabled(t, i) ? 1 : 0);
}
key = Hashing.fmix64(key, t.depthFormat);
key = Hashing.fmix64(key, colorTargets);
key = Hashing.fmix64(key, (depth ? 1 : 0) | ((stencil ? 1 : 0) << 1) | (stencilMask << 8) | (colorMask << 16));
for (int i = 0; i < CACHE_SLOTS; i++) {
if (cacheKeys[i] == key && cachePipelines[i] != 0) return cachePipelines[i];
}
buildColorFormats = formats;
buildColorCount = count;
buildDepthFormat = fbo.depthFormat;
buildDepthFlag = depthFlag;
buildStencilFlag = stencilFlag;
buildWriteMask = writeMask;
buildTarget = t;
buildColorTargets = colorTargets;
buildDepthFlag = depth;
buildStencilFlag = stencil;
buildStencilMask = stencilMask;
buildColorMask = colorMask;
final long pipeline = pipelineStore.getOrBuild(key, builder);
if (pipeline != 0) {
cacheKeys[cacheNext] = key;
@@ -178,24 +245,27 @@ public final class AttachmentClear {
private long createPipeline() {
try (var stack = stackPush()) {
final int numColorTargets = buildColorCount;
final Target t = buildTarget;
final int numColorTargets = t.colorFormats != null ? t.colorFormats.length : 0;
final SDL_GPUColorTargetDescription.Buffer colorDesc = SDL_GPUColorTargetDescription.calloc(numColorTargets, stack);
for (int i = 0; i < numColorTargets; i++) {
final var desc = colorDesc.get(i);
desc.format(buildColorFormats[i]);
desc.format(t.colorFormats[i]);
final int mask = (buildColorTargets > 0 && enabled(t, i)) ? buildColorMask : 0;
desc.blend_state()
.enable_blend(false)
.color_write_mask((byte) 0)
.color_write_mask((byte) mask)
.enable_color_write_mask(true);
}
final boolean hasDepthTarget = t.depthFormat != 0;
final SDL_GPUDepthStencilState depthStencil = SDL_GPUDepthStencilState.calloc(stack)
.enable_depth_test(buildDepthFlag)
.enable_depth_write(buildDepthFlag)
.compare_op(SDL_GPU_COMPAREOP_ALWAYS)
.enable_stencil_test(buildStencilFlag)
.compare_mask((byte) 0xFF)
.write_mask((byte) buildWriteMask);
.write_mask((byte) buildStencilMask);
if (buildStencilFlag) {
depthStencil.front_stencil_state()
.fail_op(SDL_GPU_STENCILOP_REPLACE)
@@ -220,14 +290,14 @@ public final class AttachmentClear {
final SDL_GPUGraphicsPipelineTargetInfo targetInfo = SDL_GPUGraphicsPipelineTargetInfo.calloc(stack)
.num_color_targets(numColorTargets)
.color_target_descriptions(colorDesc)
.depth_stencil_format(buildDepthFormat)
.has_depth_stencil_target(true);
.depth_stencil_format(t.depthFormat)
.has_depth_stencil_target(hasDepthTarget);
final SDL_GPUVertexInputState vertexInput = SDL_GPUVertexInputState.calloc(stack);
final SDL_GPUGraphicsPipelineCreateInfo ci = SDL_GPUGraphicsPipelineCreateInfo.calloc(stack)
.vertex_shader(sdlVertexShader)
.fragment_shader(sdlFragmentShader)
.fragment_shader(sdlFragmentShaders[buildColorTargets])
.primitive_type(SDL_GPU_PRIMITIVETYPE_TRIANGLELIST)
.rasterizer_state(rasterizer)
.multisample_state(multisample)
@@ -244,48 +314,78 @@ public final class AttachmentClear {
}
}
private boolean ensureShaders() {
if (program != 0) return true;
if (failed) return false;
failed = true;
private boolean ensureShaders(int colorTargets) {
if (colorTargets < 0 || colorTargets > MAX_COLOR_TARGETS) return false;
if (sdlFragmentShaders[colorTargets] != 0) return true;
if (fragmentFailed[colorTargets]) return false;
fragmentFailed[colorTargets] = true;
if (vertexShaderId == 0) {
vertexShaderId = shaderManager.createShader(GL20.GL_VERTEX_SHADER);
shaderManager.shaderSource(vertexShaderId, ShaderLoader.getShaderSource(VERTEX_SOURCE));
shaderManager.compileShader(vertexShaderId);
}
final String baseSource = ShaderLoader.getShaderSource(FRAGMENT_SOURCE);
final String fragmentSource = colorTargets == 0 ? baseSource : withColorOutputs(baseSource, colorTargets);
final int vs = shaderManager.createShader(GL20.GL_VERTEX_SHADER);
shaderManager.shaderSource(vs, ShaderLoader.getShaderSource(VERTEX_SOURCE));
shaderManager.compileShader(vs);
final int fs = shaderManager.createShader(GL20.GL_FRAGMENT_SHADER);
shaderManager.shaderSource(fs, ShaderLoader.getShaderSource(FRAGMENT_SOURCE));
shaderManager.shaderSource(fs, fragmentSource);
shaderManager.compileShader(fs);
final int prog = shaderManager.createProgram();
shaderManager.attachShader(prog, vs);
shaderManager.attachShader(prog, vertexShaderId);
shaderManager.attachShader(prog, fs);
shaderManager.linkProgram(prog);
final ShaderManager.ProgramObject po = shaderManager.getProgram(prog);
if (po == null || !po.linked) {
LOG.error("AttachmentClear: clear_attachment program failed to link: {}", po == null ? "no program object" : po.infoLog);
LOG.error("AttachmentClear: clear_attachment program (COLOR_TARGETS={}) failed to link: {}", colorTargets, po == null ? "no program object" : po.infoLog);
return false;
}
if (colorTargets > 0 && po.fragmentUboSize != 16) {
LOG.error("AttachmentClear: clear_attachment fragment UBO size {} != 16 for COLOR_TARGETS={}", po.fragmentUboSize, colorTargets);
return false;
}
po.sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
po.sdlFragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
if (po.sdlVertexShader == 0 || po.sdlFragmentShader == 0) {
LOG.error("AttachmentClear: clear_attachment SDL shader creation failed");
if (sdlVertexShader == 0) {
sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
if (sdlVertexShader == 0) {
LOG.error("AttachmentClear: clear_attachment vertex SDL shader creation failed");
return false;
}
}
final long fragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
if (fragmentShader == 0) {
LOG.error("AttachmentClear: clear_attachment fragment SDL shader creation failed (COLOR_TARGETS={})", colorTargets);
return false;
}
program = prog;
sdlVertexShader = po.sdlVertexShader;
sdlFragmentShader = po.sdlFragmentShader;
failed = false;
sdlFragmentShaders[colorTargets] = fragmentShader;
fragmentFailed[colorTargets] = false;
return true;
}
private static String withColorOutputs(String source, int count) {
final GlslTransformUtils.QuietParse parsed = GlslTransformUtils.parseBothQuiet(source);
final Transformer transformer = new Transformer(parsed.full());
transformer.injectVariable("uniform vec4 clearColor;");
for (int i = 0; i < count; i++) {
transformer.injectVariable("layout(location = " + i + ") out vec4 angelica_ClearColor" + i + ";");
transformer.appendMain("angelica_ClearColor" + i + " = clearColor;");
}
final String header = GlslTransformUtils.getFormattedShader(parsed.pre(), "").trim();
final StringBuilder out = new StringBuilder();
transformer.mutateTree(tree -> out.append(GlslTransformUtils.getFormattedShader(tree, header)));
return out.toString();
}
public void shutdown() {
program = 0;
vertexShaderId = 0;
sdlVertexShader = 0;
sdlFragmentShader = 0;
failed = false;
Arrays.fill(sdlFragmentShaders, 0L);
Arrays.fill(fragmentFailed, false);
Arrays.fill(cacheKeys, 0L);
Arrays.fill(cachePipelines, 0L);
cacheNext = 0;
@@ -74,6 +74,14 @@ public final class PixelOps {
};
}
public static int colorComponentMask(int sdlFormat) {
int mask = 0;
for (int c = 0; c < 4; c++) {
if (colorChannelBits(sdlFormat, c) > 0) mask |= 1 << c;
}
return mask;
}
private static int uniformChannelBits(int sdlFormat) {
return switch (sdlFormat) {
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8_SNORM,
@@ -174,6 +182,16 @@ public final class PixelOps {
};
}
public static boolean isSdlFormatInteger(int sdlFormat) {
if (isSdlFormatUnsignedInteger(sdlFormat)) return true;
return switch (sdlFormat) {
case SDL_GPU_TEXTUREFORMAT_R8_INT, SDL_GPU_TEXTUREFORMAT_R8G8_INT, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT,
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R16G16_INT, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT,
SDL_GPU_TEXTUREFORMAT_R32_INT, SDL_GPU_TEXTUREFORMAT_R32G32_INT, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT -> true;
default -> false;
};
}
public static ByteBuffer expandToRGBA(int format, ByteBuffer src, int width, int height) {
if (src == null) return null;
final int pixelCount = width * height;
@@ -10,9 +10,14 @@ import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import sun.misc.Unsafe;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -67,6 +72,12 @@ class GlsmSdlAttachmentClearTest {
return r > 200 && g < 64;
}
private static boolean isGreen(int argb) {
final int r = (argb >> 16) & 0xFF;
final int g = (argb >> 8) & 0xFF;
return g > 200 && r < 64;
}
private static boolean depthEquals(float depthValue) {
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
@@ -358,4 +369,205 @@ class GlsmSdlAttachmentClearTest {
assertTrue(Math.abs(g - 102) <= 2, "green must be untouched: " + g);
assertTrue(Math.abs(b - 153) <= 2, "blue must be untouched: " + b);
}
@Test
void scissoredColorAndDepthClearStaysInTheRect() {
GlsmSdlHeadlessRig.bindTarget();
GLStateManager.glDepthMask(true);
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClearDepth(0.9);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 1.0f);
final FrameManager.FrameState f = frame();
final long passBefore = f.renderPass;
assertTrue(passBefore != 0, "expected an active render pass before the scissored clear");
final int inPassBefore = f.inPassClearsThisFrame;
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClearDepth(0.0);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
assertEquals(passBefore, f.renderPass, "the scissored clear must not break the active render pass");
assertEquals(inPassBefore + 1, f.inPassClearsThisFrame, "the scissored clear must take the in-pass path");
final int probeSize = 4;
final int leftProbeX = SIZE / 2 - probeSize * 2;
final int rightProbeX = SIZE / 2 + probeSize;
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
GLStateManager.glDepthMask(false);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(leftProbeX, 0, probeSize, SIZE);
drawFullQuadAtZ(0.0f * 2.0f - 1.0f, 0.0f, 1.0f, 0.0f);
GLStateManager.glScissor(rightProbeX, 0, probeSize, SIZE);
drawFullQuadAtZ(0.9f * 2.0f - 1.0f, 0.0f, 1.0f, 0.0f);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 8, SIZE / 2)), "inside the scissor rect must be the clear color");
assertFalse(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE - SIZE / 8, SIZE / 2)), "outside the scissor rect must keep the prior blue quad");
assertTrue(isGreen(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, leftProbeX + probeSize / 2, SIZE / 2)), "inside the scissor rect the depth must have been cleared to 0");
assertTrue(isGreen(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, rightProbeX + probeSize / 2, SIZE / 2)), "outside the scissor rect the prior depth must be untouched");
}
@Test
void scissoredClearAfterPendingFullClear() {
GlsmSdlHeadlessRig.bindTarget();
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
GLStateManager.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glScissor(0, 0, 0, SIZE);
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
assertTrue(isGreen(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 4, SIZE / 2)), "the scissored clear must have painted the left half green");
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2 + SIZE / 4, SIZE / 2)), "the empty-scissor clear must not have touched the right half");
}
@Test
void colorMaskIsHonoredByClear() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.solidQuad(0.2f, 0.4f, 0.6f);
GLStateManager.glColorMask(true, false, false, true);
GLStateManager.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glColorMask(true, true, true, true);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
final int argb = GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
final int r = (argb >> 16) & 0xFF;
final int g = (argb >> 8) & 0xFF;
final int b = argb & 0xFF;
assertEquals(255, r, "red must be written: colorMask allowed it");
assertTrue(Math.abs(g - 102) <= 2, "green must be untouched by the masked clear: " + g);
assertTrue(Math.abs(b - 153) <= 2, "blue must be untouched by the masked clear: " + b);
}
@Test
void scissoredClearWritesEveryDrawBuffer() {
final int texA = createColorTexture();
final int texB = createColorTexture();
final int mrtFbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, mrtFbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texA, 0);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT1, GL11.GL_TEXTURE_2D, texB, 0);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glDrawBuffers(IntBuffer.wrap(new int[]{ GL30.GL_COLOR_ATTACHMENT0, GL30.GL_COLOR_ATTACHMENT1 }));
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
final int readFbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, readFbo);
assertLeftHalfRed(texA, "texture A");
assertLeftHalfRed(texB, "texture B");
GLStateManager.glDeleteFramebuffers(readFbo);
GLStateManager.glDeleteFramebuffers(mrtFbo);
GLStateManager.glDeleteTextures(texA);
GLStateManager.glDeleteTextures(texB);
GlsmSdlHeadlessRig.bindTarget();
}
private static int createColorTexture() {
return createColorTexture(GL11.GL_RGBA8, GL11.GL_RGBA);
}
private static int createColorTexture(int internalFormat, int format) {
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, internalFormat, SIZE, SIZE, 0, format, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
return tex;
}
private static void assertLeftHalfRed(int texture, String label) {
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, 0);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 4, SIZE / 2)), label + " left half must be cleared red");
assertFalse(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2 + SIZE / 4, SIZE / 2)), label + " right half must keep the base clear");
}
@Test
void rasterizerDiscardSkipsClearsAndDraws() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
GLStateManager.glEnable(GL30.GL_RASTERIZER_DISCARD);
GLStateManager.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 1.0f);
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2)), "rasterizer discard must skip both the clear and the draw");
}
@Test
void clearIsIgnoredOutsideRenderMode() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
final FloatBuffer feedbackBuffer = BufferUtils.createFloatBuffer(16);
GLStateManager.glFeedbackBuffer(GL11.GL_3D, feedbackBuffer);
GLStateManager.glRenderMode(GL11.GL_FEEDBACK);
GLStateManager.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glRenderMode(GL11.GL_RENDER);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2)), "a clear issued outside GL_RENDER mode must be ignored");
}
@Test
void colorMaskCoveringEveryPresentChannelIsAFullClear() {
final int tex = createColorTexture(GL30.GL_R8, GL11.GL_RED);
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glClearColor(0.2f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GlsmSdlHeadlessRig.solidQuad(0.4f, 0.0f, 0.0f);
final FrameManager.FrameState f = frame();
final int inPassBefore = f.inPassClearsThisFrame;
GLStateManager.glColorMask(true, false, false, false);
GLStateManager.glClearColor(0.6f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glColorMask(true, true, true, true);
assertEquals(inPassBefore, f.inPassClearsThisFrame, "masking every present channel on a single-red-channel target must take the load-op path, not in-pass");
GLStateManager.glDeleteFramebuffers(fbo);
GLStateManager.glDeleteTextures(tex);
GlsmSdlHeadlessRig.bindTarget();
}
}
@@ -95,6 +95,19 @@ class GlsmSdlFbo0ViewportTest {
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, VIEW, VIEW), BLUE, "above the viewport");
}
@Test
void scissoredClearFlipsYOnFbo0() {
bindFbo0WithFullViewport();
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, FBO0_W, VIEW);
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, FBO0_W, VIEW), RED, "GL scissor y=0 is the bottom rows of the FBO0 texture");
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, FBO0_W, VIEW), BLUE, "the top rows must stay untouched by the flipped scissor");
}
@Test
void scissorLargerThanFbo0StillCoversTheWholeTarget() {
bindFbo0WithFullViewport();
@@ -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,10 +1,6 @@
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.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
@@ -15,10 +11,8 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
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.assertTrue;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
class AttachmentClearTest {
@@ -40,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);
@@ -76,92 +69,33 @@ class AttachmentClearTest {
return fbo;
}
@Test
void eligibleTruthTable() {
final FboState fbo = makeFbo();
final ContextState baseline = new ContextState();
baseline.boundFboId = 1;
final FrameManager.FrameState f = new FrameManager.FrameState();
f.renderPass = 0xBEEFL;
f.currentDepthTarget = fbo.depthTexture;
f.currentColorTarget = fbo.primaryTarget;
f.activeLayoutHash = fbo.structuralLayoutHash;
assertTrue(attachmentClear.eligible(baseline, f, fbo, true, false), "baseline row must be eligible");
final ContextState noFbo = new ContextState();
noFbo.boundFboId = 0;
assertFalse(attachmentClear.eligible(noFbo, f, fbo, true, false), "boundFboId==0 must be ineligible");
final FrameManager.FrameState noPass = new FrameManager.FrameState();
noPass.currentDepthTarget = fbo.depthTexture;
noPass.currentColorTarget = fbo.primaryTarget;
noPass.activeLayoutHash = fbo.structuralLayoutHash;
assertFalse(attachmentClear.eligible(baseline, noPass, fbo, true, false), "renderPass==0 must be ineligible");
final FrameManager.FrameState wrongDepth = new FrameManager.FrameState();
wrongDepth.renderPass = 0xBEEFL;
wrongDepth.currentDepthTarget = fbo.depthTexture + 1;
wrongDepth.currentColorTarget = fbo.primaryTarget;
wrongDepth.activeLayoutHash = fbo.structuralLayoutHash;
assertFalse(attachmentClear.eligible(baseline, wrongDepth, fbo, true, false), "depth target mismatch must be ineligible");
final FrameManager.FrameState wrongLayout = new FrameManager.FrameState();
wrongLayout.renderPass = 0xBEEFL;
wrongLayout.currentDepthTarget = fbo.depthTexture;
wrongLayout.currentColorTarget = fbo.primaryTarget;
wrongLayout.activeLayoutHash = fbo.structuralLayoutHash ^ 1L;
assertFalse(attachmentClear.eligible(baseline, wrongLayout, fbo, true, false), "layout hash mismatch must be ineligible");
final FrameManager.FrameState wrongColor = new FrameManager.FrameState();
wrongColor.renderPass = 0xBEEFL;
wrongColor.currentDepthTarget = fbo.depthTexture;
wrongColor.currentColorTarget = fbo.primaryTarget + 1;
wrongColor.activeLayoutHash = fbo.structuralLayoutHash;
assertFalse(attachmentClear.eligible(baseline, wrongColor, fbo, true, false), "color target mismatch must be ineligible");
final ContextState pending = new ContextState();
pending.boundFboId = 1;
FBOClearTracker.recordPendingDepthClear(pending, fbo.depthTexture, 1.0f);
assertFalse(attachmentClear.eligible(pending, f, fbo, true, false), "a pending deferred clear must be ineligible");
final FboState depthOnlyFormat = makeFbo();
depthOnlyFormat.depthFormat = SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
assertFalse(attachmentClear.eligible(baseline, f, depthOnlyFormat, false, true), "stencil clear on a non depth-stencil format must be ineligible");
assertTrue(attachmentClear.eligible(baseline, f, depthOnlyFormat, true, false), "depth clear on a depth-only format stays eligible");
final FboState zeroSize = makeFbo();
zeroSize.width = 0;
assertFalse(attachmentClear.eligible(baseline, f, zeroSize, true, false), "zero-size target must be ineligible");
final ContextState scissoredEmpty = new ContextState();
scissoredEmpty.boundFboId = 1;
scissoredEmpty.scissorEnabled = true;
scissoredEmpty.scissorX = SIZE + 10;
scissoredEmpty.scissorY = 0;
scissoredEmpty.scissorW = 4;
scissoredEmpty.scissorH = 4;
assertFalse(attachmentClear.eligible(scissoredEmpty, f, fbo, true, false), "a scissor clamped to empty must be ineligible");
}
@Test
void onePipelinePerLayoutModeAndMask() {
final FboState fbo = makeFbo();
final long p1 = attachmentClear.getOrCreatePipeline(fbo, true, false, 0);
final long p1Again = attachmentClear.getOrCreatePipeline(fbo, true, false, 0);
final long p1 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, true, false, 0, 0);
final long p1Again = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, true, false, 0, 0);
assertTrue(p1 != 0, "pipeline build must succeed");
assertEquals(p1, p1Again, "the same (layout, mode, mask) must reuse the cached pipeline");
final long p2 = attachmentClear.getOrCreatePipeline(fbo, false, true, 0xFF);
final long p2 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, false, true, 0xFF, 0);
assertNotEquals(p1, p2, "a different clear mode must build a different pipeline");
final long p3 = attachmentClear.getOrCreatePipeline(fbo, false, true, 0x0F);
final long p3 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, false, true, 0x0F, 0);
assertNotEquals(p2, p3, "a different stencil write mask must build a different pipeline");
final FboState depthOnlyLayout = makeFbo();
depthOnlyLayout.drawBuffers = new int[0];
depthOnlyLayout.cachedColorFormats = new int[0];
final long p4 = attachmentClear.getOrCreatePipeline(depthOnlyLayout, true, false, 0);
final long p4 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(depthOnlyLayout), 0, true, false, 0, 0);
assertTrue(p4 != 0, "a depth-only layout pipeline must still build");
assertNotEquals(p1, p4, "a different color-target layout must build a different pipeline");
final long pc1 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 1, false, false, 0, 0xF);
final long pc1Again = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 1, false, false, 0, 0xF);
assertTrue(pc1 != 0, "color pipeline build must succeed");
assertEquals(pc1, pc1Again, "the same color mask must reuse the cached pipeline");
final long pc2 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 1, false, false, 0, 0x9);
assertNotEquals(pc1, pc2, "a different color write mask must build a different pipeline");
}
}
@@ -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();
@@ -680,6 +680,7 @@ public enum Mixins implements IMixins {
"gui.MixinGuiSlot",
"glint.MixinRenderBiped",
"glint.MixinEntityLiving_ArmorEnchantCache",
"glint.MixinRenderPlayer",
"optimization.MixinRenderItemFrame",
@@ -0,0 +1,10 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import net.minecraft.item.ItemStack;
/**
* Whether a mob's worn armor piece is enchanted, cached per stack instance.
*/
public interface ArmorEnchantCache {
boolean angelica$isArmorEnchanted(int slot, ItemStack stack);
}
@@ -2,10 +2,13 @@ package com.gtnewhorizons.angelica.mixins.interfaces;
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput;
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
import java.util.concurrent.ConcurrentLinkedDeque;
public interface RenderSectionManagerAccessor {
Long2ReferenceMap<RenderSection> angelica$getSectionByPosition();
ConcurrentLinkedDeque<Runnable> angelica$getAsyncSubmittedTasks();
ConcurrentLinkedDeque<ChunkJobResult<? extends ChunkTaskOutput>> angelica$getBuildResults();
}
@@ -91,6 +91,11 @@ public final class TracyFramePlots {
private static final PlotDelta P_TESR_CACHE_HITS = new PlotDelta("tesr.cacheHits");
private static final PlotDelta P_TESR_CACHE_MISSES = new PlotDelta("tesr.cacheMisses");
private static final PlotDelta P_TESR_MODEL_PARTS = new PlotDelta("tesr.modelParts");
private static final PlotDelta P_ARMOR_BASE_REUSES = new PlotDelta("glint.armorBaseReuses");
private static final PlotDelta P_GLINT_PASS_REUSES = new PlotDelta("glint.passReuses");
private static final PlotDelta P_ARMOR_LOOP_SKIPS = new PlotDelta("glint.armorLoopSkips");
private static final PlotDelta P_ARMOR_SECTION_SKIPS = new PlotDelta("glint.armorSectionSkips");
private static final PlotDelta P_REDRAWN_INSTANCES = new PlotDelta("glint.redrawnInstances");
private static final PlotDelta P_TESR_LIVE_FALLBACKS = new PlotDelta("tesr.liveFallbacks");
private static final PlotDelta P_ITEMS_INSTANCED = new PlotDelta("items.instanced");
private static final PlotDelta P_ITEMS_FALLBACK = new PlotDelta("items.fallback");
@@ -239,6 +244,11 @@ public final class TracyFramePlots {
P_TESR_CACHE_HITS.plot(AngelicaTesrMeshCache.cacheHits);
P_TESR_CACHE_MISSES.plot(AngelicaTesrMeshCache.cacheMisses);
P_TESR_MODEL_PARTS.plot(batcher.statParts());
P_ARMOR_BASE_REUSES.plot(batcher.statArmorBaseReuses());
P_GLINT_PASS_REUSES.plot(batcher.statGlintPassReuses());
P_ARMOR_LOOP_SKIPS.plot(batcher.statArmorLoopSkips());
P_ARMOR_SECTION_SKIPS.plot(batcher.statArmorSectionSkips());
P_REDRAWN_INSTANCES.plot(batcher.statRedrawnInstances());
P_TESR_LIVE_FALLBACKS.plot(batcher.statLiveFallbacks());
P_ITEMS_INSTANCED.plot(DroppedItemInstancer.statInstanced());
P_ITEMS_FALLBACK.plot(DroppedItemInstancer.statFallback());
@@ -243,8 +243,7 @@ public final class ClientProxy extends CommonProxy {
// Register all blocks. Because blockids are unique to a world, this must be done each load
GameData.getBlockRegistry().typeSafeIterable().forEach(o -> {
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, true, true);
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, false, true);
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o);
});
}
@@ -204,9 +204,9 @@ final class CloudLayerRenderer {
final float cellHeightBlocks = layer.cellHeight() * scaleMult;
final float coordinateWidth = layer.coordinateWidth() * scaleMult;
final float cameraY = (float) (viewEntity.lastTickPosY + (viewEntity.posY - viewEntity.lastTickPosY) * partialTicks);
final double cameraCellX = CloudDisc.cellCoordinate(viewEntity.prevPosX + (viewEntity.posX - viewEntity.prevPosX) * partialTicks, coordinateWidth, layer.offsetX());
final double cameraCellZ = CloudDisc.cellCoordinate(viewEntity.prevPosZ + (viewEntity.posZ - viewEntity.prevPosZ) * partialTicks, coordinateWidth, layer.offsetZ());
final float cameraY = (float) (viewEntity.lastTickPosY + (viewEntity.posY - viewEntity.lastTickPosY) * partialTicks + cloudView.offsetY);
final double cameraCellX = CloudDisc.cellCoordinate(viewEntity.prevPosX + (viewEntity.posX - viewEntity.prevPosX) * partialTicks + cloudView.offsetX, coordinateWidth, layer.offsetX());
final double cameraCellZ = CloudDisc.cellCoordinate(viewEntity.prevPosZ + (viewEntity.posZ - viewEntity.prevPosZ) * partialTicks + cloudView.offsetZ, coordinateWidth, layer.offsetZ());
final float cloudBaseRelativeY = layer.height() - cameraY + 0.33F;
final float cellFractionX = (float) (cameraCellX - MathHelper.floor_double(cameraCellX));
@@ -339,15 +339,18 @@ final class CloudLayerRenderer {
final float anchorDriftX = (float) (anchorX - anchorCellX);
final float anchorDriftZ = (float) (anchorZ - anchorCellZ);
// Face wedges are re-bucketed around the current cell; vertex wedges keep their build anchor.
final double cameraDriftX = (faceMeshActive ? 0 : anchorDriftX) + cellFractionX + cloudView.offsetX / cellWidthBlocks;
final double cameraDriftZ = (faceMeshActive ? 0 : anchorDriftZ) + cellFractionZ + cloudView.offsetZ / cellWidthBlocks;
final double cameraDriftX = (faceMeshActive ? 0 : anchorDriftX) + cellFractionX;
final double cameraDriftZ = (faceMeshActive ? 0 : anchorDriftZ) + cellFractionZ;
driftCells = Math.hypot(cameraDriftX, cameraDriftZ);
final double baseFromEye = cloudBaseRelativeY - cloudView.offsetY;
cullingHeightCells = Math.max(Math.abs(baseFromEye), Math.abs(baseFromEye + cellHeightBlocks)) / cellWidthBlocks;
cullingHeightCells = Math.max(Math.abs(cloudBaseRelativeY), Math.abs(cloudBaseRelativeY + cellHeightBlocks)) / cellWidthBlocks;
interiorDriftX = anchorDriftX;
interiorDriftZ = anchorDriftZ;
final float entityToCameraX = cloudView.offsetX / cellWidthBlocks;
final float entityToCameraZ = cloudView.offsetZ / cellWidthBlocks;
final float deckBaseY = cloudBaseRelativeY + cloudView.offsetY;
if (!shadersActive) {
program.bind();
final CloudUniforms uniforms = program.getInterface();
@@ -356,11 +359,11 @@ final class CloudLayerRenderer {
modelView.scale(cellWidthBlocks, 1.0f, cellWidthBlocks);
if (interiorActive) {
modelView.pushMatrix();
modelView.translate(-cellFractionX, cloudBaseRelativeY, -cellFractionZ);
modelView.translate(entityToCameraX - cellFractionX, deckBaseY, entityToCameraZ - cellFractionZ);
GLStateManager.getProjectionMatrix().mul(modelView, mvpInteriorScratch);
modelView.popMatrix();
}
modelView.translate(-(cellFractionX + anchorDriftX), cloudBaseRelativeY, -(cellFractionZ + anchorDriftZ));
modelView.translate(entityToCameraX - (cellFractionX + anchorDriftX), deckBaseY, entityToCameraZ - (cellFractionZ + anchorDriftZ));
modelViewScratch.set(modelView);
GLStateManager.getProjectionMatrix().mul(modelView, mvpScratch);
modelView.popMatrix();
@@ -376,7 +379,7 @@ final class CloudLayerRenderer {
} else {
GLStateManager.glPushMatrix();
GLStateManager.glScalef(cellWidthBlocks, 1.0f, cellWidthBlocks);
GLStateManager.glTranslatef(-(cellFractionX + anchorDriftX), cloudBaseRelativeY, -(cellFractionZ + anchorDriftZ));
GLStateManager.glTranslatef(entityToCameraX - (cellFractionX + anchorDriftX), deckBaseY, entityToCameraZ - (cellFractionZ + anchorDriftZ));
drawClouds(r, g, b, true);
GLStateManager.glPopMatrix();
}
@@ -150,11 +150,11 @@ public class CloudRenderer implements IResourceManagerReloadListener {
return true;
});
final Entity view = mc.renderViewEntity;
final double cameraY = view.lastTickPosY + (view.posY - view.lastTickPosY) * partialTicks;
drawOrder.sort((a, b) -> Double.compare(distanceToLayer(b.layer, cameraY), distanceToLayer(a.layer, cameraY)));
final ViewportState viewport = GLStateManager.getViewportState();
cloudView.update(GLStateManager.getProjectionMatrix(), GLStateManager.getModelViewMatrix(), viewport.width, viewport.height);
final Entity view = mc.renderViewEntity;
final double cameraY = view.lastTickPosY + (view.posY - view.lastTickPosY) * partialTicks + cloudView.offsetY;
drawOrder.sort((a, b) -> Double.compare(distanceToLayer(b.layer, cameraY), distanceToLayer(a.layer, cameraY)));
boolean rendered = descriptions.isEmpty();
for (CloudLayerRenderer renderer : drawOrder) rendered |= renderer.render(partialTicks, cloudView);
return rendered;
@@ -198,7 +198,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
float radius = (radiusChunks + marginChunks) * CELLS_PER_CHUNK * width;
if (cloudMode != MODE_FANCY) radius *= (float) Math.sqrt(2);
radius += MARGIN_CELLS * width;
final float cameraY = view == null ? 0.0f : (float) view.posY;
final float cameraY = view == null ? 0.0f : (float) (view.posY + cloudView.offsetY);
final float verticalSlack = Math.abs(layer.height() + 0.33f - cameraY) + layer.cellHeight() * scaleMult;
required = Math.max(required, Math.min(radius + verticalSlack, MAX_FAR_PLANE_DISTANCE));
}
@@ -1,9 +1,13 @@
package com.gtnewhorizons.angelica.render;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Vector3f;
final class CloudView {
private final Matrix4f inverseModelView = new Matrix4f();
private final Vector3f cameraOffset = new Vector3f();
private float lastForwardX = Float.NaN, lastForwardZ = Float.NaN;
double pixelsPerRadian;
double facing;
@@ -34,13 +38,13 @@ final class CloudView {
private boolean updateCamera(Matrix4fc modelView) {
offsetX = offsetY = offsetZ = 0.0f;
if (!modelView.isFinite() || modelView.m03() != 0.0f || modelView.m13() != 0.0f
|| modelView.m23() != 0.0f || modelView.m33() != 1.0f || !rigid(modelView)) return false;
final float tx = modelView.m30(), ty = modelView.m31(), tz = modelView.m32();
offsetX = -(modelView.m00() * tx + modelView.m01() * ty + modelView.m02() * tz);
offsetY = -(modelView.m10() * tx + modelView.m11() * ty + modelView.m12() * tz);
offsetZ = -(modelView.m20() * tx + modelView.m21() * ty + modelView.m22() * tz);
if (!Float.isFinite(offsetX) || !Float.isFinite(offsetY) || !Float.isFinite(offsetZ)) return false;
if (!modelView.isFinite() || !modelView.isAffine()) return false;
modelView.invertAffine(inverseModelView).getTranslation(cameraOffset);
if (!cameraOffset.isFinite()) return false;
offsetX = cameraOffset.x;
offsetY = cameraOffset.y;
offsetZ = cameraOffset.z;
if (!rigid(modelView)) return false;
final float forwardX = -modelView.m02(), forwardZ = -modelView.m22();
if (forwardX != lastForwardX || forwardZ != lastForwardZ) {
facing = Math.atan2(forwardZ, forwardX);
@@ -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();
@@ -5,47 +5,56 @@ import com.gtnewhorizons.angelica.api.ThreadSafeISBRHFactory;
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderingRegistryExt;
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
import cpw.mods.fml.client.registry.RenderingRegistry;
import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import net.minecraft.block.Block;
import java.util.concurrent.locks.StampedLock;
public class AngelicaBlockSafetyRegistry {
private static final Reference2BooleanMap<Block> BLOCK_SAFETY_MAP = new Reference2BooleanOpenHashMap<>();
private static final Reference2BooleanMap<Block> ISBRH_SAFETY_MAP = new Reference2BooleanOpenHashMap<>();
private static final Int2IntOpenHashMap ISBRH_SAFETY_MAP = new Int2IntOpenHashMap();
private static final StampedLock LOCK = new StampedLock();
/**
* This method is threadsafe to read, and threadsafe to write, but NOT if both could happen at the same time.
*/
public static boolean canBlockRenderOffThread(Block block, boolean checkISBRH, boolean shouldPopulate) {
final Reference2BooleanMap<Block> map = checkISBRH ? ISBRH_SAFETY_MAP : BLOCK_SAFETY_MAP;
if (shouldPopulate)
return populateCanRenderOffThread(block, map);
return map.getBoolean(block);
static {
ISBRH_SAFETY_MAP.defaultReturnValue(-1);
}
private static boolean populateCanRenderOffThread(Block block, Reference2BooleanMap<Block> map) {
final boolean canBeOffThread;
if(map == ISBRH_SAFETY_MAP) {
@SuppressWarnings("deprecation")
final ISimpleBlockRenderingHandler isbrh = ((IRenderingRegistryExt)RenderingRegistry.instance()).getISBRH(block.getRenderType());
canBeOffThread = isbrh != null
&& (isbrh.getClass().isAnnotationPresent(ThreadSafeISBRH.class)
|| isbrh instanceof ThreadSafeISBRHFactory);
} else {
// Check blacklist here
// !(block.getClass().getName().startsWith("com.github.bartimaeusnek."));
canBeOffThread = true;
public static boolean canBlockRenderOffThread(Block block) {
final int renderType = block.getRenderType();
long stamp = LOCK.tryOptimisticRead();
int value;
try {
value = ISBRH_SAFETY_MAP.get(renderType);
} catch (RuntimeException ignored) {
value = -1;
}
if (LOCK.validate(stamp) && value != -1) {
return value == 1;
}
final long stamp = LOCK.writeLock();
stamp = LOCK.readLock();
try {
map.put(block, canBeOffThread);
value = ISBRH_SAFETY_MAP.get(renderType);
} finally {
LOCK.unlockRead(stamp);
}
if (value != -1) {
return value == 1;
}
return populateCanRenderOffThread(renderType);
}
@SuppressWarnings("deprecation")
private static boolean populateCanRenderOffThread(int renderType) {
final ISimpleBlockRenderingHandler isbrh = ((IRenderingRegistryExt) RenderingRegistry.instance())
.getISBRH(renderType);
final boolean canBeOffThread = isbrh != null
&& (isbrh.getClass().isAnnotationPresent(ThreadSafeISBRH.class) || isbrh instanceof ThreadSafeISBRHFactory);
final long stamp = LOCK.writeLock();
try {
ISBRH_SAFETY_MAP.put(renderType, canBeOffThread ? 1 : 0);
} finally {
LOCK.unlock(stamp);
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Matrix4fc;
import org.joml.Matrix4f;
public final class GlintClock {
@@ -14,6 +15,7 @@ public final class GlintClock {
private static long millis;
private static float armorU0, armorV0, armorU1, armorV1;
private static final Matrix4f armorFirst = new Matrix4f(), armorSecond = new Matrix4f();
public static void beginFrame(long nowMillis) {
millis = nowMillis;
@@ -30,6 +32,20 @@ public final class GlintClock {
final double dv1 = frac(f1 * COS1 / 3.0);
armorU1 = (float) (3.0 * (COS1 * du1 + SIN1 * dv1));
armorV1 = (float) (3.0 * (-SIN1 * du1 + COS1 * dv1));
armorFirst.scaling(0.33333334f).rotateZ((float) Math.toRadians(30)).translate(armorU0, armorV0, 0);
armorSecond.scaling(0.33333334f).rotateZ((float) Math.toRadians(-30)).translate(armorU1, armorV1, 0);
}
public static Matrix4f secondArmorMatrix(Matrix4fc first) {
return armorFirst.equals(first, 0.000001f) ? armorSecond : null;
}
public static Matrix4f firstArmorMatrix() {
return armorFirst;
}
public static Matrix4f secondArmorMatrix() {
return armorSecond;
}
public static long millis() {
@@ -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);
@@ -0,0 +1,45 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import org.lwjgl.opengl.GL11;
/**
* The state a vanilla glint block leaves behind, for callers that skip the block. GLSM is only called where its effective state differs.
*/
public final class SkippedGlintBlock {
private static final BlendState blend = new BlendState();
private SkippedGlintBlock() {}
public static void applyArmorExitState() {
applyExitState(1.0F, 1.0F, 1.0F, 1.0F, GL11.GL_SRC_COLOR, GL11.GL_ONE, GL11.GL_SRC_COLOR, GL11.GL_ONE, true, true);
}
public static void applyHeldItemExitState() {
applyExitState(0.5F * 0.76F, 0.25F * 0.76F, 0.8F * 0.76F, 1.0F, GL11.GL_SRC_COLOR, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ZERO, false, false);
}
private static void applyExitState(float red, float green, float blue, float alpha, int srcRgb, int dstRgb, int srcAlpha, int dstAlpha, boolean resetTextureMatrix, boolean restoreDepthMask) {
final Color4 color = GLStateManager.getColor();
if (color.getRed() != red || color.getGreen() != green || color.getBlue() != blue || color.getAlpha() != alpha) {
GLStateManager.glColor4f(red, green, blue, alpha);
}
if (resetTextureMatrix && !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0))) {
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
}
if (GLStateManager.getMatrixMode().getMode() != GL11.GL_MODELVIEW) GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
if (restoreDepthMask && !GLStateManager.isEffectiveDepthMaskEnabled()) GLStateManager.glDepthMask(true);
if (!GLStateManager.getLightingState().isEnabled()) GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.getEffectiveBlendState(blend);
if (GLStateManager.getBlendState().isFuncUnknown() || blend.getSrcRgb() != srcRgb || blend.getDstRgb() != dstRgb || blend.getSrcAlpha() != srcAlpha || blend.getDstAlpha() != dstAlpha) {
GLStateManager.tryBlendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
}
if (GLStateManager.isEffectiveBlendEnabled()) GLStateManager.glDisable(GL11.GL_BLEND);
if (GLStateManager.getDepthState().getFunc() != GL11.GL_LEQUAL) GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
}
}
@@ -52,7 +52,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
private final TextureMapExtension textureAtlas;
private final ChunkVertexEncoder.Vertex[] vertices = ChunkVertexEncoder.Vertex.uninitializedQuad();
@Getter
private final WorldSlice worldSlice;
private WorldSlice worldSlice;
@Getter
private final BlockRenderContext blockRenderContext = new BlockRenderContext();
private final LightDataCache lightDataCache = new LightDataCache();
@@ -60,6 +60,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
private final FlatLightPipeline flatLightPipeline;
private final QuadLightData quadLightData = new QuadLightData();
@Getter private final FloatArrayList teBoundsScratch = new FloatArrayList();
@Getter private final long[] deferredMask = new long[64];
private final VertexArrayQuadView quadView;
private final boolean hasColoredLight;
private final float[] tintResult = new float[3];
@@ -97,6 +98,12 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
cachedSkylightSubtracted = Minecraft.getMinecraft().theWorld.skylightSubtracted;
}
public WorldSlice swapWorldSlice(WorldSlice replacement) {
final WorldSlice previous = this.worldSlice;
this.worldSlice = replacement;
return previous;
}
public void setupDynamicLights(int chunkOriginX, int chunkOriginY, int chunkOriginZ) {
if (DynamicLights.isEnabled()) {
this.dynamicLightsInstance = DynamicLights.get();
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
import com.gtnewhorizons.angelica.mixins.interfaces.OverridesGetDistanceFrom;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
@@ -10,6 +9,7 @@ import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import com.gtnewhorizons.angelica.rendering.celeritas.iris.BlockRenderContext;
import com.gtnewhorizons.angelica.rendering.celeritas.iris.ContextAwareChunkVertexEncoder;
import com.gtnewhorizons.angelica.rendering.celeritas.threading.RenderPassHelper;
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.utils.NaturalTextureUtils;
@@ -39,6 +39,7 @@ import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildContext;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildOutput;
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
import org.embeddedt.embeddium.impl.render.chunk.compile.tasks.ChunkBuilderTask;
import org.embeddedt.embeddium.impl.render.chunk.data.BuiltSectionMeshParts;
import org.embeddedt.embeddium.impl.render.chunk.occlusion.SectionVisibilityBuilder;
@@ -48,26 +49,22 @@ import org.embeddedt.embeddium.impl.util.task.CancellationToken;
import org.joml.Vector3d;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<ChunkBuildOutput> {
private static final Tracy.ZoneId Z_MESH_SECTION = Tracy.zoneId("meshSection");
private static final Tracy.ZoneId Z_MESH_DEFERRED = Tracy.zoneId("meshDeferred");
private static final Tracy.ZoneId Z_MESH_PRESCAN = Tracy.zoneId("meshPrescan");
private static final Tracy.ZoneId Z_MESH_DEFERRED_MAIN = Tracy.zoneId("meshDeferredMain");
private static final Tracy.ZoneId Z_MESH_FINALIZE = Tracy.zoneId("meshFinalize");
protected final RenderSection render;
protected final int buildTime;
protected final Vector3d camera;
private static final long DEFERRED_BLOCK_TIMEOUT_MS = 10_000; // 10 seconds
private static final int MAX_RETRIES = 2;
protected record DeferredBlock(int x, int y, int z, Block block, int meta, int pass, Material materialOverride, boolean isShaderPackOverride) {}
private DeferredMeshScheduler scheduler;
private boolean important;
public AngelicaChunkBuilderMeshingTask(RenderSection render, int time, Vector3d camera) {
this.render = render;
@@ -88,6 +85,15 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
return false;
}
protected boolean canHandOff() {
return false;
}
public void bindScheduler(DeferredMeshScheduler s, boolean important) {
this.scheduler = s;
this.important = important;
}
protected void onEnterExecute() {}
protected void onExitExecute() {}
@@ -96,13 +102,11 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
@Override
public ChunkBuildOutput execute(ChunkBuildContext context, CancellationToken cancellationToken) {
final long start = System.nanoTime();
final AngelicaChunkBuildContext buildContext = (AngelicaChunkBuildContext) context;
final AngelicaBuiltRenderSectionData renderData = new AngelicaBuiltRenderSectionData();
final SectionVisibilityBuilder occluder = new SectionVisibilityBuilder();
final ChunkBuildBuffers buffers = buildContext.buffers;
buffers.init(renderData, this.render.getSectionIndex());
final int minX = this.render.getOriginX();
final int minY = this.render.getOriginY();
final int minZ = this.render.getOriginZ();
@@ -125,7 +129,35 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
final Tessellator tessellator = getTessellator();
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(true);
ChunkBuildBuffers handoffBuffers = null;
try {
final boolean threaded = isThreaded();
final long[] deferredMask = buildContext.getDeferredMask();
final boolean handoff;
if (threaded) {
if (!canHandOff() || scheduler == null) {
throw new IllegalStateException("Threaded meshing task cannot hand off deferred blocks for " + this.render);
}
if (Tracy.ENABLED) Tracy.beginZone(Z_MESH_PRESCAN);
try {
handoff = prescanDeferred(region, minX, minY, minZ, deferredMask);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
} else {
handoff = false;
}
final ChunkBuildBuffers buffers;
if (handoff) {
handoffBuffers = scheduler.acquireBuffers();
buffers = handoffBuffers;
} else {
buffers = buildContext.buffers;
}
buffers.init(renderData, this.render.getSectionIndex());
tessellator.setTranslation(-minX, -minY, -minZ);
SmoothBiomeColorCache.setActiveCache(biomeColorCache);
@@ -137,8 +169,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
final World mcWorld = Minecraft.getMinecraft().theWorld;
final long currentTick = mcWorld != null ? mcWorld.getTotalWorldTime() : 0L;
final boolean threaded = isThreaded();
final List<DeferredBlock> deferredBlocks = threaded ? new ArrayList<>() : null;
final List<DeferredBlock> deferredBlocks = handoff ? new ArrayList<>() : null;
final FloatArrayList culledBounds = buildContext.getTeBoundsScratch();
culledBounds.clear();
@@ -207,7 +238,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
}
final boolean canRenderOffThread = !threaded || canRenderOffThread(block);
final boolean canRenderOffThread = !handoff || !isDeferred(deferredMask, x, y, z);
// Check for shader pack override
final BlockRenderLayer override = blockTypeIds != null ? blockTypeIds.get(block) : null;
@@ -245,14 +276,23 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
}
// Process deferred blocks on main thread if any
if (deferredBlocks != null && !deferredBlocks.isEmpty()) {
if (Tracy.ENABLED) Tracy.beginZone(Z_MESH_DEFERRED);
try {
processDeferredBlocks(deferredBlocks, buildContext, buffers, region, minX, minY, minZ, teMap, currentTick);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
renderData.visibilityData = occluder.computeVisibilityEncoding();
renderData.culledBlockEntityBounds = culledBounds.isEmpty() ? AngelicaBuiltRenderSectionData.EMPTY_BOUNDS : culledBounds.toFloatArray();
renderData.maxTeRenderDistSq = maxTeRenderDistSq;
if (handoff) {
if (region != buildContext.getWorldSlice()) {
throw new IllegalStateException("Deferred handoff requires the context WorldSlice for " + this.render);
}
final WorldSlice slice = buildContext.swapWorldSlice(scheduler.acquireSlice());
final DeferredSectionMesh mesh = new DeferredSectionMesh(this, cancellationToken, buffers, slice, renderData, deferredBlocks, teMap, currentTick, this.important, System.nanoTime() - start);
scheduler.submit(mesh);
handoffBuffers = null;
SmoothBiomeColorCache.clearActiveCache();
tessellator.setTranslation(0, 0, 0);
return null;
}
if (Tracy.ENABLED) Tracy.beginZone(Z_MESH_FINALIZE);
@@ -260,7 +300,6 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
try {
meshes = BuiltSectionMeshParts.groupFromBuildBuffers(buffers,
(float) camera.x - minX, (float) camera.y - minY, (float) camera.z - minZ);
renderData.visibilityData = occluder.computeVisibilityEncoding();
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
@@ -276,9 +315,6 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
}
renderData.culledBlockEntityBounds = culledBounds.isEmpty() ? AngelicaBuiltRenderSectionData.EMPTY_BOUNDS : culledBounds.toFloatArray();
renderData.maxTeRenderDistSq = maxTeRenderDistSq;
SmoothBiomeColorCache.clearActiveCache();
tessellator.setTranslation(0, 0, 0);
@@ -291,6 +327,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
} catch (Throwable ex) {
throw fillCrashInfo(CrashReport.makeCrashReport(ex, "Encountered exception while building chunk meshes"), region, blockPos);
} finally {
if (handoffBuffers != null) scheduler.releaseBuffers(handoffBuffers);
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(false);
SmoothBiomeColorCache.clearActiveCache();
if (Tracy.ENABLED) Tracy.endZone();
@@ -298,6 +335,95 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
}
private boolean prescanDeferred(IBlockAccess region, int minX, int minY, int minZ, long[] mask) {
Arrays.fill(mask, 0L);
boolean any = false;
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
final Block block = region.getBlock(minX + x, minY + y, minZ + z);
if (block != Blocks.air && !canRenderOffThread(block)) {
final int bit = (y << 8) | (z << 4) | x;
mask[bit >>> 6] |= 1L << bit;
any = true;
}
}
}
}
return any;
}
private static boolean isDeferred(long[] mask, int x, int y, int z) {
final int bit = ((y & 15) << 8) | ((z & 15) << 4) | (x & 15);
return (mask[bit >>> 6] & (1L << bit)) != 0;
}
ChunkJobResult<ChunkBuildOutput> completeDeferred(DeferredSectionMesh m, AngelicaChunkBuildContext ctx) {
final long start = System.nanoTime();
final int minX = this.render.getOriginX();
final int minY = this.render.getOriginY();
final int minZ = this.render.getOriginZ();
final WorldSlice slice = m.slice;
final BlockPos blockPos = new BlockPos(minX, minY, minZ);
final Tessellator tessellator = Tessellator.instance;
final List<DeferredBlock> blocks = m.blocks;
if (Tracy.ENABLED) {
Tracy.beginZone(Z_MESH_DEFERRED_MAIN);
Tracy.zoneValue(blocks.size());
}
try {
ctx.setupLightPipeline(slice, minX, minY, minZ);
ctx.setupDynamicLights(minX, minY, minZ);
slice.setRenderingBlock(null);
SmoothBiomeColorCache.setActiveCache(slice.getBiomeColorCache());
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(true);
tessellator.setTranslation(-minX, -minY, -minZ);
final RenderBlocks renderBlocks = new RenderBlocks(slice);
final BlockRenderContext blockRenderContext = ctx.getBlockRenderContext();
for (int i = 0, n = blocks.size(); i < n; i++) {
final DeferredBlock deferred = blocks.get(i);
blockPos.set(deferred.x(), deferred.y(), deferred.z());
slice.setRenderingBlock(deferred.block());
renderBlock(deferred.block(), deferred.meta(), deferred.x(), deferred.y(), deferred.z(), deferred.pass(),
tessellator, renderBlocks, m.buffers, ctx, blockRenderContext, minX, minY, minZ,
deferred.materialOverride(), deferred.isShaderPackOverride(), m.teMap, m.currentTick);
}
if (Tracy.ENABLED) Tracy.beginZone(Z_MESH_FINALIZE);
final Reference2ReferenceMap<TerrainRenderPass, BuiltSectionMeshParts> meshes;
try {
meshes = BuiltSectionMeshParts.groupFromBuildBuffers(m.buffers, (float) camera.x - minX, (float) camera.y - minY, (float) camera.z - minZ);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
if (!meshes.isEmpty()) {
m.renderData.hasBlockGeometry = true;
}
CeleritasDebug.incrementChunkUpdateCounter();
return new ChunkJobResult.Success<>(new ChunkBuildOutput(this.render, m.renderData, meshes, this.buildTime), m.workerNanos + (System.nanoTime() - start));
} catch (ReportedException ex) {
return new ChunkJobResult.Failure<>(fillCrashInfo(ex.getCrashReport(), slice, blockPos));
} catch (Throwable ex) {
return new ChunkJobResult.Failure<>(fillCrashInfo(CrashReport.makeCrashReport(ex, "Encountered exception while building deferred chunk meshes"), slice, blockPos));
} finally {
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(false);
tessellator.setTranslation(0, 0, 0);
SmoothBiomeColorCache.clearActiveCache();
RenderPassHelper.resetWorldRenderPass();
if (Tracy.ENABLED) Tracy.endZone();
}
}
protected void renderBlock(Block block, int metadata, int x, int y, int z, int pass, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, Material materialOverride, boolean isShaderPackOverride, NbtConditionalIdMap<Block> teMap, long currentTick) {
final var blockMaterial = block.getMaterial();
@@ -342,7 +468,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
int teBlockId = BlockRenderingSettings.getCachedTeNbtId(packedPos, currentTick);
if (teBlockId == BlockRenderingSettings.CACHE_MISS) {
final TileEntity te = getBlockAccess().getTileEntity(x, y, z);
final TileEntity te = renderBlocks.blockAccess.getTileEntity(x, y, z);
if (te != null) {
final NBTTagCompound teNbt = new NBTTagCompound();
te.writeToNBT(teNbt);
@@ -387,54 +513,6 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
}
private void processDeferredBlocks(List<DeferredBlock> deferredBlocks, AngelicaChunkBuildContext buildContext, ChunkBuildBuffers buffers, IBlockAccess region, int minX, int minY, int minZ, NbtConditionalIdMap<Block> teMap, long currentTick) {
final BlockRenderContext blockRenderContext = buildContext.getBlockRenderContext();
final CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
final RenderBlocks mainThreadRenderBlocks = new RenderBlocks(region);
final Tessellator mainTessellator = Tessellator.instance;
((StateAwareTessellator)mainTessellator).angelica$setCeleritasMeshing(true);
mainTessellator.setTranslation(-minX, -minY, -minZ);
try {
for (DeferredBlock deferred : deferredBlocks) {
renderBlock(deferred.block(), deferred.meta(), deferred.x(), deferred.y(), deferred.z(), deferred.pass(),
mainTessellator, mainThreadRenderBlocks, buffers, buildContext,
blockRenderContext, minX, minY, minZ, deferred.materialOverride(), deferred.isShaderPackOverride(), teMap, currentTick);
}
} finally {
((StateAwareTessellator)mainTessellator).angelica$setCeleritasMeshing(false);
mainTessellator.setTranslation(0, 0, 0);
}
}, AngelicaRenderQueue.executor());
if (Thread.currentThread().isInterrupted()) {
return;
}
int retries = 0;
while (retries < MAX_RETRIES) {
try {
future.get(DEFERRED_BLOCK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
return;
} catch (TimeoutException e) {
retries++;
if (Thread.currentThread().isInterrupted()) {
return;
}
if (retries >= MAX_RETRIES) {
return;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
} catch (ExecutionException e) {
throw new RuntimeException("Error during main thread deferred block rendering", e);
}
}
}
private ReportedException fillCrashInfo(CrashReport report, IBlockAccess slice, BlockPos pos) {
final CrashReportCategory crashReportSection = report.makeCategory("Block being rendered");
@@ -11,6 +11,7 @@ import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ChunkTaskProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ChunkTaskRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
@@ -20,6 +21,7 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.world.chunk.Chunk;
import org.embeddedt.embeddium.impl.gl.device.CommandList;
import org.embeddedt.embeddium.impl.render.chunk.ChunkRenderMatrices;
import org.embeddedt.embeddium.impl.render.chunk.ChunkUpdateType;
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager;
@@ -51,6 +53,7 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
private static final long P_MESH_DEVICE_USED = Tracy.plotHandle("mesh.deviceUsed", TracyBackend.PLOT_FORMAT_MEMORY);
private static final long P_DRAW_REGION_BUFFERS = Tracy.plotHandle("draw.regionBuffers");
private static final long P_MESH_DEVICE_ALLOCATED = Tracy.plotHandle("mesh.deviceAllocated", TracyBackend.PLOT_FORMAT_MEMORY);
private static final long P_MESH_DEFERRED_PENDING = Tracy.plotHandle("mesh.deferredPending");
private static final Tracy.ZoneId Z_BIOME_REBUILDS = Tracy.zoneId("biomeRebuilds", Tracy.COLOR_TERRAIN);
private static final Tracy.ZoneId Z_GRAPH_SEARCH = Tracy.zoneId("graphSearch", Tracy.COLOR_TERRAIN);
@@ -63,12 +66,14 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
private final ClonedChunkSectionCache sectionCache;
private final ChunkTaskProvider taskProvider;
private final LongOpenHashSet biomeRebuildColumns = new LongOpenHashSet();
private final DeferredMeshScheduler deferredScheduler;
public AngelicaRenderSectionManager(RenderPassConfiguration<?> configuration, WorldClient world, int renderDistance, CommandList commandList, int minSection, int maxSection, int requestedThreads, ChunkTaskProvider taskProvider) {
super(configuration, () -> new AngelicaChunkBuildContext(configuration, world), AngelicaChunkRenderer::new, renderDistance, commandList, minSection, maxSection, requestedThreads, true /* hasShadowPass = true for Iris */);
this.world = world;
this.sectionCache = new ClonedChunkSectionCache(world);
this.taskProvider = taskProvider;
this.deferredScheduler = new DeferredMeshScheduler(configuration, () -> new AngelicaChunkBuildContext(configuration, world), () -> new WorldSlice(world), AngelicaRenderQueue::submit, ((RenderSectionManagerAccessor) this).angelica$getBuildResults());
}
public static AngelicaRenderSectionManager create(ChunkVertexType vertexType, WorldClient world, int renderDistance, CommandList commandList) {
@@ -168,7 +173,12 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
return null;
}
return this.taskProvider.createRebuildTask(render, frame, this.cameraPosition, this.sectionCache);
final ChunkBuilderTask<ChunkBuildOutput> task = this.taskProvider.createRebuildTask(render, frame, this.cameraPosition, this.sectionCache);
if (task instanceof AngelicaChunkBuilderMeshingTask t) {
final ChunkUpdateType pending = render.getPendingUpdate();
t.bindScheduler(this.deferredScheduler, pending != null && pending.isImportant());
}
return task;
}
@Override
@@ -217,11 +227,19 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
if (Tracy.ENABLED) Tracy.beginZone(Z_GRAPH_SEARCH);
try {
super.updateChunks(updateImmediately);
this.deferredScheduler.runImportant();
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
}
@Override
public void destroy() {
this.deferredScheduler.markDestroyed();
super.destroy();
this.deferredScheduler.runImportant();
}
@Override
public void uploadChunks() {
if (Tracy.ENABLED) Tracy.beginZone(Z_SECTION_UPLOAD);
@@ -353,5 +371,6 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
Tracy.plotInt(P_MESH_DEVICE_USED, mem.deviceUsed + mem.indexUsed);
Tracy.plotInt(P_MESH_DEVICE_ALLOCATED, mem.deviceAllocated + mem.indexAllocated);
Tracy.plotInt(P_DRAW_REGION_BUFFERS, mem.bufferCount);
Tracy.plotInt(P_MESH_DEFERRED_PENDING, this.deferredScheduler.pendingCount());
}
}
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import net.minecraft.block.Block;
import org.embeddedt.embeddium.impl.render.chunk.terrain.material.Material;
public record DeferredBlock(int x, int y, int z, Block block, int meta, int pass, Material materialOverride, boolean isShaderPackOverride) {}
@@ -0,0 +1,104 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput;
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class DeferredMeshScheduler {
private final Supplier<AngelicaChunkBuildContext> mainContextFactory;
private final Consumer<Runnable> mainThreadQueue;
private final Queue<ChunkJobResult<? extends ChunkTaskOutput>> results;
private final ObjectPool<ChunkBuildBuffers> buffers;
private final ObjectPool<WorldSlice> slices;
private final ConcurrentLinkedQueue<DeferredSectionMesh> important = new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<DeferredSectionMesh> normal = new ConcurrentLinkedQueue<>();
private final Runnable runNext = this::runNext;
private final AtomicInteger pending = new AtomicInteger();
private AngelicaChunkBuildContext mainContext;
private boolean destroyed;
public DeferredMeshScheduler(RenderPassConfiguration<?> config, Supplier<AngelicaChunkBuildContext> mainContextFactory, Supplier<WorldSlice> sliceFactory, Consumer<Runnable> mainThreadQueue, Queue<ChunkJobResult<? extends ChunkTaskOutput>> results) {
this.mainContextFactory = mainContextFactory;
this.mainThreadQueue = mainThreadQueue;
this.results = results;
this.buffers = new ObjectPool<>(() -> new ChunkBuildBuffers(config));
this.slices = new ObjectPool<>(sliceFactory);
}
public ChunkBuildBuffers acquireBuffers() {
return buffers.acquire();
}
public void releaseBuffers(ChunkBuildBuffers buffers) {
buffers.destroy();
this.buffers.release(buffers);
}
public WorldSlice acquireSlice() {
return slices.acquire();
}
public void submit(DeferredSectionMesh m) {
pending.incrementAndGet();
if (m.important) {
important.add(m);
} else {
normal.add(m);
mainThreadQueue.accept(runNext);
}
}
public void runImportant() {
DeferredSectionMesh m;
while ((m = important.poll()) != null) {
run(m);
}
}
public void markDestroyed() {
destroyed = true;
if (mainContext != null) {
mainContext.cleanup();
mainContext = null;
}
}
public int pendingCount() {
return pending.get();
}
private AngelicaChunkBuildContext mainContext() {
if (mainContext == null) {
mainContext = mainContextFactory.get();
}
return mainContext;
}
private void runNext() {
final DeferredSectionMesh m = normal.poll();
if (m != null) {
run(m);
}
}
private void run(DeferredSectionMesh m) {
try {
if (destroyed || m.isObsolete()) return;
results.add(m.complete(mainContext()));
} finally {
releaseBuffers(m.buffers);
slices.release(m.slice);
pending.decrementAndGet();
}
}
}
@@ -0,0 +1,45 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
import net.minecraft.block.Block;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildOutput;
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
import org.embeddedt.embeddium.impl.util.task.CancellationToken;
import java.util.List;
public class DeferredSectionMesh {
public final AngelicaChunkBuilderMeshingTask task;
public final CancellationToken token;
public final ChunkBuildBuffers buffers;
public final WorldSlice slice;
public final AngelicaBuiltRenderSectionData renderData;
public final List<DeferredBlock> blocks;
public final NbtConditionalIdMap<Block> teMap;
public final long currentTick;
public final boolean important;
public final long workerNanos;
public DeferredSectionMesh(AngelicaChunkBuilderMeshingTask task, CancellationToken token, ChunkBuildBuffers buffers, WorldSlice slice, AngelicaBuiltRenderSectionData renderData, List<DeferredBlock> blocks, NbtConditionalIdMap<Block> teMap, long currentTick, boolean important, long workerNanos) {
this.task = task;
this.token = token;
this.buffers = buffers;
this.slice = slice;
this.renderData = renderData;
this.blocks = blocks;
this.teMap = teMap;
this.currentTick = currentTick;
this.important = important;
this.workerNanos = workerNanos;
}
public boolean isObsolete() {
return token.isCancelled() || task.render.isDisposed() || task.render.getLastBuiltFrame() > task.buildTime;
}
public ChunkJobResult<ChunkBuildOutput> complete(AngelicaChunkBuildContext ctx) {
return task.completeDeferred(this, ctx);
}
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Supplier;
final class ObjectPool<T> {
private final ConcurrentLinkedQueue<T> free = new ConcurrentLinkedQueue<>();
private final Supplier<T> factory;
ObjectPool(Supplier<T> factory) {
this.factory = factory;
}
T acquire() {
final T t = free.poll();
return t != null ? t : factory.get();
}
void release(T t) {
free.add(t);
}
}
@@ -94,8 +94,12 @@ public class ThreadedAngelicaChunkBuilderMeshingTask extends AngelicaChunkBuilde
@Override
protected boolean canRenderOffThread(Block block) {
final int renderType = block.getRenderType();
return (renderType >= 0 && renderType <= MAX_VANILLA_RENDER_TYPE)
|| AngelicaBlockSafetyRegistry.canBlockRenderOffThread(block, true, true);
return renderType == -1 || (renderType >= 0 && renderType <= MAX_VANILLA_RENDER_TYPE) || AngelicaBlockSafetyRegistry.canBlockRenderOffThread(block);
}
@Override
protected boolean canHandOff() {
return renderContext != null;
}
@Override
@@ -12,6 +12,7 @@ import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
import com.gtnewhorizons.angelica.rendering.tesr.BakedTransformCapture;
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials;
import com.gtnewhorizons.angelica.rendering.tesr.GlintCapture;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
@@ -34,6 +35,12 @@ public final class DroppedItemInstancer {
private static final Object[] BLOCK_ARGS = new Object[4];
private static BakedTransformCapture blockBackend;
private static boolean basePart;
private static final GlintCapture glintCapture = new GlintCapture();
private static boolean glintSeen;
private static final ItemPropCache.ItemProp glintProperties = new ItemPropCache.ItemProp();
private static final ItemPropCache.ItemProp currentGlintProperties = new ItemPropCache.ItemProp();
private static TemplateBuffer glintTemplate;
private static int glintTemplateWidth, glintTemplateHeight;
private static long instanced;
private static long glintInstanced;
@@ -81,7 +88,8 @@ public final class DroppedItemInstancer {
}
private static boolean glintEligible(ItemStack stack) {
return stack != null && stack.getItem() != null && ModelPartBatcher.INSTANCE.isActive() && !GLStateManager.isRecordingDisplayList() && !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(Tessellator.instance);
return stack != null && stack.getItem() != null && ModelPartBatcher.INSTANCE.isActive() && !GLStateManager.isRecordingDisplayList()
&& !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(Tessellator.instance);
}
static TesrMaterial material(ItemStack stack, boolean unfilteredAtlas) {
@@ -100,6 +108,8 @@ public final class DroppedItemInstancer {
}
public static void icon(ItemStack stack, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
glintCapture.reset();
glintSeen = false;
final TesrMaterial material = material(stack, true);
if (material == null) {
basePart = false;
@@ -131,10 +141,11 @@ public final class DroppedItemInstancer {
}
callIcon(ICON_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls);
return true;
return false;
}
public static void glint(Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
/** Held glints use ITEM_GLINT for its stencil guard; dropped items draw with culling on and need none. */
public static void glint(Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, TesrMaterial material, Operation<Void> original) {
if (!basePart) {
callIcon(GLINT_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
return;
@@ -146,8 +157,22 @@ public final class DroppedItemInstancer {
}
final long drawsBefore = GLStateManager.drawCalls;
if (allowed) {
currentGlintProperties.set(maxU, minV, minU, maxV, width, height, thickness);
if (glintSeen && glintProperties.equals(currentGlintProperties) && ModelPartBatcher.INSTANCE.replayGlint(glintCapture)) {
glintInstanced++;
return;
}
final TemplateBuffer template = iconTemplate(GLINT_ARGS, maxU, minV, minU, maxV, width, height, thickness, original);
if (template != null && ModelPartBatcher.INSTANCE.queueTemplate(template, EntityMaterials.GLINT)) {
final boolean capture = !glintSeen && ModelPartBatcher.INSTANCE.beginGlintCapture(glintCapture);
if (capture) glintProperties.set(currentGlintProperties);
glintSeen = true;
final boolean queued;
try {
queued = template != null && ModelPartBatcher.INSTANCE.queueTemplate(template, material);
} finally {
if (capture) glintCapture.end();
}
if (queued) {
glintInstanced++;
BatchEligibility.onPartQueued();
return;
@@ -161,6 +186,22 @@ public final class DroppedItemInstancer {
BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls);
}
public static boolean queueSkippedGlint(int iconWidth, int iconHeight) {
if (!basePart || !BatchEligibility.batchingAllowed()) return false;
if (glintTemplate == null || glintTemplateWidth != iconWidth || glintTemplateHeight != iconHeight) {
final IconMesh hit = icons.get(0.0F, 0.0F, 1.0F, 1.0F, iconWidth, iconHeight, 0.0625F);
if (hit == null || hit.template == null) return false;
glintTemplate = hit.template;
glintTemplateWidth = iconWidth;
glintTemplateHeight = iconHeight;
}
if (!ModelPartBatcher.INSTANCE.queueSkippedHeldGlint(glintTemplate)) return false;
glintInstanced += 2;
BatchEligibility.onPartQueued();
BatchEligibility.onPartQueued();
return true;
}
public static void block(ItemStack stack, RenderBlocks rb, Block block, int meta, float brightness, boolean unfilteredAtlas, Operation<Void> original) {
final TesrMaterial material = material(stack, unfilteredAtlas);
if (material == null) {
@@ -211,6 +252,9 @@ public final class DroppedItemInstancer {
blocks.clear();
blockKey.block = null;
basePart = false;
glintCapture.reset();
glintSeen = false;
glintTemplate = null;
if (blockBackend != null) {
blockBackend.delete();
blockBackend = null;
@@ -292,7 +336,9 @@ public final class DroppedItemInstancer {
private static BailReason blockStateBail(RenderBlocks renderBlocks, Block block, float brightness) {
if (BlockRenderListManager.isISBRH(block.getRenderType())) return BailReason.ISBRH;
if (renderBlocks.enableAO || renderBlocks.overrideBlockTexture != null || brightness != 1.0F || !renderBlocks.useInventoryTint || renderBlocks.blockAccess != null || (renderBlocks.uvRotateEast | renderBlocks.uvRotateWest | renderBlocks.uvRotateSouth | renderBlocks.uvRotateNorth | renderBlocks.uvRotateTop | renderBlocks.uvRotateBottom) != 0) return BailReason.BLOCK_STATE;
if (renderBlocks.enableAO || renderBlocks.overrideBlockTexture != null || brightness != 1.0F || !renderBlocks.useInventoryTint
|| renderBlocks.blockAccess != null || (renderBlocks.uvRotateEast | renderBlocks.uvRotateWest | renderBlocks.uvRotateSouth
| renderBlocks.uvRotateNorth | renderBlocks.uvRotateTop | renderBlocks.uvRotateBottom) != 0) return BailReason.BLOCK_STATE;
return null;
}
@@ -0,0 +1,102 @@
package com.gtnewhorizons.angelica.rendering.items;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.CombinedGlint;
import com.gtnewhorizons.angelica.rendering.GlintClock;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import jss.notfine.core.Settings;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import javax.imageio.ImageIO;
import java.io.IOException;
/** One draw for the two vanilla held-item layers; modified glint sequences retain their original draws. */
public final class HeldItemGlint {
private static final ResourceLocation TEXTURE = new ResourceLocation("textures/misc/enchanted_item_glint.png");
private static final Matrix4f first = new Matrix4f();
private static final Matrix4f second = new Matrix4f();
private static long matrixTime = Long.MIN_VALUE;
private static boolean listening, checked, opaque;
private HeldItemGlint() {}
private static boolean batchedGlintGuarded() {
return ModelPartBatcher.INSTANCE.entityPassActive() && ModelPartBatcher.INSTANCE.canCaptureGlint() && canResetStencil();
}
public static boolean needsImmediateBase(ItemStack stack, int pass) {
return stack.hasEffect(pass) && (boolean) Settings.MODE_GLINT_WORLD.option.getStore() && !ModelPartBatcher.INSTANCE.isShadowPass() && !batchedGlintGuarded();
}
public static boolean layersCoverSamePixels() {
return opaqueTexture(Minecraft.getMinecraft());
}
public static boolean canResetStencil() {
if (!GLStateManager.getStencilTest().isEnabled()) return true;
final var stencil = GLStateManager.getStencilState();
return stencil.getFuncFront() == GL11.GL_ALWAYS && stencil.getFuncBack() == GL11.GL_ALWAYS && stencil.getFailOpFront() == GL11.GL_KEEP && stencil.getFailOpBack() == GL11.GL_KEEP && stencil.getZFailOpFront() == GL11.GL_KEEP
&& stencil.getZFailOpBack() == GL11.GL_KEEP && stencil.getZPassOpFront() == GL11.GL_KEEP && stencil.getZPassOpBack() == GL11.GL_KEEP;
}
public static boolean eligible() {
return AngelicaConfig.enableEntityBatching && GLStateManager.getActiveProgram() == 0 && (boolean) Settings.MODE_GLINT_WORLD.option.getStore() && opaqueTexture(Minecraft.getMinecraft());
}
public static boolean begin() {
if (!eligible()) return false;
final var mc = Minecraft.getMinecraft();
final var texture = mc.getTextureManager().getTexture(TEXTURE);
if (texture == null || texture.getGlTextureId() != GLStateManager.getTextures().getTextureUnitBindings(0).getBinding()) return false;
return firstMatrix().equals(GLStateManager.getTextures().getTextureUnitMatrix(0), 0.000001f) && CombinedGlint.begin(secondMatrix());
}
public static ResourceLocation texture() {
return TEXTURE;
}
public static Matrix4f firstMatrix() {
updateMatrices();
return first;
}
public static Matrix4f secondMatrix() {
updateMatrices();
return second;
}
private static void updateMatrices() {
final long time = GlintClock.millis();
if (matrixTime == time) return;
matrixTime = time;
first.scaling(0.125f).translate((float) (time % 3000L) / 3000.0f * 8.0f, 0, 0).rotate((float) Math.toRadians(-50), 0, 0, 1);
second.scaling(0.125f).translate(-(float) (time % 4873L) / 4873.0f * 8.0f, 0, 0).rotate((float) Math.toRadians(10), 0, 0, 1);
}
private static boolean opaqueTexture(Minecraft mc) {
if (!listening) {
listening = true;
((IReloadableResourceManager) mc.getResourceManager()).registerReloadListener(manager -> checked = false);
}
if (checked) return opaque;
checked = true;
opaque = false;
try (var stream = mc.getResourceManager().getResource(TEXTURE).getInputStream()) {
final var image = ImageIO.read(stream);
if (image == null) return false;
for (int y = 0; y < image.getHeight(); y++) for (int x = 0; x < image.getWidth(); x++) {
if ((image.getRGB(x, y) >>> 24) != 255) return false;
}
opaque = true;
} catch (IOException | RuntimeException e) {
GLStateManager.LOGGER.debug("Could not inspect held glint texture; keeping separate layers", e);
}
return opaque;
}
}
@@ -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);
}
}
@@ -28,6 +28,7 @@ public final class BatchEligibility {
expectedDraws = 0L;
foreignDraws = 0L;
parts = 0;
bracketDepth = 0;
allowed = state == SAFE;
return allowed;
}
@@ -27,8 +27,8 @@ public final class EntityMaterials {
public static final TesrMaterial HELD_BLOCK_CUTOUT = TesrMaterial.builder().cutout(0.1f).stream().build();
public static final TesrMaterial HELD_BLOCK_TRANSLUCENT = TesrMaterial.builder().translucent().cutout(0.1f).stream().build();
static final TesrMaterial OVERLAY = TesrMaterial.builder().translucent().depthEqual().stream().build();
public static final TesrMaterial GLINT = TesrMaterial.builder().glint().depthEqual().noDepthWrite().unlit().stream()
.special(TesrMaterial.SpecialRender.GLINT).build();
public static final TesrMaterial GLINT = TesrMaterial.builder().glint().depthEqual().noDepthWrite().unlit().stream().special(TesrMaterial.SpecialRender.GLINT).build();
public static final TesrMaterial ITEM_GLINT = TesrMaterial.builder().glint().depthEqual().noDepthWrite().unlit().stream().special(TesrMaterial.SpecialRender.GLINT).build();
private static final BlendState effectiveBlend = new BlendState();
private static final AlphaState effectiveAlpha = new AlphaState();
@@ -39,13 +39,17 @@ public final class EntityMaterials {
final boolean textured = GLStateManager.getTextures().getTextureUnitStates(0).isEnabled();
final BlendState blend = GLStateManager.getEffectiveBlendState(effectiveBlend);
final AlphaState alpha = GLStateManager.getEffectiveAlphaState(effectiveAlpha);
return fromState(textured, textured && !texIdentity, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(), GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(),GLStateManager.isEffectiveDepthMaskEnabled());
return fromState(textured, textured && !texIdentity, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(),
GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(),
GLStateManager.isEffectiveDepthMaskEnabled());
}
public static TesrMaterial itemFromCurrentState(boolean unfilteredAtlas) {
final BlendState blend = GLStateManager.getEffectiveBlendState(effectiveBlend);
final AlphaState alpha = GLStateManager.getEffectiveAlphaState(effectiveAlpha);
return itemFromState(unfilteredAtlas, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(), GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(),GLStateManager.isEffectiveDepthMaskEnabled());
return itemFromState(unfilteredAtlas, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(),
GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(),
GLStateManager.isEffectiveDepthMaskEnabled());
}
static TesrMaterial itemFromState(boolean unfilteredAtlas, boolean blend, int srcRgb, int dstRgb, boolean alphaTest, int alphaFunc, float alphaRef, int depthFunc, boolean depthMask) {
@@ -0,0 +1,217 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.FragmentKey;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import java.util.Arrays;
/** Reuses captured instance records across armor and item glint material passes. */
public final class GlintCapture {
private static final class Range {
RetainedTesrGroups.Group group;
RetainedTesrGroups.InstanceColumns columns;
RetainedTesrGroups.TexRun source;
int start, end, instances;
}
private final ObjectArrayList<Range> ranges = new ObjectArrayList<>();
private final Matrix4f parent = new Matrix4f();
private final long[] fragment = new long[4], scratch = new long[4];
private final BlendState blend = new BlendState(), scratchBlend = new BlendState();
private boolean blendEnabled, depthMask;
private int depthFunc;
private int used, texture, color, light, overlay, fragmentLength;
private long draws;
private long pass;
private RetainedTesrGroups owner;
private boolean valid;
private boolean base;
private boolean trusted;
private boolean poseRestored;
private int restoredGeneration;
private boolean uniformLight;
public void reset() {
if (owner != null && owner.glintCapture == this) owner.glintCapture = null;
for (int i = 0; i < used; i++) {
final Range range = ranges.get(i);
range.group = null;
range.columns = null;
range.source = null;
}
used = 0;
valid = false;
owner = null;
base = false;
trusted = false;
poseRestored = false;
}
boolean begin(RetainedTesrGroups groups) {
if (!open(groups)) return false;
parent.set(GLStateManager.getModelViewMatrix());
texture = GLStateManager.getBoundTextureForServerState();
color = currentColor();
light = GLSMConfig.packedLastBrightness();
overlay = currentOverlay();
fragmentLength = FragmentKey.packFromState(fragment);
GLStateManager.getEffectiveBlendState(blend);
blendEnabled = GLStateManager.isEffectiveBlendEnabled();
depthMask = GLStateManager.isEffectiveDepthMaskEnabled();
depthFunc = GLStateManager.getDepthState().getFunc();
return true;
}
/**
* Base passes are only copied under the same parent pose. Light and overlay are recorded so the glint can draw the base instances in place when every part still carries them.
*/
boolean beginBase(RetainedTesrGroups groups) {
if (!open(groups)) return false;
parent.set(GLStateManager.getModelViewMatrix());
light = GLSMConfig.packedLastBrightness();
overlay = currentOverlay();
base = true;
return true;
}
/** For records queued without running model or mod code; the caller replays before any GL state changes. */
boolean beginTrusted(RetainedTesrGroups groups) {
if (!open(groups)) return false;
trusted = true;
return true;
}
private boolean open(RetainedTesrGroups groups) {
if (groups.glintCapture != null) return false;
reset();
owner = groups;
pass = groups.captureVersion();
groups.glintCapture = this;
draws = GLStateManager.drawCalls;
valid = true;
uniformLight = true;
return true;
}
void invalidate() { valid = false; }
void allowBase() { base = true; }
boolean capturesBase() { return base && valid; }
void record(RetainedTesrGroups.Group group, RetainedTesrGroups.InstanceColumns columns, RetainedTesrGroups.TexRun source, int start, int end, int instances) {
if (!valid) return;
if (!base && group.material != EntityMaterials.GLINT && group.material != EntityMaterials.ITEM_GLINT) {
invalidate();
return;
}
Range range = used > 0 ? ranges.get(used - 1) : null;
if (range == null || range.group != group || range.columns != columns || range.source != source || range.end != start) {
if (used == ranges.size()) ranges.add(new Range());
range = ranges.get(used++);
range.group = group;
range.columns = columns;
range.source = source;
range.start = start;
range.instances = 0;
}
range.end = end;
range.instances += instances;
if (base && uniformLight) {
for (int i = start; i < end; i++) {
if (columns.lights.getInt(i) != light || columns.overlays.getInt(i) != overlay) {
uniformLight = false;
break;
}
}
}
}
public void end() {
close(false);
}
public void endWithRestoredPose() {
close(true);
}
private void close(boolean poseRestored) {
if (owner == null || owner.glintCapture != this) return;
owner.glintCapture = null;
valid &= GLStateManager.drawCalls == draws && used > 0;
this.poseRestored = poseRestored;
restoredGeneration = GLStateManager.getMvGeneration();
}
private boolean sameParentPose() {
return poseRestored && restoredGeneration == GLStateManager.getMvGeneration() || parent.equals(GLStateManager.getModelViewMatrix());
}
boolean replay(RetainedTesrGroups groups) {
return replay(groups, GLStateManager.getTextures().getTextureUnitMatrix(0));
}
boolean replay(RetainedTesrGroups groups, Matrix4f textureMatrix) {
if (base || !valid || owner != groups || pass != groups.captureVersion() || groups.glintCapture != null || GLStateManager.drawCalls != draws) return false;
if (!trusted && (blendEnabled != GLStateManager.isEffectiveBlendEnabled() || depthMask != GLStateManager.isEffectiveDepthMaskEnabled() || depthFunc != GLStateManager.getDepthState().getFunc()
|| !blend.sameAs(GLStateManager.getEffectiveBlendState(scratchBlend)) || !sameParentPose() || texture != GLStateManager.getBoundTextureForServerState() || color != currentColor() || light != GLSMConfig.packedLastBrightness()
|| fragmentLength != FragmentKey.packFromState(scratch) || !Arrays.equals(fragment, 0, fragmentLength, scratch, 0, fragmentLength))) return false;
for (int i = 0; i < used; i++) {
final Range range = ranges.get(i);
if (!groups.captureStillValid(range.group)) return false;
}
for (int i = 0; i < used; i++) {
final Range range = ranges.get(i);
range.group.replayRange(range.columns, range.source, range.start, range.end, range.instances, textureMatrix);
}
valid = false;
return true;
}
boolean copyBase(ModelPartBatcher batcher, RetainedTesrGroups groups) {
if (!baseStillValid(groups) || EntityMaterials.fromCurrentState(false) != EntityMaterials.GLINT || !batcher.prepareArmorGlint()) return false;
final boolean inPlace = sameLightAndOverlay();
for (int i = 0; i < used; i++) {
final Range range = ranges.get(i);
batcher.copyArmorGlint(range.columns, range.start, range.end, inPlace);
}
valid = false;
return true;
}
boolean queueBothLayers(ModelPartBatcher batcher, RetainedTesrGroups groups, ResourceLocation glintTexture) {
if (!baseStillValid(groups) || !sameLightAndOverlay() || !batcher.prepareSkippedArmorGlint(glintTexture)) return false;
for (int i = 0; i < used; i++) {
final Range range = ranges.get(i);
batcher.queueArmorGlintLayers(range.columns, range.source, range.start, range.end);
}
valid = false;
return true;
}
private boolean baseStillValid(RetainedTesrGroups groups) {
if (!valid || !base || owner != groups || pass != groups.captureVersion() || !groups.canCopyInstances(EntityMaterials.GLINT) || !sameParentPose()) return false;
for (int i = 0; i < used; i++) {
final Range range = ranges.get(i);
if (!groups.captureStillValid(range.group) || range.end > range.columns.size) return false;
}
return true;
}
private boolean sameLightAndOverlay() {
return uniformLight && light == GLSMConfig.packedLastBrightness() && overlay == currentOverlay();
}
private static int currentColor() {
final var c = GLStateManager.getColor();
return AngelicaBufferSource.packAbgr(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
}
private static int currentOverlay() {
return AngelicaBufferSource.packAbgr(GLStateManager.getOverlayR(), GLStateManager.getOverlayG(), GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
}
}
@@ -11,6 +11,7 @@ public final class InstanceRing {
private OrphanStreamingBuffer orphanRing;
private boolean initialized;
private boolean usedThisFrame;
private boolean lastUploadKept;
private int bufferId;
public long upload(ByteBuffer data, int stride) {
@@ -23,15 +24,21 @@ public final class InstanceRing {
final int index = persistentRing.upload(data, stride);
if (index >= 0) {
bufferId = persistentRing.getBufferId();
lastUploadKept = true;
return (long) index * stride;
}
}
if (orphanRing == null) orphanRing = new OrphanStreamingBuffer();
orphanRing.upload(data);
bufferId = orphanRing.getBufferId();
lastUploadKept = false;
return 0;
}
public int keptUploadsEpoch() {
return lastUploadKept && persistentRing != null ? persistentRing.getWraps() : -1;
}
public int bufferId() {
return bufferId;
}
@@ -54,6 +61,7 @@ public final class InstanceRing {
}
initialized = false;
usedThisFrame = false;
lastUploadKept = false;
bufferId = 0;
}
}
@@ -65,10 +65,15 @@ final class InstancedTemplateRenderer {
}
int drawTemplates(InstanceColumns cols, TexRun run, long nowMs) {
return drawTemplates(cols, run, nowMs, false);
}
int drawTemplates(InstanceColumns cols, TexRun run, long nowMs, boolean recordUpload) {
final InstanceColumns data = run.source != null ? run.source : cols;
final long epoch = ++bucketEpochSource;
for (TexRun seg = run; seg != null; seg = seg.next) {
for (int i = seg.start, end = seg.end; i < end; i++) {
final TemplateBuffer template = cols.templates.get(i);
final TemplateBuffer template = data.templates.get(i);
final Bucket bucket;
if (template.bucketEpoch == epoch) {
bucket = liveBuckets.get(template.bucketIndex);
@@ -90,20 +95,31 @@ final class InstancedTemplateRenderer {
final IntArrayList indices = liveBuckets.get(b).indices;
for (int j = 0, m = indices.size(); j < m; j++) {
final int i = indices.getInt(j);
InstancedAttribs.writeHead(ptr, cols.matrices, i * 16, cols.colors.getInt(i), cols.overlays.getInt(i), cols.infos.getLong(i));
InstancedAttribs.writeLightmap(ptr + InstancedAttribs.OFFSET_LIGHTMAP, cols.lights.getInt(i));
final int color = run.source != null ? run.color : data.colors.getInt(i);
final long info = run.source != null ? 0L : data.infos.getLong(i);
InstancedAttribs.writeHead(ptr, data.matrices, i * 16, color, data.overlays.getInt(i), info);
InstancedAttribs.writeLightmap(ptr + InstancedAttribs.OFFSET_LIGHTMAP, data.lights.getInt(i));
ptr += InstancedAttribs.STRIDE;
}
}
final long ringBase = uploadStaging(base, ptr, InstancedAttribs.STRIDE);
if (recordUpload) {
run.uploadOffset = ringBase;
run.uploadMeshes.clear();
run.uploadCounts.clear();
}
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;
if (recordUpload) {
run.uploadMeshes.add(mesh);
run.uploadCounts.add(bucket.indices.size());
}
bindRingTo(mesh.vao, TEMPLATE_FLAGS);
InstancedAttribs.pointTemplate(recordOffset);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
@@ -114,28 +130,58 @@ final class InstancedTemplateRenderer {
bucket.mesh = null;
bucketPool.add(bucket);
}
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
liveBuckets.clear();
return draws;
}
void drawCubes(InstanceColumns cols, TexRun run) {
/**
* Draws a base run's recorded template upload again, every instance in one color.
*/
int redrawTemplates(TexRun base, int colorABGR) {
long recordOffset = base.uploadOffset;
neutralizeExtendedAttribs();
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);
bindRingTo(mesh.vao, TEMPLATE_FLAGS);
InstancedAttribs.pointTemplate(recordOffset);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
setConstantColor(colorABGR);
GLStateManager.glDrawArraysInstanced(mesh.drawMode, 0, mesh.vertexCount, count);
GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_COLOR);
recordOffset += (long) count * InstancedAttribs.STRIDE;
}
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
return base.uploadMeshes.size();
}
private static void setConstantColor(int colorABGR) {
GLStateManager.glDisableVertexAttribArray(InstancedAttribs.LOC_COLOR);
GLStateManager.glVertexAttrib4f(InstancedAttribs.LOC_COLOR, (colorABGR & 0xFF) / 255.0f, (colorABGR >>> 8 & 0xFF) / 255.0f, (colorABGR >>> 16 & 0xFF) / 255.0f, (colorABGR >>> 24) / 255.0f);
}
/** Returns the ring offset of the uploaded instances, for {@link #redrawCubes}. */
long drawCubes(InstanceColumns cols, TexRun run) {
final InstanceColumns data = run.source != null ? run.source : cols;
staging = MeshBuffer.ensureCapacity(staging, run.instances * CubeInstancedAttribs.STRIDE, false);
final long base = memAddress0(staging);
long ptr = base;
for (TexRun seg = run; seg != null; seg = seg.next) {
for (int i = seg.start, end = seg.end; i < end; i++) {
final int off = i * 16;
final CubeParams[] partCubes = cols.cubes.get(i);
final float scale = cols.scales.getFloat(i);
final int color = cols.colors.getInt(i);
final int light = cols.lights.getInt(i);
final int overlay = cols.overlays.getInt(i);
final long info = cols.infos.getLong(i);
final CubeParams[] partCubes = data.cubes.get(i);
final float scale = data.scales.getFloat(i);
final int color = run.source != null ? run.color : data.colors.getInt(i);
final int light = data.lights.getInt(i);
final int overlay = data.overlays.getInt(i);
final long info = run.source != null ? 0L : data.infos.getLong(i);
for (int p = 0, n = partCubes.length; p < n; p++) {
final CubeParams cube = partCubes[p];
cube.writeRows(ptr, cols.matrices, off, scale);
cube.writeRows(ptr, data.matrices, off, scale);
InstancedAttribs.writeTail(ptr, color, overlay, info);
InstancedAttribs.writeLightmap(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE, light);
cube.writeTexture(ptr);
@@ -149,9 +195,35 @@ 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;
}
boolean canRedraw(int ringEpoch, int buffer) {
return ringEpoch >= 0 && ringEpoch == ring.keptUploadsEpoch() && buffer == ring.bufferId();
}
int ringEpoch() {
return ring.keptUploadsEpoch();
}
int ringBuffer() {
return ring.bufferId();
}
void redrawCubes(long offset, int instances, int colorABGR) {
neutralizeExtendedAttribs();
bindRingTo(UnitCubeMesh.vao(), UnitCubeMesh.VERTEX_FLAGS);
CubeInstancedAttribs.pointInstanceAttribs(offset);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
setConstantColor(colorABGR);
GLStateManager.setFfpInstancing(Instancing.CUBE);
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, instances);
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_COLOR);
GLStateManager.glBindVertexArray(0);
}
Loaded 100 of 129 files, more files were not shown because too many files have changed in this diff. Show more