Cut more per-frame allocations in terrain and rendering (#2193)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-10-02 22:06:57 -07:00
1 parent 85892c0f7f
commit d876a203da
46 files changed
+833 -150

No files matched your search

@@ -125,7 +125,6 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.IntSupplier;
import static com.gtnewhorizons.angelica.glsm.Vendor.AMD;
import static com.gtnewhorizons.angelica.glsm.Vendor.INTEL;
@@ -5061,18 +5060,16 @@ public class GLStateManager {
RENDER_BACKEND.texParameterf(target, pname, param);
}
public static int getTexParameterOrDefault(int texture, int pname, IntSupplier defaultSupplier) {
return getTexParameterOrDefault(TextureInfoCache.INSTANCE.getInfo(texture), pname, defaultSupplier);
}
public static final long TEX_PARAM_MISS = Long.MIN_VALUE;
static int getTexParameterOrDefault(TextureInfo info, int pname, IntSupplier defaultSupplier) {
public static long lookupTexParameteri(TextureInfo info, int pname) {
if (info == null) {
if (isRecordingDisplayList()) {
throw new IllegalStateException(
"glGetTexParameteri called during display list recording with no cached TextureInfo. " +
"Cannot query OpenGL state during compilation!");
}
return defaultSupplier.getAsInt();
return TEX_PARAM_MISS;
}
return switch (pname) {
case GL11.GL_TEXTURE_MIN_FILTER -> info.getMinFilter();
@@ -5090,7 +5087,7 @@ public class GLStateManager {
"glGetTexParameteri called during display list recording with uncached pname 0x%s. " +
"Cannot query OpenGL state during compilation!", Integer.toHexString(pname)));
}
yield defaultSupplier.getAsInt();
yield TEX_PARAM_MISS;
}
};
}
@@ -5099,7 +5096,8 @@ public class GLStateManager {
if (target != GL11.GL_TEXTURE_2D || !isCachingEnabled()) {
return RENDER_BACKEND.getTexParameteri(target, pname);
}
return getTexParameterOrDefault(getBoundTextureInfo(), pname, () -> RENDER_BACKEND.getTexParameteri(target, pname));
final long v = lookupTexParameteri(getBoundTextureInfo(), pname);
return v != TEX_PARAM_MISS ? (int) v : RENDER_BACKEND.getTexParameteri(target, pname);
}
public static float glGetTexParameterf(int target, int pname) {
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
@@ -73,7 +74,8 @@ public class DSAARB extends DSAUnsupported {
@Override
public int getTexParameteri(int texture, int target, int pname) {
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> RENDER_BACKEND.getTextureParameteri(texture, target, pname));
final long v = GLStateManager.lookupTexParameteri(TextureInfoCache.INSTANCE.getInfo(texture), pname);
return v != GLStateManager.TEX_PARAM_MISS ? (int) v : RENDER_BACKEND.getTextureParameteri(texture, target, pname);
}
@Override
@@ -0,0 +1,50 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import java.util.NoSuchElementException;
final class FenceQueue {
private long[] ids = new long[8];
private int[] bytes = new int[8];
private int head;
private int size;
void enqueue(long id, int byteCount) {
if (size == ids.length) grow();
final int tail = (head + size) & (ids.length - 1);
ids[tail] = id;
bytes[tail] = byteCount;
size++;
}
boolean isEmpty() { return size == 0; }
long firstId() {
if (size == 0) throw new NoSuchElementException();
return ids[head];
}
int firstBytes() {
if (size == 0) throw new NoSuchElementException();
return bytes[head];
}
void dequeue() {
if (size == 0) throw new NoSuchElementException();
head = (head + 1) & (ids.length - 1);
size--;
}
private void grow() {
final int len = ids.length;
final long[] newIds = new long[len * 2];
final int[] newBytes = new int[len * 2];
for (int i = 0; i < size; i++) {
final int src = (head + i) & (len - 1);
newIds[i] = ids[src];
newBytes[i] = bytes[src];
}
ids = newIds;
bytes = newBytes;
head = 0;
}
}
@@ -3,8 +3,6 @@ package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
@@ -28,8 +26,7 @@ final class GlStreamingRing {
private static final long SYNC_WAIT_SLICE_NANOS = 10_000_000L;
private static final long SYNC_WAIT_TOTAL_NANOS = 5_000_000_000L;
private final LongArrayFIFOQueue fenceIds = new LongArrayFIFOQueue();
private final IntArrayFIFOQueue fenceBytes = new IntArrayFIFOQueue();
private final FenceQueue fences = new FenceQueue();
private int writePos;
private int remaining;
private int pendingBytes;
@@ -93,7 +90,7 @@ final class GlStreamingRing {
private boolean ensureRemaining(int needed) {
while (remaining < needed) {
if (fenceIds.isEmpty()) {
if (fences.isEmpty()) {
if (pendingBytes == 0) return false;
fence();
}
@@ -104,8 +101,7 @@ final class GlStreamingRing {
private void fence() {
if (pendingBytes > 0) {
fenceIds.enqueue(GLStateManager.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0));
fenceBytes.enqueue(pendingBytes);
fences.enqueue(GLStateManager.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0), pendingBytes);
pendingBytes = 0;
fencesIssued++;
}
@@ -116,18 +112,19 @@ final class GlStreamingRing {
}
private void reclaim() {
while (!fenceIds.isEmpty()) {
final long fenceId = fenceIds.firstLong();
while (!fences.isEmpty()) {
final long fenceId = fences.firstId();
if (!signaled(GLStateManager.glClientWaitSync(fenceId, 0, 0L))) break;
GLStateManager.glDeleteSync(fenceId);
fenceIds.dequeueLong();
remaining += fenceBytes.dequeueInt();
remaining += fences.firstBytes();
fences.dequeue();
}
}
private void syncOldest() {
final long fenceId = fenceIds.dequeueLong();
final int bytes = fenceBytes.dequeueInt();
final long fenceId = fences.firstId();
final int bytes = fences.firstBytes();
fences.dequeue();
forcedReclaims++;
final long deadline = System.nanoTime() + SYNC_WAIT_TOTAL_NANOS;
long remainingNanos = SYNC_WAIT_TOTAL_NANOS;
@@ -160,10 +157,10 @@ final class GlStreamingRing {
int forcedReclaims() { return forcedReclaims; }
void destroy() {
while (!fenceIds.isEmpty()) {
GLStateManager.glDeleteSync(fenceIds.dequeueLong());
while (!fences.isEmpty()) {
GLStateManager.glDeleteSync(fences.firstId());
fences.dequeue();
}
fenceBytes.clear();
if (bufferId != 0) {
GLStateManager.glBindBuffer(target, bufferId);
GLStateManager.glUnmapBuffer(target);
@@ -191,7 +191,7 @@ public class TessellatorStreamingDrawer {
}
// Shrink rawBuffer if oversized
if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize << 3)) {
if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize >> 3)) {
tess.rawBufferSize = 0x10000;
tess.rawBuffer = new int[tess.rawBufferSize];
}
@@ -383,7 +383,7 @@ public class TessellatorStreamingDrawer {
int firstVertex = -1;
if (persistentBuffer != null) {
firstVertex = persistentBuffer.upload(combined, combinedStride);
firstVertex = persistentBuffer.upload(combined, combinedStride, drawMode == GL11.GL_QUADS ? 4 : 1);
}
if (firstVertex >= 0) {
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import java.nio.IntBuffer;
@@ -144,4 +145,19 @@ public class GLSM_StateQueryRoundTrip_UnitTest {
assertEquals(viewport[2], restored[2]);
assertEquals(viewport[3], restored[3]);
}
@Test
void uncachedTexParameterFallsThroughToTheDriver() {
final int tex = GLStateManager.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_WRAP_R, GL12.GL_CLAMP_TO_EDGE);
assertEquals(GL12.GL_CLAMP_TO_EDGE, GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_WRAP_R), "uncached pname must fall through to the driver");
assertEquals(GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_WRAP_R),
GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_WRAP_R), "uncached pname must match the driver");
} finally {
GLStateManager.glDeleteTextures(tex);
}
}
}
@@ -0,0 +1,57 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class FenceQueueTest {
@Test
void fifoOrderAcrossWrapAndGrowth() {
final FenceQueue q = new FenceQueue();
for (int i = 0; i < 5; i++) q.enqueue(i, i * 10);
for (int i = 0; i < 3; i++) {
assertEquals(i, q.firstId());
assertEquals(i * 10, q.firstBytes());
q.dequeue();
}
for (int i = 5; i < 15; i++) q.enqueue(i, i * 10);
for (int i = 3; i < 15; i++) {
assertFalse(q.isEmpty());
assertEquals(i, q.firstId());
assertEquals(i * 10, q.firstBytes());
q.dequeue();
}
assertTrue(q.isEmpty());
}
@Test
void firstBytesTracksFirstId() {
final FenceQueue q = new FenceQueue();
q.enqueue(100L, 7);
q.enqueue(200L, 9);
assertEquals(100L, q.firstId());
assertEquals(7, q.firstBytes());
q.dequeue();
assertEquals(200L, q.firstId());
assertEquals(9, q.firstBytes());
}
@Test
void drainAndRefillKeepsBackingArray() {
final FenceQueue q = new FenceQueue();
for (int i = 0; i < 20; i++) q.enqueue(i, i);
while (!q.isEmpty()) q.dequeue();
final long[] before = Reflect.get(q, "ids");
for (int i = 0; i < 10; i++) q.enqueue(i, i);
final long[] after = Reflect.get(q, "ids");
assertSame(before, after);
assertEquals(0L, q.firstId());
}
}
@@ -73,6 +73,32 @@ class StreamingBufferTest {
}
}
@Test
void persistent_uploadAlignment_nonPow2Stride() {
Assumptions.assumeTrue(RenderSystem.supportsBufferStorage(), "GL4.4+ required");
final int stride = 44;
PersistentStreamingBuffer buf = PersistentStreamingBuffer.createOrNull(4096);
assertNotNull(buf);
try {
ByteBuffer first = memAlloc(3 * stride);
fillPattern(first);
first.flip();
assertTrue(buf.upload(first, stride) >= 0, "Upload should succeed");
memFree(first);
ByteBuffer second = memAlloc(3 * stride);
fillPattern(second);
second.flip();
int firstVertex = buf.upload(second, stride, 4);
assertTrue(firstVertex >= 0, "Upload should succeed");
assertEquals(0, firstVertex % 4, "firstVertex must be quad-aligned even for a non-power-of-2 stride");
memFree(second);
} finally {
buf.destroy();
}
}
@Test
void persistent_dataIntegrity() {
Assumptions.assumeTrue(RenderSystem.supportsBufferStorage(), "GL4.4+ required");
@@ -4,6 +4,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizons.angelica.glsm.GLCompatTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.minecraft.client.renderer.Tessellator;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
@@ -14,6 +15,7 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
@GLCompatTest
public class StreamingDrawVAOBindingTest {
@@ -46,4 +48,46 @@ public class StreamingDrawVAOBindingTest {
memFree(data);
}
}
@Test
void rawBufferShrinksOnlyWellBelowOneEighthFull() {
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;
try {
final int[] oversized = new int[0x40000];
tess.rawBuffer = oversized;
tess.rawBufferSize = 0x40000;
tess.drawMode = GL11.GL_QUADS;
tess.vertexCount = 4096;
tess.rawBufferIndex = 0x8000;
tess.isDrawing = true;
TessellatorStreamingDrawer.draw(tess);
assertSame(oversized, tess.rawBuffer, "at exactly 1/8 full, the buffer must not shrink");
assertEquals(0x40000, tess.rawBufferSize, "at exactly 1/8 full, the buffer size must not shrink");
tess.drawMode = GL11.GL_QUADS;
tess.vertexCount = 4;
tess.rawBufferIndex = 32;
tess.isDrawing = true;
TessellatorStreamingDrawer.draw(tess);
assertEquals(0x10000, tess.rawBufferSize, "well below 1/8 full, the buffer must shrink");
assertEquals(0x10000, tess.rawBuffer.length, "the shrunk array must match the new size");
} finally {
tess.rawBuffer = savedRawBuffer;
tess.rawBufferSize = savedRawBufferSize;
tess.rawBufferIndex = savedRawBufferIndex;
tess.vertexCount = savedVertexCount;
tess.drawMode = savedDrawMode;
tess.isDrawing = savedIsDrawing;
}
}
}
@@ -1,7 +1,6 @@
package com.gtnewhorizons.angelica.client.font;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
@@ -48,9 +47,9 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetFloat;
@@ -160,6 +159,7 @@ public class BatchingFontRenderer {
private static long vertexDataAddress = memAddress0(vertexData);
private static int vboCapacity;
private static final FloatBuffer FLUSH_MVP = memAllocFloat(16);
// OpenGL objects (static, can be used between multiple BatchingFontRenderer)
private static int vbo;
@@ -720,26 +720,23 @@ public class BatchingFontRenderer {
deferredSegments.get(0).owner.setupFontDrawState();
flushLastTexture = -1;
resetFlushLightmap();
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
boolean curDepthTest = GLStateManager.getDepthTest().isEnabled();
boolean curDepthMask = GLStateManager.isEffectiveDepthMaskEnabled();
for (final TextSegment segment : deferredSegments) {
mvpBuf.clear();
segment.mvp.get(mvpBuf);
GLStateManager.glUniformMatrix4(segment.owner.mvpMatrixLocation, false, mvpBuf);
uploadLightmap(segment.owner.lightmapLocation, segment.lightmapActive, segment.lightmapU,
segment.lightmapV, segment.lightmapTexture);
if (segment.depthTest != curDepthTest) {
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
curDepthTest = segment.depthTest;
}
if (segment.depthMask != curDepthMask) {
GLStateManager.glDepthMask(segment.depthMask);
curDepthMask = segment.depthMask;
}
drawCommands(segment.owner.batchCommands.elements(), segment.cmdStart, segment.cmdEnd, segment.owner);
boolean curDepthTest = GLStateManager.getDepthTest().isEnabled();
boolean curDepthMask = GLStateManager.isEffectiveDepthMaskEnabled();
for (final TextSegment segment : deferredSegments) {
FLUSH_MVP.clear();
segment.mvp.get(FLUSH_MVP);
GLStateManager.glUniformMatrix4(segment.owner.mvpMatrixLocation, false, FLUSH_MVP);
uploadLightmap(segment.owner.lightmapLocation, segment.lightmapActive, segment.lightmapU,
segment.lightmapV, segment.lightmapTexture);
if (segment.depthTest != curDepthTest) {
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
curDepthTest = segment.depthTest;
}
if (segment.depthMask != curDepthMask) {
GLStateManager.glDepthMask(segment.depthMask);
curDepthMask = segment.depthMask;
}
drawCommands(segment.owner.batchCommands.elements(), segment.cmdStart, segment.cmdEnd, segment.owner);
}
} finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
@@ -849,21 +846,20 @@ public class BatchingFontRenderer {
setupFontDrawState();
flushLastTexture = -1;
resetFlushLightmap();
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
try {
for (int i = 0; i < segmentCount; i++) {
final TextSegment segment = batchSegments.get(i);
mvpBuf.clear();
segment.mvp.get(mvpBuf);
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
FLUSH_MVP.clear();
segment.mvp.get(FLUSH_MVP);
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, FLUSH_MVP);
uploadLightmap(lightmapLocation, segment.lightmapActive, segment.lightmapU, segment.lightmapV, segment.lightmapTexture);
drawCommands(cmds, segment.cmdStart, segment.cmdEnd, this);
}
if (batchSealedEnd < cmdCount) {
mvpBuf.clear();
FLUSH_MVP.clear();
resolveMvp(scratchMvp);
scratchMvp.get(mvpBuf);
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
scratchMvp.get(FLUSH_MVP);
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, FLUSH_MVP);
uploadLightmap(lightmapLocation, lightmapActive, lightmapU, lightmapV, lightmapTextureId);
drawCommands(cmds, batchSealedEnd, cmdCount, this);
}
@@ -17,21 +17,25 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
public ExtendedBlockStorageExt(Chunk chunk, ExtendedBlockStorage storage) {
super(storage.yBase, storage.getSkylightArray() != null);
copyFrom(chunk, storage);
}
public void copyFrom(Chunk chunk, ExtendedBlockStorage storage) {
this.yBase = storage.yBase;
final boolean srcHasSky = storage.getSkylightArray() != null;
this.hasSky = srcHasSky;
if (ModStatus.isChunkAPILoaded) {
if (storage.getSkylightArray() != null) {
hasSky = true;
}
// ChunkAPI BlocklightManager.cloneSubChunk repoints the source at our array
final NibbleArray liveBlocklight = storage.getBlocklightArray();
DataRegistry.cloneSubChunk(chunk, storage, this);
storage.setBlocklightArray(liveBlocklight);
} else {
int arrayLen;
if (ModStatus.isNEIDLoaded){
final short[] block16BArray = ((IExtendedBlockStorageMixin)(Object)this).getBlock16BArray();
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BArray(), 0, block16BArray, 0, block16BArray.length);
if(storage.getBlockMSBArray() != null) {
this.setBlockMSBArray(new NibbleArray(block16BArray.length, 4));
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
}
copyMSB(storage, block16BArray.length);
arrayLen = block16BArray.length;
if (ModStatus.isNEIDMetadataExtended) {
final short[] block16BMetaArray = ((IExtendedBlockStorageMixin)(Object)this).getBlock16BMetaArray();
@@ -41,27 +45,37 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
else {
final byte[] blockLSBArray = this.getBlockLSBArray();
System.arraycopy(storage.getBlockLSBArray(), 0, blockLSBArray, 0, blockLSBArray.length);
if(storage.getBlockMSBArray() != null) {
this.setBlockMSBArray(new NibbleArray(blockLSBArray.length, 4));
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
}
copyMSB(storage, blockLSBArray.length);
arrayLen = blockLSBArray.length;
}
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray(storage.getMetadataArray(), this.getMetadataArray());
copyNibbleArray(storage.getBlocklightArray(), this.getBlocklightArray());
if(storage.getSkylightArray() != null) {
hasSky = true;
if (srcHasSky) {
if(this.getSkylightArray() == null) {
this.setSkylightArray(new NibbleArray(arrayLen, 4));
}
copyNibbleArray(storage.getSkylightArray(), this.getSkylightArray());
} else {
this.setSkylightArray(null);
}
}
this.blockRefCount = storage.blockRefCount;
}
private void copyMSB(ExtendedBlockStorage storage, int len) {
final NibbleArray src = storage.getBlockMSBArray();
if (src == null) {
this.setBlockMSBArray(null);
return;
}
if (this.getBlockMSBArray() == null) {
this.setBlockMSBArray(new NibbleArray(len, 4));
}
copyNibbleArray(src, this.getBlockMSBArray());
}
private static void copyNibbleArray(NibbleArray srcArray, NibbleArray dstArray) {
if (srcArray == null || dstArray == null) {
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.api.tesr.TesrMeshBuilder;
import com.gtnewhorizons.angelica.api.tesr.TesrMeshSink;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureMap;
@@ -27,8 +28,6 @@ public final class Tc4JarMeshes {
private static final double MIN_X = 0.25, MIN_Y = 0.0625, MIN_Z = 0.25, MAX_X = 0.75, MAX_Z = 0.75;
private static final double OFF_X = -0.5, OFF_Y = 0.0, OFF_Z = -0.5;
private record LiquidKey(int color, int level64) {}
private static final class Holder {
final Object cacheKey = new Object();
final TesrMaterial material;
@@ -40,7 +39,7 @@ public final class Tc4JarMeshes {
}
}
private static final Map<LiquidKey, Holder> HOLDERS = new HashMap<>();
private static final Long2ObjectOpenHashMap<Holder> HOLDERS = new Long2ObjectOpenHashMap<>();
private static final RenderBlocks FALLBACK_RENDER_BLOCKS = new RenderBlocks();
private static final ResourceLocation LABEL_TEXTURE = new ResourceLocation("thaumcraft", "textures/models/label.png");
@@ -118,7 +117,12 @@ public final class Tc4JarMeshes {
applyBrightness(jarBrightness(te));
final Holder holder = HOLDERS.computeIfAbsent(new LiquidKey(color, level64), k -> new Holder(k.color()));
final long liquidKey = ((long) color << 32) | (level64 & 0xFFFFFFFFL);
Holder holder = HOLDERS.get(liquidKey);
if (holder == null) {
holder = new Holder(color);
HOLDERS.put(liquidKey, holder);
}
GLStateManager.glPushMatrix();
GLStateManager.glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
BUILDER.material = holder.material;
@@ -432,6 +432,7 @@ public enum Mixins implements IMixins {
, "celeritas.biome_blending.MixinWorld"
, "celeritas.biome_blending.MixinRenderBlocks"
, "celeritas.threading.MixinForgeHooksClient"
, "celeritas.threading.MixinChunkJobTyped"
, "celeritas.terrain.MixinChunk"
, "celeritas.terrain.MixinWorldClient_WorkerAccess"
, "celeritas.terrain.MixinWorld_WorkerMutationGuard"
@@ -439,10 +440,16 @@ public enum Mixins implements IMixins {
, "celeritas.terrain.MixinNetHandlerPlayClient_DescriptorRepair"
, "celeritas.terrain.MixinWorld_AwaitingDescriptor"
, "celeritas.terrain.MixinRenderRegion"
, "celeritas.terrain.MixinChunkRenderList"
, "celeritas.terrain.MixinVisibleChunkCollector"
, "celeritas.terrain.MixinRenderListManager"
, "celeritas.terrain.MixinSectionRenderDataStorage"
, "celeritas.terrain.MixinSharedQuadIndexBuffer"
, "celeritas.terrain.MixinRenderRegionManager"
, "celeritas.terrain.MixinShaderLoader"
, "celeritas.terrain.MixinGlUniformMatrix4f"
, "celeritas.terrain.MixinGlUniformMatrix3f"
, "celeritas.terrain.MixinDefaultChunkRenderer"
)
),
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
public interface ChunkRenderListReuse {
void angelica$reset();
}
@@ -0,0 +1,7 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderList;
public interface VisibleChunkCollectorPool {
void angelica$setRecycled(ChunkRenderList[] slot);
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.render;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -20,20 +21,28 @@ import org.lwjgl.opengl.GL13;
final class CloudRenderResources {
private final Map<ResourceLocation, Texture> textures = new HashMap<>();
private final Map<Integer, GlProgram<CloudUniforms>> programs = new HashMap<>(4);
@SuppressWarnings("unchecked")
private final GlProgram<CloudUniforms>[] programs = (GlProgram<CloudUniforms>[]) new GlProgram[4];
Texture texture(ResourceLocation location) {
return textures.computeIfAbsent(location, Texture::new);
}
GlProgram<CloudUniforms> program(boolean faces, boolean untextured) {
int key = (faces ? 2 : 0) | (untextured ? 1 : 0);
return programs.computeIfAbsent(key, ignored -> buildProgram(faces, untextured));
final int key = (faces ? 2 : 0) | (untextured ? 1 : 0);
GlProgram<CloudUniforms> program = programs[key];
if (program == null) {
program = buildProgram(faces, untextured);
programs[key] = program;
}
return program;
}
void clear() {
for (GlProgram<CloudUniforms> program : programs.values()) program.delete();
programs.clear();
for (GlProgram<CloudUniforms> program : programs) {
if (program != null) program.delete();
}
Arrays.fill(programs, null);
textures.clear();
}
@@ -42,6 +42,7 @@ import net.minecraft.util.Vec3;
import net.minecraftforge.client.IRenderHandler;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -65,6 +66,8 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private final Map<String, CloudLayerRenderer> layers = new HashMap<>();
private final List<CloudLayerRenderer> drawOrder = new ArrayList<>();
private final Set<String> activeIds = new HashSet<>();
private double sortCameraY;
private final Comparator<CloudLayerRenderer> farthestFirst = (a, b) -> Double.compare(distanceToLayer(b.layer, sortCameraY), distanceToLayer(a.layer, sortCameraY));
private WorldClient world;
private int cloudMode = MODE_FAST, renderDistance, scaleMult = 1, lastCloudTicks;
private boolean enabled;
@@ -144,7 +147,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
renderer.configure(description, cloudMode, renderDistance, scaleMult);
if (description.alpha() > 0.0f) drawOrder.add(renderer);
}
layers.entrySet().removeIf(entry -> {
if (layers.size() != activeIds.size()) layers.entrySet().removeIf(entry -> {
if (activeIds.contains(entry.getKey())) return false;
entry.getValue().delete();
return true;
@@ -154,7 +157,8 @@ public class CloudRenderer implements IResourceManagerReloadListener {
cloudView.update(GLStateManager.getProjectionMatrix(), GLStateManager.getModelViewMatrix(), viewport.width, viewport.height);
final Entity view = mc.renderViewEntity;
final double cameraY = view.lastTickPosY + (view.posY - view.lastTickPosY) * partialTicks + cloudView.offsetY;
drawOrder.sort((a, b) -> Double.compare(distanceToLayer(b.layer, cameraY), distanceToLayer(a.layer, cameraY)));
sortCameraY = cameraY;
drawOrder.sort(farthestFirst);
boolean rendered = descriptions.isEmpty();
for (CloudLayerRenderer renderer : drawOrder) rendered |= renderer.render(partialTicks, cloudView);
return rendered;
@@ -13,6 +13,11 @@ import java.util.concurrent.ConcurrentHashMap;
@SideOnly(Side.CLIENT)
public final class EmissiveTextureHelper {
private static final ConcurrentHashMap<String, IIcon> EMISSIVE_ICON_CACHE = new ConcurrentHashMap<>();
private static volatile boolean anyEmissive;
public static boolean hasAnyEmissive() {
return anyEmissive;
}
public static IIcon getEmissiveIcon(Block block, int side, int metadata) {
IIcon baseIcon = block.getIcon(side, metadata);
@@ -26,10 +31,12 @@ public final class EmissiveTextureHelper {
public static void clearCache() {
EMISSIVE_ICON_CACHE.clear();
anyEmissive = false;
}
public static void cacheEmissive(String baseIconName, IIcon emissiveIcon) {
EMISSIVE_ICON_CACHE.put(baseIconName, emissiveIcon);
anyEmissive = true;
}
public static void renderEmissiveBlockOverlay(RenderBlocks renderer, Block block, int x, int y, int z, int metadata) {
@@ -15,6 +15,7 @@ import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import com.gtnewhorizons.angelica.rendering.voxelization.SdlShadowVoxelizationSink;
import com.gtnewhorizons.angelica.rendering.voxelization.ShadowVoxelizer;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gl.program.ComputeProgram;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
@@ -40,7 +41,9 @@ import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderListIterable;
import org.embeddedt.embeddium.impl.render.chunk.multidraw.BatchAssembler;
import org.embeddedt.embeddium.impl.render.chunk.multidraw.CachedBatch;
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkFogMode;
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderBindingPoints;
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderComponent;
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderInterface;
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderOptions;
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderTextureSlot;
@@ -75,6 +78,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
private final GpuTerrainCuller culler;
private final SdlShadowVoxelizationSink shadowVoxelSink = new SdlShadowVoxelizationSink();
private final ReusableCachedBatch gpuBatch = new ReusableCachedBatch();
private final Reference2ObjectOpenHashMap<TerrainRenderPass, GlProgram<ChunkShaderInterface>[]> programsByPass = new Reference2ObjectOpenHashMap<>();
private static final ShadowVoxelizer shadowVoxelizer = new ShadowVoxelizer();
private static int loggedVoxelizationSkips;
@@ -172,13 +176,39 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
// Fall back to default shader
this.usingIrisProgram = false;
this.irisProgram = null;
super.begin(pass);
beginDefault(pass);
bindTerrainSampler();
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
}
@SuppressWarnings("unchecked")
private void beginDefault(TerrainRenderPass pass) {
final ChunkShaderComponent.Factory<?> fog = this.environment.fogService().getFogMode();
if (!(fog instanceof ChunkFogMode mode)) {
super.begin(pass);
return;
}
GlProgram<ChunkShaderInterface>[] programs = this.programsByPass.get(pass);
if (programs == null) {
programs = (GlProgram<ChunkShaderInterface>[]) new GlProgram[ChunkFogMode.values().length];
this.programsByPass.put(pass, programs);
}
GlProgram<ChunkShaderInterface> program = programs[mode.ordinal()];
if (program == null) {
final List<ChunkShaderComponent.Factory<?>> components = getShaderComponents();
program = compileProgram(new ChunkShaderOptions(components, pass));
programs[mode.ordinal()] = program;
}
pass.startDrawing();
this.activeProgram = program;
if (program != null) {
program.bind();
program.getInterface().setupState(pass);
}
}
@Override
protected void end(TerrainRenderPass pass) {
unbindTerrainSampler();
@@ -273,6 +303,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
@Override
public void delete(CommandList commandList) {
super.delete(commandList);
programsByPass.clear();
if (culler != null) {
culler.delete();
@@ -97,6 +97,7 @@ public class DeferredMeshScheduler {
results.add(m.complete(mainContext()));
} finally {
releaseBuffers(m.buffers);
m.slice.releaseContext();
slices.release(m.slice);
pending.decrementAndGet();
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.mixins.interfaces.ChunkRenderListReuse;
import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderList;
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
import java.util.Arrays;
// Two slots are enough only while submitSearch rejects a second in-flight search per manager
public final class RenderListPool {
private final ChunkRenderList[][] slots = { new ChunkRenderList[0], new ChunkRenderList[0] };
private int generation;
public ChunkRenderList[] beginSearch(int regionIdsLength) {
final int s = generation++ & 1;
ChunkRenderList[] slot = slots[s];
if (slot.length < regionIdsLength) {
slot = slots[s] = Arrays.copyOf(slot, regionIdsLength);
}
return slot;
}
public static ChunkRenderList acquire(ChunkRenderList[] slot, RenderRegion region) {
final int id = region.getId();
final ChunkRenderList list = slot[id];
if (list != null && list.getRegion() == region) {
((ChunkRenderListReuse) list).angelica$reset();
return list;
}
return slot[id] = new ChunkRenderList(region);
}
}
@@ -102,6 +102,10 @@ public class ThreadedAngelicaChunkBuilderMeshingTask extends AngelicaChunkBuilde
return renderContext != null;
}
public void releaseUnstartedContext() {
if (this.renderContext != null) this.renderContext.release();
}
@Override
protected void onEnterExecute() {
enteredLocalMode = !TessellatorManager.isOnMainThread();
@@ -92,6 +92,8 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
// Local section copies
private ClonedChunkSection[] sections;
private ChunkRenderContext context;
// Biome data for each chunk section
private final BiomeGenBase[][] biomeData;
@@ -146,13 +148,18 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
final ClonedChunkSection[] sections = new ClonedChunkSection[SECTION_TABLE_ARRAY_SIZE];
for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) {
for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) {
for (int chunkY = minChunkY; chunkY <= maxChunkY; chunkY++) {
sections[getLocalSectionIndex(chunkX - minChunkX, chunkY - minChunkY, chunkZ - minChunkZ)] =
sectionCache.acquire(chunkX, chunkY, chunkZ);
try {
for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) {
for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) {
for (int chunkY = minChunkY; chunkY <= maxChunkY; chunkY++) {
sections[getLocalSectionIndex(chunkX - minChunkX, chunkY - minChunkY, chunkZ - minChunkZ)] =
sectionCache.acquire(chunkX, chunkY, chunkZ);
}
}
}
} catch (RuntimeException e) {
sectionCache.release(sections);
throw e;
}
return new ChunkRenderContext(origin, sections, volume);
@@ -197,6 +204,10 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
}
public void copyData(ChunkRenderContext context) {
if (this.context != context) {
releaseContext();
}
this.context = context;
this.origin = context.getOrigin();
this.sections = context.getSections();
this.volume = context.getVolume();
@@ -242,6 +253,14 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
}
}
public void releaseContext() {
final ChunkRenderContext c = this.context;
if (c != null) {
this.context = null;
c.release();
}
}
public ChunkSectionPos getOrigin() {
return this.origin;
}
@@ -8,6 +8,7 @@ public class ChunkRenderContext {
private final ChunkSectionPos origin;
private final ClonedChunkSection[] sections;
private final StructureBoundingBox volume;
private boolean released;
public ChunkRenderContext(ChunkSectionPos origin, ClonedChunkSection[] sections, StructureBoundingBox volume) {
this.origin = origin;
@@ -26,4 +27,10 @@ public class ChunkRenderContext {
public StructureBoundingBox getVolume() {
return this.volume;
}
public void release() {
if (this.released) return;
this.released = true;
this.sections[0].getBackingCache().release(this.sections);
}
}
@@ -37,12 +37,15 @@ public class ClonedChunkSection {
private ChunkSectionPos pos;
private ExtendedBlockStorageExt data;
private BiomeGenBase[] biomeData;
private final BiomeGenBase[] biomeData = new BiomeGenBase[BIOME_DATA_LENGTH];
private SectionLightData sectionLightData;
private final Short2ObjectOpenHashMap<TileEntity> tileEntities;
private long lastUsedTimestamp = Long.MAX_VALUE;
int refs;
boolean cached;
ClonedChunkSection(ClonedChunkSectionCache backingCache, World world) {
this.backingCache = backingCache;
this.world = world;
@@ -50,6 +53,7 @@ public class ClonedChunkSection {
}
public void init(ChunkSectionPos pos) {
if (refs != 0 || cached) throw new IllegalStateException("Reusing a live ClonedChunkSection (refs=" + refs + ", cached=" + cached + ")");
final Chunk chunk = world.getChunkFromChunkCoords(pos.x, pos.z);
if (chunk == null) {
@@ -69,9 +73,11 @@ public class ClonedChunkSection {
}
this.pos = pos;
this.data = new ExtendedBlockStorageExt(chunk, section);
this.biomeData = new BiomeGenBase[BIOME_DATA_LENGTH];
if (this.data == null) {
this.data = new ExtendedBlockStorageExt(chunk, section);
} else {
this.data.copyFrom(chunk, section);
}
copyBlockEntities(chunk, pos);
@@ -114,8 +120,6 @@ public class ClonedChunkSection {
} finally {
map.readUnlock();
}
this.tileEntities.trim();
}
public Block getBlock(int x, int y, int z) {
@@ -4,14 +4,17 @@ import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import it.unimi.dsi.fastutil.longs.Long2ReferenceLinkedOpenHashMap;
import net.minecraft.world.World;
import java.util.ArrayDeque;
import java.util.concurrent.TimeUnit;
public class ClonedChunkSectionCache {
private static final int MAX_CACHE_SIZE = 512;
private static final long MAX_CACHE_DURATION = TimeUnit.SECONDS.toNanos(5);
private static final int MAX_FREE = 64;
private final World world;
private final Long2ReferenceLinkedOpenHashMap<ClonedChunkSection> byPosition = new Long2ReferenceLinkedOpenHashMap<>();
private final ArrayDeque<ClonedChunkSection> free = new ArrayDeque<>();
private long time;
public ClonedChunkSectionCache(World world) {
@@ -26,7 +29,7 @@ public class ClonedChunkSectionCache {
if (this.time <= oldest.getLastUsedTimestamp() + MAX_CACHE_DURATION) {
break;
}
this.byPosition.removeFirst();
evict(this.byPosition.removeFirst());
}
}
@@ -36,24 +39,52 @@ public class ClonedChunkSectionCache {
if (section == null) {
while (this.byPosition.size() >= MAX_CACHE_SIZE) {
this.byPosition.removeFirst();
evict(this.byPosition.removeFirst());
}
section = this.createSection(key, x, y, z);
}
section.refs++;
section.setLastUsedTimestamp(this.time);
return section;
}
private ClonedChunkSection createSection(long key, int x, int y, int z) {
final ClonedChunkSection section = new ClonedChunkSection(this, this.world);
ClonedChunkSection section = this.free.pollLast();
if (section == null) {
section = new ClonedChunkSection(this, this.world);
}
section.init(ChunkSectionPos.from(x, y, z));
section.cached = true;
this.byPosition.putAndMoveToLast(key, section);
return section;
}
public synchronized void invalidate(int x, int y, int z) {
this.byPosition.remove(ChunkSectionPos.asLong(x, y, z));
final ClonedChunkSection section = this.byPosition.remove(ChunkSectionPos.asLong(x, y, z));
if (section != null) {
evict(section);
}
}
public synchronized void release(ClonedChunkSection[] sections) {
for (final ClonedChunkSection section : sections) {
if (section != null) {
section.refs--;
recycleIfIdle(section);
}
}
}
private void evict(ClonedChunkSection section) {
section.cached = false;
recycleIfIdle(section);
}
private void recycleIfIdle(ClonedChunkSection section) {
if (!section.cached && section.refs == 0 && this.free.size() < MAX_FREE) {
this.free.addLast(section);
}
}
private static long getMonotonicTimeSource() {
@@ -159,7 +159,7 @@ public final class ModelPartBatcher {
boolean afterGlint;
boolean matches(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit, boolean lightmap, boolean afterGlint) {
return this.texture == texture && this.material == material && pass.equals(this.pass) && this.offsetFactor == offsetFactor
return this.texture == texture && this.material == material && pass == this.pass && this.offsetFactor == offsetFactor
&& this.offsetUnits == offsetUnits && this.glintSlot == glintSlot && this.cull == cull && this.lit == lit && this.lightmap == lightmap
&& this.afterGlint == afterGlint;
}
@@ -182,7 +182,7 @@ public final class ModelPartBatcher {
public boolean equals(Object o) {
if (!(o instanceof LayerKey other)) return false;
return Objects.equals(texture, other.texture) && Objects.equals(material, other.material)
&& Objects.equals(pass, other.pass)
&& 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 && lightmap == other.lightmap && afterGlint == other.afterGlint;
@@ -190,7 +190,7 @@ public final class ModelPartBatcher {
@Override
public int hashCode() {
int h = (Objects.hashCode(texture) * 31 + Objects.hashCode(material)) * 31 + Objects.hashCode(pass);
int h = (Objects.hashCode(texture) * 31 + Objects.hashCode(material)) * 31 + System.identityHashCode(pass);
h = h * 31 + Float.floatToIntBits(offsetFactor);
h = h * 31 + Float.floatToIntBits(offsetUnits);
h = h * 31 + glintSlot;
@@ -246,7 +246,7 @@ public final class TesrBatchRenderer {
&& depthOnly == other.depthOnly
&& Float.floatToIntBits(cutoutAlpha) == Float.floatToIntBits(other.cutoutAlpha)
&& depthEqual == other.depthEqual && special == other.special && shader == other.shader && noPass == other.noPass
&& Objects.equals(pass, other.pass)
&& pass == other.pass
&& Float.floatToIntBits(offsetFactor) == Float.floatToIntBits(other.offsetFactor)
&& Float.floatToIntBits(offsetUnits) == Float.floatToIntBits(other.offsetUnits)
&& cull == other.cull && lit == other.lit && lightmap == other.lightmap;
@@ -265,7 +265,7 @@ public final class TesrBatchRenderer {
h = h * 31 + Objects.hashCode(special);
h = h * 31 + System.identityHashCode(shader);
h = h * 31 + (noPass ? 1 : 0);
h = h * 31 + Objects.hashCode(pass);
h = h * 31 + System.identityHashCode(pass);
h = h * 31 + Float.floatToIntBits(offsetFactor);
h = h * 31 + Float.floatToIntBits(offsetUnits);
h = h * 31 + cull;
@@ -294,7 +294,7 @@ public final class TesrBatchRenderer {
private RenderLayer lastImmediateLayer;
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int cullCode, boolean lit, boolean lightmap) {
if (texture == lastLayerTexture && material == lastLayerMaterial && pass.equals(lastLayerPass)
if (texture == lastLayerTexture && material == lastLayerMaterial && pass == lastLayerPass
&& offsetFactor == lastLayerOffsetFactor && offsetUnits == lastLayerOffsetUnits && cullCode == lastLayerCull && lit == lastLayerLit
&& lightmap == lastLayerLightmap) {
return lastLayer;
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
import com.gtnewhorizons.angelica.api.tesr.TesrMeshBuilder;
import com.gtnewhorizons.angelica.api.tesr.TesrMeshSink;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import net.minecraft.client.model.ModelChest;
import net.minecraft.client.model.ModelRenderer;
@@ -13,7 +14,6 @@ import net.minecraft.client.model.ModelSkeletonHead;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import java.util.HashMap;
import java.util.Map;
/** Cached-mesh entry points for the vanilla chest/sign/skull TESR mixins. */
@@ -36,9 +36,7 @@ public final class VanillaModelMeshes {
private static final Object KEY_SIGN_STANDING = new Object();
private static final Object KEY_SIGN_WALL = new Object();
private record SkullKey(ModelSkeletonHead model, ResourceLocation texture) {}
private static final Map<SkullKey, Object> SKULL_KEYS = new HashMap<>();
private static final Map<ModelSkeletonHead, Map<ResourceLocation, Object>> SKULL_KEYS = new Reference2ObjectOpenHashMap<>();
private static final Builder BUILDER = new Builder();
@@ -87,7 +85,7 @@ public final class VanillaModelMeshes {
if (texture == null) {
return false;
}
final Object key = SKULL_KEYS.computeIfAbsent(new SkullKey(model, texture), k -> new Object());
final Object key = SKULL_KEYS.computeIfAbsent(model, m -> new Object2ObjectOpenHashMap<>()).computeIfAbsent(texture, t -> new Object());
GLStateManager.glPushMatrix();
if (yawDegrees != 0.0f) {
GLStateManager.glRotatef(yawDegrees, 0.0f, 1.0f, 0.0f);
@@ -9,7 +9,6 @@ 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;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
@@ -63,8 +62,8 @@ public class AnimationsRenderUtils {
}
public static void markBlockTextureForUpdate(IIcon icon, IBlockAccess blockAccess) {
final TextureMap textureMap = Minecraft.getMinecraft().getTextureMapBlocks();
final TextureAtlasSprite textureAtlasSprite = textureMap.getAtlasSprite(icon.getIconName());
final TextureAtlasSprite textureAtlasSprite = icon instanceof TextureAtlasSprite s ? s
: Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(icon.getIconName());
if (textureAtlasSprite != null && textureAtlasSprite.hasAnimationMetadata()) {
// null if called by anything but chunk render cache update (for example to get blocks rendered as items in
@@ -205,28 +205,41 @@ public class CTMUtils {
return local;
}
private static boolean hasCandidates(Block block, IIcon icon) {
return (!overrides.block.isEmpty() && overrides.block.get(block) != null)
|| (!overrides.tile.isEmpty() && overrides.tile.get(icon.getIconName()) != null);
}
private static IIcon getBlockIconWithoutLock(IIcon icon, Block block, IBlockAccess blockAccess,
int x, int y, int z, int face) {
StateAndIterator local = getStateAndIterator();
if (blockAccess != null && checkFace(face)) {
local.renderBlockState.setBlock(block, blockAccess, x, y, z);
local.renderBlockState.setFace(face);
TileOverride lastOverride = local.ijkIterator.go(local.renderBlockState, icon);
local.renderBlockState.blockAccess = null;
if (lastOverride != null) {
return skipDefaultRendering(block) ? RenderBlocksUtils.blankIcon : local.ijkIterator.getIcon();
if (!hasCandidates(block, icon)) {
clearCurrentCompact();
} else {
final StateAndIterator local = getStateAndIterator();
local.renderBlockState.setBlock(block, blockAccess, x, y, z);
local.renderBlockState.setFace(face);
TileOverride lastOverride = local.ijkIterator.go(local.renderBlockState, icon);
local.renderBlockState.blockAccess = null;
if (lastOverride != null) {
return skipDefaultRendering(block) ? RenderBlocksUtils.blankIcon : local.ijkIterator.getIcon();
}
}
}
return skipDefaultRendering(block) ? RenderBlocksUtils.blankIcon : icon;
}
private static IIcon getBlockIconWithoutLock(IIcon icon, Block block, int face, int metadata) {
StateAndIterator local = getStateAndIterator();
if (checkFace(face) && checkRenderType(block)) {
local.renderBlockState.setBlockMetadata(block, metadata, face);
TileOverride lastOverride = local.metadataIterator.go(local.renderBlockState, icon);
if (lastOverride != null) {
return local.metadataIterator.getIcon();
if (!hasCandidates(block, icon)) {
clearCurrentCompact();
} else {
final StateAndIterator local = getStateAndIterator();
local.renderBlockState.setBlockMetadata(block, metadata, face);
TileOverride lastOverride = local.metadataIterator.go(local.renderBlockState, icon);
if (lastOverride != null) {
return local.metadataIterator.getIcon();
}
}
}
return icon;
@@ -8,21 +8,24 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Locale;
import java.util.Objects;
public record PassOverride(SpecialCondition special, Boolean translucent, WorldRenderingPhase phase) {
public final class PassOverride {
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);
private final SpecialCondition special;
private final Boolean translucent;
private final WorldRenderingPhase phase;
private PassOverride(SpecialCondition special, Boolean translucent, WorldRenderingPhase phase) {
this.special = special;
this.translucent = translucent;
this.phase = phase;
}
public static PassOverride capture() {
final SpecialCondition special = GbufferPrograms.getSpecialCondition();
final Boolean translucent = GbufferPrograms.getDeclaredTranslucency();
final WorldRenderingPhase phase = GbufferPrograms.getOverridePhase();
public static PassOverride of(SpecialCondition special, Boolean translucent, WorldRenderingPhase phase) {
if (special == null && translucent == null && phase == null) {
return NONE;
}
@@ -37,6 +40,10 @@ public record PassOverride(SpecialCondition special, Boolean translucent, WorldR
return created;
}
public static PassOverride capture() {
return of(GbufferPrograms.getSpecialCondition(), GbufferPrograms.getDeclaredTranslucency(), GbufferPrograms.getOverridePhase());
}
public void apply() {
GbufferPrograms.setupSpecialRenderCondition(special);
translucencyStack.add(GbufferPrograms.beginTranslucencyDeclaration(translucent));
@@ -5,6 +5,7 @@ import java.util.Objects;
public class NamespacedId {
private final String namespace;
private final String name;
private final int hash;
public NamespacedId(String combined) {
int colonIdx = combined.indexOf(':');
@@ -16,11 +17,17 @@ public class NamespacedId {
namespace = combined.substring(0, colonIdx);
name = combined.substring(colonIdx + 1);
}
hash = hash(namespace, name);
}
public NamespacedId(String namespace, String name) {
this.namespace = Objects.requireNonNull(namespace);
this.name = Objects.requireNonNull(name);
this.hash = hash(namespace, name);
}
private static int hash(String namespace, String name) {
return 31 * (31 + namespace.hashCode()) + name.hashCode();
}
public String getNamespace() {
@@ -43,12 +50,12 @@ public class NamespacedId {
NamespacedId that = (NamespacedId) o;
return namespace.equals(that.namespace) && name.equals(that.name);
return hash == that.hash && namespace.equals(that.namespace) && name.equals(that.name);
}
@Override
public int hashCode() {
return Objects.hash(namespace, name);
return hash;
}
@Override
@@ -0,0 +1,24 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
import com.gtnewhorizons.angelica.mixins.interfaces.ChunkRenderListReuse;
import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(value = ChunkRenderList.class, remap = false)
public abstract class MixinChunkRenderList implements ChunkRenderListReuse {
@Shadow private int sectionsWithGeometryCount;
@Shadow private int sectionsWithSpritesCount;
@Shadow private int sectionsWithEntitiesCount;
@Shadow private int sectionsNeedingDynamicSortCount;
@Shadow private int size;
@Override
public void angelica$reset() {
this.sectionsWithGeometryCount = 0;
this.sectionsWithSpritesCount = 0;
this.sectionsWithEntitiesCount = 0;
this.sectionsNeedingDynamicSortCount = 0;
this.size = 0;
}
}
@@ -0,0 +1,20 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
import org.embeddedt.embeddium.impl.render.chunk.DefaultChunkRenderer;
import org.embeddedt.embeddium.impl.render.chunk.data.SectionRenderDataStorage;
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.Redirect;
@Mixin(value = DefaultChunkRenderer.class, remap = false)
public abstract class MixinDefaultChunkRenderer {
@Unique private static final SectionRenderDataStorage.BatchCacheParams angelica$CULLED = new SectionRenderDataStorage.BatchCacheParams(true);
@Unique private static final SectionRenderDataStorage.BatchCacheParams angelica$UNCULLED = new SectionRenderDataStorage.BatchCacheParams(false);
@Redirect(method = "render", at = @At(value = "NEW", target = "org/embeddedt/embeddium/impl/render/chunk/data/SectionRenderDataStorage$BatchCacheParams"))
private SectionRenderDataStorage.BatchCacheParams angelica$sharedParams(boolean cull) {
return cull ? angelica$CULLED : angelica$UNCULLED;
}
}
@@ -0,0 +1,33 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniform;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix3f;
import org.joml.Matrix3f;
import org.lwjgl.BufferUtils;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Unique;
import java.nio.FloatBuffer;
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.LWJGL;
@Mixin(value = GlUniformMatrix3f.class, remap = false)
public abstract class MixinGlUniformMatrix3f extends GlUniform<Matrix3f> {
@Unique private final FloatBuffer angelica$buf = BufferUtils.createFloatBuffer(9);
private MixinGlUniformMatrix3f(int index) {
super(index);
}
/**
* @author Angelica
* @reason reuse the upload buffer
*/
@Overwrite
public void set(Matrix3f value) {
value.get(this.angelica$buf);
LWJGL.glUniformMatrix3fv(this.index, false, this.angelica$buf);
}
}
@@ -0,0 +1,33 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniform;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.BufferUtils;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Unique;
import java.nio.FloatBuffer;
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.LWJGL;
@Mixin(value = GlUniformMatrix4f.class, remap = false)
public abstract class MixinGlUniformMatrix4f extends GlUniform<Matrix4fc> {
@Unique private final FloatBuffer angelica$buf = BufferUtils.createFloatBuffer(16);
private MixinGlUniformMatrix4f(int index) {
super(index);
}
/**
* @author Angelica
* @reason reuse the upload buffer
*/
@Overwrite
public void set(Matrix4fc value) {
value.get(this.angelica$buf);
LWJGL.glUniformMatrix4fv(this.index, false, this.angelica$buf);
}
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
import com.gtnewhorizons.angelica.mixins.interfaces.VisibleChunkCollectorPool;
import com.gtnewhorizons.angelica.rendering.celeritas.RenderListPool;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import org.embeddedt.embeddium.impl.render.chunk.lists.RenderListManager;
import org.embeddedt.embeddium.impl.render.chunk.lists.VisibleChunkCollector;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(value = RenderListManager.class, remap = false)
public abstract class MixinRenderListManager {
@Unique private final RenderListPool angelica$pool = new RenderListPool();
@ModifyExpressionValue(method = "submitSearch", at = @At(value = "NEW", target = "org/embeddedt/embeddium/impl/render/chunk/lists/VisibleChunkCollector"))
private VisibleChunkCollector angelica$attachPool(VisibleChunkCollector collector, @Local(argsOnly = true, ordinal = 1) int regionIdsLength) {
((VisibleChunkCollectorPool) collector).angelica$setRecycled(this.angelica$pool.beginSearch(regionIdsLength));
return collector;
}
}
@@ -0,0 +1,26 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
import com.gtnewhorizons.angelica.mixins.interfaces.VisibleChunkCollectorPool;
import com.gtnewhorizons.angelica.rendering.celeritas.RenderListPool;
import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderList;
import org.embeddedt.embeddium.impl.render.chunk.lists.VisibleChunkCollector;
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
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.Redirect;
@Mixin(value = VisibleChunkCollector.class, remap = false)
public abstract class MixinVisibleChunkCollector implements VisibleChunkCollectorPool {
@Unique private ChunkRenderList[] angelica$recycled;
@Override
public void angelica$setRecycled(ChunkRenderList[] slot) {
this.angelica$recycled = slot;
}
@Redirect(method = "createRenderList", at = @At(value = "NEW", target = "org/embeddedt/embeddium/impl/render/chunk/lists/ChunkRenderList"))
private ChunkRenderList angelica$reuseRenderList(RenderRegion region) {
return RenderListPool.acquire(this.angelica$recycled, region);
}
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.threading;
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ThreadedAngelicaChunkBuilderMeshingTask;
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobTyped;
import org.embeddedt.embeddium.impl.render.chunk.compile.tasks.ChunkBuilderTask;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(value = ChunkJobTyped.class, remap = false)
public abstract class MixinChunkJobTyped {
@Shadow @Final private ChunkBuilderTask<?> task;
@Shadow private volatile boolean started;
@ModifyArg(method = "execute", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V"))
private Object angelica$releaseUnstartedContext(Object result) {
if (!this.started && this.task instanceof ThreadedAngelicaChunkBuilderMeshingTask t) t.releaseUnstartedContext();
return result;
}
}
@@ -29,6 +29,7 @@ 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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -128,6 +129,13 @@ public abstract class MixinRenderBlocks implements ExtCeleritasRenderBlocks {
}
}
@Redirect(method = "renderStandardBlockWithColorMultiplier",
at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;getMixedBrightnessForBlock(Lnet/minecraft/world/IBlockAccess;III)I"),
require = 1)
private int angelica$skipDiscardedBrightness(Block block, IBlockAccess world, int x, int y, int z, @Local(ordinal = 0) Tessellator tessellator) {
return this.applyingCeleritasAO && ((StateAwareTessellator) tessellator).angelica$isCeleritasMeshing() ? 0 : block.getMixedBrightnessForBlock(world, x, y, z);
}
@Override
public boolean angelica$shouldApplyCeleritasAO() {
if (!((StateAwareTessellator) TessellatorManager.get()).angelica$isCeleritasMeshing()) return false;
@@ -15,6 +15,8 @@ public class MixinRenderBlocksEmissive {
@Inject(method = "renderStandardBlock", at = @At("TAIL"))
private void angelica$renderEmissiveOverlay(Block block, int x, int y, int z, CallbackInfoReturnable<Boolean> cir) {
if (!EmissiveTextureHelper.hasAnyEmissive()) return;
RenderBlocks self = (RenderBlocks)(Object)this;
IBlockAccess world = self.blockAccess;
if (world == null) return;
@@ -3,8 +3,6 @@ package com.gtnewhorizons.angelica.mixins.early.shaders;
import com.gtnewhorizon.gtnhlib.client.renderer.ITessellatorInstance;
import com.gtnewhorizons.angelica.experimental.surround.Surround;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import net.coderbot.iris.Iris;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
@@ -23,41 +21,40 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public class MixinRenderGlobal {
@Inject(method = "renderSky", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/Tessellator;instance:Lnet/minecraft/client/renderer/Tessellator;"))
private void iris$renderSky$beginNormalSky(float partialTicks, CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
private void iris$renderSky$beginNormalSky(float partialTicks, CallbackInfo ci) {
// None of the vanilla sky is rendered until after this call, so if anything is rendered before, it's CUSTOM_SKY.
pipeline.set(Iris.getPipelineManager().getPipelineNullable());
pipeline.get().setPhase(WorldRenderingPhase.SKY);
Iris.getPipelineManager().getPipelineNullable().setPhase(WorldRenderingPhase.SKY);
}
@Inject(method = "renderSky", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/RenderGlobal;locationSunPng:Lnet/minecraft/util/ResourceLocation;"))
private void iris$setSunRenderStage(float p_72714_1_, CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
pipeline.get().setPhase(WorldRenderingPhase.SUN);
private void iris$setSunRenderStage(float p_72714_1_, CallbackInfo ci) {
Iris.getPipelineManager().getPipelineNullable().setPhase(WorldRenderingPhase.SUN);
}
@Inject(method = "renderSky", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/RenderGlobal;locationMoonPhasesPng:Lnet/minecraft/util/ResourceLocation;"))
private void iris$setMoonRenderStage(float p_72714_1_, CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
pipeline.get().setPhase(WorldRenderingPhase.MOON);
private void iris$setMoonRenderStage(float p_72714_1_, CallbackInfo ci) {
Iris.getPipelineManager().getPipelineNullable().setPhase(WorldRenderingPhase.MOON);
}
@Inject(method = "renderSky", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/WorldProvider;calcSunriseSunsetColors(FF)[F"))
private void iris$setSunsetRenderStage(float p_72714_1_, CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
pipeline.get().setPhase(WorldRenderingPhase.SUNSET);
private void iris$setSunsetRenderStage(float p_72714_1_, CallbackInfo ci) {
Iris.getPipelineManager().getPipelineNullable().setPhase(WorldRenderingPhase.SUNSET);
}
@Inject(method = "renderSky", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/WorldClient;getStarBrightness(F)F"))
private void iris$setStarRenderStage(float p_72714_1_, CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
pipeline.get().setPhase(WorldRenderingPhase.STARS);
private void iris$setStarRenderStage(float p_72714_1_, CallbackInfo ci) {
Iris.getPipelineManager().getPipelineNullable().setPhase(WorldRenderingPhase.STARS);
}
@Inject(method = "renderSky", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/EntityClientPlayerMP;getPosition(F)Lnet/minecraft/util/Vec3;"))
private void iris$setVoidRenderStage(float p_72714_1_, CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
pipeline.get().setPhase(WorldRenderingPhase.VOID);
private void iris$setVoidRenderStage(float p_72714_1_, CallbackInfo ci) {
Iris.getPipelineManager().getPipelineNullable().setPhase(WorldRenderingPhase.VOID);
}
@Inject(method = "renderSky", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/WorldClient;getCelestialAngle(F)F"),
slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/WorldClient;getRainStrength(F)F")))
private void iris$renderSky$tiltSun(float p_72714_1_, CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
GLStateManager.glRotatef(pipeline.get().getSunPathRotation(), 0.0F, 0.0F, 1.0F);
private void iris$renderSky$tiltSun(float p_72714_1_, CallbackInfo ci) {
GLStateManager.glRotatef(Iris.getPipelineManager().getPipelineNullable().getSunPathRotation(), 0.0F, 0.0F, 1.0F);
}
// Sky disc: wrap the glCallList(glSkyList) call — the first glCallList in the surface world branch
@@ -101,6 +101,7 @@ class DeferredMeshSchedulerTest {
assertEquals(0, scheduler.pendingCount());
assertSame(buffers, scheduler.acquireBuffers());
assertSame(slice, scheduler.acquireSlice());
Mockito.verify(slice).releaseContext();
}
@Test
@@ -178,5 +179,6 @@ class DeferredMeshSchedulerTest {
queue.poll().run();
assertSame(slice, scheduler.acquireSlice());
Mockito.verify(slice).releaseContext();
}
}
@@ -0,0 +1,84 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderList;
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RenderListPoolTest {
@Test
void consecutiveGenerationsNeverShareArray() {
RenderListPool pool = new RenderListPool();
ChunkRenderList[] prev = pool.beginSearch(1);
for (int i = 2; i < 40; i++) {
ChunkRenderList[] cur = pool.beginSearch(i % 7 == 0 ? i : 3);
assertNotSame(prev, cur);
prev = cur;
}
}
@Test
void slotGrowsKeepingEntries() {
RenderListPool pool = new RenderListPool();
ChunkRenderList[] small = pool.beginSearch(2);
pool.beginSearch(2);
RenderRegion region = region(1);
ChunkRenderList list = RenderListPool.acquire(small, region);
ChunkRenderList[] grown = pool.beginSearch(16);
assertTrue(grown.length >= 16);
assertSame(list, grown[1]);
pool.beginSearch(16);
assertSame(grown, pool.beginSearch(8));
}
@Test
void acquireStoresNewListAndReplacesForeignRegion() {
ChunkRenderList[] slot = new ChunkRenderList[4];
RenderRegion first = region(2);
ChunkRenderList a = RenderListPool.acquire(slot, first);
assertSame(a, slot[2]);
assertSame(first, a.getRegion());
RenderRegion reused = region(2);
ChunkRenderList b = RenderListPool.acquire(slot, reused);
assertNotSame(a, b);
assertSame(b, slot[2]);
assertSame(reused, b.getRegion());
assertEquals(0, b.size());
}
@Test
void resetCoversExactlyTheDeclaredMutableIntFields() {
final Set<String> expected = new HashSet<>(
Arrays.asList("sectionsWithGeometryCount", "sectionsWithSpritesCount", "sectionsWithEntitiesCount",
"sectionsNeedingDynamicSortCount", "size"));
final Set<String> actual = new HashSet<>();
for (Field f : ChunkRenderList.class.getDeclaredFields()) {
if (f.getType() != int.class) continue;
final int mods = f.getModifiers();
if (Modifier.isStatic(mods) || Modifier.isFinal(mods)) continue;
actual.add(f.getName());
}
assertEquals(expected, actual);
}
private static RenderRegion region(int id) {
final RenderRegion region = Reflect.allocate(RenderRegion.class);
Reflect.set(region, "id", id);
return region;
}
}
@@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
class TesrLayerKeyTest {
@@ -18,8 +19,8 @@ class TesrLayerKeyTest {
private static final TesrShader SHADER = TesrShaders.register("test:layer_key", () -> {}, () -> {});
private static final TesrMaterial.SpecialRender NONE = TesrMaterial.SpecialRender.NONE;
private static final PassOverride NO_PASS = PassOverride.NONE;
private static final PassOverride EYES = new PassOverride(SpecialCondition.ENTITY_EYES, null);
private static final PassOverride TRANSLUCENT = new PassOverride(null, Boolean.TRUE);
private static final PassOverride EYES = PassOverride.of(SpecialCondition.ENTITY_EYES, null, null);
private static final PassOverride TRANSLUCENT = PassOverride.of(null, Boolean.TRUE, null);
@Test
void scratchProbeMatchesCopiedKey() {
@@ -92,4 +93,17 @@ class TesrLayerKeyTest {
assertEquals(key, key.copy());
assertEquals(key.hashCode(), key.copy().hashCode());
}
@Test
void passOverridesAreInterned() {
assertSame(EYES, PassOverride.of(SpecialCondition.ENTITY_EYES, null, null));
assertSame(TRANSLUCENT, PassOverride.of(null, Boolean.TRUE, null));
assertSame(PassOverride.NONE, PassOverride.of(null, null, null));
assertNotSame(PassOverride.of(null, Boolean.TRUE, null), PassOverride.of(null, Boolean.FALSE, null));
final TesrBatchRenderer.LayerKey a = new TesrBatchRenderer.LayerKey().set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, false, PassOverride.of(SpecialCondition.GLINT, Boolean.FALSE, null), 0.0f, 0.0f, DrawState.DISABLED, false, true);
final TesrBatchRenderer.LayerKey b = new TesrBatchRenderer.LayerKey().set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, false, PassOverride.of(SpecialCondition.GLINT, Boolean.FALSE, null), 0.0f, 0.0f, DrawState.DISABLED, false, true);
assertEquals(a, b);
assertEquals(a.hashCode(), b.hashCode());
}
}