Pool BufferQuad objects

This commit is contained in:
James Seibel
2026-05-30 11:32:46 -05:00
parent ca4d6f158a
commit 16f72066a8
4 changed files with 146 additions and 50 deletions
@@ -41,28 +41,28 @@ public final class BufferQuad
public static final int MAX_QUAD_WIDTH_FOR_EARTH_CURVATURE = LodUtil.CHUNK_WIDTH; public static final int MAX_QUAD_WIDTH_FOR_EARTH_CURVATURE = LodUtil.CHUNK_WIDTH;
public final short x; public short x;
public final short y; public short y;
public final short z; public short z;
public short widthEastWest; public short widthEastWest;
/** This is both North/South and Up/Down since the merging logic is the same either way */ /** This is both North/South and Up/Down since the merging logic is the same either way */
public short widthNorthSouthOrHeight; public short widthNorthSouthOrHeight;
public final int color; public int color;
/** used by the Iris shader mod to determine how each LOD should be rendered */ /** used by the Iris shader mod to determine how each LOD should be rendered */
public final byte irisBlockMaterialId; public byte irisBlockMaterialId;
public final byte skyLight; public byte skyLight;
public final byte blockLight; public byte blockLight;
public final EDhDirection direction; public EDhDirection direction;
public boolean hasError = false; public boolean hasError = false;
// Pre-computed sort keys to avoid recomputing on every comparison // Pre-computed sort keys to avoid recomputing on every comparison
// Slight increase in memory for reduction in cpu usage // Slight increase in memory for reduction in cpu usage
public final long sortKeyEastWest; public long sortKeyEastWest;
public final long sortKeyNorthSouth; public long sortKeyNorthSouth;
@@ -71,15 +71,17 @@ public final class BufferQuad
//=============// //=============//
//region //region
public BufferQuad( public BufferQuad() {}
short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight,
int color, byte irisBlockMaterialId, byte skylight, byte blockLight, public void set(short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight,
EDhDirection direction) int color, byte irisBlockMaterialId, byte skylight, byte blockLight,
EDhDirection direction)
{ {
if (widthEastWest == 0 || widthNorthSouthOrHeight == 0) if (widthEastWest == 0 || widthNorthSouthOrHeight == 0)
{ {
throw new IllegalArgumentException("Size 0 quad!"); throw new IllegalArgumentException("Size 0 quad!");
} }
if (widthEastWest < 0 || widthNorthSouthOrHeight < 0) if (widthEastWest < 0 || widthNorthSouthOrHeight < 0)
{ {
throw new IllegalArgumentException("Negative sized quad!"); throw new IllegalArgumentException("Negative sized quad!");
@@ -89,7 +89,9 @@ public class LodBufferContainer implements AutoCloseable
/** Should be run on a DH thread. */ /** Should be run on a DH thread. */
public static CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync( public static CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(
long pos, IDhClientLevel clientLevel, long pos, IDhClientLevel clientLevel,
LodQuadBuilder builder) ArrayList<ByteBuffer> opaqueBuffers,
ArrayList<ByteBuffer> transparentBuffers
)
{ {
// new upload needed // new upload needed
CompletableFuture<LodBufferContainer> future = new CompletableFuture<>(); CompletableFuture<LodBufferContainer> future = new CompletableFuture<>();
@@ -107,10 +109,6 @@ public class LodBufferContainer implements AutoCloseable
DhSectionPos.getMinCornerBlockZ(pos)); DhSectionPos.getMinCornerBlockZ(pos));
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minCornerBlockPos); LodBufferContainer bufferContainer = new LodBufferContainer(pos, minCornerBlockPos);
// create CPU vertex buffers
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers();
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers();
// update arrays to contain buffers // update arrays to contain buffers
bufferContainer.vboOpaqueWrappers = resizeWrapperArray(bufferContainer.vboOpaqueWrappers, opaqueBuffers.size()); bufferContainer.vboOpaqueWrappers = resizeWrapperArray(bufferContainer.vboOpaqueWrappers, opaqueBuffers.size());
bufferContainer.vboTransparentWrappers = resizeWrapperArray(bufferContainer.vboTransparentWrappers, transparentBuffers.size()); bufferContainer.vboTransparentWrappers = resizeWrapperArray(bufferContainer.vboTransparentWrappers, transparentBuffers.size());
@@ -40,21 +40,13 @@ import org.lwjgl.system.MemoryUtil;
* *
* Note: the magic number 6 you see throughout this method represents the number of sides on a cube. * Note: the magic number 6 you see throughout this method represents the number of sides on a cube.
*/ */
public class LodQuadBuilder public class LodQuadBuilder implements AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
@SuppressWarnings("unchecked") /** ThreadLocal is the simplest way to allow each LOD loading thread to have their own builder */
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6]; private static final ThreadLocal<LodQuadBuilder> THREAD_LOCAL = ThreadLocal.withInitial(LodQuadBuilder::new);
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] transparentQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
private final boolean doTransparency;
private final IClientLevelWrapper clientLevelWrapper;
private final EDhApiDebugRendering debugRenderingMode;
private final EDhApiGrassSideRendering grassSideRenderingMode;
/** the number of bytes for a single vertex */ /** the number of bytes for a single vertex */
public static final int BYTES_PER_VERTEX = 16; public static final int BYTES_PER_VERTEX = 16;
@@ -111,6 +103,26 @@ public class LodQuadBuilder
}; };
//endregion //endregion
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] transparentQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
/**
* Caching the BufferQuad objects reduces overhead slightly. <br>
* Caching is handled per builder (vs globally in {@link BufferQuad} itself)
* to prevent concurrency overhead.
*/
private final ArrayList<BufferQuad> bufferQuadCacheList = new ArrayList<>();
private boolean doTransparency;
private IClientLevelWrapper clientLevelWrapper;
private EDhApiDebugRendering debugRenderingMode;
private EDhApiGrassSideRendering grassSideRenderingMode;
private int premergeCount = 0; private int premergeCount = 0;
@@ -120,20 +132,31 @@ public class LodQuadBuilder
//=============// //=============//
//region //region
public LodQuadBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper) private LodQuadBuilder()
{ {
this.doTransparency = doTransparency;
for (int i = 0; i < 6; i++) for (int i = 0; i < 6; i++)
{ {
this.opaqueQuads[i] = new ArrayList<>(); this.opaqueQuads[i] = new ArrayList<>();
this.transparentQuads[i] = new ArrayList<>(); this.transparentQuads[i] = new ArrayList<>();
} }
}
public static LodQuadBuilder getBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
{
LodQuadBuilder builder = THREAD_LOCAL.get();
builder.set(doTransparency, clientLevelWrapper);
return builder;
}
private void set(boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
{
this.doTransparency = doTransparency;
this.clientLevelWrapper = clientLevelWrapper; this.clientLevelWrapper = clientLevelWrapper;
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRenderingColors.get(); this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRenderingColors.get();
this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get(); this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get();
this.premergeCount = 0;
} }
//endregion //endregion
@@ -167,7 +190,8 @@ public class LodQuadBuilder
quadList = this.opaqueQuads[dir.ordinal()]; quadList = this.opaqueQuads[dir.ordinal()];
} }
BufferQuad quad = new BufferQuad(x, y, z, width, height, color, irisBlockMaterialId, skyLight, blockLight, dir); BufferQuad quad = this.getOrCreateBufferQuad();
quad.set(x, y, z, width, height, color, irisBlockMaterialId, skyLight, blockLight, dir);
if (!quadList.isEmpty() if (!quadList.isEmpty()
&& ( && (
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
@@ -189,7 +213,8 @@ public class LodQuadBuilder
? this.transparentQuads[EDhDirection.UP.ordinal()] ? this.transparentQuads[EDhDirection.UP.ordinal()]
: this.opaqueQuads[EDhDirection.UP.ordinal()]; : this.opaqueQuads[EDhDirection.UP.ordinal()];
BufferQuad quad = new BufferQuad(minX, maxY, minZ, blockWidth, blockWidth, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP); BufferQuad quad = this.getOrCreateBufferQuad();
quad.set(minX, maxY, minZ, blockWidth, blockWidth, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
quadList.add(quad); quadList.add(quad);
} }
@@ -199,7 +224,8 @@ public class LodQuadBuilder
? this.transparentQuads[EDhDirection.DOWN.ordinal()] ? this.transparentQuads[EDhDirection.DOWN.ordinal()]
: this.opaqueQuads[EDhDirection.DOWN.ordinal()]; : this.opaqueQuads[EDhDirection.DOWN.ordinal()];
BufferQuad quad = new BufferQuad(x, y, z, blockWidth, blockWidth, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN); BufferQuad quad = this.getOrCreateBufferQuad();
quad.set(x, y, z, blockWidth, blockWidth, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
quadArray.add(quad); quadArray.add(quad);
} }
@@ -517,4 +543,65 @@ public class LodQuadBuilder
//=====================//
// buffer quad pooling //
//=====================//
//region
private BufferQuad getOrCreateBufferQuad()
{
// start from the back of the list so we don't have
// to move the array around
int index = bufferQuadCacheList.size() - 1;
if (index < 0)
{
// cache empty, create a new object
return new BufferQuad();
}
BufferQuad quad = bufferQuadCacheList.remove(index);
if (quad != null) // shouldn't happen, but just in case
{
return quad;
}
return new BufferQuad();
}
private static void returnQuadsToCache(ArrayList<BufferQuad> quadCache, ArrayList<BufferQuad>[] quadsToReturn)
{
for (int i = 0; i < quadsToReturn.length; i++)
{
// manual add and loop to reduce GC pressure due to addAll() doing unnecessary
// array copies
for (int j = 0; j < quadsToReturn[i].size(); j++)
{
quadCache.add(quadsToReturn[i].get(j));
}
quadsToReturn[i].clear();
}
}
//endregion
//================//
// base overrides //
//================//
//region
// can be used/closed multiple times
@Override
public void close()
{
returnQuadsToCache(this.bufferQuadCacheList, this.opaqueQuads);
returnQuadsToCache(this.bufferQuadCacheList, this.transparentQuads);
}
//endregion
} }
@@ -47,6 +47,8 @@ import org.jetbrains.annotations.Nullable;
import javax.annotation.WillNotClose; import javax.annotation.WillNotClose;
import java.awt.*; import java.awt.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -171,20 +173,26 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
try try
{ {
// build LOD data on a DH thread // build LOD data on a DH thread
LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData(); try (LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData())
if (lodQuadBuilder == null)
{ {
future.complete(null); if (lodQuadBuilder == null)
return;
}
// uploading will primarily happen on the render thread
this.uploadToGpuAsync(future, lodQuadBuilder)
.thenRun(() ->
{ {
// the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers
future.complete(null); future.complete(null);
}); return;
}
// create CPU vertex buffers
ArrayList<ByteBuffer> opaqueBuffers = lodQuadBuilder.makeOpaqueVertexBuffers();
ArrayList<ByteBuffer> transparentBuffers = lodQuadBuilder.makeTransparentVertexBuffers();
// uploading will primarily happen on the render thread
this.uploadToGpuAsync(future, opaqueBuffers, transparentBuffers)
.thenRun(() ->
{
// the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers
future.complete(null);
});
}
} }
catch (Exception e) catch (Exception e)
{ {
@@ -225,7 +233,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get() == EDhApiTransparency.COMPLETE; boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get() == EDhApiTransparency.COMPLETE;
LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.clientLevel.getClientLevelWrapper()); LodQuadBuilder lodQuadBuilder = LodQuadBuilder.getBuilder(enableTransparency, this.clientLevel.getClientLevelWrapper());
// get the adjacent positions // get the adjacent positions
@@ -304,9 +312,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync( private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync(
CompletableFuture<Void> parentFuture, CompletableFuture<Void> parentFuture,
LodQuadBuilder lodQuadBuilder) ArrayList<ByteBuffer> opaqueBuffers,
ArrayList<ByteBuffer> transparentBuffers)
{ {
CompletableFuture<LodBufferContainer> uploadFuture = LodBufferContainer.tryMakeAndUploadBuffersAsync(this.pos, this.clientLevel, lodQuadBuilder); CompletableFuture<LodBufferContainer> uploadFuture = LodBufferContainer.tryMakeAndUploadBuffersAsync(this.pos, this.clientLevel, opaqueBuffers, transparentBuffers);
uploadFuture.whenComplete((bufferContainer, e) -> uploadFuture.whenComplete((bufferContainer, e) ->
{ {
try try