diff --git a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java index 7ac9bcffc..376780d94 100644 --- a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java @@ -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 renderBufferToSwap) + public boolean trySwapInNewlyBuiltRenderBuffer(ColumnRenderSource renderSource, AtomicReference 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; } diff --git a/core/src/main/java/com/seibel/lod/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java b/core/src/main/java/com/seibel/lod/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java index 00e5a2cf1..569cdfa79 100644 --- a/core/src/main/java/com/seibel/lod/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java +++ b/core/src/main/java/com/seibel/lod/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java @@ -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 buildBuffers(IDhClientLevel clientLevel, Reference usedBufferSlot, ColumnRenderSource renderSource, ColumnRenderSource[] adjData) + public static CompletableFuture buildBuffers(IDhClientLevel clientLevel, Reference 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; } } diff --git a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java index ab41fe922..fc1536105 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java @@ -199,10 +199,7 @@ public class LodQuadTree extends QuadTree 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 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. + *

+ * 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 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(); } diff --git a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java index 79d619c4e..1ee22b003 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java @@ -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 abstractRenderBufferRef = new AtomicReference<>(); + public final AtomicReference 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) diff --git a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java index dc4d8ca93..072d2cd9f 100644 --- a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java @@ -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); } }); }