Improve Buffer uploading speed and remove buffer upload thread

This commit is contained in:
James Seibel
2024-11-09 08:48:18 -06:00
parent ea1d79a1a6
commit adce15f648
7 changed files with 57 additions and 109 deletions
@@ -19,9 +19,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
@@ -30,7 +28,6 @@ import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
@@ -46,9 +43,6 @@ import java.util.concurrent.*;
public class ColumnRenderBuffer implements AutoCloseable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
/** number of bytes a single quad takes */
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4;
@@ -59,63 +53,81 @@ public class ColumnRenderBuffer implements AutoCloseable
public final DhBlockPos pos;
public final DhBlockPos blockPos;
public boolean buffersUploaded = false;
private GLVertexBuffer[] vbos;
private GLVertexBuffer[] vbosTransparent;
private CompletableFuture<ColumnRenderBuffer> uploadFuture = null;
//==============//
// constructors //
//==============//
public ColumnRenderBuffer(DhBlockPos pos)
public ColumnRenderBuffer(DhBlockPos blockPos)
{
this.pos = pos;
this.blockPos = blockPos;
this.vbos = new GLVertexBuffer[0];
this.vbosTransparent = new GLVertexBuffer[0];
}
//==================//
// buffer uploading //
//==================//
/** Should be run on a DH thread. */
public void makeAndUploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
public synchronized CompletableFuture<ColumnRenderBuffer> makeAndUploadBuffersAsync(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod)
{
LodUtil.assertTrue(DhApi.isDhThread(), "Buffer uploading needs to be done on a DH thread to prevent locking up any MC threads.");
if (this.uploadFuture != null)
{
return this.uploadFuture;
}
this.uploadFuture = new CompletableFuture<>();
// make the buffers
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers();
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers();
vbos = resizeBuffer(vbos, opaqueBuffers.size());
vbosTransparent = resizeBuffer(vbosTransparent, transparentBuffers.size());
this.vbos = resizeBuffer(this.vbos, opaqueBuffers.size());
this.vbosTransparent = resizeBuffer(this.vbosTransparent, transparentBuffers.size());
// upload on MC's render thread
CompletableFuture<Void> uploadFuture = new CompletableFuture<>();
GLProxy.getInstance().queueRunningOnRenderThread(() ->
{
try
{
uploadBuffersDirect(vbos, opaqueBuffers, gpuUploadMethod);
uploadBuffersDirect(vbosTransparent, transparentBuffers, gpuUploadMethod);
if (Thread.interrupted())
{
throw new InterruptedException();
}
uploadBuffersDirect(this.vbos, opaqueBuffers, gpuUploadMethod);
uploadBuffersDirect(this.vbosTransparent, transparentBuffers, gpuUploadMethod);
this.buffersUploaded = true;
uploadFuture.complete(null);
this.uploadFuture.complete(this);
this.uploadFuture = null;
}
catch (InterruptedException e)
catch (InterruptedException ignore)
{
throw new CompletionException(e);
this.uploadFuture.complete(this);
this.uploadFuture = null;
}
catch (Exception e)
{
LOGGER.error("Unexpected issue uploading buffer ["+this.blockPos +"], error: ["+e.getMessage()+"].", e);
this.uploadFuture.completeExceptionally(e);
this.uploadFuture = null;
}
finally
{
@@ -133,22 +145,7 @@ public class ColumnRenderBuffer implements AutoCloseable
}
});
try
{
// wait for the upload to finish
uploadFuture.get(5_000, TimeUnit.MILLISECONDS);
}
catch (ExecutionException e)
{
LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
}
catch (TimeoutException e)
{
// timeouts can be ignored because it generally means the
// MC Render thread executor was closed
//LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
}
return this.uploadFuture;
}
private static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
{
@@ -224,7 +221,7 @@ public class ColumnRenderBuffer implements AutoCloseable
public boolean renderOpaque(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{
boolean hasRendered = false;
renderContext.setModelViewMatrixOffset(this.pos, renderEventParam);
renderContext.setModelViewMatrixOffset(this.blockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbos)
{
if (vbo == null)
@@ -252,7 +249,7 @@ public class ColumnRenderBuffer implements AutoCloseable
try
{
// can throw an IllegalStateException if the GL program was freed before it should've been
renderContext.setModelViewMatrixOffset(this.pos, renderEventParam);
renderContext.setModelViewMatrixOffset(this.blockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
@@ -273,7 +270,7 @@ public class ColumnRenderBuffer implements AutoCloseable
}
catch (IllegalStateException e)
{
LOGGER.error("renderContext program doesn't exist for pos: "+this.pos, e);
LOGGER.error("renderContext program doesn't exist for pos: "+this.blockPos, e);
}
return hasRendered;
@@ -115,62 +115,17 @@ public class ColumnRenderBufferBuilder
LodQuadBuilder quadBuilder
)
{
// TODO put into a single future/thread so it can be easily canceled
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(pos)));
CompletableFuture<ColumnRenderBuffer> uploadFuture = buffer.makeAndUploadBuffersAsync(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
uploadFuture.whenComplete((uploadedBuffer, exception) ->
{
// one or more of the thread pools has been shut down
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
try
{
return CompletableFuture.supplyAsync(() ->
{
try
{
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(pos)));
try
{
buffer.makeAndUploadBuffers(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
if (buffer.buffersUploaded)
{
return buffer;
}
else
{
buffer.close();
return null;
}
}
catch (Exception e)
{
buffer.close();
throw e;
}
}
catch (InterruptedException e)
{
throw UncheckedInterruptedException.convert(e);
}
catch (Throwable e3)
{
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(pos)+"], error: [" + e3.getMessage() + "].", e3);
throw e3;
}
}, bufferUploaderExecutor);
}
catch (RejectedExecutionException ignore)
{
// shouldn't happen, but just in case
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
// clean up if not uploaded
if (!uploadedBuffer.buffersUploaded)
{
uploadedBuffer.close();
}
});
return uploadFuture;
}
private static void makeLodRenderData(
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
@@ -26,7 +26,11 @@ import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.tasks.*;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.InProgressWorldGenTaskGroup;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTask;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTaskGroup;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
@@ -77,7 +77,6 @@ public class F3Screen
ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor();
ThreadPoolExecutor lodBuilderPool = ThreadPoolUtil.getChunkToLodBuilderExecutor();
ThreadPoolExecutor bufferBuilderPool = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderPool = ThreadPoolUtil.getBufferUploaderExecutor();
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
Iterable<? extends IDhLevel> levelIterator = world.getAllLoadedLevels();
@@ -96,7 +95,6 @@ public class F3Screen
messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
messageList.add(getThreadPoolStatString("LOD Builder", lodBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Builder", bufferBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Uploader", bufferUploaderPool));
messageList.add("");
// chunk updates
messageList.add(SharedApi.INSTANCE.getDebugMenuString());
@@ -274,10 +274,11 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, this.pos, lodQuadBuilder);
return this.bufferUploadFuture.thenCompose((buffer) ->
{
// needed to clean up the old data
ColumnRenderBuffer previousBuffer = this.renderBuffer;
// upload complete, clean up the old data if
this.renderBuffer = buffer;
// upload complete
this.renderBuffer = buffer.buffersUploaded ? buffer : null;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
@@ -243,7 +243,7 @@ public class GLProxy
// only try running for 4ms at a time to (hopefully) prevent random lag spikes
if (runDuration > 4_000_000)
{
//break;
break;
}
@@ -56,11 +56,6 @@ public class ThreadPoolUtil
@Nullable
public static ThreadPoolExecutor getWorldGenExecutor() { return worldGenThreadPool.executor; }
public static final String BUFFER_UPLOADER_THREAD_NAME = "Buffer Uploader";
private static ThreadPoolExecutor bufferUploaderThreadPool;
@Nullable
public static ThreadPoolExecutor getBufferUploaderExecutor() { return bufferUploaderThreadPool; }
public static final String CLEANUP_THREAD_NAME = "Cleanup";
private static ThreadPoolExecutor cleanupThreadPool;
@Nullable
@@ -118,7 +113,6 @@ public class ThreadPoolUtil
updatePropagatorThreadPool = new ConfigThreadPool(UPDATE_PROPAGATOR_THREAD_FACTORY, Config.Common.MultiThreading.numberOfUpdatePropagatorThreads, Config.Common.MultiThreading.runTimeRatioForUpdatePropagatorThreads, null);
worldGenThreadPool = new ConfigThreadPool(WORLD_GEN_THREAD_FACTORY, Config.Common.MultiThreading.numberOfWorldGenerationThreads, Config.Common.MultiThreading.runTimeRatioForWorldGenerationThreads, null);
networkCompressionThreadPool = new ConfigThreadPool(NETWORK_COMPRESSION_THREAD_FACTORY, Config.Common.MultiThreading.numberOfNetworkCompressionThreads, Config.Common.MultiThreading.runTimeRatioForNetworkCompressionThreads, null);
bufferUploaderThreadPool = ThreadUtil.makeSingleThreadPool(BUFFER_UPLOADER_THREAD_NAME);
cleanupThreadPool = ThreadUtil.makeSingleThreadPool(CLEANUP_THREAD_NAME);
beaconCullingThreadPool = ThreadUtil.makeSingleThreadPool(BEACON_CULLING_THREAD_NAME);
@@ -160,7 +154,6 @@ public class ThreadPoolUtil
updatePropagatorThreadPool.shutdownExecutorService();
worldGenThreadPool.shutdownExecutorService();
networkCompressionThreadPool.shutdownExecutorService();
bufferUploaderThreadPool.shutdown();
cleanupThreadPool.shutdown();
beaconCullingThreadPool.shutdown();