move buffer building logic out of ColumnRenderBuffer
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.seibel.lod.core.dataObjects.render;
|
||||
|
||||
import com.seibel.lod.api.enums.worldGeneration.EDhApiWorldGenerationStep;
|
||||
import com.seibel.lod.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder;
|
||||
import com.seibel.lod.coreapi.ModInfo;
|
||||
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
|
||||
import com.seibel.lod.core.dataObjects.render.columnViews.ColumnArrayView;
|
||||
@@ -344,7 +345,7 @@ public class ColumnRenderSource
|
||||
// TODO return future?
|
||||
private void tryBuildBuffer(IDhClientLevel level, ColumnRenderSource renderSource)
|
||||
{
|
||||
if (this.buildRenderBufferFuture == null && !ColumnRenderBuffer.isBusy() && !this.isEmpty)
|
||||
if (this.buildRenderBufferFuture == null && !ColumnRenderBufferBuilder.isBusy() && !this.isEmpty)
|
||||
{
|
||||
ColumnRenderSource[] columnRenderSources = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length];
|
||||
for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS)
|
||||
@@ -357,7 +358,7 @@ public class ColumnRenderSource
|
||||
}
|
||||
|
||||
// TODO this needs to allow generating partial sections to allow for non-full quadTree RenderSections to render
|
||||
this.buildRenderBufferFuture = ColumnRenderBuffer.buildBuffers(level, this.columnRenderBufferRef, this, columnRenderSources);
|
||||
this.buildRenderBufferFuture = ColumnRenderBufferBuilder.buildBuffers(level, this.columnRenderBufferRef, this, columnRenderSources);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,13 +402,13 @@ public class ColumnRenderSource
|
||||
|
||||
|
||||
ColumnRenderBuffer newBuffer = this.buildRenderBufferFuture.join();
|
||||
LodUtil.assertTrue(newBuffer.areBuffersUploaded(), "The buffer future for "+renderSource.sectionPos+" returned an un-built buffer.");
|
||||
LodUtil.assertTrue(newBuffer.buffersUploaded, "The buffer future for "+renderSource.sectionPos+" returned an un-built buffer.");
|
||||
|
||||
ColumnRenderBuffer oldBuffer = renderBufferRefToSwap.getAndSet(newBuffer);
|
||||
if (oldBuffer != null)
|
||||
{
|
||||
// the old buffer is now considered unloaded, it will need to be freshly re-loaded
|
||||
oldBuffer.setBuffersUploaded(false);
|
||||
oldBuffer.buffersUploaded = false;
|
||||
}
|
||||
|
||||
ColumnRenderBuffer swapped = this.columnRenderBufferRef.swap(oldBuffer);
|
||||
@@ -422,7 +423,7 @@ public class ColumnRenderSource
|
||||
{
|
||||
if (!this.isEmpty)
|
||||
{
|
||||
if (ColumnRenderBuffer.isBusy())
|
||||
if (ColumnRenderBufferBuilder.isBusy())
|
||||
{
|
||||
this.lastNs += (long) (SWAP_BUSY_COLLISION_TIMEOUT_IN_NS * Math.random());
|
||||
}
|
||||
|
||||
+16
-330
@@ -1,61 +1,48 @@
|
||||
package com.seibel.lod.core.dataObjects.render.bufferBuilding;
|
||||
|
||||
import com.seibel.lod.core.dataObjects.render.ColumnRenderSource;
|
||||
import com.seibel.lod.core.dataObjects.render.columnViews.ColumnArrayView;
|
||||
import com.seibel.lod.core.pos.DhSectionPos;
|
||||
import com.seibel.lod.core.util.RenderDataPointUtil;
|
||||
import com.seibel.lod.core.level.IDhClientLevel;
|
||||
import com.seibel.lod.core.render.renderer.LodRenderer;
|
||||
import com.seibel.lod.core.util.objects.UncheckedInterruptedException;
|
||||
import com.seibel.lod.core.render.AbstractRenderBuffer;
|
||||
import com.seibel.lod.core.config.Config;
|
||||
import com.seibel.lod.core.enums.ELodDirection;
|
||||
import com.seibel.lod.api.enums.config.EGpuUploadMethod;
|
||||
import com.seibel.lod.api.enums.rendering.EDebugMode;
|
||||
import com.seibel.lod.api.enums.rendering.EGLProxyContext;
|
||||
import com.seibel.lod.core.logging.ConfigBasedLogger;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.pos.DhBlockPos;
|
||||
import com.seibel.lod.core.render.glObject.GLProxy;
|
||||
import com.seibel.lod.core.render.glObject.buffer.GLVertexBuffer;
|
||||
import com.seibel.lod.core.util.*;
|
||||
import com.seibel.lod.core.util.objects.Reference;
|
||||
import com.seibel.lod.core.util.objects.StatsMap;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static com.seibel.lod.core.render.glObject.GLProxy.GL_LOGGER;
|
||||
|
||||
|
||||
/**
|
||||
* Java representation of one or more OpenGL buffers for rendering.
|
||||
*
|
||||
* @see ColumnRenderBufferBuilder
|
||||
*/
|
||||
public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
{
|
||||
//TODO: Make the pool change thread count after the config value is changed
|
||||
public static final ExecutorService BUFFER_BUILDER_THREADS = ThreadUtil.makeThreadPool(Config.Client.Advanced.Threading.numberOfBufferBuilderThreads.get(), "BufferBuilder");
|
||||
public static final ExecutorService BUFFER_UPLOADER = ThreadUtil.makeSingleThreadPool("ColumnBufferUploader");
|
||||
// TODO this should probably be based on the number of builder threads
|
||||
public static final int MAX_CONCURRENT_CALL = 8;
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
|
||||
() -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get());
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
|
||||
|
||||
|
||||
public final DhBlockPos pos;
|
||||
|
||||
public boolean buffersUploaded = false;
|
||||
|
||||
private GLVertexBuffer[] vbos;
|
||||
private GLVertexBuffer[] vbosTransparent;
|
||||
private boolean buffersUploaded = false;
|
||||
private boolean closed = false;
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
|
||||
public ColumnRenderBuffer(DhBlockPos pos)
|
||||
{
|
||||
@@ -66,256 +53,6 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// vbo building //
|
||||
//==============//
|
||||
|
||||
// TODO this is static, it should be moved to its own class to prevent confusion
|
||||
/** @return null if busy */
|
||||
public static CompletableFuture<ColumnRenderBuffer> buildBuffers(IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> renderBufferRef, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
|
||||
{
|
||||
if (isBusy())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//LOGGER.info("RenderRegion startBuild @ {}", renderSource.sectionPos);
|
||||
return CompletableFuture.supplyAsync(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
boolean enableTransparency = Config.Client.Graphics.Quality.transparency.get().tranparencyEnabled;
|
||||
|
||||
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ "+renderSource.sectionPos);
|
||||
boolean enableSkyLightCulling = Config.Client.Graphics.AdvancedGraphics.enableCaveCulling.get();
|
||||
|
||||
int skyLightCullingBelow = Config.Client.Graphics.AdvancedGraphics.caveCullingHeight.get();
|
||||
// FIXME: Clamp also to the max world height.
|
||||
skyLightCullingBelow = Math.max(skyLightCullingBelow, clientLevel.getMinY());
|
||||
|
||||
LodQuadBuilder builder = new LodQuadBuilder(enableSkyLightCulling,
|
||||
(short) (skyLightCullingBelow - clientLevel.getMinY()), enableTransparency);
|
||||
|
||||
makeLodRenderData(builder, renderSource, adjData);
|
||||
EVENT_LOGGER.trace("RenderRegion end QuadBuild @ "+renderSource.sectionPos);
|
||||
return builder;
|
||||
}
|
||||
catch (UncheckedInterruptedException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e3)
|
||||
{
|
||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
|
||||
throw e3;
|
||||
}
|
||||
|
||||
}, BUFFER_BUILDER_THREADS)
|
||||
.thenApplyAsync((quadBuilder) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
EVENT_LOGGER.trace("RenderRegion start Upload @ "+renderSource.sectionPos);
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
|
||||
EGLProxyContext oldContext = glProxy.getGlContext();
|
||||
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
|
||||
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY()));
|
||||
}
|
||||
buffer.buffersUploaded = false;
|
||||
|
||||
try
|
||||
{
|
||||
buffer.uploadBuffer(quadBuilder, method);
|
||||
EVENT_LOGGER.trace("RenderRegion end Upload @ "+renderSource.sectionPos);
|
||||
return buffer;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
buffer.close();
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
glProxy.setGlContext(oldContext);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw UncheckedInterruptedException.convert(e);
|
||||
}
|
||||
catch (Throwable e3)
|
||||
{
|
||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
|
||||
throw e3;
|
||||
}
|
||||
},
|
||||
BUFFER_UPLOADER).handle((columnRenderBuffer, ex) ->
|
||||
{
|
||||
//LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos);
|
||||
if (ex != null)
|
||||
{
|
||||
LOGGER.warn("Buffer building failed: "+ex.getMessage(), ex);
|
||||
|
||||
ColumnRenderBuffer buffer;
|
||||
if (!renderBufferRef.isEmpty())
|
||||
{
|
||||
buffer = renderBufferRef.swap(null);
|
||||
buffer.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return columnRenderBuffer;
|
||||
}
|
||||
});
|
||||
}
|
||||
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource region, ColumnRenderSource[] adjRegions)
|
||||
{
|
||||
// Variable initialization
|
||||
EDebugMode debugMode = Config.Client.Advanced.Debugging.debugMode.get();
|
||||
|
||||
// TODO pass in which child DhSectionPos should be built to allow generating partial sections so non-full quadTree RenderSections will render
|
||||
byte detailLevel = region.getDataDetail();
|
||||
for (int x = 0; x < ColumnRenderSource.SECTION_SIZE; x++)
|
||||
{
|
||||
for (int z = 0; z < ColumnRenderSource.SECTION_SIZE; z++)
|
||||
{
|
||||
UncheckedInterruptedException.throwIfInterrupted();
|
||||
|
||||
ColumnArrayView posData = region.getVerticalDataPointView(x, z);
|
||||
if (posData.size() == 0
|
||||
|| !RenderDataPointUtil.doesDataPointExist(posData.get(0))
|
||||
|| RenderDataPointUtil.isVoid(posData.get(0)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ColumnRenderSource.DebugSourceFlag debugSourceFlag = region.debugGetFlag(x, z);
|
||||
|
||||
ColumnArrayView[][] adjData = new ColumnArrayView[4][];
|
||||
// We extract the adj data in the four cardinal direction
|
||||
|
||||
// we first reset the adjShadeDisabled. This is used to disable the shade on the
|
||||
// border when we have transparent block like water or glass
|
||||
// to avoid having a "darker border" underground
|
||||
// Arrays.fill(adjShadeDisabled, false);
|
||||
|
||||
// We check every adj block in each direction
|
||||
|
||||
// If the adj block is rendered in the same region and with same detail
|
||||
// and is positioned in a place that is not going to be rendered by vanilla game
|
||||
// then we can set this position as adj
|
||||
// We avoid cases where the adjPosition is in player chunk while the position is
|
||||
// not
|
||||
// to always have a wall underwater
|
||||
for (ELodDirection lodDirection : ELodDirection.ADJ_DIRECTIONS)
|
||||
{
|
||||
try
|
||||
{
|
||||
int xAdj = x + lodDirection.getNormal().x;
|
||||
int zAdj = z + lodDirection.getNormal().z;
|
||||
boolean isCrossRegionBoundary =
|
||||
(xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) ||
|
||||
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
|
||||
|
||||
ColumnRenderSource adjRegion;
|
||||
byte adjDetail;
|
||||
|
||||
//we check if the detail of the adjPos is equal to the correct one (region border fix)
|
||||
//or if the detail is wrong by 1 value (region+circle border fix)
|
||||
if (isCrossRegionBoundary)
|
||||
{
|
||||
//we compute at which detail that position should be rendered
|
||||
adjRegion = adjRegions[lodDirection.ordinal() - 2];
|
||||
if (adjRegion == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
adjDetail = adjRegion.getDataDetail();
|
||||
if (adjDetail != detailLevel)
|
||||
{
|
||||
//TODO: Implement this
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xAdj < 0)
|
||||
xAdj += ColumnRenderSource.SECTION_SIZE;
|
||||
|
||||
if (zAdj < 0)
|
||||
zAdj += ColumnRenderSource.SECTION_SIZE;
|
||||
|
||||
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
|
||||
xAdj -= ColumnRenderSource.SECTION_SIZE;
|
||||
|
||||
if (zAdj >= ColumnRenderSource.SECTION_SIZE)
|
||||
zAdj -= ColumnRenderSource.SECTION_SIZE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
adjRegion = region;
|
||||
adjDetail = detailLevel;
|
||||
}
|
||||
|
||||
if (adjDetail < detailLevel - 1 || adjDetail > detailLevel + 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (adjDetail == detailLevel || adjDetail > detailLevel)
|
||||
{
|
||||
adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[1];
|
||||
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataPointView(xAdj, zAdj);
|
||||
}
|
||||
else
|
||||
{
|
||||
adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[2];
|
||||
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataPointView(xAdj, zAdj);
|
||||
adjData[lodDirection.ordinal() - 2][1] = adjRegion.getVerticalDataPointView(
|
||||
xAdj + (lodDirection.getAxis() == ELodDirection.Axis.X ? 0 : 1),
|
||||
zAdj + (lodDirection.getAxis() == ELodDirection.Axis.Z ? 0 : 1));
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
EVENT_LOGGER.warn("Failed to get adj data for [{}:{},{}] at [{}]", detailLevel, x, z, lodDirection);
|
||||
EVENT_LOGGER.warn("Detail exception: ", e);
|
||||
}
|
||||
} // for adjacent directions
|
||||
|
||||
// We render every vertical lod present in this position
|
||||
// We only stop when we find a block that is void or non-existing block
|
||||
for (int i = 0; i < posData.size(); i++)
|
||||
{
|
||||
long data = posData.get(i);
|
||||
// If the data is not renderable (Void or non-existing) we stop since there is
|
||||
// no data left in this position
|
||||
if (RenderDataPointUtil.isVoid(data) || !RenderDataPointUtil.doesDataPointExist(data))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
long adjDataTop = i - 1 >= 0 ? posData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
|
||||
long adjDataBot = i + 1 < posData.size() ? posData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
|
||||
|
||||
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel,
|
||||
x, z, quadBuilder, debugMode, debugSourceFlag);
|
||||
}
|
||||
|
||||
}// for z
|
||||
}// for x
|
||||
|
||||
quadBuilder.mergeQuads();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==================//
|
||||
@@ -340,7 +77,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
{
|
||||
// opaque vbos //
|
||||
|
||||
this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
|
||||
this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
|
||||
for (int i = 0; i < this.vbos.length; i++)
|
||||
{
|
||||
if (this.vbos[i] == null)
|
||||
@@ -357,7 +94,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
|
||||
// transparent vbos //
|
||||
|
||||
this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
||||
this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
||||
for (int i = 0; i < this.vbosTransparent.length; i++)
|
||||
{
|
||||
if (this.vbosTransparent[i] == null)
|
||||
@@ -374,10 +111,10 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
|
||||
private void uploadBuffersDirect(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException
|
||||
{
|
||||
this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
|
||||
this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
|
||||
uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method);
|
||||
|
||||
this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
||||
this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
||||
uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method);
|
||||
}
|
||||
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EGpuUploadMethod method) throws InterruptedException
|
||||
@@ -393,7 +130,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
}
|
||||
|
||||
ByteBuffer bb = iter.next();
|
||||
GLVertexBuffer vbo = getOrMake(vbos, vboIndex++, method.useBufferStorage);
|
||||
GLVertexBuffer vbo = ColumnRenderBufferBuilder.getOrMakeBuffer(vbos, vboIndex++, method.useBufferStorage);
|
||||
int size = bb.limit() - bb.position();
|
||||
|
||||
try
|
||||
@@ -436,41 +173,6 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// vbo interaction //
|
||||
//=================//
|
||||
|
||||
private static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
|
||||
{
|
||||
if (vbos.length == newSize)
|
||||
{
|
||||
return vbos;
|
||||
}
|
||||
|
||||
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
|
||||
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
|
||||
if (newSize < vbos.length)
|
||||
{
|
||||
for (int i = newSize; i < vbos.length; i++)
|
||||
{
|
||||
if (vbos[i] != null)
|
||||
{
|
||||
vbos[i].close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return newVbos;
|
||||
}
|
||||
|
||||
private static GLVertexBuffer getOrMake(GLVertexBuffer[] vbos, int iIndex, boolean useBuffStorage)
|
||||
{
|
||||
if (vbos[iIndex] == null)
|
||||
{
|
||||
vbos[iIndex] = new GLVertexBuffer(useBuffStorage);
|
||||
}
|
||||
return vbos[iIndex];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//========//
|
||||
@@ -588,20 +290,4 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
}
|
||||
|
||||
|
||||
//=========//
|
||||
// getters //
|
||||
//=========//
|
||||
|
||||
public void setBuffersUploaded(boolean value) { this.buffersUploaded = value; }
|
||||
public boolean areBuffersUploaded() { return this.buffersUploaded; }
|
||||
|
||||
// TODO move static methods to their own class to avoid confusion
|
||||
private static long getCurrentJobsCount()
|
||||
{
|
||||
long jobs = ((ThreadPoolExecutor) BUFFER_BUILDER_THREADS).getQueue().stream().filter(runnable -> !((Future<?>) runnable).isDone()).count();
|
||||
jobs += ((ThreadPoolExecutor) BUFFER_UPLOADER).getQueue().stream().filter(runnable -> !((Future<?>) runnable).isDone()).count();
|
||||
return jobs;
|
||||
}
|
||||
public static boolean isBusy() { return getCurrentJobsCount() > MAX_CONCURRENT_CALL; }
|
||||
|
||||
}
|
||||
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
package com.seibel.lod.core.dataObjects.render.bufferBuilding;
|
||||
|
||||
import com.seibel.lod.api.enums.config.EGpuUploadMethod;
|
||||
import com.seibel.lod.api.enums.rendering.EDebugMode;
|
||||
import com.seibel.lod.api.enums.rendering.EGLProxyContext;
|
||||
import com.seibel.lod.core.config.Config;
|
||||
import com.seibel.lod.core.dataObjects.render.ColumnRenderSource;
|
||||
import com.seibel.lod.core.dataObjects.render.columnViews.ColumnArrayView;
|
||||
import com.seibel.lod.core.enums.ELodDirection;
|
||||
import com.seibel.lod.core.level.IDhClientLevel;
|
||||
import com.seibel.lod.core.logging.ConfigBasedLogger;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.pos.DhBlockPos;
|
||||
import com.seibel.lod.core.render.glObject.GLProxy;
|
||||
import com.seibel.lod.core.render.glObject.buffer.GLVertexBuffer;
|
||||
import com.seibel.lod.core.util.RenderDataPointUtil;
|
||||
import com.seibel.lod.core.util.ThreadUtil;
|
||||
import com.seibel.lod.core.util.objects.Reference;
|
||||
import com.seibel.lod.core.util.objects.UncheckedInterruptedException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* Used to populate the buffers in a {@link ColumnRenderSource} object.
|
||||
*
|
||||
* @see ColumnRenderSource
|
||||
*/
|
||||
public class ColumnRenderBufferBuilder
|
||||
{
|
||||
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
|
||||
() -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get());
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
//TODO: Make the pool change thread count after the config value is changed
|
||||
public static final ExecutorService BUFFER_BUILDER_THREADS = ThreadUtil.makeThreadPool(Config.Client.Advanced.Threading.numberOfBufferBuilderThreads.get(), "BufferBuilder");
|
||||
public static final ExecutorService BUFFER_UPLOADER = ThreadUtil.makeSingleThreadPool("ColumnBufferUploader");
|
||||
// TODO this should probably be based on the number of builder threads
|
||||
public static final int MAX_CONCURRENT_CALL = 8;
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// vbo building //
|
||||
//==============//
|
||||
|
||||
/// TODO this is static, it should be moved to its own class to prevent confusion
|
||||
/** @return null if busy */
|
||||
public static CompletableFuture<ColumnRenderBuffer> buildBuffers(IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> renderBufferRef, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
|
||||
{
|
||||
if (isBusy())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//LOGGER.info("RenderRegion startBuild @ {}", renderSource.sectionPos);
|
||||
return CompletableFuture.supplyAsync(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
boolean enableTransparency = Config.Client.Graphics.Quality.transparency.get().tranparencyEnabled;
|
||||
|
||||
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ "+renderSource.sectionPos);
|
||||
boolean enableSkyLightCulling = Config.Client.Graphics.AdvancedGraphics.enableCaveCulling.get();
|
||||
|
||||
int skyLightCullingBelow = Config.Client.Graphics.AdvancedGraphics.caveCullingHeight.get();
|
||||
// FIXME: Clamp also to the max world height.
|
||||
skyLightCullingBelow = Math.max(skyLightCullingBelow, clientLevel.getMinY());
|
||||
|
||||
LodQuadBuilder builder = new LodQuadBuilder(enableSkyLightCulling,
|
||||
(short) (skyLightCullingBelow - clientLevel.getMinY()), enableTransparency);
|
||||
|
||||
makeLodRenderData(builder, renderSource, adjData);
|
||||
EVENT_LOGGER.trace("RenderRegion end QuadBuild @ "+renderSource.sectionPos);
|
||||
return builder;
|
||||
}
|
||||
catch (UncheckedInterruptedException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e3)
|
||||
{
|
||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
|
||||
throw e3;
|
||||
}
|
||||
|
||||
}, BUFFER_BUILDER_THREADS)
|
||||
.thenApplyAsync((quadBuilder) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
EVENT_LOGGER.trace("RenderRegion start Upload @ "+renderSource.sectionPos);
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
|
||||
EGLProxyContext oldContext = glProxy.getGlContext();
|
||||
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
|
||||
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY()));
|
||||
}
|
||||
buffer.buffersUploaded = false;
|
||||
|
||||
try
|
||||
{
|
||||
buffer.uploadBuffer(quadBuilder, method);
|
||||
EVENT_LOGGER.trace("RenderRegion end Upload @ "+renderSource.sectionPos);
|
||||
return buffer;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
buffer.close();
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
glProxy.setGlContext(oldContext);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw UncheckedInterruptedException.convert(e);
|
||||
}
|
||||
catch (Throwable e3)
|
||||
{
|
||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
|
||||
throw e3;
|
||||
}
|
||||
},
|
||||
BUFFER_UPLOADER).handle((columnRenderBuffer, ex) ->
|
||||
{
|
||||
//LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos);
|
||||
if (ex != null)
|
||||
{
|
||||
LOGGER.warn("Buffer building failed: "+ex.getMessage(), ex);
|
||||
|
||||
ColumnRenderBuffer buffer;
|
||||
if (!renderBufferRef.isEmpty())
|
||||
{
|
||||
buffer = renderBufferRef.swap(null);
|
||||
buffer.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return columnRenderBuffer;
|
||||
}
|
||||
});
|
||||
}
|
||||
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource region, ColumnRenderSource[] adjRegions)
|
||||
{
|
||||
// Variable initialization
|
||||
EDebugMode debugMode = Config.Client.Advanced.Debugging.debugMode.get();
|
||||
|
||||
// TODO pass in which child DhSectionPos should be built to allow generating partial sections so non-full quadTree RenderSections will render
|
||||
byte detailLevel = region.getDataDetail();
|
||||
for (int x = 0; x < ColumnRenderSource.SECTION_SIZE; x++)
|
||||
{
|
||||
for (int z = 0; z < ColumnRenderSource.SECTION_SIZE; z++)
|
||||
{
|
||||
UncheckedInterruptedException.throwIfInterrupted();
|
||||
|
||||
ColumnArrayView posData = region.getVerticalDataPointView(x, z);
|
||||
if (posData.size() == 0
|
||||
|| !RenderDataPointUtil.doesDataPointExist(posData.get(0))
|
||||
|| RenderDataPointUtil.isVoid(posData.get(0)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ColumnRenderSource.DebugSourceFlag debugSourceFlag = region.debugGetFlag(x, z);
|
||||
|
||||
ColumnArrayView[][] adjData = new ColumnArrayView[4][];
|
||||
// We extract the adj data in the four cardinal direction
|
||||
|
||||
// we first reset the adjShadeDisabled. This is used to disable the shade on the
|
||||
// border when we have transparent block like water or glass
|
||||
// to avoid having a "darker border" underground
|
||||
// Arrays.fill(adjShadeDisabled, false);
|
||||
|
||||
// We check every adj block in each direction
|
||||
|
||||
// If the adj block is rendered in the same region and with same detail
|
||||
// and is positioned in a place that is not going to be rendered by vanilla game
|
||||
// then we can set this position as adj
|
||||
// We avoid cases where the adjPosition is in player chunk while the position is
|
||||
// not
|
||||
// to always have a wall underwater
|
||||
for (ELodDirection lodDirection : ELodDirection.ADJ_DIRECTIONS)
|
||||
{
|
||||
try
|
||||
{
|
||||
int xAdj = x + lodDirection.getNormal().x;
|
||||
int zAdj = z + lodDirection.getNormal().z;
|
||||
boolean isCrossRegionBoundary =
|
||||
(xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) ||
|
||||
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
|
||||
|
||||
ColumnRenderSource adjRegion;
|
||||
byte adjDetail;
|
||||
|
||||
//we check if the detail of the adjPos is equal to the correct one (region border fix)
|
||||
//or if the detail is wrong by 1 value (region+circle border fix)
|
||||
if (isCrossRegionBoundary)
|
||||
{
|
||||
//we compute at which detail that position should be rendered
|
||||
adjRegion = adjRegions[lodDirection.ordinal() - 2];
|
||||
if (adjRegion == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
adjDetail = adjRegion.getDataDetail();
|
||||
if (adjDetail != detailLevel)
|
||||
{
|
||||
//TODO: Implement this
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xAdj < 0)
|
||||
xAdj += ColumnRenderSource.SECTION_SIZE;
|
||||
|
||||
if (zAdj < 0)
|
||||
zAdj += ColumnRenderSource.SECTION_SIZE;
|
||||
|
||||
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
|
||||
xAdj -= ColumnRenderSource.SECTION_SIZE;
|
||||
|
||||
if (zAdj >= ColumnRenderSource.SECTION_SIZE)
|
||||
zAdj -= ColumnRenderSource.SECTION_SIZE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
adjRegion = region;
|
||||
adjDetail = detailLevel;
|
||||
}
|
||||
|
||||
if (adjDetail < detailLevel - 1 || adjDetail > detailLevel + 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (adjDetail == detailLevel || adjDetail > detailLevel)
|
||||
{
|
||||
adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[1];
|
||||
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataPointView(xAdj, zAdj);
|
||||
}
|
||||
else
|
||||
{
|
||||
adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[2];
|
||||
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataPointView(xAdj, zAdj);
|
||||
adjData[lodDirection.ordinal() - 2][1] = adjRegion.getVerticalDataPointView(
|
||||
xAdj + (lodDirection.getAxis() == ELodDirection.Axis.X ? 0 : 1),
|
||||
zAdj + (lodDirection.getAxis() == ELodDirection.Axis.Z ? 0 : 1));
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
EVENT_LOGGER.warn("Failed to get adj data for [{}:{},{}] at [{}]", detailLevel, x, z, lodDirection);
|
||||
EVENT_LOGGER.warn("Detail exception: ", e);
|
||||
}
|
||||
} // for adjacent directions
|
||||
|
||||
// We render every vertical lod present in this position
|
||||
// We only stop when we find a block that is void or non-existing block
|
||||
for (int i = 0; i < posData.size(); i++)
|
||||
{
|
||||
long data = posData.get(i);
|
||||
// If the data is not renderable (Void or non-existing) we stop since there is
|
||||
// no data left in this position
|
||||
if (RenderDataPointUtil.isVoid(data) || !RenderDataPointUtil.doesDataPointExist(data))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
long adjDataTop = i - 1 >= 0 ? posData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
|
||||
long adjDataBot = i + 1 < posData.size() ? posData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
|
||||
|
||||
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel,
|
||||
x, z, quadBuilder, debugMode, debugSourceFlag);
|
||||
}
|
||||
|
||||
}// for z
|
||||
}// for x
|
||||
|
||||
quadBuilder.mergeQuads();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// vbo interaction //
|
||||
//=================//
|
||||
|
||||
public static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
|
||||
{
|
||||
if (vbos.length == newSize)
|
||||
{
|
||||
return vbos;
|
||||
}
|
||||
|
||||
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
|
||||
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
|
||||
if (newSize < vbos.length)
|
||||
{
|
||||
for (int i = newSize; i < vbos.length; i++)
|
||||
{
|
||||
if (vbos[i] != null)
|
||||
{
|
||||
vbos[i].close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return newVbos;
|
||||
}
|
||||
|
||||
public static GLVertexBuffer getOrMakeBuffer(GLVertexBuffer[] vbos, int iIndex, boolean useBuffStorage)
|
||||
{
|
||||
if (vbos[iIndex] == null)
|
||||
{
|
||||
vbos[iIndex] = new GLVertexBuffer(useBuffStorage);
|
||||
}
|
||||
return vbos[iIndex];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// getters //
|
||||
//=========//
|
||||
|
||||
// TODO move static methods to their own class to avoid confusion
|
||||
private static long getCurrentJobsCount()
|
||||
{
|
||||
long jobs = ((ThreadPoolExecutor) BUFFER_BUILDER_THREADS).getQueue().stream().filter(runnable -> !((Future<?>) runnable).isDone()).count();
|
||||
jobs += ((ThreadPoolExecutor) BUFFER_UPLOADER).getQueue().stream().filter(runnable -> !((Future<?>) runnable).isDone()).count();
|
||||
return jobs;
|
||||
}
|
||||
public static boolean isBusy() { return getCurrentJobsCount() > MAX_CONCURRENT_CALL; }
|
||||
|
||||
}
|
||||
@@ -189,7 +189,7 @@ public class LodRenderSection
|
||||
(
|
||||
// check if the buffers have been loaded
|
||||
this.renderBufferRef.get() != null
|
||||
&& this.renderBufferRef.get().areBuffersUploaded()
|
||||
&& this.renderBufferRef.get().buffersUploaded
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ public class RenderBufferHandler
|
||||
|
||||
|
||||
/**
|
||||
* The following buildRenderList sorting method is based on the following reddit post:
|
||||
* https://www.reddit.com/r/VoxelGameDev/comments/a0l8zc/correct_depthordering_for_translucent_discrete/
|
||||
*
|
||||
* The following buildRenderList sorting method is based on the following reddit post: <br>
|
||||
* <a href="https://www.reddit.com/r/VoxelGameDev/comments/a0l8zc/correct_depthordering_for_translucent_discrete/">correct_depth_ordering_for_translucent_discrete</a> <br><br>
|
||||
*
|
||||
* TODO: This might get locked by update() causing move() call. Is there a way to avoid this?
|
||||
* Maybe dupe the base list and use atomic swap on render? Or is this not worth it?
|
||||
*/
|
||||
@@ -147,7 +147,7 @@ public class RenderBufferHandler
|
||||
|
||||
if (renderSection != null && renderSection.shouldRender())
|
||||
{
|
||||
if (renderSection.renderBufferRef.get() != null && renderSection.renderBufferRef.get().areBuffersUploaded())
|
||||
if (renderSection.renderBufferRef.get() != null && renderSection.renderBufferRef.get().buffersUploaded)
|
||||
{
|
||||
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(renderSection.renderBufferRef.get(), sectionPos));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user