From fc5cb6796b7b6158ba1be94d8958aa8d7d67e0b8 Mon Sep 17 00:00:00 2001 From: TomTheFurry Date: Fri, 16 Sep 2022 17:53:54 +0800 Subject: [PATCH] Clean up transparency and buffer render orders. --- .../com/seibel/lod/core/util/math/Mat4f.java | 5 +- .../lod/core/datatype/LodRenderSource.java | 6 +- .../datatype/PlaceHolderRenderSource.java | 7 +- .../datatype/column/ColumnRenderSource.java | 39 +-- .../column/render/ColumnRenderBuffer.java | 172 ++++++------ .../column/render/LodQuadBuilder.java | 262 ++++++++++++++---- .../datatype/full/IdBiomeBlockStateMap.java | 3 +- .../lod/core/generation/BatchGenerator.java | 195 ------------- .../lod/core/render/LodRenderSection.java | 2 +- .../seibel/lod/core/render/RenderBuffer.java | 4 +- .../lod/core/render/RenderBufferHandler.java | 183 +++++++----- .../lod/core/render/glObject/GLState.java | 5 +- .../lod/core/render/renderer/LodRenderer.java | 28 +- .../lod/core/util/objects/SortedArraySet.java | 155 +++++++++++ 14 files changed, 606 insertions(+), 460 deletions(-) create mode 100644 core/src/main/java/com/seibel/lod/core/util/objects/SortedArraySet.java diff --git a/api/src/main/java/com/seibel/lod/core/util/math/Mat4f.java b/api/src/main/java/com/seibel/lod/core/util/math/Mat4f.java index d3fcadb10..030a88cd4 100644 --- a/api/src/main/java/com/seibel/lod/core/util/math/Mat4f.java +++ b/api/src/main/java/com/seibel/lod/core/util/math/Mat4f.java @@ -472,7 +472,10 @@ public class Mat4f this.m33, }; } - + + public Vec3f asNonNormalizedLookForwardVector() { + return new Vec3f(this.m02, this.m12, this.m22); + } //===============// // Forge methods // diff --git a/core/src/main/java/com/seibel/lod/core/datatype/LodRenderSource.java b/core/src/main/java/com/seibel/lod/core/datatype/LodRenderSource.java index 0372b4c4f..439449815 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/LodRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/LodRenderSource.java @@ -17,18 +17,16 @@ public interface LodRenderSource { void enableRender(IClientLevel level, LodQuadTree quadTree); void disableRender(); - boolean isRenderReady(); void dispose(); // notify the container that the parent lodSection is now disposed (can be in loaded or unloaded state) /** * Try and swap in new render buffer for this section. Note that before this call, there should be no other * places storing or referencing the render buffer. - * @param referenceSlotsOpaque The opaque slot for swapping in the new buffer. - * @param referenceSlotsTransparent The transparent slot for swapping in the new buffer. + * @param referenceSlot The slot for swapping in the new buffer. * @return True if the swap was successful. False if swap is not needed or if it is in progress. */ - boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference referenceSlotsOpaque, AtomicReference referenceSlotsTransparent); + boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference referenceSlot); void saveRender(IClientLevel level, RenderMetaFile file, OutputStream dataStream) throws IOException; diff --git a/core/src/main/java/com/seibel/lod/core/datatype/PlaceHolderRenderSource.java b/core/src/main/java/com/seibel/lod/core/datatype/PlaceHolderRenderSource.java index 3146ef4cb..fa3c390e3 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/PlaceHolderRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/PlaceHolderRenderSource.java @@ -34,15 +34,10 @@ public class PlaceHolderRenderSource implements LodRenderSource { @Override public void disableRender() {} - - @Override - public boolean isRenderReady() { - return false; - } @Override public void dispose() {} @Override - public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference referenceSlotsOpaque, AtomicReference referenceSlotsTransparent) { + public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference referenceSlots) { return false; } @Override diff --git a/core/src/main/java/com/seibel/lod/core/datatype/column/ColumnRenderSource.java b/core/src/main/java/com/seibel/lod/core/datatype/column/ColumnRenderSource.java index 321525fe1..63b62fd5a 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/column/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/column/ColumnRenderSource.java @@ -265,9 +265,8 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype { return SECTION_SIZE_OFFSET; } - private CompletableFuture inBuildRenderBuffer = null; - private Reference usedBufferOpaque = new Reference<>(); - private Reference usedBufferTransparent = new Reference<>(); + private CompletableFuture inBuildRenderBuffer = null; + private Reference usedBuffer = new Reference<>(); private void tryBuildBuffer(IClientLevel level, LodQuadTree quadTree) { @@ -279,7 +278,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype { data[direction.ordinal()-2] = ((ColumnRenderSource) section.getRenderSource()); } } - inBuildRenderBuffer = ColumnRenderBuffer.build(level, usedBufferOpaque, usedBufferTransparent, this, data); + inBuildRenderBuffer = ColumnRenderBuffer.build(level, usedBuffer, this, data); } } private void cancelBuildBuffer() { @@ -302,11 +301,6 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype { cancelBuildBuffer(); } - @Override - public boolean isRenderReady() { - return inBuildRenderBuffer == null || inBuildRenderBuffer.isDone(); - } - @Override public void dispose() { cancelBuildBuffer(); @@ -318,7 +312,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype { private static final long SWAP_BUSY_COLLISION_TIMEOUT = /* 1 sec */ 1_000_000_000L; @Override - public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference referenceSlotsOpaque, AtomicReference referenceSlotsTransparent) { + public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference referenceSlot) { if (lastNs != -1 && System.nanoTime() - lastNs < SWAP_TIMEOUT) { return false; } @@ -326,29 +320,12 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype { if (inBuildRenderBuffer.isDone()) { lastNs = System.nanoTime(); //LOGGER.info("Swapping render buffer for {}", sectionPos); - - RenderBuffer[] newBuffers = inBuildRenderBuffer.join(); - - RenderBuffer oldBuffersOpaque = referenceSlotsOpaque.getAndSet(newBuffers[0]); - - ColumnRenderBuffer swapped; - - - if (oldBuffersOpaque instanceof ColumnRenderBuffer) { - swapped = usedBufferOpaque.swap((ColumnRenderBuffer) oldBuffersOpaque); + RenderBuffer newBuffer = inBuildRenderBuffer.join(); + RenderBuffer oldBuffer = referenceSlot.getAndSet(newBuffer); + if (oldBuffer instanceof ColumnRenderBuffer) { + ColumnRenderBuffer swapped = usedBuffer.swap((ColumnRenderBuffer) oldBuffer); LodUtil.assertTrue(swapped == null); } - - if(LodRenderer.transparencyEnabled) { - RenderBuffer oldBuffersTransparent = referenceSlotsTransparent.getAndSet(newBuffers[1]); - - if (LodRenderer.transparencyEnabled) { - if (oldBuffersTransparent instanceof ColumnRenderBuffer) { - swapped = usedBufferTransparent.swap((ColumnRenderBuffer) oldBuffersTransparent); - LodUtil.assertTrue(swapped == null); - } - } - } inBuildRenderBuffer = null; return true; } diff --git a/core/src/main/java/com/seibel/lod/core/datatype/column/render/ColumnRenderBuffer.java b/core/src/main/java/com/seibel/lod/core/datatype/column/render/ColumnRenderBuffer.java index 9828751bd..10ddfe7ec 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/column/render/ColumnRenderBuffer.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/column/render/ColumnRenderBuffer.java @@ -42,27 +42,25 @@ public class ColumnRenderBuffer extends RenderBuffer { private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000; GLVertexBuffer[] vbos; + GLVertexBuffer[] vbosTransparent; public final DhBlockPos pos; public ColumnRenderBuffer(DhBlockPos pos) { this.pos = pos; vbos = new GLVertexBuffer[0]; + vbosTransparent = new GLVertexBuffer[0]; } - - private void _uploadBuffersDirect(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException { - resize(builder.getCurrentNeededVertexBufferCount()); + 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; - Iterator iter = builder.makeVertexBuffers(); while (iter.hasNext()) { if (i >= vbos.length) { throw new RuntimeException("Too many vertex buffers!!"); } ByteBuffer bb = iter.next(); - GLVertexBuffer vbo = getOrMakeVbo(i++, method.useBufferStorage); + GLVertexBuffer vbo = getOrMake(vbos, i++, method.useBufferStorage); int size = bb.limit() - bb.position(); try { vbo.bind(); @@ -88,44 +86,59 @@ public class ColumnRenderBuffer extends RenderBuffer { } } + + 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) { - resize(builder.getCurrentNeededVertexBufferCount()); + vbos = resize(vbos, builder.getCurrentNeededOpaqueVertexBufferCount()); for (int i=0; i size) { - for (int i=size; i MAX_CONCURRENT_CALL; } - public static CompletableFuture build(IClientLevel clientLevel, Reference usedBufferSlotOpaque, Reference usedBufferSlotTransparent, ColumnRenderSource data, ColumnRenderSource[] adjData) { + public static CompletableFuture build(IClientLevel clientLevel, Reference usedBufferSlot, ColumnRenderSource data, ColumnRenderSource[] adjData) { if (isBusy()) return null; //LOGGER.info("RenderRegion startBuild @ {}", data.sectionPos); return CompletableFuture.supplyAsync(() -> { try { + boolean enableTransparency = Config.Client.Graphics.Quality.transparency.get().tranparencyEnabled; EVENT_LOGGER.trace("RenderRegion start QuadBuild @ {}", data.sectionPos); boolean enableSkyLightCulling = Config.Client.Graphics.AdvancedGraphics.enableCaveCulling.get(); int skyLightCullingBelow = Config.Client.Graphics.AdvancedGraphics.caveCullingHeight.get(); // FIXME: Clamp also to the max world height. skyLightCullingBelow = Math.max(skyLightCullingBelow, clientLevel.getMinY()); - LodQuadBuilder builderOpaque = new LodQuadBuilder(enableSkyLightCulling, - (short) (skyLightCullingBelow - clientLevel.getMinY())); + LodQuadBuilder builder = new LodQuadBuilder(enableSkyLightCulling, + (short) (skyLightCullingBelow - clientLevel.getMinY()), enableTransparency); - LodQuadBuilder builderTransparent = new LodQuadBuilder(enableSkyLightCulling, - (short) (skyLightCullingBelow - clientLevel.getMinY())); - - makeLodRenderData(builderOpaque, builderTransparent, data, adjData); - if (builderOpaque.getCurrentQuadsCount() > 0) { - //LOGGER.info("her"); - } + makeLodRenderData(builder, data, adjData); EVENT_LOGGER.trace("RenderRegion end QuadBuild @ {}", data.sectionPos); - LodQuadBuilder[] builders = new LodQuadBuilder[2]; - builders[0] = builderOpaque; - builders[1] = builderTransparent; - - return builders; + return builder; } catch (UncheckedInterruptedException e) { throw e; } @@ -220,35 +242,25 @@ public class ColumnRenderBuffer extends RenderBuffer { throw e3; } }, BUFFER_BUILDERS) - .thenApplyAsync((builders) -> { + .thenApplyAsync((builder) -> { try { EVENT_LOGGER.trace("RenderRegion start Upload @ {}", data.sectionPos); GLProxy glProxy = GLProxy.getInstance(); EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod(); EGLProxyContext oldContext = glProxy.getGlContext(); glProxy.setGlContext(EGLProxyContext.LOD_BUILDER); - ColumnRenderBuffer buffersSlotOpaque = usedBufferSlotOpaque.swap(null); - ColumnRenderBuffer buffersSlotTransparent = usedBufferSlotTransparent.swap(null); + ColumnRenderBuffer buffer = usedBufferSlot.swap(null); - if (buffersSlotOpaque == null) - buffersSlotOpaque = new ColumnRenderBuffer( + if (buffer == null) + buffer = new ColumnRenderBuffer( new DhBlockPos(data.sectionPos.getCorner().getCorner(), clientLevel.getMinY()) ); - if (buffersSlotTransparent == null) - buffersSlotTransparent = new ColumnRenderBuffer( - new DhBlockPos(data.sectionPos.getCorner().getCorner(), clientLevel.getMinY()) - ); try { - buffersSlotOpaque.uploadBuffer(builders[0], method); - buffersSlotTransparent.uploadBuffer(builders[1], method); + buffer.uploadBuffer(builder, method); EVENT_LOGGER.trace("RenderRegion end Upload @ {}", data.sectionPos); - ColumnRenderBuffer[] buffers = new ColumnRenderBuffer[2]; - buffers[0] = buffersSlotOpaque; - buffers[1] = buffersSlotTransparent; - return buffers; + return buffer; } catch (Exception e) { - buffersSlotOpaque.close(); - buffersSlotTransparent.close(); + buffer.close(); throw e; } finally { glProxy.setGlContext(oldContext); @@ -262,14 +274,10 @@ public class ColumnRenderBuffer extends RenderBuffer { }, BUFFER_UPLOADER).handle((v, e) -> { //LOGGER.info("RenderRegion endBuild @ {}", data.sectionPos); if (e != null) { - ColumnRenderBuffer buffersSlot; - if (!usedBufferSlotOpaque.isEmpty()) { - buffersSlot = usedBufferSlotOpaque.swap(null); - buffersSlot.close(); - } - if (!usedBufferSlotTransparent.isEmpty()) { - buffersSlot = usedBufferSlotTransparent.swap(null); - buffersSlot.close(); + ColumnRenderBuffer buffer; + if (!usedBufferSlot.isEmpty()) { + buffer = usedBufferSlot.swap(null); + buffer.close(); } return null; } else { @@ -280,7 +288,7 @@ public class ColumnRenderBuffer extends RenderBuffer { - private static void makeLodRenderData(LodQuadBuilder quadBuilderOpaque, LodQuadBuilder quadBuilderTransparent, ColumnRenderSource region, ColumnRenderSource[] adjRegions) { + private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource region, ColumnRenderSource[] adjRegions) { // Variable initialization EDebugMode debugMode = Config.Client.Advanced.Debugging.debugMode.get(); @@ -372,23 +380,11 @@ public class ColumnRenderBuffer extends RenderBuffer { long adjDataTop = i - 1 >= 0 ? posData.get(i - 1) : ColumnFormat.EMPTY_DATA; long adjDataBot = i + 1 < posData.size() ? posData.get(i + 1) : ColumnFormat.EMPTY_DATA; - - // We send the call to create the vertices - if(ColumnFormat.getAlpha(data) == 255 || !LodRenderer.transparencyEnabled) - { - CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel, - x, z, quadBuilderOpaque, debugMode); - } - else - { - CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel, - x, z, quadBuilderTransparent, debugMode); - } + CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel, + x, z, quadBuilder, debugMode); } } } - quadBuilderOpaque.mergeQuads(); - if(LodRenderer.transparencyEnabled) - quadBuilderTransparent.mergeQuads(); + quadBuilder.mergeQuads(); } } diff --git a/core/src/main/java/com/seibel/lod/core/datatype/column/render/LodQuadBuilder.java b/core/src/main/java/com/seibel/lod/core/datatype/column/render/LodQuadBuilder.java index 49fcf25de..0be3c05f7 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/column/render/LodQuadBuilder.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/column/render/LodQuadBuilder.java @@ -24,6 +24,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Iterator; import java.util.ListIterator; +import java.util.Objects; import com.seibel.lod.core.render.RenderBuffer; import com.seibel.lod.core.enums.ELodDirection; @@ -47,7 +48,11 @@ public class LodQuadBuilder private static final Logger LOGGER = DhLoggerBuilder.getLogger(); public final boolean skipQuadsWithZeroSkylight; public final short skyLightCullingBelow; - final ArrayList[] quads = (ArrayList[]) new ArrayList[6]; + @SuppressWarnings("unchecked") + final ArrayList[] opaqueQuads = (ArrayList[]) new ArrayList[6]; + @SuppressWarnings("unchecked") + final ArrayList[] transparentQuads = (ArrayList[]) new ArrayList[6]; + final boolean doTransparency; public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][] { @@ -100,18 +105,17 @@ public class LodQuadBuilder private int premergeCount = 0; - public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow) + public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow, boolean doTransparency) { - for (int i = 0; i < 6; i++) - quads[i] = new ArrayList<>(); - + this.doTransparency = doTransparency; + for (int i = 0; i < 6; i++) { + opaqueQuads[i] = new ArrayList<>(); + if (doTransparency) transparentQuads[i] = new ArrayList<>(); + } this.skipQuadsWithZeroSkylight = enableSkylightCulling; this.skyLightCullingBelow = skyLightCullingBelow; } - - - public void addQuadAdj(ELodDirection dir, short x, short y, short z, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte skylight, byte blocklight) @@ -121,7 +125,8 @@ public class LodQuadBuilder if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow) return; BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, skylight, blocklight, dir); - ArrayList qs = quads[dir.ordinal()]; + ArrayList qs = (doTransparency && ColorUtil.getAlpha(color) < 255) + ? transparentQuads[dir.ordinal()] : opaqueQuads[dir.ordinal()]; if (!qs.isEmpty() && (qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) || qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown)) @@ -138,7 +143,8 @@ public class LodQuadBuilder if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow) return; BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, ELodDirection.UP); - ArrayList qs = quads[ELodDirection.UP.ordinal()]; + ArrayList qs = (doTransparency && ColorUtil.getAlpha(color) < 255) + ? transparentQuads[ELodDirection.UP.ordinal()] : opaqueQuads[ELodDirection.UP.ordinal()]; if (!qs.isEmpty() && (qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) || qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown)) @@ -154,7 +160,8 @@ public class LodQuadBuilder if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow) return; BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, ELodDirection.DOWN); - ArrayList qs = quads[ELodDirection.DOWN.ordinal()]; + ArrayList qs = (doTransparency && ColorUtil.getAlpha(color) < 255) + ? transparentQuads[ELodDirection.DOWN.ordinal()] : opaqueQuads[ELodDirection.DOWN.ordinal()]; if (!qs.isEmpty() && (qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) || qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown)) @@ -249,35 +256,38 @@ public class LodQuadBuilder public void mergeQuads() { long mergeCount = 0; - long preQuadsCount = getCurrentQuadsCount(); + long preQuadsCount = getCurrentOpaqueQuadsCount() + getCurrentTransparentQuadsCount(); if (preQuadsCount <= 1) return; for (int directionIndex = 0; directionIndex < 6; directionIndex++) { - mergeCount += mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.EastWest); + mergeCount += mergeQuadsInternal(opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest); + if (doTransparency) + mergeCount += mergeQuadsInternal(transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest); // only run the second merge if the face is the top or bottom - //if (directionIndex == LodDirection.UP.ordinal() || directionIndex == LodDirection.DOWN.ordinal()) - //{ - // long pass2 = mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); - // mergeCount += pass2; - //} + if (directionIndex == ELodDirection.UP.ordinal() || directionIndex == ELodDirection.DOWN.ordinal()) + { + mergeCount += mergeQuadsInternal(opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); + if (doTransparency) + mergeCount += mergeQuadsInternal(transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); + } } - long postQuadsCount = getCurrentQuadsCount(); + long postQuadsCount = getCurrentOpaqueQuadsCount() + getCurrentTransparentQuadsCount(); //if (mergeCount != 0) LOGGER.debug("Merged {}/{}({}) quads", mergeCount, preQuadsCount, mergeCount / (double) preQuadsCount); } /** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */ - private long mergeQuadsInternal(int directionIndex, BufferMergeDirectionEnum mergeDirection) + private static long mergeQuadsInternal(ArrayList[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection) { - if (quads[directionIndex].size() <= 1) + if (list[directionIndex].size() <= 1) return 0; - - quads[directionIndex].sort( (objOne, objTwo) -> objOne.compare(objTwo, mergeDirection) ); + + list[directionIndex].sort( (objOne, objTwo) -> objOne.compare(objTwo, mergeDirection) ); long mergeCount = 0; - ListIterator iter = quads[directionIndex].listIterator(); + ListIterator iter = list[directionIndex].listIterator(); BufferQuad currentQuad = iter.next(); while (iter.hasNext()) { @@ -295,13 +305,13 @@ public class LodQuadBuilder currentQuad = nextQuad; } } - quads[directionIndex].removeIf(o -> o == null); + list[directionIndex].removeIf(Objects::isNull); return mergeCount; } - public Iterator makeVertexBuffers() + public Iterator makeOpaqueVertexBuffers() { return new Iterator() { @@ -312,7 +322,7 @@ public class LodQuadBuilder private int skipEmpty(int d) { - while (d < 6 && quads[d].isEmpty()) + while (d < 6 && opaqueQuads[d].isEmpty()) d++; return d; } @@ -344,16 +354,82 @@ public class LodQuadBuilder private void writeData() { int i = quad; - for (; i < quads[dir].size(); i++) + for (; i < opaqueQuads[dir].size(); i++) { if (!bb.hasRemaining()) { break; } - putQuad(bb, quads[dir].get(i)); + putQuad(bb, opaqueQuads[dir].get(i)); } - if (i >= quads[dir].size()) + if (i >= opaqueQuads[dir].size()) + { + quad = 0; + dir++; + dir = skipEmpty(dir); + } + else + { + quad = i; + } + } + }; + } + + public Iterator makeTransparentVertexBuffers() + { + return new Iterator() + { + final ByteBuffer bb = ByteBuffer.allocateDirect(RenderBuffer.FULL_SIZED_BUFFER) + .order(ByteOrder.nativeOrder()); + int dir = skipEmpty(0); + int quad = 0; + + private int skipEmpty(int d) + { + while (d < 6 && transparentQuads[d].isEmpty()) + d++; + return d; + } + + @Override + public boolean hasNext() + { + return dir < 6; + } + + @Override + public ByteBuffer next() + { + if (dir >= 6) + { + return null; + } + bb.clear(); + bb.limit(RenderBuffer.FULL_SIZED_BUFFER); + while (bb.hasRemaining() && dir < 6) + { + writeData(); + } + bb.limit(bb.position()); + bb.rewind(); + return bb; + } + + private void writeData() + { + int i = quad; + for (; i < transparentQuads[dir].size(); i++) + { + if (!bb.hasRemaining()) + { + break; + } + putQuad(bb, transparentQuads[dir].get(i)); + } + + if (i >= transparentQuads[dir].size()) { quad = 0; dir++; @@ -366,14 +442,13 @@ public class LodQuadBuilder } }; } - public interface BufferFiller { /** If true: more data needs to be filled */ boolean fill(GLVertexBuffer vbo); } - public BufferFiller makeBufferFiller(EGpuUploadMethod method) + public BufferFiller makeOpaqueBufferFiller(EGpuUploadMethod method) { return new BufferFiller() { @@ -414,10 +489,10 @@ public class LodQuadBuilder private int _countRemainingQuads() { - int a = quads[dir].size() - quad; - for (int i = dir + 1; i < quads.length; i++) + int a = opaqueQuads[dir].size() - quad; + for (int i = dir + 1; i < opaqueQuads.length; i++) { - a += quads[i].size(); + a += opaqueQuads[i].size(); } return a; } @@ -427,20 +502,20 @@ public class LodQuadBuilder int startQ = quad; int i = startQ; - for (i = startQ; i < quads[dir].size(); i++) + for (i = startQ; i < opaqueQuads[dir].size(); i++) { if (!bb.hasRemaining()) { break; } - putQuad(bb, quads[dir].get(i)); + putQuad(bb, opaqueQuads[dir].get(i)); } - if (i >= quads[dir].size()) + if (i >= opaqueQuads[dir].size()) { quad = 0; dir++; - while (dir < 6 && quads[dir].isEmpty()) + while (dir < 6 && opaqueQuads[dir].isEmpty()) dir++; } else @@ -450,21 +525,112 @@ public class LodQuadBuilder } }; } - - - - public int getCurrentQuadsCount() + + public BufferFiller makeTransparentBufferFiller(EGpuUploadMethod method) + { + return new BufferFiller() + { + int dir = 0; + int quad = 0; + + public boolean fill(GLVertexBuffer vbo) + { + if (dir >= 6) + { + vbo.setVertexCount(0); + return false; + } + + int numOfQuads = _countRemainingQuads(); + if (numOfQuads > RenderBuffer.MAX_QUADS_PER_BUFFER) + numOfQuads = RenderBuffer.MAX_QUADS_PER_BUFFER; + if (numOfQuads == 0) + { + vbo.setVertexCount(0); + return false; + } + ByteBuffer bb = vbo.mapBuffer(numOfQuads * RenderBuffer.QUADS_BYTE_SIZE, method, + RenderBuffer.FULL_SIZED_BUFFER); + if (bb == null) + throw new NullPointerException("mapBuffer returned null"); + bb.clear(); + bb.limit(numOfQuads * RenderBuffer.QUADS_BYTE_SIZE); + while (bb.hasRemaining() && dir < 6) + { + writeData(bb); + } + bb.rewind(); + vbo.unmapBuffer(); + vbo.setVertexCount(numOfQuads*4); + return dir < 6; + } + + private int _countRemainingQuads() + { + int a = transparentQuads[dir].size() - quad; + for (int i = dir + 1; i < transparentQuads.length; i++) + { + a += transparentQuads[i].size(); + } + return a; + } + + private void writeData(ByteBuffer bb) + { + int startQ = quad; + + int i = startQ; + for (i = startQ; i < transparentQuads[dir].size(); i++) + { + if (!bb.hasRemaining()) + { + break; + } + putQuad(bb, transparentQuads[dir].get(i)); + } + + if (i >= transparentQuads[dir].size()) + { + quad = 0; + dir++; + while (dir < 6 && transparentQuads[dir].isEmpty()) + dir++; + } + else + { + quad = i; + } + } + }; + } + + + + public int getCurrentOpaqueQuadsCount() { int i = 0; - for (ArrayList qs : quads) + for (ArrayList qs : opaqueQuads) + i += qs.size(); + return i; + } + public int getCurrentTransparentQuadsCount() + { + if (!doTransparency) return 0; + int i = 0; + for (ArrayList qs : transparentQuads) i += qs.size(); return i; } - /** Returns how many Buffers will be needed to render everything in this builder. */ - public int getCurrentNeededVertexBufferCount() + /** Returns how many Buffers will be needed to render opaque quads in this builder. */ + public int getCurrentNeededOpaqueVertexBufferCount() { - return MathUtil.ceilDiv(getCurrentQuadsCount(), RenderBuffer.MAX_QUADS_PER_BUFFER); + return MathUtil.ceilDiv(getCurrentOpaqueQuadsCount(), RenderBuffer.MAX_QUADS_PER_BUFFER); + } + /** Returns how many Buffers will be needed to render transparent quads in this builder. */ + public int getCurrentNeededTransparentVertexBufferCount() + { + if (!doTransparency) return 0; + return MathUtil.ceilDiv(getCurrentTransparentQuadsCount(), RenderBuffer.MAX_QUADS_PER_BUFFER); } - } diff --git a/core/src/main/java/com/seibel/lod/core/datatype/full/IdBiomeBlockStateMap.java b/core/src/main/java/com/seibel/lod/core/datatype/full/IdBiomeBlockStateMap.java index 19e6a72cd..03302a5f0 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/full/IdBiomeBlockStateMap.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/full/IdBiomeBlockStateMap.java @@ -9,6 +9,7 @@ import java.io.*; import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; // WARNING: This is not THREAD-SAFE! public class IdBiomeBlockStateMap { @@ -47,7 +48,7 @@ public class IdBiomeBlockStateMap { final ArrayList entries = new ArrayList<>(); - final HashMap idMap = new HashMap<>(); + final ConcurrentHashMap idMap = new ConcurrentHashMap<>(); // FIXME: Improve performance public Entry get(int id) { return entries.get(id); diff --git a/core/src/main/java/com/seibel/lod/core/generation/BatchGenerator.java b/core/src/main/java/com/seibel/lod/core/generation/BatchGenerator.java index 7c84f712a..6dd73e329 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/BatchGenerator.java +++ b/core/src/main/java/com/seibel/lod/core/generation/BatchGenerator.java @@ -46,9 +46,6 @@ public class BatchGenerator implements IChunkGenerator public ILevel targetLodLevel; public static final int generationGroupSize = 4; private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); - -// private int estimatedSampleNeeded = 128; -// private int estimatedPointsToQueue = 1; public BatchGenerator(ILevel targetLodLevel) { this.targetLodLevel = targetLodLevel; @@ -56,198 +53,6 @@ public class BatchGenerator implements IChunkGenerator LOGGER.info("Batch Chunk Generator initialized"); } -// @SuppressWarnings("unused") -// public void queueGenerationRequests() { -// EDistanceGenerationMode mode = Config.Client.WorldGenerator.distanceGenerationMode.get(); -// int newThreadCount = Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get()<1 ? 1 : (int) Math.ceil(Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get()); -// if (newThreadCount != previousThreadCount) { -// generationGroup.resizeThreadPool(newThreadCount); -// previousThreadCount = newThreadCount; -// } -// if (estimatedPointsToQueue < newThreadCount) -// estimatedPointsToQueue = newThreadCount; -// -// EGenerationPriority priority = Config.Client.WorldGenerator.generationPriority.get(); -// if (priority == EGenerationPriority.AUTO) -// priority = MC.hasSinglePlayerServer() ? EGenerationPriority.FAR_FIRST : EGenerationPriority.NEAR_FIRST; -// -// generationGroup.updateAllFutures(); -// if (!MC.hasSinglePlayerServer()) -// return; -// if (!LodUtil.checkRamUsage(0.1, 64)) return; -// -// int eventsCount = generationGroup.getEventCount(); -// // If we still all jobs running, return. -// if (eventsCount >= estimatedPointsToQueue) { -// estimatedPointsToQueue--; -// if (estimatedPointsToQueue < newThreadCount) -// estimatedPointsToQueue = newThreadCount; -// return; -// } -// -// final int targetToGenerate = estimatedPointsToQueue - eventsCount; -// int toGenerate = targetToGenerate; -// int positionGoneThough = 0; -// -// // round the player's block position down to the nearest chunk BlockPos -// int playerPosX = MC.getPlayerBlockPos().getX(); -// int playerPosZ = MC.getPlayerBlockPos().getZ(); -// double runTimeRatio = Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get()>1 ? 1.0 : Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get(); -// -// //FIXME -// PosToGenerateContainer posToGenerate = null;//lodDim.getPosToGenerate(estimatedSampleNeeded, playerPosX, playerPosZ, -// //priority, mode); -// -// if (eventsCount == 0 && posToGenerate.getNumberOfPos() >= estimatedSampleNeeded) { -// estimatedPointsToQueue++; -// if (estimatedPointsToQueue > newThreadCount * 10) -// estimatedPointsToQueue = newThreadCount * 10; -// } -// -// // ApiShared.LOGGER.info("PosToGenerate: {}", posToGenerate); -// -// // Find the max number of iterations we need to go though. -// // We are checking one FarPos, and one NearPos per iterations. -// // This ensures we aren't just always picking one or the other. -// Steps targetStep; -// switch (mode) { -// case NONE: -// targetStep = Steps.Empty; // NOTE: Only load in existing chunks. No new chunk generation -// break; -// case BIOME_ONLY: -// targetStep = Steps.Biomes; // NOTE: No block. Require fake height in LodBuilder -// break; -// case BIOME_ONLY_SIMULATE_HEIGHT: -// targetStep = Steps.Noise; // NOTE: Stone only. Require fake surface -// break; -// case SURFACE: -// targetStep = Steps.Surface; // Carvers or Surface??? -// break; -// case FEATURES: -// case FULL: -// targetStep = Steps.Features; -// break; -// default: -// assert false; -// return; -// } -// -// if (ENABLE_GENERATOR_STATS_LOGGING) -// LOGGER.info("WorldGen. Near:" + posToGenerate.getNumberOfNearPos() + " Far:" -// + posToGenerate.getNumberOfFarPos()); -// if (priority == EGenerationPriority.FAR_FIRST || priority == EGenerationPriority.BALANCED) { -// -// int nearCount = posToGenerate.getNumberOfNearPos(); -// int farCount = posToGenerate.getNumberOfFarPos(); -// if (ENABLE_GENERATOR_STATS_LOGGING) -// LOGGER.info("WorldGen. Near:" + nearCount + " Far:" + farCount); -// int maxIteration = Math.max(nearCount, farCount); -// for (int i = 0; i < maxIteration; i++) { -// -// // We have farPos to go though -// if (i < farCount && posToGenerate.getNthDetail(i, false) != 0) { -// positionGoneThough++; -// byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1); -// int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false)); -// int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false)); -// int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize; -// if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, false, runTimeRatio)) { -// toGenerate--; -// } -// } -// if (toGenerate <= 0) -// break; -// -// // We have nearPos to go though -// if (i < nearCount && posToGenerate.getNthDetail(i, true) != 0) { -// positionGoneThough++; -// byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1); -// int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true)); -// int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true)); -// int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize; -// if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, true, runTimeRatio)) { -// toGenerate--; -// } -// } -// -// if (toGenerate <= 0) -// break; -// } -// } else { -// int nearCount = posToGenerate.getNumberOfNearPos(); -// for (int i = 0; i < nearCount; i++) { -// -// // We have nearPos to go though -// if (posToGenerate.getNthDetail(i, true) != 0) { -// positionGoneThough++; -// byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1); -// int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true)); -// int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true)); -// int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize; -// if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, true, runTimeRatio)) { -// toGenerate--; -// } -// if (toGenerate <= 0) -// break; -// } -// } -// // Only do far gen if toGenerate is non 0 and that we have requested all samples -// // we can get. -// if (toGenerate > 0 && estimatedSampleNeeded > posToGenerate.getNumberOfPos()) { -// int farCount = posToGenerate.getNumberOfFarPos(); -// for (int i = 0; i < farCount; i++) { -// // We have farPos to go though -// if (posToGenerate.getNthDetail(i, false) != 0) { -// positionGoneThough++; -// byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1); -// int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false)); -// int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false)); -// int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize; -// if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, false, runTimeRatio)) { -// toGenerate--; -// } -// } -// if (toGenerate <= 0) -// break; -// } -// } -// } -// -// if (targetToGenerate != toGenerate && ENABLE_GENERATOR_STATS_LOGGING) { -// if (toGenerate <= 0) { -// LOGGER.info( -// "WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of " + estimatedSampleNeeded -// + " points, started all targeted " + targetToGenerate + " generations."); -// } else { -// LOGGER.info("WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of " -// + estimatedSampleNeeded + " points, started " + (targetToGenerate - toGenerate) -// + " out of targeted " + targetToGenerate + " generations."); -// } -// } -// -// if (toGenerate > 0 && estimatedSampleNeeded <= posToGenerate.getNumberOfPos()) { -// // We failed to generate enough points from the samples. -// // Let's increase the estimatedSampleNeeded. -// estimatedSampleNeeded *= 1.3; -// // Ensure wee don't go to basically infinity -// if (estimatedSampleNeeded > 32768) -// estimatedSampleNeeded = 32768; -// if (ENABLE_GENERATOR_STATS_LOGGING) -// LOGGER.info("WorldGenerator: Increasing estimatedSampleNeeded to " + estimatedSampleNeeded); -// -// } else if (toGenerate <= 0 && positionGoneThough * 1.5 < posToGenerate.getNumberOfPos()) { -// // We haven't gone through half of them, and it's already enough. -// // Let's shrink the estimatedSampleNeeded. -// estimatedSampleNeeded /= 1.2; -// // Ensure we don't go near zero. -// if (estimatedSampleNeeded < 4) -// estimatedSampleNeeded = 4; -// if (ENABLE_GENERATOR_STATS_LOGGING) -// LOGGER.info("WorldGenerator: Decreasing estimatedSampleNeeded to " + estimatedSampleNeeded); -// } -// -// } - public void stop(boolean blocking) { LOGGER.info("1.18 Experimental Chunk Generator shutting down..."); generationGroup.stop(blocking); diff --git a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java index 5eed8b4a9..641e624d1 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java @@ -82,7 +82,7 @@ public class LodRenderSection { } public boolean canRender() { - return isLoaded() && isRenderEnabled && lodRenderSource != null && lodRenderSource.isRenderReady(); + return isLoaded() && isRenderEnabled && lodRenderSource != null; } public boolean isLoaded() { diff --git a/core/src/main/java/com/seibel/lod/core/render/RenderBuffer.java b/core/src/main/java/com/seibel/lod/core/render/RenderBuffer.java index 4fe82e1d7..de0cb4a17 100644 --- a/core/src/main/java/com/seibel/lod/core/render/RenderBuffer.java +++ b/core/src/main/java/com/seibel/lod/core/render/RenderBuffer.java @@ -32,7 +32,8 @@ public abstract class RenderBuffer implements AutoCloseable // ========== Called by render thread ========== /* Called on... well... rendering. * Return false if nothing rendered. (Optional) */ - public abstract boolean render(LodRenderer renderContext); + public abstract boolean renderOpaque(LodRenderer renderContext); + public abstract boolean renderTransparent(LodRenderer renderContext); // ========== Called by any thread. (thread safe) ========== @@ -57,5 +58,4 @@ public abstract class RenderBuffer implements AutoCloseable public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE; - } diff --git a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java index 18d46bb84..8e03ab90e 100644 --- a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java @@ -1,63 +1,128 @@ package com.seibel.lod.core.render; import com.seibel.lod.core.datatype.LodRenderSource; +import com.seibel.lod.core.enums.ELodDirection; import com.seibel.lod.core.pos.Pos2D; import com.seibel.lod.core.pos.DhSectionPos; import com.seibel.lod.core.render.renderer.LodRenderer; import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.util.gridList.MovableGridRingList; +import com.seibel.lod.core.util.math.Mat4f; +import com.seibel.lod.core.util.math.Vec3f; +import com.seibel.lod.core.util.objects.SortedArraySet; +import java.util.Comparator; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.Vector; import java.util.concurrent.atomic.AtomicReference; public class RenderBufferHandler { public final LodQuadTree target; private final MovableGridRingList renderBufferNodes; + private static class LoadedRenderBuffer { + public final RenderBuffer buffer; + public final DhSectionPos pos; + LoadedRenderBuffer(RenderBuffer buffer, DhSectionPos pos) { + this.buffer = buffer; + this.pos = pos; + } + } + + // TODO: Make sorting go into the update loop instead of the render loop as it doesn't need to be done every frame + private SortedArraySet loadedNearToFarBuffers = null; + + // The followiing buildRenderList sorting method is based on the following reddit post: + // https://www.reddit.com/r/VoxelGameDev/comments/a0l8zc/correct_depthordering_for_translucent_discrete/ + public void buildRenderList(Vec3f lookForwardVector) { + ELodDirection[] axisDirections = new ELodDirection[3]; + // Do the axis that are longest first (i.e. the largest absolute value of the lookForwardVector) + // , with the sign being the opposite of the respective lookForwardVector component's sign + float absX = Math.abs(lookForwardVector.x); + float absY = Math.abs(lookForwardVector.y); + float absZ = Math.abs(lookForwardVector.z); + ELodDirection xDir = lookForwardVector.x < 0 ? ELodDirection.EAST : ELodDirection.WEST; + ELodDirection yDir = lookForwardVector.y < 0 ? ELodDirection.UP : ELodDirection.DOWN; + ELodDirection zDir = lookForwardVector.z < 0 ? ELodDirection.SOUTH : ELodDirection.NORTH; + if (absX >= absY && absX >= absZ) { + axisDirections[0] = xDir; + if (absY >= absZ) { + axisDirections[1] = yDir; + axisDirections[2] = zDir; + } else { + axisDirections[1] = zDir; + axisDirections[2] = yDir; + } + } else if (absY >= absX && absY >= absZ) { + axisDirections[0] = yDir; + if (absX >= absZ) { + axisDirections[1] = xDir; + axisDirections[2] = zDir; + } else { + axisDirections[1] = zDir; + axisDirections[2] = xDir; + } + } else { + axisDirections[0] = zDir; + if (absX >= absY) { + axisDirections[1] = xDir; + axisDirections[2] = yDir; + } else { + axisDirections[1] = yDir; + axisDirections[2] = xDir; + } + } + + // Now that we have the axis directions, we can sort the render list + Comparator sortFarToNear = (a, b) -> { + Pos2D aPos = a.pos.getCenter().getCenter().toPos2D(); + Pos2D bPos = b.pos.getCenter().getCenter().toPos2D(); + for (ELodDirection axisDirection : axisDirections) { + if (axisDirection.getAxis().isVertical()) continue; // We works on the horizontal plane only for section sorting + int abDiff; + if (axisDirection.getAxis().equals(ELodDirection.Axis.X)) { + abDiff = aPos.x - bPos.x; + } else { + abDiff = aPos.y - bPos.y; + } + if (abDiff == 0) continue; + if (axisDirection.getAxisDirection().equals(ELodDirection.AxisDirection.NEGATIVE)) { + abDiff = -abDiff; // Reverse the sign + } + return abDiff; + } + return a.pos.sectionDetail - b.pos.sectionDetail; // If all else fails, sort by detail + }; + Comparator sortNearToFar = (a, b) -> -sortFarToNear.compare(a, b); + // Build the sorted list + loadedNearToFarBuffers = new SortedArraySet<>(sortNearToFar); + // Add all the loaded buffers to the sorted list + renderBufferNodes.forEach((r) -> {if (r!=null) r.collect(loadedNearToFarBuffers);}); + } + + class RenderBufferNode implements AutoCloseable { public final DhSectionPos pos; public volatile RenderBufferNode[] children = null; + //FIXME: The multiple Atomics will cause race conditions between them! - public final AtomicReference renderBufferSlotOpaque = new AtomicReference<>(); - public final AtomicReference renderBufferSlotTransparent = new AtomicReference<>(); + public final AtomicReference renderBufferSlot = new AtomicReference<>(); public RenderBufferNode(DhSectionPos pos) { this.pos = pos; } - /** - * This will render all opaque lods - * @param renderContext - */ - public void renderOpaque(LodRenderer renderContext) { + public void collect(SortedArraySet sortedSet) { RenderBuffer buff; - - buff = renderBufferSlotOpaque.get(); + buff = renderBufferSlot.get(); if (buff != null) { - buff.render(renderContext); + sortedSet.add(new LoadedRenderBuffer(buff, pos)); } else { RenderBufferNode[] childs = children; if (childs != null) { for (RenderBufferNode child : childs) { - child.renderOpaque(renderContext); - } - } - } - } - - /** - * This will render all transparent lods - * @param renderContext - */ - public void renderTransparent(LodRenderer renderContext) { - RenderBuffer buff; - buff = renderBufferSlotTransparent.get(); - if (buff != null) { - buff.render(renderContext); - } else { - RenderBufferNode[] childs = children; - if (childs != null) { - for (RenderBufferNode child : childs) { - child.renderTransparent(renderContext); + child.collect(sortedSet); } } } @@ -77,17 +142,13 @@ public class RenderBufferHandler { boolean shouldRender = section.canRender(); if (!shouldRender) { //TODO: Does this really need to force the old buffer to not be rendered? - RenderBuffer buff = renderBufferSlotOpaque.getAndSet(null); - if (buff != null) { - buff.close(); - } - buff = renderBufferSlotTransparent.getAndSet(null); + RenderBuffer buff = renderBufferSlot.getAndSet(null); if (buff != null) { buff.close(); } } else { LodUtil.assertTrue(container != null); // section.isLoaded() should have ensured this - container.trySwapRenderBuffer(target, renderBufferSlotOpaque, renderBufferSlotTransparent); + container.trySwapRenderBuffer(target, renderBufferSlot); } // Update children's render buffer state @@ -109,7 +170,7 @@ public class RenderBufferHandler { } else { if (children != null) { //FIXME: Concurrency issue here: If render thread is concurrently using the child's buffer, - // and this thread got priority to close the buffer, it causes a bug wher the render thread + // and this thread got priority to close the buffer, it causes a bug where the render thread // will be using a closed buffer!!!! RenderBufferNode[] childs = children; children = null; @@ -128,11 +189,7 @@ public class RenderBufferHandler { } } RenderBuffer buff; - buff = renderBufferSlotOpaque.getAndSet(null); - if (buff != null) { - buff.close(); - } - buff = renderBufferSlotTransparent.getAndSet(null); + buff = renderBufferSlot.getAndSet(null); if (buff != null) { buff.close(); } @@ -146,14 +203,19 @@ public class RenderBufferHandler { renderBufferNodes = new MovableGridRingList<>(referenceList.getHalfSize(), center); } - public void render(LodRenderer renderContext) { - //TODO: This might get locked by update() causing move() call. Is there a way to avoid this? - // Maybe dupe the base list and use atomic swap on render? Or is this not worth it? + //TODO: This might get locked by update() causing move() call. Is there a way to avoid this? + // Maybe dupe the base list and use atomic swap on render? Or is this not worth it? + public void prepare(LodRenderer renderContext) { + buildRenderList(renderContext.getLookVector()); + } + + public void renderOpaque(LodRenderer renderContext) { //TODO: Directional culling - //TODO: Ordered by distance - renderBufferNodes.forEachOrdered(n -> n.renderOpaque(renderContext)); + loadedNearToFarBuffers.forEach(b -> b.buffer.renderOpaque(renderContext)); + } + public void renderTransparent(LodRenderer renderContext) { if(LodRenderer.transparencyEnabled) - renderBufferNodes.forEachOrdered(n -> n.renderTransparent(renderContext)); + loadedNearToFarBuffers.forEach(b -> b.buffer.renderTransparent(renderContext)); } public void update() { @@ -186,31 +248,6 @@ public class RenderBufferHandler { // Update node node.update(); }); - - - /**TODO improve the ordering*/ - /* DHBlockPos playerPos = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).getPlayerBlockPos(); - int x = playerPos.x; - int z = playerPos.z; - Comparator byDistance = new Comparator() { - @Override - public int compare(RenderBufferNode o1, RenderBufferNode o2) { - if ((o1 == null) && (o2 == null)) { - return 0; - } else if (o1 == null) { - return 1; - } else if (o2 == null) { - return -1; - } - int x1 = o1.pos.sectionX; - int z1 = o1.pos.sectionZ; - int x2 = o2.pos.sectionX; - int z2 = o2.pos.sectionZ; - - return Integer.compare((x1 - x) ^ 2 + (z1 - z) ^ 2, (x2 - x) ^ 2 + (z2 - z) ^ 2); - } - }; - renderBufferNodes.sort(byDistance);*/ } public void close() { diff --git a/core/src/main/java/com/seibel/lod/core/render/glObject/GLState.java b/core/src/main/java/com/seibel/lod/core/render/glObject/GLState.java index 757fe613d..5d11f96c6 100644 --- a/core/src/main/java/com/seibel/lod/core/render/glObject/GLState.java +++ b/core/src/main/java/com/seibel/lod/core/render/glObject/GLState.java @@ -36,6 +36,7 @@ public class GLState { public int blendSrc; public int blendDst; public boolean depth; + public boolean depthWrite; public int depthFunc; public boolean stencil; public int stencilFunc; @@ -61,6 +62,7 @@ public class GLState { blendSrc = GL32.glGetInteger(GL32.GL_BLEND_SRC); blendDst = GL32.glGetInteger(GL32.GL_BLEND_DST); depth = GL32.glIsEnabled(GL32.GL_DEPTH_TEST); + depthWrite = GL32.glGetInteger(GL32.GL_DEPTH_WRITEMASK) == GL32.GL_TRUE; depthFunc = GL32.glGetInteger(GL32.GL_DEPTH_FUNC); stencil = GL32.glIsEnabled(GL32.GL_STENCIL_TEST); stencilFunc = GL32.glGetInteger(GL32.GL_STENCIL_FUNC); @@ -100,7 +102,9 @@ public class GLState { GL32.glBindVertexArray(vao); GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo); GL32.glBindBuffer(GL32.GL_ELEMENT_ARRAY_BUFFER, ebo); + GL32.glUseProgram(prog); + GL32.glDepthMask(depthWrite); GL32.glBlendFunc(blendSrc, blendDst); if (depth) { GL32.glEnable(GL32.GL_DEPTH_TEST); @@ -115,7 +119,6 @@ public class GLState { } GL32.glStencilFunc(stencilFunc, stencilRef, stencilMask); GL32.glViewport(view[0], view[1], view[2], view[3]); - GL32.glUseProgram(prog); if (cull) { GL32.glEnable(GL32.GL_CULL_FACE); } else { diff --git a/core/src/main/java/com/seibel/lod/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/lod/core/render/renderer/LodRenderer.java index 45a5675bf..4d3322faa 100644 --- a/core/src/main/java/com/seibel/lod/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/lod/core/render/renderer/LodRenderer.java @@ -85,6 +85,10 @@ public class LodRenderer GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount()/4)*6, quadIBO.getType(), 0); } + public Vec3f getLookVector() { + return MC_RENDER.getLookAtVector(); + } + public static class LagSpikeCatcher { long timer = System.nanoTime(); @@ -130,7 +134,6 @@ public class LodRenderer EVENT_LOGGER.error("drawLODs() called after close()!"); return; } - // get MC's shader program // Save all MC render state @@ -179,13 +182,9 @@ public class LodRenderer transparencyEnabled = Config.Client.Graphics.Quality.transparency.get().tranparencyEnabled; fakeOceanFloor = Config.Client.Graphics.Quality.transparency.get().fakeTransparencyEnabled; - if(transparencyEnabled) { - GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA); - GL32.glEnable(GL32.GL_BLEND); - }else{ - GL32.glDisable(GL32.GL_BLEND); - } + GL32.glDisable(GL32.GL_BLEND); // We render opaque first, then transparent + GL32.glDepthMask(true); GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT); /*---------Bind required objects--------*/ @@ -223,6 +222,8 @@ public class LodRenderer //GL32.glEnable( GL32.GL_POLYGON_OFFSET_FILL ); //GL32.glPolygonOffset( 1f, 1f ); + bufferHandler.prepare(this); + //===========// // rendering // //===========// @@ -233,11 +234,20 @@ public class LodRenderer Vec3d cameraPos = MC_RENDER.getCameraExactPosition(); DhBlockPos cameraBlockPos = MC_RENDER.getCameraBlockPosition(); Vec3f cameraDir = MC_RENDER.getLookAtVector(); - int drawCount = 0; //TODO: Directional culling - bufferHandler.render(this); + bufferHandler.renderOpaque(this); + //======================// + // render transparency // + //======================// + if (LodRenderer.transparencyEnabled) { + GL32.glEnable(GL32.GL_BLEND); + GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA); + GL32.glDepthMask(false); // This so that even on incorrect sorting of transparent blocks, it still mostly looks correct + bufferHandler.renderTransparent(this); + GL32.glDepthMask(true); // Apparently the depth mask state is stored in the FBO, so glState fails to restore it... + } //if (drawCall==0) // tickLogger.info("DrawCall Count: {}", drawCount); diff --git a/core/src/main/java/com/seibel/lod/core/util/objects/SortedArraySet.java b/core/src/main/java/com/seibel/lod/core/util/objects/SortedArraySet.java new file mode 100644 index 000000000..8c0fe6ae1 --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/util/objects/SortedArraySet.java @@ -0,0 +1,155 @@ +package com.seibel.lod.core.util.objects; + +import java.util.*; + +public class SortedArraySet implements SortedSet { + private final ArrayList list; + private final Comparator comparator; + + public SortedArraySet() { + list = new ArrayList<>(); + comparator = null; + } + + public SortedArraySet(Comparator comparator) { + list = new ArrayList<>(); + this.comparator = comparator; + } + + public SortedArraySet(Collection collection) { + list = new ArrayList<>(collection); + comparator = null; + list.sort(null); + } + + public SortedArraySet(Collection collection, Comparator comparator) { + list = new ArrayList<>(collection); + this.comparator = comparator; + list.sort(comparator); + } + + @Override + public Comparator comparator() { + return comparator; + } + + @Override + public E first() { + return list.get(0); + } + + @Override + public E last() { + return list.get(list.size() - 1); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return list.contains(o); + } + + @Override + public Iterator iterator() { + return list.iterator(); + } + + public ListIterator listIterator() { + return list.listIterator(); + } + + + @Override + public Object[] toArray() { + return list.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return list.toArray(a); + } + + @Override + public boolean add(E e) { + int index = Collections.binarySearch(list, e, comparator); + if (index < 0) { + index = ~index; + } + list.add(index, e); + return true; + } + + @Override + public boolean remove(Object o) { + return list.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return list.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + boolean changed = false; + for (E e : c) { + changed |= add(e); + } + return changed; + } + + @Override + public boolean retainAll(Collection c) { + return list.retainAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return list.removeAll(c); + } + + @Override + public void clear() { + list.clear(); + } + + @Override + public String toString() { + return "SortedArraySet{" + + "list=" + list + + ", comparator=" + comparator + + '}'; + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + int fromIndex = Collections.binarySearch(list, fromElement, comparator); + if (fromIndex < 0) fromIndex = ~fromIndex; + int toIndex = Collections.binarySearch(list, toElement, comparator); + if (toIndex < 0) toIndex = ~toIndex; + return new SortedArraySet<>(list.subList(fromIndex, toIndex), comparator); + } + + @Override + public SortedSet headSet(E toElement) { + int toIndex = Collections.binarySearch(list, toElement, comparator); + if (toIndex < 0) toIndex = ~toIndex; + return new SortedArraySet<>(list.subList(0, toIndex), comparator); + } + + @Override + public SortedSet tailSet(E fromElement) { + int fromIndex = Collections.binarySearch(list, fromElement, comparator); + if (fromIndex < 0) fromIndex = ~fromIndex; + return new SortedArraySet<>(list.subList(fromIndex, list.size()), comparator); + } +}