Fix Iris issues with instancing and batching (#2158)

This commit is contained in:
Eclipse-Sol authored and GitHub committed 2026-09-27 17:21:39 -07:00
1 parent 7bb5bfd45c
commit af599f292e
103 files changed
+4691 -535

No files matched your search

+1
View File
@@ -162,6 +162,7 @@ DependencyHandlerScope.of(dependencies).apply {
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
compileOnly("com.github.GTNewHorizons:OpenBlocks:1.12.19-GTNH:dev")
compileOnly("com.github.GTNewHorizons:Draconic-Evolution:1.5.30-GTNH:dev")
compileOnly("com.github.GTNewHorizons:Chisel:2.17.31-GTNH:dev")
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
@@ -86,7 +86,6 @@ public class DisplayListManager {
private static List<AccumulatedDraw> accumulatedDraws = null; // Accumulates quad draws for batching
private static AccumulatedDraw pendingDraw = null;
private static DisplayListCallback transformCallback = null;
private static int relativeTransformType;
private static final int TRANSFORM_SCALE = 0x1; // Can be extracted to a glScale easily
private static final int TRANSFORM_TRANSLATE = 0x2; // Can be extracted to a glTranslate easily
@@ -95,7 +94,7 @@ public class DisplayListManager {
private static StackTraceElement[] compilationStackTrace = null; // For logging: captured at glNewList()
// Debug logging: track sources of MULT_MATRIX commands and draw origins; only populated when LOG_DISPLAY_LIST_COMPILATION is true
private static List<String> pendingTransformOps = null; // Ops accumulated since last flush
private static List<List<String>> pendingTransformOps = null; // Ops per matrix slot since its last flush
private static List<List<String>> multMatrixSources = null; // Source ops for each MULT_MATRIX in raw buffer
private static List<String> drawRangeSources = null; // Source type for each DRAW_RANGE in final buffer
@@ -119,7 +118,7 @@ public class DisplayListManager {
int recordedStatePushes,
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
List<String> pendingOps,
List<List<String>> pendingOps,
List<List<String>> matrixSources,
List<String> drawSources
) {}
@@ -175,7 +174,9 @@ public class DisplayListManager {
static void flushAll() {
drawBarrier();
flushMatrix();
for (int slot = 0; slot < DisplayListCallback.SLOT_COUNT; slot++) {
flushMatrix(slot);
}
}
@@ -190,13 +191,21 @@ public class DisplayListManager {
}
/**
* Emit accumulated transform as MultMatrix if non-identity, then reset.
* Emit the current matrix target's accumulated transform as MultMatrix if non-identity, then reset.
*/
static void flushMatrix() {
if (transformCallback.isIdentity()) {
flushMatrix(transformCallback.getCurrentSlot());
}
/**
* Emit one matrix target's accumulated transform as MultMatrix if non-identity, then reset.
*/
private static void flushMatrix(int slot) {
if (transformCallback.isIdentity(slot)) {
// Clear pending ops even if we don't emit - they were no-ops (identity)
transformCallback.reset(slot);
if (pendingTransformOps != null) {
pendingTransformOps.clear();
pendingTransformOps.get(slot).clear();
}
return;
}
@@ -204,32 +213,48 @@ public class DisplayListManager {
// Save pending transform ops for logging (before we clear them)
if (multMatrixSources != null && pendingTransformOps != null) {
if (pendingTransformOps.isEmpty()) {
final List<String> ops = pendingTransformOps.get(slot);
if (ops.isEmpty()) {
GLStateManager.LOGGER.warn("flushMatrix: non-identity transform with no tracked ops");
multMatrixSources.add(Collections.singletonList("(unknown source)"));
} else {
multMatrixSources.add(new ArrayList<>(pendingTransformOps));
multMatrixSources.add(new ArrayList<>(ops));
}
pendingTransformOps.clear();
ops.clear();
}
// Record the collapsed MultMatrix command (for playback)
// Always put a draw barrier BEFORE flushing the matrix (the transforms are already baked into the current draw)
drawBarrier();
if (relativeTransformType == TRANSFORM_SCALE) {
currentRecorder.writeScale(transformCallback.getScale(transformVector));
} else if (relativeTransformType == TRANSFORM_TRANSLATE) {
currentRecorder.writeTranslate(transformCallback.getTranslation(transformVector));
syncStreamMatrixMode(transformCallback.getSlotMatrixMode(slot));
final Matrix4f pending = transformCallback.getReadMatrix(slot);
final int type = transformCallback.getTransformType(slot);
if (type == TRANSFORM_SCALE) {
currentRecorder.writeScale(transformCallback.getScale(slot, transformVector));
} else if (type == TRANSFORM_TRANSLATE) {
currentRecorder.writeTranslate(transformCallback.getTranslation(slot, transformVector));
} else {
currentRecorder.writeMultMatrix(transformCallback.getReadMatrix());
currentRecorder.writeMultMatrix(pending);
}
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
GLStateManager.applyMultMatrix(transformCallback.getReadMatrix());
GLStateManager.applyMultMatrix(pending);
}
// Reset to identity - we're now synchronized with GL
resetRelativeTransform();
transformCallback.reset(slot);
}
private static void syncStreamMatrixMode(int mode) {
if (transformCallback.getStreamMatrixMode() == 0 && !transformCallback.explicitMatrixMode) {
return;
}
if (transformCallback.getStreamMatrixMode() == mode) {
return;
}
drawBarrier();
transformCallback.setStreamMatrixMode(mode);
currentRecorder.writeMatrixMode(mode);
}
public static int getRecordingListId() {
@@ -319,6 +344,11 @@ public class DisplayListManager {
currentRecorder.writeSecondaryColor(r, g, b);
}
public static void recordVertexAttrib(int index, float x, float y, float z, float w) {
drawBarrier();
currentRecorder.writeVertexAttrib(index, x, y, z, w);
}
public static void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
drawBarrier();
currentRecorder.writeColorMask(r, g, b, a);
@@ -375,14 +405,37 @@ public class DisplayListManager {
}
public static void recordMatrixMode(int mode) {
flushMatrix(); // Matrix barrier: flush and reset
final int previous = transformCallback.getMatrixMode();
if (!transformCallback.explicitMatrixMode) {
// Pending transforms still inherit the caller's target until this explicit selection.
flushMatrix();
}
transformCallback.explicitMatrixMode = true;
if (previous == mode) {
// The initial target and the target after a nested list are unknown at playback.
syncStreamMatrixMode(mode);
return;
}
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
// Live GL follows the app's matrix mode, so a pending delta can only be replayed onto GL
// while its own target is still current.
flushAll();
} else {
// MODELVIEW and PROJECTION share the vertex delta, so a delta belonging to one of them has to
// be flushed before the other starts adding to it.
final int slot = DisplayListCallback.slotOf(mode);
if (transformCallback.getSlotMatrixMode(slot) != mode) {
flushMatrix(slot);
}
}
transformCallback.setMatrixMode(mode);
currentRecorder.writeMatrixMode(mode);
}
public static void recordPushMatrix() {
drawBarrier();
// Flush any pending delta, then record push.
flushMatrix();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writePushMatrix();
}
@@ -390,6 +443,7 @@ public class DisplayListManager {
// Discard any transformations & flush any pending draw operations
drawBarrier();
resetRelativeTransform();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writePopMatrix();
}
@@ -439,6 +493,8 @@ public class DisplayListManager {
}
public static void recordActiveTexture(int texture) {
drawBarrier();
flushMatrix(DisplayListCallback.SLOT_TEXTURE);
currentRecorder.writeActiveTexture(texture);
}
@@ -447,15 +503,31 @@ public class DisplayListManager {
currentRecorder.writeUseProgram(program);
}
// PushAttrib saves state but doesn't change it - not a draw barrier
private record AttribState(int matrixMode, boolean restoresTexture) {}
public static void recordPushAttrib(int mask) {
final boolean savesMode = (mask & GL11.GL_TRANSFORM_BIT) != 0;
if (savesMode) syncStreamMatrixMode(transformCallback.getMatrixMode());
transformCallback.attribStates.push(new AttribState(
savesMode ? transformCallback.getStreamMatrixMode() : -1, (mask & GL11.GL_TEXTURE_BIT) != 0));
currentRecorder.writePushAttrib(mask);
}
// PopAttrib restores potentially any state - draw barrier
public static void recordPopAttrib() {
drawBarrier();
final AttribState restored = transformCallback.attribStates.poll();
final int restoredMode = restored == null ? 0 : restored.matrixMode();
if (restoredMode != -1) flushAll();
else {
drawBarrier();
if (restored.restoresTexture()) flushMatrix(DisplayListCallback.SLOT_TEXTURE);
}
currentRecorder.writePopAttrib();
if (restoredMode != -1) {
transformCallback.setMatrixMode(restoredMode == 0 ? GL11.GL_MODELVIEW : restoredMode);
transformCallback.setStreamMatrixMode(restoredMode);
transformCallback.explicitMatrixMode = restoredMode != 0;
}
}
public static int virtualStateDepth(RecordMode mode) {
@@ -575,7 +647,10 @@ public class DisplayListManager {
public static void recordCallList(int listId) {
// Flush all pending draws & transformations
flushAll();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writeCallList(listId);
transformCallback.setStreamMatrixMode(0);
transformCallback.explicitMatrixMode = false;
}
public static void recordDrawBuffer(int mode) {
@@ -598,6 +673,19 @@ public class DisplayListManager {
currentRecorder.writeComplexCommand(cmd);
}
public static void recordStateCommand(DisplayListCommand cmd) {
drawBarrier();
currentRecorder.writeComplexCommand(cmd);
}
public static void recordStateCommandIfChanged(DisplayListCommand cmd) {
if (!currentRecorder.isLastCommand(cmd)) recordStateCommand(cmd);
}
public static void recordStateCommandOnce(DisplayListCommand cmd) {
if (currentRecorder.markRecordedOnce(cmd)) recordStateCommand(cmd);
}
public static void recordIndexedDrawCapture(IndexedDrawCapture capture) {
flushAll(); //TODO
currentRecorder.writeIndexedDrawCapture(capture);
@@ -606,12 +694,14 @@ public class DisplayListManager {
public static void recordLoadMatrix(Matrix4f matrix) {
drawBarrier();
resetRelativeTransform();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writeLoadMatrix(matrix);
}
public static void recordLoadIdentity() {
drawBarrier();
resetRelativeTransform();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writeLoadIdentity();
}
@@ -649,11 +739,10 @@ public class DisplayListManager {
}
public static void applyMatrixTranslation(float x, float y, float z) {
transformCallback.translate(x, y, z);
relativeTransformType |= TRANSFORM_TRANSLATE;
transformCallback.translate(x, y, z, TRANSFORM_TRANSLATE);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glTranslatef(%.4f, %.4f, %.4f)", x, y, z));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glTranslatef(%.4f, %.4f, %.4f)", x, y, z));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -662,11 +751,10 @@ public class DisplayListManager {
}
public static void applyMatrixScale(float x, float y, float z) {
transformCallback.scale(x, y, z);
relativeTransformType |= TRANSFORM_SCALE;
transformCallback.scale(x, y, z, TRANSFORM_SCALE);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glScalef(%.4f, %.4f, %.4f)", x, y, z));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glScalef(%.4f, %.4f, %.4f)", x, y, z));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -680,11 +768,10 @@ public class DisplayListManager {
* Requires the angle to be in radians & the coordinates to be normalized.
*/
public static void applyMatrixRotation(float rad, float x, float y, float z) {
transformCallback.rotate(rad, x, y, z);
relativeTransformType |= TRANSFORM_COMPLEX;
transformCallback.rotate(rad, x, y, z, TRANSFORM_COMPLEX);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glRotatef(%.4f, %.4f, %.4f, %.4f)", Math.toDegrees(rad), x, y, z));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glRotatef(%.4f, %.4f, %.4f, %.4f)", Math.toDegrees(rad), x, y, z));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -702,11 +789,10 @@ public class DisplayListManager {
* @param matrix The matrix to multiply
*/
public static void updateRelativeTransform(Matrix4f matrix) {
transformCallback.multMatrix(matrix);
relativeTransformType |= TRANSFORM_COMPLEX;
transformCallback.multMatrix(matrix, TRANSFORM_COMPLEX);
if (pendingTransformOps != null) {
pendingTransformOps.add("glMultMatrixf(...)");
pendingTransformOps.get(transformCallback.getCurrentSlot()).add("glMultMatrixf(...)");
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -720,11 +806,10 @@ public class DisplayListManager {
public static void updateRelativeTransformOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
orthoFrustumTemp.identity().ortho((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
transformCallback.multMatrix(orthoFrustumTemp);
relativeTransformType |= TRANSFORM_COMPLEX;
transformCallback.multMatrix(orthoFrustumTemp, TRANSFORM_COMPLEX);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glOrtho(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glOrtho(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -735,11 +820,10 @@ public class DisplayListManager {
public static void updateRelativeTransformFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
orthoFrustumTemp.identity().frustum((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
transformCallback.multMatrix(orthoFrustumTemp);
relativeTransformType |= TRANSFORM_COMPLEX;
transformCallback.multMatrix(orthoFrustumTemp, TRANSFORM_COMPLEX);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glFrustum(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glFrustum(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -753,8 +837,8 @@ public class DisplayListManager {
* subsequent transforms are relative to that loaded matrix (i.e., start from identity).
*/
static void resetRelativeTransform() {
transformCallback.setIdentity();
relativeTransformType = 0;
transformCallback.reset(transformCallback.getCurrentSlot());
if (pendingTransformOps != null) pendingTransformOps.get(transformCallback.getCurrentSlot()).clear();
}
/**
@@ -829,6 +913,9 @@ public class DisplayListManager {
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
pendingTransformOps = new ArrayList<>();
for (int slot = 0; slot < DisplayListCallback.SLOT_COUNT; slot++) {
pendingTransformOps.add(new ArrayList<>());
}
multMatrixSources = new ArrayList<>();
drawRangeSources = new ArrayList<>();
} else {
@@ -901,10 +988,8 @@ public class DisplayListManager {
flushAll();
// Reset back to MODELVIEW (default state)
if (transformCallback.getMatrixMode() != GL11.GL_MODELVIEW) {
recordMatrixMode(GL11.GL_MODELVIEW);
}
transformCallback.setMatrixMode(GL11.GL_MODELVIEW);
syncStreamMatrixMode(GL11.GL_MODELVIEW);
final CompiledDisplayList compiled;
// Create CompiledDisplayList with both unoptimized and optimized versions
@@ -1399,12 +1484,32 @@ public class DisplayListManager {
private static final class DisplayListCallback extends VertexTransformCallback {
static final int SLOT_VERTEX = 0; // GL_MODELVIEW + GL_PROJECTION, feeds into gl_Position
static final int SLOT_TEXTURE = 1;
static final int SLOT_COLOR = 2;
static final int SLOT_OTHER = 3;
static final int SLOT_COUNT = 4;
static int slotOf(int mode) {
return switch (mode) {
case GL11.GL_MODELVIEW, GL11.GL_PROJECTION -> SLOT_VERTEX;
case GL11.GL_TEXTURE -> SLOT_TEXTURE;
case GL11.GL_COLOR -> SLOT_COLOR;
default -> SLOT_OTHER;
};
}
private int matrixMode = GL11.GL_MODELVIEW;
private int streamMatrixMode;
private boolean explicitMatrixMode;
private final Deque<AttribState> attribStates = new ArrayDeque<>();
private final Matrix3f normalMatrix = new Matrix3f();
private final Matrix4f transformMatrix = new Matrix4f();
private final Matrix4f[] transformMatrices = { new Matrix4f(), new Matrix4f(), new Matrix4f(), new Matrix4f() };
private final int[] transformTypes = new int[SLOT_COUNT];
private final int[] slotModes = { GL11.GL_MODELVIEW, GL11.GL_TEXTURE, GL11.GL_COLOR, 0 };
private DirectTessellator bakedOwner;
private int bakedVertices;
@@ -1416,49 +1521,69 @@ public class DisplayListManager {
}
}
public void scale(float x, float y, float z) {
transformMatrix.scale(x, y, z);
private Matrix4f begin(int typeBits) {
final int slot = getCurrentSlot();
transformTypes[slot] |= typeBits;
slotModes[slot] = matrixMode;
return transformMatrices[slot];
}
public void scale(float x, float y, float z, int typeBits) {
begin(typeBits).scale(x, y, z);
markDirty();
}
public void translate(float x, float y, float z) {
transformMatrix.translate(x, y, z);
public void translate(float x, float y, float z, int typeBits) {
begin(typeBits).translate(x, y, z);
markDirty();
}
public void rotate(float rad, float x, float y, float z) {
transformMatrix.rotate(rad, x, y, z);
public void rotate(float rad, float x, float y, float z, int typeBits) {
begin(typeBits).rotate(rad, x, y, z);
markDirty();
}
public void multMatrix(Matrix4f matrix4f) {
transformMatrix.mul(matrix4f);
public void multMatrix(Matrix4f matrix4f, int typeBits) {
begin(typeBits).mul(matrix4f);
markDirty();
}
public void setIdentity() {
transformMatrix.identity();
markDirty();
public void reset(int slot) {
transformMatrices[slot].identity();
transformTypes[slot] = 0;
if (slot == SLOT_VERTEX) {
markDirty();
}
}
public Vector3f getScale(Vector3f dest) {
return transformMatrix.getScale(dest);
public Vector3f getScale(int slot, Vector3f dest) {
final Matrix4f matrix = transformMatrices[slot];
return dest.set(matrix.m00(), matrix.m11(), matrix.m22());
}
public Vector3f getTranslation(Vector3f dest) {
return transformMatrix.getTranslation(dest);
public Vector3f getTranslation(int slot, Vector3f dest) {
return transformMatrices[slot].getTranslation(dest);
}
/**
* WARNING: If you apply transformations to this matrix, make sure to call markDirty() afterward!
*/
public Matrix4f getReadMatrix() {
return transformMatrix;
public Matrix4f getReadMatrix(int slot) {
return transformMatrices[slot];
}
public int getTransformType(int slot) {
return transformTypes[slot];
}
/** The GL matrix target a slot's pending delta belongs to. */
public int getSlotMatrixMode(int slot) {
return slotModes[slot];
}
public void markDirty() {
if (matrixMode == GL11.GL_MODELVIEW) {
NormalHelper.getNormalMatrix(transformMatrix, normalMatrix);
NormalHelper.getNormalMatrix(transformMatrices[SLOT_VERTEX], normalMatrix);
}
}
@@ -1467,15 +1592,26 @@ public class DisplayListManager {
*/
public void setMatrixMode(int mode) {
this.matrixMode = mode;
markDirty();
}
public int getMatrixMode() {
return this.matrixMode;
}
public boolean isIdentity() {
return MatrixHelper.isIdentity(transformMatrix);
public int getCurrentSlot() {
return slotOf(matrixMode);
}
public int getStreamMatrixMode() {
return this.streamMatrixMode;
}
public void setStreamMatrixMode(int mode) {
this.streamMatrixMode = mode;
}
public boolean isIdentity(int slot) {
return MatrixHelper.isIdentity(transformMatrices[slot]);
}
@Override
@@ -1488,10 +1624,12 @@ public class DisplayListManager {
@Override
public boolean onDraw(CallbackTessellator tessellator) {
if (!tessellator.isEmpty()) {
if (!isIdentity()) {
if (!isIdentity(SLOT_VERTEX) || !isIdentity(SLOT_TEXTURE) || !isIdentity(SLOT_COLOR)) {
final int baked = bakedOwner == tessellator ? bakedVertices : 0;
if (bakedVertices == 0) {
flushMatrix();
flushMatrix(SLOT_VERTEX);
flushMatrix(SLOT_TEXTURE);
flushMatrix(SLOT_COLOR);
} else if (baked != tessellator.vertexCount) {
GLStateManager.warnOnce("dl-mixed-baked-raw", "Display list {}: draw of {} vertices carries {} transformed ones - the rest lose the pending transform", glListId, tessellator.vertexCount, baked);
}
@@ -1515,22 +1653,22 @@ public class DisplayListManager {
@Override
protected Matrix4f getMVPMatrix() {
return matrixMode <= GL11.GL_PROJECTION ? transformMatrix : null;
return transformTypes[SLOT_VERTEX] != 0 ? transformMatrices[SLOT_VERTEX] : null;
}
@Override
protected Matrix4f getTextureMatrix() {
return matrixMode == GL11.GL_TEXTURE ? transformMatrix : null;
return transformTypes[SLOT_TEXTURE] != 0 ? transformMatrices[SLOT_TEXTURE] : null;
}
@Override
protected Matrix4f getColorMatrix() {
return matrixMode == GL11.GL_COLOR ? transformMatrix : null;
return transformTypes[SLOT_COLOR] != 0 ? transformMatrices[SLOT_COLOR] : null;
}
@Override
protected Matrix3f getNormalMatrix() {
return matrixMode == GL11.GL_MODELVIEW ? normalMatrix : null; // GL_PROJECTION doesn't transform normals
return transformTypes[SLOT_VERTEX] != 0 && slotModes[SLOT_VERTEX] == GL11.GL_MODELVIEW ? normalMatrix : null;
}
}
}
@@ -107,6 +107,8 @@ public class GLContextState {
public int maxBoundImageUnit = 0;
public final StateSet[] attribSets = new StateSet[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public int attribDepth = 0;
public int batchStateGeneration;
public final int[] savedBatchStateGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public CowStateStack<?>[] stackById;
public CowDepths[] depthsById;
public int[] restoreBitById;
@@ -10,7 +10,6 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
@@ -23,6 +22,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
@@ -118,6 +118,7 @@ import java.nio.ShortBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
@@ -146,6 +147,32 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
@SuppressWarnings("unused") // Used in ASM
public class GLStateManager {
/** Called before a mutation, while deferred draws can still use their original state. */
public static void beforeUncapturedStateChange() {
if (BatchStateGuard.isSuspended()) return;
if (DisplayListManager.getRecordMode() == RecordMode.COMPILE) return;
BatchStateGuard.beforeChange();
ctx().batchStateGeneration++;
}
public static void beforeUncapturedCapabilityChange(int cap) {
if (cap == GL11.GL_FOG || cap == GL11.GL_SCISSOR_TEST || cap == GL11.GL_STENCIL_TEST
|| (cap == GL11.GL_TEXTURE_2D && getActiveTextureUnit() != 0)
|| cap == GL11.GL_TEXTURE_1D || cap == GL12.GL_TEXTURE_3D || cap == GL13.GL_TEXTURE_CUBE_MAP
|| (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES)
|| (cap >= GL11.GL_TEXTURE_GEN_S && cap <= GL11.GL_TEXTURE_GEN_Q)) {
beforeUncapturedStateChange();
}
}
private static void beforeMatrixChange() {
final GLContextState c = ctx();
if (c.matrixMode.getMode() == GL11.GL_PROJECTION
|| (c.matrixMode.getMode() == GL11.GL_TEXTURE && c.activeTextureUnit.getValue() != 0)) {
beforeUncapturedStateChange();
}
}
public static final Logger LOGGER = LogManager.getLogger("GLSM");
private static final Set<String> WARN_ONCE = ConcurrentHashMap.newKeySet();
@@ -167,7 +194,7 @@ public class GLStateManager {
return st != null ? st : primaryContext();
}
private static GLContextState primaryContext() {
public static GLContextState primaryContext() {
GLContextState p = primaryContext;
if (p != null) return p;
synchronized (GLStateManager.class) {
@@ -1387,6 +1414,7 @@ public class GLStateManager {
final boolean caching = isCachingEnabled();
final boolean bypass = !caching;
if (bypass || red != glCtx.blendState.getBlendColorR() || green != glCtx.blendState.getBlendColorG() || blue != glCtx.blendState.getBlendColorB() || alpha != glCtx.blendState.getBlendColorA()) {
beforeUncapturedStateChange();
glCtx.blendState.setBlendColorR(red);
glCtx.blendState.setBlendColorG(green);
glCtx.blendState.setBlendColorB(blue);
@@ -1624,6 +1652,7 @@ public class GLStateManager {
final boolean caching = isCachingEnabled();
final boolean bypass = !caching;
if (bypass || glCtx.blendState.getEquationRgb() != mode || glCtx.blendState.getEquationAlpha() != mode) {
beforeUncapturedStateChange();
glCtx.blendState.setEquationRgb(mode);
glCtx.blendState.setEquationAlpha(mode);
RENDER_BACKEND.blendEquation(mode);
@@ -1639,6 +1668,7 @@ public class GLStateManager {
final boolean caching = isCachingEnabled();
final boolean bypass = !caching;
if (bypass || glCtx.blendState.getEquationRgb() != modeRGB || glCtx.blendState.getEquationAlpha() != modeAlpha) {
beforeUncapturedStateChange();
glCtx.blendState.setEquationRgb(modeRGB);
glCtx.blendState.setEquationAlpha(modeAlpha);
RENDER_BACKEND.blendEquationSeparate(modeRGB, modeAlpha);
@@ -2205,6 +2235,7 @@ public class GLStateManager {
final int cachedBinding = textureUnit.getBinding();
if (cachedBinding != texture || (texture == 0 && textureUnit.getTarget() != target)) {
if (activeUnit != 0 || target != GL11.GL_TEXTURE_2D) beforeUncapturedStateChange();
if (Tracy.ENABLED) texBindMisses++;
RENDER_BACKEND.bindTexture(target, texture);
mod(textureUnit).setBinding(texture, target);
@@ -3121,18 +3152,24 @@ public class GLStateManager {
}
public static void glVertexAttrib2f(int index, float v0, float v1) {
RENDER_BACKEND.vertexAttrib2f(index, v0, v1);
glVertexAttrib4f(index, v0, v1, 0.0f, 1.0f);
}
public static void glVertexAttrib2s(int index, short v0, short v1) {
RENDER_BACKEND.vertexAttrib2f(index, v0, v1);
glVertexAttrib4f(index, v0, v1, 0.0f, 1.0f);
}
public static void glVertexAttrib3f(int index, float v0, float v1, float v2) {
RENDER_BACKEND.vertexAttrib3f(index, v0, v1, v2);
glVertexAttrib4f(index, v0, v1, v2, 1.0f);
}
public static void glVertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordVertexAttrib(index, v0, v1, v2, v3);
if (mode == RecordMode.COMPILE) return;
}
beforeUncapturedStateChange();
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
}
@@ -3651,6 +3688,7 @@ public class GLStateManager {
final float green = param.get(1);
final float blue = param.get(2);
beforeUncapturedStateChange();
mod(glCtx.fogState);
glCtx.fogState.getFogColor().set(red, green, blue);
glCtx.fogState.setFogAlpha(param.get(3));
@@ -3674,6 +3712,7 @@ public class GLStateManager {
final GLContextState glCtx = ctx();
final boolean caching = isCachingEnabled();
if (!caching || red != glCtx.fogState.getFogColor().x || green != glCtx.fogState.getFogColor().y || blue != glCtx.fogState.getFogColor().z || alpha != glCtx.fogState.getFogAlpha()) {
beforeUncapturedStateChange();
mod(glCtx.fogState);
glCtx.fogState.getFogColor().set(red, green, blue);
glCtx.fogState.setFogAlpha(alpha);
@@ -3699,10 +3738,10 @@ public class GLStateManager {
if (isCachingEnabled()) {
boolean changed = false;
switch (pname) {
case GL11.GL_FOG_DENSITY -> { if (!isCachingEnabled() || glCtx.fogState.getDensity() != param) { mod(glCtx.fogState).setDensity(param); changed = true; } }
case GL11.GL_FOG_START -> { if (!isCachingEnabled() || glCtx.fogState.getStart() != param) { mod(glCtx.fogState).setStart(param); changed = true; } }
case GL11.GL_FOG_END -> { if (!isCachingEnabled() || glCtx.fogState.getEnd() != param) { mod(glCtx.fogState).setEnd(param); changed = true; } }
case GL11.GL_FOG_MODE -> { if (!isCachingEnabled() || glCtx.fogState.getFogMode() != (int) param) { mod(glCtx.fogState).setFogMode((int) param); changed = true; } }
case GL11.GL_FOG_DENSITY -> { if (!isCachingEnabled() || glCtx.fogState.getDensity() != param) { beforeUncapturedStateChange(); mod(glCtx.fogState).setDensity(param); changed = true; } }
case GL11.GL_FOG_START -> { if (!isCachingEnabled() || glCtx.fogState.getStart() != param) { beforeUncapturedStateChange(); mod(glCtx.fogState).setStart(param); changed = true; } }
case GL11.GL_FOG_END -> { if (!isCachingEnabled() || glCtx.fogState.getEnd() != param) { beforeUncapturedStateChange(); mod(glCtx.fogState).setEnd(param); changed = true; } }
case GL11.GL_FOG_MODE -> { if (!isCachingEnabled() || glCtx.fogState.getFogMode() != (int) param) { beforeUncapturedStateChange(); mod(glCtx.fogState).setFogMode((int) param); changed = true; } }
default -> {}
}
if (changed) {
@@ -3725,6 +3764,7 @@ public class GLStateManager {
}
// Update cached state (FFP shader reads from cache)
if (isCachingEnabled() && pname == GL11.GL_FOG_MODE && (!isCachingEnabled() || glCtx.fogState.getFogMode() != param)) {
beforeUncapturedStateChange();
mod(glCtx.fogState).setFogMode(param);
glCtx.fragmentGeneration++;
if (GLSMHooks.FOG_STATE_CHANGE.hasListeners()) {
@@ -3979,6 +4019,7 @@ public class GLStateManager {
}
final int depthBefore = glCtx.attribDepth;
glCtx.attribSets[depthBefore] = set;
glCtx.savedBatchStateGen[depthBefore] = glCtx.batchStateGeneration;
snapshotGenerations(glCtx, set.bump);
@@ -4027,6 +4068,9 @@ public class GLStateManager {
public static void popState() {
final GLContextState glCtx = ctx();
if (glCtx.attribDepth > 0 && glCtx.savedBatchStateGen[glCtx.attribDepth - 1] != glCtx.batchStateGeneration) {
beforeUncapturedStateChange();
}
final StateSet set = glCtx.attribSets[glCtx.attribDepth - 1];
final boolean inspectAlpha = GLSMHooks.ALPHA_STATE_CHANGE.hasListeners() && (set.contains(glCtx.alphaState.stackId()) || set.contains(glCtx.alphaTest.stackId()));
final AlphaState effectiveAlpha = inspectAlpha ? glCtx.alphaState.readEffective(glCtx.restoredAlphaScratch) : null;
@@ -4418,6 +4462,7 @@ public class GLStateManager {
}
public static void glLoadMatrix(FloatBuffer m) {
beforeMatrixChange();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
final Matrix4f matrix = new Matrix4f().set(m);
@@ -4434,6 +4479,7 @@ public class GLStateManager {
}
public static void glLoadMatrix(DoubleBuffer m) {
beforeMatrixChange();
final GLContextState glCtx = ctx();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -4464,6 +4510,7 @@ public class GLStateManager {
}
public static void setTextureMatrix(int unit, Matrix4fc m) {
if (unit != 0) beforeUncapturedStateChange();
if (isCachingEnabled()) {
final GLContextState glCtx = ctx();
glCtx.textures.getTextureUnitMatrix(unit).set(m);
@@ -4513,6 +4560,7 @@ public class GLStateManager {
}
public static void glLoadIdentity() {
beforeMatrixChange();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordLoadIdentity();
@@ -4527,6 +4575,7 @@ public class GLStateManager {
}
public static void glTranslatef(float x, float y, float z) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixTranslation(x, y, z);
return;
@@ -4538,6 +4587,7 @@ public class GLStateManager {
}
public static void glTranslated(double x, double y, double z) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixTranslation((float) x, (float) y, (float) z);
return;
@@ -4549,6 +4599,7 @@ public class GLStateManager {
}
public static void glScalef(float x, float y, float z) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixScale(x, y, z);
return;
@@ -4560,6 +4611,7 @@ public class GLStateManager {
}
public static void glScaled(double x, double y, double z) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixScale((float) x, (float) y, (float) z);
return;
@@ -4571,6 +4623,7 @@ public class GLStateManager {
}
public static void glMultMatrix(FloatBuffer floatBuffer) {
beforeMatrixChange();
final GLContextState glCtx = ctx();
glCtx.multMatrix.set(floatBuffer);
final int currentMode = glCtx.matrixMode.getMode();
@@ -4586,6 +4639,7 @@ public class GLStateManager {
}
public static void applyMultMatrix(Matrix4f multMatrix) {
beforeMatrixChange();
if (isCachingEnabled()) {
getMatrixStack().mul(multMatrix);
bumpMatrixGeneration();
@@ -4593,6 +4647,7 @@ public class GLStateManager {
}
public static void glMultMatrix(DoubleBuffer matrix) {
beforeMatrixChange();
final GLContextState glCtx = ctx();
glCtx.conversionMatrix4d.set(matrix);
glCtx.conversionMatrix4f.set(glCtx.conversionMatrix4d);
@@ -4608,6 +4663,7 @@ public class GLStateManager {
}
public static void glRotatef(float angle, float x, float y, float z) {
beforeMatrixChange();
final float lenSq = x * x + y * y + z * z;
if (lenSq == 0.0f) return;
@@ -4630,6 +4686,7 @@ public class GLStateManager {
}
public static void glRotated(double angle, double x, double y, double z) {
beforeMatrixChange();
final double lenSq = x * x + y * y + z * z;
if (lenSq == 0.0) return;
@@ -4652,6 +4709,7 @@ public class GLStateManager {
}
public static void glOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.updateRelativeTransformOrtho(left, right, bottom, top, zNear, zFar);
return;
@@ -4663,6 +4721,7 @@ public class GLStateManager {
}
public static void glFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.updateRelativeTransformFrustum(left, right, bottom, top, zNear, zFar);
return;
@@ -4692,6 +4751,7 @@ public class GLStateManager {
}
public static void glPopMatrix() {
beforeMatrixChange();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordPopMatrix(); // Handles flush + relativeTransform stack + lastRecordedTransform sync
@@ -4787,6 +4847,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
RENDER_BACKEND.viewport(x, y, width, height);
// Only update cached state when caching is enabled
if (isCachingEnabled()) {
@@ -4905,6 +4966,7 @@ public class GLStateManager {
return;
}
}
if (getActiveTextureUnit() != 0) beforeUncapturedStateChange();
if (target != GL11.GL_TEXTURE_2D) {
RENDER_BACKEND.texParameteri(target, pname, param);
return;
@@ -4948,6 +5010,7 @@ public class GLStateManager {
return;
}
}
if (getActiveTextureUnit() != 0) beforeUncapturedStateChange();
if (target != GL11.GL_TEXTURE_2D) {
RENDER_BACKEND.texParameterf(target, pname, param);
return;
@@ -5194,6 +5257,7 @@ public class GLStateManager {
public static void glBindSampler(int unit, int sampler) {
final SamplerUnitArray units = ctx().samplerUnits;
if (isCachingEnabled() && unit >= 0 && unit < units.size() && units.get(unit) == sampler) return;
beforeUncapturedStateChange();
if (!units.set(unit, sampler)) {
warnOnce("samplerUnitRange", "glBindSampler unit {} exceeds GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS {}", unit, units.size());
}
@@ -5646,6 +5710,10 @@ public class GLStateManager {
}
// Non-zero program or FFP emulation not enabled
if (program == 0) {
warnOnce("useprogram-zero-ffp-disabled",
"[FFP] glUseProgram(0) reached the driver because emulation is disabled");
}
if (ffp.isActive()) {
ffp.deactivate();
}
@@ -6138,6 +6206,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
RENDER_BACKEND.scissor(x, y, width, height);
if (isCachingEnabled()) {
mod(ctx().scissorState).setScissor(x, y, width, height);
@@ -6156,6 +6225,7 @@ public class GLStateManager {
final int clampedMask = mask & glCtx.stencilBitMask;
final boolean caching = isCachingEnabled();
if (!caching || glCtx.stencilState.getFuncFront() != func || glCtx.stencilState.getRefFront() != ref || glCtx.stencilState.getValueMaskFront() != clampedMask) {
beforeUncapturedStateChange();
mod(glCtx.stencilState).setFunc(func, ref, clampedMask);
RENDER_BACKEND.stencilFunc(func, ref, clampedMask);
}
@@ -6173,6 +6243,7 @@ public class GLStateManager {
final int clampedMask = mask & glCtx.stencilBitMask;
final boolean caching = isCachingEnabled();
if (!caching || glCtx.stencilState.getWriteMaskFront() != clampedMask) {
beforeUncapturedStateChange();
mod(glCtx.stencilState).setWriteMask(clampedMask);
RENDER_BACKEND.stencilMask(clampedMask);
}
@@ -6189,6 +6260,7 @@ public class GLStateManager {
}
final boolean caching = isCachingEnabled();
if (!caching || glCtx.stencilState.getFailOpFront() != fail || glCtx.stencilState.getZFailOpFront() != zfail || glCtx.stencilState.getZPassOpFront() != zpass) {
beforeUncapturedStateChange();
mod(glCtx.stencilState).setOp(fail, zfail, zpass);
RENDER_BACKEND.stencilOp(fail, zfail, zpass);
}
@@ -6244,6 +6316,7 @@ public class GLStateManager {
final int index = plane - GL11.GL_CLIP_PLANE0;
if (index < 0 || index >= MAX_CLIP_PLANES) return;
final int pos = equation.position();
beforeUncapturedStateChange();
glCtx.clipPlaneState.setPlane(index, equation.get(pos), equation.get(pos + 1), equation.get(pos + 2), equation.get(pos + 3), glCtx.modelViewMatrix);
glCtx.clipPlaneGeneration++;
}
@@ -6315,6 +6388,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
final int clampedMask = mask & glCtx.stencilBitMask;
final boolean caching = isCachingEnabled();
boolean needsUpdate = !caching;
@@ -6351,6 +6425,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
final int clampedMask = mask & glCtx.stencilBitMask;
final boolean caching = isCachingEnabled();
boolean needsUpdate = !caching;
@@ -6383,6 +6458,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
final boolean caching = isCachingEnabled();
boolean needsUpdate = !caching;
if (!needsUpdate) {
@@ -7149,7 +7225,7 @@ public class GLStateManager {
glCtx.ffp.setTexCoord(s, t, 0.0f, 1.0f);
glCtx.dirtyTexCoordAttrib = true;
} else if (target == GL13.GL_TEXTURE1) {
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
if (ImmediateModeRecorder.isDrawing()) {
ImmediateModeRecorder.setLightmapCoord(s, t);
return;
}
@@ -7160,6 +7236,7 @@ public class GLStateManager {
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
glCtx.unit23TexCoordSetDuringDraw = true;
}
beforeUncapturedStateChange();
glCtx.ffp.setTexCoord(unit, s, t, 0.0f, 1.0f);
}
}
@@ -7175,6 +7252,11 @@ public class GLStateManager {
public static void setLightmapTextureCoords(int unit, float x, float y) {
if (unit == GL13.GL_TEXTURE1) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordStateCommand(() -> setLightmapTextureCoords(unit, x, y));
if (mode == RecordMode.COMPILE) return;
}
final GLContextState glCtx = ctx();
glCtx.lastBrightnessX = x;
glCtx.lastBrightnessY = y;
@@ -7253,6 +7335,7 @@ public class GLStateManager {
public static void glTexEnv(int target, int pname, FloatBuffer params) {
final GLContextState glCtx = ctx();
if (target == GL11.GL_TEXTURE_ENV && pname == GL11.GL_TEXTURE_ENV_COLOR && params.remaining() >= 4) {
beforeUncapturedStateChange();
final int pos = params.position();
final var envState = glCtx.textures.getTexEnvState(glCtx.activeTextureUnit.getValue());
envState.beforeModify();
@@ -7283,6 +7366,7 @@ public class GLStateManager {
* GL_TEXTURE_ENV parameters including GL_COMBINE sub-params are tracked in per-unit TexEnvState.
*/
private static void handleTexEnvScalar(int target, int pname, float param) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (target == GL14.GL_TEXTURE_FILTER_CONTROL) {
// LOD bias via legacy glTexEnv path - remap to core-profile glTexParameterf
@@ -7429,6 +7513,7 @@ public class GLStateManager {
public static void glTexGeni(int coord, int pname, int param) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (!isCachingEnabled()) return;
if (pname == GL11.GL_TEXTURE_GEN_MODE) {
@@ -7448,6 +7533,7 @@ public class GLStateManager {
}
public static void glTexGen(int coord, int pname, FloatBuffer params) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (!isCachingEnabled()) return;
final int unit = glCtx.activeTextureUnit.getValue();
@@ -7478,6 +7564,7 @@ public class GLStateManager {
}
public static void glTexGen(int coord, int pname, DoubleBuffer params) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (!isCachingEnabled()) return;
final int pos = params.position();
@@ -7504,6 +7591,7 @@ public class GLStateManager {
}
public static void glTexGen(int coord, int pname, IntBuffer params) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (!isCachingEnabled()) return;
if (pname == GL11.GL_TEXTURE_GEN_MODE) {
@@ -7672,6 +7760,14 @@ public class GLStateManager {
RENDER_BACKEND.deleteShader(vertShader);
}
@Getter
private static long programLifetimeGeneration;
private static final IntOpenHashSet programsPendingDeletion = new IntOpenHashSet();
public static boolean isProgramPendingDeletion(int program) {
return programsPendingDeletion.contains(program);
}
public static void glDeleteProgram(int program) {
if (program == 0) return;
CompatUniformManager.onDeleteProgram(program);
@@ -7683,6 +7779,9 @@ public class GLStateManager {
GLSMHooks.PROGRAM_DELETE.post(GLSMHooks.programDeleteEvent);
}
RENDER_BACKEND.deleteProgram(program);
programsPendingDeletion.add(program);
programsPendingDeletion.removeIf((int id) -> !RENDER_BACKEND.isProgram(id));
programLifetimeGeneration++;
}
public static int glCreateShader(int type) {
@@ -7694,7 +7793,10 @@ public class GLStateManager {
}
public static int glCreateProgram() {
return RENDER_BACKEND.createProgram();
final int program = RENDER_BACKEND.createProgram();
programsPendingDeletion.remove(program);
programLifetimeGeneration++;
return program;
}
public static void glAttachShader(int program, int shader) {
@@ -7742,7 +7844,9 @@ public class GLStateManager {
}
public static boolean glIsProgram(int obj) {
return RENDER_BACKEND.isProgram(obj);
final boolean valid = RENDER_BACKEND.isProgram(obj);
if (!valid) programsPendingDeletion.remove(obj);
return valid;
}
public static boolean glIsShader(int obj) {
@@ -496,10 +496,12 @@ public class RenderSystem {
}
public static void bindTextureToUnit(int unit, int texture) {
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
dsaState.bindTextureToUnit(unit, texture);
}
public static void bindTextureToUnit(int target, int unit, int texture) {
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
dsaState.bindTextureToUnit(target, unit, texture);
}
@@ -4,6 +4,7 @@ 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.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -226,8 +227,13 @@ public final class FfpExtendedAttribs {
}
public static void setNeutralCurrentValues() {
GLStateManager.glVertexAttrib2f(ImmediateExtendedAttribHandler.LOC_MID_TEX, 0.5f, 0.5f);
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
BatchStateGuard.suspend();
try {
GLStateManager.glVertexAttrib2f(ImmediateExtendedAttribHandler.LOC_MID_TEX, 0.5f, 0.5f);
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
} finally {
BatchStateGuard.resume();
}
}
public static void onDeleteBuffer(GLContextState glCtx, int vboId) {
@@ -55,13 +55,18 @@ public final class InstancedAttribs {
return (entity & 0xFFFFL) | ((blockEntity & 0xFFFFL) << 16) | ((item & 0xFFFFL) << 32);
}
public static int unpackId(long info, int shift) {
final int id = (int) (info >>> shift) & 0xFFFF;
return id == 0xFFFF ? -1 : id;
}
public static void pointHead(long base, int stride) {
for (int r = 0; r < 3; r++) {
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
}
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_SHORT, stride, base + OFFSET_ENTITY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_UNSIGNED_SHORT, stride, base + OFFSET_ENTITY);
}
public static void pointTemplate(long base) {
@@ -102,7 +102,11 @@ public class Program {
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) backend.uniform1i(ffpProgram.locSampler[i], i);
}
backend.useProgram(previousProgram);
if (previousProgram != 0) {
backend.useProgram(previousProgram);
} else {
ShaderManager.getInstance().invalidateProgram();
}
return ffpProgram;
} catch (RuntimeException e) {
@@ -116,9 +116,10 @@ public final class ShaderManager {
public void activate() {
active = true;
lastBoundProgramId = GLStateManager.getActiveProgram();
invalidateProgram();
}
/** Invalidate both binding and state key so the next draw selects and binds an FFP variant. */
public void invalidateProgram() {
lastBoundProgramId = -1;
currentProgram = null;
@@ -170,10 +171,11 @@ public final class ShaderManager {
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
currentFKLen = fkLen;
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
// Building a variant can invalidate the previous binding and key.
currentVertexKeyPacked = vkPacked;
final int programId = currentProgram.getProgramId();
if (programId != lastBoundProgramId) {
if (Tracy.ENABLED) variantSwitches++;
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
/** Drains deferred geometry before changing state that is not stored with each batch. */
public final class BatchStateGuard {
public static Runnable flush;
private static int suspended;
private BatchStateGuard() {}
public static void suspend() { suspended++; }
public static void resume() { suspended--; }
public static boolean isSuspended() { return suspended != 0; }
public static void beforeChange() {
if (flush == null || suspended != 0 || !GLStateManager.isMainThread()
|| DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.COMPILE) return;
final CommandRecorder recorder = DisplayListManager.isRecording() ? DisplayListManager.pauseRecording() : null;
suspend();
try {
flush.run();
} finally {
resume();
if (recorder != null) DisplayListManager.resumeRecording(recorder);
}
}
}
@@ -97,7 +97,8 @@ public final class CommandBufferExecutor {
ptr += 4;
}
case GLCommand.USE_PROGRAM -> {
GLStateManager.glUseProgram(memGetInt(ptr));
final int recordedProgram = memGetInt(ptr);
GLStateManager.glUseProgram(compiledDisplayList.resolveProgram(recordedProgram));
ptr += 4;
}
case GLCommand.PUSH_ATTRIB -> {
@@ -252,6 +253,11 @@ public final class CommandBufferExecutor {
}
// === Float commands ===
case GLCommand.VERTEX_ATTRIB -> {
GLStateManager.glVertexAttrib4f(memGetInt(ptr), memGetFloat(ptr + 4), memGetFloat(ptr + 8),
memGetFloat(ptr + 12), memGetFloat(ptr + 16));
ptr += 20;
}
case GLCommand.POINT_SIZE -> {
GLStateManager.glPointSize(memGetFloat(ptr));
ptr += 4;
@@ -10,6 +10,8 @@ import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
@@ -21,6 +23,13 @@ public final class CommandRecorder {
private long writePointer;
private final ArrayList<DisplayListCommand> complexObjects = new ArrayList<>();
private final IndexedDrawBatchBuilder indexedDraws = new IndexedDrawBatchBuilder();
private Set<DisplayListCommand> onceCommands;
private DisplayListCommand lastComplexCommand;
private int lastComplexCommandEnd = -1;
public boolean isLastCommand(DisplayListCommand command) {
return size() == lastComplexCommandEnd && command.equals(lastComplexCommand);
}
public CommandRecorder() {
this(DEFAULT_CAPACITY);
@@ -32,6 +41,11 @@ public final class CommandRecorder {
this.writePointer = basePointer;
}
public boolean markRecordedOnce(DisplayListCommand command) {
if (onceCommands == null) onceCommands = new HashSet<>();
return onceCommands.add(command);
}
// === Low-level write operations ===
private void writeInt(int value) {
@@ -364,6 +378,16 @@ public final class CommandRecorder {
writeFloat(b);
}
public void writeVertexAttrib(int index, float x, float y, float z, float w) {
ensureCapacity(24);
writeInt(GLCommand.VERTEX_ATTRIB);
writeInt(index);
writeFloat(x);
writeFloat(y);
writeFloat(z);
writeFloat(w);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
@@ -649,6 +673,8 @@ public final class CommandRecorder {
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
lastComplexCommand = cmd;
lastComplexCommandEnd = size();
}
// === Lifecycle ===
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -35,6 +36,24 @@ public final class CompiledDisplayList {
private final DisplayListCommand[] complexObjects; // Complex commands (TexImage2D, etc.)
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
private Int2IntMap replayPrograms;
private long programLifetimeGeneration = -1;
int resolveProgram(int program) {
if (program == 0) return 0;
final long generation = GLStateManager.getProgramLifetimeGeneration();
if (replayPrograms == null) replayPrograms = new Int2IntOpenHashMap();
if (programLifetimeGeneration != generation) {
replayPrograms.clear();
programLifetimeGeneration = generation;
}
if (replayPrograms.containsKey(program)) return replayPrograms.get(program);
final boolean valid = GLStateManager.glIsProgram(program);
if (!valid || !GLStateManager.isProgramPendingDeletion(program)) {
replayPrograms.put(program, valid ? program : 0);
}
return valid ? program : 0;
}
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
this.commandBuffer = commandBuffer;
@@ -24,9 +24,9 @@ public final class GLCommand {
public static final int USE_PROGRAM = 11; // [cmd:4][program:4]
public static final int PUSH_ATTRIB = 12; // [cmd:4][mask:4]
public static final int POP_ATTRIB = 13; // [cmd:4][unused:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4][matrixMode:4]
public static final int PUSH_MATRIX = 15; // [cmd:4][matrixMode:4]
public static final int POP_MATRIX = 16; // [cmd:4][matrixMode:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4]
public static final int PUSH_MATRIX = 15; // [cmd:4]
public static final int POP_MATRIX = 16; // [cmd:4]
public static final int STENCIL_MASK = 17; // [cmd:4][mask:4]
public static final int DEPTH_MASK = 18; // [cmd:4][flag:4] (0 or 1)
public static final int FRONT_FACE = 19; // [cmd:4][mode:4]
@@ -62,6 +62,7 @@ public final class GLCommand {
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int SECONDARY_COLOR = 57; // [cmd:4][r:4f][g:4f][b:4f]
public static final int VERTEX_ATTRIB = 58; // [cmd:4][index:4][x:4f][y:4f][z:4f][w:4f]
// === Mixed int+float commands ===
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
@@ -73,14 +74,13 @@ public final class GLCommand {
public static final int MATERIALF = 66; // [cmd:4][face:4][pname:4][param:4f]
public static final int TEX_PARAMETERF = 67; // [cmd:4][target:4][pname:4][param:4f]
// === Double commands ===
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int TRANSLATE = 70; // [cmd:4][x:4f][y:4f][z:4f] = 16 bytes
public static final int SCALE = 72; // [cmd:4][x:4f][y:4f][z:4f] = 16 bytes
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
// === Matrix commands (72 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
// === Matrix commands (68 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][m00-m33:64f] = 68 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][m00-m33:64f] = 68 bytes
// === Buffer commands (inline 4 floats max) ===
public static final int FOG = 90; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
@@ -157,6 +157,7 @@ public final class GLCommand {
case CLEAR_COLOR -> "CLEAR_COLOR";
case BLEND_COLOR -> "BLEND_COLOR";
case SECONDARY_COLOR -> "SECONDARY_COLOR";
case VERTEX_ATTRIB -> "VERTEX_ATTRIB";
case ALPHA_FUNC -> "ALPHA_FUNC";
case FOGF -> "FOGF";
case LIGHTF -> "LIGHTF";
@@ -221,6 +222,8 @@ public final class GLCommand {
GLCommand.SCISSOR,
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
case GLCommand.VERTEX_ATTRIB -> 24;
// DRAW_RANGE_RESTORE: 28 bytes
case GLCommand.DRAW_RANGE_RESTORE -> 28;
@@ -46,6 +46,7 @@ public class BooleanState implements ISettableState<BooleanState> {
public void setEnabled(boolean enabled) {
final boolean bypass = !GLStateManager.isCachingEnabled();
if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
GLStateManager.beforeUncapturedCapabilityChange(glCap);
stateUnknown = false;
this.enabled = enabled;
onEnabledChanged();
@@ -0,0 +1,246 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
class DisplayListMatrixRestorationTest extends DisplayListTestFixture {
@Test
void flushedScalesPreserveNegativeComponentsInEveryMatrixTarget() {
for (int compileMode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
for (int target : new int[] { GL11.GL_MODELVIEW, GL11.GL_PROJECTION, GL11.GL_TEXTURE }) {
resetMatrices();
int list = newList();
GLStateManager.glNewList(list, compileMode);
GLStateManager.glMatrixMode(target);
GLStateManager.glScalef(-2, 3, -4);
GLStateManager.glEndList();
resetMatrices();
GLStateManager.glCallList(list);
FloatBuffer matrix = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(switch (target) {
case GL11.GL_PROJECTION -> GL11.GL_PROJECTION_MATRIX;
case GL11.GL_TEXTURE -> GL11.GL_TEXTURE_MATRIX;
default -> GL11.GL_MODELVIEW_MATRIX;
}, matrix);
assertEquals(-2f, matrix.get(0));
assertEquals(3f, matrix.get(5));
assertEquals(-4f, matrix.get(10));
}
}
}
@BeforeEach
@AfterEach
void resetMatrices() {
for (int unit = 0; unit < 2; unit++) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
}
private static float translation(int target) {
FloatBuffer b = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(target, b);
return b.get(12);
}
@Test
void transformAttributesSaveAndRestoreLazyMode() {
for (int mask : new int[] { GL11.GL_TRANSFORM_BIT, GL11.GL_ALL_ATTRIB_BITS }) {
for (int compileMode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
int list = newList();
GLStateManager.glNewList(list, compileMode);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glPushAttrib(mask);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(3, 0, 0);
GLStateManager.glPopAttrib();
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(9, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glCallList(list);
assertEquals(3f, translation(GL11.GL_MODELVIEW_MATRIX));
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX));
}
}
}
@Test
void nestedAttributesRestoreInheritedCallerMode() {
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPopAttrib();
GLStateManager.glLoadIdentity();
GLStateManager.glPopAttrib();
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glTranslatef(7, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(9, 0, 0);
GLStateManager.glCallList(list);
assertEquals(5f, translation(GL11.GL_MODELVIEW_MATRIX));
assertEquals(0f, translation(GL11.GL_PROJECTION_MATRIX));
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX));
assertEquals(GL11.GL_TEXTURE, GLStateManager.glGetInteger(GL11.GL_MATRIX_MODE));
}
@Test
void implicitMatrixCommandsAndEmptyListsInheritCallerTarget() {
int empty = newList();
GLStateManager.glNewList(empty, GL11.GL_COMPILE);
GLStateManager.glEndList();
int load = newList();
GLStateManager.glNewList(load, GL11.GL_COMPILE);
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(9, 0, 0);
GLStateManager.glCallList(empty);
assertEquals(GL11.GL_TEXTURE, GLStateManager.glGetInteger(GL11.GL_MATRIX_MODE));
assertEquals(9f, translation(GL11.GL_TEXTURE_MATRIX));
GLStateManager.glCallList(load);
assertEquals(GL11.GL_TEXTURE, GLStateManager.glGetInteger(GL11.GL_MATRIX_MODE));
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX));
assertEquals(5f, translation(GL11.GL_MODELVIEW_MATRIX));
}
@Test
void explicitInitialModelviewSurvivesReplayFromTexture() {
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(9, 0, 0);
GLStateManager.glCallList(list);
assertEquals(0f, translation(GL11.GL_MODELVIEW_MATRIX));
assertEquals(9f, translation(GL11.GL_TEXTURE_MATRIX));
}
@ParameterizedTest
@ValueSource(ints = { GL11.GL_MODELVIEW, GL11.GL_PROJECTION, GL11.GL_TEXTURE })
void transformsBeforeExplicitModeInheritCallerTarget(int explicitTarget) {
for (int compileMode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
resetMatrices();
int list = newList();
GLStateManager.glNewList(list, compileMode);
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glMatrixMode(explicitTarget);
GLStateManager.glTranslatef(7, 0, 0);
GLStateManager.glEndList();
for (int callerTarget : new int[] { GL11.GL_MODELVIEW, GL11.GL_PROJECTION, GL11.GL_TEXTURE }) {
resetMatrices();
GLStateManager.glMatrixMode(callerTarget);
GLStateManager.glCallList(list);
String context = "compileMode=" + compileMode + ", callerTarget=" + callerTarget;
assertEquals((callerTarget == GL11.GL_MODELVIEW ? 5f : 0f) + (explicitTarget == GL11.GL_MODELVIEW ? 7f : 0f),
translation(GL11.GL_MODELVIEW_MATRIX), context);
assertEquals((callerTarget == GL11.GL_PROJECTION ? 5f : 0f) + (explicitTarget == GL11.GL_PROJECTION ? 7f : 0f),
translation(GL11.GL_PROJECTION_MATRIX), context);
assertEquals((callerTarget == GL11.GL_TEXTURE ? 5f : 0f) + (explicitTarget == GL11.GL_TEXTURE ? 7f : 0f),
translation(GL11.GL_TEXTURE_MATRIX), context);
}
}
}
@ParameterizedTest
@ValueSource(ints = { GL11.GL_TEXTURE_BIT, GL11.GL_TEXTURE_BIT | GL11.GL_TRANSFORM_BIT, GL11.GL_ALL_ATTRIB_BITS })
void textureAttributesFlushTransformsBeforeRestoringUnit(int mask) {
for (int compileMode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
resetMatrices();
int list = newList();
GLStateManager.glNewList(list, compileMode);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glPushAttrib(mask);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(7, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glTranslatef(3, 0, 0);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glPopAttrib();
GLStateManager.glPopAttrib();
GLStateManager.glEndList();
resetMatrices();
GLStateManager.glCallList(list);
String context = "compileMode=" + compileMode;
assertEquals(GL13.GL_TEXTURE0, GLStateManager.glGetInteger(GL13.GL_ACTIVE_TEXTURE), context);
assertEquals(3f, translation(GL11.GL_MODELVIEW_MATRIX), context);
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX), context);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(7f, translation(GL11.GL_TEXTURE_MATRIX), context);
}
}
@Test
void pendingTextureTransformStaysOnOriginalUnit() {
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(7, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glEndList();
GLStateManager.glCallList(list);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
assertEquals(7f, translation(GL11.GL_TEXTURE_MATRIX));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX));
}
@Test
void explicitModeAfterNestedListIsNotDeduplicated() {
int child = newList();
GLStateManager.glNewList(child, GL11.GL_COMPILE);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glEndList();
int parent = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(child);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glCallList(parent);
assertEquals(5f, translation(GL11.GL_MODELVIEW_MATRIX));
}
}
@@ -206,7 +206,7 @@ class InstancedFFPDrawGLTest {
assertPixel(51, 50, 255, 0, 0, "entity -1 must render red");
assertPixel(150, 50, 0, 255, 0, "entity 7 must render green");
assertPixel(249, 50, 0, 0, 255, "entity 42 must render blue");
assertPixel(249, 50, 0, 0, 255, "entity 50020 must render blue");
}
private static void assertPixel(int x, int y, int r, int g, int b, String label) {
@@ -30,16 +30,17 @@ public final class FfpFixture {
layout(location = 5) in vec4 a_InstRow0;
layout(location = 6) in vec4 a_InstRow1;
layout(location = 7) in vec4 a_InstRow2;
layout(location = 11) in ivec4 iris_Entity;
layout(location = 11) in uvec4 iris_Entity;
flat out vec3 v_Color;
void main() {
""";
private static final String VS_TAIL = """
gl_Position = instMV * vec4(a_Position, 1.0);
if (iris_Entity.x == -1) { v_Color = vec3(1.0, 0.0, 0.0); }
else if (iris_Entity.x == 7) { v_Color = vec3(0.0, 1.0, 0.0); }
else { v_Color = vec3(0.0, 0.0, 1.0); }
if (iris_Entity.x == 65535u) { v_Color = vec3(1.0, 0.0, 0.0); }
else if (iris_Entity.x == 7u) { v_Color = vec3(0.0, 1.0, 0.0); }
else if (iris_Entity.x == 50020u) { v_Color = vec3(0.0, 0.0, 1.0); }
else { v_Color = vec3(1.0, 0.0, 1.0); }
}
""";
@@ -64,7 +65,7 @@ public final class FfpFixture {
GLStateManager.glUseProgram(program);
buildTemplate();
buildInstances(new float[] { -spacing, 0.0f, spacing }, new int[] { -1, 7, 42 });
buildInstances(new float[] { -spacing, 0.0f, spacing }, new int[] { -1, 7, 50020 });
}
public static void draw() {
@@ -228,7 +228,7 @@ class GlsmSdlFfpTest {
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 13, SIZE / 2, 0xFFFF0000, "entity -1 must render red");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 32, SIZE / 2, 0xFF00FF00, "entity 7 must render green");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 51, SIZE / 2, 0xFF0000FF, "entity 42 must render blue");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 51, SIZE / 2, 0xFF0000FF, "entity 50020 must render blue");
}
private static void enableGuiStandardItemLighting() {
@@ -0,0 +1,45 @@
package com.gtnewhorizons.angelica.compat.draconicevolution;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
import net.minecraft.client.renderer.entity.RenderItem;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraftforge.client.IItemRenderer.ItemRenderType;
import net.minecraftforge.client.MinecraftForgeClient;
import org.lwjgl.opengl.GL11;
public final class PlacedItemRenderCompat {
private PlacedItemRenderCompat() {}
public static boolean requiresLiveRender(ItemStack stack) {
if (stack == null) return false;
if (MinecraftForgeClient.getItemRenderer(stack, ItemRenderType.ENTITY) != null) return true;
//TODO: Properly optimize this later, right now it's breaking everything.
return stack.getItem() instanceof ItemBlock;
}
public static void render(double x, double y, double z, Runnable draw) {
final boolean inFrame = RenderItem.renderInFrame;
GLStateManager.glPushMatrix();
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
try {
GLStateManager.glTranslated(x, y, z);
draw.run();
} finally {
RenderItem.renderInFrame = inFrame;
GLStateManager.glPopAttrib();
GLStateManager.glPopMatrix();
}
}
public static byte renderWithBatchState(double x, double y, double z, byte state, Runnable draw) {
BatchEligibility.beginIsolated(state, GLStateManager.drawCalls);
try {
render(x, y, z, draw);
} finally {
state = BatchEligibility.endIsolated(state, GLStateManager.drawCalls);
}
return state;
}
}
@@ -87,17 +87,21 @@ public final class RenderLayer implements BlendingStateHolder {
}
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit) {
return build(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cull, lit, false);
return tesr(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cull, lit, false);
}
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit, boolean afterGlint) {
return build(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cull, lit, false, afterGlint);
}
public static RenderLayer tesrNoPass(ResourceLocation texture, TesrMaterial material, int cull, boolean lit) {
return build(texture, material, PassOverride.NONE, 0.0f, 0.0f, ShaderGlint.NO_TINT, cull, lit, true);
return build(texture, material, PassOverride.NONE, 0.0f, 0.0f, ShaderGlint.NO_TINT, cull, lit, true, false);
}
private static RenderLayer build(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit, boolean noPass) {
private static RenderLayer build(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit, boolean noPass, boolean afterGlint) {
final DrawState state = DrawState.forMaterial(material, cull, lit, offsetFactor, offsetUnits);
final Hook hook = hookFor(material, noPass ? PassOverride.NONE : pass, glintSlot);
final TransparencyType transparency = transparencyFor(state.getBlend());
final TransparencyType transparency = afterGlint ? TransparencyType.AFTER_GLINT : transparencyFor(state.getBlend());
final RenderLayer candidate = new RenderLayer(null, BatchVertexFormats.POSITION_COLOR_TEXTURE_LIGHTF_NORMAL,
GL11.GL_QUADS, TESR_BUFFER_SIZE, state, texture, material.isUnfilteredAtlas(), transparency, hook,
noPass);
@@ -107,7 +111,7 @@ public final class RenderLayer implements BlendingStateHolder {
+ material.transparency().name().toLowerCase(Locale.ROOT) + (noPass ? "" : pass.nameSuffix())
+ (offsetFactor == 0.0f && offsetUnits == 0.0f ? "" : "_offset" + offsetFactor + "_" + offsetUnits)
+ "_cull" + state.getCull() + (state.isLit() ? "_lit" : "")
+ (glintSlot == ShaderGlint.NO_TINT ? "" : "_tint" + glintSlot);
+ (glintSlot == ShaderGlint.NO_TINT ? "" : "_tint" + glintSlot) + (afterGlint ? "_after_glint" : "");
}
return retained;
}
@@ -0,0 +1,121 @@
package com.gtnewhorizons.angelica.compat.thaumcraft;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.uniforms.ItemIdManager;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
/** The held scanner's pane, icons and labels must follow translucent world geometry. */
public final class ThaumometerScreen {
private static final List<Capture> pending = new ArrayList<>();
private ThaumometerScreen() {}
public static Capture begin() {
if (DisplayListManager.isRecording()) return null;
final Capture capture = new Capture();
GLStateManager.glNewList(capture.list, GL11.GL_COMPILE);
return capture;
}
public static void finish(Capture capture) {
GLStateManager.glEndList();
pending.add(capture);
}
public static void abort(Capture capture) {
if (DisplayListManager.isRecording()) GLStateManager.glEndList();
GLStateManager.glDeleteLists(capture.list, 1);
}
public static void discard() {
for (Capture capture : pending) GLStateManager.glDeleteLists(capture.list, 1);
pending.clear();
}
public static void render(WorldRenderingPipeline pipeline) {
if (pending.isEmpty()) return;
final WorldRenderingPhase phase = pipeline.getPhase();
final Matrix4f texture = new Matrix4f(GLStateManager.getTextures().getTextureUnitMatrix(0));
final Matrix4f lightmap = new Matrix4f(GLStateManager.getTextures().getTextureUnitMatrix(1));
final float brightnessX = GLStateManager.ctx().lastBrightnessX;
final float brightnessY = GLStateManager.ctx().lastBrightnessY;
final Boolean translucency = GbufferPrograms.beginTranslucencyDeclaration(true);
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
ItemIdManager.pushItemId();
try {
pipeline.setPhase(WorldRenderingPhase.HAND_TRANSLUCENT);
GbufferPrograms.setBlockEntityDefaults();
GLStateManager.enableDepthTest();
GLStateManager.glDepthMask(false);
for (Capture capture : pending) capture.replay();
} finally {
ItemIdManager.popItemId();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPopMatrix();
GLStateManager.setTextureMatrix(0, texture);
GLStateManager.setTextureMatrix(1, lightmap);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPopMatrix();
GLStateManager.glPopAttrib();
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, brightnessX, brightnessY);
GbufferPrograms.endTranslucencyDeclaration(translucency);
pipeline.setPhase(phase);
discard();
}
}
public static final class Capture {
private final int list = GLStateManager.glGenLists(1);
private final Matrix4f modelView = new Matrix4f(GLStateManager.getModelViewMatrix());
private final Matrix4f projection = new Matrix4f(GLStateManager.getProjectionMatrix());
private final Matrix4f texture = new Matrix4f(GLStateManager.getTextures().getTextureUnitMatrix(0));
private final Matrix4f lightmapMatrix = new Matrix4f(GLStateManager.getTextures().getTextureUnitMatrix(1));
private final float brightnessX = GLStateManager.ctx().lastBrightnessX;
private final float brightnessY = GLStateManager.ctx().lastBrightnessY;
private final Vector3f normal = new Vector3f(ShaderManager.getCurrentNormal());
private final AlphaState alphaState = GLStateManager.getAlphaState().readEffective(new AlphaState());
private final int depthFunction = GLStateManager.getDepthState().getFunc();
private final int itemId = ItemIdManager.getItemId();
private final boolean cull = GLStateManager.getCullState().isEnabled();
private final boolean alpha = GLStateManager.getAlphaTest().isEffectivelyEnabled();
private final boolean lightmap = GLStateManager.getTextures().getTextureUnitStates(1).isEnabled();
private final boolean lighting = GLStateManager.getLightingState().isEnabled();
private void replay() {
GLStateManager.setProjectionMatrix(projection);
GLStateManager.setModelViewMatrix(modelView);
GLStateManager.setTextureMatrix(0, texture);
GLStateManager.setTextureMatrix(1, lightmapMatrix);
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, brightnessX, brightnessY);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glNormal3f(normal.x, normal.y, normal.z);
GLStateManager.glDepthFunc(depthFunction);
GLStateManager.glAlphaFunc(alphaState.getFunction(), alphaState.getReference());
ItemIdManager.setItemIdRaw(itemId);
if (cull) GLStateManager.enableCull(); else GLStateManager.disableCull();
if (alpha) GLStateManager.enableAlphaTest(); else GLStateManager.disableAlphaTest();
if (lighting) GLStateManager.enableLighting(); else GLStateManager.disableLighting();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
if (lightmap) GLStateManager.enableTexture(); else GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
GLStateManager.glCallList(list);
}
}
}
@@ -0,0 +1,335 @@
package com.gtnewhorizons.angelica.iris;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
import com.gtnewhorizons.angelica.rendering.FallingBlockRendering;
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.EntityIdHelper;
import net.coderbot.iris.uniforms.ItemIdManager;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
/**
* Records Java-side rendering scopes alongside cached geometry.
*/
public final class IrisDisplayListState {
private IrisDisplayListState() {}
public static void runProgramTransition(Runnable transition) {
BatchStateGuard.suspend();
try {
runUnrecorded(transition);
} finally {
BatchStateGuard.resume();
}
}
public static void runUnrecorded(Runnable transition) {
final DisplayListManager.RecordMode mode = DisplayListManager.getRecordMode();
if (mode == DisplayListManager.RecordMode.COMPILE) return;
if (mode == DisplayListManager.RecordMode.NONE) {
transition.run();
return;
}
final CommandRecorder recorder = DisplayListManager.pauseRecording();
try {
transition.run();
} finally {
DisplayListManager.resumeRecording(recorder);
}
}
private static void record(DisplayListCommand cmd) {
if (DisplayListManager.isRecording()) {
DisplayListManager.recordStateCommand(cmd);
}
}
public static void recordSpecialCondition(SpecialCondition condition, boolean begin) {
if (DisplayListManager.isRecording()) record(new SpecialConditionCmd(condition, begin));
}
public static void recordGlintSpan(boolean begin) {
if (DisplayListManager.isRecording()) record(new GlintSpanCmd(begin));
}
/** Keep nested items on the entity pass, including its depth and TAA behavior. */
public static void recordOverridePhase(WorldRenderingPhase phase) {
if (DisplayListManager.isRecording()) record(new OverridePhaseCmd(phase));
}
public static void recordPhaseScope(WorldRenderingPhase phase, boolean begin) {
if (DisplayListManager.isRecording()) record(new PhaseScopeCmd(phase, begin));
}
public static void recordNestedEntityScope(boolean begin) {
if (DisplayListManager.isRecording()) record(new NestedEntityScopeCmd(begin));
}
public static void recordTranslucency(Boolean translucent) {
if (DisplayListManager.isRecording()) record(new TranslucencyCmd(translucent));
}
public static void recordTranslucencyScope(Boolean translucent, boolean begin) {
if (DisplayListManager.isRecording()) record(new TranslucencyScopeCmd(translucent, begin));
}
/** Record explicit IDs as well as item identities so replay cannot inherit a previous draw's ID. */
public static void recordItemId(int itemId) {
if (DisplayListManager.isRecording()) {
DisplayListManager.recordStateCommandIfChanged(new ItemIdCmd(itemId));
}
}
public static void recordItem(ItemStack item) {
if (DisplayListManager.isRecording()) {
record(new ItemCmd(item == null ? null : item.copy()));
}
}
public static void recordItem(Item item, int metadata) {
if (DisplayListManager.isRecording()) record(new ItemTypeCmd(item, metadata));
}
public static void recordNamedItem(NamespacedId item) {
if (DisplayListManager.isRecording()) record(new NamedItemCmd(item));
}
public static void recordBlockItem(Block block, int metadata) {
if (DisplayListManager.isRecording()) record(new BlockItemCmd(block, metadata));
}
public static void recordItemScope(boolean push) {
if (DisplayListManager.isRecording()) record(new ItemScopeCmd(push));
}
public static void recordEntity(Entity entity) {
if (DisplayListManager.isRecording()) {
record(new EntityCmd(EntityIdHelper.snapshot(entity)));
}
}
public static void recordEntityScope(boolean push) {
if (DisplayListManager.isRecording()) record(new EntityScopeCmd(push));
}
public static void recordEntityAndItem(int entity, int item) {
if (DisplayListManager.isRecording()) {
DisplayListManager.recordStateCommandIfChanged(new EntityAndItemCmd(entity, item));
}
}
public static void recordNamedEntity(NamespacedId entity) {
if (DisplayListManager.isRecording()) record(new NamedEntityCmd(entity));
}
public static void recordBlockEntity(int id) {
if (DisplayListManager.isRecording()) {
DisplayListManager.recordStateCommandIfChanged(new BlockEntityIdCmd(id));
}
}
public static void recordBlockEntity(Block block, int metadata) {
if (DisplayListManager.isRecording()) record(new BlockEntityCmd(block, metadata));
}
public static void recordBlockEntityScope(boolean push) {
if (DisplayListManager.isRecording()) record(new BlockEntityScopeCmd(push));
}
public static void recordEntityColor(float r, float g, float b, float a) {
if (DisplayListManager.isRecording()) {
DisplayListManager.recordStateCommandIfChanged(new EntityColorCmd(r, g, b, a));
}
}
public static void recordEntityColorScope(boolean push) {
if (DisplayListManager.isRecording()) record(new EntityColorScopeCmd(push));
}
public static void recordBlockEntityAttribute(Block block, int metadata) {
if (DisplayListManager.isRecording()) record(new BlockEntityAttributeCmd(block, metadata));
}
private record BlockEntityAttributeCmd(Block block, int metadata) implements DisplayListCommand {
@Override
public void execute() {
if (block == null) FallingBlockRendering.resetEntityAttribute();
else FallingBlockRendering.setEntityAttribute(block, metadata);
}
}
private record EntityAndItemCmd(int entity, int item) implements DisplayListCommand {
@Override
public void execute() { CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(entity, item); }
}
private record NamedEntityCmd(NamespacedId entity) implements DisplayListCommand {
@Override
public void execute() { CapturedRenderingState.INSTANCE.setCurrentNamedEntity(entity); }
}
private record BlockEntityIdCmd(int id) implements DisplayListCommand {
@Override
public void execute() { CapturedRenderingState.INSTANCE.setCurrentBlockEntity(id); }
}
private record BlockEntityCmd(Block block, int metadata) implements DisplayListCommand {
@Override
public void execute() { CapturedRenderingState.INSTANCE.setCurrentBlockEntity(block, metadata); }
}
private record BlockEntityScopeCmd(boolean push) implements DisplayListCommand {
@Override
public void execute() {
if (push) CapturedRenderingState.INSTANCE.pushCurrentBlockEntity();
else CapturedRenderingState.INSTANCE.popCurrentBlockEntity();
}
}
private record EntityColorCmd(float r, float g, float b, float a) implements DisplayListCommand {
@Override
public void execute() { CapturedRenderingState.INSTANCE.setCurrentEntityColor(r, g, b, a); }
}
private record EntityColorScopeCmd(boolean push) implements DisplayListCommand {
@Override
public void execute() {
if (push) CapturedRenderingState.INSTANCE.pushCurrentEntityColor();
else CapturedRenderingState.INSTANCE.popCurrentEntityColor();
}
}
private record EntityCmd(EntityIdHelper.EntityIdentity identity) implements DisplayListCommand {
@Override
public void execute() {
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(identity == null ? -1 : identity.resolve(), 0);
}
}
private record EntityScopeCmd(boolean push) implements DisplayListCommand {
@Override
public void execute() {
if (push) CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
else CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
}
}
private record ItemScopeCmd(boolean push) implements DisplayListCommand {
@Override
public void execute() {
if (push) {
CapturedRenderingState.INSTANCE.pushCurrentRenderedItem();
} else {
CapturedRenderingState.INSTANCE.popCurrentRenderedItem();
}
}
}
private record SpecialConditionCmd(SpecialCondition condition, boolean begin) implements DisplayListCommand {
@Override
public void execute() {
if (begin) {
GbufferPrograms.setupSpecialRenderCondition(condition);
} else {
GbufferPrograms.teardownSpecialRenderCondition();
}
}
}
private record GlintSpanCmd(boolean begin) implements DisplayListCommand {
@Override
public void execute() {
if (begin) {
ShaderGlint.beginGlint();
} else {
ShaderGlint.endGlint();
}
}
}
private record OverridePhaseCmd(WorldRenderingPhase phase) implements DisplayListCommand {
@Override
public void execute() {
GbufferPrograms.setOverridePhase(phase);
}
}
private record PhaseScopeCmd(WorldRenderingPhase phase, boolean begin) implements DisplayListCommand {
@Override
public void execute() {
if (begin) GbufferPrograms.pushOverridePhase(phase);
else GbufferPrograms.popOverridePhase();
}
}
private static final BooleanArrayList replayNestedEntityStack = new BooleanArrayList();
private record NestedEntityScopeCmd(boolean begin) implements DisplayListCommand {
@Override
public void execute() {
if (begin) replayNestedEntityStack.push(GbufferPrograms.beginNestedEntityPhase());
else GbufferPrograms.endNestedEntityPhase(replayNestedEntityStack.popBoolean());
}
}
private record TranslucencyCmd(Boolean translucent) implements DisplayListCommand {
@Override
public void execute() {
GbufferPrograms.setTranslucencyDeclaration(translucent);
}
}
private static final ObjectArrayList<Boolean> replayTranslucencyStack = new ObjectArrayList<>();
private record TranslucencyScopeCmd(Boolean translucent, boolean begin) implements DisplayListCommand {
@Override
public void execute() {
if (begin) replayTranslucencyStack.add(GbufferPrograms.beginTranslucencyDeclaration(translucent));
else GbufferPrograms.endTranslucencyDeclaration(replayTranslucencyStack.pop());
}
}
private record ItemIdCmd(int itemId) implements DisplayListCommand {
@Override
public void execute() {
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(itemId);
}
}
private record ItemCmd(ItemStack item) implements DisplayListCommand {
@Override
public void execute() {
ItemIdManager.setItemId(item);
}
}
private record ItemTypeCmd(Item item, int metadata) implements DisplayListCommand {
@Override
public void execute() { CapturedRenderingState.INSTANCE.setCurrentRenderedItem(item, metadata); }
}
private record NamedItemCmd(NamespacedId item) implements DisplayListCommand {
@Override
public void execute() { CapturedRenderingState.INSTANCE.setCurrentNamedItem(item); }
}
private record BlockItemCmd(Block block, int metadata) implements DisplayListCommand {
@Override
public void execute() {
ItemIdManager.setBlockId(block, metadata);
}
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.iris;
import com.gtnewhorizons.angelica.client.rendering.TextureTracker;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredAlphaHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
@@ -20,6 +21,7 @@ import net.coderbot.iris.gl.blending.AlphaTestStorage;
import net.coderbot.iris.gl.blending.BlendModeStorage;
import net.coderbot.iris.gl.blending.DepthColorStorage;
import net.coderbot.iris.gl.program.ProgramUniforms;
import net.coderbot.iris.gl.program.Program;
import net.coderbot.iris.gl.state.StateUpdateNotifiers;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
@@ -87,6 +89,18 @@ public class IrisGLSMBridge {
};
}
static void installVanillaStateLayers() {
// Forge initializes mods with the splash screen's shared drawable current.
// Iris overrides belong to the display context used for world rendering.
final GLContextState context = GLStateManager.primaryContext();
context.blendMode.setVanillaLayer(BlendModeStorage.ENABLE_LAYER);
context.blendState.setVanillaLayer(BlendModeStorage.FUNC_LAYER);
context.alphaTest.setVanillaLayer(AlphaTestStorage.ENABLE_LAYER);
context.alphaState.setVanillaLayer(AlphaTestStorage.FUNC_LAYER);
context.depthState.setVanillaLayer(DepthColorStorage.DEPTH_LAYER);
context.colorMask.setVanillaLayer(DepthColorStorage.COLOR_LAYER);
}
public static void register() {
GLSMConfig.expandVertexFormats = Iris.enabled;
IrisSamplers.initRenderer();
@@ -124,12 +138,7 @@ public class IrisGLSMBridge {
}
};
GLStateManager.getBlendMode().setVanillaLayer(BlendModeStorage.ENABLE_LAYER);
GLStateManager.getBlendState().setVanillaLayer(BlendModeStorage.FUNC_LAYER);
GLStateManager.getAlphaTest().setVanillaLayer(AlphaTestStorage.ENABLE_LAYER);
GLStateManager.getAlphaState().setVanillaLayer(AlphaTestStorage.FUNC_LAYER);
GLStateManager.getDepthState().setVanillaLayer(DepthColorStorage.DEPTH_LAYER);
GLStateManager.getColorMask().setVanillaLayer(DepthColorStorage.COLOR_LAYER);
installVanillaStateLayers();
GLSMHooks.alphaHandler = new DeferredAlphaHandler() {
@Override
@@ -270,7 +279,7 @@ public class IrisGLSMBridge {
if (!drp.shouldOverrideShaders()) return;
DepthColorStorage.unlockDepthColor();
if (event.newProgram != 0 && !DepthColorStorage.isOwnedProgram(event.newProgram)) {
if (event.newProgram != 0 && !Program.isManagedBind(event.newProgram) && !DepthColorStorage.isOwnedProgram(event.newProgram)) {
drp.onModProgramOverride();
}
});
@@ -294,12 +303,15 @@ public class IrisGLSMBridge {
GLSMHooks.PROGRAM_CHANGE.addListener(event -> {
if (!Iris.enabled) return;
if (!event.postBind) return;
if (Program.isManagedBind(event.newProgram)) return;
WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
if (pipeline instanceof DeferredWorldRenderingPipeline drp) {
DeferredWorldRenderingPipeline.Pass activePass = drp.getActivePassProgram();
if (activePass != null && activePass.getProgram() != null && activePass.getProgram().getProgramId() == event.newProgram) {
final int frame = SystemTimeUniforms.COUNTER.getAsInt();
if (programLastUpdatedFrame.get(event.newProgram) != frame) {
if (programLastUpdatedFrame.get(event.newProgram) != frame
|| !ProgramUniforms.isActiveProgramBound()
|| ProgramUniforms.activeHasDeferredUploads()) {
activePass.getProgram().getUniforms().update();
programLastUpdatedFrame.put(event.newProgram, frame);
}
@@ -215,6 +215,13 @@ public enum Mixins implements IMixins {
)
),
THAUMCRAFT_SCANNER_SCREEN(new MixinBuilder("Render the held Thaumometer screen after world transparency")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.THAUMCRAFT)
.setApplyIf(() -> AngelicaConfig.enableIris)
.addClientMixins("client.thaumcraft.MixinItemThaumometerRenderer")
),
THAUMCRAFT_TESR_JAR_CACHE(new MixinBuilder("Batch TC4 jar liquid via the retained TESR mesh cache")
.setPhase(Phase.LATE)
.setApplyIf(() -> AngelicaConfig.enableTESRJarCache)
@@ -814,6 +821,11 @@ public enum Mixins implements IMixins {
.setApplyIf(() -> AngelicaConfig.enableIris)
.addClientMixins("client.etfuturum.MixinTileEntityNewBeaconRenderer")
),
DRACONIC_PLACED_ITEM_RENDERER(new MixinBuilder("Keep custom placed-item renderers live")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.DRACONIC_EVOLUTION)
.addClientMixins("client.draconicevolution.MixinRenderTilePlacedItem")
),
OPENBLOCKS_TROPHY_ENTITY_GBUFFER(new MixinBuilder("Render OpenBlocks trophies with the entity gbuffer programs")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.OPENBLOCKS)
@@ -16,6 +16,7 @@ public enum TargetedMod implements ITargetMod {
DYNAMIC_SURROUNDINGS_MIST("org.blockartistry.mod.DynSurround.mixinplugin.DynamicSurroundingsEarlyMixins", "dsurround"),
DYNAMIC_SURROUNDINGS_ORIGINAL("org.blockartistry.mod.DynSurround.asm.TransformLoader", "dsurround"),
DRAGON_API("Reika.DragonAPI.Auxiliary.DragonAPIASMHandler", "DragonAPI"),
DRACONIC_EVOLUTION(null, "DraconicEvolution"),
ET_FUTURUM_REQUIEM(null, "etfuturum"),
EXTRAUTILS(null, "ExtraUtilities"),
FARSEEK("farseek.core.FarseekCoreMod", "farseek"),
@@ -2,8 +2,8 @@ package com.gtnewhorizons.angelica.mixins.interfaces;
@SuppressWarnings("unused")
public interface IPatchedTextureAtlasSprite {
void markNeedsAnimationUpdate();
boolean needsAnimationUpdate();
void unmarkNeedsAnimationUpdate();
void updateAnimationsDryRun();
void angelica$markNeedsAnimationUpdate();
boolean angelica$needsAnimationUpdate();
void angelica$unmarkNeedsAnimationUpdate();
void angelica$updateAnimationsDryRun();
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.iris.IrisDisplayListState;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
@@ -25,13 +26,17 @@ public final class FallingBlockRendering {
}
public static void setEntityAttribute(Block block, int metadata) {
IrisDisplayListState.recordBlockEntityAttribute(block, metadata);
if (!shadersActive()) return;
GLStateManager.glVertexAttrib2s(ProgramCreator.MC_ENTITY, (short) blockMaterialId(block, metadata), (short) 0);
IrisDisplayListState.runUnrecorded(() ->
GLStateManager.glVertexAttrib2s(ProgramCreator.MC_ENTITY, (short) blockMaterialId(block, metadata), (short) 0));
}
public static void resetEntityAttribute() {
IrisDisplayListState.recordBlockEntityAttribute(null, 0);
if (!shadersActive()) return;
GLStateManager.glVertexAttrib2s(ProgramCreator.MC_ENTITY, (short) -1, (short) -1);
IrisDisplayListState.runUnrecorded(() ->
GLStateManager.glVertexAttrib2s(ProgramCreator.MC_ENTITY, (short) -1, (short) -1));
}
private static int blockMaterialId(Block block, int metadata) {
@@ -15,6 +15,7 @@ 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.gtnewhorizons.angelica.utils.AnimationsRenderUtils;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.block.Block;
@@ -107,7 +108,7 @@ public final class DroppedItemInstancer {
return material;
}
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) {
public static void icon(ItemStack stack, boolean afterGlint, 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);
@@ -116,10 +117,10 @@ public final class DroppedItemInstancer {
callIcon(ICON_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness);
return;
}
basePart = batchIcon(material, t, maxU, minV, minU, maxV, width, height, thickness, original);
basePart = batchIcon(material, afterGlint, t, maxU, minV, minU, maxV, width, height, thickness, original);
}
static boolean batchIcon(TesrMaterial material, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
static boolean batchIcon(TesrMaterial material, boolean afterGlint, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original) {
final long drawsBefore = GLStateManager.drawCalls;
if (BatchEligibility.batchingAllowed()) {
final TemplateBuffer template = iconTemplate(ICON_ARGS, maxU, minV, minU, maxV, width, height, thickness, original);
@@ -129,7 +130,7 @@ public final class DroppedItemInstancer {
bail(BailReason.TEMPLATE);
return false;
}
if (ModelPartBatcher.INSTANCE.queueTemplate(template, material)) {
if (ModelPartBatcher.INSTANCE.queueTemplate(template, material, afterGlint)) {
instanced++;
BatchEligibility.onPartQueued();
return true;
@@ -234,6 +235,7 @@ public final class DroppedItemInstancer {
return;
}
if (ModelPartBatcher.INSTANCE.queueTemplate(mesh.template(), material)) {
mesh.sprites().markUsed();
instanced++;
BatchEligibility.onPartQueued();
return;
@@ -311,6 +313,7 @@ public final class DroppedItemInstancer {
blockBackend = new BakedTransformCapture(CAPTURE_BYTES);
}
final TemplateBuffer template;
final AnimationsRenderUtils.SpriteCapture sprites = AnimationsRenderUtils.captureSprites();
GLStateManager.glPushMatrix();
try {
blockBackend.begin();
@@ -321,9 +324,10 @@ public final class DroppedItemInstancer {
}
} finally {
GLStateManager.glPopMatrix();
sprites.close();
}
final Color4 color = GLStateManager.getColor();
final BlockMesh mesh = new BlockMesh(template, color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
final BlockMesh mesh = new BlockMesh(template, color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha(), sprites);
blocks.put(new BlockMeta(block, meta), mesh);
return mesh;
}
@@ -346,5 +350,6 @@ public final class DroppedItemInstancer {
TemplateBuffer template;
}
private record BlockMesh(TemplateBuffer template, float red, float green, float blue, float alpha) {}
private record BlockMesh(TemplateBuffer template, float red, float green, float blue, float alpha,
AnimationsRenderUtils.SpriteCapture sprites) {}
}
@@ -27,7 +27,7 @@ public final class HeldItemGlint {
private HeldItemGlint() {}
private static boolean batchedGlintGuarded() {
return ModelPartBatcher.INSTANCE.entityPassActive() && ModelPartBatcher.INSTANCE.canCaptureGlint() && canResetStencil();
return ModelPartBatcher.INSTANCE.entityPassActive() && ModelPartBatcher.INSTANCE.canQueueGlint() && canResetStencil();
}
public static boolean needsImmediateBase(ItemStack stack, int pass) {
@@ -46,7 +46,7 @@ public final class HeldItemGlint {
}
public static boolean eligible() {
return AngelicaConfig.enableEntityBatching && GLStateManager.getActiveProgram() == 0 && (boolean) Settings.MODE_GLINT_WORLD.option.getStore() && opaqueTexture(Minecraft.getMinecraft());
return AngelicaConfig.enableEntityBatching && (boolean) Settings.MODE_GLINT_WORLD.option.getStore() && opaqueTexture(Minecraft.getMinecraft());
}
public static boolean begin() {
@@ -3,6 +3,8 @@ package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import java.util.ArrayList;
/** Excludes renderers that mix batched model parts with their own draws, which reorders their geometry. */
public final class BatchEligibility {
@@ -19,15 +21,74 @@ public final class BatchEligibility {
private static long bracketExpectedAtStart;
private static int bracketDepth;
private static int parts;
private static boolean uncapturedState;
private static ModelPartBatcher.BailReason allowedBail;
private static final ArrayList<SavedScope> isolatedScopes = new ArrayList<>();
private static int isolatedDepth;
private static final class SavedScope {
int depth, bracketDepth, parts;
boolean allowed, uncapturedState;
ModelPartBatcher.BailReason allowedBail;
long drawsAtStart, expectedDraws, foreignDraws, bracketDrawsAtStart, bracketExpectedAtStart, isolatedDrawsAtStart;
void save(long drawCount) {
depth = BatchEligibility.depth;
allowed = BatchEligibility.allowed;
uncapturedState = BatchEligibility.uncapturedState;
allowedBail = BatchEligibility.allowedBail;
drawsAtStart = BatchEligibility.drawsAtStart;
expectedDraws = BatchEligibility.expectedDraws;
foreignDraws = BatchEligibility.foreignDraws;
bracketDrawsAtStart = BatchEligibility.bracketDrawsAtStart;
bracketExpectedAtStart = BatchEligibility.bracketExpectedAtStart;
bracketDepth = BatchEligibility.bracketDepth;
parts = BatchEligibility.parts;
isolatedDrawsAtStart = drawCount;
}
void restore(long drawCount) {
BatchEligibility.depth = depth;
BatchEligibility.allowed = allowed;
BatchEligibility.uncapturedState = uncapturedState;
BatchEligibility.allowedBail = allowedBail;
BatchEligibility.drawsAtStart = drawsAtStart;
BatchEligibility.expectedDraws = expectedDraws + drawCount - isolatedDrawsAtStart;
BatchEligibility.foreignDraws = foreignDraws;
BatchEligibility.bracketDrawsAtStart = bracketDrawsAtStart;
BatchEligibility.bracketExpectedAtStart = bracketExpectedAtStart;
BatchEligibility.bracketDepth = bracketDepth;
BatchEligibility.parts = parts;
}
}
private BatchEligibility() {}
public static void beginIsolated(byte state, long drawCount) {
if (isolatedDepth == isolatedScopes.size()) isolatedScopes.add(new SavedScope());
isolatedScopes.get(isolatedDepth++).save(drawCount);
depth = 0;
bracketDepth = 0;
begin(state, drawCount);
}
public static byte endIsolated(byte state, long drawCount) {
try {
return end(state, drawCount);
} finally {
isolatedScopes.get(--isolatedDepth).restore(drawCount);
}
}
public static boolean begin(byte state, long drawCount) {
if (depth++ > 0) return allowed;
drawsAtStart = drawCount;
expectedDraws = 0L;
foreignDraws = 0L;
parts = 0;
uncapturedState = false;
allowedBail = null;
bracketDepth = 0;
allowed = state == SAFE;
return allowed;
@@ -37,6 +98,7 @@ public final class BatchEligibility {
if (--depth > 0) return state;
allowed = false;
GLSMHooks.resolvePendingProgram();
if (uncapturedState) return DENIED;
if (parts == 0) return state;
final long foreign = (drawCount - drawsAtStart) - expectedDraws;
if (foreign > 0L) {
@@ -50,6 +112,27 @@ public final class BatchEligibility {
return allowed;
}
static boolean insideRenderer() { return depth != 0; }
static void onUncapturedState() {
if (depth == 0) return;
uncapturedState = true;
allowed = false;
}
static void onBatchFlushed(long draws) {
if (depth != 0) expectedDraws += draws;
}
static void onBail(ModelPartBatcher.BailReason reason) {
if (depth == 0 || !allowed) return;
if (reason == ModelPartBatcher.BailReason.INACTIVE) {
allowed = false;
return;
}
if (allowedBail == null) allowedBail = reason;
}
public static void onPartQueued() {
parts++;
}
@@ -76,6 +159,10 @@ public final class BatchEligibility {
public static void onStateChange(Object renderer, byte state) {
if (state != DENIED) return;
final String name = renderer.getClass().getName();
GLStateManager.warnOnce("tesr-mixed:" + name, "{} draws its own geometry alongside model parts ({} foreign draws) - excluding it from model part batching", name, foreignDraws);
if (uncapturedState) {
GLStateManager.warnOnce("tesr-state:" + name, "{} changes state outside the batch format - keeping its model parts immediate", name);
return;
}
GLStateManager.warnOnce("tesr-mixed:" + name, "{} draws its own geometry alongside model parts ({} foreign draws, first allowed bail: {}) - excluding it from model part batching", name, foreignDraws, allowedBail);
}
}
@@ -0,0 +1,39 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
/** Uncaptured state changes draw the queued batch before the mutation takes effect. */
final class BatchStateFallback {
private static final Runnable FLUSH = BatchStateFallback::flush;
static void install() { BatchStateGuard.flush = FLUSH; }
private static void flush() {
final ModelPartBatcher models = ModelPartBatcher.INSTANCE;
final TesrBatchRenderer tesrs = TesrBatchRenderer.INSTANCE;
if (!models.isActive() && !tesrs.hasPendingGeometry()) return;
// Pass setup may establish defaults after beginPass, before any renderer or queued draw.
if (!models.hasQueuedGeometry() && !tesrs.hasQueuedGeometry() && !BatchEligibility.insideRenderer()) return;
if (models.entityPassActive()) {
final boolean inside = BatchEligibility.insideRenderer();
GLStateManager.warnOnce("batch-fallback-entities:" + inside, "Uncaptured state change {} a renderer flushed the entity batch early",
inside ? "inside" : "between", new Throwable());
}
final int mode = GLStateManager.getMatrixMode().getMode();
final int unit = GLStateManager.getActiveTextureUnit();
BatchEligibility.onUncapturedState();
final long before = GLStateManager.drawCalls;
try {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
models.flushForStateChange();
tesrs.flushForStateChange();
} finally {
GLStateManager.glMatrixMode(mode);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
BatchEligibility.onBatchFlushed(GLStateManager.drawCalls - before);
}
}
}
@@ -47,6 +47,8 @@ final class EntityShadowBatcher {
private long quads;
private long draws;
boolean hasPendingGeometry() { return count != 0; }
long statQuads() { return quads; }
long statDraws() { return draws; }
@@ -59,7 +59,6 @@ public final class GlintCapture {
texture = GLStateManager.getBoundTextureForServerState();
color = currentColor();
light = GLSMConfig.packedLastBrightness();
overlay = currentOverlay();
fragmentLength = FragmentKey.packFromState(fragment);
GLStateManager.getEffectiveBlendState(blend);
blendEnabled = GLStateManager.isEffectiveBlendEnabled();
@@ -71,11 +70,11 @@ public final class GlintCapture {
/**
* 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) {
boolean beginBase(RetainedTesrGroups groups, int overlay) {
if (!open(groups)) return false;
parent.set(GLStateManager.getModelViewMatrix());
light = GLSMConfig.packedLastBrightness();
overlay = currentOverlay();
this.overlay = overlay;
base = true;
return true;
}
@@ -174,7 +173,7 @@ public final class GlintCapture {
boolean copyBase(ModelPartBatcher batcher, RetainedTesrGroups groups) {
if (!baseStillValid(groups) || EntityMaterials.fromCurrentState(false) != EntityMaterials.GLINT || !batcher.prepareArmorGlint()) return false;
final boolean inPlace = sameLightAndOverlay();
final boolean inPlace = sameLightAndOverlay(batcher.currentOverlay());
for (int i = 0; i < used; i++) {
final Range range = ranges.get(i);
batcher.copyArmorGlint(range.columns, range.start, range.end, inPlace);
@@ -184,7 +183,7 @@ public final class GlintCapture {
}
boolean queueBothLayers(ModelPartBatcher batcher, RetainedTesrGroups groups, ResourceLocation glintTexture) {
if (!baseStillValid(groups) || !sameLightAndOverlay() || !batcher.prepareSkippedArmorGlint(glintTexture)) return false;
if (!baseStillValid(groups) || !sameLightAndOverlay(batcher.currentOverlay()) || !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);
@@ -202,16 +201,12 @@ public final class GlintCapture {
return true;
}
private boolean sameLightAndOverlay() {
return uniformLight && light == GLSMConfig.packedLastBrightness() && overlay == currentOverlay();
private boolean sameLightAndOverlay(int currentOverlay) {
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());
}
}
@@ -96,8 +96,7 @@ final class InstancedTemplateRenderer {
for (int j = 0, m = indices.size(); j < m; j++) {
final int i = indices.getInt(j);
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.writeHead(ptr, data.matrices, i * 16, color, data.overlays.getInt(i), data.infos.getLong(i));
InstancedAttribs.writeLightmap(ptr + InstancedAttribs.OFFSET_LIGHTMAP, data.lights.getInt(i));
ptr += InstancedAttribs.STRIDE;
}
@@ -178,7 +177,7 @@ final class InstancedTemplateRenderer {
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);
final long info = data.infos.getLong(i);
for (int p = 0, n = partCubes.length; p < n; p++) {
final CubeParams cube = partCubes[p];
cube.writeRows(ptr, data.matrices, off, scale);
@@ -10,6 +10,8 @@ import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
@@ -33,15 +35,20 @@ import net.coderbot.iris.Iris;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.layer.PassOverride;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.ModelBox;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.MinecraftForgeClient;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.Arrays;
import java.util.List;
@@ -75,10 +82,14 @@ public final class ModelPartBatcher {
private static final Tracy.ZoneId Z_ENTITY_LAYER_LOOP = Tracy.zoneId("entityLayerLoop", Tracy.COLOR_CLIENT);
private final AngelicaBufferSource bufferSource = new AngelicaBufferSource();
private final AngelicaBufferSource entitySource = new AngelicaBufferSource();
private final EntityShadowBatcher shadows = new EntityShadowBatcher();
private final RetainedTesrGroups groups = new RetainedTesrGroups(bufferSource);
private final RetainedTesrGroups entityGroups = new RetainedTesrGroups(entitySource);
private final RetainedTesrGroups shadowGroups = new RetainedTesrGroups(bufferSource);
private RetainedTesrGroups activeGroups = groups;
private AngelicaBufferSource activeSource = bufferSource;
private boolean entityTranslucentsHeld;
private boolean shadow;
private final AngelicaTesrMeshCache.GtnhMeshBackend captureBackend = new AngelicaTesrMeshCache.GtnhMeshBackend();
private final Matrix4f identity = new Matrix4f();
@@ -111,17 +122,18 @@ public final class ModelPartBatcher {
public long statArmorLoopSkips() { return armorLoopSkips; }
public long statArmorSectionSkips() { return armorSectionSkips; }
public long statLiveFallbacks() { return liveFallbacks; }
public long statInstancedDraws() { return groups.instancedDraws + shadowGroups.instancedDraws; }
public long statInstancedInstances() { return groups.instancedInstances + shadowGroups.instancedInstances; }
public long statCubeInstances() { return groups.cubeInstances + shadowGroups.cubeInstances; }
public long statRedrawnInstances() { return groups.redrawnInstances + shadowGroups.redrawnInstances; }
public long statTexMatrixRuns() { return groups.texMatrixRuns + shadowGroups.texMatrixRuns; }
public long statInstancedDraws() { return groups.instancedDraws + entityGroups.instancedDraws + shadowGroups.instancedDraws; }
public long statInstancedInstances() { return groups.instancedInstances + entityGroups.instancedInstances + shadowGroups.instancedInstances; }
public long statCubeInstances() { return groups.cubeInstances + entityGroups.cubeInstances + shadowGroups.cubeInstances; }
public long statRedrawnInstances() { return groups.redrawnInstances + entityGroups.redrawnInstances + shadowGroups.redrawnInstances; }
public long statTexMatrixRuns() { return groups.texMatrixRuns + entityGroups.texMatrixRuns + shadowGroups.texMatrixRuns; }
public long statBail(BailReason reason) { return bails[reason.ordinal()]; }
public long statShadowQuads() { return shadows.statQuads(); }
public long statShadowDraws() { return shadows.statDraws(); }
private void bail(BailReason reason) {
bails[reason.ordinal()]++;
BatchEligibility.onBail(reason);
}
private static final class PartTemplate {
@@ -143,13 +155,14 @@ public final class ModelPartBatcher {
int glintSlot;
int cull;
boolean lit;
boolean afterGlint;
boolean matches(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit) {
boolean matches(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit, boolean afterGlint) {
return this.texture == texture && this.material == material && pass.equals(this.pass) && this.offsetFactor == offsetFactor
&& this.offsetUnits == offsetUnits && this.glintSlot == glintSlot && this.cull == cull && this.lit == lit;
&& this.offsetUnits == offsetUnits && this.glintSlot == glintSlot && this.cull == cull && this.lit == lit && this.afterGlint == afterGlint;
}
LayerKey set(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit) {
LayerKey set(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit, boolean afterGlint) {
this.texture = texture;
this.material = material;
this.pass = pass;
@@ -158,6 +171,7 @@ public final class ModelPartBatcher {
this.glintSlot = glintSlot;
this.cull = cull;
this.lit = lit;
this.afterGlint = afterGlint;
return this;
}
@@ -168,7 +182,7 @@ public final class ModelPartBatcher {
&& Objects.equals(pass, other.pass)
&& Float.floatToIntBits(offsetFactor) == Float.floatToIntBits(other.offsetFactor)
&& Float.floatToIntBits(offsetUnits) == Float.floatToIntBits(other.offsetUnits)
&& glintSlot == other.glintSlot && cull == other.cull && lit == other.lit;
&& glintSlot == other.glintSlot && cull == other.cull && lit == other.lit && afterGlint == other.afterGlint;
}
@Override
@@ -179,6 +193,7 @@ public final class ModelPartBatcher {
h = h * 31 + glintSlot;
h = h * 31 + cull;
h = h * 31 + (lit ? 1 : 0);
h = h * 31 + (afterGlint ? 1 : 0);
return h;
}
}
@@ -213,6 +228,7 @@ public final class ModelPartBatcher {
}
public void begin(Mode mode, boolean shadow) {
BatchStateFallback.install();
this.mode = mode;
this.shadow = shadow;
instancedThisCycle = TesrBatchRenderer.instancedCapable();
@@ -222,39 +238,65 @@ public final class ModelPartBatcher {
shaderPipeline = null;
return;
}
final boolean entityPass = mode == Mode.ENTITIES && !shadow;
if (entityPass && entityTranslucentsHeld) {
discardHeldEntities();
}
active = true;
final long nowMs = System.currentTimeMillis();
mvCaptured = false;
texCaptured = false;
activeGroups = shadow ? shadowGroups : groups;
bufferSource.setIdKind(mode == Mode.ENTITIES ? AngelicaBufferSource.GroupIdKind.ENTITY : AngelicaBufferSource.GroupIdKind.BLOCK_ENTITY);
activeGroups = shadow ? shadowGroups : entityPass ? entityGroups : groups;
activeSource = entityPass ? entitySource : bufferSource;
activeSource.setIdKind(mode == Mode.ENTITIES ? AngelicaBufferSource.GroupIdKind.ENTITY : AngelicaBufferSource.GroupIdKind.BLOCK_ENTITY);
activeGroups.beginPass(identity, 0, 0, 0, instancedThisCycle ? TesrBatchRenderer.INSTANCE.instancedRenderer : null, shaderPipeline);
if (nowMs - lastSweepMs >= SWEEP_INTERVAL_MS) {
lastSweepMs = nowMs;
groups.sweep(nowMs);
entityGroups.sweep(nowMs);
shadowGroups.sweep(nowMs);
sweepTemplates(nowMs);
}
}
public void flush() {
BatchStateGuard.suspend();
try {
flushNow(true);
} finally {
BatchStateGuard.resume();
}
}
void flushForStateChange() {
if (!active) return;
flushNow(false);
active = true;
mvCaptured = false;
texCaptured = false;
activeGroups.beginPass(identity, 0, 0, 0, instancedThisCycle ? TesrBatchRenderer.INSTANCE.instancedRenderer : null, shaderPipeline);
}
private void flushNow(boolean allowHold) {
if (!active) return;
active = false;
final boolean hold = allowHold && mode == Mode.ENTITIES && !shadow && MinecraftForgeClient.getRenderPass() == 0
&& shaderPipeline != null && shaderPipeline.shouldSeparateEntityDraws();
if (shadow) {
drawBatches();
drawBatches(false);
} else {
final boolean inEntityLoop = mode == Mode.ENTITIES && Iris.enabled && GbufferPrograms.isEntityLoopActive();
final boolean wrapEntities = mode == Mode.ENTITIES && Iris.enabled && !inEntityLoop;
if (wrapEntities) GbufferPrograms.beginEntities();
else if (inEntityLoop) GbufferPrograms.onEntityRenderBoundary();
try {
shadows.flush(bufferSource);
drawBatches();
shadows.flush(activeSource);
drawBatches(hold);
} finally {
if (wrapEntities) GbufferPrograms.endEntities();
}
}
if (instancedThisCycle) {
if (instancedThisCycle && !hold) {
TesrBatchRenderer.INSTANCE.instancedRenderer.endFrame();
}
if (!TesrBatchRenderer.INSTANCE.hasPendingGeometry()) {
@@ -262,26 +304,87 @@ public final class ModelPartBatcher {
}
}
private void drawBatches() {
private void drawBatches(boolean hold) {
if (Tracy.ENABLED) Tracy.beginZone(Z_ENTITY_LAYER_LOOP);
try {
bufferSource.endBatchWithType(TransparencyType.OPAQUE, activeGroups);
bufferSource.endBatch(activeGroups);
activeSource.endBatchWithType(TransparencyType.OPAQUE, activeGroups);
if (hold) {
activeSource.endBatchWithType(TransparencyType.WATER_MASK, activeGroups);
activeSource.pauseBatch();
entityTranslucentsHeld = true;
} else {
activeSource.endBatch(activeGroups);
}
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
}
public void flushEntitiesAfterDeferred() {
if (!entityTranslucentsHeld) return;
entityTranslucentsHeld = false;
BatchStateGuard.suspend();
try {
final EntityRenderer entityRenderer = Minecraft.getMinecraft().entityRenderer;
final int deferredDepth = GLStateManager.pushState(StateSet.BATCH);
try {
GLStateManager.glDepthMask(true);
GLStateManager.enableDepthTest();
GLStateManager.enableBlend();
GLStateManager.defaultBlendFunc();
entityRenderer.enableLightmap(0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
final boolean wrap = Iris.enabled && GbufferPrograms.getCurrentPhase() == WorldRenderingPhase.NONE;
if (wrap) GbufferPrograms.beginEntities();
try {
entitySource.endBatch(entityGroups);
if (instancedThisCycle) {
TesrBatchRenderer.INSTANCE.instancedRenderer.endFrame();
}
AngelicaBufferSource.rebindPass();
} finally {
if (wrap) GbufferPrograms.endEntities();
entityRenderer.disableLightmap(0);
}
} finally {
GLStateManager.popStateTo(deferredDepth);
}
} finally {
if (Tracy.ENABLED) Tracy.endZone();
BatchStateGuard.resume();
}
}
private void discardHeldEntities() {
entityTranslucentsHeld = false;
entitySource.discard();
}
public boolean isActive() {
return active;
}
boolean hasHeldEntities() {
return entityTranslucentsHeld;
}
boolean hasQueuedGeometry() {
return active && (activeSource.hasPendingLayers() || shadows.hasPendingGeometry());
}
public boolean beginGlintCapture(GlintCapture capture) {
return canCaptureGlint() && capture.begin(activeGroups);
}
public boolean canCaptureGlint() {
return active && instancedThisCycle && shaderPipeline == null && !shadow && BatchEligibility.batchingAllowed() && GLStateManager.getActiveProgram() == 0 && !GLStateManager.isRecordingDisplayList();
return shaderPipeline == null && canQueueGlint();
}
/** Skipped glint blocks queue each layer as its own instanced draw, so a shader pack only needs its pass program current. */
public boolean canQueueGlint() {
return active && instancedThisCycle && !shadow && BatchEligibility.batchingAllowed() && !GLStateManager.isRecordingDisplayList()
&& GLStateManager.getActiveProgram() == (shaderPipeline != null ? shaderPipeline.getActivePassProgramId() : 0);
}
public boolean replayGlint(GlintCapture capture) {
@@ -304,7 +407,7 @@ public final class ModelPartBatcher {
}
public boolean beginArmorCapture(GlintCapture capture) {
return canCaptureGlint() && capture.beginBase(activeGroups);
return canQueueGlint() && capture.beginBase(activeGroups, currentOverlay());
}
public boolean reuseArmorBase(GlintCapture base, GlintCapture glint) {
@@ -324,7 +427,7 @@ public final class ModelPartBatcher {
}
public boolean queueSkippedArmorGlint(GlintCapture base, ResourceLocation glintTexture) {
if (!canCaptureGlint() || GLStateManager.getActiveTextureUnit() != 0 || !GLStateManager.getTextures().getTextureUnitStates(0).isEnabled() || !base.queueBothLayers(this, activeGroups, glintTexture)) return false;
if (!canQueueGlint() || GLStateManager.getActiveTextureUnit() != 0 || !GLStateManager.getTextures().getTextureUnitStates(0).isEnabled() || !base.queueBothLayers(this, activeGroups, glintTexture)) return false;
armorBaseReuses++;
glintPassReuses++;
armorSectionSkips++;
@@ -333,12 +436,13 @@ public final class ModelPartBatcher {
}
public boolean queueSkippedHeldGlint(TemplateBuffer template) {
if (!canCaptureGlint() || GLStateManager.getActiveTextureUnit() != 0 || !activeGroups.canCopyInstances(EntityMaterials.ITEM_GLINT) || !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0))) return false;
if (!canQueueGlint() || GLStateManager.getActiveTextureUnit() != 0 || !activeGroups.canCopyInstances(EntityMaterials.ITEM_GLINT) || !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0))) return false;
final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL);
final PolygonState polygon = GLStateManager.getPolygonState();
final int cullCode = EntityMaterials.ITEM_GLINT.isNoCull() ? DrawState.DISABLED : DrawState.liveCull();
final RenderLayer layer = layerFor(HeldItemGlint.texture(), EntityMaterials.ITEM_GLINT, PassOverride.NONE, offset ? polygon.getOffsetFactor() : 0, offset ? polygon.getOffsetUnits() : 0, ShaderGlint.NO_TINT, cullCode, false);
activeGroups.queueGlintLayers(template, layer, EntityMaterials.ITEM_GLINT, currentModelView(), GLSMConfig.packedLastBrightness(), VANILLA_GLINT_COLOR, currentOverlay(), HeldItemGlint.firstMatrix(), HeldItemGlint.secondMatrix());
activeGroups.queueGlintLayers(template, layer, EntityMaterials.ITEM_GLINT, currentModelView(), GLSMConfig.packedLastBrightness(), VANILLA_GLINT_COLOR, currentOverlay(), currentEntityInfo(0),
HeldItemGlint.firstMatrix(), HeldItemGlint.secondMatrix());
parts++;
glintPassReuses++;
return true;
@@ -354,8 +458,13 @@ public final class ModelPartBatcher {
return modelView;
}
private static int currentOverlay() {
return AngelicaBufferSource.packAbgr(GLStateManager.getOverlayR(), GLStateManager.getOverlayG(), GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
private long currentEntityInfo(int item) {
return shaderPipeline != null ? InstancedAttribs.packEntityInfo(CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(), item) : 0L;
}
int currentOverlay() {
return shaderPipeline != null ? AngelicaBufferSource.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor())
: AngelicaBufferSource.packAbgr(GLStateManager.getOverlayR(), GLStateManager.getOverlayG(), GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
}
boolean prepareArmorGlint() {
@@ -438,14 +547,22 @@ public final class ModelPartBatcher {
if (shadow && isDepthEqualDecalState()) {
return true;
}
return queueTemplate(entry.template, entry.cubes, scale, null);
return queueTemplate(entry.template, entry.cubes, scale, null, false);
}
public boolean queueTemplate(TemplateBuffer template, TesrMaterial material) {
return queueTemplate(template, null, 1.0f, material);
return queueTemplate(template, null, 1.0f, material, false);
}
private boolean queueTemplate(TemplateBuffer template, CubeParams[] cubes, float scale, TesrMaterial explicitMaterial) {
public boolean queueTemplate(TemplateBuffer template, TesrMaterial material, boolean afterGlint) {
return queueTemplate(template, null, 1.0f, material, afterGlint);
}
private boolean queueTemplate(TemplateBuffer template, CubeParams[] cubes, float scale, TesrMaterial explicitMaterial, boolean afterGlint) {
if (!active) {
bail(BailReason.INACTIVE);
return false;
}
if (shaderPipeline != null && GLStateManager.getActiveProgram() != shaderPipeline.getActivePassProgramId()) {
bail(BailReason.FOREIGN_PROGRAM);
return false;
@@ -488,15 +605,13 @@ public final class ModelPartBatcher {
final int glintSlot = material.special() == TesrMaterial.SpecialRender.GLINT ? lastBoundGlintSlot : ShaderGlint.NO_TINT;
final int cullCode = material.isNoCull() ? DrawState.DISABLED : DrawState.liveCull();
final boolean lit = DrawState.liveLit(material);
final RenderLayer layer = layerFor(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit);
final RenderLayer layer = layerFor(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, afterGlint);
// Entities nested inside a TESR (mob spawner, OpenBlocks trophy) run in the block entity pass but carry an entity id
final int entityId = groupId(shaderPipeline != null, mode == Mode.ENTITIES || pass.isEntityPhase(), CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity());
final Color4 color = GLStateManager.getColor();
final int colorABGR = AngelicaBufferSource.packAbgr(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
final int overlayABGR = shaderPipeline != null ? AngelicaBufferSource.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor())
: AngelicaBufferSource.packAbgr(GLStateManager.getOverlayR(), GLStateManager.getOverlayG(), GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
final long entityInfo = shaderPipeline != null ? InstancedAttribs.packEntityInfo(CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(),
CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedItem()) : 0L;
final int overlayABGR = currentOverlay();
final long entityInfo = currentEntityInfo(CapturedRenderingState.INSTANCE.getCurrentRenderedItem());
final int packedLight = GLSMConfig.packedLastBrightness();
activeGroups.queue(template, cubes, scale, layer, material, currentModelView(), packedLight, colorABGR, overlayABGR, entityInfo, entityId, texMatrix);
parts++;
@@ -591,19 +706,23 @@ public final class ModelPartBatcher {
}
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cullCode, boolean lit) {
return layerFor(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, false);
}
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cullCode, boolean lit, boolean afterGlint) {
for (int i = 0; i < RECENT_LAYERS; i++) {
final RenderLayer recent = recentLayers[i];
if (recent != null && recentKeys[i].matches(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit)) {
if (recent != null && recentKeys[i].matches(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, afterGlint)) {
return lastLayer = recent;
}
}
RenderLayer layer = layers.get(scratchKey.set(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit));
RenderLayer layer = layers.get(scratchKey.set(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, afterGlint));
if (layer == null) {
layer = RenderLayer.tesr(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit);
layers.put(new LayerKey().set(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit), layer);
layer = RenderLayer.tesr(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, afterGlint);
layers.put(new LayerKey().set(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, afterGlint), layer);
}
if (recentKeys[recentNext] == null) recentKeys[recentNext] = new LayerKey();
recentKeys[recentNext].set(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit);
recentKeys[recentNext].set(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, afterGlint);
recentLayers[recentNext] = layer;
recentNext = (recentNext + 1) % RECENT_LAYERS;
return lastLayer = layer;
@@ -622,14 +741,20 @@ public final class ModelPartBatcher {
public void clear() {
shadows.reset();
groups.clear();
entityGroups.clear();
shadowGroups.clear();
templates.clear();
entitySource.discard();
bufferSource.discard();
bufferSource.freeBuffers();
entitySource.freeBuffers();
activeGroups = groups;
activeSource = bufferSource;
entityTranslucentsHeld = false;
active = false;
shaderPipeline = null;
layers.clear();
scratchKey.set(null, null, null, 0.0f, 0.0f, ShaderGlint.NO_TINT, DrawState.DISABLED, false);
scratchKey.set(null, null, null, 0.0f, 0.0f, ShaderGlint.NO_TINT, DrawState.DISABLED, false, false);
Arrays.fill(recentLayers, null);
recentNext = 0;
lastLayer = null;
@@ -638,7 +763,7 @@ public final class ModelPartBatcher {
}
public String getDebugString() {
final String line = String.format("Parts: %d grp, inst %d, live %d", groups.groupCount() + shadowGroups.groupCount(), parts - lastParts, liveFallbacks - lastLiveFallbacks);
final String line = String.format("Parts: %d grp, inst %d, live %d", groups.groupCount() + entityGroups.groupCount() + shadowGroups.groupCount(), parts - lastParts, liveFallbacks - lastLiveFallbacks);
lastParts = parts;
lastLiveFallbacks = liveFallbacks;
return line;
@@ -4,12 +4,13 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
import com.gtnewhorizons.angelica.glsm.ffp.CombinedGlint;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import it.unimi.dsi.fastutil.floats.FloatArrayList;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@@ -181,7 +182,9 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
boolean anchored;
long frameMark = -1;
long lastUsedMs;
int entityColor;
final int entityColor;
final int itemId;
final DrawStateKey state;
final InstanceColumns templateColumns = new InstanceColumns();
final InstanceColumns cubeColumns = new InstanceColumns();
@@ -334,10 +337,13 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
runPoolUsed = 0;
}
Group(RenderLayer layer, TesrMaterial material, int blockEntityId) {
Group(RenderLayer layer, TesrMaterial material, int blockEntityId, DrawStateKey state) {
this.layer = layer;
this.material = material;
this.blockEntityId = blockEntityId;
this.state = state;
this.itemId = state.item;
this.entityColor = state.color;
this.opaque = material.transparency() == TesrMaterial.Transparency.OPAQUE;
this.stream = material.isStream();
this.mergeRuns = material.isDepthEqual() && material.isNoDepthWrite();
@@ -345,7 +351,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
}
private final Reference2ObjectOpenHashMap<RenderLayer, Reference2ObjectOpenHashMap<TesrMaterial, Int2ObjectOpenHashMap<Group>>> groups = new Reference2ObjectOpenHashMap<>();
private final Reference2ObjectOpenHashMap<RenderLayer, Reference2ObjectOpenHashMap<TesrMaterial, Object2ObjectOpenHashMap<DrawStateKey, Group>>> groups = new Reference2ObjectOpenHashMap<>();
private final Reference2ObjectOpenHashMap<RenderLayer, ObjectArrayList<Group>> byLayer = new Reference2ObjectOpenHashMap<>();
private static final int RECENT_GROUPS = 4;
private final Group[] recentGroups = new Group[RECENT_GROUPS];
@@ -389,32 +395,66 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
long captureVersion() { return frameMark; }
/** Mutable lookup avoids allocating a key for each queued model part. Stored keys are never mutated. */
private static final class DrawStateKey {
int entity, block, item, color;
DrawStateKey set(int entity, int block, int item, int color) {
this.entity = entity;
this.block = block;
this.item = item;
this.color = color;
return this;
}
boolean matches(int entity, int block, int item, int color) {
return this.entity == entity && this.block == block && this.item == item && this.color == color;
}
@Override
public boolean equals(Object other) {
return other instanceof DrawStateKey key && matches(key.entity, key.block, key.item, key.color);
}
@Override
public int hashCode() { return ((entity * 31 + block) * 31 + item) * 31 + color; }
}
private final DrawStateKey lookupState = new DrawStateKey();
private Group groupFor(RenderLayer layer, TesrMaterial material, int id) {
final int key = instanceableMaterial(material) ? 0 : id;
final boolean perInstance = instanceableMaterial(material);
final boolean entityKind = !perInstance && source.effectiveIdKind() == AngelicaBufferSource.GroupIdKind.ENTITY;
final CapturedRenderingState captured = CapturedRenderingState.INSTANCE;
final int entity = perInstance ? 0 : entityKind ? id : captured.getCurrentRenderedEntity();
final int block = perInstance ? 0 : entityKind ? captured.getCurrentRenderedBlockEntity() : id;
final int item = perInstance ? 0 : captured.getCurrentRenderedItem();
final int color = perInstance ? 0 : AngelicaBufferSource.packEntityColor(captured.getCurrentEntityColor());
Group group = null;
for (int i = 0; i < RECENT_GROUPS; i++) {
final Group recent = recentGroups[i];
if (recent != null && recent.layer == layer && recent.material == material && recent.blockEntityId == key) {
if (recent != null && recent.layer == layer && recent.material == material && recent.state.matches(entity, block, item, color)) {
group = recent;
break;
}
}
if (group == null) {
Reference2ObjectOpenHashMap<TesrMaterial, Int2ObjectOpenHashMap<Group>> byMaterial = groups.get(layer);
Reference2ObjectOpenHashMap<TesrMaterial, Object2ObjectOpenHashMap<DrawStateKey, Group>> byMaterial = groups.get(layer);
if (byMaterial == null) {
byMaterial = new Reference2ObjectOpenHashMap<>();
groups.put(layer, byMaterial);
}
Int2ObjectOpenHashMap<Group> byId = byMaterial.get(material);
if (byId == null) {
byId = new Int2ObjectOpenHashMap<>();
byMaterial.put(material, byId);
Object2ObjectOpenHashMap<DrawStateKey, Group> byState = byMaterial.get(material);
if (byState == null) {
byState = new Object2ObjectOpenHashMap<>();
byMaterial.put(material, byState);
}
group = byId.get(key);
group = byState.get(lookupState.set(entity, block, item, color));
if (group == null) {
group = new Group(layer, material, key);
final DrawStateKey key = new DrawStateKey().set(entity, block, item, color);
group = new Group(layer, material, perInstance ? 0 : id, key);
group.lastUsedMs = nowMs;
byId.put(key, group);
byState.put(key, group);
byLayer.computeIfAbsent(layer, l -> new ObjectArrayList<>()).add(group);
}
recentGroups[recentGroupNext] = group;
@@ -533,13 +573,13 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
dst.referencedParts += end - start;
}
void queueGlintLayers(TemplateBuffer template, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, Matrix4f first, Matrix4f second) {
void queueGlintLayers(TemplateBuffer template, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, Matrix4f first, Matrix4f second) {
final Group group = groupFor(layer, material, 0);
touch(group, layer);
final InstanceColumns dst = group.templateColumns;
final int index = dst.size;
final TexRun run = group.openOrExtendRun(dst, first, true, false);
dst.add(template, currentMV, packedLight, colorABGR, overlayABGR, 0L);
dst.add(template, currentMV, packedLight, colorABGR, overlayABGR, entityInfo);
run.last.end = dst.size;
run.parts++;
run.instances++;
@@ -576,7 +616,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
group.frameMark = frameMark;
group.lastUsedMs = nowMs;
group.entityColor = AngelicaBufferSource.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor());
group.hashAcc = 0;
group.clearInstances();
if (!group.anchored || TesrAnchorMath.shouldReanchor(camX, camY, camZ, group.anchorX, group.anchorY, group.anchorZ)) {
@@ -677,7 +716,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
if (!live(group) || !instanceable(group)) continue;
if (group.templateColumns.hasInstances()) {
final boolean fixedFunction = deferred == null && GLStateManager.getActiveProgram() == 0;
drawTemplateRuns(group.templateColumns, group.material.special() == TesrMaterial.SpecialRender.GLINT && fixedFunction, group.material == EntityMaterials.ITEM_GLINT && fixedFunction);
drawTemplateRuns(group.templateColumns, group.material.special() == TesrMaterial.SpecialRender.GLINT && fixedFunction, group.material == EntityMaterials.ITEM_GLINT);
}
if (!cubeAvailable && group.cubeColumns.hasInstances()) {
drawTemplateRuns(group.cubeColumns, group.material == EntityMaterials.GLINT && deferred == null, false);
@@ -810,15 +849,15 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
private static final Matrix4f IDENTITY = new Matrix4f();
static int entityFromInfo(long info) {
return (short) info;
return InstancedAttribs.unpackId(info, 0);
}
static int blockEntityFromInfo(long info) {
return (short) (info >>> 16);
return InstancedAttribs.unpackId(info, 16);
}
private int idFromInfo(long info) {
return source.effectiveIdKind() == AngelicaBufferSource.GroupIdKind.ENTITY ? entityFromInfo(info) : blockEntityFromInfo(info);
static int itemFromInfo(long info) {
return InstancedAttribs.unpackId(info, 32);
}
private void drawInstancesCpu(Group group, InstanceColumns cols) {
@@ -834,14 +873,14 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
final int runEnd = seg.end;
int sub = seg.start;
while (sub < runEnd) {
final long info = head.source != null ? 0L : data.infos.getLong(sub);
final long info = data.infos.getLong(sub);
final int overlay = data.overlays.getInt(sub);
int subEnd = sub;
while (subEnd < runEnd && (head.source != null || data.infos.getLong(subEnd) == info) && data.overlays.getInt(subEnd) == overlay) {
while (subEnd < runEnd && data.infos.getLong(subEnd) == info && data.overlays.getInt(subEnd) == overlay) {
subEnd++;
}
source.applyIdsAndRebind(entityFromInfo(info), blockEntityFromInfo(info), itemFromInfo(info));
AngelicaBufferSource.setEntityColor(overlay);
source.applyIdAndRebind(idFromInfo(info));
int start = sub;
while (start < subEnd) {
final int drawMode = templates.get(start).drawMode;
@@ -895,11 +934,11 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
if (now - group.lastUsedMs <= GROUP_TTL_MS) continue;
group.mesh.delete();
list.remove(i);
final Reference2ObjectOpenHashMap<TesrMaterial, Int2ObjectOpenHashMap<Group>> byMaterial = groups.get(group.layer);
final Reference2ObjectOpenHashMap<TesrMaterial, Object2ObjectOpenHashMap<DrawStateKey, Group>> byMaterial = groups.get(group.layer);
if (byMaterial != null) {
final Int2ObjectOpenHashMap<Group> byId = byMaterial.get(group.material);
final Object2ObjectOpenHashMap<DrawStateKey, Group> byId = byMaterial.get(group.material);
if (byId != null) {
byId.remove(group.blockEntityId);
byId.remove(group.state);
}
}
}
@@ -1020,8 +1059,8 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
private void draw(Group group) {
retainedDraws++;
source.applyIdsAndRebind(group.state.entity, group.state.block, group.state.item);
AngelicaBufferSource.setEntityColor(group.entityColor);
source.applyIdAndRebind(group.blockEntityId);
drawMV.set(baseMV).translate((float) (group.anchorX - camX), (float) (group.anchorY - camY), (float) (group.anchorZ - camZ));
saveMV();
GLStateManager.setModelViewMatrix(drawMV);
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.api.tesr.TesrShader;
import com.gtnewhorizons.angelica.client.font.BatchingFontRenderer;
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
@@ -82,6 +83,7 @@ public final class TesrBatchRenderer {
}
public void beginPass(int passKey, Matrix4f baseMV, double camX, double camY, double camZ) {
BatchStateFallback.install();
if (deferredFlushPending) {
discardDeferred();
}
@@ -158,6 +160,10 @@ public final class TesrBatchRenderer {
return activePass >= 0 || deferredFlushPending;
}
boolean hasQueuedGeometry() {
return hasPendingGeometry() && bufferSource.hasPendingLayers();
}
public void queue(TemplateBuffer template, ResourceLocation texture, TesrMaterial material) {
modelView.set(GLStateManager.getModelViewMatrix());
final int packedLight = currentPackedLight(material);
@@ -174,7 +180,8 @@ public final class TesrBatchRenderer {
? CapturedRenderingState.INSTANCE.getCurrentRenderedEntity()
: CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
final long entityInfo = InstancedAttribs.packEntityInfo(CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedItem());
retained[activePass].queue(template, layer, material, modelView, packedLight, colorABGR, 0, entityInfo, blockEntityId, captureTextureMatrix());
retained[activePass].queue(template, layer, material, modelView, packedLight, colorABGR,
AngelicaBufferSource.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor()), entityInfo, blockEntityId, captureTextureMatrix());
} else {
drawImmediate(template, texture, material, packedLight, colorABGR);
}
@@ -337,6 +344,26 @@ public final class TesrBatchRenderer {
}
public void flush() {
BatchStateGuard.suspend();
try {
flushNow();
} finally {
BatchStateGuard.resume();
}
}
void flushForStateChange() {
final RetainedTesrGroups hook = activePass >= 0 ? retained[activePass] : pendingDeferredHook;
if (!hasPendingGeometry()) return;
activePass = -1;
deferredFlushPending = false;
pendingDeferredHook = null;
bufferSource.endBatch(hook);
instancedRenderer.endFrame();
BatchingFontRenderer.flushDeferredText();
}
private void flushNow() {
final RetainedTesrGroups hook = activePass >= 0 ? retained[activePass] : null;
if (Tracy.ENABLED) Tracy.beginZone(Z_TESR_OPAQUE);
try {
@@ -373,6 +400,15 @@ public final class TesrBatchRenderer {
}
public void flushAfterDeferred() {
BatchStateGuard.suspend();
try {
flushAfterDeferredNow();
} finally {
BatchStateGuard.resume();
}
}
private void flushAfterDeferredNow() {
if (!deferredFlushPending) return;
deferredFlushPending = false;
final RetainedTesrGroups hook = pendingDeferredHook;
@@ -29,12 +29,16 @@ public final class TesrProviderDispatch {
final Object key = provider.angelica$meshKey(te);
if (key == null) return false;
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(resolveBlockEntityId(te));
CapturedRenderingState.INSTANCE.pushCurrentBlockEntity();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(te == null ? null : te.getBlockType(), te == null ? 0 : te.getBlockMetadata());
GLStateManager.glPushMatrix();
provider.angelica$transform(te, x, y, z);
AngelicaTesrMeshCache.INSTANCE.renderCached(key, provider.angelica$meshDirty(te), provider, te);
GLStateManager.glPopMatrix();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
try {
provider.angelica$transform(te, x, y, z);
AngelicaTesrMeshCache.INSTANCE.renderCached(key, provider.angelica$meshDirty(te), provider, te);
} finally {
GLStateManager.glPopMatrix();
CapturedRenderingState.INSTANCE.popCurrentBlockEntity();
}
return true;
}
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.shadercompat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
import com.gtnewhorizons.angelica.iris.IrisDisplayListState;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import net.coderbot.iris.pipeline.ShadowRenderer;
@@ -57,12 +58,14 @@ public final class ShaderGlint {
private ShaderGlint() {}
public static void beginGlint() {
if (ShadowRenderer.ACTIVE || !IrisApi.getInstance().isShaderPackInUse()) return;
IrisDisplayListState.recordGlintSpan(true);
// If a glint span is already active, keep the outer span's saved state and
// let the nested render ride on its handler.
if (depth++ > 0) return;
if (ShadowRenderer.ACTIVE || !IrisApi.getInstance().isShaderPackInUse()) return;
swapped = false;
injecting = false;
@@ -74,6 +77,7 @@ public final class ShaderGlint {
}
public static void endGlint() {
IrisDisplayListState.recordGlintSpan(false);
if (depth == 0 || --depth > 0) return;
if (GLSMHooks.glintColorHandler != COLOR_HANDLER) return;
GLSMHooks.glintColorHandler = null;
@@ -2,8 +2,11 @@ package com.gtnewhorizons.angelica.utils;
import java.util.ArrayDeque;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.mixins.interfaces.IPatchedTextureAtlasSprite;
import com.gtnewhorizons.angelica.mixins.interfaces.ITexturesCache;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureMap;
@@ -12,6 +15,49 @@ import net.minecraft.world.IBlockAccess;
public class AnimationsRenderUtils {
private static final ThreadLocal<SpriteCapture> SPRITE_CAPTURE = new ThreadLocal<>();
/** Collects visibility notifications while baking a mesh, for replay when that mesh is reused. */
public static final class SpriteCapture implements AutoCloseable {
private final ReferenceOpenHashSet<IPatchedTextureAtlasSprite> sprites = new ReferenceOpenHashSet<>();
private SpriteCapture parent;
private SpriteCapture() {
parent = SPRITE_CAPTURE.get();
SPRITE_CAPTURE.set(this);
}
public void markUsed() {
for (IPatchedTextureAtlasSprite sprite : sprites) sprite.angelica$markNeedsAnimationUpdate();
}
@Override
public void close() {
SPRITE_CAPTURE.set(parent);
if (parent != null) parent.sprites.addAll(sprites);
parent = null;
}
}
public static SpriteCapture captureSprites() {
return new SpriteCapture();
}
public static void recordSpriteUsage(IPatchedTextureAtlasSprite sprite) {
final SpriteCapture capture = SPRITE_CAPTURE.get();
if (capture != null) capture.sprites.add(sprite);
if (DisplayListManager.isRecording()) {
DisplayListManager.recordStateCommandOnce(new SpriteUsage(sprite));
}
}
private record SpriteUsage(IPatchedTextureAtlasSprite sprite) implements DisplayListCommand {
@Override
public void execute() {
sprite.angelica$markNeedsAnimationUpdate();
}
}
public static void markBlockTextureForUpdate(IIcon icon) {
markBlockTextureForUpdate(icon, null);
}
@@ -26,7 +72,7 @@ public class AnimationsRenderUtils {
if (blockAccess instanceof ITexturesCache texturesCache) {
texturesCache.getRenderedTextures().add(textureAtlasSprite);
} else if(textureAtlasSprite instanceof IPatchedTextureAtlasSprite patchedSprite){
patchedSprite.markNeedsAnimationUpdate();
patchedSprite.angelica$markNeedsAnimationUpdate();
}
}
}
@@ -38,7 +84,7 @@ public class AnimationsRenderUtils {
if (stack == null || stack.isEmpty()) {
// icon was used outside of chunk building, it's probably an item in an inventory or something
sprite.markNeedsAnimationUpdate();
sprite.angelica$markNeedsAnimationUpdate();
return;
}
@@ -43,7 +43,7 @@ public class AngelicaBufferSource implements Groupable {
private boolean prepared;
private final List<RenderLayer> order = new ArrayList<>();
private int drawStateDepth = -1;
private boolean anyIdSet;
private boolean irisStateSaved;
private GroupIdKind idKind = GroupIdKind.BLOCK_ENTITY;
public AngelicaBufferSource() {
@@ -65,7 +65,7 @@ public class AngelicaBufferSource implements Groupable {
affinities.put(type, affinity);
}
final SegmentedBufferBuilder builder = builders[affinity];
builder.begin(type, blockEntityId);
builder.begin(type, blockEntityId, effectiveIdKind() == GroupIdKind.ENTITY);
return builder;
}
@@ -73,6 +73,10 @@ public class AngelicaBufferSource implements Groupable {
renderOrderManager.begin(type);
}
public boolean hasPendingLayers() {
return prepared ? !order.isEmpty() : !renderOrderManager.getRenderOrder().isEmpty();
}
public boolean isEmpty() {
for (SegmentedBufferBuilder builder : builders) {
if (!builder.isEmpty()) return false;
@@ -112,7 +116,6 @@ public class AngelicaBufferSource implements Groupable {
}
}
affinities.clear();
anyIdSet = false;
}
List<RenderLayer> prepare() {
@@ -148,9 +151,16 @@ public class AngelicaBufferSource implements Groupable {
public void endBatch(LayerDrawHook hook) {
ensurePrepared();
try {
int lastGlint = -1;
for (int i = 0, n = order.size(); i < n; i++) {
drawLayer(order.get(i), hook);
if (getTransparencyType(order.get(i)) == TransparencyType.DECAL) lastGlint = i;
}
for (int i = 0, n = order.size(); i < n; i++) {
final RenderLayer layer = order.get(i);
if (getTransparencyType(layer) != TransparencyType.AFTER_GLINT) drawLayer(layer, hook);
if (i == lastGlint) drawAfterGlintLayers(hook);
}
if (lastGlint < 0) drawAfterGlintLayers(hook);
} catch (Throwable t) {
discardAfterFailure(t);
throw t;
@@ -159,9 +169,16 @@ public class AngelicaBufferSource implements Groupable {
finish();
}
private void drawAfterGlintLayers(LayerDrawHook hook) {
for (int i = 0, n = order.size(); i < n; i++) {
final RenderLayer layer = order.get(i);
if (getTransparencyType(layer) == TransparencyType.AFTER_GLINT) drawLayer(layer, hook);
}
}
public void pauseBatch() {
restoreDrawState();
clearCurrentId();
restoreIrisState();
}
public void discard() {
@@ -176,13 +193,20 @@ public class AngelicaBufferSource implements Groupable {
}
}
private void clearCurrentId() {
if (!anyIdSet) return;
// A pass can set either kind: nested entities inside a TESR write the entity id during a block entity pass
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(-1, 0);
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0f, 0f, 0f, 0f);
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
anyIdSet = false;
private void restoreIrisState() {
if (!irisStateSaved) return;
CapturedRenderingState.INSTANCE.popCurrentEntityColor();
CapturedRenderingState.INSTANCE.popCurrentBlockEntity();
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
irisStateSaved = false;
}
private void saveIrisState() {
if (irisStateSaved) return;
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
CapturedRenderingState.INSTANCE.pushCurrentBlockEntity();
CapturedRenderingState.INSTANCE.pushCurrentEntityColor();
irisStateSaved = true;
}
public GroupIdKind effectiveIdKind() {
@@ -198,6 +222,7 @@ public class AngelicaBufferSource implements Groupable {
drawStateDepth = GLStateManager.pushState(StateSet.BATCH);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
saveIrisState();
layer.startDrawing();
Throwable failure = null;
try {
@@ -205,7 +230,6 @@ public class AngelicaBufferSource implements Groupable {
drawDynamic(segments);
}
if (hasRetained) {
anyIdSet = true;
hook.drawLayer(layer);
}
} catch (Throwable t) {
@@ -221,7 +245,6 @@ public class AngelicaBufferSource implements Groupable {
}
private void drawDynamic(List<BufferSegment> segments) {
anyIdSet = true;
boolean matrixPushed = false;
SegmentedBufferBuilder.LayerBuffer bound = null;
boolean boundSetup = false;
@@ -230,20 +253,24 @@ public class AngelicaBufferSource implements Groupable {
GLStateManager.glPushMatrix();
matrixPushed = true;
GLStateManager.glLoadIdentity();
final boolean entityKind = effectiveIdKind() == GroupIdKind.ENTITY;
int currentId = Integer.MIN_VALUE;
int currentEntity = Integer.MIN_VALUE;
int currentBlock = Integer.MIN_VALUE;
int currentItem = Integer.MIN_VALUE;
int currentColor = 0;
boolean colorSet = false;
for (int i = 0, n = segments.size(); i < n; i++) {
final BufferSegment segment = segments.get(i);
if (entityKind && (!colorSet || segment.getEntityColor() != currentColor)) {
if (!colorSet || segment.getEntityColor() != currentColor) {
colorSet = true;
currentColor = segment.getEntityColor();
setEntityColor(currentColor);
}
if (segment.getBlockEntityId() != currentId) {
currentId = segment.getBlockEntityId();
applyIdAndRebind(currentId);
if (segment.getEntityId() != currentEntity || segment.getRenderedBlockEntityId() != currentBlock
|| segment.getItemId() != currentItem) {
currentEntity = segment.getEntityId();
currentBlock = segment.getRenderedBlockEntityId();
currentItem = segment.getItemId();
applyIdsAndRebind(currentEntity, currentBlock, currentItem);
}
final SegmentedBufferBuilder.LayerBuffer owner = segment.getOwner();
if (owner != bound) {
@@ -298,7 +325,7 @@ public class AngelicaBufferSource implements Groupable {
failure = RenderFailures.suppress(failure, t);
}
try {
clearCurrentId();
restoreIrisState();
} catch (Throwable t) {
failure = RenderFailures.suppress(failure, t);
}
@@ -328,6 +355,8 @@ public class AngelicaBufferSource implements Groupable {
}
public void freeBuffers() {
restoreDrawState();
restoreIrisState();
for (SegmentedBufferBuilder builder : builders) {
builder.freeAll();
}
@@ -361,14 +390,11 @@ public class AngelicaBufferSource implements Groupable {
this.idKind = kind;
}
public void applyIdAndRebind(int id) {
anyIdSet = true;
if (effectiveIdKind() == GroupIdKind.ENTITY) {
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(id, 0);
rebindPass();
} else {
setBlockEntityAndRebind(id);
}
public void applyIdsAndRebind(int entityId, int blockEntityId, int itemId) {
saveIrisState();
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(entityId, itemId);
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(blockEntityId);
rebindPass();
}
public static int packEntityColor(Vector4fc c) {
@@ -387,11 +413,6 @@ public class AngelicaBufferSource implements Groupable {
CapturedRenderingState.INSTANCE.setCurrentEntityColor((abgr & 0xFF) / 255f, ((abgr >>> 8) & 0xFF) / 255f, ((abgr >>> 16) & 0xFF) / 255f, (abgr >>> 24) / 255f);
}
public static void setBlockEntityAndRebind(int blockEntityId) {
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(blockEntityId);
rebindPass();
}
public static void rebindPass() {
if (Iris.enabled) {
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
@@ -1,18 +1,33 @@
package net.coderbot.batchedentityrendering.impl;
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import lombok.Getter;
public class BufferSegment {
private final RenderLayer type;
@Getter
private final int blockEntityId;
@Getter
private final int itemId;
@Getter
private final int entityId;
@Getter
private final int renderedBlockEntityId;
@Getter
private final int entityColor;
@Getter
private final int firstVertex;
@Getter
private final int vertexCount;
@Getter
private final SegmentedBufferBuilder.LayerBuffer owner;
public BufferSegment(RenderLayer type, int blockEntityId, int entityColor, int firstVertex, int vertexCount, SegmentedBufferBuilder.LayerBuffer owner) {
public BufferSegment(RenderLayer type, int blockEntityId, int entityId, int renderedBlockEntityId, int itemId, int entityColor, int firstVertex, int vertexCount, SegmentedBufferBuilder.LayerBuffer owner) {
this.type = type;
this.blockEntityId = blockEntityId;
this.itemId = itemId;
this.entityId = entityId;
this.renderedBlockEntityId = renderedBlockEntityId;
this.entityColor = entityColor;
this.firstVertex = firstVertex;
this.vertexCount = vertexCount;
@@ -23,23 +38,4 @@ public class BufferSegment {
return type;
}
public int getBlockEntityId() {
return blockEntityId;
}
public int getEntityColor() {
return entityColor;
}
public int getFirstVertex() {
return firstVertex;
}
public int getVertexCount() {
return vertexCount;
}
public SegmentedBufferBuilder.LayerBuffer getOwner() {
return owner;
}
}
@@ -74,13 +74,26 @@ public class SegmentedBufferBuilder {
private LayerBuffer current;
private RenderLayer currentType;
private int currentBlockEntityId;
private int currentItemId;
private int currentEntityId;
private int currentRenderedBlockEntityId;
private int currentEntityColor;
private int segmentStartVertex;
private long allocatedBytes;
public void begin(RenderLayer type, int blockEntityId) {
begin(type, blockEntityId, false);
}
public void begin(RenderLayer type, int blockEntityId, boolean entityKind) {
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
final int entityId = entityKind ? blockEntityId : state.getCurrentRenderedEntity();
final int renderedBlockEntityId = entityKind ? state.getCurrentRenderedBlockEntity() : blockEntityId;
final int entityColor = AngelicaBufferSource.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor());
if (type == currentType && blockEntityId == currentBlockEntityId && entityColor == currentEntityColor && current != null) {
final int itemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
if (type == currentType && blockEntityId == currentBlockEntityId && entityId == currentEntityId
&& renderedBlockEntityId == currentRenderedBlockEntityId && itemId == currentItemId
&& entityColor == currentEntityColor && current != null) {
return;
}
endSegment();
@@ -94,6 +107,9 @@ public class SegmentedBufferBuilder {
current = buffer;
currentType = type;
currentBlockEntityId = blockEntityId;
currentItemId = itemId;
currentEntityId = entityId;
currentRenderedBlockEntityId = renderedBlockEntityId;
currentEntityColor = entityColor;
segmentStartVertex = current.vertexCount;
}
@@ -142,7 +158,7 @@ public class SegmentedBufferBuilder {
private void endSegment() {
if (current != null && current.vertexCount > segmentStartVertex) {
segments.add(new BufferSegment(currentType, currentBlockEntityId, currentEntityColor, segmentStartVertex,
segments.add(new BufferSegment(currentType, currentBlockEntityId, currentEntityId, currentRenderedBlockEntityId, currentItemId, currentEntityColor, segmentStartVertex,
current.vertexCount - segmentStartVertex, current));
}
segmentStartVertex = current == null ? 0 : current.vertexCount;
@@ -14,6 +14,10 @@ public enum TransparencyType {
* These *must* be rendered after their corresponding opaque / transparent parts.
*/
DECAL,
/**
* Opaque item passes vanilla draws after an earlier pass's glint, such as a potion's bottle over its glinted liquid.
*/
AFTER_GLINT,
/**
* Water mask, should be drawn after pretty much everything except for translucent terrain and lines.
* Prevents water from appearing inside of boats.
@@ -10,6 +10,12 @@ import org.lwjgl.opengl.GL43;
public final class Program extends GlResource {
private static int managedBindProgram;
public static boolean isManagedBind(int program) {
return program != 0 && managedBindProgram == program;
}
@Getter
private final ProgramUniforms uniforms;
@@ -31,13 +37,23 @@ public final class Program extends GlResource {
public void use() {
RenderSystem.memoryBarrier(GL42.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL42.GL_TEXTURE_FETCH_BARRIER_BIT | GL43.GL_SHADER_STORAGE_BARRIER_BIT);
GLStateManager.glUseProgram(getGlId());
bindManaged(getGlId());
uniforms.update();
samplers.update();
images.update();
}
static void bindManaged(int program) {
final int previousManagedBind = managedBindProgram;
managedBindProgram = program;
try {
GLStateManager.glUseProgram(program);
} finally {
managedBindProgram = previousManagedBind;
}
}
public static void unbind() {
ProgramUniforms.clearActiveUniforms();
ProgramSamplers.clearActiveSamplers();
@@ -1,7 +1,10 @@
package net.coderbot.iris.gl.program;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import net.coderbot.iris.gl.image.ImageHolder;
import net.coderbot.iris.gl.sampler.GlSampler;
import net.coderbot.iris.gl.sampler.SamplerHolder;
@@ -54,6 +57,7 @@ public class ProgramBuilder extends ProgramUniforms.Builder implements SamplerHo
shaders.add(fragment);
int programId = ProgramCreator.create(name, shaders.toArray(new GlShader[0]));
initializeFallbackUniforms(programId);
for (GlShader shader : shaders) {
shader.destroy();
@@ -62,6 +66,42 @@ public class ProgramBuilder extends ProgramUniforms.Builder implements SamplerHo
return new ProgramBuilder(name, programId, reservedTextureUnits);
}
/** Initialize alpha and color uniforms to neutral values until the first pass update. */
private static void initializeFallbackUniforms(int programId) {
final int previous = GLStateManager.getActiveProgram();
final CommandRecorder recorder = DisplayListManager.isRecording() ? DisplayListManager.pauseRecording() : null;
try {
Program.bindManaged(programId);
seedUniform1i(programId, "iris_currentAlphaFunc", 7);
seedUniform1f(programId, "iris_currentAlphaTest", -1.0f);
seedUniform1f(programId, "alphaTestRef", -1.0f);
final int modulator = GLStateManager.glGetUniformLocation(programId, "iris_ColorModulator");
if (modulator != -1) {
RenderSystem.uniform4f(modulator, 1.0f, 1.0f, 1.0f, 1.0f);
}
} finally {
try {
Program.bindManaged(previous);
} finally {
if (recorder != null) DisplayListManager.resumeRecording(recorder);
}
}
}
private static void seedUniform1i(int programId, String name, int value) {
final int location = GLStateManager.glGetUniformLocation(programId, name);
if (location != -1) {
RenderSystem.uniform1i(location, value);
}
}
private static void seedUniform1f(int programId, String name, float value) {
final int location = GLStateManager.glGetUniformLocation(programId, name);
if (location != -1) {
RenderSystem.uniform1f(location, value);
}
}
public static ProgramBuilder beginCompute(String name, @Nullable String source, ImmutableSet<Integer> reservedTextureUnits) {
if (!RenderSystem.supportsCompute()) {
throw new IllegalStateException("This PC does not support compute shaders, but it's attempting to be used???");
@@ -3,6 +3,7 @@ package net.coderbot.iris.gl.program;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import java.nio.IntBuffer;
import java.util.ArrayList;
@@ -11,7 +12,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.function.Supplier;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
@@ -23,7 +23,6 @@ import net.coderbot.iris.gl.uniform.UniformUpdateFrequency;
import net.coderbot.iris.uniforms.SystemTimeUniforms;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import org.joml.Vector3i;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBShaderImageLoadStore;
import org.lwjgl.opengl.GL11;
@@ -32,6 +31,7 @@ import org.lwjgl.opengl.GL30;
public class ProgramUniforms {
private static ProgramUniforms active;
private final int program;
private final ImmutableList<Uniform> perTick;
private final ImmutableList<Uniform> perFrame;
private final ImmutableList<Uniform> dynamic;
@@ -40,9 +40,11 @@ public class ProgramUniforms {
private ImmutableList<Uniform> once;
long lastTick = -1;
int lastFrame = -1;
private boolean hasDeferredUploads;
public ProgramUniforms(ImmutableList<Uniform> once, ImmutableList<Uniform> perTick, ImmutableList<Uniform> perFrame,
public ProgramUniforms(int program, ImmutableList<Uniform> once, ImmutableList<Uniform> perTick, ImmutableList<Uniform> perFrame,
ImmutableList<Uniform> dynamic, ImmutableList<ValueUpdateNotifier> notifiersToReset) {
this.program = program;
this.once = once;
this.perTick = perTick;
this.perFrame = perFrame;
@@ -68,11 +70,18 @@ public class ProgramUniforms {
}
public void update() {
// glUseProgram is only recorded during GL_COMPILE
if (DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.COMPILE
|| GLStateManager.getActiveProgram() != program) {
hasDeferredUploads = true;
return;
}
if (active != null) {
active.removeListeners();
}
active = this;
hasDeferredUploads = false;
updateStage(dynamic);
@@ -118,6 +127,19 @@ public class ProgramUniforms {
}
}
public static boolean isActiveProgramBound() {
return active != null && GLStateManager.getActiveProgram() == active.program;
}
public static void markActiveDeferred() {
if (active != null) {
active.hasDeferredUploads = true;
}
}
public static boolean activeHasDeferredUploads() {
return active != null && active.hasDeferredUploads;
}
public static Builder builder(String name, int program) {
return new Builder(name, program);
}
@@ -263,7 +285,7 @@ public class ProgramUniforms {
}
}
return new ProgramUniforms(ImmutableList.copyOf(once.values()), ImmutableList.copyOf(perTick.values()), ImmutableList.copyOf(perFrame.values()),
return new ProgramUniforms(program, ImmutableList.copyOf(once.values()), ImmutableList.copyOf(perTick.values()), ImmutableList.copyOf(perFrame.values()),
ImmutableList.copyOf(dynamic.values()), ImmutableList.copyOf(notifiersToReset));
}
@@ -4,7 +4,7 @@ import com.gtnewhorizons.angelica.glsm.RenderSystem;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
public class FloatUniform extends Uniform {
private final Runnable updateListener = this::updateValue;
private final Runnable updateListener = uploadWhileBound(this::updateValue);
private float cachedValue;
private final FloatSupplier value;
@@ -31,8 +31,9 @@ public class FloatUniform extends Uniform {
private void updateValue() {
float newValue = value.getAsFloat();
if (cachedValue != newValue) {
if (dirty || cachedValue != newValue) {
cachedValue = newValue;
dirty = false;
RenderSystem.uniform1f(location, newValue);
}
}
@@ -6,7 +6,7 @@ import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import java.util.function.IntSupplier;
public class IntUniform extends Uniform {
private final Runnable updateListener = this::updateValue;
private final Runnable updateListener = uploadWhileBound(this::updateValue);
private int cachedValue;
private final IntSupplier value;
@@ -33,8 +33,9 @@ public class IntUniform extends Uniform {
private void updateValue() {
int newValue = value.getAsInt();
if (cachedValue != newValue) {
if (dirty || cachedValue != newValue) {
cachedValue = newValue;
dirty = false;
RenderSystem.uniform1i(location, newValue);
}
}
@@ -1,10 +1,13 @@
package net.coderbot.iris.gl.uniform;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import net.coderbot.iris.gl.program.ProgramUniforms;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
public abstract class Uniform {
protected final int location;
protected final ValueUpdateNotifier notifier;
protected boolean dirty = true;
Uniform(int location) {
this(location, null);
@@ -17,6 +20,18 @@ public abstract class Uniform {
public abstract void update();
protected final Runnable uploadWhileBound(Runnable upload) {
return () -> {
if (DisplayListManager.getRecordMode() != DisplayListManager.RecordMode.COMPILE
&& ProgramUniforms.isActiveProgramBound()) {
upload.run();
} else {
dirty = true;
ProgramUniforms.markActiveDeferred();
}
};
}
public final int getLocation() {
return location;
}
@@ -8,7 +8,7 @@ import org.joml.Vector2ic;
import java.util.function.Supplier;
public class Vector2IntegerJomlUniform extends Uniform {
private final Runnable updateListener = this::updateValue;
private final Runnable updateListener = uploadWhileBound(this::updateValue);
private final Vector2i cachedValue;
private final Supplier<Vector2ic> value;
@@ -35,7 +35,8 @@ public class Vector2IntegerJomlUniform extends Uniform {
private void updateValue() {
Vector2ic newValue = value.get();
if (!newValue.equals(cachedValue)) {
if (dirty || !newValue.equals(cachedValue)) {
dirty = false;
cachedValue.set(newValue.x(), newValue.y());
RenderSystem.uniform2i(this.location, cachedValue.x, cachedValue.y);
}
@@ -8,7 +8,7 @@ import org.joml.Vector3ic;
import java.util.function.Supplier;
public class Vector3IntegerJomlUniform extends Uniform {
private final Runnable updateListener = this::updateValue;
private final Runnable updateListener = uploadWhileBound(this::updateValue);
private final Vector3i cachedValue;
private final Supplier<Vector3ic> value;
@@ -35,7 +35,8 @@ public class Vector3IntegerJomlUniform extends Uniform {
private void updateValue() {
Vector3ic newValue = value.get();
if (!newValue.equals(cachedValue)) {
if (dirty || !newValue.equals(cachedValue)) {
dirty = false;
cachedValue.set(newValue.x(), newValue.y(), newValue.z());
RenderSystem.uniform3i(this.location, cachedValue.x(), cachedValue.y(), cachedValue.z());
}
@@ -7,7 +7,7 @@ import java.util.Arrays;
import java.util.function.Supplier;
public class Vector4ArrayUniform extends Uniform {
private final Runnable updateListener = this::updateValue;
private final Runnable updateListener = uploadWhileBound(this::updateValue);
private final Supplier<float[]> value;
private float[] cachedValue;
@@ -34,7 +34,8 @@ public class Vector4ArrayUniform extends Uniform {
private void updateValue() {
float[] newValue = value.get();
if (!Arrays.equals(newValue, cachedValue)) {
if (dirty || !Arrays.equals(newValue, cachedValue)) {
dirty = false;
cachedValue = newValue;
RenderSystem.uniform4f(location, cachedValue[0], cachedValue[1], cachedValue[2], cachedValue[3]);
}
@@ -8,7 +8,7 @@ import org.joml.Vector4ic;
import java.util.function.Supplier;
public class Vector4IntegerJomlUniform extends Uniform {
private final Runnable updateListener = this::updateValue;
private final Runnable updateListener = uploadWhileBound(this::updateValue);
private final Vector4i cachedValue;
private final Supplier<Vector4ic> value;
@@ -35,7 +35,8 @@ public class Vector4IntegerJomlUniform extends Uniform {
private void updateValue() {
Vector4ic newValue = value.get();
if (!newValue.equals(cachedValue)) {
if (dirty || !newValue.equals(cachedValue)) {
dirty = false;
cachedValue.set(newValue.x(), newValue.y(), newValue.z(), newValue.w());
RenderSystem.uniform4i(this.location, cachedValue.x, cachedValue.y, cachedValue.z, cachedValue.w);
}
@@ -8,7 +8,7 @@ import org.joml.Vector4fc;
import java.util.function.Supplier;
public class Vector4Uniform extends Uniform {
private final Runnable updateListener = this::updateValue;
private final Runnable updateListener = uploadWhileBound(this::updateValue);
private final Vector4f cachedValue;
private final Supplier<Vector4fc> value;
@@ -35,7 +35,8 @@ public class Vector4Uniform extends Uniform {
private void updateValue() {
Vector4fc newValue = value.get();
if (!newValue.equals(cachedValue)) {
if (dirty || !newValue.equals(cachedValue)) {
dirty = false;
cachedValue.set(newValue.x(), newValue.y(), newValue.z(), newValue.w());
RenderSystem.uniform4f(location, cachedValue.x(), cachedValue.y(), cachedValue.z(), cachedValue.w());
}
@@ -1,10 +1,12 @@
package net.coderbot.iris.layer;
import lombok.Getter;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.iris.IrisDisplayListState;
import net.coderbot.iris.gl.shader.ProgramCreator;
import net.coderbot.iris.gl.state.StateUpdateNotifiers;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
@@ -179,20 +181,48 @@ public class GbufferPrograms {
}
public static boolean beginNestedEntityPhase() {
IrisDisplayListState.recordNestedEntityScope(true);
if (getCurrentPhase() != WorldRenderingPhase.BLOCK_ENTITIES) {
return false;
}
setOverridePhase(WorldRenderingPhase.ENTITIES);
applyPushOverridePhase(WorldRenderingPhase.ENTITIES);
return true;
}
public static void endNestedEntityPhase(boolean pushed) {
IrisDisplayListState.recordNestedEntityScope(false);
if (pushed) {
setOverridePhase(null);
applyPopOverridePhase();
}
}
public static void setOverridePhase(WorldRenderingPhase phase) {
IrisDisplayListState.recordOverridePhase(phase);
applyOverridePhase(phase);
}
private static final ObjectArrayList<WorldRenderingPhase> phaseStack = new ObjectArrayList<>();
public static void pushOverridePhase(WorldRenderingPhase phase) {
IrisDisplayListState.recordPhaseScope(phase, true);
applyPushOverridePhase(phase);
}
private static void applyPushOverridePhase(WorldRenderingPhase phase) {
phaseStack.add(overridePhase);
applyOverridePhase(phase);
}
public static void popOverridePhase() {
IrisDisplayListState.recordPhaseScope(null, false);
applyPopOverridePhase();
}
private static void applyPopOverridePhase() {
applyOverridePhase(phaseStack.pop());
}
private static void applyOverridePhase(WorldRenderingPhase phase) {
overridePhase = phase;
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
@@ -206,6 +236,7 @@ public class GbufferPrograms {
private static WorldRenderingPhase overridePhase;
public static Boolean beginTranslucencyDeclaration(Boolean translucent) {
IrisDisplayListState.recordTranslucencyScope(translucent, true);
final Boolean previous = declaredTranslucent;
declaredTranslucent = translucent;
applyTranslucencyDeclaration();
@@ -213,11 +244,13 @@ public class GbufferPrograms {
}
public static void endTranslucencyDeclaration(Boolean previous) {
IrisDisplayListState.recordTranslucencyScope(null, false);
declaredTranslucent = previous;
applyTranslucencyDeclaration();
}
public static void setTranslucencyDeclaration(Boolean translucent) {
IrisDisplayListState.recordTranslucency(translucent);
declaredTranslucent = translucent;
applyTranslucencyDeclaration();
}
@@ -233,6 +266,7 @@ public class GbufferPrograms {
}
private static SpecialCondition currentSpecial;
private static final ObjectArrayList<SpecialCondition> specialStack = new ObjectArrayList<>();
public static SpecialCondition getSpecialCondition() {
return currentSpecial;
@@ -243,6 +277,12 @@ public class GbufferPrograms {
}
public static void setupSpecialRenderCondition(SpecialCondition override) {
IrisDisplayListState.recordSpecialCondition(override, true);
specialStack.add(currentSpecial);
applySpecialCondition(override);
}
private static void applySpecialCondition(SpecialCondition override) {
currentSpecial = override;
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
@@ -252,12 +292,8 @@ public class GbufferPrograms {
}
public static void teardownSpecialRenderCondition() {
currentSpecial = null;
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
if (pipeline != null) {
pipeline.setSpecialCondition(null);
}
IrisDisplayListState.recordSpecialCondition(null, false);
applySpecialCondition(specialStack.pop());
}
public static void runPhaseChangeNotifier() {
@@ -13,6 +13,7 @@ public record PassOverride(SpecialCondition special, Boolean translucent, WorldR
public static final PassOverride NONE = new PassOverride(null, null, null);
private static final ObjectArrayList<PassOverride> INTERNED = new ObjectArrayList<>();
private static final ObjectArrayList<Boolean> translucencyStack = new ObjectArrayList<>();
public PassOverride(SpecialCondition special, Boolean translucent) {
this(special, translucent, null);
@@ -38,14 +39,14 @@ public record PassOverride(SpecialCondition special, Boolean translucent, WorldR
public void apply() {
GbufferPrograms.setupSpecialRenderCondition(special);
GbufferPrograms.setTranslucencyDeclaration(translucent);
GbufferPrograms.setOverridePhase(phase);
translucencyStack.add(GbufferPrograms.beginTranslucencyDeclaration(translucent));
GbufferPrograms.pushOverridePhase(phase);
}
public void clear() {
GbufferPrograms.setupSpecialRenderCondition(null);
GbufferPrograms.setTranslucencyDeclaration(null);
GbufferPrograms.setOverridePhase(null);
GbufferPrograms.popOverridePhase();
GbufferPrograms.endTranslucencyDeclaration(translucencyStack.pop());
GbufferPrograms.teardownSpecialRenderCondition();
}
public boolean isEntityPhase() {
@@ -1,5 +1,8 @@
package net.coderbot.iris.pipeline;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.iris.IrisDisplayListState;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
@@ -10,10 +13,10 @@ import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix3f;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f;
import org.joml.Matrix3f;
import org.joml.Matrix4fc;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.PendingProgramSelection;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
@@ -113,7 +116,6 @@ import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
@@ -837,6 +839,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
return shouldRenderParticlesBeforeDeferred;
}
public boolean shouldSeparateEntityDraws() {
return packDirectives.shouldUseSeparateEntityDraws();
}
@Override
public boolean allowConcurrentCompute() {
return allowConcurrentCompute;
@@ -991,6 +997,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
private boolean matchingBlend;
private void requestMatch() {
if (DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.COMPILE) return;
if (GLStateManager.isForeignDraw() || BatchEligibility.batchingAllowed()) {
GLSMHooks.pendingProgramSelection = this;
return;
@@ -1022,6 +1029,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
}
private void matchPass(RenderCondition condition) {
if (DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.COMPILE) return;
currentCondition = condition;
beginPass(table.match(condition, inputs));
}
@@ -1041,6 +1049,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
}
public void beginPass(Pass pass) {
IrisDisplayListState.runProgramTransition(() -> beginPassNow(pass));
}
private void beginPassNow(Pass pass) {
if (current == pass) {
return;
}
@@ -1064,9 +1076,11 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
}
private boolean drivingProgram;
private Instancing activeInstancing = Instancing.NONE;
@Override
public void rebindCurrentPass() {
activeInstancing = Instancing.NONE;
GLSMHooks.resolvePendingProgram();
final Pass pass = this.current;
if (pass == null) {
@@ -1353,6 +1367,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
}
public void use() {
IrisDisplayListState.runProgramTransition(this::useNow);
}
private void useNow() {
DepthColorStorage.unlockDepthColor();
if (isBeforeTranslucent) {
@@ -1388,11 +1406,12 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
bufferBlendOverrides.forEach(BufferBlendOverride::apply);
}
if (program != null) {
program.use();
if (activeInstancing != Instancing.NONE && program != null) {
DeferredWorldRenderingPipeline.this.bindInstancedVariant(activeInstancing);
} else {
if (program != null) program.use();
DeferredWorldRenderingPipeline.this.customUniforms.push(this);
}
DeferredWorldRenderingPipeline.this.customUniforms.push(this);
}
public void stopUsing() {
@@ -1483,6 +1502,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
@Override
public void bindInstancedVariant(Instancing kind) {
activeInstancing = kind;
final Program variant = current.instancedVariant(kind);
variant.use();
this.customUniforms.push(variant);
@@ -2265,13 +2285,18 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
currentNormalTexture = pbrHolder.getNormalTexture().getGlTextureId();
currentSpecularTexture = pbrHolder.getSpecularTexture().getGlTextureId();
final TextureFormat textureFormat = TextureFormatLoader.getFormat();
if (textureFormat != null) {
textureFormat.setupTextureParameters(PBRType.NORMAL, pbrHolder.getNormalTexture());
textureFormat.setupTextureParameters(PBRType.SPECULAR, pbrHolder.getSpecularTexture());
}
BatchStateGuard.suspend();
try {
final TextureFormat textureFormat = TextureFormatLoader.getFormat();
if (textureFormat != null) {
textureFormat.setupTextureParameters(PBRType.NORMAL, pbrHolder.getNormalTexture());
textureFormat.setupTextureParameters(PBRType.SPECULAR, pbrHolder.getSpecularTexture());
}
PBRTextureManager.notifyPBRTexturesChanged();
PBRTextureManager.notifyPBRTexturesChanged();
} finally {
BatchStateGuard.resume();
}
}
}
@@ -32,11 +32,11 @@ class EntityPatcher {
switch (parameters.type) {
case VERTEX -> {
if (!transformer.hasVariable("iris_Entity")) {
transformer.injectVariable("layout(location = " + InstancedAttribs.LOC_ENTITY + ") in ivec4 iris_Entity;");
}
removeIfDeclared(transformer, "iris_Entity");
transformer.injectVariable("layout(location = " + InstancedAttribs.LOC_ENTITY + ") in uvec4 iris_Entity;");
transformer.injectVariable("flat out ivec3 iris_entityInfo;");
transformer.prependMain("iris_entityInfo = iris_Entity.xyz;");
transformer.prependMain("iris_entityInfo = ivec3(iris_Entity.x == 65535u ? -1 : int(iris_Entity.x), "
+ "iris_Entity.y == 65535u ? -1 : int(iris_Entity.y), iris_Entity.z == 65535u ? -1 : int(iris_Entity.z));");
}
case TESSELATION_CONTROL -> {
transformer.injectVariable("flat out ivec3 iris_entityInfoTCS[];");
@@ -1,8 +1,17 @@
package net.coderbot.iris.uniforms;
import com.gtnewhorizons.angelica.iris.IrisDisplayListState;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.floats.FloatArrayList;
import lombok.Getter;
import lombok.Setter;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import org.joml.Vector4f;
@@ -26,9 +35,13 @@ public class CapturedRenderingState {
@Getter
private int currentRenderedItem = -1;
private Runnable itemIdListener = null;
private final IntArrayList itemStack = new IntArrayList();
private final IntArrayList entityStack = new IntArrayList();
private final IntArrayList blockEntityStack = new IntArrayList();
private final FloatArrayList entityColorStack = new FloatArrayList();
@Getter
private final Vector4f currentEntityColor = new Vector4f();
private final Vector4f currentEntityColor = new Vector4f(0f);
private Runnable entityColorListener = null;
private CapturedRenderingState() {
@@ -47,6 +60,29 @@ public class CapturedRenderingState {
}
public void setCurrentBlockEntity(int entity) {
IrisDisplayListState.recordBlockEntity(entity);
applyCurrentBlockEntity(entity);
}
public void setCurrentBlockEntity(Block block, int metadata) {
IrisDisplayListState.recordBlockEntity(block, metadata);
final var matches = BlockRenderingSettings.INSTANCE.getBlockMetaMatches();
final var meta = matches == null || block == null ? null : matches.get(block);
applyCurrentBlockEntity(meta == null ? 0 : Math.max(0, meta.get(metadata)));
}
public void pushCurrentBlockEntity() {
IrisDisplayListState.recordBlockEntityScope(true);
blockEntityStack.push(currentRenderedBlockEntity);
}
public void popCurrentBlockEntity() {
IrisDisplayListState.recordBlockEntityScope(false);
applyCurrentBlockEntity(blockEntityStack.popInt());
}
private void applyCurrentBlockEntity(int entity) {
if (currentRenderedBlockEntity == entity) return;
this.currentRenderedBlockEntity = entity;
if (this.blockEntityIdListener != null) {
@@ -55,6 +91,7 @@ public class CapturedRenderingState {
}
private void setCurrentEntity(int entity) {
if (currentRenderedEntity == entity) return;
this.currentRenderedEntity = entity;
if (this.entityIdListener != null) {
@@ -66,11 +103,59 @@ public class CapturedRenderingState {
* I should have done this a while ago, better solution to not forget these exist in tandem.
*/
public void setCurrentEntityAndItem(int entity, int item) {
IrisDisplayListState.recordEntityAndItem(entity, item);
setCurrentEntity(entity);
setCurrentRenderedItem(item);
applyCurrentRenderedItem(item);
}
public void setCurrentNamedEntity(NamespacedId entity) {
IrisDisplayListState.recordNamedEntity(entity);
final var ids = BlockRenderingSettings.INSTANCE.getEntityIds();
setCurrentEntity(ids == null ? -1 : ids.applyAsInt(entity));
applyCurrentRenderedItem(0);
}
public void pushCurrentEntityAndItem() {
IrisDisplayListState.recordEntityScope(true);
entityStack.push(currentRenderedEntity);
itemStack.push(currentRenderedItem);
}
public void setCurrentRenderedEntity(Entity entity) {
IrisDisplayListState.recordEntity(entity);
setCurrentEntity(EntityIdHelper.getEntityId(entity));
applyCurrentRenderedItem(0);
}
public void popCurrentEntityAndItem() {
IrisDisplayListState.recordEntityScope(false);
// Restore the playback caller, never the IDs seen while compiling the list.
setCurrentEntity(entityStack.popInt());
applyCurrentRenderedItem(itemStack.popInt());
}
public void setCurrentEntityColor(float r, float g, float b, float a) {
IrisDisplayListState.recordEntityColor(r, g, b, a);
applyCurrentEntityColor(r, g, b, a);
}
public void pushCurrentEntityColor() {
IrisDisplayListState.recordEntityColorScope(true);
entityColorStack.push(currentEntityColor.x);
entityColorStack.push(currentEntityColor.y);
entityColorStack.push(currentEntityColor.z);
entityColorStack.push(currentEntityColor.w);
}
public void popCurrentEntityColor() {
IrisDisplayListState.recordEntityColorScope(false);
final float a = entityColorStack.popFloat(), b = entityColorStack.popFloat();
final float g = entityColorStack.popFloat(), r = entityColorStack.popFloat();
applyCurrentEntityColor(r, g, b, a);
}
private void applyCurrentEntityColor(float r, float g, float b, float a) {
if (currentEntityColor.equals(r, g, b, a)) return;
this.currentEntityColor.set(r, g, b, a);
if (this.entityColorListener != null) {
@@ -79,6 +164,42 @@ public class CapturedRenderingState {
}
public void setCurrentRenderedItem(int item) {
IrisDisplayListState.recordItemId(item);
applyCurrentRenderedItem(item);
}
public void pushCurrentRenderedItem() {
IrisDisplayListState.recordItemScope(true);
itemStack.push(currentRenderedItem);
}
public void setCurrentRenderedItem(ItemStack item) {
IrisDisplayListState.recordItem(item);
applyCurrentRenderedItem(ItemMaterialHelper.getMaterialId(item));
}
public void setCurrentRenderedItem(Item item, int metadata) {
IrisDisplayListState.recordItem(item, metadata);
applyCurrentRenderedItem(ItemMaterialHelper.getMaterialId(item, metadata));
}
public void setCurrentNamedItem(NamespacedId item) {
IrisDisplayListState.recordNamedItem(item);
final var ids = BlockRenderingSettings.INSTANCE.getItemIds();
applyCurrentRenderedItem(ids == null ? 0 : ids.applyAsInt(item));
}
public void setCurrentRenderedBlockItem(Block block, int metadata) {
IrisDisplayListState.recordBlockItem(block, metadata);
applyCurrentRenderedItem(ItemMaterialHelper.getMaterialId(block, metadata));
}
public void popCurrentRenderedItem() {
IrisDisplayListState.recordItemScope(false);
applyCurrentRenderedItem(itemStack.popInt());
}
private void applyCurrentRenderedItem(int item) {
if (this.currentRenderedItem == item) {
return;
}
@@ -55,21 +55,12 @@ public final class EntityIdHelper {
* @return The entity ID, or -1 if no mapping exists
*/
public static int getEntityId(Entity entity) {
Object2IntFunction<NamespacedId> entityIdMap = BlockRenderingSettings.INSTANCE.getEntityIds();
if (entity == null) return -1;
final Object2IntFunction<NamespacedId> entityIdMap = currentEntityIds();
if (entityIdMap == null) {
return -1;
}
// Invalidate caches if the map changed (shader reload)
if (entityIdMap != cachedEntityIdMap) {
entityIdCache.clear();
entityNameCache.clear();
entityNbtCache.clear();
cachedEntityIdMap = entityIdMap;
cachedCurrentPlayerId = entityIdMap.applyAsInt(CURRENT_PLAYER);
cachedConvertingVillagerId = entityIdMap.applyAsInt(CONVERTING_VILLAGER);
}
// Check NBT-conditional match first
final NbtConditionalIdMap<NamespacedId> entityNbtMap = BlockRenderingSettings.INSTANCE.getEntityNbtMap();
if (entityNbtMap != null && !entityNbtMap.isEmpty() && entity.worldObj != null) {
@@ -100,7 +91,7 @@ public final class EntityIdHelper {
}
// Normal entity type lookup
final int normalId = getNormalEntityId(entity, entityIdMap);
final int normalId = getNormalEntityId(entity.getClass(), getCachedEntityName(entity), entityIdMap);
// Check for special entity type overrides
final int specialId = getSpecialEntityId(entity);
@@ -111,6 +102,52 @@ public final class EntityIdHelper {
return normalId;
}
/** A display list retains the entity's material identity without retaining its world. */
public record EntityIdentity(Class<?> type, NamespacedId registeredName, boolean currentPlayer,
boolean convertingVillager, NBTTagCompound nbt) {
public int resolve() {
final Object2IntFunction<NamespacedId> ids = currentEntityIds();
if (ids == null) return -1;
final NbtConditionalIdMap<NamespacedId> conditions = BlockRenderingSettings.INSTANCE.getEntityNbtMap();
if (nbt != null && registeredName != null && conditions != null && !conditions.isEmpty()
&& conditions.hasConditions(registeredName)) {
final int conditional = conditions.resolve(registeredName, nbt);
if (conditional != -1) return conditional;
}
final int special = currentPlayer ? cachedCurrentPlayerId
: convertingVillager ? cachedConvertingVillagerId : -1;
return special != -1 ? special : getNormalEntityId(type, registeredName, ids);
}
}
public static EntityIdentity snapshot(Entity entity) {
if (entity == null) return null;
NBTTagCompound nbt = null;
if (entity.worldObj != null) {
nbt = new NBTTagCompound();
entity.writeToNBT(nbt);
// Entity serializers can attach live item tags to the output compound.
nbt = (NBTTagCompound) nbt.copy();
}
final String registeredName = EntityList.getEntityString(entity);
return new EntityIdentity(entity.getClass(), registeredName == null ? null : new NamespacedId(registeredName),
entity instanceof EntityPlayer && entity == Minecraft.getMinecraft().renderViewEntity,
entity instanceof EntityZombie zombie && zombie.isConverting(), nbt);
}
private static Object2IntFunction<NamespacedId> currentEntityIds() {
final Object2IntFunction<NamespacedId> ids = BlockRenderingSettings.INSTANCE.getEntityIds();
if (ids != cachedEntityIdMap) {
entityIdCache.clear();
entityNameCache.clear();
entityNbtCache.clear();
cachedEntityIdMap = ids;
cachedCurrentPlayerId = ids == null ? -1 : ids.applyAsInt(CURRENT_PLAYER);
cachedConvertingVillagerId = ids == null ? -1 : ids.applyAsInt(CONVERTING_VILLAGER);
}
return ids;
}
/**
* Check if the entity should use a special hardcoded entity ID.
*/
@@ -134,10 +171,7 @@ public final class EntityIdHelper {
* Get the normal entity ID based on entity type, with caching.
* Uses Class<?> as cache key for fast reference-equality lookups.
*/
private static int getNormalEntityId(Entity entity, Object2IntFunction<NamespacedId> entityIdMap) {
// Use entity class as cache key
Class<?> entityClass = entity.getClass();
private static int getNormalEntityId(Class<?> entityClass, NamespacedId entityName, Object2IntFunction<NamespacedId> entityIdMap) {
// Check cache first
int cached = entityIdCache.getInt(entityClass);
if (cached != Integer.MIN_VALUE) {
@@ -145,7 +179,7 @@ public final class EntityIdHelper {
}
// Cache miss, store for next lookup
int resolvedId = resolveEntityId(entity, entityClass, entityIdMap);
int resolvedId = resolveEntityId(entityClass, entityName, entityIdMap);
entityIdCache.put(entityClass, resolvedId);
return resolvedId;
@@ -158,11 +192,10 @@ public final class EntityIdHelper {
* 3. Fully qualified class name (e.g., "net.minecraft.entity.effect.EntityLightningBolt")
* 4. Special hardcoded cases (e.g., "minecraft:lightning_bolt")
*/
private static int resolveEntityId(Entity entity, Class<?> entityClass, Object2IntFunction<NamespacedId> entityIdMap) {
private static int resolveEntityId(Class<?> entityClass, NamespacedId entityType, Object2IntFunction<NamespacedId> entityIdMap) {
// Try registered entity name first (most common)
String entityType = EntityList.getEntityString(entity);
if (entityType != null) {
return entityIdMap.applyAsInt(new NamespacedId(entityType));
return entityIdMap.applyAsInt(entityType);
}
String simpleClassName = entityClass.getSimpleName();
@@ -173,7 +206,7 @@ public final class EntityIdHelper {
id = entityIdMap.applyAsInt(new NamespacedId(className));
}
if (id == -1 && entity instanceof EntityLightningBolt) {
if (id == -1 && EntityLightningBolt.class.isAssignableFrom(entityClass)) {
// Shaderpacks use "minecraft:lightning_bolt" so we should continue to provide it here
// Even if we can just use EntityLightningBolt
id = entityIdMap.applyAsInt(LIGHTNING_BOLT_ID);
@@ -188,12 +221,13 @@ public final class EntityIdHelper {
private static NamespacedId getCachedEntityName(Entity entity) {
Class<?> entityClass = entity.getClass();
NamespacedId cached = entityNameCache.get(entityClass);
if (cached != null) {
if (cached != null || entityNameCache.containsKey(entityClass)) {
return cached;
}
String entityType = EntityList.getEntityString(entity);
if (entityType == null) {
entityNameCache.put(entityClass, null);
return null;
}
@@ -10,6 +10,14 @@ import net.minecraft.item.ItemStack;
* Helper class to manage the currentRenderedItem ID uniform.
*/
public class ItemIdManager {
public static void pushItemId() {
CapturedRenderingState.INSTANCE.pushCurrentRenderedItem();
}
public static void popItemId() {
CapturedRenderingState.INSTANCE.popCurrentRenderedItem();
}
/**
* Set the item ID for an armor piece or held item.
* If the ItemStack is null/empty, resets to 0.
@@ -18,20 +26,14 @@ public class ItemIdManager {
* @param itemStack The armor or item being rendered
*/
public static void setItemId(ItemStack itemStack) {
if (itemStack == null || itemStack.getItem() == null) {
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(0);
return;
}
int id = ItemMaterialHelper.getMaterialId(itemStack);
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(id);
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(itemStack);
}
/**
* Set the item ID for a Block rendered outside of terrain.
*/
public static void setBlockId(Block block, int metadata) {
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(ItemMaterialHelper.getMaterialId(block, metadata));
CapturedRenderingState.INSTANCE.setCurrentRenderedBlockItem(block, metadata);
}
/**
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.animation;
import com.gtnewhorizons.angelica.mixins.interfaces.IPatchedTextureAtlasSprite;
import com.gtnewhorizons.angelica.utils.AnimationsRenderUtils;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.data.AnimationMetadataSection;
import org.spongepowered.asm.mixin.Mixin;
@@ -21,12 +22,13 @@ public abstract class MixinTextureAtlasSprite implements IPatchedTextureAtlasSpr
@Unique private boolean angelica$isActive = false;
@Override
public void markNeedsAnimationUpdate() {
public void angelica$markNeedsAnimationUpdate() {
this.angelica$isActive = true;
AnimationsRenderUtils.recordSpriteUsage(this);
}
@Override
public boolean needsAnimationUpdate() {
public boolean angelica$needsAnimationUpdate() {
if (this.angelica$isActive) {
this.angelica$isActive = false;
return true;
@@ -35,12 +37,12 @@ public abstract class MixinTextureAtlasSprite implements IPatchedTextureAtlasSpr
}
@Override
public void unmarkNeedsAnimationUpdate() {
public void angelica$unmarkNeedsAnimationUpdate() {
this.angelica$isActive = false;
}
@Override
public void updateAnimationsDryRun() {
public void angelica$updateAnimationsDryRun() {
if (animationMetadata == null || framesTextureData == null) return;
tickCounter++;
@@ -30,7 +30,7 @@ public abstract class MixinTextureMap extends AbstractTexture {
@Overwrite
public void updateAnimations() {
final boolean renderVisible = ClientProxy.options().performance.animateOnlyVisibleTextures;
Minecraft.getMinecraft().mcProfiler.startSection("updateAnimations");
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, this.getGlTextureId());
@@ -40,11 +40,11 @@ public abstract class MixinTextureMap extends AbstractTexture {
final IPatchedTextureAtlasSprite patched = (IPatchedTextureAtlasSprite) sprite;
// needsAnimationUpdate() is one-shot: returns true if marked, then auto-resets
if (!renderVisible || patched.needsAnimationUpdate()) {
if (!renderVisible || patched.angelica$needsAnimationUpdate()) {
sprite.updateAnimation();
} else {
// Keep frame counters in sync for sprites not being updated
patched.updateAnimationsDryRun();
patched.angelica$updateAnimationsDryRun();
}
}
Minecraft.getMinecraft().mcProfiler.endSection();
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.CombinedGlint;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.rendering.items.HeldItemGlint;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
@@ -77,11 +78,16 @@ public class MixinItemRenderer_EdgeDepth {
&& GLStateManager.getDepthTest().isEnabled() && GLStateManager.getDepthState().isEnabled()
&& HeldItemGlint.canResetStencil() && HeldItemGlint.eligible() && HeldItemGlint.needsImmediateBase(stack, pass);
if (angelica$preparingStencil) {
GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilMask(1);
GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 0, 1);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
BatchStateGuard.suspend();
try {
GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilMask(1);
GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 0, 1);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
} finally {
BatchStateGuard.resume();
}
}
}
@@ -91,7 +97,14 @@ public class MixinItemRenderer_EdgeDepth {
remap = false
)
private void angelica$finishGlintStencil(CallbackInfo ci) {
if (angelica$preparingStencil) GLStateManager.glPopAttrib();
if (angelica$preparingStencil) {
BatchStateGuard.suspend();
try {
GLStateManager.glPopAttrib();
} finally {
BatchStateGuard.resume();
}
}
angelica$stencilPrepared = angelica$preparingStencil;
angelica$preparingStencil = false;
}
@@ -119,33 +132,38 @@ public class MixinItemRenderer_EdgeDepth {
&& !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(tess)) {
if (angelica$combinedGlint) return;
final boolean secondLayer = angelica$firstLayerMarked;
final boolean combined = !secondLayer && HeldItemGlint.begin();
// Depth selects the surface, stencil limits it to one blend.
// The base draw's stencil reset or the first layer's marks avoid a masked full-screen clear.
GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
BatchStateGuard.suspend();
try {
GLStateManager.glDepthMask(false);
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilMask(1);
if (secondLayer) {
// Same pixels as the first layer, so removing its marks leaves the stencil reset.
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 1);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_DECR);
} else {
if (!angelica$stencilPrepared) {
GLStateManager.glClearStencil(0);
GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT);
final boolean combined = !secondLayer && HeldItemGlint.begin();
// Depth selects the surface, stencil limits it to one blend.
// The base draw's stencil reset or the first layer's marks avoid a masked full-screen clear.
GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
try {
GLStateManager.glDepthMask(false);
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilMask(1);
if (secondLayer) {
// Same pixels as the first layer, so removing its marks leaves the stencil reset.
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 1);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_DECR);
} else {
if (!angelica$stencilPrepared) {
GLStateManager.glClearStencil(0);
GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT);
}
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 0, 1);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INCR);
}
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 0, 1);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INCR);
original.call(tess, minU, minV, maxU, maxV, width, height, thickness);
} finally {
GLStateManager.glPopAttrib();
if (combined) CombinedGlint.end();
}
original.call(tess, minU, minV, maxU, maxV, width, height, thickness);
angelica$combinedGlint = combined;
angelica$firstLayerMarked = !combined && !secondLayer && HeldItemGlint.layersCoverSamePixels();
} finally {
GLStateManager.glPopAttrib();
if (combined) CombinedGlint.end();
BatchStateGuard.resume();
}
angelica$combinedGlint = combined;
angelica$firstLayerMarked = !combined && !secondLayer && HeldItemGlint.layersCoverSamePixels();
return;
}
}
@@ -31,6 +31,8 @@ public abstract class MixinItemRenderer_Instanced {
@Unique private static int angelica$pass;
@Unique private static ItemRenderType angelica$type;
@Unique private static int angelica$iconWidth, angelica$iconHeight;
@Unique private static ItemStack angelica$deferredGlintStack;
@Unique private static int angelica$deferredGlintPass;
@Inject(
method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
@@ -41,6 +43,7 @@ public abstract class MixinItemRenderer_Instanced {
angelica$stack = stack;
angelica$pass = pass;
angelica$type = type;
if (stack != angelica$deferredGlintStack || pass <= angelica$deferredGlintPass) angelica$deferredGlintStack = null;
}
@WrapOperation(
@@ -57,7 +60,9 @@ public abstract class MixinItemRenderer_Instanced {
final ItemStack stack = angelica$stack;
if (Tracy.FINE_ZONES) Tracy.beginZone(angelica$Z_HELD_ICON);
try {
DroppedItemInstancer.icon(angelica$batchable(angelica$type) && !HeldItemGlint.needsImmediateBase(stack, angelica$pass) ? stack : null, tessellator, maxU, minV, minU, maxV, width, height, thickness, original);
// A later pass without glint covers the deferred glint of an earlier one, as vanilla draws it after that glint.
final boolean afterGlint = angelica$deferredGlintStack != null && stack == angelica$deferredGlintStack && !stack.hasEffect(angelica$pass);
DroppedItemInstancer.icon(angelica$batchable(angelica$type) && !HeldItemGlint.needsImmediateBase(stack, angelica$pass) ? stack : null, afterGlint, tessellator, maxU, minV, minU, maxV, width, height, thickness, original);
} finally {
if (Tracy.FINE_ZONES) Tracy.endZone();
}
@@ -70,6 +75,8 @@ public abstract class MixinItemRenderer_Instanced {
)
private boolean angelica$queueHeldGlintBlock(boolean hasEffect) {
if (!hasEffect || !HeldItemGlint.eligible() || !DroppedItemInstancer.queueSkippedGlint(angelica$iconWidth, angelica$iconHeight)) return hasEffect;
angelica$deferredGlintStack = angelica$stack;
angelica$deferredGlintPass = angelica$pass;
SkippedGlintBlock.applyHeldItemExitState();
return false;
}
@@ -31,7 +31,7 @@ public abstract class MixinRenderItem_Instanced {
)
)
private void angelica$instanceDroppedIcon(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation<Void> original, @Local ItemStack stack) {
DroppedItemInstancer.icon(stack, tessellator, maxU, minV, minU, maxV, width, height, thickness, original);
DroppedItemInstancer.icon(stack, false, tessellator, maxU, minV, minU, maxV, width, height, thickness, original);
}
@WrapOperation(
@@ -7,8 +7,8 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.rendering.tesr.GlintCapture;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.model.ModelBase;
import net.minecraft.entity.Entity;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
@@ -37,6 +37,7 @@ public abstract class MixinRendererLivingEntity_GlintClock {
@Unique private ModelBase angelica$baseModel;
@Unique private boolean angelica$capturingBase;
@Unique private int angelica$armorPassFlags;
@Unique private int angelica$nextGlintLayer;
@Unique private int angelica$glintLayer;
@Unique private boolean angelica$secondArmorQueued;
@Unique private boolean angelica$zoneOpen;
@@ -55,25 +56,33 @@ public abstract class MixinRendererLivingEntity_GlintClock {
constant = @Constant(intValue = 15, ordinal = 0))
private int angelica$queueArmorGlintBlock(int mask) {
final ModelBase model = ((RendererLivingEntity) (Object) this).renderPassModel;
if ((angelica$armorPassFlags & 15) != 15 || model != angelica$baseModel || GLStateManager.getOverlayA() != 0.0F || RendererLivingEntityHelper.hasEyePass(this)
if ((angelica$armorPassFlags & 15) != 15) return mask;
angelica$nextGlintLayer = 0;
if (model != angelica$baseModel || GLStateManager.getOverlayA() != 0.0F || RendererLivingEntityHelper.hasEyePass(this)
|| !ModelPartBatcher.INSTANCE.queueSkippedArmorGlint(angelica$armorBase, RES_ITEM_GLINT)) return mask;
SkippedGlintBlock.applyArmorExitState();
return 0;
}
@ModifyExpressionValue(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;shouldRenderPass(Lnet/minecraft/entity/EntityLivingBase;IF)I"))
private int angelica$rememberPassFlags(int passFlags) {
angelica$armorPassFlags = passFlags;
return passFlags;
}
@Inject(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = {
@At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 0),
@At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 1)
})
private void angelica$beginArmorBase(CallbackInfo ci, @Local(ordinal = 0) int passFlags) {
private void angelica$beginArmorBase(CallbackInfo ci) {
if (Tracy.FINE_ZONES) {
Tracy.beginZone(angelica$Z_ARMOR_BASE);
angelica$zoneOpen = true;
}
angelica$armorPassFlags = passFlags;
angelica$baseModel = null;
angelica$capturingBase = (passFlags & 15) == 15 && ModelPartBatcher.INSTANCE.beginArmorCapture(angelica$armorBase);
angelica$capturingBase = (angelica$armorPassFlags & 15) == 15 && ModelPartBatcher.INSTANCE.beginArmorCapture(angelica$armorBase);
if (angelica$capturingBase) GLStateManager.glPushMatrix();
}
@@ -95,11 +104,12 @@ public abstract class MixinRendererLivingEntity_GlintClock {
@WrapWithCondition(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;RES_ITEM_GLINT:Lnet/minecraft/util/ResourceLocation;")),
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 0))
private boolean angelica$renderArmorGlintLayer(ModelBase model, Entity entity, float swing, float amount, float age, float yaw, float pitch, float scale, @Local(ordinal = 2) int layer) {
private boolean angelica$renderArmorGlintLayer(ModelBase model, Entity entity, float swing, float amount, float age, float yaw, float pitch, float scale) {
if (Tracy.FINE_ZONES) {
Tracy.beginZone(angelica$Z_ARMOR_GLINT);
angelica$zoneOpen = true;
}
final int layer = angelica$nextGlintLayer++;
final ModelPartBatcher batcher = ModelPartBatcher.INSTANCE;
if (layer == 0) angelica$secondArmorQueued = false;
angelica$glintLayer = GLINT_LAYER_QUEUED;
@@ -22,7 +22,7 @@ public class MixinRenderLiquidCell {
@Inject(at = @At(ordinal = 0, shift = Shift.AFTER, target = "Lorg/lwjgl/opengl/GL11;glColor3ub(BBB)V", value = "INVOKE"), locals = LocalCapture.CAPTURE_FAILSOFT, method = "renderItem", remap = false)
private void angelica$markNeedsAnimationUpdate(ItemRenderType type, ItemStack item, Object[] data, CallbackInfo ci, ItemFluidCell cell, IIcon icon, FluidStack fs, IIcon windowIcon, IIcon fluidicon) {
if (fluidicon instanceof TextureAtlasSprite) {
((IPatchedTextureAtlasSprite) fluidicon).markNeedsAnimationUpdate();
((IPatchedTextureAtlasSprite) fluidicon).angelica$markNeedsAnimationUpdate();
}
}
@@ -14,14 +14,14 @@ public abstract class MixinTextureAtlasSprite implements SpriteExtension {
@Override
public void celeritas$markActive() {
if (this instanceof IPatchedTextureAtlasSprite patched) {
patched.markNeedsAnimationUpdate();
patched.angelica$markNeedsAnimationUpdate();
}
}
@Override
public boolean celeritas$shouldUpdate() {
if (this instanceof IPatchedTextureAtlasSprite patched) {
return patched.needsAnimationUpdate();
return patched.angelica$needsAnimationUpdate();
}
return false;
}
@@ -29,7 +29,7 @@ public abstract class MixinTextureAtlasSprite implements SpriteExtension {
@ModifyReturnValue(method = {"getMinU", "getInterpolatedU"}, at = @At("RETURN"))
private float celeritas$markActiveOnUVAccess(float original) {
if (this instanceof IPatchedTextureAtlasSprite patched) {
patched.markNeedsAnimationUpdate();
patched.angelica$markNeedsAnimationUpdate();
}
return original;
}
@@ -12,6 +12,7 @@ import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.celeritas.BlockRenderLayer;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasSetup;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
import net.coderbot.iris.Iris;
import net.coderbot.iris.layer.GbufferPrograms;
@@ -328,6 +329,7 @@ public class MixinRenderGlobal implements IRenderGlobalExt {
HandRenderer.INSTANCE.renderSolid(camera.getPartialTicks(), camera, mc.renderGlobal, pipeline);
mc.mcProfiler.endStartSection("iris_pre_translucent");
pipeline.beginTranslucents();
ModelPartBatcher.INSTANCE.flushEntitiesAfterDeferred();
TesrBatchRenderer.INSTANCE.flushAfterDeferred();
}
@@ -15,6 +15,7 @@ import net.coderbot.iris.compat.dh.DHCompat;
import net.coderbot.iris.gl.program.Program;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.HandRenderer;
import com.gtnewhorizons.angelica.compat.thaumcraft.ThaumometerScreen;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.uniforms.CapturedRenderingState;
@@ -53,6 +54,7 @@ public abstract class MixinEntityRenderer implements IResourceManagerReloadListe
Iris.tryLoadShaderpackWhenPossible();
CapturedRenderingState.INSTANCE.setTickDelta(partialTicks);
ThaumometerScreen.discard();
SystemTimeUniforms.COUNTER.beginFrame();
SystemTimeUniforms.TIMER.beginFrame(System.nanoTime());
@@ -71,6 +73,7 @@ public abstract class MixinEntityRenderer implements IResourceManagerReloadListe
private void iris$endLevelRender(float partialTicks, long limitTime, CallbackInfo callback, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
// TODO: Iris
HandRenderer.INSTANCE.renderTranslucent(partialTicks, Camera.INSTANCE, mc.renderGlobal, pipeline.get());
ThaumometerScreen.render(pipeline.get());
Minecraft.getMinecraft().mcProfiler.endStartSection("iris_final");
pipeline.get().finalizeLevelRendering();
pipeline.set(null);
@@ -38,7 +38,7 @@ public class MixinItemRenderer_ItemId implements ItemRendererAccessor {
remap = false
)
private void iris$entityItemId(EntityLivingBase entity, ItemStack itemStack, int renderPass, IItemRenderer.ItemRenderType type, Operation<Void> original) {
final int prevItemId = ItemIdManager.getItemId();
ItemIdManager.pushItemId();
final int stateDepth = GLStateManager.pushState(StateSet.CUTOUT);
Boolean prevTranslucency = null;
boolean translucencyDeclared = false;
@@ -53,7 +53,7 @@ public class MixinItemRenderer_ItemId implements ItemRendererAccessor {
original.call(entity, itemStack, renderPass, type);
} finally {
if (translucencyDeclared) GbufferPrograms.endTranslucencyDeclaration(prevTranslucency);
ItemIdManager.setItemIdRaw(prevItemId);
ItemIdManager.popItemId();
GLStateManager.popStateTo(stateDepth);
}
}
@@ -64,6 +64,7 @@ public class MixinItemRenderer_ItemId implements ItemRendererAccessor {
remap = false
)
private void iris$glintStart(CallbackInfo ci) {
ItemIdManager.pushItemId();
ItemIdManager.resetItemId();
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT);
ShaderGlint.beginGlint();
@@ -77,5 +78,6 @@ public class MixinItemRenderer_ItemId implements ItemRendererAccessor {
private void iris$glintEnd(CallbackInfo ci) {
GbufferPrograms.teardownSpecialRenderCondition();
ShaderGlint.endGlint();
ItemIdManager.popItemId();
}
}
@@ -24,7 +24,8 @@ public class MixinRenderBlocks_ItemId {
}
final int prevItemId = ItemIdManager.getItemId();
final int prevBlockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
ItemIdManager.pushItemId();
CapturedRenderingState.INSTANCE.pushCurrentBlockEntity();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(1);
@@ -35,8 +36,8 @@ public class MixinRenderBlocks_ItemId {
try {
original.call(block, metadata, brightness);
} finally {
ItemIdManager.setItemIdRaw(prevItemId);
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
ItemIdManager.popItemId();
CapturedRenderingState.INSTANCE.popCurrentBlockEntity();
}
}
}
@@ -3,8 +3,6 @@ package com.gtnewhorizons.angelica.mixins.early.shaders;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
@@ -32,19 +30,7 @@ public abstract class MixinRenderDragon {
private static final NamespacedId DRAGON_DEATH_RAY = new NamespacedId("minecraft", "dragon_death_rays");
@Unique
private static final int NOTHING_SAVED = Integer.MIN_VALUE;
@Unique
private int angelica$previousEntityId = NOTHING_SAVED;
@Unique
private int angelica$previousDeathRayEntityId = NOTHING_SAVED;
@Unique
private int angelica$previousItemId = 0;
@Unique
private int angelica$previousDeathRayItemId = 0;
private boolean angelica$beamScope;
@Unique
private boolean angelica$deathBeamsActive = false;
@@ -62,15 +48,9 @@ public abstract class MixinRenderDragon {
private void iris$setBeamEntityId(EntityDragon dragon, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
// Only set the ID if the dragon is being healed by a crystal
if (dragon.healingEnderCrystal != null) {
Object2IntFunction<NamespacedId> entityIdMap = BlockRenderingSettings.INSTANCE.getEntityIds();
if (entityIdMap != null) {
// Save the current entity ID
angelica$previousEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity();
angelica$previousItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
int beamId = entityIdMap.applyAsInt(END_CRYSTAL_BEAM);
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(beamId, 0);
}
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(END_CRYSTAL_BEAM);
angelica$beamScope = true;
}
}
@@ -79,10 +59,9 @@ public abstract class MixinRenderDragon {
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glPopMatrix()V", ordinal = 0, shift = At.Shift.BEFORE, remap = false)
)
private void iris$restoreEntityId(EntityDragon dragon, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
if (angelica$previousEntityId != NOTHING_SAVED) {
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(angelica$previousEntityId, angelica$previousItemId);
angelica$previousEntityId = NOTHING_SAVED;
angelica$previousItemId = 0;
if (angelica$beamScope) {
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
angelica$beamScope = false;
}
}
@@ -100,13 +79,8 @@ public abstract class MixinRenderDragon {
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING);
angelica$deathBeamsActive = true;
Object2IntFunction<NamespacedId> entityIdMap = BlockRenderingSettings.INSTANCE.getEntityIds();
if (entityIdMap != null) {
angelica$previousDeathRayEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity();
angelica$previousDeathRayItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
int deathRayId = entityIdMap.applyAsInt(DRAGON_DEATH_RAY);
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(deathRayId, 0);
}
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(DRAGON_DEATH_RAY);
angelica$depthPassReplay++;
GLStateManager.glColorMask(false, false, false, false);
@@ -143,11 +117,7 @@ public abstract class MixinRenderDragon {
private void angelica$endDeathBeamsLighting(EntityDragon dragon, float partialTicks, CallbackInfo ci) {
if (angelica$depthPassReplay > 0) return;
if (angelica$deathBeamsActive) {
if (angelica$previousDeathRayEntityId != NOTHING_SAVED) {
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(angelica$previousDeathRayEntityId, angelica$previousDeathRayItemId);
angelica$previousDeathRayEntityId = NOTHING_SAVED;
angelica$previousDeathRayItemId = 0;
}
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
GbufferPrograms.teardownSpecialRenderCondition();
angelica$deathBeamsActive = false;
}
@@ -1,7 +1,5 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.client.renderer.entity.Render;
@@ -23,15 +21,13 @@ public class MixinRenderEntityFlame {
// This runs after the initial entity has finished being rendered.
// The flame is not a real entity but from the shader's perspective, it is.
private void iris$setFlame(Entity entity, double x, double y, double z, float partialTicks, CallbackInfo ci) {
Object2IntFunction<NamespacedId> entityIdMap = BlockRenderingSettings.INSTANCE.getEntityIds();
if (entityIdMap != null) {
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(entityIdMap.applyAsInt(flameId), 0);
}
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(flameId);
}
@Inject(method = "renderEntityOnFire", at = @At("RETURN"))
private void iris$resetFlame(Entity entity, double x, double y, double z, float partialTicks, CallbackInfo ci) {
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(0, 0);
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
}
}
@@ -28,9 +28,10 @@ public class MixinRenderFallingBlock {
@Inject(method = "doRender(Lnet/minecraft/entity/item/EntityFallingBlock;DDDFF)V", at = @At("HEAD"))
private void angelica$beginFallingBlock(EntityFallingBlock entity, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
FallingBlockRendering.active = true;
CapturedRenderingState.INSTANCE.pushCurrentBlockEntity();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
GbufferPrograms.setOverridePhase(WorldRenderingPhase.TERRAIN_SOLID);
GbufferPrograms.pushOverridePhase(WorldRenderingPhase.TERRAIN_SOLID);
FallingBlockRendering.setEntityAttribute(entity.func_145805_f(), entity.field_145814_a);
}
@@ -39,8 +40,8 @@ public class MixinRenderFallingBlock {
FallingBlockRendering.active = false;
FallingBlockRendering.resetEntityAttribute();
GbufferPrograms.setOverridePhase(null);
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
GbufferPrograms.popOverridePhase();
CapturedRenderingState.INSTANCE.popCurrentBlockEntity();
}
@WrapOperation(
@@ -5,7 +5,6 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.ItemIdManager;
import net.coderbot.iris.uniforms.ItemMaterialHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.model.ModelRenderer;
@@ -80,9 +79,9 @@ public abstract class MixinRenderHorse {
}
Item armorItem = angelica$getArmorItem(horse, armorIndex);
ItemIdManager.pushItemId();
if (armorItem != null) {
int id = ItemMaterialHelper.getMaterialId(armorItem, 0);
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(id);
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(armorItem, 0);
}
// Copy animation state from the base model to the armor model
@@ -91,9 +90,11 @@ public abstract class MixinRenderHorse {
ResourceLocation armorTexture = angelica$armorTextureCache.computeIfAbsent(armorTexturePath, ResourceLocation::new);
Minecraft.getMinecraft().getTextureManager().bindTexture(armorTexture);
angelica$armorModel.render(horse, p1, p2, p3, p4, p5, p6);
ItemIdManager.resetItemId();
try {
angelica$armorModel.render(horse, p1, p2, p3, p4, p5, p6);
} finally {
ItemIdManager.popItemId();
}
}
// Copy rotation angles and positions from one model's renderers to another's
@@ -26,7 +26,7 @@ public class MixinRenderItem {
*/
@WrapMethod(method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V")
private void iris$droppedItemRender(EntityItem entity, double x, double y, double z, float entityYaw, float partialTicks, Operation<Void> original) {
final int prevItemId = ItemIdManager.getItemId();
ItemIdManager.pushItemId();
final int stateDepth = GLStateManager.pushState(StateSet.CUTOUT);
try {
GbufferPrograms.setCutoutDefaults();
@@ -34,7 +34,7 @@ public class MixinRenderItem {
ItemIdManager.setItemId(entity.getEntityItem());
original.call(entity, x, y, z, entityYaw, partialTicks);
} finally {
ItemIdManager.setItemIdRaw(prevItemId);
ItemIdManager.popItemId();
GLStateManager.popStateTo(stateDepth);
}
}
@@ -48,6 +48,7 @@ public class MixinRenderItem {
remap = false
)
private void iris$glintStart(CallbackInfo ci) {
ItemIdManager.pushItemId();
ItemIdManager.resetItemId();
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT);
ShaderGlint.beginGlint();
@@ -64,5 +65,6 @@ public class MixinRenderItem {
private void iris$glintEnd(CallbackInfo ci) {
GbufferPrograms.teardownSpecialRenderCondition();
ShaderGlint.endGlint();
ItemIdManager.popItemId();
}
}
@@ -41,11 +41,11 @@ public class MixinRenderManager {
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/Render;doRender(Lnet/minecraft/entity/Entity;DDDFF)V")
)
private void iris$wrapDoRender(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks, Operation<Void> original) {
final int prevEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity();
final int prevItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
CapturedRenderingState.INSTANCE.pushCurrentEntityColor();
final Class<?> prevRenderable = TesrAttribution.currentRenderable;
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(EntityIdHelper.getEntityId(entity), 0);
CapturedRenderingState.INSTANCE.setCurrentRenderedEntity(entity);
TesrAttribution.currentRenderable = entity != null ? entity.getClass() : null;
final boolean lightning = EntityIdHelper.isLightningBolt(entity);
if (lightning) {
@@ -60,7 +60,8 @@ public class MixinRenderManager {
if (lightning) {
GbufferPrograms.teardownSpecialRenderCondition();
}
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(prevEntityId, prevItemId);
CapturedRenderingState.INSTANCE.popCurrentEntityColor();
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
TesrAttribution.currentRenderable = prevRenderable;
}
}
@@ -24,11 +24,11 @@ public class MixinRenderManagerDAPI {
require = 1 // Require this if DAPI is present, which should be the case when this mixin is applied.
)
private void iris$wrapDoRenderDragonAPI(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks, Operation<Void> original) {
final int prevEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity();
final int prevItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
CapturedRenderingState.INSTANCE.pushCurrentEntityColor();
final Class<?> prevRenderable = TesrAttribution.currentRenderable;
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(EntityIdHelper.getEntityId(entity), 0);
CapturedRenderingState.INSTANCE.setCurrentRenderedEntity(entity);
TesrAttribution.currentRenderable = entity != null ? entity.getClass() : null;
final boolean lightning = EntityIdHelper.isLightningBolt(entity);
if (lightning) {
@@ -43,7 +43,8 @@ public class MixinRenderManagerDAPI {
if (lightning) {
GbufferPrograms.teardownSpecialRenderCondition();
}
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(prevEntityId, prevItemId);
CapturedRenderingState.INSTANCE.popCurrentEntityColor();
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
TesrAttribution.currentRenderable = prevRenderable;
}
}
@@ -1,7 +1,5 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.client.renderer.entity.Render;
@@ -22,15 +20,6 @@ public class MixinRenderNameTag {
@Unique
private static final NamespacedId NAME_TAG_ID = new NamespacedId("minecraft", "name_tag");
@Unique
private static final int NOTHING_SAVED = Integer.MIN_VALUE;
@Unique
private int angelica$previousEntityId = NOTHING_SAVED;
@Unique
private int angelica$previousItemId = 0;
/**
* Inject at the HEAD of func_147906_a to set the special name_tag entity ID before rendering.
* func_147906_a is the method that renders entity name tags.
@@ -40,15 +29,8 @@ public class MixinRenderNameTag {
at = @At("HEAD")
)
private void iris$setNameTagEntityId(Entity entity, String name, double x, double y, double z, int maxDistance, CallbackInfo ci) {
Object2IntFunction<NamespacedId> entityIdMap = BlockRenderingSettings.INSTANCE.getEntityIds();
if (entityIdMap != null) {
angelica$previousEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity();
angelica$previousItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
// Set the special name_tag entity ID
int nameTagId = entityIdMap.applyAsInt(NAME_TAG_ID);
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(nameTagId, 0);
}
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(NAME_TAG_ID);
}
/**
@@ -59,10 +41,6 @@ public class MixinRenderNameTag {
at = @At("RETURN")
)
private void iris$restoreEntityId(Entity entity, String name, double x, double y, double z, int maxDistance, CallbackInfo ci) {
if (angelica$previousEntityId != NOTHING_SAVED) {
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(angelica$previousEntityId, angelica$previousItemId);
angelica$previousEntityId = NOTHING_SAVED;
angelica$previousItemId = 0;
}
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
}
}
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.mixins.early.shaders;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import net.coderbot.iris.uniforms.CapturedRenderingState;
@@ -16,8 +15,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Objects;
/**
* Mixin to set currentRenderedItem ID when rendering armor and equipment on players.
* RENDER ORDER (same as parent RendererLivingEntity):
@@ -51,18 +48,15 @@ public class MixinRenderPlayerArmor {
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBiped;renderCloak(F)V")
)
private void iris$setCapeItemId(ModelBiped modelBiped, float scale, Operation<Void> original) {
if (BlockRenderingSettings.INSTANCE.getItemIds() != null) {
assert BlockRenderingSettings.INSTANCE.getItemIds() != null;
int capeId = Objects.requireNonNull(BlockRenderingSettings.INSTANCE.getItemIds()).applyAsInt(PLAYER_CAPE);
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(capeId);
}
ItemIdManager.pushItemId();
CapturedRenderingState.INSTANCE.setCurrentNamedItem(PLAYER_CAPE);
final Boolean previous = GbufferPrograms.beginTranslucencyDeclaration(Boolean.FALSE);
try {
original.call(modelBiped, scale);
} finally {
GbufferPrograms.endTranslucencyDeclaration(previous);
ItemIdManager.popItemId();
}
ItemIdManager.resetItemId();
}
}
@@ -4,13 +4,11 @@ import com.gtnewhorizons.angelica.rendering.tesr.PassRebindGate;
import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution;
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBlendScope;
import com.gtnewhorizons.angelica.rendering.tesr.TesrProviderDispatch;
import com.prupe.mcpatcher.ctm.CTMUtils;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.tileentity.TileEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -23,7 +21,8 @@ public class MixinTileEntityRendererDispatcher {
RenderRecovery.throwIfCrashTestArmed();
TesrBlendScope.enter();
CTMUtils.clearCurrentCompact();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(iris$resolveBlockEntityId(te));
CapturedRenderingState.INSTANCE.pushCurrentBlockEntity();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(te == null ? null : te.getBlockType(), te == null ? 0 : te.getBlockMetadata());
TesrAttribution.currentRenderable = te != null ? te.getClass() : null;
PassRebindGate.rebindIfDirty();
}
@@ -31,13 +30,8 @@ public class MixinTileEntityRendererDispatcher {
@Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("RETURN"))
private void iris$endBlockEntity(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
CTMUtils.clearCurrentCompact();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
CapturedRenderingState.INSTANCE.popCurrentBlockEntity();
TesrAttribution.currentRenderable = null;
TesrBlendScope.exit();
}
@Unique
private static int iris$resolveBlockEntityId(TileEntity te) {
return TesrProviderDispatch.resolveBlockEntityId(te);
}
}
@@ -0,0 +1,38 @@
package com.gtnewhorizons.angelica.mixins.late.client.draconicevolution;
import com.brandon3055.draconicevolution.client.render.tile.RenderTilePlacedItem;
import com.brandon3055.draconicevolution.common.tileentities.TilePlacedItem;
import com.gtnewhorizons.angelica.compat.draconicevolution.PlacedItemRenderCompat;
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = RenderTilePlacedItem.class, remap = false)
public abstract class MixinRenderTilePlacedItem {
@Unique
private byte angelica$ordinaryItemBatchState = BatchEligibility.UNKNOWN;
@Shadow(remap = false)
public abstract void renderItem(TilePlacedItem tile);
@Inject(method = "renderTileEntityAt", at = @At("HEAD"), cancellable = true, remap = true)
private void angelica$renderPlacedItem(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
if (!(te instanceof TilePlacedItem tile) || tile.getWorldObj() == null) return;
final ItemStack stack = tile.getStack();
if (stack == null) return;
final boolean custom = PlacedItemRenderCompat.requiresLiveRender(stack);
if (!custom && !ModelPartBatcher.INSTANCE.isActive()) return;
final byte next = PlacedItemRenderCompat.renderWithBatchState(x, y, z,
custom ? BatchEligibility.DENIED : angelica$ordinaryItemBatchState, () -> renderItem(tile));
if (!custom) angelica$ordinaryItemBatchState = next;
ci.cancel();
}
}
@@ -4,7 +4,6 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.EntityIdHelper;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.entity.Entity;
import openblocks.client.renderer.tileentity.TileEntityTrophyRenderer;
@@ -21,10 +20,9 @@ public class MixinTileEntityTrophyRenderer {
method = "renderTrophy",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/Render;doRender(Lnet/minecraft/entity/Entity;DDDFF)V", remap = true))
private static void angelica$renderTrophyAsEntity(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks, Operation<Void> original) {
final int prevEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity();
final int prevItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(EntityIdHelper.getEntityId(entity), 0);
CapturedRenderingState.INSTANCE.setCurrentRenderedEntity(entity);
final boolean nestedInBlockEntity = GbufferPrograms.beginNestedEntityPhase();
@@ -32,7 +30,7 @@ public class MixinTileEntityTrophyRenderer {
original.call(render, entity, x, y, z, entityYaw, partialTicks);
} finally {
GbufferPrograms.endNestedEntityPhase(nestedInBlockEntity);
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(prevEntityId, prevItemId);
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
}
}
}
@@ -0,0 +1,47 @@
package com.gtnewhorizons.angelica.mixins.late.client.thaumcraft;
import com.gtnewhorizons.angelica.compat.thaumcraft.ThaumometerScreen;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.coderbot.iris.pipeline.HandRenderer;
import net.minecraft.item.ItemStack;
import net.minecraftforge.client.IItemRenderer.ItemRenderType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import thaumcraft.client.renderers.item.ItemThaumometerRenderer;
@Mixin(value = ItemThaumometerRenderer.class, remap = false)
public abstract class MixinItemThaumometerRenderer {
@Unique private ThaumometerScreen.Capture angelica$screen;
@Inject(method = "renderItem", at = @At(value = "INVOKE",
target = "Lnet/minecraftforge/client/model/IModelCustom;renderAll()V", shift = At.Shift.AFTER))
private void angelica$captureScreen(ItemRenderType type, ItemStack item, Object[] data, CallbackInfo ci) {
if (type == ItemRenderType.EQUIPPED_FIRST_PERSON && HandRenderer.INSTANCE.isActive()) {
angelica$screen = ThaumometerScreen.begin();
}
}
@Inject(method = "renderItem", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glPopMatrix()V", ordinal = 6))
private void angelica$finishScreen(ItemRenderType type, ItemStack item, Object[] data, CallbackInfo ci) {
if (angelica$screen != null) {
ThaumometerScreen.finish(angelica$screen);
angelica$screen = null;
}
}
@WrapMethod(method = "renderItem")
private void angelica$cleanupCapture(ItemRenderType type, ItemStack item, Object[] data, Operation<Void> original) {
try {
original.call(type, item, data);
} finally {
if (angelica$screen != null) {
ThaumometerScreen.abort(angelica$screen);
angelica$screen = null;
}
}
}
}
@@ -0,0 +1,87 @@
package com.gtnewhorizons.angelica.compat.draconicevolution;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
import net.minecraft.client.renderer.entity.RenderItem;
import org.joml.Matrix4f;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.*;
@GLCoreTest
class PlacedItemRenderCompatTest {
@Test
void customAnimationRunsEveryFrameAndRestoresEligibilityAfterFailure() {
final Matrix4f original = new Matrix4f(GLStateManager.getModelViewMatrix());
final int[] frames = { 0 };
BatchEligibility.begin(BatchEligibility.SAFE, GLStateManager.drawCalls);
try {
BatchEligibility.onPartQueued();
for (int frame = 0; frame < 3; frame++) {
assertEquals(BatchEligibility.DENIED, PlacedItemRenderCompat.renderWithBatchState(1, 2, 3,
BatchEligibility.DENIED, () -> {
assertFalse(BatchEligibility.batchingAllowed());
GLStateManager.glRotatef(frames[0]++ * 30, 0, 1, 0);
final long before = GLStateManager.drawCalls;
GLStateManager.drawCalls += 2;
BatchEligibility.onPartFallback(before, GLStateManager.drawCalls);
GLStateManager.drawCalls++;
}));
assertTrue(BatchEligibility.batchingAllowed());
assertTrue(original.equals(GLStateManager.getModelViewMatrix(), 1e-5f));
}
assertEquals(3, frames[0]);
assertThrows(IllegalStateException.class, () -> PlacedItemRenderCompat.renderWithBatchState(1, 2, 3,
BatchEligibility.DENIED, () -> {
GLStateManager.drawCalls++;
throw new IllegalStateException("renderer failed");
}));
assertTrue(BatchEligibility.batchingAllowed());
assertTrue(original.equals(GLStateManager.getModelViewMatrix(), 1e-5f));
} finally {
assertEquals(BatchEligibility.SAFE, BatchEligibility.end(BatchEligibility.SAFE, GLStateManager.drawCalls));
}
assertFalse(BatchEligibility.batchingAllowed());
}
@Test
void eachFrameRunsTheAnimationAndRestoresPlacementStateEvenOnFailure() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
final Matrix4f original = new Matrix4f(GLStateManager.getModelViewMatrix());
final boolean originalFrame = RenderItem.renderInFrame;
final boolean originalDepth = GLStateManager.getDepthTest().isEnabled();
final int[] calls = { 0 };
try {
GLStateManager.glLoadIdentity();
GLStateManager.enableDepthTest();
RenderItem.renderInFrame = true;
for (int frame = 0; frame < 3; frame++) {
final int tick = frame;
PlacedItemRenderCompat.render(1, 2, 3, () -> {
calls[0]++;
GLStateManager.glRotatef(tick * 30, 0, 1, 0);
assertTrue(new Matrix4f().translation(1, 2, 3).rotateY((float) Math.toRadians(tick * 30))
.equals(GLStateManager.getModelViewMatrix(), 1e-5f));
GLStateManager.disableDepthTest();
RenderItem.renderInFrame = false;
});
assertEquals(frame + 1, calls[0]);
assertTrue(new Matrix4f().equals(GLStateManager.getModelViewMatrix(), 1e-5f));
assertTrue(GL11.glIsEnabled(GL11.GL_DEPTH_TEST));
assertTrue(RenderItem.renderInFrame);
}
assertThrows(IllegalStateException.class, () -> PlacedItemRenderCompat.render(1, 2, 3, () -> {
RenderItem.renderInFrame = false;
throw new IllegalStateException("renderer failed");
}));
assertTrue(new Matrix4f().equals(GLStateManager.getModelViewMatrix(), 1e-5f));
assertTrue(RenderItem.renderInFrame);
} finally {
GLStateManager.setModelViewMatrix(original);
GLStateManager.getDepthTest().setEnabled(originalDepth);
RenderItem.renderInFrame = originalFrame;
}
}
}
@@ -0,0 +1,750 @@
package com.gtnewhorizons.angelica.iris;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import net.coderbot.iris.Iris;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
import net.coderbot.iris.gl.program.ProgramUniforms;
import net.coderbot.iris.gl.uniform.Uniform;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
import net.coderbot.iris.pipeline.FixedFunctionWorldRenderingPipeline;
import net.coderbot.iris.pipeline.PipelineManager;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.ItemIdManager;
import net.coderbot.iris.uniforms.ItemMaterialHelper;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL13;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
@GLCoreTest
class IrisDisplayListStateTest {
private final List<Integer> lists = new ArrayList<>();
private final List<Integer> programs = new ArrayList<>();
private int selectedProgram;
@AfterEach
void cleanup() {
ProgramUniforms.clearActiveUniforms();
if (DisplayListManager.isRecording()) GLStateManager.glEndList();
GLStateManager.glUseProgram(0);
for (int list : lists) GLStateManager.glDeleteLists(list, 1);
for (int program : programs) GLStateManager.glDeleteProgram(program);
}
private int newList() {
int list = GLStateManager.glGenLists(1);
lists.add(list);
return list;
}
private int program() {
int vertex = shader(GL20.GL_VERTEX_SHADER,
"#version 330 core\nvoid main() { gl_Position = vec4(0, 0, 0, 1); }");
int fragment = shader(GL20.GL_FRAGMENT_SHADER,
"#version 330 core\nuniform int itemId; out vec4 color; void main() { color = vec4(float(itemId)); }");
int program = GLStateManager.glCreateProgram();
programs.add(program);
GLStateManager.glAttachShader(program, vertex);
GLStateManager.glAttachShader(program, fragment);
GLStateManager.glLinkProgram(program);
GLStateManager.glDeleteShader(vertex);
GLStateManager.glDeleteShader(fragment);
assertEquals(GL11.GL_TRUE, GLStateManager.glGetProgrami(program, GL20.GL_LINK_STATUS));
return program;
}
private static int shader(int type, String source) {
int shader = GLStateManager.glCreateShader(type);
GLStateManager.glShaderSource(shader, source);
GLStateManager.glCompileShader(shader);
assertEquals(GL11.GL_TRUE, GLStateManager.glGetShaderi(shader, GL20.GL_COMPILE_STATUS));
return shader;
}
private void selectProgram() {
IrisDisplayListState.runProgramTransition(() -> GLStateManager.glUseProgram(selectedProgram));
}
@Test
void cutoutScopesRestorePlaybackAlphaAndLightmapWithoutChangingItsActiveUnit() {
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
try {
for (int mode : new int[] {GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE}) {
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.2f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE3);
final int list = newList();
GLStateManager.glNewList(list, mode);
final int outer = GLStateManager.pushState(StateSet.CUTOUT);
GbufferPrograms.setCutoutDefaults();
final int inner = GLStateManager.pushState(StateSet.CUTOUT);
GbufferPrograms.setCutoutDefaults();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.popStateTo(inner);
DisplayListManager.recordStateCommand(() -> assertTrue(GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()));
GLStateManager.popStateTo(outer);
GLStateManager.glEndList();
for (boolean lightmap : new boolean[] {true, false}) {
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GEQUAL, 0.7f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
if (lightmap) GLStateManager.enableTexture(); else GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glCallList(list);
assertTrue(GLStateManager.isEffectiveAlphaTestEnabled());
assertEquals(GL11.GL_GEQUAL, GLStateManager.getAlphaState().getFunction());
assertEquals(0.7f, GLStateManager.getAlphaState().getReference());
assertEquals(lightmap, GLStateManager.getTextures().getTextureUnitStates(1).isEnabled());
assertEquals(2, GLStateManager.getActiveTextureUnit(), "scope restores must preserve the list's explicit unit selection");
}
}
} finally {
GLStateManager.glPopAttrib();
}
}
@Test
void nestedBlendScopesRestorePlaybackValuesEvenIfCompileTimePopWasANoop() {
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
try {
for (int mode : new int[] {GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE}) {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
final int child = newList();
GLStateManager.glNewList(child, mode);
final int scope = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.popStateTo(scope);
GLStateManager.glEndList();
final int parent = newList();
GLStateManager.glNewList(parent, mode);
final int parentScope = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.glCallList(child);
GLStateManager.popStateTo(parentScope);
GLStateManager.glEndList();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ZERO, GL11.GL_ONE);
final int caller = GLStateManager.pushState(StateSet.BLEND);
try {
GLStateManager.glCallList(parent);
assertTrue(GLStateManager.isEffectiveBlendEnabled());
final var blend = GLStateManager.getEffectiveBlendState(new com.gtnewhorizons.angelica.glsm.states.BlendState());
assertEquals(GL11.GL_SRC_ALPHA, blend.getSrcRgb());
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, blend.getDstRgb());
assertEquals(GL11.GL_ZERO, blend.getSrcAlpha());
assertEquals(GL11.GL_ONE, blend.getDstAlpha());
assertEquals(caller + 1, GLStateManager.getAttribDepth());
} finally {
GLStateManager.popStateTo(caller);
}
}
} finally {
GLStateManager.glPopAttrib();
}
}
@Test
void idsAndColorReplayInsideNestedScopesAndRestoreThePlaybackCaller() {
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
for (int mode : new int[] {GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE}) {
state.setCurrentEntityAndItem(10, 11);
state.setCurrentBlockEntity(12);
state.setCurrentEntityColor(1, 0, 0, 0.5f);
final int child = newList();
GLStateManager.glNewList(child, mode);
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
state.setCurrentEntityAndItem(45010, 45011);
state.setCurrentBlockEntity(45012);
state.setCurrentEntityColor(0, 1, 0, 0.25f);
DisplayListManager.recordStateCommand(() -> {
assertEquals(45010, state.getCurrentRenderedEntity());
assertEquals(45011, state.getCurrentRenderedItem());
assertEquals(45012, state.getCurrentRenderedBlockEntity());
assertTrue(state.getCurrentEntityColor().equals(0, 1, 0, 0.25f));
});
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
GLStateManager.glEndList();
final int parent = newList();
GLStateManager.glNewList(parent, mode);
state.pushCurrentBlockEntity();
state.setCurrentBlockEntity(99);
GLStateManager.glCallList(child);
DisplayListManager.recordStateCommand(() -> assertEquals(99, state.getCurrentRenderedBlockEntity()));
state.popCurrentBlockEntity();
GLStateManager.glEndList();
for (int caller : new int[] {33, 44}) {
state.setCurrentEntityAndItem(caller, caller + 1);
state.setCurrentBlockEntity(caller + 2);
state.setCurrentEntityColor(0, 0, 1, 0.75f);
GLStateManager.glCallList(parent);
assertEquals(caller, state.getCurrentRenderedEntity());
assertEquals(caller + 1, state.getCurrentRenderedItem());
assertEquals(caller + 2, state.getCurrentRenderedBlockEntity());
assertTrue(state.getCurrentEntityColor().equals(0, 0, 1, 0.75f));
}
}
} finally {
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@Test
void explicitStateRecordsEvenWhenUnchangedAndOnlyDeduplicatesAdjacentCommands() {
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
state.setCurrentEntityAndItem(7, 8);
state.setCurrentBlockEntity(9);
state.setCurrentEntityColor(1, 0, 0, 0.5f);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
for (int i = 0; i < 100; i++) state.setCurrentEntityAndItem(7, 8);
for (int i = 0; i < 100; i++) state.setCurrentBlockEntity(9);
for (int i = 0; i < 100; i++) state.setCurrentEntityColor(1, 0, 0, 0.5f);
GLStateManager.glEndList();
assertEquals(3, DisplayListManager.getDisplayList(list).getCommandCounts().get(GLCommand.COMPLEX_REF));
state.setCurrentEntityAndItem(70, 80);
state.setCurrentBlockEntity(90);
state.setCurrentEntityColor(0, 1, 0, 1);
GLStateManager.glCallList(list);
assertEquals(7, state.getCurrentRenderedEntity());
assertEquals(8, state.getCurrentRenderedItem());
assertEquals(9, state.getCurrentRenderedBlockEntity());
assertTrue(state.getCurrentEntityColor().equals(1, 0, 0, 0.5f));
} finally {
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@Test
void namedEffectsResolveTheCurrentPackOnReplay() {
final var settings = BlockRenderingSettings.INSTANCE;
final var originalIds = settings.getEntityIds();
final var originalItemIds = settings.getItemIds();
final var state = CapturedRenderingState.INSTANCE;
final NamespacedId name = new NamespacedId("minecraft", "name_tag");
state.pushCurrentEntityAndItem();
try {
settings.setEntityIds(null);
settings.setItemIds(null);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
state.setCurrentNamedEntity(name);
state.setCurrentNamedItem(name);
GLStateManager.glEndList();
for (int id : new int[] {45001, 45002, -1}) {
settings.setEntityIds(key -> name.equals(key) ? id : -1);
settings.setItemIds(key -> name.equals(key) ? id + 4 : 0);
GLStateManager.glCallList(list);
assertEquals(id, state.getCurrentRenderedEntity());
assertEquals(id + 4, state.getCurrentRenderedItem());
}
} finally {
settings.setEntityIds(originalIds);
settings.setItemIds(originalItemIds);
state.popCurrentEntityAndItem();
}
}
@Test
void blockIdentityResolvesAfterPackReplacementAndBlockUniformDefersDuringCompile() {
final var settings = BlockRenderingSettings.INSTANCE;
final var previousMatches = settings.getBlockMetaMatches();
final var block = mock(net.minecraft.block.Block.class);
final var state = CapturedRenderingState.INSTANCE;
state.pushCurrentBlockEntity();
final int program = program();
GLStateManager.glUseProgram(program);
state.setCurrentBlockEntity(111);
final var builder = ProgramUniforms.builder("block identity", program);
builder.uniform1i("itemId", state::getCurrentRenderedBlockEntity, state.getBlockEntityIdNotifier());
final var uniforms = builder.buildUniforms();
Reflect.setStatic(ProgramUniforms.class, "active", uniforms);
Reflect.<List<Uniform>>get(uniforms, "dynamic").get(0).update();
try {
settings.setBlockMetaMatches(null);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
state.setCurrentBlockEntity(block, 3);
uniforms.update();
assertEquals(111, itemUniform(program), "compile-only notifications must not change the live uniform");
GLStateManager.glEndList();
for (int id : new int[] {45020, 45021}) {
final var meta = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap();
meta.put(3, id);
final var matches = new it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap<net.minecraft.block.Block, it.unimi.dsi.fastutil.ints.Int2IntMap>();
matches.put(block, meta);
settings.setBlockMetaMatches(matches);
state.setCurrentBlockEntity(222);
GLStateManager.glCallList(list);
assertEquals(id, state.getCurrentRenderedBlockEntity());
assertEquals(id, itemUniform(program));
}
} finally {
ProgramUniforms.clearActiveUniforms();
settings.setBlockMetaMatches(previousMatches);
state.popCurrentBlockEntity();
}
}
@Test
void constantVertexAttributesReplayWithoutChangingLiveStateDuringCompile() {
final int index = 12;
final var values = BufferUtils.createFloatBuffer(4);
try {
for (int mode : new int[] {GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE}) {
GLStateManager.glVertexAttrib4f(index, 1, 2, 3, 4);
final int list = newList();
GLStateManager.glNewList(list, mode);
GLStateManager.glVertexAttrib2s(index, (short) -1, (short) 7);
GL20.glGetVertexAttrib(index, GL20.GL_CURRENT_VERTEX_ATTRIB, values);
assertEquals(mode == GL11.GL_COMPILE ? 1f : -1f, values.get(0));
GLStateManager.glEndList();
GLStateManager.glVertexAttrib4f(index, 9, 9, 9, 9);
GLStateManager.glCallList(list);
GL20.glGetVertexAttrib(index, GL20.GL_CURRENT_VERTEX_ATTRIB, values);
assertEquals(-1f, values.get(0));
assertEquals(7f, values.get(1));
assertEquals(0f, values.get(2));
assertEquals(1f, values.get(3));
assertEquals(1, DisplayListManager.getDisplayList(list).getCommandCounts().get(GLCommand.VERTEX_ATTRIB));
}
} finally {
GLStateManager.glVertexAttrib4f(index, 0, 0, 0, 1);
}
}
@Test
void passBindsUsePlaybackSelectionInBothCompileModes() {
int caller = program();
int compileProgram = program();
int playbackProgram = program();
for (int mode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
GLStateManager.glUseProgram(caller);
selectedProgram = compileProgram;
int list = newList();
GLStateManager.glNewList(list, mode);
DisplayListManager.recordStateCommand(this::selectProgram);
selectProgram();
assertEquals(mode == GL11.GL_COMPILE ? caller : compileProgram, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM));
GLStateManager.glEndList();
assertEquals(0, DisplayListManager.getDisplayList(list).getCommandCounts().get(GLCommand.USE_PROGRAM));
selectedProgram = playbackProgram;
GLStateManager.glCallList(list);
assertEquals(playbackProgram, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM));
assertEquals(playbackProgram, GLStateManager.getActiveProgram());
}
}
@Test
void explicitForeignProgramBindsStillRecord() {
int explicit = program();
int caller = program();
GLStateManager.glUseProgram(caller);
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glUseProgram(explicit);
GLStateManager.glEndList();
assertEquals(caller, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM));
GLStateManager.glCallList(list);
assertEquals(explicit, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM));
}
@Test
void transitionFailureResumesRecording() {
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
assertThrows(IllegalStateException.class, () -> IrisDisplayListState.runProgramTransition(() -> {
throw new IllegalStateException("test");
}));
assertTrue(DisplayListManager.isRecording());
GLStateManager.glEndList();
}
@Test
void repeatedItemIdsDeduplicateWithoutInheritingTheCompilerOrCrossingNestedCalls() {
final int original = ItemIdManager.getItemId();
try {
final int child = newList();
GLStateManager.glNewList(child, GL11.GL_COMPILE);
ItemIdManager.setItemIdRaw(19);
GLStateManager.glEndList();
ItemIdManager.setItemIdRaw(42);
final int parent = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
ItemIdManager.setItemIdRaw(42);
ItemIdManager.setItemIdRaw(42);
GLStateManager.glCallList(child);
ItemIdManager.setItemIdRaw(42);
ItemIdManager.setItemIdRaw(42);
GLStateManager.glEndList();
assertEquals(2, DisplayListManager.getDisplayList(parent).getCommandCounts().get(GLCommand.COMPLEX_REF));
ItemIdManager.setItemIdRaw(77);
GLStateManager.glCallList(parent);
assertEquals(42, ItemIdManager.getItemId());
} finally {
ItemIdManager.setItemIdRaw(original);
}
}
@Test
void nestedItemScopesRestorePlaybackCallerAndIntermediateValues() {
int original = ItemIdManager.getItemId();
try {
ItemIdManager.setItemIdRaw(111);
int child = newList();
GLStateManager.glNewList(child, GL11.GL_COMPILE);
ItemIdManager.pushItemId();
ItemIdManager.setItemIdRaw(9);
ItemIdManager.popItemId();
GLStateManager.glEndList();
int parent = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
ItemIdManager.pushItemId();
ItemIdManager.setItemIdRaw(42);
GLStateManager.glCallList(child);
DisplayListManager.recordStateCommand(() -> assertEquals(42, ItemIdManager.getItemId()));
ItemIdManager.popItemId();
GLStateManager.glEndList();
assertEquals(111, ItemIdManager.getItemId());
for (int caller : new int[] {222, 333}) {
ItemIdManager.setItemIdRaw(caller);
GLStateManager.glCallList(parent);
assertEquals(caller, ItemIdManager.getItemId());
}
} finally {
ItemIdManager.setItemIdRaw(original);
}
}
@Test
void phaseSpecialAndTranslucencyScopesRestorePlaybackCaller() {
WorldRenderingPhase originalPhase = GbufferPrograms.getOverridePhase();
Boolean originalTranslucency = GbufferPrograms.getDeclaredTranslucency();
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GbufferPrograms.pushOverridePhase(WorldRenderingPhase.ENTITIES);
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT);
Boolean previous = GbufferPrograms.beginTranslucencyDeclaration(false);
GbufferPrograms.endTranslucencyDeclaration(previous);
GbufferPrograms.teardownSpecialRenderCondition();
GbufferPrograms.popOverridePhase();
GLStateManager.glEndList();
GbufferPrograms.setOverridePhase(WorldRenderingPhase.HAND_SOLID);
GbufferPrograms.setTranslucencyDeclaration(true);
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.ENTITY_EYES);
try {
GLStateManager.glCallList(list);
assertEquals(WorldRenderingPhase.HAND_SOLID, GbufferPrograms.getOverridePhase());
assertEquals(SpecialCondition.ENTITY_EYES, GbufferPrograms.getSpecialCondition());
assertEquals(Boolean.TRUE, GbufferPrograms.getDeclaredTranslucency());
} finally {
GbufferPrograms.teardownSpecialRenderCondition();
GbufferPrograms.setOverridePhase(originalPhase);
GbufferPrograms.setTranslucencyDeclaration(originalTranslucency);
}
}
@Test
void nestedEntityPhaseUsesThePlaybackPipelineInBothCompileModes() {
PipelineManager manager = Iris.getPipelineManager();
WorldRenderingPipeline previousPipeline = manager.getPipelineNullable();
WorldRenderingPhase previousOverride = GbufferPrograms.getOverridePhase();
PhasePipeline pipeline = new PhasePipeline(program(), program());
WorldRenderingPhase[] expected = { WorldRenderingPhase.NONE };
Reflect.set(manager, "pipeline", pipeline);
GbufferPrograms.setOverridePhase(null);
try {
for (int mode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
for (WorldRenderingPhase compiling : new WorldRenderingPhase[] { WorldRenderingPhase.NONE, WorldRenderingPhase.BLOCK_ENTITIES }) {
pipeline.phase = compiling;
int child = newList();
GLStateManager.glNewList(child, mode);
boolean childScope = GbufferPrograms.beginNestedEntityPhase();
DisplayListManager.recordStateCommand(() -> {
assertEquals(expected[0], GbufferPrograms.getCurrentPhase());
assertEquals(expected[0] == WorldRenderingPhase.ENTITIES ? pipeline.entityProgram : pipeline.blockProgram,
GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM));
});
GbufferPrograms.endNestedEntityPhase(childScope);
GLStateManager.glEndList();
int parent = newList();
GLStateManager.glNewList(parent, mode);
boolean parentScope = GbufferPrograms.beginNestedEntityPhase();
// COMPILE_AND_EXECUTE also replays the child now.
expected[0] = GbufferPrograms.getCurrentPhase();
pipeline.bind();
GLStateManager.glCallList(child);
GbufferPrograms.endNestedEntityPhase(parentScope);
GLStateManager.glEndList();
for (WorldRenderingPhase playback : new WorldRenderingPhase[] { WorldRenderingPhase.BLOCK_ENTITIES, WorldRenderingPhase.HAND_SOLID, WorldRenderingPhase.ENTITIES }) {
pipeline.phase = playback;
GbufferPrograms.setOverridePhase(null);
expected[0] = playback == WorldRenderingPhase.BLOCK_ENTITIES ? WorldRenderingPhase.ENTITIES : playback;
GLStateManager.glCallList(parent);
assertEquals(playback, GbufferPrograms.getCurrentPhase());
assertNull(GbufferPrograms.getOverridePhase());
assertEquals(playback == WorldRenderingPhase.ENTITIES ? pipeline.entityProgram : pipeline.blockProgram,
GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM));
}
}
}
} finally {
if (DisplayListManager.isRecording()) GLStateManager.glEndList();
GbufferPrograms.setOverridePhase(previousOverride);
Reflect.set(manager, "pipeline", previousPipeline);
}
}
private static final class PhasePipeline extends FixedFunctionWorldRenderingPipeline {
private WorldRenderingPhase phase = WorldRenderingPhase.NONE;
private WorldRenderingPhase override;
private final int entityProgram;
private final int blockProgram;
private PhasePipeline(int entityProgram, int blockProgram) {
this.entityProgram = entityProgram;
this.blockProgram = blockProgram;
}
@Override
public WorldRenderingPhase getPhase() {
return override == null ? phase : override;
}
@Override
public void setOverridePhase(WorldRenderingPhase phase) {
override = phase;
bind();
}
private void bind() {
IrisDisplayListState.runProgramTransition(() -> GLStateManager.glUseProgram(
getPhase() == WorldRenderingPhase.ENTITIES ? entityProgram : blockProgram));
}
}
private static int itemUniform(int program) {
IntBuffer result = BufferUtils.createIntBuffer(1);
GL20.glGetUniform(program, GLStateManager.glGetUniformLocation(program, "itemId"), result);
return result.get(0);
}
@Test
void cachedSpawnerEntityResolvesCurrentPackAndRestoresNestedCallerIds() {
final BlockRenderingSettings settings = BlockRenderingSettings.INSTANCE;
final Object2IntFunction<NamespacedId> previousIds = settings.getEntityIds();
final NbtConditionalIdMap<NamespacedId> previousNbt = settings.getEntityNbtMap();
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
final int previousEntity = state.getCurrentRenderedEntity();
final int previousItem = state.getCurrentRenderedItem();
final int previousBlock = state.getCurrentRenderedBlockEntity();
final Entity entity = mock(Entity.class);
final String registered = EntityList.getEntityString(entity);
final NamespacedId name = new NamespacedId(registered == null ? entity.getClass().getSimpleName() : registered);
final int program = program();
GLStateManager.glUseProgram(program);
final ProgramUniforms.Builder builder = ProgramUniforms.builder("cached entity", program);
// The fixture's active int uniform carries the entity ID here.
builder.uniform1i("itemId", state::getCurrentRenderedEntity, state.getEntityIdNotifier());
final ProgramUniforms uniforms = builder.buildUniforms();
Reflect.setStatic(ProgramUniforms.class, "active", uniforms);
Reflect.<List<Uniform>>get(uniforms, "dynamic").get(0).update();
final int[] expected = { -1 };
try {
settings.setEntityNbtMap(null);
state.setCurrentBlockEntity(52);
for (int mode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
settings.setEntityIds(null);
expected[0] = -1;
state.setCurrentEntityAndItem(111, 112);
final int child = newList();
GLStateManager.glNewList(child, mode);
state.pushCurrentEntityAndItem();
state.setCurrentRenderedEntity(entity);
DisplayListManager.recordStateCommand(() -> {
assertEquals(expected[0], state.getCurrentRenderedEntity());
assertEquals(expected[0], itemUniform(program));
assertEquals(0, state.getCurrentRenderedItem());
assertEquals(52, state.getCurrentRenderedBlockEntity(), "the cage keeps its block ID");
});
state.popCurrentEntityAndItem();
GLStateManager.glEndList();
assertEquals(111, state.getCurrentRenderedEntity());
assertEquals(112, state.getCurrentRenderedItem());
final int parent = newList();
GLStateManager.glNewList(parent, mode);
state.pushCurrentEntityAndItem();
state.setCurrentRenderedEntity(entity);
ItemIdManager.pushItemId();
ItemIdManager.setItemIdRaw(73);
GLStateManager.glCallList(child);
DisplayListManager.recordStateCommand(() -> {
assertEquals(expected[0], state.getCurrentRenderedEntity());
assertEquals(73, state.getCurrentRenderedItem(), "nested entity restores outer item ID");
});
ItemIdManager.popItemId();
state.popCurrentEntityAndItem();
GLStateManager.glEndList();
for (int id : new int[] { 45010, 45011, -1 }) {
settings.setEntityIds(key -> name.equals(key) ? id : -1);
expected[0] = id;
state.setCurrentEntityAndItem(222, 223);
GLStateManager.glCallList(parent);
assertEquals(222, state.getCurrentRenderedEntity());
assertEquals(222, itemUniform(program));
assertEquals(223, state.getCurrentRenderedItem());
assertEquals(52, state.getCurrentRenderedBlockEntity());
}
}
} finally {
if (DisplayListManager.isRecording()) GLStateManager.glEndList();
ProgramUniforms.clearActiveUniforms();
settings.setEntityIds(previousIds);
settings.setEntityNbtMap(previousNbt);
state.setCurrentEntityAndItem(previousEntity, previousItem);
state.setCurrentBlockEntity(previousBlock);
}
}
@Test
void cachedItemResolvesItsIdAfterFirstPackLoadAndPackReplacement() {
BlockRenderingSettings settings = BlockRenderingSettings.INSTANCE;
Object2IntFunction<NamespacedId> previousIds = settings.getItemIds();
NbtConditionalIdMap<NamespacedId> previousNbt = settings.getItemNbtMap();
int previousItem = ItemIdManager.getItemId();
Item item = new Item();
NamespacedId name = new NamespacedId("angelica", "cached_item_test");
int program = program();
GLStateManager.glUseProgram(program);
ProgramUniforms.Builder builder = ProgramUniforms.builder("cached item", program);
builder.uniform1i("itemId", ItemIdManager::getItemId, CapturedRenderingState.INSTANCE.getItemIdNotifier());
ProgramUniforms uniforms = builder.buildUniforms();
Reflect.setStatic(ProgramUniforms.class, "active", uniforms);
Reflect.<List<Uniform>>get(uniforms, "dynamic").get(0).update();
int[] expected = { 0 };
try {
for (int mode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
settings.setItemIds(null);
settings.setItemNbtMap(null);
ItemMaterialHelper.clearCache();
ItemIdManager.setItemIdRaw(111);
ItemStack stack = new ItemStack(item);
int list = newList();
GLStateManager.glNewList(list, mode);
ItemIdManager.pushItemId();
ItemIdManager.setItemId(stack);
DisplayListManager.recordStateCommand(() -> {
assertEquals(expected[0], ItemIdManager.getItemId());
assertEquals(expected[0], itemUniform(program));
});
ItemIdManager.popItemId();
GLStateManager.glEndList();
assertEquals(111, ItemIdManager.getItemId());
// A renderer may reuse its ItemStack after compiling a list.
stack.func_150996_a(new Item());
for (int id : new int[] { 45020, 45028 }) {
settings.setItemIds(key -> name.equals(key) ? id : 0);
ItemMaterialHelper.clearCache();
// Supply a registry name without bootstrapping the entire Forge item registry.
Reflect.<Reference2ObjectMap<Item, NamespacedId>>getStatic(ItemMaterialHelper.class, "ITEM_NAME_CACHE").put(item, name);
expected[0] = id;
ItemIdManager.setItemIdRaw(222);
GLStateManager.glCallList(list);
assertEquals(222, ItemIdManager.getItemId());
assertEquals(222, itemUniform(program));
}
}
} finally {
if (DisplayListManager.isRecording()) GLStateManager.glEndList();
ProgramUniforms.clearActiveUniforms();
settings.setItemIds(previousIds);
settings.setItemNbtMap(previousNbt);
ItemMaterialHelper.clearCache();
ItemIdManager.setItemIdRaw(previousItem);
}
}
@Test
void compileOnlyItemNotificationsDoNotUploadToLiveProgram() {
int program = program();
int original = ItemIdManager.getItemId();
GLStateManager.glUseProgram(program);
ItemIdManager.setItemIdRaw(111);
ProgramUniforms.Builder builder = ProgramUniforms.builder("review", program);
builder.uniform1i("itemId", ItemIdManager::getItemId, CapturedRenderingState.INSTANCE.getItemIdNotifier());
ProgramUniforms uniforms = builder.buildUniforms();
List<Uniform> dynamic = Reflect.get(uniforms, "dynamic");
Reflect.setStatic(ProgramUniforms.class, "active", uniforms);
dynamic.get(0).update();
try {
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
ItemIdManager.pushItemId();
ItemIdManager.setItemIdRaw(42);
uniforms.update();
assertEquals(111, itemUniform(program));
ItemIdManager.popItemId();
GLStateManager.glEndList();
ItemIdManager.setItemIdRaw(222);
assertEquals(222, itemUniform(program));
GLStateManager.glCallList(list);
assertEquals(222, itemUniform(program));
} finally {
ProgramUniforms.clearActiveUniforms();
ItemIdManager.setItemIdRaw(original);
}
}
}
@@ -2,19 +2,29 @@ package com.gtnewhorizons.angelica.rendering.items;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.compat.draconicevolution.PlacedItemRenderCompat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityClientPlayerMP;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.NORMAL_INDEX;
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.TEX_X_INDEX;
@@ -26,6 +36,8 @@ import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.Z_
import static com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility.SAFE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
@GLCoreTest
class ItemTemplateCaptureGLTest {
@@ -141,6 +153,68 @@ class ItemTemplateCaptureGLTest {
assertCaptureMatches(1.0f, 0.0f, 0.0f, 1.0f, 16, 16, 0.0625f);
}
@Test
void placedIconsShareCapturedGeometryAcrossLiveCustomItems() {
final boolean batching = AngelicaConfig.enableEntityBatching;
final int capacity = AngelicaConfig.itemRendererCacheSize;
final Minecraft minecraft = Minecraft.getMinecraft();
final ShaderManager shaders = ShaderManager.getInstance();
final boolean ffp = shaders.isEnabled();
final ModelPartBatcher batcher = ModelPartBatcher.INSTANCE;
final Matrix4f originalMatrix = new Matrix4f(GLStateManager.getModelViewMatrix());
final int[] captures = { 0 };
final Operation<Void> original = args -> {
captures[0]++;
ItemRenderer.renderItemIn2D((Tessellator) args[0], (float) args[1], (float) args[2],
(float) args[3], (float) args[4], (int) args[5], (int) args[6], (float) args[7]);
return null;
};
try {
AngelicaConfig.enableEntityBatching = true;
AngelicaConfig.itemRendererCacheSize = 16;
final Minecraft testMinecraft = mock(Minecraft.class);
testMinecraft.thePlayer = mock(EntityClientPlayerMP.class);
Reflect.setStatic(Minecraft.class, "theMinecraft", testMinecraft);
shaders.enable();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
batcher.begin(ModelPartBatcher.Mode.BLOCK_ENTITIES);
assertTrue(batcher.isActive());
ModelPartBatcher.onTextureBind(new ResourceLocation("test", "placed-items"), GLStateManager.getBoundTextureForServerState());
final long parts = batcher.statParts();
final long instances = DroppedItemInstancer.statInstanced();
BatchEligibility.begin(BatchEligibility.DENIED, GLStateManager.drawCalls);
try {
for (int item = 0; item < 2; item++) {
final int rotation = item * 90;
assertEquals(SAFE, PlacedItemRenderCompat.renderWithBatchState(item, 0, 0, SAFE, () -> {
GLStateManager.glRotatef(rotation, 0, 1, 0);
DroppedItemInstancer.batchIcon(EntityMaterials.DROPPED_ITEM_CUTOUT, false, Tessellator.instance,
1f, 0f, 0f, 1f, 16, 16, 0.0625f, original);
}));
PlacedItemRenderCompat.renderWithBatchState(0, 0, 0, BatchEligibility.DENIED, () -> {
final long before = GLStateManager.drawCalls;
GLStateManager.drawCalls++;
BatchEligibility.onPartFallback(before, GLStateManager.drawCalls);
});
}
} finally {
BatchEligibility.end(BatchEligibility.DENIED, GLStateManager.drawCalls);
}
assertEquals(1, captures[0], "the second placement must reuse the icon mesh");
assertEquals(2, batcher.statParts() - parts, "both transforms reach the shared batcher");
assertEquals(2, DroppedItemInstancer.statInstanced() - instances);
assertTrue(new Matrix4f().equals(GLStateManager.getModelViewMatrix(), 1e-5f));
} finally {
batcher.clear();
GLStateManager.setModelViewMatrix(originalMatrix);
AngelicaConfig.enableEntityBatching = batching;
AngelicaConfig.itemRendererCacheSize = capacity;
Reflect.setStatic(Minecraft.class, "theMinecraft", minecraft);
if (!ffp) shaders.disable();
}
}
@Test
void anEmptyBlockCaptureDrawsVanillaWithoutDenyingTheRenderer() {
final Block block = new ProbeBlock();
@@ -16,6 +16,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class BatchEligibilityTest {
@Test
void uncapturedStateDemotesSafeRenderersAndPreventsUnknownPromotion() {
for (byte state : new byte[] {BatchEligibility.SAFE, BatchEligibility.UNKNOWN}) {
BatchEligibility.begin(state, 0);
BatchEligibility.onPartQueued();
BatchEligibility.onUncapturedState();
assertFalse(BatchEligibility.batchingAllowed());
BatchEligibility.onBatchFlushed(4);
BatchEligibility.onPartFallback(4, 5);
assertEquals(BatchEligibility.DENIED, BatchEligibility.end(state, 5));
}
assertTrue(BatchEligibility.begin(BatchEligibility.SAFE, 5));
assertEquals(BatchEligibility.SAFE, BatchEligibility.end(BatchEligibility.SAFE, 5));
}
private long drawCalls;
@BeforeEach
@@ -110,6 +125,73 @@ class BatchEligibilityTest {
assertFalse(BatchEligibility.batchingAllowed());
}
@Test
void ordinaryPlacedItemCanBatchInsideADeniedWrapper() {
BatchEligibility.begin(DENIED, drawCalls);
BatchEligibility.beginIsolated(SAFE, drawCalls);
assertTrue(BatchEligibility.batchingAllowed());
BatchEligibility.begin(DENIED, drawCalls);
assertTrue(BatchEligibility.batchingAllowed(), "nested RenderItem inherits the content scope");
BatchEligibility.onPartQueued();
assertEquals(DENIED, BatchEligibility.end(DENIED, drawCalls));
assertEquals(SAFE, BatchEligibility.endIsolated(SAFE, drawCalls));
assertFalse(BatchEligibility.batchingAllowed(), "restore the wrapper's eligibility");
assertEquals(DENIED, BatchEligibility.end(DENIED, drawCalls));
}
@Test
void customPlacedItemStaysEntirelyLiveWithoutPoisoningOrdinaryItems() {
BatchEligibility.begin(SAFE, drawCalls);
BatchEligibility.onPartQueued();
BatchEligibility.beginIsolated(DENIED, drawCalls);
assertFalse(BatchEligibility.batchingAllowed());
BatchEligibility.begin(SAFE, drawCalls);
assertFalse(BatchEligibility.batchingAllowed(), "even a batchable nested mob must stay live");
unbatchedPart(2);
draw(3);
assertEquals(SAFE, BatchEligibility.end(SAFE, drawCalls));
assertEquals(DENIED, BatchEligibility.endIsolated(DENIED, drawCalls));
assertTrue(BatchEligibility.batchingAllowed());
BatchEligibility.beginIsolated(UNKNOWN, drawCalls);
unbatchedPart(1);
assertEquals(SAFE, BatchEligibility.endIsolated(UNKNOWN, drawCalls));
assertEquals(SAFE, BatchEligibility.end(SAFE, drawCalls));
}
@Test
void isolatedScopesRestoreEnclosingDrawBracketsWithoutDoubleCounting() {
BatchEligibility.begin(UNKNOWN, drawCalls);
BatchEligibility.beginExpectedDraws(drawCalls);
draw(1);
BatchEligibility.beginIsolated(UNKNOWN, drawCalls);
BatchEligibility.beginExpectedDraws(drawCalls);
unbatchedPart(2);
BatchEligibility.endExpectedDraws(drawCalls);
assertEquals(SAFE, BatchEligibility.endIsolated(UNKNOWN, drawCalls));
draw(1);
BatchEligibility.endExpectedDraws(drawCalls);
unbatchedPart(1);
draw(1);
assertEquals(DENIED, BatchEligibility.end(UNKNOWN, drawCalls));
assertEquals(1L, (long) Reflect.getStatic(BatchEligibility.class, "foreignDraws"));
}
@Test
void isolatedScopesCanNestAndDoNotPromoteTheirWrapper() {
BatchEligibility.begin(UNKNOWN, drawCalls);
BatchEligibility.beginIsolated(SAFE, drawCalls);
BatchEligibility.onPartQueued();
BatchEligibility.beginIsolated(DENIED, drawCalls);
unbatchedPart(1);
draw(1);
assertEquals(DENIED, BatchEligibility.endIsolated(DENIED, drawCalls));
assertTrue(BatchEligibility.batchingAllowed());
assertEquals(SAFE, BatchEligibility.endIsolated(SAFE, drawCalls));
assertEquals(UNKNOWN, BatchEligibility.end(UNKNOWN, drawCalls));
assertFalse(BatchEligibility.batchingAllowed());
}
@Test
void bracketedDrawsWithAQueuedPartPromoteUnknownToSafe() {
BatchEligibility.begin(UNKNOWN, drawCalls);
@@ -0,0 +1,165 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.iris.IrisDisplayListState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
@GLCoreTest
class BatchStateGuardGLTest {
private Runnable previousFlush;
@BeforeEach
void saveState() {
previousFlush = BatchStateGuard.flush;
BatchStateGuard.flush = null;
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@AfterEach
void restoreState() {
BatchStateGuard.flush = null;
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPopMatrix();
GLStateManager.glPopAttrib();
BatchStateGuard.flush = previousFlush;
}
@Test
void projectionMutationFlushesBeforeOverwritingTheMatrixAndDoesNotReenter() {
final AtomicInteger flushes = new AtomicInteger();
BatchStateGuard.flush = () -> {
flushes.incrementAndGet();
assertEquals(1f, GLStateManager.getProjectionMatrix().m00());
// A flushing renderer is allowed to set up its own draw state.
GLStateManager.glScissor(0, 0, 8, 8);
};
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glScalef(2, 3, 1);
assertEquals(1, flushes.get());
assertEquals(2f, GLStateManager.getProjectionMatrix().m00());
}
@Test
void commonCapturedStateAndManagedPassSetupDoNotDrainBatches() {
final AtomicInteger flushes = new AtomicInteger();
BatchStateGuard.flush = flushes::incrementAndGet;
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(1, 2, 3);
GLStateManager.glPopMatrix();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glColor4f(0.5f, 1, 1, 1);
GLStateManager.enableBlend();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
IrisDisplayListState.runProgramTransition(() -> {
GLStateManager.glFogf(GL11.GL_FOG_START, 123);
GLStateManager.glViewport(0, 0, 8, 8);
});
assertEquals(0, flushes.get());
assertFalse(BatchStateGuard.isSuspended());
}
@Test
void fogAndStencilFlushBeforeChangingTheCachedState() {
GLStateManager.glFogf(GL11.GL_FOG_START, 17);
BatchStateGuard.flush = () -> assertEquals(17f, GLStateManager.getFogState().getStart());
GLStateManager.glFogf(GL11.GL_FOG_START, 29);
assertEquals(29f, GLStateManager.getFogState().getStart());
BatchStateGuard.flush = null;
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
final AtomicInteger flushes = new AtomicInteger();
BatchStateGuard.flush = () -> {
assertFalse(GLStateManager.glIsEnabled(GL11.GL_STENCIL_TEST));
flushes.incrementAndGet();
};
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
assertEquals(1, flushes.get());
}
@Test
void extraTextureBindingsAndAttributeRestoresCannotOvertakeQueuedDraws() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glPushAttrib(GL11.GL_TEXTURE_BIT);
final int texture = GLStateManager.glGenTextures();
try {
final AtomicInteger flushes = new AtomicInteger();
BatchStateGuard.flush = () -> {
assertEquals(0, GLStateManager.getBoundTextureForServerState(2));
flushes.incrementAndGet();
};
RenderSystem.bindTextureToUnit(GL11.GL_TEXTURE_2D, 2, texture);
assertTrue(flushes.get() > 0);
flushes.set(0);
BatchStateGuard.flush = () -> {
assertEquals(texture, GLStateManager.getBoundTextureForServerState(2));
flushes.incrementAndGet();
};
GLStateManager.glPopAttrib();
assertTrue(flushes.get() > 0);
assertEquals(0, GLStateManager.getBoundTextureForServerState(2));
} finally {
BatchStateGuard.flush = null;
GLStateManager.glDeleteTextures(texture);
}
}
@Test
void compileOnlyDefersTheBarrierUntilPlayback() {
final int list = GLStateManager.glGenLists(1);
final AtomicInteger flushes = new AtomicInteger();
BatchStateGuard.flush = flushes::incrementAndGet;
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glScissor(1, 2, 3, 4);
GLStateManager.glEndList();
assertEquals(0, flushes.get());
GLStateManager.glCallList(list);
assertEquals(1, flushes.get());
} finally {
GLStateManager.glDeleteLists(list, 1);
}
}
@Test
void textureMatrixChangesDrainOtherUnitsButKeepUnitZeroInstanced() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
final AtomicInteger flushes = new AtomicInteger();
try {
BatchStateGuard.flush = () -> {
assertEquals(0f, GLStateManager.getTextures().getTextureUnitMatrix(2).m30());
flushes.incrementAndGet();
};
GLStateManager.glTranslatef(0.5f, 0, 0);
assertEquals(1, flushes.get());
BatchStateGuard.flush = flushes::incrementAndGet;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(0.5f, 0, 0);
GLStateManager.glPopMatrix();
assertEquals(1, flushes.get(), "the base texture matrix is already stored in batch runs");
} finally {
BatchStateGuard.flush = null;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.glPopMatrix();
}
}
}
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
import com.gtnewhorizons.angelica.glsm.ffp.CubeParityFixture;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
@@ -22,6 +23,7 @@ import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.coderbot.iris.layer.PassOverride;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
@@ -42,6 +44,8 @@ import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.CO
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.VERTEX_SIZE;
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.X_INDEX;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -58,6 +62,7 @@ class RetainedTesrGroupsGLTest {
private static boolean overridesRegistered;
private static boolean skipClientArrays;
private static Runnable onBufferSetup;
private AngelicaBufferSource source;
private InstanceRing ring;
@@ -80,7 +85,10 @@ class RetainedTesrGroupsGLTest {
static void registerClientArrayOverrides() {
if (overridesRegistered) return;
overridesRegistered = true;
VertexFormat.registerSetupBufferStateOverride((format, offset) -> skipClientArrays);
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
if (onBufferSetup != null) onBufferSetup.run();
return skipClientArrays;
});
VertexFormat.registerClearBufferStateOverride(format -> skipClientArrays);
}
@@ -110,7 +118,9 @@ class RetainedTesrGroupsGLTest {
@AfterEach
void cleanup() {
onBufferSetup = null;
skipClientArrays = false;
source.discard();
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
@@ -186,6 +196,50 @@ class RetainedTesrGroupsGLTest {
}
}
@Test
void cpuFallbackRestoresEveryIdAndTintBeforeDrawingAndRestoresTheCaller() {
final var state = net.coderbot.iris.uniforms.CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
final var draws = new java.util.ArrayList<long[]>();
onBufferSetup = () -> draws.add(new long[] {
state.getCurrentRenderedEntity(), state.getCurrentRenderedBlockEntity(), state.getCurrentRenderedItem(),
AngelicaBufferSource.packEntityColor(state.getCurrentEntityColor())
});
groups.beginPass(new Matrix4f(), 0, 0, 0, instanced, new CountingPipeline(false, false));
final TemplateBuffer template = capturedQuad(0.1);
for (int i = 0; i < 3; i++) {
final long info = com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs.packEntityInfo(45010 + i, 45020 + i, 45030 + i);
final int color = AngelicaBufferSource.packAbgr(i / 2f, 0, 0, 1);
groups.queue(template, layer, RetainedTesrGroupsTest.STREAM, new Matrix4f(), 0, -1, color, info, 45020 + i, null);
}
state.setCurrentEntityAndItem(81, 82);
state.setCurrentBlockEntity(83);
state.setCurrentEntityColor(0, 1, 0, 0.5f);
draws.clear();
skipClientArrays = true;
groups.drawLayer(layer);
assertEquals(3, draws.size(), "only differing uniform values split the CPU fallback");
for (int i = 0; i < 3; i++) {
assertArrayEquals(new long[] {45010 + i, 45020 + i, 45030 + i,
AngelicaBufferSource.packAbgr(i / 2f, 0, 0, 1)}, draws.get(i));
}
source.discard();
assertEquals(81, state.getCurrentRenderedEntity());
assertEquals(82, state.getCurrentRenderedItem());
assertEquals(83, state.getCurrentRenderedBlockEntity());
assertTrue(state.getCurrentEntityColor().equals(0, 1, 0, 0.5f));
} finally {
onBufferSetup = null;
source.discard();
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@Test
void bothVariantsBindOncePerLayer() {
final CountingPipeline pipeline = new CountingPipeline(true, true);
@@ -322,6 +376,181 @@ class RetainedTesrGroupsGLTest {
assertTrue(identityAtOrigin[0] > 200 && identityAtOrigin[1] < 50 && identityAtOrigin[2] < 50, "an identity instance must render red at the origin, got " + identityAtOrigin[0] + "," + identityAtOrigin[1] + "," + identityAtOrigin[2]);
}
@Test
void scissorChangeDrainsQueuedGeometryBeforeClippingAndEndsModelBatching() {
GLStateManager.disableBlend();
GLStateManager.disableAlphaTest();
GLStateManager.disableTexture();
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, 0, 0);
FfpFixture.clear();
groups.beginPass(new Matrix4f(), 0, 0, 0, instanced);
groups.queue(template(TRIANGLE, COLOR_ABGR, GL11.GL_TRIANGLES), layer, RetainedTesrGroupsTest.STREAM,
new Matrix4f(), 0, COLOR_ABGR, 0, 0L, 0, null);
final ModelPartBatcher models = ModelPartBatcher.INSTANCE;
final Object oldSource = Reflect.get(models, "activeSource");
final Object oldGroups = Reflect.get(models, "activeGroups");
final Runnable oldFlush = com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard.flush;
try {
Reflect.set(models, "activeSource", source);
Reflect.set(models, "activeGroups", groups);
Reflect.set(models, "active", true);
BatchStateFallback.install();
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
assertTrue(models.isActive(), "batching must resume for the rest of the pass after the flush");
assertFalse(models.hasQueuedGeometry(), "the flush must drain what was queued before the change");
assertTrue(FfpFixture.readPixel(400, 300)[0] > 200, "queued geometry must draw before the zero-area scissor takes effect");
} finally {
Reflect.set(models, "active", false);
Reflect.set(models, "activeSource", oldSource);
Reflect.set(models, "activeGroups", oldGroups);
com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard.flush = oldFlush;
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
}
}
@Test
void heldEntityTranslucentsSurviveUncapturedStateChange() {
GLStateManager.disableBlend();
GLStateManager.disableAlphaTest();
GLStateManager.disableTexture();
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, 0, 0);
FfpFixture.clear();
groups.beginPass(new Matrix4f(), 0, 0, 0, instanced);
groups.queue(template(TRIANGLE, COLOR_ABGR, GL11.GL_TRIANGLES), layer, RetainedTesrGroupsTest.STREAM,
new Matrix4f(), 0, COLOR_ABGR, 0, 0L, 0, null);
final ModelPartBatcher models = ModelPartBatcher.INSTANCE;
final Object oldSource = Reflect.get(models, "entitySource");
final Object oldGroups = Reflect.get(models, "entityGroups");
final Runnable oldFlush = com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard.flush;
try {
Reflect.set(models, "entitySource", source);
Reflect.set(models, "entityGroups", groups);
Reflect.set(models, "entityTranslucentsHeld", true);
BatchStateFallback.install();
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
assertTrue(models.hasHeldEntities(), "held entity translucents wait for the post-deferred flush");
assertTrue(FfpFixture.readPixel(400, 300)[0] < 50, "held entity geometry must not draw before deferred");
} finally {
Reflect.set(models, "entityTranslucentsHeld", false);
Reflect.set(models, "entitySource", oldSource);
Reflect.set(models, "entityGroups", oldGroups);
com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard.flush = oldFlush;
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
}
}
@Test
void fogChangeDrainsProviderBatchesAndClearsTheActivePass() {
GLStateManager.disableBlend();
GLStateManager.disableAlphaTest();
GLStateManager.disableTexture();
FfpFixture.clear();
groups.beginPass(new Matrix4f(), 0, 0, 0, instanced);
groups.queue(template(TRIANGLE, COLOR_ABGR, GL11.GL_TRIANGLES), layer, RetainedTesrGroupsTest.STREAM,
new Matrix4f(), 0, COLOR_ABGR, 0, 0L, 0, null);
final TesrBatchRenderer renderer = TesrBatchRenderer.INSTANCE;
final Object oldSource = Reflect.get(renderer, "bufferSource");
final RetainedTesrGroups[] retained = Reflect.get(renderer, "retained");
final RetainedTesrGroups oldGroups = retained[0];
final Runnable oldFlush = com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard.flush;
try {
Reflect.set(renderer, "bufferSource", source);
retained[0] = groups;
Reflect.set(renderer, "activePass", 0);
BatchStateFallback.install();
GLStateManager.enableFog();
assertFalse(renderer.hasPendingGeometry());
assertTrue(FfpFixture.readPixel(400, 300)[0] > 200, "provider geometry must reach the framebuffer before fog is enabled");
} finally {
Reflect.set(renderer, "activePass", -1);
Reflect.set(renderer, "bufferSource", oldSource);
retained[0] = oldGroups;
com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard.flush = oldFlush;
GLStateManager.disableFog();
}
}
@Test
void emptyShadowPassSetupKeepsBatchingButRendererSpecificStateDisablesIt() {
final TesrBatchRenderer renderer = TesrBatchRenderer.INSTANCE;
final Object oldSource = Reflect.get(renderer, "bufferSource");
final Runnable oldFlush = com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard.flush;
try {
Reflect.set(renderer, "bufferSource", source);
Reflect.set(renderer, "activePass", TesrBatchRenderer.PASS_SHADOW);
BatchStateFallback.install();
net.coderbot.iris.layer.GbufferPrograms.setBlockEntityDefaults();
assertTrue(renderer.hasActivePass(), "default attributes before the first renderer must not disable shadow batching");
BatchEligibility.begin(BatchEligibility.SAFE, GLStateManager.drawCalls);
try {
GLStateManager.glScissor(0, 0, 8, 8);
assertFalse(renderer.hasActivePass(), "unsupported renderer state needs fallback even before its first model part");
} finally {
assertEquals(BatchEligibility.DENIED, BatchEligibility.end(BatchEligibility.SAFE, GLStateManager.drawCalls));
}
} finally {
Reflect.set(renderer, "activePass", -1);
Reflect.set(renderer, "bufferSource", oldSource);
com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard.flush = oldFlush;
}
}
@Test
void mirroredItemKeepsItsOuterFacesAndDepthTestedGlintWhenDrawnLater() {
final AngelicaTesrMeshCache.GtnhMeshBackend backend = new AngelicaTesrMeshCache.GtnhMeshBackend();
final Tessellator direct = backend.beginCapture(DefaultVertexFormat.POSITION_TEXTURE_NORMAL);
ItemRenderer.renderItemIn2D(direct, 1f, 0f, 0f, 1f, 16, 16, 0.0625f);
final TemplateBuffer icon = backend.endCaptureToTemplate();
assertNotNull(icon);
// Red front/back faces must hide the green internal pixel walls.
for (int i = 0; i < icon.vertexCount; i++) {
icon.data[i * VERTEX_SIZE + COLOR_INDEX] = i < 8 ? 0xFF0000FF : 0xFF00FF00;
}
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.glFrontFace(GL11.GL_CCW);
GLStateManager.disableCull();
final RenderLayer base = TesrLayerStateTest.capturedLayer(EntityMaterials.DROPPED_ITEM_CUTOUT);
final RenderLayer glint = TesrLayerStateTest.capturedLayer(
TesrMaterial.builder().glint().depthEqual().noDepthWrite().unlit().stream().build());
for (float yScale : new float[] { -0.9f, 0.9f }) {
final Matrix4f mv = new Matrix4f().rotateY(0.55f).scale(0.9f, yScale, 0.9f).translate(-0.5f, -0.5f, 0f);
final int[][] reference = drawItemAndGlint(icon, mv, base, glint, true);
final int[][] queued = drawItemAndGlint(icon, mv, base, glint, false);
assertArrayEquals(reference[0], queued[0], "queued base must preserve its original culling state");
FfpFixture.assertOverlayFootprint(reference[0], reference[1], BACKGROUND, "item glint");
assertArrayEquals(reference[1], queued[1], "glint must cover the same visible faces after batching");
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
ModelPartBatcher.INSTANCE.clear();
}
private int[][] drawItemAndGlint(TemplateBuffer icon, Matrix4f mv, RenderLayer base, RenderLayer glint, boolean reference) {
GLStateManager.disableLighting();
GLStateManager.glDepthMask(true);
FfpFixture.clear();
GLStateManager.enableCull();
final int depth = GLStateManager.pushState(StateSet.BATCH);
base.startDrawing();
if (reference) GLStateManager.disableCull();
drawSingleInstance(instanced, icon, mv, 0xFF808080);
final int[] basePixels = FfpFixture.readRegion(SIZE);
base.endDrawing();
glint.startDrawing();
if (reference) GLStateManager.disableCull();
assertTrue(GL11.glIsEnabled(GL11.GL_DEPTH_TEST));
assertEquals(GL11.GL_EQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC));
assertFalse(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK));
drawSingleInstance(instanced, icon, mv, 0xFF808080);
final int[] glintPixels = FfpFixture.readRegion(SIZE);
glint.endDrawing();
GLStateManager.popStateTo(depth);
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE), "the batch bracket restores the flush caller's culling");
return new int[][] { basePixels, glintPixels };
}
private void glintState() {
GLStateManager.glViewport(0, 0, SIZE, SIZE);
layer = RenderLayer.tesr(GLINT_TEXTURE, TesrMaterial.CURRENT_STATE, PassOverride.NONE, 0f, 0f, ShaderGlint.NO_TINT, DrawState.DISABLED, false);
@@ -12,6 +12,7 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.coderbot.batchedentityrendering.impl.BufferSegment;
import net.coderbot.batchedentityrendering.impl.BufferSourceProbe;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.layer.PassOverride;
import org.joml.Matrix4f;
import org.junit.jupiter.api.BeforeEach;
@@ -48,6 +49,120 @@ class RetainedTesrGroupsTest {
layer = testLayer();
}
@Test
void fallbackKeepsHighMaterialIdsAndTheUnmappedSentinel() {
for (int id : new int[] { -1, 0, 32767, 32768, 45020, 50020, 50072, 65534 }) {
final long info = InstancedAttribs.packEntityInfo(id, id, id);
assertEquals(id, RetainedTesrGroups.entityFromInfo(info));
assertEquals(id, RetainedTesrGroups.blockEntityFromInfo(info));
assertEquals(id, RetainedTesrGroups.itemFromInfo(info));
}
}
@Test
void retainedGroupsKeepDifferentItemIdsSeparateAcrossFrames() {
int previous = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
try {
final TemplateBuffer template = template();
RetainedTesrGroups.Group first = null;
for (int frame = 0; frame < 2; frame++) {
groups.beginPass(new Matrix4f(), 0, 0, 0);
for (int item : new int[] {42, 43, 42}) {
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(item);
groups.queue(template, layer, TesrMaterial.CURRENT_STATE, new Matrix4f(), 0, -1, 0, 0L, 5, null);
}
final Reference2ObjectOpenHashMap<RenderLayer, ObjectArrayList<RetainedTesrGroups.Group>> byLayer = Reflect.get(groups, "byLayer");
final ObjectArrayList<RetainedTesrGroups.Group> entries = byLayer.get(layer);
assertEquals(2, entries.size());
assertEquals(42, entries.get(0).itemId);
assertEquals(2, entries.get(0).templateColumns.size);
assertEquals(43, entries.get(1).itemId);
assertEquals(1, entries.get(1).templateColumns.size);
if (first != null) assertSame(first, entries.get(0), "stable groups must be reused across frames");
first = entries.get(0);
}
} finally {
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(previous);
}
}
@Test
void instancedGroupsKeepItemIdsPerInstanceWithoutSplitting() {
int previous = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
try {
groups.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring), new CountingPipeline(true, true));
for (int item : new int[] {42, 43}) {
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(item);
long info = InstancedAttribs.packEntityInfo(-1, 5, item);
groups.queue(template(), layer, STREAM, new Matrix4f(), 0, -1, 0, info, 5, null);
}
final RetainedTesrGroups.Group group = onlyGroup(groups);
assertEquals(2, group.templateColumns.size);
assertEquals(42, RetainedTesrGroups.itemFromInfo(group.templateColumns.infos.getLong(0)));
assertEquals(43, RetainedTesrGroups.itemFromInfo(group.templateColumns.infos.getLong(1)));
} finally {
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(previous);
}
}
@Test
void retainedGroupsSeparateTheFullIdTupleAndTintAndReuseStableKeys() {
final var state = CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
for (int frame = 0; frame < 2; frame++) {
groups.beginPass(new Matrix4f(), 0, 0, 0);
for (int entity : new int[] {11, 12}) {
state.setCurrentEntityAndItem(entity, 13);
for (int block : new int[] {21, 22}) {
state.setCurrentBlockEntity(block);
for (float red : new float[] {0, 1}) {
state.setCurrentEntityColor(red, 0, 0, 0.5f);
groups.queue(template(), layer, TesrMaterial.CURRENT_STATE, new Matrix4f(), 0, -1, 0, 0L, block, null);
}
}
}
assertEquals(8, groups.groupCount(), "only distinct ID/tint combinations allocate groups, including across frames");
}
} finally {
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@Test
void instancedGroupsKeepFullIdsAndTintPerInstanceWithoutSplitting() {
final var state = CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
groups.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring), new CountingPipeline(true, true));
for (int i = 0; i < 100; i++) {
state.setCurrentEntityAndItem(i, i + 200);
state.setCurrentBlockEntity(i + 100);
state.setCurrentEntityColor(i / 100f, 0, 0, 1);
final long info = InstancedAttribs.packEntityInfo(i, i + 100, i + 200);
final int overlay = AngelicaBufferSource.packEntityColor(state.getCurrentEntityColor());
groups.queue(template(), layer, STREAM, new Matrix4f(), 0, -1, overlay, info, i + 100, null);
}
final var group = onlyGroup(groups);
assertEquals(100, group.templateColumns.size);
assertEquals(1, group.templateColumns.runs.size());
for (int i = 0; i < 100; i++) {
assertEquals(InstancedAttribs.packEntityInfo(i, i + 100, i + 200), group.templateColumns.infos.getLong(i));
assertEquals(AngelicaBufferSource.packAbgr(i / 100f, 0, 0, 1), group.templateColumns.overlays.getInt(i));
}
} finally {
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@Test
void retainedGroupAccumulatesInsteadOfStreaming() {
@@ -9,10 +9,12 @@ import com.gtnewhorizons.angelica.glsm.states.AlphaTestFunction;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
import com.gtnewhorizons.angelica.glsm.states.DepthState;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import net.coderbot.iris.gl.blending.AlphaTest;
import net.coderbot.iris.gl.blending.AlphaTestStorage;
import net.coderbot.iris.gl.blending.BlendModeStorage;
import net.coderbot.iris.gl.blending.DepthColorStorage;
import net.coderbot.iris.gl.program.ProgramUniforms;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import net.coderbot.iris.gl.uniform.FloatSupplier;
import net.coderbot.iris.gl.uniform.FloatUniform;
@@ -52,6 +54,7 @@ class StateSetOverrideGLTest {
@AfterEach
void cleanup() {
listener = null;
ProgramUniforms.clearActiveUniforms();
BlendModeStorage.restoreBlend();
AlphaTestStorage.restoreAlphaTest();
DepthColorStorage.unlockDepthColor();
@@ -481,6 +484,7 @@ class StateSetOverrideGLTest {
GLStateManager.glUseProgram(program);
final int referenceLocation = GLStateManager.glGetUniformLocation(program, "alphaReference");
final int functionLocation = GLStateManager.glGetUniformLocation(program, "alphaFunction");
Reflect.setStatic(ProgramUniforms.class, "active", ProgramUniforms.builder("alpha", program).buildUniforms());
final Runnable[] updates = new Runnable[2];
final FloatUniform reference = newFloatUniform(referenceLocation, () -> GLStateManager.getAlphaState().getReference(), update -> updates[0] = update);
final IntUniform function = newIntUniform(functionLocation, () -> GLStateManager.getAlphaState().getFunction(), update -> updates[1] = update);
@@ -5,12 +5,16 @@ import com.gtnewhorizons.angelica.api.tesr.TesrShaders;
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.coderbot.batchedentityrendering.impl.TransparencyType;
import net.coderbot.iris.gl.blending.AlphaTestStorage;
import net.coderbot.iris.gl.blending.DepthColorStorage;
import net.coderbot.iris.layer.PassOverride;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -18,6 +22,9 @@ import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
@@ -73,6 +80,92 @@ class TesrLayerStateTest {
return RenderLayer.tesr(null, material, PassOverride.NONE, 0f, 0f, ShaderGlint.NO_TINT, DrawState.CULL_BACK, true);
}
@Test
void batchBracketRestoresCallerDepthAfterGlint() {
for (boolean enabled : new boolean[] { true, false }) {
GLStateManager.getDepthTest().setEnabled(enabled);
GLStateManager.glDepthFunc(GL11.GL_GREATER);
final int depth = GLStateManager.pushState(StateSet.BATCH);
final RenderLayer base = layer(EntityMaterials.DROPPED_ITEM_CUTOUT);
final RenderLayer glint = glint(ShaderGlint.NO_TINT);
base.startDrawing();
assertDepth(true, GL11.GL_LEQUAL);
base.endDrawing();
glint.startDrawing();
assertDepth(true, GL11.GL_EQUAL);
glint.endDrawing();
GLStateManager.popStateTo(depth);
assertDepth(enabled, GL11.GL_GREATER);
}
}
private static void assertDepth(boolean enabled, int function) {
assertEquals(enabled, GLStateManager.getDepthTest().isEnabled(), "cached depth enable");
assertEquals(enabled, GL11.glIsEnabled(GL11.GL_DEPTH_TEST), "driver depth enable");
assertEquals(function, GLStateManager.getDepthState().getFunc(), "cached depth function");
assertEquals(function, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth function");
}
@Test
void entityBatchRestoresDepthBeforeBlockEntities() {
final AngelicaBufferSource source = new AngelicaBufferSource();
final RenderLayer entityLayer = layer(EntityMaterials.DROPPED_ITEM_CUTOUT);
final AngelicaBufferSource.LayerDrawHook hook = new AngelicaBufferSource.LayerDrawHook() {
@Override
public boolean hasDraws(RenderLayer layer) {
return layer == entityLayer;
}
@Override
public void drawLayer(RenderLayer layer) {
assertTrue(GL11.glIsEnabled(GL11.GL_DEPTH_TEST), "entity draw depth test");
}
};
try {
source.declareUse(entityLayer);
source.endBatchWithType(TransparencyType.OPAQUE, hook);
source.endBatch(hook);
assertTrue(GLStateManager.getDepthTest().isEnabled(), "cached depth before block entities");
assertTrue(GL11.glIsEnabled(GL11.GL_DEPTH_TEST), "driver depth before block entities");
} finally {
source.discard();
source.freeBuffers();
}
}
@Test
void afterGlintLayerDrawsAfterEveryGlintLayer() {
final AngelicaBufferSource source = new AngelicaBufferSource();
final RenderLayer liquid = layer(EntityMaterials.DROPPED_ITEM_CUTOUT);
final RenderLayer glint = glint(ShaderGlint.NO_TINT);
final RenderLayer bottle = RenderLayer.tesr(null, EntityMaterials.DROPPED_ITEM_CUTOUT, PassOverride.NONE, 0f, 0f, ShaderGlint.NO_TINT, DrawState.CULL_BACK, true, true);
final List<RenderLayer> drawn = new ArrayList<>();
final AngelicaBufferSource.LayerDrawHook hook = new AngelicaBufferSource.LayerDrawHook() {
@Override
public boolean hasDraws(RenderLayer layer) {
return true;
}
@Override
public void drawLayer(RenderLayer layer) {
drawn.add(layer);
}
};
try {
assertNotSame(liquid, bottle);
assertEquals(TransparencyType.AFTER_GLINT, bottle.getTransparencyType());
source.declareUse(liquid);
source.declareUse(bottle);
source.declareUse(glint);
source.endBatchWithType(TransparencyType.OPAQUE, hook);
source.endBatch(hook);
assertEquals(List.of(liquid, glint, bottle), drawn);
} finally {
source.discard();
source.freeBuffers();
}
}
private static RenderLayer glint(int slot) {
return RenderLayer.tesr(null, EntityMaterials.GLINT, PassOverride.NONE, 0f, 0f, slot, DrawState.CULL_BACK, true);
}
@@ -96,6 +189,82 @@ class TesrLayerStateTest {
assertSame(teardown, thrown.getSuppressed()[0]);
}
private static int liveCull(TesrMaterial material) {
return material.isNoCull() ? DrawState.DISABLED : DrawState.liveCull();
}
static RenderLayer capturedLayer(TesrMaterial material) {
return Reflect.invoke(ModelPartBatcher.INSTANCE, "layerFor",
new Class<?>[] { ResourceLocation.class, TesrMaterial.class, PassOverride.class, float.class, float.class, int.class, int.class, boolean.class },
null, material, PassOverride.NONE, 0f, 0f, ShaderGlint.NO_TINT, liveCull(material), DrawState.liveLit(material));
}
private static RenderLayer queuedLayer(TesrMaterial material) {
return Reflect.invoke(TesrBatchRenderer.INSTANCE, "layerFor",
new Class<?>[] { ResourceLocation.class, TesrMaterial.class, PassOverride.class, float.class, float.class, int.class, boolean.class },
null, material, PassOverride.NONE, 0f, 0f, liveCull(material), DrawState.liveLit(material));
}
private static void drawInBatch(RenderLayer layer, boolean culled) {
final int depth = GLStateManager.pushState(StateSet.BATCH);
layer.startDrawing();
assertEquals(culled, GL11.glIsEnabled(GL11.GL_CULL_FACE));
layer.endDrawing();
GLStateManager.popStateTo(depth);
}
@Test
void queuedCullingSplitsBothLayerCachesAndRestoresTheDrawCaller() {
for (TesrMaterial material : new TesrMaterial[] { EntityMaterials.DROPPED_ITEM_CUTOUT, EntityMaterials.GLINT }) {
GLStateManager.disableCull();
final RenderLayer twoSided = capturedLayer(material);
GLStateManager.enableCull();
final RenderLayer culled = capturedLayer(material);
assertNotSame(twoSided, culled, "the last-layer shortcut must include culling");
GLStateManager.disableCull();
assertSame(twoSided, capturedLayer(material), "the layer map must retain both states");
}
GLStateManager.disableCull();
final RenderLayer twoSided = capturedLayer(EntityMaterials.DROPPED_ITEM_CUTOUT);
GLStateManager.enableCull();
final RenderLayer culled = capturedLayer(EntityMaterials.DROPPED_ITEM_CUTOUT);
twoSided.startDrawing();
assertFalse(GL11.glIsEnabled(GL11.GL_CULL_FACE));
assertTrue(GL11.glIsEnabled(GL11.GL_DEPTH_TEST));
assertTrue(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK));
twoSided.endDrawing();
GLStateManager.enableCull();
drawInBatch(twoSided, false);
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE));
GLStateManager.disableCull();
drawInBatch(culled, true);
assertFalse(GL11.glIsEnabled(GL11.GL_CULL_FACE));
}
@Test
void queuedTemplateCullingSurvivesCacheHitsAndRestoresTheCaller() {
final TesrMaterial material = TesrMaterial.CURRENT_STATE;
GLStateManager.disableCull();
final RenderLayer twoSided = queuedLayer(material);
GLStateManager.enableCull();
final RenderLayer culled = queuedLayer(material);
assertNotSame(twoSided, culled);
GLStateManager.disableCull();
assertSame(twoSided, queuedLayer(material));
drawInBatch(culled, true);
assertFalse(GL11.glIsEnabled(GL11.GL_CULL_FACE));
GLStateManager.enableCull();
drawInBatch(twoSided, false);
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE));
final RenderLayer forcedTwoSided = queuedLayer(TesrMaterial.builder().noCull().build());
drawInBatch(forcedTwoSided, false);
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE));
}
@Test
void glintTintSlotsSplitLayers() {
assertSame(glint(0), glint(0), "the same slot must reuse the layer");
@@ -0,0 +1,81 @@
package com.gtnewhorizons.angelica.utils;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import com.gtnewhorizons.angelica.mixins.interfaces.IPatchedTextureAtlasSprite;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@GLCoreTest
class DisplayListTextureAnimationTest {
@Test
void cachedMeshMarksEachCapturedSpriteOncePerUse() {
final IPatchedTextureAtlasSprite sprite = mock(IPatchedTextureAtlasSprite.class);
final IPatchedTextureAtlasSprite overlay = mock(IPatchedTextureAtlasSprite.class);
final AnimationsRenderUtils.SpriteCapture capture = AnimationsRenderUtils.captureSprites();
try (capture) {
for (int vertex = 0; vertex < 1000; vertex++) AnimationsRenderUtils.recordSpriteUsage(sprite);
AnimationsRenderUtils.recordSpriteUsage(overlay);
}
for (int frame = 0; frame < 3; frame++) {
clearInvocations(sprite, overlay);
capture.markUsed();
verify(sprite).angelica$markNeedsAnimationUpdate();
verify(overlay).angelica$markNeedsAnimationUpdate();
}
}
@Test
void nestedCaptureRestoresItsParentEvenWhenRenderingThrows() {
final IPatchedTextureAtlasSprite nested = mock(IPatchedTextureAtlasSprite.class);
final IPatchedTextureAtlasSprite outer = mock(IPatchedTextureAtlasSprite.class);
final IPatchedTextureAtlasSprite unrelated = mock(IPatchedTextureAtlasSprite.class);
final AnimationsRenderUtils.SpriteCapture capture = AnimationsRenderUtils.captureSprites();
try (capture) {
assertThrows(IllegalStateException.class, () -> {
try (var child = AnimationsRenderUtils.captureSprites()) {
AnimationsRenderUtils.recordSpriteUsage(nested);
throw new IllegalStateException("renderer failed");
}
});
AnimationsRenderUtils.recordSpriteUsage(outer);
}
AnimationsRenderUtils.recordSpriteUsage(unrelated);
capture.markUsed();
verify(nested).angelica$markNeedsAnimationUpdate();
verify(outer).angelica$markNeedsAnimationUpdate();
verifyNoInteractions(unrelated);
}
@Test
void cachedDrawMarksItsSpriteEveryFrame() {
final IPatchedTextureAtlasSprite sprite = mock(IPatchedTextureAtlasSprite.class);
for (int mode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
final int list = GLStateManager.glGenLists(1);
try {
GLStateManager.glNewList(list, mode);
for (int vertex = 0; vertex < 1000; vertex++) AnimationsRenderUtils.recordSpriteUsage(sprite);
GLStateManager.glEndList();
assertEquals(1, DisplayListManager.getDisplayList(list).getCommandCounts().get(GLCommand.COMPLEX_REF),
"repeated UV accesses should record only one notification");
for (int frame = 0; frame < 3; frame++) {
clearInvocations(sprite);
GLStateManager.glCallList(list);
verify(sprite).angelica$markNeedsAnimationUpdate();
}
} finally {
if (DisplayListManager.isRecording()) GLStateManager.glEndList();
GLStateManager.glDeleteLists(list, 1);
}
}
}
}
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.rendering.tesr.DrawState;
import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer;
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
import net.coderbot.iris.layer.PassOverride;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.joml.Vector3f;
@@ -28,6 +29,44 @@ class SegmentedBufferBuilderTest {
private static final TesrMaterial OPAQUE_MATERIAL = TesrMaterial.CURRENT_STATE;
private static final TesrMaterial TRANSLUCENT_MATERIAL = TesrMaterial.builder().translucent().build();
@Test
void segmentsSnapshotAllIdsAndTintAtQueueTime() {
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
final SegmentedBufferBuilder builder = new SegmentedBufferBuilder();
final RenderLayer layer = layer("snapshot", TransparencyType.OPAQUE);
for (int i = 0; i < 3; i++) {
state.setCurrentEntityAndItem(11 + i, 13);
state.setCurrentBlockEntity(12);
state.setCurrentEntityColor(i / 2f, 0, 0, 1);
builder.begin(layer, 12);
builder.addQuad(quad());
builder.begin(layer, 12);
builder.addQuad(quad());
}
state.setCurrentEntityAndItem(90, 91);
state.setCurrentBlockEntity(92);
state.setCurrentEntityColor(0, 1, 0, 0);
final var segments = builder.getSegments();
assertEquals(3, segments.size(), "adjacent equal states still merge");
for (int i = 0; i < 3; i++) {
final var segment = segments.get(i);
assertEquals(11 + i, segment.getEntityId());
assertEquals(12, segment.getRenderedBlockEntityId());
assertEquals(13, segment.getItemId());
assertEquals(AngelicaBufferSource.packAbgr(i / 2f, 0, 0, 1), segment.getEntityColor());
assertEquals(8, segment.getVertexCount());
}
} finally {
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
static RenderLayer layer(String name, TransparencyType transparencyType) {
final TesrMaterial material = transparencyType == TransparencyType.OPAQUE ? OPAQUE_MATERIAL : TRANSLUCENT_MATERIAL;
return RenderLayer.tesr(new ResourceLocation("angelicatest", name), material, PassOverride.NONE, 0f, 0f, ShaderGlint.NO_TINT, DrawState.DISABLED, false);
@@ -0,0 +1,177 @@
package net.coderbot.iris.gl.program;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.iris.IrisGLSMBridge;
import net.coderbot.iris.Iris;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
import net.coderbot.iris.pipeline.PipelineManager;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import org.junit.jupiter.api.Test;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.AdditionalAnswers.delegatesTo;
import static org.mockito.Mockito.*;
@GLCoreTest
class ProgramBindingRegressionTest {
@Test
void fallbackInitializationExecutesImmediatelyWithoutRecordingProgramBinds() {
final String vertex = ShaderLoader.getShaderSource("angelica:fontFilter.vsh");
final String fragment = ShaderLoader.getShaderSource("angelica:fontFilter.fsh");
final int caller = linkedProgram();
try {
for (int mode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
final int list = GLStateManager.glGenLists(1);
Program initialized = null;
GLStateManager.glUseProgram(caller);
try {
GLStateManager.glNewList(list, mode);
initialized = ProgramBuilder.begin("fallback-test", vertex, null, fragment, ImmutableSet.of(0)).build();
assertEquals(mode == GL11.GL_COMPILE ? DisplayListManager.RecordMode.COMPILE
: DisplayListManager.RecordMode.COMPILE_AND_EXECUTE, DisplayListManager.getRecordMode());
assertEquals(caller, GLStateManager.getActiveProgram());
assertEquals(caller, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM));
GLStateManager.glEndList();
final int location = GLStateManager.glGetUniformLocation(initialized.getProgramId(), "alphaTestRef");
assertNotEquals(-1, location);
final FloatBuffer value = BufferUtils.createFloatBuffer(4);
GLStateManager.glGetUniform(initialized.getProgramId(), location, value);
assertEquals(-1f, value.get(0));
assertEquals(0, DisplayListManager.getDisplayList(list).getCommandCounts().get(GLCommand.USE_PROGRAM));
GLStateManager.glCallList(list);
assertEquals(caller, GLStateManager.getActiveProgram());
} finally {
if (DisplayListManager.isRecording()) GLStateManager.glEndList();
GLStateManager.glDeleteLists(list, 1);
if (initialized != null) initialized.destroy();
}
}
} finally {
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(caller);
}
}
@Test
void deletionBookkeepingRetainsOnlyDeferredDeletes() {
final int bound = linkedProgram();
GLStateManager.glUseProgram(bound);
GLStateManager.glDeleteProgram(bound);
assertTrue(GLStateManager.isProgramPendingDeletion(bound));
final int other = linkedProgram();
GLStateManager.glUseProgram(other);
for (int i = 0; i < 8; i++) {
final int unused = GLStateManager.glCreateProgram();
GLStateManager.glDeleteProgram(unused);
assertFalse(GLStateManager.isProgramPendingDeletion(unused));
assertFalse(GLStateManager.isProgramPendingDeletion(bound));
}
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(other);
}
private static int linkedProgram() {
int vertex = GLStateManager.glCreateShader(GL20.GL_VERTEX_SHADER);
int fragment = GLStateManager.glCreateShader(GL20.GL_FRAGMENT_SHADER);
GLStateManager.glShaderSource(vertex, "#version 330 core\nvoid main() { gl_Position = vec4(0); }");
GLStateManager.glShaderSource(fragment, "#version 330 core\nout vec4 color; void main() { color = vec4(1); }");
GLStateManager.glCompileShader(vertex);
GLStateManager.glCompileShader(fragment);
int program = GLStateManager.glCreateProgram();
GLStateManager.glAttachShader(program, vertex);
GLStateManager.glAttachShader(program, fragment);
GLStateManager.glLinkProgram(program);
GLStateManager.glDeleteShader(vertex);
GLStateManager.glDeleteShader(fragment);
assertEquals(GL11.GL_TRUE, GLStateManager.glGetProgrami(program, GL20.GL_LINK_STATUS));
return program;
}
@Test
void managedSwitchesUpdateOnceAndForeignBindsStillUpdate() {
IrisGLSMBridge.register();
PipelineManager manager = Iris.getPipelineManager();
WorldRenderingPipeline previous = manager.getPipelineNullable();
boolean enabled = Iris.enabled;
int id = linkedProgram();
int other = linkedProgram();
ProgramUniforms uniforms = mock(ProgramUniforms.class);
Program program = new Program(id, uniforms, mock(ProgramSamplers.class), mock(ProgramImages.class));
DeferredWorldRenderingPipeline pipeline = mock(DeferredWorldRenderingPipeline.class);
DeferredWorldRenderingPipeline.Pass pass = Reflect.allocate(DeferredWorldRenderingPipeline.Pass.class);
Reflect.set(pass, "program", program);
when(pipeline.getActivePassProgram()).thenReturn(pass);
Reflect.set(manager, "pipeline", pipeline);
Reflect.setStaticFinal(Iris.class, "enabled", boolean.class, true);
try {
for (int i = 0; i < 3; i++) {
GLStateManager.glUseProgram(other);
program.use();
verify(uniforms, times(i + 1)).update();
assertFalse(Program.isManagedBind(id));
}
program.use(); // Cached binds must still update dynamic uniforms.
verify(uniforms, times(4)).update();
GLStateManager.glUseProgram(other);
GLStateManager.glUseProgram(id);
verify(uniforms, times(5)).update();
} finally {
Reflect.setStaticFinal(Iris.class, "enabled", boolean.class, enabled);
Reflect.set(manager, "pipeline", previous);
GLStateManager.glUseProgram(0);
program.destroy();
GLStateManager.glDeleteProgram(other);
}
}
@Test
void replayCachesValidityAndRevalidatesDeletionIncludingBoundPrograms() {
int id = linkedProgram();
int other = linkedProgram();
int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glUseProgram(id);
GLStateManager.glEndList();
RenderBackend original = BackendManager.RENDER_BACKEND;
RenderBackend backend = mock(RenderBackend.class, delegatesTo(original));
Reflect.setStaticFinal(BackendManager.class, "RENDER_BACKEND", RenderBackend.class, backend);
try {
for (int i = 0; i < 5; i++) GLStateManager.glCallList(list);
verify(backend, times(1)).isProgram(id);
assertEquals(id, GLStateManager.getActiveProgram());
GLStateManager.glDeleteProgram(id);
GLStateManager.glCallList(list); // Still valid while bound, but must not be cached.
assertEquals(id, GLStateManager.getActiveProgram());
GLStateManager.glUseProgram(other); // Completes deferred deletion.
GLStateManager.glCallList(list);
assertEquals(0, GLStateManager.getActiveProgram());
verify(backend, times(4)).isProgram(id);
GLStateManager.glCallList(list);
verify(backend, times(4)).isProgram(id);
GLStateManager.glDeleteProgram(other);
GLStateManager.glCallList(list);
verify(backend, times(5)).isProgram(id);
} finally {
Reflect.setStaticFinal(BackendManager.class, "RENDER_BACKEND", RenderBackend.class, original);
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteLists(list, 1);
if (GLStateManager.glIsProgram(id)) GLStateManager.glDeleteProgram(id);
if (GLStateManager.glIsProgram(other)) GLStateManager.glDeleteProgram(other);
}
}
}
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff. Show more