move buffer building logic out of ColumnRenderBuffer

This commit is contained in:
James Seibel
2023-05-06 19:40:01 -05:00
parent 7bcef43ef9
commit 64ebadea65
5 changed files with 377 additions and 340 deletions
@@ -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());
}
@@ -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; }
}
@@ -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));
}