Set entity, block entity and item IDs per vertex (#2265)

This commit is contained in:
Eclipse-Sol authored and GitHub committed 2026-10-16 21:47:40 -07:00
1 parent cfc6e12e00
commit ab3bf17696
26 files changed
+720 -95

No files matched your search

@@ -34,6 +34,7 @@ import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
@@ -104,6 +105,8 @@ public class GLContextState {
public boolean dirtyNormalAttrib;
public boolean dirtyTexCoordAttrib;
public boolean dirtyLightmapAttrib = true;
public long entityIds = InstancedAttribs.DEFAULT_ENTITY_IDS;
public boolean dirtyEntityIdsAttrib = true;
public boolean unit23TexCoordSetDuringDraw = false;
public int maxBoundTextureUnit = 0;
public int maxBoundImageUnit = 0;
@@ -12,6 +12,7 @@ import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
@@ -25,6 +26,7 @@ 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.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
@@ -68,6 +70,7 @@ import com.gtnewhorizons.angelica.glsm.states.ImageUnitBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.streaming.LineQuads;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorEntityIds;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
@@ -396,6 +399,26 @@ public class GLStateManager {
}
}
public static void setEntityIds(int entity, int blockEntity, int item) {
if (!isMainThread()) return;
final GLContextState glCtx = ctx();
final long ids = InstancedAttribs.packEntityInfo(entity, blockEntity, item);
if (ids == glCtx.entityIds) return;
TessellatorEntityIds.onIdsChanged(glCtx.entityIds, ids);
glCtx.entityIds = ids;
glCtx.dirtyEntityIdsAttrib = true;
}
public static void flushEntityIds(GLContextState glCtx) {
if (!glCtx.dirtyEntityIdsAttrib || glCtx.activeProgram == 0) return;
final ImmediateExtendedAttribHandler handler = GLSMHooks.immediateExtendedHandler;
if (handler == null || !handler.wantsEntityIds()) return;
final long ids = glCtx.entityIds;
RENDER_BACKEND.vertexAttrib4f(ImmediateExtendedAttribHandler.LOC_ENTITY_IDS,
InstancedAttribs.normalizedId(ids, 0), InstancedAttribs.normalizedId(ids, 16), InstancedAttribs.normalizedId(ids, 32), 0.0f);
glCtx.dirtyEntityIdsAttrib = false;
}
public static int getMaxBoundTextureUnit() {
return ctx().maxBoundTextureUnit;
@@ -4823,6 +4846,7 @@ public class GLStateManager {
glCtx.dirtyNormalAttrib = true;
glCtx.dirtyTexCoordAttrib = true;
glCtx.dirtyLightmapAttrib = true;
glCtx.dirtyEntityIdsAttrib = true;
}
private static void applyPolygonOffset(GLContextState glCtx) {
@@ -25,6 +25,8 @@ public final class InstancedAttribs {
public static final int OFFSET_LIGHTMAP = HEAD_SIZE;
public static final int STRIDE = 72;
public static final long DEFAULT_ENTITY_IDS = packEntityInfo(-1, 0, -1);
public static void writeHead(long ptr, float[] colMajor, int off, int colorABGR, int overlayABGR, long entityInfo) {
writeRows(ptr, colMajor, off);
writeTail(ptr, colorABGR, overlayABGR, entityInfo);
@@ -60,13 +62,21 @@ public final class InstancedAttribs {
return id == 0xFFFF ? -1 : id;
}
public static float normalizedId(long info, int shift) {
return ((info >>> shift) & 0xFFFF) / 65535.0f;
}
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_UNSIGNED_SHORT, stride, base + OFFSET_ENTITY);
pointEntityIds(LOC_ENTITY, stride, base + OFFSET_ENTITY);
}
public static void pointEntityIds(int location, int stride, long offset) {
GLStateManager.glVertexAttribPointer(location, 4, GL11.GL_UNSIGNED_SHORT, true, stride, offset);
}
public static void pointTemplate(long base) {
@@ -139,6 +139,7 @@ public final class ShaderManager {
GLSMHooks.resolvePendingProgram();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
GLStateManager.flushEntityIds(glCtx);
// Handle FFP & Iris uniforms
final int currentProgramId = glCtx.activeProgram;
@@ -3,15 +3,18 @@ package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import org.lwjgl.opengl.GL11;
public interface ImmediateExtendedAttribHandler {
int EXT_STRIDE = 12;
int ENTITY_IDS_STRIDE = 8;
int LOC_MC_ENTITY = 11;
int LOC_MID_TEX = 12;
int LOC_TANGENT = 13;
int LOC_MIDBLOCK = 14;
int LOC_ENTITY_IDS = 15;
int RAW_NORMAL_INDEX = 6;
@@ -19,6 +22,8 @@ public interface ImmediateExtendedAttribHandler {
boolean wantsTangent();
boolean wantsEntityIds();
boolean wantsExtendedCapture();
boolean wantsFaceNormals();
@@ -65,4 +70,9 @@ public interface ImmediateExtendedAttribHandler {
GLStateManager.glEnableVertexAttribArray(LOC_TANGENT);
GLStateManager.glVertexAttribPointer(LOC_TANGENT, 4, GL11.GL_BYTE, true, stride, baseOffset + 8);
}
static void setupEntityIdsPointer(long offset, int stride) {
GLStateManager.glEnableVertexAttribArray(LOC_ENTITY_IDS);
InstancedAttribs.pointEntityIds(LOC_ENTITY_IDS, stride, offset);
}
}
@@ -5,12 +5,25 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
public final class StreamingVaos {
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE;
static final int MID_TEX_AND_TANGENT = 1;
static final int ENTITY_IDS = 2;
private static final int EXTENDED_LAYOUTS = MID_TEX_AND_TANGENT | ENTITY_IDS;
final int[] persistentVAOs = new int[FORMAT_COUNT];
final int[] orphanVAOs = new int[FORMAT_COUNT];
final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
// An extended draw always carries at least one layout, so row 0 is layout 1
private final int[][] extendedPersistentVAOs = new int[EXTENDED_LAYOUTS][FORMAT_COUNT];
private final int[][] extendedOrphanVAOs = new int[EXTENDED_LAYOUTS][FORMAT_COUNT];
final int[] lineQuadPersistentVAOs = new int[FORMAT_COUNT];
final int[] lineQuadOrphanVAOs = new int[FORMAT_COUNT];
int[] extendedPersistent(int layout) {
return extendedPersistentVAOs[layout - 1];
}
int[] extendedOrphan(int layout) {
return extendedOrphanVAOs[layout - 1];
}
}
@@ -0,0 +1,78 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import net.minecraft.client.renderer.Tessellator;
import java.util.Arrays;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
/**
* Entity, block entity, and item ids of the vertices in the main Tessellator batch.
*/
public final class TessellatorEntityIds {
private static Tessellator owner;
private static int runCount;
private static int[] runStarts = new int[8];
private static long[] runIds = new long[8];
private TessellatorEntityIds() {}
public static void onIdsChanged(long previous, long next) {
final Tessellator tess = Tessellator.instance;
if (tess == null || !tess.isDrawing || tess.vertexCount == 0) return;
final int start = tess.vertexCount;
if (owner != tess) {
owner = tess;
runCount = 0;
}
if (runCount == 0) addRun(0, previous);
if (runStarts[runCount - 1] == start) {
runIds[runCount - 1] = next;
if (runCount > 1 && runIds[runCount - 2] == next) runCount--;
} else {
addRun(start, next);
}
}
public static void onReset(Tessellator tess) {
if (tess != owner) return;
owner = null;
runCount = 0;
}
public static boolean hasIdChanges(Tessellator tess) {
return tess == owner && runCount > 1;
}
static int runsFor(Tessellator tess, int vertexCount) {
if (tess != owner) return 0;
int runs = runCount;
while (runs > 0 && runStarts[runs - 1] >= vertexCount) runs--;
return runs;
}
static long firstRunIds() {
return runIds[0];
}
static void write(long dst, int stride, int vertexCount, int runs) {
for (int run = 0; run < runs; run++) {
final int end = run + 1 < runs ? runStarts[run + 1] : vertexCount;
final long ids = runIds[run];
for (int vertex = runStarts[run]; vertex < end; vertex++) {
memPutLong(dst + (long) vertex * stride, ids);
}
}
}
private static void addRun(int start, long ids) {
if (runCount == runStarts.length) {
runStarts = Arrays.copyOf(runStarts, runCount * 2);
runIds = Arrays.copyOf(runIds, runCount * 2);
}
runStarts[runCount] = start;
runIds[runCount] = ids;
runCount++;
}
}
@@ -130,20 +130,34 @@ public class TessellatorStreamingDrawer {
final int extPrim = extHandler == null ? 0 : ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount);
final boolean wantsExt = extHandler != null && extHandler.wantsExtended();
final boolean doExt = extPrim != 0 && tess.hasTexture && wantsExt;
if (doExt) {
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, effectiveFlags, format, combinedStride, tess.drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} else {
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
final int idRuns = TessellatorEntityIds.runsFor(tess, vertexCount);
final boolean doIds = idRuns > 1 && extHandler != null && extHandler.wantsEntityIds() && !LineQuads.isLineMode(tess.drawMode);
final GLContextState swappedCtx = idRuns >= 1 && !doIds ? GLStateManager.ctx() : null;
final long restoreIds = swappedCtx != null ? swapEntityIds(swappedCtx, TessellatorEntityIds.firstRunIds()) : 0L;
try {
if (doExt || doIds) {
final int layout = (doExt ? StreamingVaos.MID_TEX_AND_TANGENT : 0) | (doIds ? StreamingVaos.ENTITY_IDS : 0);
final int combinedStride = combinedStride(format, layout);
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
long extAddress = extScratchAddress + vertexSize;
if (doExt) {
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extAddress, combinedStride);
extAddress += ImmediateExtendedAttribHandler.EXT_STRIDE;
}
if (doIds) TessellatorEntityIds.write(extAddress, combinedStride, vertexCount, idRuns);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, effectiveFlags, format, layout, combinedStride, tess.drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} else {
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
}
} finally {
if (swappedCtx != null) swapEntityIds(swappedCtx, restoreIds);
}
} finally {
if (locked) GLStateManager.releaseDrawLock();
@@ -253,7 +267,7 @@ public class TessellatorStreamingDrawer {
final long srcBase = memAddress0(packed) + packed.position();
final int texOffset = ImmediateExtendedAttribHandler.texOffset(format);
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedStride = combinedStride(format, StreamingVaos.MID_TEX_AND_TANGENT);
final int combinedBytes = vertexCount * combinedStride;
final boolean locked = GLStateManager.acquireDrawLock();
try {
@@ -262,7 +276,7 @@ public class TessellatorStreamingDrawer {
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
uploadAndDrawExtended(extScratch, flags, format, StreamingVaos.MID_TEX_AND_TANGENT, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} finally {
if (locked) GLStateManager.releaseDrawLock();
@@ -458,11 +472,11 @@ public class TessellatorStreamingDrawer {
}
}
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int layout, int combinedStride, int drawMode, int vertexCount) {
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos.persistentVAOs, vaos.orphanVAOs, flags, format);
ensureExtendedVAOs(vaos, flags, format);
ensureExtendedVAOs(vaos, flags, format, layout);
int firstVertex = -1;
@@ -471,9 +485,9 @@ public class TessellatorStreamingDrawer {
}
if (firstVertex >= 0) {
GLStateManager.glBindVertexArray(vaos.extendedPersistentVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedPersistent(layout)[flags]);
} else {
GLStateManager.glBindVertexArray(vaos.extendedOrphanVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedOrphan(layout)[flags]);
orphanBuffers[flags].upload(combined);
firstVertex = 0;
}
@@ -482,14 +496,14 @@ public class TessellatorStreamingDrawer {
GLStateManager.glBindVertexArray(0);
}
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format) {
final int[] extendedOrphanVAOs = vaos.extendedOrphanVAOs;
final int[] extendedPersistentVAOs = vaos.extendedPersistentVAOs;
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format, int layout) {
final int[] extendedOrphanVAOs = vaos.extendedOrphan(layout);
final int[] extendedPersistentVAOs = vaos.extendedPersistent(layout);
if (extendedOrphanVAOs[flags] == 0) {
extendedOrphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
setupExtendedBufferState(format);
setupExtendedBufferState(format, layout);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
@@ -498,20 +512,42 @@ public class TessellatorStreamingDrawer {
extendedPersistentVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
setupExtendedBufferState(format);
setupExtendedBufferState(format, layout);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
}
private static void setupExtendedBufferState(VertexFormat format) {
final int combinedStride = format.getVertexSize() + ImmediateExtendedAttribHandler.EXT_STRIDE;
private static long swapEntityIds(GLContextState glCtx, long ids) {
final long previous = glCtx.entityIds;
if (previous != ids) {
glCtx.entityIds = ids;
glCtx.dirtyEntityIdsAttrib = true;
}
return previous;
}
private static int combinedStride(VertexFormat format, int layout) {
int stride = format.getVertexSize();
if ((layout & StreamingVaos.MID_TEX_AND_TANGENT) != 0) stride += ImmediateExtendedAttribHandler.EXT_STRIDE;
if ((layout & StreamingVaos.ENTITY_IDS) != 0) stride += ImmediateExtendedAttribHandler.ENTITY_IDS_STRIDE;
return stride;
}
private static void setupExtendedBufferState(VertexFormat format, int layout) {
final int combinedStride = combinedStride(format, layout);
long offset = 0;
for (final VertexFormatElement element : format.elementsArray) {
element.setupBufferState(offset, combinedStride);
offset += element.getByteSize();
}
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
if ((layout & StreamingVaos.MID_TEX_AND_TANGENT) != 0) {
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
offset += ImmediateExtendedAttribHandler.EXT_STRIDE;
}
if ((layout & StreamingVaos.ENTITY_IDS) != 0) {
ImmediateExtendedAttribHandler.setupEntityIdsPointer(offset, combinedStride);
}
}
private static void ensureExtScratch(int requiredBytes) {
@@ -85,7 +85,7 @@ class InstancedFFPDrawGLTest {
GLSMHooks.pendingProgramSelection = null;
GLSMHooks.blendHandler = null;
log.clear();
FfpFixture.IntegerInstances.delete();
FfpFixture.EntityIdInstances.delete();
FfpFixture.resetFfpState();
CubeParityFixture.disableDirectionalLight();
CubeParityFixture.deleteResources();
@@ -196,14 +196,14 @@ class InstancedFFPDrawGLTest {
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
FfpFixture.IntegerInstances.build(0.66f);
FfpFixture.EntityIdInstances.build(0.66f);
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
FfpFixture.IntegerInstances.draw();
FfpFixture.EntityIdInstances.draw();
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced ivec4 draw must not raise a GL error");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced entity id draw must not raise a GL error");
assertPixel(51, 50, 255, 0, 0, "entity -1 must render red");
assertPixel(150, 50, 0, 255, 0, "entity 7 must render green");
@@ -0,0 +1,69 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.glsm.EntityIdsOnlyHandler;
import com.gtnewhorizons.angelica.glsm.GLCompatTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@GLCompatTest
class TessellatorEntityIdsGLTest {
private static final int[] GREEN = { 0, 255, 0, 255 };
private static final int[] BLUE = { 0, 0, 255, 255 };
private ImmediateExtendedAttribHandler previousHandler;
private int program;
@BeforeEach
void setUp() {
previousHandler = EntityIdsOnlyHandler.install();
program = GlTestSupport.program(TessellatorEntityIdsFixture.VERTEX, TessellatorEntityIdsFixture.FRAGMENT);
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
GLStateManager.glViewport(0, 0, 8, 8);
GLStateManager.glUseProgram(program);
GLStateManager.glClearColor(0, 0, 0, 1);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
}
@AfterEach
void tearDown() {
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(program);
GLStateManager.glPopAttrib();
GLSMHooks.immediateExtendedHandler = previousHandler;
}
@Test
void idsChangedMidBatchApplyToTheVerticesAddedAfterTheChange() {
TessellatorEntityIdsFixture.drawIdsChangedMidBatch();
assertColor(1, GREEN, "left quad was added under item 7");
assertColor(6, BLUE, "right quad was added under item 50020");
}
@Test
void idsChangedAfterTheLastVertexLeaveTheBatchAlone() {
TessellatorEntityIdsFixture.drawIdsChangedAfterTheLastVertex();
assertColor(1, GREEN, "every vertex was added under item 7");
assertColor(6, GREEN, "every vertex was added under item 7");
}
private static void assertColor(int x, int[] expected, String message) {
final int[] pixel = FfpFixture.readPixel(x, 4);
assertArrayEquals(expected, pixel, message + ", got " + Arrays.toString(pixel));
}
}
@@ -0,0 +1,24 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
public final class EntityIdsOnlyHandler implements ImmediateExtendedAttribHandler {
public static ImmediateExtendedAttribHandler install() {
final ImmediateExtendedAttribHandler previous = GLSMHooks.immediateExtendedHandler;
GLSMHooks.immediateExtendedHandler = new EntityIdsOnlyHandler();
GLStateManager.ctx().dirtyEntityIdsAttrib = true;
return previous;
}
@Override public boolean wantsExtended() { return false; }
@Override public boolean wantsTangent() { return false; }
@Override public boolean wantsEntityIds() { return GLStateManager.getActiveProgram() != 0; }
@Override public boolean wantsExtendedCapture() { return false; }
@Override public boolean wantsFaceNormals() { return false; }
@Override public void writeFaceNormals(int[] rawBuffer, int vertexCount) {}
@Override public void build(int[] rawBuffer, int vertexCount, int vertsPerPrim, int normalIntIndex, long dstAddr, int dstStride) {}
@Override public void buildPacked(long srcBase, int stride, int posOffset, int texOffset, int normalOffset,
int vertexCount, int vertsPerPrim, long dstAddr, int dstStride) {}
}
@@ -21,7 +21,7 @@ public final class FfpFixture {
private FfpFixture() {}
public static final class IntegerInstances {
public static final class EntityIdInstances {
private static final int INSTANCES = 3;
@@ -31,16 +31,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 uvec4 iris_Entity;
layout(location = 11) in vec4 iris_InstEntity;
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 == 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); }
int entity = int(round(iris_InstEntity.x * 65535.0));
if (entity == 65535) { v_Color = vec3(1.0, 0.0, 0.0); }
else if (entity == 7) { v_Color = vec3(0.0, 1.0, 0.0); }
else if (entity == 50020) { v_Color = vec3(0.0, 0.0, 1.0); }
else { v_Color = vec3(1.0, 0.0, 1.0); }
}
""";
@@ -59,7 +60,7 @@ public final class FfpFixture {
private static int templateVbo;
private static int instanceVbo;
private IntegerInstances() {}
private EntityIdInstances() {}
public static void build(float spacing) {
program = buildProgram();
@@ -0,0 +1,112 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import net.minecraft.client.renderer.Tessellator;
import org.lwjgl.opengl.GL11;
public final class TessellatorEntityIdsFixture {
public static final String VERTEX = """
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = %d) in vec4 iris_Entity;
flat out vec3 v_Color;
void main() {
gl_Position = vec4(a_Position, 1.0);
int item = int(round(iris_Entity.z * 65535.0));
v_Color = item == 7 ? vec3(0.0, 1.0, 0.0) : item == 50020 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
}
""".formatted(ImmediateExtendedAttribHandler.LOC_ENTITY_IDS);
public static final String FRAGMENT = """
#version 330 core
flat in vec3 v_Color;
out vec4 fragColor;
void main() { fragColor = vec4(v_Color, 1.0); }
""";
private TessellatorEntityIdsFixture() {}
public static void drawIdsChangedMidBatch() {
draw(() -> {
GLStateManager.setEntityIds(-1, 0, 7);
startQuads();
addQuad(-1.0f, 0.0f);
GLStateManager.setEntityIds(-1, 0, 50020);
addQuad(0.0f, 1.0f);
GLStateManager.setEntityIds(-1, 0, -1);
});
}
public static void drawIdsChangedAfterTheLastVertex() {
draw(() -> {
GLStateManager.setEntityIds(-1, 0, 7);
startQuads();
addQuad(-1.0f, 1.0f);
GLStateManager.setEntityIds(-1, 0, 50020);
});
}
private static void draw(Runnable fill) {
final Tessellator tess = Tessellator.instance;
final int[] savedRawBuffer = tess.rawBuffer;
final int savedRawBufferSize = tess.rawBufferSize;
final int savedRawBufferIndex = tess.rawBufferIndex;
final int savedVertexCount = tess.vertexCount;
final int savedDrawMode = tess.drawMode;
final boolean savedIsDrawing = tess.isDrawing;
final boolean savedHasTexture = tess.hasTexture;
final boolean savedHasColor = tess.hasColor;
final boolean savedHasNormals = tess.hasNormals;
final boolean savedHasBrightness = tess.hasBrightness;
try {
fill.run();
TessellatorStreamingDrawer.draw(tess);
} finally {
TessellatorEntityIds.onReset(tess);
GLStateManager.setEntityIds(-1, 0, -1);
tess.rawBuffer = savedRawBuffer;
tess.rawBufferSize = savedRawBufferSize;
tess.rawBufferIndex = savedRawBufferIndex;
tess.vertexCount = savedVertexCount;
tess.drawMode = savedDrawMode;
tess.isDrawing = savedIsDrawing;
tess.hasTexture = savedHasTexture;
tess.hasColor = savedHasColor;
tess.hasNormals = savedHasNormals;
tess.hasBrightness = savedHasBrightness;
}
}
private static void startQuads() {
final Tessellator tess = Tessellator.instance;
tess.rawBuffer = new int[64];
tess.rawBufferSize = tess.rawBuffer.length;
tess.rawBufferIndex = 0;
tess.vertexCount = 0;
tess.drawMode = GL11.GL_QUADS;
tess.hasTexture = false;
tess.hasColor = false;
tess.hasNormals = false;
tess.hasBrightness = false;
tess.isDrawing = true;
}
private static void addQuad(float left, float right) {
addVertex(left, -1.0f);
addVertex(right, -1.0f);
addVertex(right, 1.0f);
addVertex(left, 1.0f);
}
private static void addVertex(float x, float y) {
final Tessellator tess = Tessellator.instance;
final int base = tess.rawBufferIndex;
tess.rawBuffer[base] = Float.floatToRawIntBits(x);
tess.rawBuffer[base + 1] = Float.floatToRawIntBits(y);
tess.rawBuffer[base + 2] = Float.floatToRawIntBits(0.0f);
tess.rawBufferIndex += 8;
tess.vertexCount++;
}
}
@@ -0,0 +1,66 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
import com.gtnewhorizons.angelica.glsm.ffp.CubeInstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL20;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlAttribBindingTest {
private static final String INSTANCED_VERTEX = """
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = %d) in vec4 iris_InstEntity;
layout(location = %d) in vec4 iris_CubeTex;
void main() { gl_Position = vec4(a_Position, 1.0) + iris_InstEntity + iris_CubeTex; }
""".formatted(InstancedAttribs.LOC_ENTITY, CubeInstancedAttribs.LOC_CUBE_TEX);
private static final String IMMEDIATE_VERTEX = """
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = %d) in vec4 mc_Entity;
layout(location = %d) in vec4 iris_Entity;
void main() { gl_Position = vec4(a_Position, 1.0) + mc_Entity + iris_Entity; }
""".formatted(ImmediateExtendedAttribHandler.LOC_MC_ENTITY, ImmediateExtendedAttribHandler.LOC_ENTITY_IDS);
@Test
void instancedEntityIdsStayAtTheInstanceSlot() {
final int program = linkWithIrisBindings(INSTANCED_VERTEX);
try {
assertEquals(InstancedAttribs.LOC_ENTITY, GLStateManager.glGetAttribLocation(program, "iris_InstEntity"));
assertEquals(CubeInstancedAttribs.LOC_CUBE_TEX, GLStateManager.glGetAttribLocation(program, "iris_CubeTex"));
} finally {
GLStateManager.glDeleteProgram(program);
}
}
@Test
void immediateEntityIdsDoNotMoveOntoMcEntity() {
final int program = linkWithIrisBindings(IMMEDIATE_VERTEX);
try {
assertEquals(ImmediateExtendedAttribHandler.LOC_MC_ENTITY, GLStateManager.glGetAttribLocation(program, "mc_Entity"));
assertEquals(ImmediateExtendedAttribHandler.LOC_ENTITY_IDS, GLStateManager.glGetAttribLocation(program, "iris_Entity"));
} finally {
GLStateManager.glDeleteProgram(program);
}
}
private static int linkWithIrisBindings(String vertexSource) {
final int program = GLStateManager.glCreateProgram();
GLStateManager.glAttachShader(program, GlTestSupport.compile(GL20.GL_VERTEX_SHADER, vertexSource));
GLStateManager.glAttachShader(program, GlTestSupport.compile(GL20.GL_FRAGMENT_SHADER, GlTestSupport.TRIVIAL_FS));
GLStateManager.glBindAttribLocation(program, InstancedAttribs.LOC_ENTITY, "iris_InstEntity");
GLStateManager.glBindAttribLocation(program, ImmediateExtendedAttribHandler.LOC_MC_ENTITY, "mc_Entity");
GLStateManager.glLinkProgram(program);
return program;
}
}
@@ -46,7 +46,7 @@ class GlsmSdlFfpTest {
GLStateManager.setFfpInstancing(Instancing.NONE);
CubeParityFixture.deleteResources();
ParticleParityFixture.deleteResources();
FfpFixture.IntegerInstances.delete();
FfpFixture.EntityIdInstances.delete();
}
private static void bindBaseTexture() {
@@ -172,11 +172,11 @@ class GlsmSdlFfpTest {
@Test
void entityAttribSelectsColorPerInstance() {
FfpFixture.IntegerInstances.build(0.6f);
FfpFixture.EntityIdInstances.build(0.6f);
GlsmSdlHeadlessRig.beginFrameAndClear();
FfpFixture.IntegerInstances.draw();
FfpFixture.EntityIdInstances.draw();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
@@ -0,0 +1,60 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.EntityIdsOnlyHandler;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorEntityIdsFixture;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.BLUE;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.GREEN;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlTessellatorEntityIdsTest {
private ImmediateExtendedAttribHandler previousHandler;
private int program;
@BeforeEach
void setUp() {
previousHandler = EntityIdsOnlyHandler.install();
program = GlTestSupport.program(TessellatorEntityIdsFixture.VERTEX, TessellatorEntityIdsFixture.FRAGMENT);
}
@AfterEach
void tearDown() {
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(program);
GLSMHooks.immediateExtendedHandler = previousHandler;
}
@Test
void idsChangedMidBatchApplyToTheVerticesAddedAfterTheChange() {
GlsmSdlHeadlessRig.beginFrameAndClear();
GLStateManager.glUseProgram(program);
TessellatorEntityIdsFixture.drawIdsChangedMidBatch();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 4, SIZE / 2, GREEN, "left quad was added under item 7");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 3 * SIZE / 4, SIZE / 2, BLUE, "right quad was added under item 50020");
}
@Test
void idsChangedAfterTheLastVertexLeaveTheBatchAlone() {
GlsmSdlHeadlessRig.beginFrameAndClear();
GLStateManager.glUseProgram(program);
TessellatorEntityIdsFixture.drawIdsChangedAfterTheLastVertex();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 4, SIZE / 2, GREEN, "every vertex was added under item 7");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 3 * SIZE / 4, SIZE / 2, GREEN, "every vertex was added under item 7");
}
}
@@ -38,6 +38,7 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
private static final int WANTS_MID = 1;
private static final int WANTS_TANGENT = 2;
private static final int WANTS_ENTITY_IDS = 4;
private static final int NEUTRAL_TANGENT = NormI8.pack(1.0f, 0.0f, 0.0f, 1.0f);
@@ -57,6 +58,7 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
wants = 0;
if (GLStateManager.glGetAttribLocation(program, "mc_midTexCoord") >= 0) wants |= WANTS_MID;
if (GLStateManager.glGetAttribLocation(program, "at_tangent") >= 0) wants |= WANTS_TANGENT;
if (GLStateManager.glGetAttribLocation(program, "iris_Entity") == LOC_ENTITY_IDS) wants |= WANTS_ENTITY_IDS;
programWantsExtCache.put(program, wants);
}
currentProgramWants = wants;
@@ -83,8 +85,7 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
@Override
public boolean wantsExtended() {
GLSMHooks.resolvePendingProgram();
return Iris.enabled && currentProgramWants != 0 && GLStateManager.getActiveProgram() != 0;
return programWants(WANTS_MID | WANTS_TANGENT);
}
@Override
@@ -92,6 +93,17 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
return (currentProgramWants & WANTS_TANGENT) != 0;
}
@Override
public boolean wantsEntityIds() {
return programWants(WANTS_ENTITY_IDS);
}
private static boolean programWants(int mask) {
if (!Iris.enabled) return false;
GLSMHooks.resolvePendingProgram();
return (currentProgramWants & mask) != 0 && GLStateManager.getActiveProgram() != 0;
}
@Override
public boolean wantsExtendedCapture() {
return Iris.enabled && Iris.getCurrentPack().isPresent();
@@ -26,6 +26,7 @@ import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.samplers.IrisSamplers;
import net.coderbot.iris.texture.pbr.PBRTextureManager;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.SystemTimeUniforms;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import org.lwjgl.opengl.GL11;
@@ -65,6 +66,9 @@ public class IrisGLSMBridge {
public static void installImmediateExtendedHandler() {
GLSMHooks.immediateExtendedHandler = new ImmediateExtendedAttribs();
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.setEntityIdsListener(() -> GLStateManager.setEntityIds(
state.getCurrentRenderedEntity(), state.getCurrentRenderedBlockEntity(), state.getCurrentRenderedItem()));
}
public static void installPostTransformHook() {
@@ -16,6 +16,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorEntityIds;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
@@ -652,6 +653,7 @@ public final class ModelPartBatcher {
private TessellatorRoute routeTessellatorDraw(Tessellator tess) {
if (!active || mode != Mode.BLOCK_ENTITIES || !GLStateManager.isMainThread() || !BatchEligibility.insideRenderer()) return TessellatorRoute.DRAW_NOW;
if (!tess.isDrawing || tess.drawMode != GL11.GL_QUADS || tess.vertexCount == 0 || (tess.vertexCount & 3) != 0) return TessellatorRoute.DRAW_NOW;
if (shaderPipeline != null && TessellatorEntityIds.hasIdChanges(tess)) return TessellatorRoute.DRAW_NOW;
if (DisplayListManager.isRecording() || !GLStateManager.glIsEnabled(GL11.GL_DEPTH_TEST) || !unitTexIdentity()) return TessellatorRoute.DRAW_NOW;
if (GLStateManager.getActiveProgram() != (shaderPipeline != null ? shaderPipeline.getActivePassProgramId() : 0)) return TessellatorRoute.DRAW_NOW;
final TesrMaterial material = EntityMaterials.fromCurrentState(true);
@@ -5,6 +5,7 @@ package net.coderbot.iris.gl.shader;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.shader.ProgramBinaryCache;
import org.apache.logging.log4j.LogManager;
@@ -24,8 +25,8 @@ public class ProgramCreator {
public static final int AT_MIDBLOCK = ImmediateExtendedAttribHandler.LOC_MIDBLOCK;
// TODO: This is *really* hardcoded, we need to refactor this to support external calls to glBindAttribLocation
private static final String[] ATTRIBUTE_NAMES = { "iris_Entity", "mc_Entity", "mc_midTexCoord", "iris_CubeTangent", "at_tangent", "at_midBlock" };
private static final int[] ATTRIBUTE_LOCATIONS = { MC_ENTITY, MC_ENTITY, MC_MID_TEX_COORD, AT_TANGENT, AT_TANGENT, AT_MIDBLOCK };
private static final String[] ATTRIBUTE_NAMES = { "iris_InstEntity", "mc_Entity", "mc_midTexCoord", "iris_CubeTangent", "at_tangent", "at_midBlock" };
private static final int[] ATTRIBUTE_LOCATIONS = { InstancedAttribs.LOC_ENTITY, MC_ENTITY, MC_MID_TEX_COORD, AT_TANGENT, AT_TANGENT, AT_MIDBLOCK };
@Nullable
public static ProgramBinaryCache.Key cacheKey() {
@@ -1,24 +1,21 @@
package net.coderbot.iris.pipeline.transform;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
import net.coderbot.iris.pipeline.transform.parameter.AttributeParameters;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.ParserRuleContext;
import org.taumc.glsl.Transformer;
import org.taumc.glsl.grammar.GLSLParser;
class EntityPatcher {
private static final String[] ID_NAMES = { "entityId", "blockEntityId", "currentRenderedItemId" };
private static final String[] ID_COMPONENTS = { ".x", ".y", ".z" };
public static void patchEntityId(Transformer transformer, AttributeParameters parameters) {
if (!hasInstanceAttributes(parameters)) {
injectUniformIfUndeclared(transformer, "entityId", "uniform int entityId;");
injectUniformIfUndeclared(transformer, "blockEntityId", "uniform int blockEntityId;");
injectUniformIfUndeclared(transformer, "currentRenderedItemId", "uniform int currentRenderedItemId;");
return;
}
removeIfDeclared(transformer, "entityId");
removeIfDeclared(transformer, "blockEntityId");
removeIfDeclared(transformer, "currentRenderedItemId");
final String geometryIn = parameters.hasTesselation ? "iris_entityInfoTES" : "iris_entityInfo";
final String info = switch (parameters.type) {
case GEOMETRY -> geometryIn + "[0]";
@@ -26,17 +23,37 @@ class EntityPatcher {
case TESSELATION_EVAL -> "iris_entityInfoTCS[0]";
default -> "iris_entityInfo";
};
transformer.replaceExpression("entityId", info + ".x", GLSLParser::postfix_expression);
transformer.replaceExpression("blockEntityId", info + ".y", GLSLParser::postfix_expression);
transformer.replaceExpression("currentRenderedItemId", info + ".z", GLSLParser::postfix_expression);
final StringBuilder assignments = new StringBuilder();
for (int i = 0; i < ID_NAMES.length; i++) {
final String name = ID_NAMES[i];
if (!transformer.containsCall(name)) continue;
final GLSLParser.Typeless_declarationContext global = topLevelDeclarator(transformer, name);
if (global != null) {
if (!isUniform(global)) continue;
// removeVariable takes the last declaration it finds by name, which can be a pack local
final String removed = "iris_removed_" + name;
((CommonToken) global.IDENTIFIER().getSymbol()).setText(removed);
transformer.removeVariable(removed);
}
transformer.injectVariable("int " + name + ";");
assignments.append(name).append(" = ").append(info).append(ID_COMPONENTS[i]).append("; ");
}
if (parameters.type != ShaderType.VERTEX && assignments.length() > 0) {
transformer.prependMain("{ " + assignments + "}");
}
switch (parameters.type) {
case VERTEX -> {
final boolean instanced = hasInstanceAttributes(parameters);
final String input = instanced ? "iris_InstEntity" : "iris_Entity";
final int location = instanced ? InstancedAttribs.LOC_ENTITY : ImmediateExtendedAttribHandler.LOC_ENTITY_IDS;
removeIfDeclared(transformer, "iris_Entity");
transformer.injectVariable("layout(location = " + InstancedAttribs.LOC_ENTITY + ") in uvec4 iris_Entity;");
removeIfDeclared(transformer, "iris_InstEntity");
transformer.injectVariable("layout(location = " + location + ") in vec4 " + input + ";");
transformer.injectVariable("flat out ivec3 iris_entityInfo;");
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));");
transformer.prependMain("{ ivec3 iris_ids = ivec3(round(" + input + ".xyz * 65535.0)); "
+ "iris_entityInfo = ivec3(iris_ids.x == 65535 ? -1 : iris_ids.x, iris_ids.y == 65535 ? -1 : iris_ids.y, "
+ "iris_ids.z == 65535 ? -1 : iris_ids.z); " + assignments + "}");
}
case TESSELATION_CONTROL -> {
transformer.injectVariable("flat out ivec3 iris_entityInfoTCS[];");
@@ -113,6 +130,27 @@ class EntityPatcher {
}
}
private static GLSLParser.Typeless_declarationContext topLevelDeclarator(Transformer transformer, String name) {
final GLSLParser.Typeless_declarationContext[] found = new GLSLParser.Typeless_declarationContext[1];
transformer.mutateTree(root -> {
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
final GLSLParser.DeclarationContext declaration = external.declaration();
if (declaration == null || declaration.init_declarator_list() == null) continue;
for (GLSLParser.Typeless_declarationContext declarator : ShaderIndex.declarators(declaration.init_declarator_list())) {
if (declarator.IDENTIFIER().getText().equals(name)) found[0] = declarator;
}
}
});
return found[0];
}
private static boolean isUniform(GLSLParser.Typeless_declarationContext declarator) {
final ParserRuleContext parent = declarator.getParent();
final GLSLParser.Init_declarator_listContext list = (GLSLParser.Init_declarator_listContext)
(parent instanceof GLSLParser.Single_declarationContext ? parent.getParent() : parent);
return ShaderIndex.hasStorageQualifier(list.single_declaration().fully_specified_type().type_qualifier(), "uniform");
}
private static boolean hasInstanceAttributes(AttributeParameters parameters) {
return parameters.instancing.hasInstanceHead();
}
@@ -35,6 +35,7 @@ public class CapturedRenderingState {
@Getter
private int currentRenderedItem = -1;
private Runnable itemIdListener = null;
private Runnable entityIdsListener = null;
private final IntArrayList itemStack = new IntArrayList();
private final IntArrayList entityStack = new IntArrayList();
private final IntArrayList blockEntityStack = new IntArrayList();
@@ -88,15 +89,25 @@ public class CapturedRenderingState {
if (this.blockEntityIdListener != null) {
this.blockEntityIdListener.run();
}
notifyEntityIdsChanged();
}
private void setCurrentEntity(int entity) {
if (currentRenderedEntity == entity) return;
this.currentRenderedEntity = entity;
if (this.entityIdListener != null) {
this.entityIdListener.run();
private void applyCurrentEntityAndItem(int entity, int item) {
final boolean entityChanged = currentRenderedEntity != entity;
final boolean itemChanged = currentRenderedItem != item;
if (entityChanged) {
this.currentRenderedEntity = entity;
if (this.entityIdListener != null) {
this.entityIdListener.run();
}
}
if (itemChanged) {
this.currentRenderedItem = item;
if (this.itemIdListener != null) {
this.itemIdListener.run();
}
}
if (entityChanged || itemChanged) notifyEntityIdsChanged();
}
/**
@@ -104,15 +115,13 @@ public class CapturedRenderingState {
*/
public void setCurrentEntityAndItem(int entity, int item) {
IrisDisplayListState.recordEntityAndItem(entity, item);
setCurrentEntity(entity);
applyCurrentRenderedItem(item);
applyCurrentEntityAndItem(entity, item);
}
public void setCurrentNamedEntity(NamespacedId entity) {
IrisDisplayListState.recordNamedEntity(entity);
final var ids = BlockRenderingSettings.INSTANCE.getEntityIds();
setCurrentEntity(ids == null ? -1 : ids.applyAsInt(entity));
applyCurrentRenderedItem(0);
applyCurrentEntityAndItem(ids == null ? -1 : ids.applyAsInt(entity), 0);
}
public void pushCurrentEntityAndItem() {
@@ -123,15 +132,13 @@ public class CapturedRenderingState {
public void setCurrentRenderedEntity(Entity entity) {
IrisDisplayListState.recordEntity(entity);
setCurrentEntity(EntityIdHelper.getEntityId(entity));
applyCurrentRenderedItem(0);
applyCurrentEntityAndItem(EntityIdHelper.getEntityId(entity), 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());
applyCurrentEntityAndItem(entityStack.popInt(), itemStack.popInt());
}
public void setCurrentEntityColor(float r, float g, float b, float a) {
@@ -209,6 +216,18 @@ public class CapturedRenderingState {
if (this.itemIdListener != null) {
this.itemIdListener.run();
}
notifyEntityIdsChanged();
}
public void setEntityIdsListener(Runnable listener) {
this.entityIdsListener = listener;
notifyEntityIdsChanged();
}
private void notifyEntityIdsChanged() {
if (this.entityIdsListener != null) {
this.entityIdsListener.run();
}
}
public ValueUpdateNotifier getEntityIdNotifier() {
@@ -1,11 +1,13 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.ffp;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorEntityIds;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import net.minecraft.client.renderer.Tessellator;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
@@ -19,4 +21,9 @@ public class MixinTessellator_CoreProfile {
private void angelica$coreProfileDraw(CallbackInfoReturnable<Integer> cir) {
cir.setReturnValue(ModelPartBatcher.drawTessellator((Tessellator) (Object) this));
}
@Inject(method = "reset", at = @At("HEAD"))
private void angelica$forgetEntityIds(CallbackInfo ci) {
TessellatorEntityIds.onReset((Tessellator) (Object) this);
}
}
@@ -1,5 +1,6 @@
package net.coderbot.iris.pipeline;
import com.gtnewhorizons.angelica.glsm.EntityIdsOnlyHandler;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
@@ -7,6 +8,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
@@ -403,15 +405,19 @@ class IrisInstancingGLTest {
""";
final String fragmentSource = """
#version 120
uniform int entityId;
uniform int blockEntityId;
uniform int entityId, blockEntityId;
uniform int currentRenderedItemId;
uniform int expectedId;
// a parameter and a local sharing the id names keep their own values
bool isFortyTwo(int entityId) { return entityId == 42; }
void main() {
bool matches = entityId == expectedId && blockEntityId == expectedId && currentRenderedItemId == expectedId;
gl_FragData[0] = matches ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
bool shadowed = isFortyTwo(42);
{ int blockEntityId = 7; shadowed = shadowed && blockEntityId == 7; }
gl_FragData[0] = matches && shadowed ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
}
""";
final int[] ids = { -1, 0, 32767, 32768, 45020, 50020, 50072, 65534 };
final int vao = GLStateManager.glGenVertexArrays();
final int buffer = GLStateManager.glGenBuffers();
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
@@ -432,7 +438,7 @@ class IrisInstancingGLTest {
GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_ENTITY);
GLStateManager.glVertexAttribDivisor(InstancedAttribs.LOC_ENTITY, 1);
final ByteBuffer data = BufferUtils.createByteBuffer(InstancedAttribs.HEAD_SIZE);
for (int id : new int[] { -1, 0, 32767, 32768, 45020, 50020, 50072, 65534 }) {
for (int id : ids) {
data.putLong(InstancedAttribs.OFFSET_ENTITY, InstancedAttribs.packEntityInfo(id, id, id));
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(program, "expectedId"), id);
@@ -449,6 +455,30 @@ class IrisInstancingGLTest {
GLStateManager.glDeleteProgram(program);
}
}
final int program = linkProgram("highIdsImmediate", TransformPatcher.patchAttributes(vertexSource, null, null, null, fragmentSource, TEX_LM, false));
final ImmediateExtendedAttribHandler previousHandler = GLSMHooks.immediateExtendedHandler;
try {
EntityIdsOnlyHandler.install();
GLStateManager.glUseProgram(program);
GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(program, "iris_currentAlphaFunc"), 7);
GLStateManager.glBindVertexArray(vao);
for (int id : ids) {
GLStateManager.setEntityIds(id, id, id);
GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(program, "expectedId"), id);
GLStateManager.glClearColor(0, 0, 0, 1);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "draw error");
final int[] pixel = FfpFixture.readPixel(0, 0);
assertEquals(255, pixel[1], "immediate material ID " + id + " pixel " + Arrays.toString(pixel));
}
} finally {
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(program);
GLSMHooks.immediateExtendedHandler = previousHandler;
GLStateManager.setEntityIds(-1, 0, -1);
}
} finally {
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.CubeInstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.ParticleInstancedAttribs;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import net.coderbot.iris.gbuffer_overrides.matching.InputAvailability;
import net.coderbot.iris.pipeline.transform.parameter.AttributeParameters;
import org.junit.jupiter.api.AfterAll;
@@ -259,9 +260,10 @@ class AttributeTransformerInstancedTest {
assertLacks(v, "uniform int entityId;");
assertLacks(v, "uniform int blockEntityId;");
assertLacks(v, "uniform int currentRenderedItemId;");
assertHas(v, "layout(location = " + InstancedAttribs.LOC_ENTITY + ") in uvec4 iris_Entity;");
assertHas(v, "layout(location = " + InstancedAttribs.LOC_ENTITY + ") in vec4 iris_InstEntity;");
assertLacks(v, "in vec4 iris_Entity;");
assertHas(v, "flat out ivec3 iris_entityInfo;");
assertHas(v, "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));");
assertHas(v, "ivec3 iris_ids = ivec3(round(iris_InstEntity.xyz * 65535.0));");
assertLacks(f, "uniform int entityId;");
assertHas(f, "flat in ivec3 iris_entityInfo;");
@@ -362,6 +364,8 @@ class AttributeTransformerInstancedTest {
assertHas(v, "layout(location = 3) in vec4 iris_CubeDelta");
assertHas(v, "layout(location = " + CubeInstancedAttribs.LOC_CUBE_TEX + ") in vec4 iris_CubeTex");
assertHas(v, "layout(location = " + CubeInstancedAttribs.LOC_LIGHTMAP_SCALE + ") in vec4 iris_InstLightmapScale");
assertHas(v, "layout(location = " + InstancedAttribs.LOC_ENTITY + ") in vec4 iris_InstEntity;");
assertLacks(v, "in vec4 iris_Entity;");
assertLacks(v, "layout(location = 2) in vec4 iris_MultiTexCoord0");
assertHas(v, "vec4 iris_MultiTexCoord0;");
assertHas(v, "iris_MultiTexCoord1 = vec4(iris_InstLightmapScale.xy, 0.0, 1.0)");
@@ -417,10 +421,11 @@ class AttributeTransformerInstancedTest {
assertEquals(ParticleInstancedAttribs.LOC_UV, locationOf(byLocation, "iris_InstUv"));
assertEquals(InstancedAttribs.LOC_COLOR, locationOf(byLocation, "iris_InstColor"));
assertEquals(InstancedAttribs.LOC_LIGHTMAP, locationOf(byLocation, "iris_InstLightmap"));
assertEquals(ImmediateExtendedAttribHandler.LOC_ENTITY_IDS, locationOf(byLocation, "iris_Entity"));
assertFalse(byLocation.containsKey(7), "location 7 is unused");
for (final int loc : byLocation.keySet()) {
assertTrue(loc < 11, "unexpected attribute location " + loc + " declared for the particle variant");
assertTrue(loc < 11 || loc == ImmediateExtendedAttribHandler.LOC_ENTITY_IDS, "unexpected attribute location " + loc + " declared for the particle variant");
}
}
@@ -431,7 +436,7 @@ class AttributeTransformerInstancedTest {
}
@Test
void particleEntityIdAndOverlayColorStayAsPlainUniformsLikeTheNonePath() {
void particleEntityIdsAndOverlayColorFollowTheNonePath() {
final Map<PatchShaderType, String> result = TransformPatcher.patchAttributesInstanced(VERTEX_ENTITY_AND_COLOR, null, null, null, FRAGMENT_ENTITY_AND_COLOR, TEX_LM, false, Instancing.PARTICLE);
assertNotNull(result);
final String v = result.get(PatchShaderType.VERTEX);
@@ -441,16 +446,16 @@ class AttributeTransformerInstancedTest {
ShaderParser.parseShader(v);
ShaderParser.parseShader(f);
assertHas(v, "uniform int entityId;");
assertLacks(v, "uniform int entityId;");
assertHas(v, "layout(location = " + ImmediateExtendedAttribHandler.LOC_ENTITY_IDS + ") in vec4 iris_Entity;");
assertHas(v, "uniform vec4 entityColor;");
assertLacks(v, "iris_entityInfo");
assertLacks(v, "iris_InstOverlay");
assertLacks(v, "out vec4 entityColor;");
assertHas(f, "uniform int entityId;");
assertLacks(f, "uniform int entityId;");
assertHas(f, "flat in ivec3 iris_entityInfo;");
assertHas(f, "uniform vec4 entityColor;");
assertLacks(f, "in vec4 entityColor;");
assertLacks(f, "flat in ivec3 iris_entityInfo;");
}
@Test
@@ -465,6 +470,6 @@ class AttributeTransformerInstancedTest {
final Map<PatchShaderType, String> entityResult = TransformPatcher.patchAttributes(VERTEX_ENTITY, null, null, null, FRAGMENT_ENTITY, TEX_LM, false);
assertNotNull(entityResult);
assertHas(entityResult.get(PatchShaderType.VERTEX), "uniform int entityId;");
assertHas(entityResult.get(PatchShaderType.VERTEX), "layout(location = " + ImmediateExtendedAttribHandler.LOC_ENTITY_IDS + ") in vec4 iris_Entity;");
}
}
@@ -118,10 +118,10 @@ class VertexReplayExtractionTest {
final RwImageStoreExtractor.Result result = replayOf(TransformPatcher.patchAttributesInstanced(CRYSTAL_VERTEX, null, null, null, FRAGMENT, TEX_LM, false, Instancing.TEMPLATE));
final List<VertexInput> inputs = RwImageStoreExtractor.parseVertexReplayInputs(result.computeSource());
final VertexInput entity = input(inputs, "iris_Entity");
final VertexInput entity = input(inputs, "iris_InstEntity");
assertEquals(InstancedAttribs.LOC_ENTITY, entity.location());
assertEquals("uvec4", entity.glslType());
assertTrue(result.computeSource().contains("iris_Entity = uvec4(_vg_uint("), result.computeSource());
assertEquals("vec4", entity.glslType());
assertTrue(result.computeSource().contains("iris_InstEntity = vec4(_vg_float("), result.computeSource());
assertFalse(result.computeSource().contains("location"), "a location on a former interface variable does not compile in compute");
}