Fix holes when moving around the world

This commit is contained in:
James Seibel
2023-03-30 07:26:25 -05:00
parent 8856843830
commit afc045dd4c
5 changed files with 125 additions and 99 deletions
@@ -9,7 +9,6 @@ import com.seibel.lod.core.dataObjects.fullData.sources.ChunkSizedFullDataSource
import com.seibel.lod.core.dataObjects.transformers.FullToColumnTransformer;
import com.seibel.lod.core.level.IDhClientLevel;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.render.AbstractRenderBuffer;
import com.seibel.lod.core.enums.ELodDirection;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.level.IDhLevel;
@@ -337,6 +336,7 @@ public class ColumnRenderSource
// Render Methods //
//================//
// TODO return future?
private void tryBuildBuffer(IDhClientLevel level, ColumnRenderSource renderSource)
{
if (this.buildRenderBufferFuture == null && !ColumnRenderBuffer.isBusy() && !this.isEmpty)
@@ -378,10 +378,10 @@ public class ColumnRenderSource
/**
* Try and swap in new render buffer for this section. Note that before this call, there should be no other
* places storing or referencing the render buffer.
* @param renderBufferToSwap The slot for swapping in the new buffer.
* @param renderBufferRefToSwap The slot for swapping in the new buffer.
* @return True if the swap was successful. False if swap is not needed or if it is in progress.
*/
public boolean trySwapRenderBuffer(ColumnRenderSource renderSource, AtomicReference<AbstractRenderBuffer> renderBufferToSwap)
public boolean trySwapInNewlyBuiltRenderBuffer(ColumnRenderSource renderSource, AtomicReference<ColumnRenderBuffer> renderBufferRefToSwap)
{
// prevent swapping the buffer to quickly
if (this.lastNs != -1 && System.nanoTime() - this.lastNs < SWAP_TIMEOUT_IN_NS)
@@ -397,18 +397,13 @@ public class ColumnRenderSource
this.lastNs = System.nanoTime();
//LOGGER.info("Swapping render buffer for {}", sectionPos);
AbstractRenderBuffer newBuffer = this.buildRenderBufferFuture.join();
AbstractRenderBuffer oldBuffer = renderBufferToSwap.getAndSet(newBuffer);
if (oldBuffer instanceof ColumnRenderBuffer)
{
ColumnRenderBuffer swapped = this.columnRenderBufferRef.swap((ColumnRenderBuffer) oldBuffer);
LodUtil.assertTrue(swapped == null);
}
else if (oldBuffer != null)
{
throw new UnsupportedOperationException("swap buffer fail, Expected "+AbstractRenderBuffer.class.getSimpleName()+" of type: "+ColumnRenderBuffer.class.getSimpleName()+" class given: "+oldBuffer.getClass().getSimpleName());
}
ColumnRenderBuffer newBuffer = this.buildRenderBufferFuture.join();
ColumnRenderBuffer oldBuffer = renderBufferRefToSwap.getAndSet(newBuffer);
ColumnRenderBuffer swapped = this.columnRenderBufferRef.swap(oldBuffer);
LodUtil.assertTrue(swapped == null);
this.buildRenderBufferFuture = null;
return true;
}
@@ -36,20 +36,26 @@ 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");
public static final int MAX_CONCURRENT_CALL = 8;
// TODO this should probably be based on the number of builder threads
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());
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
GLVertexBuffer[] vbos;
GLVertexBuffer[] vbosTransparent;
public final DhBlockPos pos;
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
public final DhBlockPos pos;
private GLVertexBuffer[] vbos;
private GLVertexBuffer[] vbosTransparent;
private boolean buffersUploaded = false;
private boolean closed = false;
public ColumnRenderBuffer(DhBlockPos pos)
{
this.pos = pos;
@@ -63,8 +69,9 @@ 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> usedBufferSlot, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
public static CompletableFuture<ColumnRenderBuffer> buildBuffers(IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> renderBufferRef, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
{
if (isBusy())
{
@@ -104,56 +111,59 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
}, 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
{
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);
}
buffer.uploadBuffer(quadBuilder, method);
EVENT_LOGGER.trace("RenderRegion end Upload @ "+renderSource.sectionPos);
return buffer;
}
catch (InterruptedException e)
catch (Exception e)
{
throw UncheckedInterruptedException.convert(e);
buffer.close();
throw e;
}
catch (Throwable e3)
finally
{
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
throw e3;
glProxy.setGlContext(oldContext);
}
},
BUFFER_UPLOADER).handle((columnRenderBuffer, ex) ->
}
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 (!usedBufferSlot.isEmpty())
if (!renderBufferRef.isEmpty())
{
buffer = usedBufferSlot.swap(null);
buffer = renderBufferRef.swap(null);
buffer.close();
}
@@ -321,6 +331,8 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
{
this.uploadBuffersDirect(builder, method);
}
this.buffersUploaded = true;
}
private void uploadBuffersMapped(LodQuadBuilder builder, EGpuUploadMethod method)
@@ -552,6 +564,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
return;
}
this.closed = true;
this.buffersUploaded = false;
GLProxy.getInstance().recordOpenGlCall(() ->
{
@@ -578,13 +591,15 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
// getters //
//=========//
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; }
}
@@ -199,10 +199,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
if (nullableRenderSection != null)
{
// TODO this logic isn't ready
// it can cause buffers to render on top of each other and doesn't completely solve the empty section rendering bug.
// Some sort of logic to determine if the buffer has been uploaded is probably necessary
// if (areChildRenderSectionsLoaded(nullableRenderSection))
if (areChildRenderSectionsLoaded(nullableRenderSection))
{
nullableRenderSection.disableAndDisposeRendering();
}
@@ -250,21 +247,31 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
// enable the render section
nullableRenderSection.loadRenderSourceAndEnableRendering(this.renderSourceProvider);
// determine if the section has loaded yet // TODO rename "tick" to check loading future or something?
nullableRenderSection.tick(this.level);
// delete/disable children
nullableQuadNode.deleteAllChildren((renderSection) ->
if (isSectionLoaded(nullableRenderSection))
{
if (renderSection != null)
nullableQuadNode.deleteAllChildren((renderSection) ->
{
renderSection.disableAndDisposeRendering();
}
});
if (renderSection != null)
{
renderSection.disableAndDisposeRendering();
}
});
}
}
}
}
/**
* Used to determine if a section can unload or not.
* If this returns true, that means there are child render sections ready to render,
* so there won't be any holes in the world by disabling the parent.
* <br><Br>
* FIXME sometimes sections will render on top of each other
*/
private boolean areChildRenderSectionsLoaded(LodRenderSection renderSection)
{
if (renderSection == null)
@@ -279,31 +286,40 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
}
else
{
// recursively look for a loaded child
// recursively check if all children are loaded
try
for (int i = 0; i < 4; i++)
{
LodRenderSection child0 = this.getChildSection(renderSection.pos, 0);
LodRenderSection child1 = this.getChildSection(renderSection.pos, 1);
LodRenderSection child2 = this.getChildSection(renderSection.pos, 2);
LodRenderSection child3 = this.getChildSection(renderSection.pos, 3);
// either the child section is loaded, or check the next section down
return (isSectionLoaded(child0) || areChildRenderSectionsLoaded(child0))
&& (isSectionLoaded(child1) || areChildRenderSectionsLoaded(child1))
&& (isSectionLoaded(child2) || areChildRenderSectionsLoaded(child2))
&& (isSectionLoaded(child3) || areChildRenderSectionsLoaded(child3));
}
catch (IndexOutOfBoundsException e)
{
// FIXME will happen if children are outside the render distance
return true;
DhSectionPos childPos = renderSection.pos.getChildByIndex(i);
// if a section is out of bounds, act like it is loaded
if (this.isSectionPosInBounds(childPos))
{
LodRenderSection child = this.getChildSection(renderSection.pos, i);
// check if either this child or all of its children are loaded
boolean childLoaded = isSectionLoaded(child) || areChildRenderSectionsLoaded(child);
if (!childLoaded)
{
// at least one child isn't loaded
return false;
}
}
}
// all children are loaded
return true;
}
}
private static boolean isSectionLoaded(LodRenderSection renderSection)
{
return renderSection != null && renderSection.isLoaded() && renderSection.isRenderingEnabled() && !renderSection.getRenderSource().isEmpty();
return renderSection != null
&& renderSection.isLoaded()
&& renderSection.isRenderingEnabled()
&& renderSection.renderBufferRef.get() != null
&& renderSection.renderBufferRef.get().areBuffersUploaded()
&& renderSection.getRenderSource() != null
&& !renderSection.getRenderSource().isEmpty();
}
@@ -1,6 +1,7 @@
package com.seibel.lod.core.render;
import com.seibel.lod.core.dataObjects.render.ColumnRenderSource;
import com.seibel.lod.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
import com.seibel.lod.core.level.IDhClientLevel;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.pos.DhSectionPos;
@@ -22,7 +23,7 @@ public class LodRenderSection
private ColumnRenderSource renderSource;
private ILodRenderSourceProvider renderSourceProvider = null;
public final AtomicReference<AbstractRenderBuffer> abstractRenderBufferRef = new AtomicReference<>();
public final AtomicReference<ColumnRenderBuffer> renderBufferRef = new AtomicReference<>();
@@ -128,10 +129,10 @@ public class LodRenderSection
this.renderSource = null;
}
if (this.abstractRenderBufferRef.get() != null)
if (this.renderBufferRef.get() != null)
{
this.abstractRenderBufferRef.get().close();
this.abstractRenderBufferRef.set(null);
this.renderBufferRef.get().close();
this.renderBufferRef.set(null);
}
if (this.loadFuture != null)
@@ -139,10 +139,9 @@ public class RenderBufferHandler
{
if (renderSection != null && renderSection.shouldRender())
{
// this should always be true
if (renderSection.abstractRenderBufferRef.get() != null)
if (renderSection.renderBufferRef.get() != null && renderSection.renderBufferRef.get().areBuffersUploaded())
{
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(renderSection.abstractRenderBufferRef.get(), sectionPos));
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(renderSection.renderBufferRef.get(), sectionPos));
}
}
});
@@ -171,7 +170,7 @@ public class RenderBufferHandler
if (!renderSection.shouldRender())
{
//TODO: Does this really need to force the old buffer to not be rendered?
AbstractRenderBuffer previousRenderBuffer = renderSection.abstractRenderBufferRef.getAndSet(null);
AbstractRenderBuffer previousRenderBuffer = renderSection.renderBufferRef.getAndSet(null);
if (previousRenderBuffer != null)
{
previousRenderBuffer.close();
@@ -180,7 +179,7 @@ public class RenderBufferHandler
else
{
LodUtil.assertTrue(currentRenderSource != null); // section.shouldRender() should have ensured this
currentRenderSource.trySwapRenderBuffer(renderSection.getRenderSource(), renderSection.abstractRenderBufferRef);
currentRenderSource.trySwapInNewlyBuiltRenderBuffer(renderSection.getRenderSource(), renderSection.renderBufferRef);
}
}
});
@@ -190,10 +189,10 @@ public class RenderBufferHandler
{
this.quadTree.forEachValue((renderSection) ->
{
if (renderSection != null && renderSection.abstractRenderBufferRef.get() != null)
if (renderSection != null && renderSection.renderBufferRef.get() != null)
{
renderSection.abstractRenderBufferRef.get().close();
renderSection.abstractRenderBufferRef.set(null);
renderSection.renderBufferRef.get().close();
renderSection.renderBufferRef.set(null);
}
});
}