diff --git a/api/src/main/java/com/seibel/lod/api/enums/config/EGpuUploadMethod.java b/api/src/main/java/com/seibel/lod/api/enums/config/EGpuUploadMethod.java index 3fdd478bb..8483f5f6a 100644 --- a/api/src/main/java/com/seibel/lod/api/enums/config/EGpuUploadMethod.java +++ b/api/src/main/java/com/seibel/lod/api/enums/config/EGpuUploadMethod.java @@ -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; } 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 cbbf92b05..aa1758636 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 @@ -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); } } 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 9338179fa..e4ce05bb0 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 @@ -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 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 { - 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 build(IDhClientLevel clientLevel, Reference usedBufferSlot, ColumnRenderSource renderSource, ColumnRenderSource[] adjData) + //==============// + // vbo building // + //==============// + + /** @return null if busy */ + public static CompletableFuture buildBuffers(IDhClientLevel clientLevel, Reference 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 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; } + } diff --git a/core/src/main/java/com/seibel/lod/core/render/AbstractRenderBuffer.java b/core/src/main/java/com/seibel/lod/core/render/AbstractRenderBuffer.java index c0548ac49..b6ee44d0d 100644 --- a/core/src/main/java/com/seibel/lod/core/render/AbstractRenderBuffer.java +++ b/core/src/main/java/com/seibel/lod/core/render/AbstractRenderBuffer.java @@ -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) ==========