Cut terrain rebuilds and draw-buffer churn (#2312)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-10-29 15:07:17 -07:00
1 parent 08aa37712f
commit bbbdc12b2a
14 files changed
+301 -38

No files matched your search

@@ -110,7 +110,7 @@ public final class GLSMAttribStack {
if (!vaos.vaoMap.containsKey(saved.boundVAO)) return;
GLSMVertexInput.bindVertexArrayInternal(glCtx, saved.boundVAO);
glCtx.clientActiveTextureUnit = saved.clientActiveTextureUnit;
if (vaos.isBufferLive(saved.boundVBO, saved)) {
if (vaos.isSavedVboLive(saved)) {
final int changed = vaos.restoreClientArrays(saved);
for (int pointers = changed & 0xFFFF; pointers != 0; pointers &= pointers - 1) {
GLSMVertexInput.pointAttrib(glCtx, Integer.numberOfTrailingZeros(pointers));
@@ -120,7 +120,7 @@ public final class GLSMAttribStack {
}
if (glCtx.boundVBO != saved.boundVBO) GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, saved.boundVBO);
}
if (vaos.boundEBO != saved.boundEBO && vaos.isBufferLive(saved.boundEBO, saved)) GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, saved.boundEBO);
if (vaos.boundEBO != saved.boundEBO && vaos.isSavedEboLive(saved)) GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, saved.boundEBO);
}
public static void pushState(int mask) {
@@ -64,9 +64,12 @@ public final class GLSMBuffers {
}
}
public static long bufferDeletes;
private static void invalidateDeletedBuffer(int buffer) {
final GLContextState glCtx = ctx();
if (buffer == 0) return;
bufferDeletes++;
final boolean locked = acquireDrawLock();
try {
FfpExtendedAttribs.onDeleteBuffer(glCtx, buffer);
@@ -87,7 +90,7 @@ public final class GLSMBuffers {
}
writeMappedBuffers.remove(buffer);
glCtx.vaos.onDeleteBuffer(buffer);
glCtx.vaos.onDeleteBuffer(buffer, glCtx.clientAttribSavedArrays);
} finally {
if (locked) releaseDrawLock();
}
@@ -123,15 +123,63 @@ public final class VAOManager {
private int clientSideEnabledCount = 0;
private int arrayVersion = 0;
private int versionClock = 0;
private static final int DELETED_NAME_PRUNE_THRESHOLD = 4096;
private static final int DEAD_EPOCH = -1;
private final int pruneThreshold;
private int bufferEpoch = 0;
private final Int2IntOpenHashMap bufferDeletedAt = new Int2IntOpenHashMap();
public VAOManager() {
this(DELETED_NAME_PRUNE_THRESHOLD);
}
VAOManager(int pruneThreshold) {
this.pruneThreshold = pruneThreshold;
}
private void touch() {
arrayVersion = ++versionClock;
}
private boolean isBufferLive(int buffer, int sinceEpoch) {
return bufferDeletedAt.get(buffer) <= sinceEpoch;
return buffer == 0 || bufferDeletedAt.get(buffer) <= sinceEpoch;
}
private int retiredEpoch(int buffer, int epoch, boolean rebase) {
if (rebase) return epoch == DEAD_EPOCH ? DEAD_EPOCH : 0;
return isBufferLive(buffer, epoch) ? epoch : DEAD_EPOCH;
}
private void retire(@Nullable Attrib[] slots, boolean rebase) {
if (slots == null) return;
for (final Attrib a : slots) {
if (a != null) a.epoch = retiredEpoch(a.vboId, a.epoch, rebase);
}
}
private void retireAll(ClientArrays[] savedStack, boolean rebase) {
for (final VAOData data : vaoMap.values()) {
retire(data.attribs, rebase);
retire(data.generic, rebase);
retire(data.conventional, rebase);
}
retire(currentAttribs, rebase);
retire(currentGeneric, rebase);
retire(currentConventional, rebase);
for (final ClientArrays saved : savedStack) {
if (saved == null) continue;
saved.vboEpoch = retiredEpoch(saved.boundVBO, saved.vboEpoch, rebase);
saved.eboEpoch = retiredEpoch(saved.boundEBO, saved.eboEpoch, rebase);
retire(saved.generic, rebase);
retire(saved.conventional, rebase);
}
}
private void pruneDeletedNames(ClientArrays[] savedStack) {
retireAll(savedStack, false);
bufferDeletedAt.clear();
retireAll(savedStack, true);
bufferEpoch = 0;
}
public boolean isBufferLive(Attrib a) {
@@ -206,9 +254,10 @@ public final class VAOManager {
return detached;
}
public void onDeleteBuffer(int buffer) {
public void onDeleteBuffer(int buffer, ClientArrays[] savedStack) {
if (buffer == 0) return;
bufferDeletedAt.put(buffer, ++bufferEpoch);
if (bufferEpoch >= pruneThreshold) pruneDeletedNames(savedStack);
if (currentAttribs != null) {
boolean detached = detach(currentGeneric != null ? currentGeneric : currentAttribs, buffer);
if (currentConventional != null) detached |= detach(currentConventional, buffer);
@@ -447,7 +496,8 @@ public final class VAOManager {
public int boundEBO;
public int clientActiveTextureUnit;
int arrayVersion;
int bufferEpoch;
int vboEpoch;
int eboEpoch;
int vertexFlags;
int genericEnabled;
int conventionalEnabled;
@@ -463,13 +513,18 @@ public final class VAOManager {
saved[i].copyPointer(source);
}
public boolean isBufferLive(int buffer, ClientArrays saved) {
return isBufferLive(buffer, saved.bufferEpoch);
public boolean isSavedVboLive(ClientArrays saved) {
return isBufferLive(saved.boundVBO, saved.vboEpoch);
}
public boolean isSavedEboLive(ClientArrays saved) {
return isBufferLive(saved.boundEBO, saved.eboEpoch);
}
public void saveClientArrays(ClientArrays out) {
out.arrayVersion = arrayVersion;
out.bufferEpoch = bufferEpoch;
out.vboEpoch = bufferEpoch;
out.eboEpoch = bufferEpoch;
out.vertexFlags = currentVertexFlags;
out.genericEnabled = genericEnabled;
out.conventionalEnabled = conventionalEnabled;
@@ -158,6 +158,8 @@ final class GLSMOwners {
static final String[][] FIELDS = {
{"com/gtnewhorizons/angelica/glsm/GLSMAttribStack",
"attribBackendCalls", "attribDiscards", "attribPushes", "attribSlotsSaved", "attribValueRestores"},
{"com/gtnewhorizons/angelica/glsm/GLSMBuffers",
"bufferDeletes"},
{"com/gtnewhorizons/angelica/glsm/GLSMDraws",
"drawCalls"},
{"com/gtnewhorizons/angelica/glsm/GLSMFramebuffer",
@@ -487,29 +487,39 @@ public class GLSM_ClientArrays_GLTest {
}
@Test
void popKeepsCurrentArraysWhenTheSavedArrayBufferWasDeleted() {
void popKeepsCurrentStateWhenTheSavedBuffersWereDeleted() {
final int vbo2 = GLSMBuffers.glGenBuffers();
final int ebo = GLSMBuffers.glGenBuffers();
final int ebo2 = GLSMBuffers.glGenBuffers();
try {
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLSMTextures.glClientActiveTexture(GL13.GL_TEXTURE0);
GLSMVertexInput.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLSMAttribStack.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo2);
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo2);
GLSMVertexInput.glVertexPointer(2, GL11.GL_SHORT, 4, 0L);
GLSMTextures.glClientActiveTexture(GL13.GL_TEXTURE1);
GLSMBuffers.glDeleteBuffers(vbo);
GLSMBuffers.glDeleteBuffers(ebo);
GLSMAttribStack.glPopClientAttrib();
assertEquals(2, GLSMQueries.glGetInteger(GL11.GL_VERTEX_ARRAY_SIZE), "array state is not restored once its array buffer is gone");
assertEquals(vbo2, GLSMQueries.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING));
assertEquals(vbo2, GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING));
assertEquals(ebo2, GLSMQueries.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING), "a deleted element buffer is not rebound by the pop");
assertEquals(ebo2, GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
assertEquals(GL13.GL_TEXTURE0, GLSMQueries.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
} finally {
drainClientAttribStack();
GLSMTextures.glClientActiveTexture(GL13.GL_TEXTURE0);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GLSMBuffers.glDeleteBuffers(vbo2);
GLSMBuffers.glDeleteBuffers(ebo2);
GLSMBuffers.glDeleteBuffers(ebo);
}
}
@@ -1,10 +1,14 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import java.util.Random;
@@ -15,6 +19,7 @@ public class VAOManagerEboIndexTest {
private static final int DEFAULT_VAO = 0;
private static final int NO_VAO = -1;
private static final VAOManager.ClientArrays[] NO_SAVED = new VAOManager.ClientArrays[0];
private final VAOManager vaos = new VAOManager();
private final Int2IntOpenHashMap expectedEbo = new Int2IntOpenHashMap();
@@ -62,7 +67,7 @@ public class VAOManagerEboIndexTest {
}
private void deleteBuffer(int buffer) {
vaos.onDeleteBuffer(buffer);
vaos.onDeleteBuffer(buffer, NO_SAVED);
for (int vao : expectedEbo.keySet().toIntArray()) {
if (expectedEbo.get(vao) == buffer) expectedEbo.put(vao, 0);
}
@@ -126,4 +131,190 @@ public class VAOManagerEboIndexTest {
}
assertTrue(sharedEboDeletes > 20, "op stream never freed a multi-owner EBO (hits: " + sharedEboDeletes + ")");
}
private static final int PRUNE_INTERVAL = 4;
private static final int OPS = 40_000;
private static final int SLOTS = 4;
private static final int[] CONVS = {VAOManager.CONV_POSITION, VAOManager.CONV_COLOR, VAOManager.CONV_SECONDARY_COLOR, VAOManager.CONV_TEX0};
private static final String[] CURRENT_FIELDS = {"currentAttribs", "currentGeneric", "currentConventional"};
private static final int STACK_DEPTH = 16;
private static final int MAX_VAOS = 8;
private static final int MAX_LIVE_BUFFERS = 24;
private final VAOManager pruned = new VAOManager(PRUNE_INTERVAL);
private final VAOManager oracle = new VAOManager(OPS + 1);
private final VAOManager[] pair = {pruned, oracle};
private final VAOManager.ClientArrays[][] stacks = new VAOManager.ClientArrays[pair.length][STACK_DEPTH];
private int deadSlotChecks;
private static int pick(Random random, IntArrayList list) {
return list.getInt(random.nextInt(list.size()));
}
private int staleName(Random random, IntArrayList vaoIds, IntLinkedOpenHashSet freeNames) {
final VAOManager.VAOData data = oracle.vaoMap.get(pick(random, vaoIds));
final VAOManager.Attrib[] slots = random.nextBoolean() ? data.attribs : data.conventional;
if (slots == null) return 0;
final VAOManager.Attrib a = slots[random.nextInt(slots.length)];
return a != null && a.vboId != 0 && freeNames.remove(a.vboId) ? a.vboId : 0;
}
private void bindBoth(int vao) {
if (vao == currentVao) return;
for (VAOManager m : pair) m.onBindVertexArrayPre(vao);
currentVao = vao;
}
private static void restore(VAOManager m, VAOManager.ClientArrays saved) {
if (m.isSavedVboLive(saved)) m.restoreClientArrays(saved);
if (m.boundEBO != saved.boundEBO && m.isSavedEboLive(saved)) m.onBindEBO(saved.boundEBO);
}
private static String where(int op, String kind, int id, int slot) {
return "op " + op + ": " + kind + " " + id + " slot " + slot;
}
private void assertSameSlot(int op, String kind, int id, int slot, VAOManager.Attrib p, VAOManager.Attrib o, boolean countDead) {
assertEquals(o == null, p == null, () -> where(op, kind, id, slot) + " presence");
if (o == null) return;
assertEquals(o.vboId, p.vboId, () -> where(op, kind, id, slot) + " vbo");
final boolean live = oracle.isBufferLive(o);
assertEquals(live, pruned.isBufferLive(p), () -> where(op, kind, id, slot) + " liveness of buffer " + o.vboId);
if (countDead && !live && o.vboId != 0) deadSlotChecks++;
}
private void assertSameSlots(int op, String kind, int id, VAOManager.Attrib[] p, VAOManager.Attrib[] o, boolean countDead) {
assertEquals(o == null, p == null, () -> where(op, kind, id, -1) + " array presence");
if (o == null) return;
for (int i = 0; i < o.length; i++) assertSameSlot(op, kind, id, i, p[i], o[i], countDead);
}
private void assertSameLiveness(int op, int sp) {
assertEquals(oracle.vaoMap.keySet(), pruned.vaoMap.keySet(), () -> "op " + op + ": vao sets differ");
for (var entry : oracle.vaoMap.int2ObjectEntrySet()) {
final int vao = entry.getIntKey();
final VAOManager.VAOData o = entry.getValue();
final VAOManager.VAOData p = pruned.vaoMap.get(vao);
final boolean countDead = vao != currentVao;
assertSameSlots(op, "vao attribs", vao, p.attribs, o.attribs, countDead);
assertSameSlots(op, "vao generic", vao, p.generic, o.generic, countDead);
assertSameSlots(op, "vao conventional", vao, p.conventional, o.conventional, countDead);
}
for (String field : CURRENT_FIELDS) {
assertSameSlots(op, field, currentVao, Reflect.get(pruned, field), Reflect.get(oracle, field), false);
}
for (int s = 0; s < sp; s++) {
final VAOManager.ClientArrays p = stacks[0][s];
final VAOManager.ClientArrays o = stacks[1][s];
final int depth = s;
assertEquals(oracle.isSavedVboLive(o), pruned.isSavedVboLive(p), () -> "op " + op + ": saved " + depth + " vbo liveness");
assertEquals(oracle.isSavedEboLive(o), pruned.isSavedEboLive(p), () -> "op " + op + ": saved " + depth + " ebo liveness");
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if ((o.genericPresent >> i & 1) != 0) assertSameSlot(op, "saved generic", s, i, p.generic[i], o.generic[i], true);
}
for (int i = 0; i < VAOManager.CONV_COUNT; i++) {
if ((o.conventionalPresent >> i & 1) != 0) assertSameSlot(op, "saved conventional", s, i, p.conventional[i], o.conventional[i], true);
}
}
}
@Test
void prunedDeletedNamesAnswerLivenessLikeAnUnprunedReference() {
final Random random = new Random(0x5EED);
for (VAOManager m : pair) m.init(DEFAULT_VAO);
for (VAOManager.ClientArrays[] stack : stacks) {
for (int s = 0; s < STACK_DEPTH; s++) stack[s] = new VAOManager.ClientArrays();
}
final IntArrayList vaoIds = IntArrayList.of(DEFAULT_VAO);
final IntArrayList liveBuffers = new IntArrayList();
final IntLinkedOpenHashSet freeNames = new IntLinkedOpenHashSet();
final IntOpenHashSet everDeleted = new IntOpenHashSet();
int sp = 0;
int nextVao = 1;
int nextBuffer = 1;
int prunes = 0;
int reusedDeletes = 0;
for (int op = 0; op < OPS; op++) {
final int epochBefore = Reflect.get(pruned, "bufferEpoch");
switch (random.nextInt(11)) {
case 0, 1 -> {
if (liveBuffers.size() >= MAX_LIVE_BUFFERS) break;
final int stale = random.nextInt(3) == 0 ? staleName(random, vaoIds, freeNames) : 0;
liveBuffers.add(stale != 0 ? stale : !freeNames.isEmpty() && random.nextInt(3) == 0 ? freeNames.removeLastInt() : nextBuffer++);
}
case 2, 3 -> {
if (liveBuffers.isEmpty()) break;
final int buffer = liveBuffers.removeInt(random.nextInt(liveBuffers.size()));
if (!everDeleted.add(buffer)) reusedDeletes++;
for (int m = 0; m < pair.length; m++) {
if (pair[m].boundEBO == buffer) pair[m].onBindEBO(0);
pair[m].onDeleteBuffer(buffer, stacks[m]);
}
freeNames.add(buffer);
}
case 4 -> {
final int index = random.nextInt(SLOTS);
final int buffer = liveBuffers.isEmpty() || random.nextInt(8) == 0 ? 0 : pick(random, liveBuffers);
for (VAOManager m : pair) m.setAttribute(index, 4, GL11.GL_FLOAT, false, 0, 0L, buffer);
}
case 5 -> {
final int conv = CONVS[random.nextInt(CONVS.length)];
final int buffer = liveBuffers.isEmpty() || random.nextInt(8) == 0 ? 0 : pick(random, liveBuffers);
for (VAOManager m : pair) m.setConventional(conv, 4, GL11.GL_FLOAT, false, 0, 0L, buffer);
}
case 6 -> {
final int vao;
if (vaoIds.size() < MAX_VAOS && random.nextBoolean()) {
vao = nextVao++;
vaoIds.add(vao);
} else {
vao = pick(random, vaoIds);
}
bindBoth(vao);
}
case 7 -> {
final int ebo = liveBuffers.isEmpty() || random.nextInt(4) == 0 ? 0 : pick(random, liveBuffers);
for (VAOManager m : pair) m.onBindEBO(ebo);
}
case 8 -> {
if (sp == STACK_DEPTH) break;
final int vbo = liveBuffers.isEmpty() || random.nextInt(4) == 0 ? 0 : pick(random, liveBuffers);
for (int m = 0; m < pair.length; m++) {
final VAOManager.ClientArrays saved = stacks[m][sp];
saved.boundVAO = currentVao;
saved.boundVBO = vbo;
saved.boundEBO = pair[m].boundEBO;
pair[m].saveClientArrays(saved);
}
sp++;
}
case 9 -> {
if (sp == 0) break;
sp--;
final int vao = stacks[1][sp].boundVAO;
if (!oracle.vaoMap.containsKey(vao)) break;
bindBoth(vao);
for (int m = 0; m < pair.length; m++) restore(pair[m], stacks[m][sp]);
}
case 10 -> {
if (vaoIds.size() == 1) break;
final int vao = vaoIds.removeInt(1 + random.nextInt(vaoIds.size() - 1));
for (VAOManager m : pair) m.onDeleteVertexArray(vao);
if (vao == currentVao) {
currentVao = NO_VAO;
bindBoth(DEFAULT_VAO);
}
}
}
final int epoch = Reflect.get(pruned, "bufferEpoch");
final int at = op;
assertTrue(epoch < PRUNE_INTERVAL, () -> "op " + at + ": epoch " + epoch + " escaped the prune interval");
if (epoch < epochBefore) prunes++;
assertSameLiveness(op, sp);
}
assertTrue(prunes >= 800, "op stream pruned only " + prunes + " times");
assertTrue(deadSlotChecks >= 400_000, "op stream compared only " + deadSlotChecks + " dead slots");
assertTrue(reusedDeletes >= 1_000, "op stream deleted only " + reusedDeletes + " reused names");
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ lwjgl3 = "3.4.2"
lwjgl3Release = "3.4.2"
lwjgl3ify = "3.0.31"
celeritas = "2.5.13-GTNH"
celeritas = "2.5.14-GTNH"
gtnhlib = "0.11.36"
eventbus = "7.0-beta.15"
glslTransformLib = "0.2.0-33.g3ec381e"
+3 -1
View File
@@ -14,9 +14,11 @@ PLOTS = [
"items.bail.ineligible", "items.bail.material", "items.bail.isbrh", "items.bail.blockState", "items.bail.notAllowed", "items.bail.template", "items.bail.queue",
"particles.direct", "particles.captured", "particles.spilled", "particles.undecodable", "particles.draws", "sdl.renderPasses", "sdl.submits", "sdl.gpuWaitUs",
"sdl.acquireWaitUs",
"glsm.bufferDeletes", "draw.batchRebuilds.translucent", "draw.batchRebuilds", "terrain.regionsDrawn",
"sdl.bufferCreates", "sdl.bufferDeletes", "sdl.bufferPoolHits",
"weather.strength", "weather.quads", "weather.drawCalls", "weather.micros",
]
ZONES = ["entityModelParts", "tesrBatch", "entityLayerLoop", "entityDeferred", "tesrOpaque", "particlePass", "cl:weather", "streamingEndFrame"]
ZONES = ["entityModelParts", "tesrBatch", "entityLayerLoop", "entityDeferred", "tesrOpaque", "particlePass", "cl:weather", "streamingEndFrame", "chunkAssembleRegion"]
CLOCK = "gl.drawCalls"
@@ -807,6 +807,7 @@ public final class ResourceManager {
final ByteBuffer droppedPboStaging;
final ByteBuffer droppedPlainStaging;
final PersistentMapping droppedPm;
final boolean hadEboShadow;
wLock.lock();
try {
final long handle = bufferHandles.remove(glId);
@@ -820,7 +821,7 @@ public final class ResourceManager {
droppedPlainStaging = removePlainMappingLocked(glId);
droppedPboStaging = pboStagingData.remove(glId);
deleteUboShadow(glId);
deleteEboShadow(glId);
hadEboShadow = deleteEboShadow(glId);
deleteArrayShadow(glId);
if (handle != 0) {
returnBufferToPool(handle, usage, size);
@@ -829,6 +830,7 @@ public final class ResourceManager {
} finally {
wLock.unlock();
}
if (hadEboShadow) invalidateSplitCacheFor(glId);
if (droppedPboStaging != null) {
memFree(droppedPboStaging);
}
@@ -1277,11 +1279,12 @@ public final class ResourceManager {
try { return eboShadowVersions.get(bufferGlId); } finally { rLock.unlock(); }
}
private void deleteEboShadow(int bufferGlId) {
private boolean deleteEboShadow(int bufferGlId) {
final ByteBuffer existing = eboShadows.remove(bufferGlId);
if (existing != null) memFree(existing);
eboShadowVersions.remove(bufferGlId);
eboShadowWanted.remove(bufferGlId);
return existing != null;
}
public boolean canShadowArrayBuffer(int size) {
@@ -514,7 +514,6 @@ public enum Mixins implements IMixins {
, "celeritas.terrain.MixinShaderLoader"
, "celeritas.terrain.MixinGlUniformMatrix4f"
, "celeritas.terrain.MixinGlUniformMatrix3f"
, "celeritas.terrain.MixinDefaultChunkRenderer"
)
),
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLSMAttribStack;
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
import com.gtnewhorizons.angelica.glsm.GLSMPrograms;
import com.gtnewhorizons.angelica.glsm.GLSMTextureLifetime;
@@ -40,6 +41,7 @@ public final class TracyFramePlots {
private static final PlotDelta P_GL_TEX_BIND_MISSES = new PlotDelta("gl.texBindMisses");
private static final PlotDelta P_GL_PROGRAM_SWITCHES = new PlotDelta("gl.programSwitches");
private static final long P_GL_TEX_AWAITING_RELEASE = Tracy.plotHandle("gl.texAwaitingRelease");
private static final PlotDelta P_GLSM_BUFFER_DELETES = new PlotDelta("glsm.bufferDeletes");
private static final PlotDelta P_GLSM_BRACKETS = new PlotDelta("glsm.brackets");
private static final PlotDelta P_GLSM_COW_SLOTS = new PlotDelta("glsm.cowSlots");
private static final PlotDelta P_GLSM_POP_RESTORES = new PlotDelta("glsm.popRestores");
@@ -62,6 +64,7 @@ public final class TracyFramePlots {
private static final long P_VOX_RANGES = Tracy.plotHandle("voxelization.ranges");
private static final long P_VOX_REGIONS = Tracy.plotHandle("voxelization.regions");
private static final long P_DRAW_BATCH_REBUILDS = Tracy.plotHandle("draw.batchRebuilds");
private static final long P_DRAW_BATCH_REBUILDS_TRANSLUCENT = Tracy.plotHandle("draw.batchRebuilds.translucent");
private static final PlotDelta P_DRAW_INDEX_BUFFER_GROWTHS = new PlotDelta("draw.indexBufferGrowths");
private static final long P_CULL_COMMANDS_SUBMITTED = Tracy.plotHandle("cull.commandsSubmitted");
@@ -197,6 +200,7 @@ public final class TracyFramePlots {
P_GL_TEX_BIND_MISSES.plot(GLSMTextures.texBindMisses);
P_GL_PROGRAM_SWITCHES.plot(GLSMPrograms.programSwitches);
Tracy.plotInt(P_GL_TEX_AWAITING_RELEASE, GLSMTextureLifetime.deferredTexturesAwaitingRelease());
P_GLSM_BUFFER_DELETES.plot(GLSMBuffers.bufferDeletes);
P_GLSM_BRACKETS.plot(GLSMAttribStack.attribPushes);
P_GLSM_COW_SLOTS.plot(GLSMAttribStack.attribSlotsSaved);
P_GLSM_POP_RESTORES.plot(GLSMAttribStack.attribValueRestores);
@@ -214,6 +218,7 @@ public final class TracyFramePlots {
Tracy.plotInt(P_TERRAIN_DRAW_COMMANDS, TerrainDrawStats.takeCommandsSubmitted());
Tracy.plotInt(P_TERRAIN_REGIONS_DRAWN, TerrainDrawStats.takeRegionsDrawn());
Tracy.plotInt(P_DRAW_BATCH_REBUILDS, TerrainDrawStats.takeBatchRebuilds());
Tracy.plotInt(P_DRAW_BATCH_REBUILDS_TRANSLUCENT, TerrainDrawStats.takeBatchRebuildsTranslucent());
Tracy.plotInt(P_VOX_ENCODERS, SdlShadowVoxelizationSink.takeEncoders());
Tracy.plotInt(P_VOX_RANGES, SdlShadowVoxelizationSink.takeRanges());
Tracy.plotInt(P_VOX_REGIONS, SdlShadowVoxelizationSink.takeRegions());
@@ -305,6 +305,11 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
}
}
@Override
protected SectionRenderDataStorage.BatchCacheParams batchCacheParams(boolean useBlockFaceCulling) {
return SectionRenderDataStorage.BatchCacheParams.of(useBlockFaceCulling, ShadowRenderingState.areShadowsCurrentlyBeingRendered() ? 1 : 0);
}
@Override
protected @Nullable CachedBatch getRegionBatch(CommandList commandList, RenderRegion region,
SectionRenderDataStorage storage, ChunkRenderList renderList,
@@ -327,7 +332,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
try {
final CachedBatch cached = super.getRegionBatch(commandList, region, storage, renderList, occlusionCamera,
renderPass, useBlockFaceCulling, cacheParams);
if (cached != before) TerrainDrawStats.recordRebuild();
if (cached != before) TerrainDrawStats.recordRebuild(renderPass.isSorted());
final MultiDrawBatch batch = cached != null ? cached.getBatch() : null;
if (batch != null && !batch.isEmpty()) TerrainDrawStats.recordBatch(batch.size());
return cached;
@@ -8,6 +8,7 @@ public final class TerrainDrawStats {
private static int commandsSubmitted;
private static int regionsDrawn;
private static int batchRebuilds;
private static int batchRebuildsTranslucent;
private static int cullCommandsSubmitted;
private static int cullRegionsDrawn;
@@ -35,8 +36,9 @@ public final class TerrainDrawStats {
regionsDrawn++;
}
public static void recordRebuild() {
public static void recordRebuild(boolean translucent) {
batchRebuilds++;
if (translucent) batchRebuildsTranslucent++;
}
public static void recordCullRegion(int commandCount) {
@@ -76,6 +78,12 @@ public final class TerrainDrawStats {
return n;
}
public static int takeBatchRebuildsTranslucent() {
final int n = batchRebuildsTranslucent;
batchRebuildsTranslucent = 0;
return n;
}
public static int takeCommandsSubmitted() {
final int n = commandsSubmitted;
commandsSubmitted = 0;
@@ -1,20 +0,0 @@
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;
}
}