refactor ColumnRenderBuffer
This commit is contained in:
@@ -64,9 +64,13 @@ public enum EGpuUploadMethod
|
||||
*/
|
||||
DATA(false, false);
|
||||
|
||||
|
||||
|
||||
public final boolean useEarlyMapping;
|
||||
public final boolean useBufferStorage;
|
||||
EGpuUploadMethod(boolean useEarlyMapping, boolean useBufferStorage) {
|
||||
|
||||
EGpuUploadMethod(boolean useEarlyMapping, boolean useBufferStorage)
|
||||
{
|
||||
this.useEarlyMapping = useEarlyMapping;
|
||||
this.useBufferStorage = useBufferStorage;
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ public class ColumnRenderSource
|
||||
//LOGGER.info("attempting to build buffer for: "+renderSection.pos);
|
||||
}
|
||||
}
|
||||
this.buildRenderBufferFuture = ColumnRenderBuffer.build(level, this.columnRenderBufferRef, this, columnRenderSources);
|
||||
this.buildRenderBufferFuture = ColumnRenderBuffer.buildBuffers(level, this.columnRenderBufferRef, this, columnRenderSources);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+476
-312
@@ -34,10 +34,10 @@ import static com.seibel.lod.core.render.glObject.GLProxy.GL_LOGGER;
|
||||
public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
{
|
||||
//TODO: Make the pool change thread count after the config value is changed
|
||||
public static final ExecutorService BUFFER_BUILDERS = ThreadUtil.makeThreadPool(Config.Client.Advanced.Threading.numberOfBufferBuilderThreads.get(), "BufferBuilder");
|
||||
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");
|
||||
public static final int MAX_CONCURRENT_CALL = 8;
|
||||
|
||||
|
||||
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
|
||||
() -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get());
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
|
||||
@@ -46,183 +46,25 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
GLVertexBuffer[] vbosTransparent;
|
||||
public final DhBlockPos pos;
|
||||
|
||||
private boolean closed = false;
|
||||
|
||||
|
||||
public ColumnRenderBuffer(DhBlockPos pos) {
|
||||
this.pos = pos;
|
||||
vbos = new GLVertexBuffer[0];
|
||||
vbosTransparent = new GLVertexBuffer[0];
|
||||
}
|
||||
|
||||
|
||||
public ColumnRenderBuffer(DhBlockPos pos)
|
||||
{
|
||||
this.pos = pos;
|
||||
vbos = new GLVertexBuffer[0];
|
||||
vbosTransparent = new GLVertexBuffer[0];
|
||||
}
|
||||
|
||||
|
||||
private static void _doUploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EGpuUploadMethod method) throws InterruptedException {
|
||||
long remainingNS = 0;
|
||||
long BPerNS = Config.Client.Advanced.Buffers.gpuUploadPerMegabyteInMilliseconds.get();
|
||||
int i = 0;
|
||||
while (iter.hasNext()) {
|
||||
if (i >= vbos.length) {
|
||||
throw new RuntimeException("Too many vertex buffers!!");
|
||||
}
|
||||
ByteBuffer bb = iter.next();
|
||||
GLVertexBuffer vbo = getOrMake(vbos, i++, method.useBufferStorage);
|
||||
int size = bb.limit() - bb.position();
|
||||
try {
|
||||
vbo.bind();
|
||||
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
|
||||
} catch (Exception e) {
|
||||
vbos[i-1] = null;
|
||||
vbo.close();
|
||||
LOGGER.error("Failed to upload buffer: ", e);
|
||||
}
|
||||
if (BPerNS<=0) continue;
|
||||
// upload buffers over an extended period of time
|
||||
// to hopefully prevent stuttering.
|
||||
remainingNS += size * BPerNS;
|
||||
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) {
|
||||
if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
|
||||
remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
|
||||
Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000));
|
||||
remainingNS = 0;
|
||||
}
|
||||
}
|
||||
if (i < vbos.length) {
|
||||
throw new RuntimeException("Too few vertex buffers!!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void _uploadBuffersDirect(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException {
|
||||
vbos = resize(vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
|
||||
_doUploadBuffersDirect(vbos, builder.makeOpaqueVertexBuffers(), method);
|
||||
vbosTransparent = resize(vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
||||
_doUploadBuffersDirect(vbosTransparent, builder.makeTransparentVertexBuffers(), method);
|
||||
}
|
||||
|
||||
private void _uploadBuffersMapped(LodQuadBuilder builder, EGpuUploadMethod method)
|
||||
{
|
||||
vbos = resize(vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
|
||||
for (int i=0; i<vbos.length; i++) {
|
||||
if (vbos[i]==null) vbos[i] = new GLVertexBuffer(method.useBufferStorage);
|
||||
}
|
||||
LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method);
|
||||
{
|
||||
int i = 0;
|
||||
while (i < vbos.length && func.fill(vbos[i++])) {
|
||||
}
|
||||
}
|
||||
vbosTransparent = resize(vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
||||
for (int i=0; i<vbosTransparent.length; i++) {
|
||||
if (vbosTransparent[i]==null) vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage);
|
||||
}
|
||||
func = builder.makeTransparentBufferFiller(method);
|
||||
{
|
||||
int i = 0;
|
||||
while (i < vbosTransparent.length && func.fill(vbosTransparent[i++])) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static GLVertexBuffer[] resize(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];
|
||||
}
|
||||
|
||||
public void uploadBuffer(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException {
|
||||
if (method.useEarlyMapping) {
|
||||
_uploadBuffersMapped(builder, method);
|
||||
} else {
|
||||
_uploadBuffersDirect(builder, method);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renderOpaque(LodRenderer renderContext) {
|
||||
boolean hasRendered = false;
|
||||
renderContext.setupOffset(pos);
|
||||
for (GLVertexBuffer vbo : vbos) {
|
||||
if (vbo == null) continue;
|
||||
if (vbo.getVertexCount() == 0) continue;
|
||||
hasRendered = true;
|
||||
renderContext.drawVbo(vbo);
|
||||
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
|
||||
}
|
||||
return hasRendered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renderTransparent(LodRenderer renderContext) {
|
||||
boolean hasRendered = false;
|
||||
renderContext.setupOffset(pos);
|
||||
for (GLVertexBuffer vbo : vbosTransparent) {
|
||||
if (vbo == null) continue;
|
||||
if (vbo.getVertexCount() == 0) continue;
|
||||
hasRendered = true;
|
||||
renderContext.drawVbo(vbo);
|
||||
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
|
||||
}
|
||||
return hasRendered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugDumpStats(StatsMap statsMap) {
|
||||
statsMap.incStat("RenderBuffers");
|
||||
statsMap.incStat("SimpleRenderBuffers");
|
||||
for (GLVertexBuffer b : vbos) {
|
||||
if (b == null) continue;
|
||||
statsMap.incStat("VBOs");
|
||||
if (b.getSize() == FULL_SIZED_BUFFER) {
|
||||
statsMap.incStat("FullsizedVBOs");
|
||||
}
|
||||
if (b.getSize() == 0) GL_LOGGER.warn("VBO with size 0");
|
||||
statsMap.incBytesStat("TotalUsage", b.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean closed = false;
|
||||
@Override
|
||||
public void close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
GLProxy.getInstance().recordOpenGlCall(() -> {
|
||||
for (GLVertexBuffer b : vbos) {
|
||||
if (b == null) continue;
|
||||
b.destroy(false);
|
||||
}
|
||||
for (GLVertexBuffer b : vbosTransparent) {
|
||||
if (b == null) continue;
|
||||
b.destroy(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static long getCurrentJobsCount() {
|
||||
long jobs = ((ThreadPoolExecutor) BUFFER_BUILDERS).getQueue().stream().filter(t -> !((Future<?>) t).isDone()).count();
|
||||
jobs += ((ThreadPoolExecutor) BUFFER_UPLOADER).getQueue().stream().filter(t -> !((Future<?>) t).isDone()).count();
|
||||
return jobs;
|
||||
}
|
||||
|
||||
public static boolean isBusy() {
|
||||
return getCurrentJobsCount() > MAX_CONCURRENT_CALL;
|
||||
}
|
||||
|
||||
public static CompletableFuture<ColumnRenderBuffer> build(IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> usedBufferSlot, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
|
||||
//==============//
|
||||
// vbo building //
|
||||
//==============//
|
||||
|
||||
/** @return null if busy */
|
||||
public static CompletableFuture<ColumnRenderBuffer> buildBuffers(IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> usedBufferSlot, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
|
||||
{
|
||||
if (isBusy())
|
||||
{
|
||||
@@ -230,13 +72,13 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
}
|
||||
|
||||
//LOGGER.info("RenderRegion startBuild @ {}", renderSource.sectionPos);
|
||||
return CompletableFuture.supplyAsync(() ->
|
||||
return CompletableFuture.supplyAsync(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
boolean enableTransparency = Config.Client.Graphics.Quality.transparency.get().tranparencyEnabled;
|
||||
|
||||
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ {}", renderSource.sectionPos);
|
||||
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ "+renderSource.sectionPos);
|
||||
boolean enableSkyLightCulling = Config.Client.Graphics.AdvancedGraphics.enableCaveCulling.get();
|
||||
|
||||
int skyLightCullingBelow = Config.Client.Graphics.AdvancedGraphics.caveCullingHeight.get();
|
||||
@@ -247,7 +89,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
(short) (skyLightCullingBelow - clientLevel.getMinY()), enableTransparency);
|
||||
|
||||
makeLodRenderData(builder, renderSource, adjData);
|
||||
EVENT_LOGGER.trace("RenderRegion end QuadBuild @ {}", renderSource.sectionPos);
|
||||
EVENT_LOGGER.trace("RenderRegion end QuadBuild @ "+renderSource.sectionPos);
|
||||
return builder;
|
||||
}
|
||||
catch (UncheckedInterruptedException e)
|
||||
@@ -260,48 +102,51 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
throw e3;
|
||||
}
|
||||
|
||||
}, BUFFER_BUILDERS)
|
||||
.thenApplyAsync((quadBuilder) ->
|
||||
{
|
||||
try
|
||||
}, BUFFER_BUILDER_THREADS)
|
||||
.thenApplyAsync((quadBuilder) ->
|
||||
{
|
||||
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 = usedBufferSlot.swap(null);
|
||||
|
||||
if (buffer == null)
|
||||
buffer = new ColumnRenderBuffer(
|
||||
new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY())
|
||||
);
|
||||
try
|
||||
{
|
||||
buffer.uploadBuffer(quadBuilder, method);
|
||||
EVENT_LOGGER.trace("RenderRegion end Upload @ {}", renderSource.sectionPos);
|
||||
return buffer;
|
||||
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 = usedBufferSlot.swap(null);
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY()));
|
||||
}
|
||||
|
||||
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 (Exception e)
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
buffer.close();
|
||||
throw e;
|
||||
throw UncheckedInterruptedException.convert(e);
|
||||
}
|
||||
finally
|
||||
catch (Throwable e3)
|
||||
{
|
||||
glProxy.setGlContext(oldContext);
|
||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
|
||||
throw e3;
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
},
|
||||
BUFFER_UPLOADER).handle((v, e) ->
|
||||
{
|
||||
throw UncheckedInterruptedException.convert(e);
|
||||
}
|
||||
catch (Throwable e3)
|
||||
{
|
||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
|
||||
throw e3;
|
||||
}
|
||||
}, BUFFER_UPLOADER).handle((v, e) -> {
|
||||
//LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos);
|
||||
if (e != null)
|
||||
{
|
||||
@@ -311,6 +156,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
buffer = usedBufferSlot.swap(null);
|
||||
buffer.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
@@ -319,107 +165,425 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource region, ColumnRenderSource[] adjRegions) {
|
||||
|
||||
// Variable initialization
|
||||
EDebugMode debugMode = Config.Client.Advanced.Debugging.debugMode.get();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
quadBuilder.mergeQuads();
|
||||
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource region, ColumnRenderSource[] adjRegions)
|
||||
{
|
||||
// Variable initialization
|
||||
EDebugMode debugMode = Config.Client.Advanced.Debugging.debugMode.get();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==================//
|
||||
// buffer uploading //
|
||||
//==================//
|
||||
|
||||
public void uploadBuffer(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException
|
||||
{
|
||||
if (method.useEarlyMapping)
|
||||
{
|
||||
this.uploadBuffersMapped(builder, method);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.uploadBuffersDirect(builder, method);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadBuffersMapped(LodQuadBuilder builder, EGpuUploadMethod method)
|
||||
{
|
||||
// opaque vbos //
|
||||
|
||||
this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
|
||||
for (int i = 0; i < this.vbos.length; i++)
|
||||
{
|
||||
if (this.vbos[i] == null)
|
||||
{
|
||||
this.vbos[i] = new GLVertexBuffer(method.useBufferStorage);
|
||||
}
|
||||
}
|
||||
LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method);
|
||||
for (GLVertexBuffer vbo : this.vbos)
|
||||
{
|
||||
func.fill(vbo);
|
||||
}
|
||||
|
||||
|
||||
// transparent vbos //
|
||||
|
||||
this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
||||
for (int i = 0; i < this.vbosTransparent.length; i++)
|
||||
{
|
||||
if (this.vbosTransparent[i] == null)
|
||||
{
|
||||
this.vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage);
|
||||
}
|
||||
}
|
||||
LodQuadBuilder.BufferFiller transparentFillerFunc = builder.makeTransparentBufferFiller(method);
|
||||
for (GLVertexBuffer vbo : this.vbosTransparent)
|
||||
{
|
||||
transparentFillerFunc.fill(vbo);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadBuffersDirect(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException
|
||||
{
|
||||
this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
|
||||
uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method);
|
||||
|
||||
this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
||||
uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method);
|
||||
}
|
||||
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EGpuUploadMethod method) throws InterruptedException
|
||||
{
|
||||
long remainingNS = 0;
|
||||
long BPerNS = Config.Client.Advanced.Buffers.gpuUploadPerMegabyteInMilliseconds.get();
|
||||
int vboIndex = 0;
|
||||
while (iter.hasNext())
|
||||
{
|
||||
if (vboIndex >= vbos.length)
|
||||
{
|
||||
throw new RuntimeException("Too many vertex buffers!!");
|
||||
}
|
||||
|
||||
ByteBuffer bb = iter.next();
|
||||
GLVertexBuffer vbo = getOrMake(vbos, vboIndex++, method.useBufferStorage);
|
||||
int size = bb.limit() - bb.position();
|
||||
|
||||
try
|
||||
{
|
||||
vbo.bind();
|
||||
vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
vbos[vboIndex - 1] = null;
|
||||
vbo.close();
|
||||
LOGGER.error("Failed to upload buffer: ", e);
|
||||
}
|
||||
|
||||
if (BPerNS <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// upload buffers over an extended period of time
|
||||
// to hopefully prevent stuttering.
|
||||
remainingNS += size * BPerNS;
|
||||
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS))
|
||||
{
|
||||
if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
|
||||
{
|
||||
remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
|
||||
}
|
||||
|
||||
Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000));
|
||||
remainingNS = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (vboIndex < vbos.length)
|
||||
{
|
||||
throw new RuntimeException("Too few vertex buffers!!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// 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];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//========//
|
||||
// render //
|
||||
//========//
|
||||
|
||||
@Override
|
||||
public boolean renderOpaque(LodRenderer renderContext)
|
||||
{
|
||||
boolean hasRendered = false;
|
||||
renderContext.setupOffset(this.pos);
|
||||
for (GLVertexBuffer vbo : this.vbos)
|
||||
{
|
||||
if (vbo == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vbo.getVertexCount() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
hasRendered = true;
|
||||
renderContext.drawVbo(vbo);
|
||||
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
|
||||
}
|
||||
return hasRendered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renderTransparent(LodRenderer renderContext)
|
||||
{
|
||||
boolean hasRendered = false;
|
||||
|
||||
renderContext.setupOffset(this.pos);
|
||||
for (GLVertexBuffer vbo : this.vbosTransparent)
|
||||
{
|
||||
if (vbo == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vbo.getVertexCount() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
hasRendered = true;
|
||||
renderContext.drawVbo(vbo);
|
||||
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
|
||||
}
|
||||
|
||||
return hasRendered;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// misc methods //
|
||||
//==============//
|
||||
|
||||
@Override
|
||||
public void debugDumpStats(StatsMap statsMap)
|
||||
{
|
||||
statsMap.incStat("RenderBuffers");
|
||||
statsMap.incStat("SimpleRenderBuffers");
|
||||
for (GLVertexBuffer vertexBuffer : vbos)
|
||||
{
|
||||
if (vertexBuffer != null)
|
||||
{
|
||||
statsMap.incStat("VBOs");
|
||||
if (vertexBuffer.getSize() == FULL_SIZED_BUFFER)
|
||||
{
|
||||
statsMap.incStat("FullsizedVBOs");
|
||||
}
|
||||
|
||||
if (vertexBuffer.getSize() == 0)
|
||||
{
|
||||
GL_LOGGER.warn("VBO with size 0");
|
||||
}
|
||||
statsMap.incBytesStat("TotalUsage", vertexBuffer.getSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
if (this.closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.closed = true;
|
||||
|
||||
GLProxy.getInstance().recordOpenGlCall(() ->
|
||||
{
|
||||
for (GLVertexBuffer buffer : this.vbos)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
buffer.destroy(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (GLVertexBuffer buffer : this.vbosTransparent)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
buffer.destroy(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//=========//
|
||||
// getters //
|
||||
//=========//
|
||||
|
||||
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; }
|
||||
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ public abstract class AbstractRenderBuffer implements AutoCloseable
|
||||
// ======================================================================
|
||||
|
||||
// ========== Called by render thread ==========
|
||||
/* Called on... well... rendering.
|
||||
* Return false if nothing rendered. (Optional) */
|
||||
/** @return true if something was rendered, false otherwise */
|
||||
public abstract boolean renderOpaque(LodRenderer renderContext);
|
||||
/** @return true if something was rendered, false otherwise */
|
||||
public abstract boolean renderTransparent(LodRenderer renderContext);
|
||||
|
||||
// ========== Called by any thread. (thread safe) ==========
|
||||
|
||||
Reference in New Issue
Block a user