diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java index 4b0433f7a..8cdfd8f20 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java @@ -36,13 +36,4 @@ public interface IDhApiGpuBuffersConfig extends IDhApiConfigGroup /** Defines how geometry data is uploaded to the GPU. */ IDhApiConfigValue gpuUploadMethod(); - /** - * Defines how long we should wait after uploading one - * Megabyte of geometry data to the GPU before uploading - * the next Megabyte of data.
- * This can be set to a non-zero number to reduce stuttering caused by - * uploading buffers to the GPU. - */ - IDhApiConfigValue gpuUploadPerMegabyteInMilliseconds(); - } diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java index f57efbe15..73f7eb63e 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java @@ -19,7 +19,8 @@ package com.seibel.distanthorizons.coreapi.util; -import java.util.ArrayList; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; import java.util.Arrays; /** @@ -85,4 +86,25 @@ public class StringUtil return new String(hexChars); } + /** + * Source: + * https://stackoverflow.com/questions/3758606/how-can-i-convert-byte-size-into-a-human-readable-format-in-java#3758880 + */ + public static String convertByteCountToHumanReadableSI(long bytes) + { + if (-1000 < bytes && bytes < 1000) + { + return bytes + " B"; + } + + CharacterIterator ci = new StringCharacterIterator("kMGTPE"); + while (bytes <= -999_950 || bytes >= 999_950) + { + bytes /= 1000; + ci.next(); + } + + return String.format("%.1f %cB", bytes / 1000.0, ci.current()); + } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java index 22f4852b0..a87e57092 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java @@ -36,7 +36,4 @@ public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig public IDhApiConfigValue gpuUploadMethod() { return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadMethod); } - public IDhApiConfigValue gpuUploadPerMegabyteInMilliseconds() - { return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds); } - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index 0ace849e4..1fc0065bb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -1054,20 +1054,6 @@ public class Config + "") .build(); - public static ConfigEntry gpuUploadPerMegabyteInMilliseconds = new ConfigEntry.Builder() - .setMinDefaultMax(0, 0, 50) - .comment("" - + "How long should a buffer wait per Megabyte of data uploaded? \n" - + "Helpful resource for frame times: https://fpstoms.com \n" - + "\n" - + "Longer times may reduce stuttering but will make LODs \n" - + "transition and load slower. Change this to [0] for no timeout. \n" - + "\n" - + "NOTE:\n" - + "Before changing this config, try changing the \"GPU Upload method\" first. \n" - + "") - .build(); - } public static class AutoUpdater diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java index 442a83625..618ff21f8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java @@ -20,15 +20,11 @@ package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; -import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhBlockPos; -import com.seibel.distanthorizons.core.render.glObject.GLProxy; -import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer; import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.util.LodUtil; -import com.seibel.distanthorizons.core.util.objects.StatsMap; import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; import com.seibel.distanthorizons.core.util.*; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; @@ -59,13 +55,12 @@ public class ColumnRenderBuffer implements AutoCloseable - public final DhBlockPos pos; public boolean buffersUploaded = false; - private GLVertexBuffer[] vbos; - private GLVertexBuffer[] vbosTransparent; + private SharedVbo.BufferSlice[] opaqueBufferSlices; + private SharedVbo.BufferSlice[] transparentBufferSlices; @@ -76,14 +71,12 @@ public class ColumnRenderBuffer implements AutoCloseable public ColumnRenderBuffer(DhBlockPos pos) { this.pos = pos; - this.vbos = new GLVertexBuffer[0]; - this.vbosTransparent = new GLVertexBuffer[0]; + this.opaqueBufferSlices = new SharedVbo.BufferSlice[0]; + this.transparentBufferSlices = new SharedVbo.BufferSlice[0]; } - - //==================// // buffer uploading // //==================// @@ -128,192 +121,143 @@ public class ColumnRenderBuffer implements AutoCloseable } private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException { - if (gpuUploadMethod.useEarlyMapping) - { - this.uploadBuffersMapped(builder, gpuUploadMethod); - } - else - { - this.uploadBuffersDirect(builder, gpuUploadMethod); - } + //if (gpuUploadMethod.useEarlyMapping) + //{ + // this.uploadBuffersMapped(builder, gpuUploadMethod); + //} + //else + //{ + this.uploadBuffersDirect(builder); + //} this.buffersUploaded = true; } - private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method) + //private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method) + //{ + // // opaque vbos // + // + // this.vbos = ColumnRenderBufferBuilder.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 = ColumnRenderBufferBuilder.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) { - // opaque vbos // - - this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount()); - for (int i = 0; i < this.vbos.length; i++) + int opaqueSliceCount = builder.getCurrentNeededOpaqueVertexBufferCount(); + if (this.opaqueBufferSlices.length != opaqueSliceCount) { - 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); + SharedVbo.OPAQUE.deallocateChunks(this.opaqueBufferSlices); + this.opaqueBufferSlices = new SharedVbo.BufferSlice[opaqueSliceCount]; } + uploadBuffersDirect(SharedVbo.OPAQUE, this.opaqueBufferSlices, builder.makeOpaqueVertexBuffers()); - // transparent vbos // - - this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount()); - for (int i = 0; i < this.vbosTransparent.length; i++) + int transparentSliceCount = builder.getCurrentNeededTransparentVertexBufferCount(); + if (this.transparentBufferSlices.length != transparentSliceCount) { - 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); + SharedVbo.TRANSPARENT.deallocateChunks(this.transparentBufferSlices); + this.transparentBufferSlices = new SharedVbo.BufferSlice[transparentSliceCount]; } + uploadBuffersDirect(SharedVbo.TRANSPARENT, this.transparentBufferSlices, builder.makeTransparentVertexBuffers()); } - private void uploadBuffersDirect(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException + private static void uploadBuffersDirect(SharedVbo handler, SharedVbo.BufferSlice[] bufferSlices, Iterator iter) { - this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount()); - uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method); - - this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount()); - uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method); - } - private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator iter, EDhApiGpuUploadMethod method) throws InterruptedException - { - long remainingMS = 0; - long MBPerMS = Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds.get(); - int vboIndex = 0; + int i = 0; while (iter.hasNext()) { - if (vboIndex >= vbos.length) + if (i >= bufferSlices.length) { throw new RuntimeException("Too many vertex buffers!!"); } - - // get or create the VBO - if (vbos[vboIndex] == null) + // get or create the buffer + if (bufferSlices[i] == null) { - vbos[vboIndex] = new GLVertexBuffer(method.useBufferStorage); + bufferSlices[i] = handler.allocateSlice(); } - GLVertexBuffer vbo = vbos[vboIndex]; + SharedVbo.BufferSlice slice = bufferSlices[i]; - ByteBuffer bb = iter.next(); - int size = bb.limit() - bb.position(); - + ByteBuffer buffer = iter.next(); try { - vbo.bind(); - vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER); + handler.updateBuffer(slice, buffer); } catch (Exception e) { - vbos[vboIndex] = null; - vbo.close(); - LOGGER.error("Failed to upload buffer: ", e); + bufferSlices[i] = null; + handler.deallocateChunk(slice); + LOGGER.error("Failed to upload buffer. Error: ["+e.getMessage()+"].", e); } - - if (MBPerMS > 0) - { - // upload buffers over an extended period of time - // to hopefully prevent stuttering. - remainingMS += size * MBPerMS; - if (remainingMS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) - { - if (remainingMS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) - { - remainingMS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS; - } - - Thread.sleep(remainingMS / 1000000, (int) (remainingMS % 1000000)); - remainingMS = 0; - } - } - - vboIndex++; + i++; } - if (vboIndex < vbos.length) + if (i < bufferSlices.length) { - throw new RuntimeException("Too few vertex buffers!!"); + throw new RuntimeException("Too few buffer chunks!"); } } - - //========// // render // //========// - /** @return true if something was rendered, false otherwise */ - public boolean renderOpaque(LodRenderer renderContext, DhApiRenderParam renderEventParam) + public void renderOpaque(LodRenderer renderContext, DhApiRenderParam renderEventParam) { - boolean hasRendered = false; renderContext.setModelViewMatrixOffset(this.pos, renderEventParam); - for (GLVertexBuffer vbo : this.vbos) + for (SharedVbo.BufferSlice slice : this.opaqueBufferSlices) { - if (vbo == null) + if (slice != null && slice.length != 0) { - continue; + renderContext.drawVbo(SharedVbo.OPAQUE, slice); } - - if (vbo.getVertexCount() == 0) - { - continue; - } - - hasRendered = true; - renderContext.drawVbo(vbo); - //LodRenderer.tickLogger.info("Vertex buffer: {}", vbo); } - return hasRendered; } - /** @return true if something was rendered, false otherwise */ - public boolean renderTransparent(LodRenderer renderContext, DhApiRenderParam renderEventParam) + public void renderTransparent(LodRenderer renderContext, DhApiRenderParam renderEventParam) { - boolean hasRendered = false; - - try + renderContext.setModelViewMatrixOffset(this.pos, renderEventParam); + for (SharedVbo.BufferSlice slice : this.transparentBufferSlices) { - // can throw an IllegalStateException if the GL program was freed before it should've been - renderContext.setModelViewMatrixOffset(this.pos, renderEventParam); - - for (GLVertexBuffer vbo : this.vbosTransparent) + if (slice != null && slice.length != 0) { - if (vbo == null) - { - continue; - } - - if (vbo.getVertexCount() == 0) - { - continue; - } - - hasRendered = true; - renderContext.drawVbo(vbo); - //LodRenderer.tickLogger.info("Vertex buffer: {}", vbo); + renderContext.drawVbo(SharedVbo.TRANSPARENT, slice); } } - catch (IllegalStateException e) - { - LOGGER.error("renderContext program doesn't exist for pos: "+this.pos, e); - } - - return hasRendered; } @@ -323,49 +267,26 @@ public class ColumnRenderBuffer implements AutoCloseable //==============// /** can be used when debugging */ - public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; } + public boolean hasNonNullVbos() { return false; } //this.vbos != null || this.vbosTransparent != null; } /** can be used when debugging */ - public int vboBufferCount() + public int bufferSliceCount() { int count = 0; - if (this.vbos != null) + if (this.opaqueBufferSlices != null) { - count += this.vbos.length; + count += this.opaqueBufferSlices.length; } - - if (this.vbosTransparent != null) + + if (this.transparentBufferSlices != null) { - count += this.vbosTransparent.length; + count += this.transparentBufferSlices.length; } return count; } - 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) - { - GLProxy.GL_LOGGER.warn("VBO with size 0"); - } - statsMap.incBytesStat("TotalUsage", vertexBuffer.getSize()); - } - } - } - /** * This method is called when object is no longer in use. * Called either after uploadBuffers() returned false (On buffer Upload @@ -377,24 +298,8 @@ public class ColumnRenderBuffer implements AutoCloseable { this.buffersUploaded = false; - GLProxy.getInstance().queueRunningOnRenderThread(() -> - { - for (GLVertexBuffer buffer : this.vbos) - { - if (buffer != null) - { - buffer.destroyAsync(); - } - } - - for (GLVertexBuffer buffer : this.vbosTransparent) - { - if (buffer != null) - { - buffer.destroyAsync(); - } - } - }); + SharedVbo.OPAQUE.deallocateChunks(this.opaqueBufferSlices); + SharedVbo.TRANSPARENT.deallocateChunks(this.transparentBufferSlices); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java index b5751e34b..a5743ca14 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java @@ -333,32 +333,4 @@ public class ColumnRenderBufferBuilder quadBuilder.finalizeData(); } - - - //=================// - // vbo interaction // - //=================// - - public 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; - } - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/SharedVbo.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/SharedVbo.java new file mode 100644 index 000000000..d795d05de --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/SharedVbo.java @@ -0,0 +1,173 @@ +package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; + +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.logging.f3.F3Screen; +import com.seibel.distanthorizons.core.render.glObject.GLProxy; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.coreapi.util.StringUtil; +import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL32; + +import java.nio.ByteBuffer; + +/** Used to allow multiple {@link ColumnRenderBuffer}'s to be put in a single VBO. */ +public class SharedVbo +{ + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + + public static final SharedVbo OPAQUE = new SharedVbo(1_000_000_000/*1GB*/, 1024 * 1024/*1MB*/); + public static final SharedVbo TRANSPARENT = new SharedVbo(1_000_000_000/*1GB*/, 1024 * 1024/*1MB*/); + + + public final int vboId; + + + private final int bufferTotalByteSize; + /** the length of a single chunk of this VBO in bytes. */ + private final int chunkByteSize; + + private final Int2ReferenceOpenHashMap bufferSliceByStartingIndex = new Int2ReferenceOpenHashMap<>(); + + + + //=============// + // constructor // + //=============// + + public SharedVbo(int totalSize, int chunkByteSize) + { + LodUtil.assertTrue(GLProxy.getInstance().runningOnRenderThread(), "Buffer Handler has to be created on the render thread."); + + this.bufferTotalByteSize = totalSize; + this.chunkByteSize = chunkByteSize; + + // Generate and bind the VBO + this.vboId = GL32.glGenBuffers(); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.vboId); + GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.bufferTotalByteSize, GL32.GL_DYNAMIC_DRAW); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); + } + + + + //============// + // allocation // + //============// + + @Nullable + public SharedVbo.BufferSlice allocateSlice() + { + // Find the first free chunk + for (int startingIndex = 0; startingIndex < this.bufferTotalByteSize; startingIndex += this.chunkByteSize) + { + // check if this section is free + if (!this.bufferSliceByStartingIndex.containsKey(startingIndex)) + { + BufferSlice newSlice = new BufferSlice(startingIndex, this.chunkByteSize); + this.bufferSliceByStartingIndex.put(startingIndex, newSlice); + return newSlice; + } + } + + return null; // No free chunk found + } + public void deallocateChunks(BufferSlice[] slices) + { + if (slices != null) + { + for (BufferSlice slice : slices) + { + if (slice != null) + { + this.bufferSliceByStartingIndex.remove(slice.startIndex); + } + } + } + } + public void deallocateChunk(BufferSlice chunk) + { + if (chunk != null) + { + this.bufferSliceByStartingIndex.remove(chunk.startIndex); + } + } + public void clear() { this.bufferSliceByStartingIndex.clear(); } + + + + //=================// + // buffer handling // + //=================// + + public void updateBuffer(BufferSlice chunk, ByteBuffer buffer) + { + int size = buffer.limit() - buffer.position(); + if (size > chunk.length) + { + // if this was fired that means we didn't split up the buffer into the right size + // if this isn't stopped the buffer will overwrite an adjacent section and cause incorrect rendering + throw new RuntimeException("Programmer error: Uploaded buffer bigger than the allocated area. Allocated: ["+chunk.length+"], buffer: ["+size+"]"); + } + + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.vboId); + GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, chunk.startIndex, buffer); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); + chunk.vertexCount = size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(); + } + + public void bind() { GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.vboId); } + public void unbind() { GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); } + + + + //=======// + // debug // + //=======// + + public String getDebugMenuString() + { + long maxChunkCount = (this.bufferTotalByteSize / this.chunkByteSize); + long chunkCount = 0; + long allocatedBytes = 0; + + for (BufferSlice slice : this.bufferSliceByStartingIndex.values()) + { + allocatedBytes += slice.length; + chunkCount++; + } + + return "Slices: ["+F3Screen.NUMBER_FORMAT.format(chunkCount)+"/"+F3Screen.NUMBER_FORMAT.format(maxChunkCount)+"], " + + "Mem: ["+StringUtil.convertByteCountToHumanReadableSI(allocatedBytes)+"/"+StringUtil.convertByteCountToHumanReadableSI(this.bufferTotalByteSize)+"]"; + } + + + + //================// + // helper classes // + //================// + + /** represents a single allocated slice of the parent VBO */ + public static class BufferSlice + { + public final int startIndex; + public final int length; + + public int vertexCount; + + + + //=============// + // constructor // + //=============// + + public BufferSlice(int startIndex, int length) + { + this.startIndex = startIndex; + this.length = length; + } + + } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java b/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java index b0e0f5449..990043a0d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java @@ -20,6 +20,7 @@ package com.seibel.distanthorizons.core.logging.f3; import com.seibel.distanthorizons.core.api.internal.SharedApi; +import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.SharedVbo; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; @@ -111,6 +112,11 @@ public class F3Screen messageList.add(genericRenderer.getVboRenderDebugMenuString()); } } + messageList.add(""); + // GPU memory + messageList.add("GPU Opaque " + SharedVbo.OPAQUE.getDebugMenuString()); + messageList.add("GPU Transp " + SharedVbo.TRANSPARENT.getDebugMenuString()); + messageList.add(""); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java index ffadc3c9d..e263a04ae 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java @@ -31,7 +31,6 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; -import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode; @@ -647,7 +646,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen } else if (renderSection.renderBuffer.hasNonNullVbos()) { - if (renderSection.renderBuffer.vboBufferCount() != 0) + if (renderSection.renderBuffer.bufferSliceCount() != 0) { color = Color.GREEN; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java index 681364ac2..da5d91f65 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java @@ -25,6 +25,7 @@ import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShader import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.SharedVbo; import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer; import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; @@ -35,11 +36,11 @@ import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.GLState; -import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer; import com.seibel.distanthorizons.core.render.glObject.buffer.QuadElementBuffer; import com.seibel.distanthorizons.core.render.glObject.texture.*; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; import com.seibel.distanthorizons.core.render.renderer.shaders.*; +import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; @@ -496,31 +497,23 @@ public class LodRenderer ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeBufferRenderEvent.class, new DhApiBeforeBufferRenderEvent.EventParam(renderEventParam, modelPos)); } - public void drawVbo(GLVertexBuffer vbo) + public void drawVbo(SharedVbo sharedVbo, SharedVbo.BufferSlice slice) { - //// can be uncommented to add additional debug validation to prevent crashes if invalid buffers are being created - //// shouldn't be used in production due to the performance hit - //if (GL32.glIsBuffer(vbo.getId())) + IDhApiShaderProgram shaderProgram = this.lodRenderProgram; + IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class); + if (shaderProgramOverride != null && shaderProgram.overrideThisFrame()) { - IDhApiShaderProgram shaderProgram = this.lodRenderProgram; - IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class); - if (shaderProgramOverride != null && shaderProgram.overrideThisFrame()) - { - shaderProgram = shaderProgramOverride; - } - - - vbo.bind(); - shaderProgram.bindVertexBuffer(vbo.getId()); - GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount() / 4) * 6, // TODO what does the 4 and 6 here represent? - this.quadIBO.getType(), 0); - vbo.unbind(); + shaderProgram = shaderProgramOverride; } - //else - //{ - // // will spam the log if uncommented, but helpful for validation - // //LOGGER.warn("Unable to draw VBO: "+vbo.getId()); - //} + + + sharedVbo.bind(); + shaderProgram.bindVertexBuffer(sharedVbo.vboId); + + int vertexCount = (slice.vertexCount / 4) * 6; // 4 vertices per quad, 6 indices per quad + int startingVertexIndex = slice.startIndex / LodUtil.LOD_VERTEX_FORMAT.getByteSize(); // Adjust base vertex calculation + GL32.glDrawElementsBaseVertex(GL32.GL_TRIANGLES, vertexCount, this.quadIBO.getType(), 0, startingVertexIndex); + sharedVbo.unbind(); } diff --git a/core/src/test/java/tests/CompressionTest.java b/core/src/test/java/tests/CompressionTest.java index f17af721a..6601bc02f 100644 --- a/core/src/test/java/tests/CompressionTest.java +++ b/core/src/test/java/tests/CompressionTest.java @@ -23,12 +23,11 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo; +import com.seibel.distanthorizons.coreapi.util.StringUtil; import it.unimi.dsi.fastutil.longs.LongArrayList; import org.junit.Assert; import java.io.*; -import java.text.CharacterIterator; -import java.text.StringCharacterIterator; /** * Note: @@ -356,10 +355,10 @@ public class CompressionTest System.out.println("\n"); System.out.println("Results: " + compressorName); System.out.println(); - System.out.println("Total uncompressed data: [" + humanReadableByteCountSI(totalUncompressedFileSizeInBytes) + "] Total compressed data: [" + humanReadableByteCountSI(totalCompressedFileSizeInBytes) + "]. Compression ratio: [" + compressionRatioString + "]."); - System.out.println("Min uncompressed data: [" + humanReadableByteCountSI(minUncompressedDtoSizeInBytes) + "] Min compressed data: [" + humanReadableByteCountSI(minCompressedDtoSizeInBytes) + "]."); - System.out.println("Max uncompressed data: [" + humanReadableByteCountSI(maxUncompressedDtoSizeInBytes) + "] Max compressed data: [" + humanReadableByteCountSI(maxCompressedDtoSizeInBytes) + "]."); - System.out.println("Avg uncompressed data: [" + humanReadableByteCountSI(avgUncompressedDtoSizeInBytes) + "] Avg compressed data: [" + humanReadableByteCountSI(avgCompressedDtoSizeInBytes) + "]."); + System.out.println("Total uncompressed data: [" + StringUtil.convertByteCountToHumanReadableSI(totalUncompressedFileSizeInBytes) + "] Total compressed data: [" + StringUtil.convertByteCountToHumanReadableSI(totalCompressedFileSizeInBytes) + "]. Compression ratio: [" + compressionRatioString + "]."); + System.out.println("Min uncompressed data: [" + StringUtil.convertByteCountToHumanReadableSI(minUncompressedDtoSizeInBytes) + "] Min compressed data: [" + StringUtil.convertByteCountToHumanReadableSI(minCompressedDtoSizeInBytes) + "]."); + System.out.println("Max uncompressed data: [" + StringUtil.convertByteCountToHumanReadableSI(maxUncompressedDtoSizeInBytes) + "] Max compressed data: [" + StringUtil.convertByteCountToHumanReadableSI(maxCompressedDtoSizeInBytes) + "]."); + System.out.println("Avg uncompressed data: [" + StringUtil.convertByteCountToHumanReadableSI(avgUncompressedDtoSizeInBytes) + "] Avg compressed data: [" + StringUtil.convertByteCountToHumanReadableSI(avgCompressedDtoSizeInBytes) + "]."); System.out.println(); System.out.println("Total read time in MS: [" + totalReadTimeInNano / 1_000_000.0 + "] Average read time per dto: [" + (totalReadTimeInNano / processedDtoCount) / 1_000_000.0 + "]"); System.out.println("Total write time in MS: [" + totalWriteTimeInNano / 1_000_000.0 + "] Average write time per dto: [" + (totalWriteTimeInNano / processedDtoCount) / 1_000_000.0 + "]"); @@ -373,23 +372,4 @@ public class CompressionTest } - /** - * Source: - * https://stackoverflow.com/questions/3758606/how-can-i-convert-byte-size-into-a-human-readable-format-in-java#3758880 - */ - public static String humanReadableByteCountSI(long bytes) - { - if (-1000 < bytes && bytes < 1000) - { - return bytes + " B"; - } - CharacterIterator ci = new StringCharacterIterator("kMGTPE"); - while (bytes <= -999_950 || bytes >= 999_950) - { - bytes /= 1000; - ci.next(); - } - return String.format("%.1f %cB", bytes / 1000.0, ci.current()); - } - }