From 3e7fed7ad47018a19b6c6f97ac9403af657e4197 Mon Sep 17 00:00:00 2001 From: tom lee Date: Fri, 18 Feb 2022 14:56:54 +0800 Subject: [PATCH] Start rework to allow individual render region swapping --- .../com/seibel/lod/core/api/ClientApi.java | 13 +- .../LodBufferBuilderFactory.java | 260 +----------------- .../core/enums/config/BufferRebuildTimes.java | 2 + .../seibel/lod/core/objects/RenderRegion.java | 43 +++ .../objects/opengl/ComplexRenderRegion.java | 114 ++++++++ .../core/objects/opengl/LodVertexBuffer.java | 151 +++++++++- .../lod/core/objects/opengl/RenderRegion.java | 50 ---- .../objects/opengl/SimpleRenderRegion.java | 136 +++++++++ .../com/seibel/lod/core/render/GLProxy.java | 2 +- .../seibel/lod/core/render/LodRenderer.java | 48 +--- .../com/seibel/lod/core/util/StatsMap.java | 34 +++ .../com/seibel/lod/core/util/UnitBytes.java | 2 + 12 files changed, 506 insertions(+), 349 deletions(-) create mode 100644 src/main/java/com/seibel/lod/core/objects/RenderRegion.java create mode 100644 src/main/java/com/seibel/lod/core/objects/opengl/ComplexRenderRegion.java delete mode 100644 src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java create mode 100644 src/main/java/com/seibel/lod/core/objects/opengl/SimpleRenderRegion.java create mode 100644 src/main/java/com/seibel/lod/core/util/StatsMap.java diff --git a/src/main/java/com/seibel/lod/core/api/ClientApi.java b/src/main/java/com/seibel/lod/core/api/ClientApi.java index 56420fc5a..dcaf6b89e 100644 --- a/src/main/java/com/seibel/lod/core/api/ClientApi.java +++ b/src/main/java/com/seibel/lod/core/api/ClientApi.java @@ -33,7 +33,6 @@ import org.lwjgl.glfw.GLFW; import com.seibel.lod.core.ModInfo; import com.seibel.lod.core.enums.config.DistanceGenerationMode; -import com.seibel.lod.core.enums.config.VerticalQuality; import com.seibel.lod.core.objects.lod.LodDimension; import com.seibel.lod.core.objects.math.Mat4f; import com.seibel.lod.core.render.GLProxy; @@ -235,10 +234,13 @@ public class ClientApi ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler()); } catch (RuntimeException e) { rendererDisabledBecauseOfExceptions = true; + ClientApi.LOGGER.error("Renderer thrown an uncaught exception: ",e); try { - //ClientApi.renderer.ma (); - } catch (RuntimeException welpLookLikeWeWillLeakResource) {} - throw e; + MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons" + + " renderer has encountered an exception!"); + MC.sendChatMessage("\u00A74Renderer is now disabled to prevent futher issues."); + MC.sendChatMessage("\u00A74Exception detail: "+e.toString()); + } catch (RuntimeException noMessagesThen) {} } } profiler.pop(); // end LOD @@ -254,8 +256,7 @@ public class ClientApi } catch (Exception e) { - ClientApi.LOGGER.error("client proxy: " + e.getMessage()); - e.printStackTrace(); + ClientApi.LOGGER.error("client proxy uncaught exception: ", e); } } diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java index f0919703d..863e4befa 100644 --- a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java @@ -19,10 +19,8 @@ package com.seibel.lod.core.builders.bufferBuilding; -import java.nio.ByteBuffer; import java.time.Duration; import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; @@ -33,9 +31,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; -import org.lwjgl.opengl.GL32; -import org.lwjgl.opengl.GL44; - import com.seibel.lod.core.api.ClientApi; import com.seibel.lod.core.enums.LodDirection; import com.seibel.lod.core.enums.config.GpuUploadMethod; @@ -43,12 +38,11 @@ import com.seibel.lod.core.enums.config.VanillaOverdraw; import com.seibel.lod.core.enums.rendering.DebugMode; import com.seibel.lod.core.enums.rendering.GLProxyContext; import com.seibel.lod.core.objects.PosToRenderContainer; +import com.seibel.lod.core.objects.RenderRegion; import com.seibel.lod.core.objects.lod.LodDimension; import com.seibel.lod.core.objects.lod.LodRegion; import com.seibel.lod.core.objects.lod.RegionPos; import com.seibel.lod.core.objects.opengl.LodQuadBuilder; -import com.seibel.lod.core.objects.opengl.LodVertexBuffer; -import com.seibel.lod.core.objects.opengl.RenderRegion; import com.seibel.lod.core.render.GLProxy; import com.seibel.lod.core.render.LodRenderer; import com.seibel.lod.core.util.DataPointUtil; @@ -58,7 +52,7 @@ import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.util.MovableGridList; import com.seibel.lod.core.util.SingletonHandler; import com.seibel.lod.core.util.SpamReducedLogger; -import com.seibel.lod.core.util.UnitBytes; +import com.seibel.lod.core.util.StatsMap; import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; @@ -143,14 +137,8 @@ public class LodBufferBuilderFactory { /** Used when building new VBOs */ public volatile MovableGridList buildableVbos; - public volatile int buildableCenterBlockX; - public volatile int buildableCenterBlockY; - public volatile int buildableCenterBlockZ; /** VBOs that are sent over to the LodNodeRenderer */ public volatile MovableGridList drawableVbos; - public volatile int drawableCenterBlockX; - public volatile int drawableCenterBlockY; - public volatile int drawableCenterBlockZ; /** * if this is true the LOD buffers need to be reset and the Renderer should call * the lodGenBuffers nomatter it should have been a full or partial regen or not @@ -186,9 +174,6 @@ public class LodBufferBuilderFactory { private MovableGridList setsToRender; - private int lastX = 0; - private int lastZ = 0; - public LodBufferBuilderFactory() { } @@ -246,14 +231,8 @@ public class LodBufferBuilderFactory { int playerRegionX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerX, LodUtil.REGION_DETAIL_LEVEL); int playerRegionZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerZ, LodUtil.REGION_DETAIL_LEVEL); int renderRange; - int vboX; - int vboY; - int vboZ; - boolean tooFar = Math.abs(buildableCenterBlockX - playerX) - + Math.abs(buildableCenterBlockZ - playerZ) > 100_000; - - if (fullRegen || tooFar || buildableVbos == null || setsToRender == null) { + if (fullRegen || buildableVbos == null || setsToRender == null) { if (buildableVbos != null) { buildableVbos.clear(RenderRegion::close); } @@ -261,20 +240,8 @@ public class LodBufferBuilderFactory { renderRange = lodDim.getWidth() / 2; // get lodDim half width buildableVbos = new MovableGridList(renderRange, playerRegionX, playerRegionZ); setsToRender = new MovableGridList(renderRange, playerRegionX, playerRegionZ); - // this will be the center of the VBOs once they have been built - // FIXME: Currently this will drift apart from player pos if there has not been - // a fullRegen for a while - buildableCenterBlockX = playerX; - buildableCenterBlockY = playerY; - buildableCenterBlockZ = playerZ; - vboX = playerX; - vboY = playerY; - vboZ = playerZ; } else { renderRange = buildableVbos.gridCentreToEdge; - vboX = buildableCenterBlockX; - vboY = buildableCenterBlockY; - vboZ = buildableCenterBlockZ; buildableVbos.move(playerRegionX, playerRegionZ, RenderRegion::close); setsToRender.move(playerRegionX, playerRegionZ); } @@ -292,8 +259,6 @@ public class LodBufferBuilderFactory { // minCullingRange); // int cullingRangeZ = Math.max((int)(1.5 * Math.abs(lastZ - playerZ)), // minCullingRange); - lastX = playerX; - lastZ = playerZ; List> futuresBuffer = new LinkedList>(); for (int indexX = 0; indexX < buildableVbos.gridSize; indexX++) { for (int indexZ = 0; indexZ < buildableVbos.gridSize; indexZ++) { @@ -324,19 +289,11 @@ public class LodBufferBuilderFactory { this.quadBuilder = quadBuilder; this.regionPos = regionPos; } - - LodQuadBuilder quadBuilder() { - return quadBuilder; - } - - RegionPos regionPos() { - return regionPos; - } } CompletableFuture future = CompletableFuture.supplyAsync(() -> { LodQuadBuilder quadBuilder = new LodQuadBuilder(6); - makeLodRenderData(quadBuilder, lodDim, regionPos, pX, pZ, vboX, vboZ, minDetail); + makeLodRenderData(quadBuilder, lodDim, regionPos, pX, pZ, minDetail); return new ResultPair(quadBuilder, regionPos); }, bufferUploadThread).whenCompleteAsync((result, e) -> { if (e != null) @@ -400,7 +357,7 @@ public class LodBufferBuilderFactory { } private RegionPos makeLodRenderData(LodQuadBuilder quadBuilder, LodDimension lodDim, RegionPos regPos, int playerX, - int playerZ, int vboX, int vboZ, byte minDetail) {// , int cullingRangeX, int cullingRangeZ) { + int playerZ, byte minDetail) {// , int cullingRangeX, int cullingRangeZ) { // Variable initialization int playerChunkX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL, playerX, LodUtil.CHUNK_DETAIL_LEVEL); @@ -523,29 +480,15 @@ public class LodBufferBuilderFactory { if (!ramLogger.canMaybeLog()) return; ramLogger.info("Dumping Ram Usage for buffer usage..."); - long bufferCount = 0; - long fullBufferCount = 0; - long totalUsage = 0; - int maxLength = MAX_TRIANGLES_PER_BUFFER * (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3); + StatsMap statsMap = new StatsMap(); + if (buildableVbos == null) { ramLogger.info("Buildable VBOs are null!"); } else for (RenderRegion buffers : buildableVbos) { if (buffers == null) continue; - LodVertexBuffer[] bs = buffers.debugGetBuffers().clone(); - for (LodVertexBuffer b : bs) { - if (b == null) - continue; - bufferCount++; - if (b.size == maxLength) { - fullBufferCount++; - } else if (b.size > maxLength) { - ramLogger.info("BUFFER OVERSIZED: {} (max size is {})", new UnitBytes(b.size), - new UnitBytes(maxLength)); - } - totalUsage += b.size; - } + buffers.debugDumpStats(statsMap); } if (drawableVbos == null) { ramLogger.info("Drawable VBOs are null!"); @@ -553,23 +496,10 @@ public class LodBufferBuilderFactory { for (RenderRegion buffers : drawableVbos) { if (buffers == null) continue; - LodVertexBuffer[] bs = buffers.debugGetBuffers().clone(); - for (LodVertexBuffer b : bs) { - if (b == null) - continue; - bufferCount++; - if (b.size == maxLength) { - fullBufferCount++; - } else if (b.size > maxLength) { - ramLogger.info("BUFFER OVERSIZED: {} (max size is {})", new UnitBytes(b.size), - new UnitBytes(maxLength)); - } - totalUsage += b.size; - } + buffers.debugDumpStats(statsMap); } ramLogger.info("================================================"); - ramLogger.info("Buffers: [{}], Full-sized Buffers: [{}], Total: [{}]", bufferCount, fullBufferCount, - new UnitBytes(totalUsage)); + ramLogger.info("Stats: {}", statsMap); ramLogger.info("================================================"); ramLogger.incLogTries(); } @@ -621,163 +551,21 @@ public class LodBufferBuilderFactory { // determine the upload method GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod(); - // determine the upload timeout - long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds(); // MB -> B = - // 1/1,000,000. - // MS -> NS = - // 1,000,000. - // So, MBPerMS = - // BPerNS. - long remainingNS = 0; // We don't want to pause for like 0.1 ms... so we store those tiny MS. - long totalBytes = 0; - int vbosNeeded = quadBuilder.getCurrentNeededVertexBuffers(); - // Setup the VBO array LagSpikeCatcher vboSetup = new LagSpikeCatcher(); RenderRegion renderRegion = buildableVbos.get(p.x, p.z); - if (renderRegion == null) - renderRegion = buildableVbos.setAndGet(p.x, p.z, new RenderRegion(vbosNeeded)); - else - renderRegion.resize(vbosNeeded); + RenderRegion newRenderRegion = RenderRegion.updateStatus(renderRegion, quadBuilder, p); + if (newRenderRegion != null) { + renderRegion = buildableVbos.setAndGet(p.x, p.z, newRenderRegion); + } vboSetup.end("vboSetup"); - // Start the upload - Iterator iter = quadBuilder.makeVertexBuffers(); - int i = 0; - while (iter.hasNext()) { - ByteBuffer bb = iter.next(); - LagSpikeCatcher vboU = new LagSpikeCatcher(); - vboUpload(p, i++, bb, uploadMethod); - vboU.end("vboUpload"); - - // upload buffers over an extended period of time - // to hopefully prevent stuttering. - totalBytes += bb.limit(); - remainingNS += bb.limit() * BPerNS; - if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) { - if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) - remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS; - try { - Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000)); - } catch (InterruptedException e) { - } - remainingNS = 0; - } - } - if (ENABLE_BUFFER_UPLOAD_LOGGING) - ClientApi.LOGGER.info("Uploaded {} sub buffers for {} with total of {}", i, p, - new UnitBytes(totalBytes)); + renderRegion.uploadBuffers(quadBuilder, uploadMethod); } finally { glProxy.setGlContext(oldContext); } } - /** Uploads the uploadBuffer so the GPU can use it. */ - private void vboUpload(RegionPos regPos, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod) { - int maxLength = MAX_TRIANGLES_PER_BUFFER * (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3); - boolean useBuffStorage = uploadMethod == GpuUploadMethod.BUFFER_STORAGE; - RenderRegion region = buildableVbos.get(regPos.x, regPos.z); - LodVertexBuffer vbo = region.getOrMakeVbo(iIndex, useBuffStorage); - - // this is how many points will be rendered - vbo.vertexCount = (uploadBuffer.limit() / LodUtil.LOD_VERTEX_FORMAT.getByteSize()); - - // If size is zero, just ignore it. - if (uploadBuffer.limit() == 0) - return; - LagSpikeCatcher bindBuff = new LagSpikeCatcher(); - bindBuff.end("glBindBuffer vbo.id"); - - try { - // if possible use the faster buffer storage route - if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE) { - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); - long size = vbo.size; - if (size < uploadBuffer.limit() - || size > uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) { - int newSize = (int) (uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER); - if (newSize > maxLength) - newSize = maxLength; - LagSpikeCatcher buffResizeRegen = new LagSpikeCatcher(); - GL32.glDeleteBuffers(vbo.id); - vbo.id = GL32.glGenBuffers(); - buffResizeRegen.end("glDeleteBuffers BuffStorage resize"); - LagSpikeCatcher buffResize = new LagSpikeCatcher(); - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); - GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_DYNAMIC_STORAGE_BIT); - vbo.size = newSize; - buffResize.end("glBufferStorage BuffStorage resize"); - } - LagSpikeCatcher buffSubData = new LagSpikeCatcher(); - GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer); - buffSubData.end("glBufferSubData BuffStorage"); - } else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING) { - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); - // no stuttering but high GPU usage - // stores everything in system memory instead of GPU memory - // making rendering much slower. - // Unless the user is running integrated graphics, - // in that case this will actually work better than SUB_DATA. - long size = vbo.size; - if (size < uploadBuffer.limit() - || size > uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) { - int newSize = (int) (uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER); - if (newSize > maxLength) - newSize = maxLength; - LagSpikeCatcher buffResize = new LagSpikeCatcher(); - GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW); - vbo.size = newSize; - buffResize.end("glBufferData BuffMapping resize"); - } - ByteBuffer vboBuffer; - // map buffer range is better since it can be explicitly unsynchronized - LagSpikeCatcher buffMap = new LagSpikeCatcher(); - vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer.limit(), - GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT); - buffMap.end("glMapBufferRange BuffMapping"); - LagSpikeCatcher buffWrite = new LagSpikeCatcher(); - vboBuffer.put(uploadBuffer); - LagSpikeCatcher buffUnmap = new LagSpikeCatcher(); - GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER); - buffUnmap.end("glUnmapBuffer"); - - buffWrite.end("WriteData BuffMapping"); - } else if (uploadMethod == GpuUploadMethod.DATA) { - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); - // TODO: Check this nonsense comment! - // hybrid bufferData // - // high stutter, low GPU usage - // But simplest/most compatible - LagSpikeCatcher buffData = new LagSpikeCatcher(); - GL32.glBufferData(GL32.GL_ARRAY_BUFFER, uploadBuffer, GL32.GL_STATIC_DRAW); - vbo.size = uploadBuffer.limit(); - buffData.end("glBufferData Data"); - } else { - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); - // TODO: Check this nonsense comment! - // hybrid subData/bufferData // - // less stutter, low GPU usage - long size = vbo.size; - if (size < uploadBuffer.limit() - || size > uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) { - int newSize = (int) (uploadBuffer.limit() * BUFFER_EXPANSION_MULTIPLIER); - if (newSize > maxLength) - newSize = maxLength; - LagSpikeCatcher buffResize = new LagSpikeCatcher(); - GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW); - vbo.size = newSize; - buffResize.end("glBufferData SubData resize"); - } - LagSpikeCatcher buffSubData = new LagSpikeCatcher(); - GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer); - buffSubData.end("glBufferSubData SubData"); - } - } catch (Exception e) { - ClientApi.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName()); - e.printStackTrace(); - } - }// vboUpload - private boolean swapBuffers() { bufferLock.lock(); if (ENABLE_BUFFER_SWAP_LOGGING) @@ -790,16 +578,6 @@ public class LodBufferBuilderFactory { buildableVbos = tmpVbo; // ClientApi.LOGGER.info("Lod Swapped Buffers: "+drawableVbos.toDetailString()); - - int tempX = drawableCenterBlockX; - int tempY = drawableCenterBlockY; - int tempZ = drawableCenterBlockZ; - drawableCenterBlockX = buildableCenterBlockX; - drawableCenterBlockY = buildableCenterBlockY; - drawableCenterBlockZ = buildableCenterBlockZ; - buildableCenterBlockX = tempX; - buildableCenterBlockY = tempY; - buildableCenterBlockZ = tempZ; // the vbos have been swapped switchVbos = false; @@ -824,14 +602,6 @@ public class LodBufferBuilderFactory { return shouldDrawFrontBuffer() ? drawableVbos : null; } - public int getFrontBuffersCenterX() { - return drawableCenterBlockX; - } - - public int getFrontBuffersCenterZ() { - return drawableCenterBlockZ; - } - public void triggerReset() { allBuffersRequireReset = true; hideBackBuffer = true; diff --git a/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java b/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java index 3c5db0390..9c66f7740 100644 --- a/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java +++ b/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java @@ -31,6 +31,8 @@ package com.seibel.lod.core.enums.config; */ public enum BufferRebuildTimes { + CONSTANT(0, 0, 0, 1), + FREQUENT(1000, 500, 2500, 1), NORMAL(2000, 1000, 5000, 4), diff --git a/src/main/java/com/seibel/lod/core/objects/RenderRegion.java b/src/main/java/com/seibel/lod/core/objects/RenderRegion.java new file mode 100644 index 000000000..2385e5ea7 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/RenderRegion.java @@ -0,0 +1,43 @@ +package com.seibel.lod.core.objects; + +import com.seibel.lod.core.enums.config.GpuUploadMethod; +import com.seibel.lod.core.objects.lod.RegionPos; +import com.seibel.lod.core.objects.opengl.ComplexRenderRegion; +import com.seibel.lod.core.objects.opengl.LodQuadBuilder; +import com.seibel.lod.core.objects.opengl.SimpleRenderRegion; +import com.seibel.lod.core.render.LodRenderProgram; +import com.seibel.lod.core.util.StatsMap; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; + +public abstract class RenderRegion implements AutoCloseable +{ + // target can be Null. + // If return null, means all status updated without switching objects. + @SuppressWarnings("resource") + public static RenderRegion updateStatus(RenderRegion target, LodQuadBuilder builder, RegionPos regPos) { + boolean useSimpleRegion = (builder.getCurrentNeededVertexBuffers() <= 6) || true; + if ((target instanceof SimpleRenderRegion && !useSimpleRegion) || + target instanceof ComplexRenderRegion && useSimpleRegion) { + target.close(); + target = null; + } + if (target == null) { + return useSimpleRegion ? + new SimpleRenderRegion(builder.getCurrentNeededVertexBuffers(), regPos) + : new ComplexRenderRegion(regPos); + } + return null; + } + + public abstract void uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod); + public abstract boolean shouldRender(IMinecraftRenderWrapper renderer, boolean enableDirectionalCulling); + public abstract void render(LodRenderProgram shaderProgram); + public abstract void debugDumpStats(StatsMap statsMap); + + @Override + public abstract void close(); + + + + +} diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/ComplexRenderRegion.java b/src/main/java/com/seibel/lod/core/objects/opengl/ComplexRenderRegion.java new file mode 100644 index 000000000..55107357a --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/opengl/ComplexRenderRegion.java @@ -0,0 +1,114 @@ +package com.seibel.lod.core.objects.opengl; + +import java.util.TreeMap; + +import org.lwjgl.opengl.GL32; + +import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory; +import com.seibel.lod.core.builders.lodBuilding.LodBuilder; +import com.seibel.lod.core.enums.config.GpuUploadMethod; +import com.seibel.lod.core.objects.RenderRegion; +import com.seibel.lod.core.objects.lod.RegionPos; +import com.seibel.lod.core.objects.math.Mat4f; +import com.seibel.lod.core.objects.math.Vec3d; +import com.seibel.lod.core.render.LodRenderProgram; +import com.seibel.lod.core.render.RenderUtil; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.StatsMap; +import com.seibel.lod.core.util.UnitBytes; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; + +public class ComplexRenderRegion extends RenderRegion { + LodVertexBuffer[] vbos; + final RegionPos regPos; + private static final float FULL_SIZED_BUFFERS = + LodBufferBuilderFactory.MAX_TRIANGLES_PER_BUFFER * LodUtil.LOD_VERTEX_FORMAT.getByteSize(); + + public ComplexRenderRegion(RegionPos pos) { + vbos = new LodVertexBuffer[1]; + regPos = pos; + } + + public void resize(int size) { + if (vbos.length != size) { + LodVertexBuffer[] newVbos = new LodVertexBuffer[size]; + if (vbos.length > size) { + for (int i=size; i GL32.glDeleteBuffers(id)); + } + this.id = -1; + count--; + } + + private void _uploadBufferStorage(ByteBuffer bb, int maxExpensionSize) { + if (!isBufferStorage) throw new IllegalStateException("Buffer isn't bufferStorage but its trying to use BufferStorage upload method!"); + int bbSize = bb.limit() - bb.position(); + if (size < bbSize || size > bbSize * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) { + int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER); + if (newSize > maxExpensionSize) newSize = maxExpensionSize; + GL32.glDeleteBuffers(id); + id = GL32.glGenBuffers(); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id); + GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_MAP_WRITE_BIT); + size = newSize; + } else { + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id); + } + + // map buffer range is better since it can be explicitly unsynchronized + ByteBuffer vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, bbSize, + GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT); + if (vboBuffer == null) { + ClientApi.LOGGER.error("MapBufferRange Failed: bbSize: {}, maxSize: {}, size: {}", bbSize, maxExpensionSize, size); + } + vboBuffer.put(bb); + GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER); + } + + // no stuttering but high GPU usage + // stores everything in system memory instead of GPU memory + // making rendering much slower. + // Unless the user is running integrated graphics, + // in that case this will actually work better than SUB_DATA. + private void _uploadBufferMapping(ByteBuffer bb, int maxExpensionSize) { + if (isBufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use BufferMapping upload method!"); + int bbSize = bb.limit() - bb.position(); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id); + if (size < bbSize || size > bbSize * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) { + int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER); + if (newSize > maxExpensionSize) newSize = maxExpensionSize; + GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW); + size = newSize; + } + + // map buffer range is better since it can be explicitly unsynchronized + ByteBuffer vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, bbSize, + GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT); + vboBuffer.put(bb); + GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER); + } + + // bufferData + // simplest/most compatible + private void _uploadData(ByteBuffer bb) { + if (isBufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use Data upload method!"); + int bbSize = bb.limit() - bb.position(); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id); + GL32.glBufferData(GL32.GL_ARRAY_BUFFER, bb, GL32.GL_STATIC_DRAW); + size = bbSize; + } + + // bufferSubData + // less stutter, low GPU usage? + private void _uploadSubData(ByteBuffer bb, int maxExpensionSize) { + if (isBufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use SubData upload method!"); + int bbSize = bb.limit() - bb.position(); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id); + if (size < bbSize || size > bbSize * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) { + int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER); + if (newSize > maxExpensionSize) newSize = maxExpensionSize; + GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW); + size = newSize; + } + GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, bb); + } + + public void uploadBuffer(ByteBuffer bb, int vertCount, GpuUploadMethod uploadMethod, int maxExpensionSize) { + if (vertCount < 0) throw new IllegalArgumentException("VertCount is negative!"); + vertexCount = vertCount; + int bbSize = bb.limit()-bb.position(); + if (bbSize > maxExpensionSize) + throw new IllegalArgumentException("maxExpensionSize is "+maxExpensionSize+" but buffer size is "+bbSize+"!"); + // If size is zero, just ignore it. + if (bbSize == 0) return; + boolean useBuffStorage = uploadMethod == GpuUploadMethod.BUFFER_STORAGE; + if (useBuffStorage != isBufferStorage) { + _destroy(); + _create(useBuffStorage); + } + try { + switch (uploadMethod) { + case AUTO: + throw new IllegalArgumentException("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!"); + case BUFFER_MAPPING: + _uploadBufferMapping(bb, maxExpensionSize); + break; + case BUFFER_STORAGE: + _uploadBufferStorage(bb, maxExpensionSize); + break; + case DATA: + _uploadData(bb); + break; + case SUB_DATA: + _uploadSubData(bb, maxExpensionSize); + break; + default: + throw new IllegalArgumentException("Invalid GpuUploadMethod enum"); + } + } catch (IllegalArgumentException e) { + throw e; + } catch (Exception e) { + ClientApi.LOGGER.error("vboUpload failed: ", e); + } } @@ -56,15 +191,7 @@ public class LodVertexBuffer implements AutoCloseable { if (this.id >= 0) { - if (GLProxy.getInstance().getGlContext() == GLProxyContext.PROXY_WORKER) { - GL32.glDeleteBuffers(this.id); - } else { - final int id = this.id; - GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(id)); - } - this.id = -1; - count--; - ClientApi.LOGGER.debug("LodVertexBuffer Count: "+count); + _destroy(); if (count==0) ClientApi.LOGGER.info("All LodVerrtexBuffer is freed."); } else { ClientApi.LOGGER.error("LodVertexBuffer double close!"); diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java b/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java deleted file mode 100644 index 4da36d039..000000000 --- a/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.seibel.lod.core.objects.opengl; - -public class RenderRegion implements AutoCloseable { - LodVertexBuffer[] vbos; - - public RenderRegion(int size) { - vbos = new LodVertexBuffer[size]; - } - - public void resize(int size) { - if (vbos.length != size) { - LodVertexBuffer[] newVbos = new LodVertexBuffer[size]; - if (vbos.length > size) { - for (int i=size; i size) { + for (int i=size; i iter = builder.makeVertexBuffers(); + while (iter.hasNext()) { + ByteBuffer bb = iter.next(); + LodVertexBuffer vbo = getOrMakeVbo(i++, uploadMethod==GpuUploadMethod.BUFFER_STORAGE); + int size = bb.limit() - bb.position(); + vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), uploadMethod, FULL_SIZED_BUFFERS); + // 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 > LodBufferBuilderFactory.MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) + remainingNS = LodBufferBuilderFactory.MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS; + try { + Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000)); + } catch (InterruptedException e) { + } + remainingNS = 0; + } + } + } + + @Override + public boolean shouldRender(IMinecraftRenderWrapper renderer, boolean enableDirectionalCulling) { + if (enableDirectionalCulling && !RenderUtil.isRegionInViewFrustum(renderer.getCameraBlockPosition(), + renderer.getLookAtVector(), regPos.x, regPos.z)) return false; + return true; + } + + @Override + public void render(LodRenderProgram shaderProgram) + { + for (LodVertexBuffer vbo : vbos) { + if (vbo == null) continue; + if (vbo.vertexCount == 0) continue; + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); + shaderProgram.bindVertexBuffer(vbo.id); + GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.vertexCount); + } + + } + + @Override + public void debugDumpStats(StatsMap statsMap) + { + statsMap.incStat("RegionRegions"); + statsMap.incStat("SimpleRegionRegions"); + for (LodVertexBuffer b : vbos) { + if (b == null) continue; + statsMap.incStat("Buffers"); + if (b.size == FULL_SIZED_BUFFERS) { + statsMap.incStat("FullsizedBuffers"); + } + statsMap.incBytesStat("TotalUsage", b.size); + } + } + +} diff --git a/src/main/java/com/seibel/lod/core/render/GLProxy.java b/src/main/java/com/seibel/lod/core/render/GLProxy.java index 9f453541f..90f82f8b8 100644 --- a/src/main/java/com/seibel/lod/core/render/GLProxy.java +++ b/src/main/java/com/seibel/lod/core/render/GLProxy.java @@ -159,7 +159,7 @@ public class GLProxy { // boolean enableDebugLogging = CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL; - boolean enableDebugLogging = false; + boolean enableDebugLogging = true; // this must be created on minecraft's render context to work correctly ClientApi.LOGGER.info("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error."); diff --git a/src/main/java/com/seibel/lod/core/render/LodRenderer.java b/src/main/java/com/seibel/lod/core/render/LodRenderer.java index 8e554f117..dacf3af1f 100644 --- a/src/main/java/com/seibel/lod/core/render/LodRenderer.java +++ b/src/main/java/com/seibel/lod/core/render/LodRenderer.java @@ -34,15 +34,12 @@ import com.seibel.lod.core.enums.rendering.DebugMode; import com.seibel.lod.core.enums.rendering.FogColorMode; import com.seibel.lod.core.enums.rendering.FogDistance; import com.seibel.lod.core.handlers.IReflectionHandler; +import com.seibel.lod.core.objects.RenderRegion; import com.seibel.lod.core.objects.lod.LodDimension; import com.seibel.lod.core.objects.math.Mat4f; import com.seibel.lod.core.objects.math.Vec3d; -import com.seibel.lod.core.objects.math.Vec3f; -import com.seibel.lod.core.objects.opengl.LodVertexBuffer; -import com.seibel.lod.core.objects.opengl.RenderRegion; import com.seibel.lod.core.render.objects.LightmapTexture; import com.seibel.lod.core.util.DetailDistanceUtil; -import com.seibel.lod.core.util.LevelPosUtil; import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.util.MovableGridList; import com.seibel.lod.core.util.SingletonHandler; @@ -220,8 +217,6 @@ public class LodRenderer swapBuffer.end("SwapBuffer"); // Get the front buffers to draw MovableGridList regions = lodBufferBuilderFactory.getFrontBuffers(); - int vbosCenterX = lodBufferBuilderFactory.getFrontBuffersCenterX(); - int vbosCenterZ = lodBufferBuilderFactory.getFrontBuffersCenterZ(); if (regions == null) { // There is no vbos, which means nothing needs to be drawn. So skip rendering @@ -317,43 +312,26 @@ public class LodRenderer LagSpikeCatcher draw = new LagSpikeCatcher(); boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling(); + Vec3d cameraPos = MC_RENDER.getCameraExactPosition(); // where the center of the buffers is (needed when culling regions) // render each of the buffers int lowRegionX = regions.getCenterX() - regions.gridCentreToEdge; int lowRegionZ = regions.getCenterY() - regions.gridCentreToEdge; - int drawCall = 0; - int vCount0 = 0; for (int regionX=lowRegionX; regionX longMap = new TreeMap(); + final TreeMap bytesMap = new TreeMap(); + + /** + * + */ + @SuppressWarnings("unused") + private static final long serialVersionUID = 1926219295516863173L; + + public StatsMap() {super();} + + public void incStat(String key) { + incStat(key, 1); + } + public void incStat(String key, long value) { + longMap.put(key, longMap.getOrDefault(key, 0L)+value); + } + public void incBytesStat(String key, long bytes) { + bytesMap.put(key, new UnitBytes(bytesMap.getOrDefault(key, new UnitBytes(0)).value()+bytes)); + } + + @Override + public String toString() { + return longMap.toString() + " " + bytesMap.toString(); + } + + +} diff --git a/src/main/java/com/seibel/lod/core/util/UnitBytes.java b/src/main/java/com/seibel/lod/core/util/UnitBytes.java index 6a8081e96..9add9e62b 100644 --- a/src/main/java/com/seibel/lod/core/util/UnitBytes.java +++ b/src/main/java/com/seibel/lod/core/util/UnitBytes.java @@ -6,6 +6,8 @@ public class UnitBytes public UnitBytes(long value) { this.value = value; } + public long value() {return value;} + public static long byteToGB(long v) { return v/1073741824; }