mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-25 20:22:22 +00:00
Cut more per-frame allocations in terrain and rendering (#2193)
This commit is contained in:
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);
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+16
@@ -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");
|
||||
|
||||
+44
@@ -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();
|
||||
}
|
||||
+7
@@ -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) {
|
||||
|
||||
+32
-1
@@ -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();
|
||||
|
||||
+1
@@ -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);
|
||||
}
|
||||
}
|
||||
+4
@@ -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;
|
||||
}
|
||||
|
||||
+7
@@ -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);
|
||||
}
|
||||
}
|
||||
+10
-6
@@ -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) {
|
||||
|
||||
+35
-4
@@ -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
|
||||
|
||||
+24
@@ -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;
|
||||
}
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
}
|
||||
+33
@@ -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);
|
||||
}
|
||||
}
|
||||
+33
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
@@ -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;
|
||||
}
|
||||
}
|
||||
+26
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
@@ -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;
|
||||
}
|
||||
}
|
||||
+8
@@ -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;
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+14
-17
@@ -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
|
||||
|
||||
+2
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user