From eb7172aaba093957a3fff5a577f0f3708019bc9b Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Fri, 21 Mar 2025 22:47:16 +1000 Subject: [PATCH] Stuff kinda works --- .../voxy/client/VoxyClientInstance.java | 2 +- .../client/core/rendering/RenderService.java | 3 +- .../core/rendering/SectionUpdateRouter.java | 7 + .../building/RenderDataFactory45.java | 756 ++++++++++++++++++ .../building/RenderGenerationService.java | 24 +- .../rendering/hierachical/NodeCleaner.java | 81 +- .../rendering/hierachical/NodeManager.java | 60 +- .../core/rendering/hierachical/NodeStore.java | 7 + .../hierachical/TestNodeManager.java | 28 +- .../section/BasicSectionGeometryManager.java | 2 +- .../voxy/common/util/HierarchicalBitSet.java | 34 +- .../cortex/voxy/common/util/MemoryBuffer.java | 2 +- .../voxelization/WorldConversionFactory.java | 116 +-- .../common/world/ActiveSectionTracker.java | 38 +- .../cortex/voxy/common/world/WorldEngine.java | 6 +- .../voxy/common/world/WorldSection.java | 2 +- .../cortex/voxy/commonImpl/VoxyInstance.java | 2 - .../cleaner/result_transformer.comp | 10 +- .../hierarchical/cleaner/sort_visibility.comp | 31 +- .../voxy/shaders/lod/hierarchical/node.glsl | 6 +- 20 files changed, 1092 insertions(+), 125 deletions(-) create mode 100644 src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory45.java diff --git a/src/main/java/me/cortex/voxy/client/VoxyClientInstance.java b/src/main/java/me/cortex/voxy/client/VoxyClientInstance.java index b528dd26..cbf60dcf 100644 --- a/src/main/java/me/cortex/voxy/client/VoxyClientInstance.java +++ b/src/main/java/me/cortex/voxy/client/VoxyClientInstance.java @@ -13,7 +13,7 @@ public class VoxyClientInstance extends VoxyInstance { public WorldImportWrapper importWrapper; public VoxyClientInstance() { - super(12); + super(14); } @Override diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java b/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java index 641dcd89..8493a243 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java @@ -191,13 +191,14 @@ public class RenderService, J extends Vi if (true /* firstInvocationThisFrame */) { DownloadStream.INSTANCE.tick(); - this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here?? this.sectionUpdateQueue.consume(); this.geometryUpdateQueue.consume(); if (this.nodeManager.writeChanges(this.traversal.getNodeBuffer())) {//TODO: maybe move the node buffer out of the traversal class UploadStream.INSTANCE.commit(); } + this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here?? + //this needs to go after, due to geometry updates committed by the nodeManager this.sectionRenderer.getGeometryManager().tick(); } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/SectionUpdateRouter.java b/src/main/java/me/cortex/voxy/client/core/rendering/SectionUpdateRouter.java index a613d7ec..6e2c4941 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/SectionUpdateRouter.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/SectionUpdateRouter.java @@ -76,6 +76,13 @@ public class SectionUpdateRouter implements ISectionWatcher { } } + public int get(long position) { + var set = this.slices[getSliceIndex(position)]; + synchronized (set) { + return set.getOrDefault(position, (byte) 0); + } + } + public void forwardEvent(WorldSection section, int type) { final long position = section.key; var set = this.slices[getSliceIndex(position)]; diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory45.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory45.java new file mode 100644 index 00000000..81d6d0df --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory45.java @@ -0,0 +1,756 @@ +package me.cortex.voxy.client.core.rendering.building; + +import me.cortex.voxy.client.core.gl.Capabilities; +import me.cortex.voxy.client.core.model.ModelFactory; +import me.cortex.voxy.client.core.model.ModelQueries; +import me.cortex.voxy.client.core.util.Mesher2D; +import me.cortex.voxy.client.core.util.ScanMesher2D; +import me.cortex.voxy.common.util.MemoryBuffer; +import me.cortex.voxy.common.util.UnsafeUtil; +import me.cortex.voxy.common.world.WorldEngine; +import me.cortex.voxy.common.world.WorldSection; +import me.cortex.voxy.common.world.other.Mapper; +import me.cortex.voxy.commonImpl.VoxyCommon; +import org.lwjgl.system.MemoryUtil; + +import java.util.Arrays; +import java.util.Map; + + +public class RenderDataFactory45 { + private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing"); + + private final WorldEngine world; + private final ModelFactory modelMan; + + //private final long[] sectionData = new long[32*32*32*2]; + private final long[] sectionData = new long[32*32*32*2]; + private final long[] neighboringFaces = new long[32*32*6]; + //private final int[] neighboringOpaqueMasks = new int[32*6]; + + private final int[] opaqueMasks = new int[32*32]; + private final int[] nonOpaqueMasks = new int[32*32]; + + + //TODO: emit directly to memory buffer instead of long arrays + + //Each axis gets a max quad count of 2^16 (65536 quads) since that is the max the basic geometry manager can handle + private final MemoryBuffer directionalQuadBuffer = new MemoryBuffer(6*(8*(1<<16))); + private final long directionalQuadBufferPtr = this.directionalQuadBuffer.address; + private final int[] directionalQuadCounters = new int[6];//Maybe change to short? (or long /w raw pointers) + + + private int minX; + private int minY; + private int minZ; + private int maxX; + private int maxY; + private int maxZ; + + private int quadCount = 0; + + + //Wont work for double sided quads + private final class Mesher extends ScanMesher2D { + public int auxiliaryPosition = 0; + public boolean doAuxiliaryFaceOffset = true; + public int axis = 0; + + //Note x, z are in top right + @Override + protected void emitQuad(int x, int z, int length, int width, long data) { + RenderDataFactory45.this.quadCount++; + + if (VERIFY_MESHING) { + if (length<1||length>16) { + throw new IllegalStateException("length out of bounds: " + length); + } + if (width<1||width>16) { + throw new IllegalStateException("width out of bounds: " + width); + } + if (x<0||x>31) { + throw new IllegalStateException("x out of bounds: " + x); + } + if (z<0||z>31) { + throw new IllegalStateException("z out of bounds: " + z); + } + if (x-(length-1)<0 || z-(width-1)<0) { + throw new IllegalStateException("dim out of bounds: " + (x-(length-1))+", " + (z-(width-1))); + } + + } + x -= length-1; + z -= width-1; + + if (this.axis == 2) { + //Need to swizzle the data if on x axis + int tmp = x; + x = z; + z = tmp; + + tmp = length; + length = width; + width = tmp; + } + + //Lower 26 bits can be auxiliary data since that is where quad position information goes; + int auxData = (int) (data&((1<<26)-1)); + int faceSide = auxData&1; + data &= ~(data&((1<<26)-1)); + + final int axis = this.axis; + int face = (axis<<1)|faceSide; + int encodedPosition = face; + + //Shift up if is negative axis + int auxPos = this.auxiliaryPosition; + auxPos += this.doAuxiliaryFaceOffset?(1-faceSide):0;//Shift + + if (VERIFY_MESHING) { + if (auxPos > 31) { + throw new IllegalStateException("OOB face: " + auxPos + ", " + faceSide); + } + } + + encodedPosition |= ((width - 1) << 7) | ((length - 1) << 3); + + encodedPosition |= x << (axis==2?16:21); + encodedPosition |= z << (axis==1?16:11); + encodedPosition |= auxPos << (axis==0?16:(axis==1?11:21)); + + long quad = data | encodedPosition; + + + MemoryUtil.memPutLong(RenderDataFactory45.this.directionalQuadBufferPtr + (RenderDataFactory45.this.directionalQuadCounters[face]++)*8L + face*8L*(1<<16), quad); + } + } + + + private final Mesher blockMesher = new Mesher(); + + public RenderDataFactory45(WorldEngine world, ModelFactory modelManager, boolean emitMeshlets) { + this.world = world; + this.modelMan = modelManager; + } + + + + private int prepareSectionData() { + final var sectionData = this.sectionData; + int opaque = 0; + int notEmpty = 0; + + int neighborAcquireMsk = 0;//-+x, -+y, -+Z + for (int i = 0; i < 32*32*32;) { + long block = sectionData[i + 32 * 32 * 32];//Get the block mapping + + int modelId = this.modelMan.getModelId(Mapper.getBlockId(block)); + long modelMetadata = this.modelMan.getModelMetadataFromClientId(modelId); + + sectionData[i * 2] = modelId | ((long) (Mapper.getLightId(block)) << 16) | (ModelQueries.isBiomeColoured(modelMetadata)?(((long) (Mapper.getBiomeId(block))) << 24):0); + sectionData[i * 2 + 1] = modelMetadata; + + boolean isFullyOpaque = ModelQueries.isFullyOpaque(modelMetadata); + opaque |= (isFullyOpaque ? 1:0) << (i & 31); + notEmpty |= (modelId!=0 ? 1:0) << (i & 31); + + //TODO: here also do bitmask of what neighboring sections are needed to compute (may be getting rid of this in future) + + //Do increment here + i++; + + if ((i & 31) == 0) { + this.opaqueMasks[(i >> 5) - 1] = opaque; + this.nonOpaqueMasks[(i >> 5) - 1] = notEmpty^opaque; + + int neighborMsk = 0; + //-+x + neighborMsk |= notEmpty&1;//-x + neighborMsk |= (notEmpty>>>30)&0b10;//+x + + //notEmpty = (notEmpty != 0)?1:0; + neighborMsk |= notEmpty!=0&&((i-1)>>10)==0?0b100:0;//-y + neighborMsk |= notEmpty!=0&&((i-1)>>10)==31?0b1000:0;//+y + neighborMsk |= notEmpty!=0&&(((i-1)>>5)&0x1F)==0?0b10000:0;//-z + neighborMsk |= notEmpty!=0&&(((i-1)>>5)&0x1F)==31?0b100000:0;//+z + + neighborAcquireMsk |= neighborMsk; + + + opaque = 0; + notEmpty = 0; + } + } + return neighborAcquireMsk; + } + + private void acquireNeighborData(WorldSection section, int msk) { + //TODO: fixme!!! its probably more efficent to just access the raw section array on demand instead of copying it + if ((msk&1)!=0) {//-x + var sec = this.world.acquire(section.lvl, section.x - 1, section.y, section.z); + //Note this is not thread safe! (but eh, fk it) + var raw = sec._unsafeGetRawDataArray(); + for (int i = 0; i < 32*32; i++) { + this.neighboringFaces[i] = raw[(i<<5)+31];//pull the +x faces from the section + } + sec.release(); + } + if ((msk&2)!=0) {//+x + var sec = this.world.acquire(section.lvl, section.x + 1, section.y, section.z); + //Note this is not thread safe! (but eh, fk it) + var raw = sec._unsafeGetRawDataArray(); + for (int i = 0; i < 32*32; i++) { + this.neighboringFaces[i+32*32] = raw[(i<<5)];//pull the -x faces from the section + } + sec.release(); + } + + if ((msk&4)!=0) {//-y + var sec = this.world.acquire(section.lvl, section.x, section.y - 1, section.z); + //Note this is not thread safe! (but eh, fk it) + var raw = sec._unsafeGetRawDataArray(); + for (int i = 0; i < 32*32; i++) { + this.neighboringFaces[i+32*32*2] = raw[i|(0x1F<<10)];//pull the +y faces from the section + } + sec.release(); + } + if ((msk&8)!=0) {//+y + var sec = this.world.acquire(section.lvl, section.x, section.y + 1, section.z); + //Note this is not thread safe! (but eh, fk it) + var raw = sec._unsafeGetRawDataArray(); + for (int i = 0; i < 32*32; i++) { + this.neighboringFaces[i+32*32*3] = raw[i];//pull the -y faces from the section + } + sec.release(); + } + + if ((msk&16)!=0) {//-z + var sec = this.world.acquire(section.lvl, section.x, section.y, section.z - 1); + //Note this is not thread safe! (but eh, fk it) + var raw = sec._unsafeGetRawDataArray(); + for (int i = 0; i < 32*32; i++) { + this.neighboringFaces[i+32*32*4] = raw[Integer.expand(i,0b11111_00000_11111)|(0x1F<<5)];//pull the +z faces from the section + } + sec.release(); + } + if ((msk&32)!=0) {//+z + var sec = this.world.acquire(section.lvl, section.x, section.y, section.z + 1); + //Note this is not thread safe! (but eh, fk it) + var raw = sec._unsafeGetRawDataArray(); + for (int i = 0; i < 32*32; i++) { + this.neighboringFaces[i+32*32*5] = raw[Integer.expand(i,0b11111_00000_11111)];//pull the -z faces from the section + } + sec.release(); + } + } + + private void generateYZFaces() { + for (int axis = 0; axis < 2; axis++) {//Y then Z + this.blockMesher.axis = axis; + if (true) { + for (int layer = 0; layer < 31; layer++) { + this.blockMesher.auxiliaryPosition = layer; + for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections + int pidx = axis==0 ?(layer*32+other):(other*32+layer); + int skipAmount = axis==0?32:1; + + int current = this.opaqueMasks[pidx]; + int next = this.opaqueMasks[pidx + skipAmount]; + + int msk = current ^ next; + if (msk == 0) { + this.blockMesher.skip(32); + continue; + } + + //TODO: For boarder sections, should NOT EMIT neighbors faces + int faceForwardMsk = msk & current; + int cIdx = -1; + while (msk != 0) { + int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index + int delta = index - cIdx - 1; + cIdx = index; //index--; + if (delta != 0) this.blockMesher.skip(delta); + msk &= ~Integer.lowestOneBit(msk); + + int facingForward = ((faceForwardMsk >> index) & 1); + + { + int idx = index + (pidx*32); + + //TODO: swap this out for something not getting the next entry + long A = this.sectionData[idx * 2]; + long B = this.sectionData[(idx + skipAmount * 32) * 2]; + + //Flip data with respect to facing direction + long selfModel = facingForward == 1 ? A : B; + long nextModel = facingForward == 1 ? B : A; + + //Example thing thats just wrong but as example + this.blockMesher.putNext(((long) facingForward) |//Facing + ((selfModel & 0xFFFF) << 26) | //ModelId + (((nextModel>>16)&0xFF) << 55) |//Lighting + ((selfModel&(0x1FFL<<24))<<(46-24))//biomeId + ); + } + } + this.blockMesher.endRow(); + } + this.blockMesher.finish(); + } + } + + if (true) { + this.blockMesher.doAuxiliaryFaceOffset = false; + //Hacky generate section side faces (without check neighbor section) + for (int side = 0; side < 2; side++) {//-, + + int layer = side == 0 ? 0 : 31; + this.blockMesher.auxiliaryPosition = layer; + for (int other = 0; other < 32; other++) { + int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer); + int msk = this.opaqueMasks[pidx]; + if (msk == 0) { + this.blockMesher.skip(32); + continue; + } + + int cIdx = -1; + while (msk != 0) { + int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index + int delta = index - cIdx - 1; + cIdx = index; //index--; + if (delta != 0) this.blockMesher.skip(delta); + msk &= ~Integer.lowestOneBit(msk); + + { + int idx = index + (pidx * 32); + + + int neighborIdx = ((axis+1)*32*32 * 2)+(side)*32*32; + long neighborId = this.neighboringFaces[neighborIdx + (other*32) + index]; + + if (Mapper.getBlockId(neighborId) != 0) {//Not air + long meta = this.modelMan.getModelMetadataFromClientId(this.modelMan.getModelId(Mapper.getBlockId(neighborId))); + if (ModelQueries.isFullyOpaque(meta)) {//Dont mesh this face + this.blockMesher.putNext(0); + continue; + } + } + + + //TODO: swap this out for something not getting the next entry + long A = this.sectionData[idx * 2]; + + //Example thing thats just wrong but as example + this.blockMesher.putNext((side == 0 ? 0L : 1L) | + ((A & 0xFFFFL) << 26) | + (((long)Mapper.getLightId(neighborId)) << 55) | + ((A&(0x1FFL<<24))<<(46-24)) + ); + } + } + this.blockMesher.endRow(); + } + + this.blockMesher.finish(); + } + this.blockMesher.doAuxiliaryFaceOffset = true; + } + } + } + + + private final Mesher[] xAxisMeshers = new Mesher[32]; + { + for (int i = 0; i < 32; i++) { + var mesher = new Mesher(); + mesher.auxiliaryPosition = i; + mesher.axis = 2;//X axis + this.xAxisMeshers[i] = mesher; + } + } + + private static final long X_I_MSK = 0x4210842108421L; + private void generateXFaces() { + for (int y = 0; y < 32; y++) { + long sumA = 0; + long sumB = 0; + long sumC = 0; + int partialHasCount = -1; + int msk = 0; + for (int z = 0; z < 32; z++) { + int lMsk = this.opaqueMasks[y*32+z]; + msk = (lMsk^(lMsk>>>1)); + msk &= -1>>>1;//Remove top bit as we dont actually know/have the data for that slice + + //Always increment cause can do funny trick (i.e. -1 on skip amount) + sumA += X_I_MSK; + sumB += X_I_MSK; + sumC += X_I_MSK; + + partialHasCount &= ~msk; + + if (z == 30 && partialHasCount != 0) {//Hackfix for incremental count overflow issue + int cmsk = partialHasCount; + while (cmsk!=0) { + int index = Integer.numberOfTrailingZeros(cmsk); + cmsk &= ~Integer.lowestOneBit(cmsk); + //TODO: fixme! check this is correct or if should be 30 + this.xAxisMeshers[index].skip(31); + } + //Clear the sum + sumA &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount), X_I_MSK)*0x1F); + sumB &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>11, X_I_MSK)*0x1F); + sumC &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>22, X_I_MSK)*0x1F); + } + + if (msk == 0) { + continue; + } + + /* + {//Dont need this as can just increment everything then -1 in mask + //Compute and increment skips for indexes + long imsk = Integer.toUnsignedLong(~msk);// we only want to increment where there isnt a face + sumA += Long.expand(imsk, X_I_MSK); + sumB += Long.expand(imsk>>11, X_I_MSK); + sumC += Long.expand(imsk>>22, X_I_MSK); + }*/ + + int faceForwardMsk = msk&lMsk; + int iter = msk; + while (iter!=0) { + int index = Integer.numberOfTrailingZeros(iter); + iter &= ~Integer.lowestOneBit(iter); + + var mesher = this.xAxisMeshers[index]; + + int skipCount;//Compute the skip count + {//TODO: Branch-less + //Compute skip and clear + if (index<11) { + skipCount = (int) (sumA>>(index*5)); + sumA &= ~(0x1FL<<(index*5)); + } else if (index<22) { + skipCount = (int) (sumB>>((index-11)*5)); + sumB &= ~(0x1FL<<((index-11)*5)); + } else { + skipCount = (int) (sumC>>((index-22)*5)); + sumC &= ~(0x1FL<<((index-22)*5)); + } + skipCount &= 0x1F; + skipCount--; + } + + if (skipCount != 0) { + mesher.skip(skipCount); + } + + int facingForward = ((faceForwardMsk>>index)&1); + { + int idx = index + (z * 32) + (y * 32 * 32); + //TODO: swap this out for something not getting the next entry + long A = this.sectionData[idx * 2]; + long B = this.sectionData[(idx + 1) * 2]; + + //Flip data with respect to facing direction + long selfModel = facingForward==1?A:B; + long nextModel = facingForward==1?B:A; + + //Example thing thats just wrong but as example + mesher.putNext(((long) facingForward) |//Facing + ((selfModel & 0xFFFF) << 26) | //ModelId + (((nextModel>>16)&0xFF) << 55) |//Lighting + ((selfModel&(0x1FFL<<24))<<(46-24))//biomeId + ); + } + } + } + + //Need to skip the remaining entries in the skip array + { + msk = ~msk;//Invert the mask as we only need to set stuff that isnt 0 + while (msk!=0) { + int index = Integer.numberOfTrailingZeros(msk); + msk &= ~Integer.lowestOneBit(msk); + int skipCount; + if (index < 11) { + skipCount = (int) (sumA>>(index*5)); + } else if (index<22) { + skipCount = (int) (sumB>>((index-11)*5)); + } else { + skipCount = (int) (sumC>>((index-22)*5)); + } + skipCount &= 0x1F; + + if (skipCount != 0) { + this.xAxisMeshers[index].skip(skipCount); + } + } + } + } + + //Generate the side faces, hackily, using 0 and 1 mesher + if (true) { + var ma = this.xAxisMeshers[0]; + var mb = this.xAxisMeshers[31]; + ma.finish(); + mb.finish(); + ma.doAuxiliaryFaceOffset = false; + mb.doAuxiliaryFaceOffset = false; + for (int y = 0; y < 32; y++) { + int skipA = 0; + int skipB = 0; + for (int z = 0; z < 32; z++) { + int i = y*32+z; + int msk = this.opaqueMasks[i]; + if ((msk & 1) != 0) {//-x + long neighborId = this.neighboringFaces[i]; + boolean oki = true; + if (Mapper.getBlockId(neighborId) != 0) {//Not air + long meta = this.modelMan.getModelMetadataFromClientId(this.modelMan.getModelId(Mapper.getBlockId(neighborId))); + if (ModelQueries.isFullyOpaque(meta)) { + oki = false; + } + } + if (oki) { + ma.skip(skipA); skipA = 0; + long A = this.sectionData[(i<<5) * 2]; + ma.putNext(0L | ((A&0xFFFF)<<26) | (((long)Mapper.getLightId(neighborId))<<55)|((A&(0x1FFL<<24))<<(46-24))); + } else {skipA++;} + } else {skipA++;} + + if ((msk & (1<<31)) != 0) {//+x + long neighborId = this.neighboringFaces[i+32*32]; + boolean oki = true; + if (Mapper.getBlockId(neighborId) != 0) {//Not air + long meta = this.modelMan.getModelMetadataFromClientId(this.modelMan.getModelId(Mapper.getBlockId(neighborId))); + if (ModelQueries.isFullyOpaque(meta)) { + oki = false; + } + } + if (oki) { + mb.skip(skipB); skipB = 0; + long A = this.sectionData[(i*32+31) * 2]; + mb.putNext(1L | ((A&0xFFFF)<<26) | (((long)Mapper.getLightId(neighborId))<<55)|((A&(0x1FFL<<24))<<(46-24))); + } else {skipB++;} + } else {skipB++;} + } + ma.skip(skipA); + mb.skip(skipB); + } + ma.finish(); + mb.finish(); + ma.doAuxiliaryFaceOffset = true; + mb.doAuxiliaryFaceOffset = true; + } + + for (var mesher : this.xAxisMeshers) { + mesher.finish(); + } + } + + /* + private static long createQuad() { + ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(metadata)?1:0)) | otherFlags + + + long data = Integer.toUnsignedLong(array[i*3+1]); + data |= ((long) array[i*3+2])<<32; + long encodedQuad = Integer.toUnsignedLong(QuadEncoder.encodePosition(face, otherAxis, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46); + }*/ + + //section is already acquired and gets released by the parent + public BuiltSection generateMesh(WorldSection section) { + //Copy section data to end of array so that can mutate array while reading safely + section.copyDataTo(this.sectionData, 32*32*32); + + this.quadCount = 0; + + this.minX = Integer.MAX_VALUE; + this.minY = Integer.MAX_VALUE; + this.minZ = Integer.MAX_VALUE; + this.maxX = Integer.MIN_VALUE; + this.maxY = Integer.MIN_VALUE; + this.maxZ = Integer.MIN_VALUE; + + Arrays.fill(this.directionalQuadCounters, (short) 0); + + //Prepare everything + int neighborMsk = this.prepareSectionData(); + this.acquireNeighborData(section, neighborMsk); + + this.generateYZFaces(); + this.generateXFaces(); + + + //TODO:NOTE! when doing face culling of translucent blocks, + // if the connecting type of the translucent block is the same AND the face is full, discard it + // this stops e.g. multiple layers of glass (and ocean) from having 3000 layers of quads etc + + if (this.quadCount == 0) { + return BuiltSection.emptyWithChildren(section.key, section.getNonEmptyChildren()); + } + + //TODO: FIXME AND OPTIMIZE, get rid of the stupid quad collector bullshit + + int[] offsets = new int[8]; + var buff = new MemoryBuffer(this.quadCount * 8L); + long ptr = buff.address; + int coff = 0; + + for (int face = 0; face < 6; face++) { + offsets[face + 2] = coff; + int size = this.directionalQuadCounters[face]; + UnsafeUtil.memcpy(this.directionalQuadBufferPtr + (face*(8*(1<<16))), ptr + coff*8L, (size* 8L)); + coff += size; + } + + + int aabb = 0; + aabb |= 0; + aabb |= 0<<5; + aabb |= 0<<10; + aabb |= (31)<<15; + aabb |= (31)<<20; + aabb |= (31)<<25; + + return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets); + + /* + buff = new MemoryBuffer(bufferSize * 8L); + long ptr = buff.address; + int coff = 0; + + //Ordering is: translucent, double sided quads, directional quads + offsets[0] = coff; + int size = this.translucentQuadCollector.size(); + LongArrayList arrayList = this.translucentQuadCollector; + for (int i = 0; i < size; i++) { + long data = arrayList.getLong(i); + MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data); + } + + offsets[1] = coff; + size = this.doubleSidedQuadCollector.size(); + arrayList = this.doubleSidedQuadCollector; + for (int i = 0; i < size; i++) { + long data = arrayList.getLong(i); + MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data); + } + + for (int face = 0; face < 6; face++) { + offsets[face + 2] = coff; + final LongArrayList faceArray = this.directionalQuadCollectors[face]; + size = faceArray.size(); + for (int i = 0; i < size; i++) { + long data = faceArray.getLong(i); + MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data); + } + } + + int aabb = 0; + aabb |= this.minX; + aabb |= this.minY<<5; + aabb |= this.minZ<<10; + aabb |= (this.maxX-this.minX)<<15; + aabb |= (this.maxY-this.minY)<<20; + aabb |= (this.maxZ-this.minZ)<<25; + + return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets); + */ + } + + public void free() { + this.directionalQuadBuffer.free(); + } + + + + + + + + + + + + + + + //Returns true if a face was placed + private boolean putFluidFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfClientModelId, int selfBlockId, long facingState, long facingMetadata, int facingClientModelId, int a, int b) { + int selfFluidClientId = this.modelMan.getFluidClientStateId(selfClientModelId); + long selfFluidMetadata = this.modelMan.getModelMetadataFromClientId(selfFluidClientId); + + int facingFluidClientId = -1; + if (ModelQueries.containsFluid(facingMetadata)) { + facingFluidClientId = this.modelMan.getFluidClientStateId(facingClientModelId); + } + + //If both of the states are the same, then dont render the fluid face + if (selfFluidClientId == facingFluidClientId) { + return false; + } + + if (facingFluidClientId != -1) { + //TODO: OPTIMIZE + if (this.world.getMapper().getBlockStateFromBlockId(selfBlockId).getBlock() == this.world.getMapper().getBlockStateFromBlockId(Mapper.getBlockId(facingState)).getBlock()) { + return false; + } + } + + + if (ModelQueries.faceOccludes(facingMetadata, opposingFace)) { + return false; + } + + //if the model has a fluid state but is not a liquid need to see if the solid state had a face rendered and that face is occluding, if so, dont render the fluid state face + if ((!ModelQueries.isFluid(metadata)) && ModelQueries.faceOccludes(metadata, face)) { + return false; + } + + + + //TODO:FIXME SOMEHOW THIS IS CRITICAL!!!!!!!!!!!!!!!!!! + // so there is one more issue need to be fixed, if water is layered ontop of eachother, the side faces depend on the water state ontop + // this has been hackfixed in the model texture bakery but a proper solution that doesnt explode the sides of the water textures needs to be done + // the issue is that the fluid rendering depends on the up state aswell not just the face state which is really really painful to account for + // e.g the sides of a full water is 8 high or something, not the full block height, this results in a gap between water layers + + + + long otherFlags = 0; + otherFlags |= ModelQueries.isTranslucent(selfFluidMetadata)?1L<<33:0; + otherFlags |= ModelQueries.isDoubleSided(selfFluidMetadata)?1L<<34:0; + mesher.put(a, b, ((long)selfFluidClientId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(selfFluidMetadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(selfFluidMetadata)?1:0)) | otherFlags); + return true; + } + + //Returns true if a face was placed + private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long metadata, int clientModelId, int selfBiome, long facingModelId, long facingMetadata, int selfLight, int facingLight, int a, int b) { + if (ModelQueries.cullsSame(metadata) && clientModelId == facingModelId) { + //If we are facing a block, and we are both the same state, dont render that face + return false; + } + + //If face can be occluded and is occluded from the facing block, then dont render the face + if (ModelQueries.faceCanBeOccluded(metadata, face) && ModelQueries.faceOccludes(facingMetadata, opposingFace)) { + return false; + } + + long otherFlags = 0; + otherFlags |= ModelQueries.isTranslucent(metadata)?1L<<33:0; + otherFlags |= ModelQueries.isDoubleSided(metadata)?1L<<34:0; + mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?selfLight:facingLight))<<16) | (ModelQueries.isBiomeColoured(metadata)?(((long) Mapper.getBiomeId(selfBiome))<<24):0) | otherFlags); + return true; + } + + private static int getMeshletHoldingCount(int quads, int quadsPerMeshlet, int meshletSize) { + return ((quads+(quadsPerMeshlet-1))/quadsPerMeshlet)*meshletSize; + } + + public static int alignUp(int n, int alignment) { + return (n + alignment - 1) & -alignment; + } +} \ No newline at end of file diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java index 6c040d06..41c01640 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java @@ -46,7 +46,7 @@ public class RenderGenerationService { this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{ //Thread local instance of the factory - var factory = new RenderDataFactory4(this.world, this.modelBakery.factory, this.emitMeshlets); + var factory = new RenderDataFactory45(this.world, this.modelBakery.factory, this.emitMeshlets); return new Pair<>(() -> { this.processJob(factory); }, factory::free); @@ -75,7 +75,7 @@ public class RenderGenerationService { } //TODO: add a generated render data cache - private void processJob(RenderDataFactory4 factory) { + private void processJob(RenderDataFactory45 factory) { BuildTask task; synchronized (this.taskQueue) { task = this.taskQueue.removeFirst(); @@ -133,7 +133,9 @@ public class RenderGenerationService { queuedTask.hasDoneModelRequest = true;//Mark (or remark) the section as having chunks requested if (queuedTask == task) {//use the == not .equal to see if we need to release a permit - this.threads.execute();//Since we put in queue, release permit + if (this.threads.isAlive()) {//Only execute if were not dead + this.threads.execute();//Since we put in queue, release permit + } //If we did put it in the queue, dont release the section shouldFreeSection = false; @@ -186,11 +188,25 @@ public class RenderGenerationService { */ public void shutdown() { + //Steal and free as much work as possible + while (this.threads.steal()) { + synchronized (this.taskQueue) { + var task = this.taskQueue.removeFirst(); + if (task.section != null) { + task.section.release(); + } + } + } + + //Shutdown the threads this.threads.shutdown(); //Cleanup any remaining data while (!this.taskQueue.isEmpty()) { - this.taskQueue.removeFirst(); + var task = this.taskQueue.removeFirst(); + if (task.section != null) { + task.section.release(); + } } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeCleaner.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeCleaner.java index fc15b797..9c26e817 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeCleaner.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeCleaner.java @@ -2,6 +2,8 @@ package me.cortex.voxy.client.core.rendering.hierachical; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue; +import it.unimi.dsi.fastutil.ints.IntCollection; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.shader.AutoBindingShader; @@ -14,6 +16,7 @@ import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.common.world.WorldEngine; import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.opengl.ARBDirectStateAccess.glCopyNamedBufferSubData; import static org.lwjgl.opengl.GL20.glUniform1i; import static org.lwjgl.opengl.GL30C.glBindBufferRange; import static org.lwjgl.opengl.GL42C.glMemoryBarrier; @@ -30,11 +33,16 @@ import static org.lwjgl.opengl.GL43C.*; public class NodeCleaner { //TODO: use batch_visibility_set to clear visibility data when nodes are removed!! (TODO: nodeManager will need to forward info to this) - private static final int OUTPUT_COUNT = 512; + + private static final int SORTING_WORKER_SIZE = 64; + private static final int OUTPUT_COUNT = 256; + private static final int BATCH_SET_SIZE = 2048; + private final AutoBindingShader sorter = Shader.makeAuto(PrintfDebugUtil.PRINTF_processor) + .define("WORK_SIZE", SORTING_WORKER_SIZE) .define("OUTPUT_SIZE", OUTPUT_COUNT) .define("VISIBILITY_BUFFER_BINDING", 1) .define("OUTPUT_BUFFER_BINDING", 2) @@ -62,7 +70,8 @@ public class NodeCleaner { private final GlBuffer outputBuffer = new GlBuffer(OUTPUT_COUNT*4+OUTPUT_COUNT*8);//Scratch + output private final GlBuffer scratchBuffer = new GlBuffer(BATCH_SET_SIZE*4);//Scratch buffer for setting ids with - private final IntArrayFIFOQueue idsToClear = new IntArrayFIFOQueue(); + private final IntOpenHashSet allocIds = new IntOpenHashSet(); + private final IntOpenHashSet freeIds = new IntOpenHashSet(); private final NodeManager nodeManager; int visibilityId = 0; @@ -81,18 +90,35 @@ public class NodeCleaner { .ssbo("VISIBILITY_BUFFER_BINDING", this.visibilityBuffer) .ssbo("OUTPUT_BUFFER_BINDING", this.outputBuffer); - this.nodeManager.setClearIdCallback(this::clearId); + this.nodeManager.setClear(new NodeManager.ICleaner() { + @Override + public void alloc(int id) { + NodeCleaner.this.allocIds.add(id); + NodeCleaner.this.freeIds.remove(id); + } + + @Override + public void move(int from, int to) { + NodeCleaner.this.allocIds.remove(to); + glCopyNamedBufferSubData(NodeCleaner.this.visibilityBuffer.id, NodeCleaner.this.visibilityBuffer.id, 4L*from, 4L*to, 4); + } + + @Override + public void free(int id) { + NodeCleaner.this.freeIds.add(id); + NodeCleaner.this.allocIds.remove(id); + } + }); } - public void clearId(int id) { - this.idsToClear.enqueue(id); - } public void tick(GlBuffer nodeDataBuffer) { this.visibilityId++; - this.clearIds(); - if (this.shouldCleanGeometry() && false ) { + this.setIds(this.allocIds, this.visibilityId); + this.setIds(this.freeIds, -1); + + if (this.shouldCleanGeometry()) { glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); this.outputBuffer.fill(this.nodeManager.maxNodeCount-2);//TODO: maybe dont set to zero?? @@ -100,9 +126,10 @@ public class NodeCleaner { this.sorter.bind(); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, nodeDataBuffer.id); + //TODO: choose whether this is in nodeSpace or section/geometryId space - //this.nodeManager.getCurrentMaxNodeId() - glDispatchCompute((200_000+127)/128, 1, 1); + // + glDispatchCompute((this.nodeManager.getCurrentMaxNodeId()+SORTING_WORKER_SIZE-1)/SORTING_WORKER_SIZE, 1, 1); glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); this.resultTransformer.bind(); @@ -110,29 +137,38 @@ public class NodeCleaner { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, nodeDataBuffer.id); glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 2, this.outputBuffer.id, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.visibilityBuffer.id); + glUniform1ui(0, this.visibilityId); glDispatchCompute(1,1,1); + //glFinish(); DownloadStream.INSTANCE.download(this.outputBuffer, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT, this::onDownload); + //glFinish(); } } private boolean shouldCleanGeometry() { - // if there is less than 200mb of space, clean - return this.nodeManager.getGeometryManager().getRemainingCapacity() < 3_000_000_000L; + //// if there is less than 200mb of space, clean + //return this.nodeManager.getGeometryManager().getRemainingCapacity() < 1_000_000_000L; + + //If used more than 75% of geometry buffer + return 3<((double)this.nodeManager.getGeometryManager().getUsedCapacity())/((double)this.nodeManager.getGeometryManager().getRemainingCapacity()); } private void onDownload(long ptr, long size) { //StringBuilder b = new StringBuilder(); - Long2IntOpenHashMap aa = new Long2IntOpenHashMap(); + //Long2IntOpenHashMap aa = new Long2IntOpenHashMap(); for (int i = 0; i < OUTPUT_COUNT; i++) { long pos = Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i))<<32; - pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i + 4)); - aa.addTo(pos, 1); + pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i + 4)); + //aa.addTo(pos, 1); if (pos == -1) { //TODO: investigate how or what this happens continue; } + //if (WorldEngine.getLevel(pos) == 4 && WorldEngine.getX(pos)<-32) { + // int a = 0; + //} this.nodeManager.removeNodeGeometry(pos); //b.append(", ").append(WorldEngine.pprintPos(pos));//.append(((int)((pos>>32)&0xFFFFFFFFL)));// } @@ -141,19 +177,20 @@ public class NodeCleaner { //System.out.println(b); } - private void clearIds() { - if (!this.idsToClear.isEmpty()) { + private void setIds(IntOpenHashSet collection, int setTo) { + if (!collection.isEmpty()) { this.batchClear.bind(); - - while (!this.idsToClear.isEmpty()) { - int cnt = Math.min(this.idsToClear.size(), BATCH_SET_SIZE); + var iter = collection.iterator(); + while (iter.hasNext()) { + int cnt = Math.min(collection.size(), BATCH_SET_SIZE); long ptr = UploadStream.INSTANCE.upload(this.scratchBuffer, 0, cnt * 4L); for (int i = 0; i < cnt; i++) { - MemoryUtil.memPutInt(ptr + i * 4, this.idsToClear.dequeueInt()); + MemoryUtil.memPutInt(ptr + i * 4, iter.nextInt()); + iter.remove(); } UploadStream.INSTANCE.commit(); glUniform1ui(0, cnt); - glUniform1ui(1, this.visibilityId); + glUniform1ui(1, setTo); glDispatchCompute((cnt+127)/128, 1, 1); } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeManager.java index e4a5d788..3516cbc9 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeManager.java @@ -2,6 +2,7 @@ package me.cortex.voxy.client.core.rendering.hierachical; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; @@ -90,10 +91,16 @@ public class NodeManager { private final LongOpenHashSet topLevelNodes = new LongOpenHashSet(); private int activeNodeRequestCount; - public interface ClearIdCallback {void clearId(int id);} - private ClearIdCallback clearIdCallback; - public void setClearIdCallback(ClearIdCallback callback) {this.clearIdCallback = callback;} - private void clearId(int id) { if (this.clearIdCallback != null) this.clearIdCallback.clearId(id); } + public interface ICleaner { + void alloc(int id); + void move(int from, int to); + void free(int id); + } + private ICleaner cleanerInterface; + public void setClear(ICleaner callback) {this.cleanerInterface = callback;} + private void clearAllocId(int id) { if (this.cleanerInterface != null) this.cleanerInterface.alloc(id); } + private void clearMoveId(int from, int to) { if (this.cleanerInterface != null) this.cleanerInterface.move(from, to); } + private void clearFreeId(int id) { if (this.cleanerInterface != null) this.cleanerInterface.free(id); } public NodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, ISectionWatcher watcher) { if (!MathUtil.isPowerOfTwo(maxNodeCount)) { @@ -153,7 +160,7 @@ public class NodeManager { long pos = sectionResult.position; int nodeId = this.activeSectionMap.get(pos); if (nodeId == -1) { - Logger.warn("Got geometry update for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, discarding!"); + //Logger.warn("Got geometry update for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, discarding!"); sectionResult.free(); return; } @@ -495,6 +502,10 @@ public class NodeManager { //copy the previous entry to its new location this.nodeData.copyNode(prevChildId, newChildId); + this.clearAllocId(newChildId); + this.clearMoveId(prevChildId, newChildId); + this.clearFreeId(prevChildId); + int prevNodeId = this.activeSectionMap.get(cPos); if ((prevNodeId & NODE_TYPE_MSK) == NODE_TYPE_REQUEST) { throw new IllegalStateException(); @@ -503,6 +514,7 @@ public class NodeManager { throw new IllegalStateException("State inconsistency"); } this.activeSectionMap.put(cPos, (prevNodeId & NODE_TYPE_MSK) | newChildId); + //Release the old entry this.nodeData.free(prevChildId); //Need to invalidate the old and the new @@ -722,7 +734,7 @@ public class NodeManager { this.geometryManager.removeSection(geometry); this.nodeData.free(nodeId); - this.clearId(nodeId); + this.clearFreeId(nodeId); this.invalidateNode(nodeId); //Unwatch position @@ -756,6 +768,7 @@ public class NodeManager { //Assume that this is always a top node // FIXME: DONT DO THIS this.topLevelNodeIds.add(id); + this.clearAllocId(id); } private void finishRequest(int requestId, NodeChildRequest request) { @@ -813,13 +826,15 @@ public class NodeManager { this.nodeData.setNodeGeometry(childNodeId, request.getChildMesh(childIdx)); //Mark for update this.invalidateNode(childNodeId); - this.clearId(childNodeId);//Clear the id + //this.clearId(childNodeId);//Clear the id //Put in map int pid = this.activeSectionMap.put(childPos, childNodeId|NODE_TYPE_LEAF); if ((pid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) { throw new IllegalStateException("Put node in map from request but type was not request: " + pid + " " + WorldEngine.pprintPos(childPos)); } + + this.clearAllocId(childNodeId); } //Free request this.childRequests.release(requestId); @@ -912,6 +927,7 @@ public class NodeManager { if ((pid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) { throw new IllegalStateException("Put node in map from request but type was not request: " + pid + " " + WorldEngine.pprintPos(childPos)); } + this.clearAllocId(childId); } else { prevChildId++; @@ -920,6 +936,10 @@ public class NodeManager { //Its a previous entry, copy it to its new location this.nodeData.copyNode(prevChildId, childId); + this.clearAllocId(childId); + this.clearMoveId(prevChildId, childId); + this.clearFreeId(prevChildId); + int prevNodeId = this.activeSectionMap.get(pos); if ((prevNodeId&NODE_TYPE_MSK) == NODE_TYPE_REQUEST) { throw new IllegalStateException(); @@ -1113,7 +1133,7 @@ public class NodeManager { public void removeNodeGeometry(long pos) { int nodeId = this.activeSectionMap.get(pos); if (nodeId == -1) { - Logger.warn("Got geometry removal for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!"); + //Logger.warn("Got geometry removal for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!"); return; } int nodeType = nodeId&NODE_TYPE_MSK; @@ -1122,7 +1142,7 @@ public class NodeManager { Logger.error("Tried removing geometry for pos: " + WorldEngine.pprintPos(pos) + " but its type was a request, ignoring!"); return; } - this.clearId(nodeId); + //this.clearId(nodeId); if (nodeType == NODE_TYPE_INNER) { this.clearGeometryInternal(pos, nodeId); @@ -1157,7 +1177,8 @@ public class NodeManager { throw new IllegalStateException("Parent node must be an inner node"); pId &= NODE_ID_MSK; - {//Check all children are leaf nodes + if (false) {//Check all children are leaf nodes + //TODO: make a better way to do this (i.e. gpu driven) int cPtr = this.nodeData.getChildPtr(pId); if (cPtr != SENTINEL_EMPTY_CHILD_PTR) { if (cPtr == -1) { @@ -1172,8 +1193,11 @@ public class NodeManager { if (cn==-1) throw new IllegalStateException(); //If a child is not a leaf, return - if ((cn&NODE_TYPE_MSK)!=NODE_TYPE_LEAF) + if ((cn&NODE_TYPE_MSK)!=NODE_TYPE_LEAF) { + + this.clearAllocId(this.activeSectionMap.get(cPos)&NODE_ID_MSK); return; + } } } } @@ -1185,7 +1209,7 @@ public class NodeManager { } else { //Convert to leaf node this.recurseRemoveChildNodes(pPos);//TODO: make this download/fetch the data instead of just deleting it - this.clearId(pId); + //this.clearId(pId); int old = this.activeSectionMap.put(pPos, NODE_TYPE_LEAF|pId); if (old == -1) @@ -1552,9 +1576,9 @@ public class NodeManager { } public void verifyIntegrity() { - this.verifyIntegrity(null); + this.verifyIntegrity(null, null); } - public void verifyIntegrity(LongSet watchingPosSet) { + public void verifyIntegrity(LongSet watchingPosSet, IntSet nodes) { //Should verify integrity of node manager, everything // should traverse from top (root positions) down // after it should check if there is anything it hasnt tracked, if so thats badd @@ -1594,5 +1618,13 @@ public class NodeManager { throw new IllegalStateException(); } } + if (nodes != null) { + if (!nodes.containsAll(seenNodes)) { + throw new IllegalStateException(); + } + if (!seenNodes.containsAll(nodes)) { + throw new IllegalStateException(); + } + } } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeStore.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeStore.java index cdf2a2d8..248726b6 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeStore.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeStore.java @@ -250,6 +250,13 @@ public final class NodeStore { //Writes out a nodes data to the ptr in the compacted/reduced format public void writeNode(long ptr, int nodeId) { + if (!this.nodeExists(nodeId)) { + MemoryUtil.memPutLong(ptr, -1); + MemoryUtil.memPutLong(ptr + 8, -1); + MemoryUtil.memPutLong(ptr + 16, -1); + MemoryUtil.memPutLong(ptr + 24, -1); + return; + } long pos = this.nodePosition(nodeId); MemoryUtil.memPutInt(ptr, (int) (pos>>32)); ptr += 4; MemoryUtil.memPutInt(ptr, (int) pos); ptr += 4; diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/TestNodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/TestNodeManager.java index 893998d3..02a2f8d8 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/TestNodeManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/TestNodeManager.java @@ -1,6 +1,7 @@ package me.cortex.voxy.client.core.rendering.hierachical; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntFunction; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; @@ -76,6 +77,28 @@ public class TestNodeManager { } } + private static class CleanerImp implements NodeManager.ICleaner { + private final IntOpenHashSet active = new IntOpenHashSet(); + + @Override + public void alloc(int id) { + if (!this.active.add(id)) { + throw new IllegalStateException(); + } + } + + @Override + public void move(int from, int to) { + + } + + @Override + public void free(int id) { + if (!this.active.remove(id)) { + throw new IllegalStateException(); + } + } + } private static class Watcher implements ISectionWatcher { private final Long2ByteOpenHashMap updateTypes = new Long2ByteOpenHashMap(); @@ -137,11 +160,14 @@ public class TestNodeManager { public final MemoryGeometryManager geometryManager; public final NodeManager nodeManager; public final Watcher watcher; + public final CleanerImp cleaner; public TestBase() { this.watcher = new Watcher(); + this.cleaner = new CleanerImp(); this.geometryManager = new MemoryGeometryManager(1<<20, 1<<30); this.nodeManager = new NodeManager(1 << 21, this.geometryManager, this.watcher); + this.nodeManager.setClear(this.cleaner); } public void putTopPos(long pos) { @@ -208,7 +234,7 @@ public class TestNodeManager { } public void verifyIntegrity() { - this.nodeManager.verifyIntegrity(this.watcher.updateTypes.keySet()); + this.nodeManager.verifyIntegrity(this.watcher.updateTypes.keySet(), this.cleaner.active); } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/BasicSectionGeometryManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/BasicSectionGeometryManager.java index 01dcd38b..67f725d0 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/section/BasicSectionGeometryManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/BasicSectionGeometryManager.java @@ -137,7 +137,7 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager this.geometry.downloadRemove(oldMetadata.geometryPtr, buffer -> callback.accept(new BuiltSection(oldMetadata.position, oldMetadata.childExistence, oldMetadata.aabb, buffer.copy(), oldMetadata.offsets)) ); - this.geometry.free(oldMetadata.geometryPtr); + //this.geometry.free(oldMetadata.geometryPtr); this.invalidatedSectionIds.add(id); } diff --git a/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java b/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java index 3597ec75..1bd6d4ac 100644 --- a/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java +++ b/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java @@ -18,6 +18,8 @@ public class HierarchicalBitSet { } } + private int endId = -1; + public HierarchicalBitSet() { this(1<<(6*4)); } @@ -55,11 +57,12 @@ public class HierarchicalBitSet { } } this.cnt++; - + this.endId += ret==(this.endId+1)?1:0; return ret; } private void set(int idx) { + this.endId += idx==(this.endId+1)?1:0; long dp = this.D[idx>>6] |= 1L<<(idx&0x3f); if (dp==-1) { idx >>= 6; @@ -139,6 +142,13 @@ public class HierarchicalBitSet { long v = this.D[idx>>6]; boolean wasSet = (v&(1L<<(idx&0x3f)))!=0; this.cnt -= wasSet?1:0; + + if (wasSet && idx == this.endId) { + //Need to go back until we find the endIdx bit + for (this.endId--; this.endId>=0 && !this.isSet(this.endId); this.endId--); + //this.endId++; + } + this.D[idx>>6] = v&~(1L<<(idx&0x3f)); idx >>= 6; this.C[idx>>6] &= ~(1L<<(idx&0x3f)); @@ -146,6 +156,7 @@ public class HierarchicalBitSet { this.B[idx>>6] &= ~(1L<<(idx&0x3f)); idx >>= 6; this.A &= ~(1L<<(idx&0x3f)); + return wasSet; } @@ -162,7 +173,7 @@ public class HierarchicalBitSet { public int getMaxIndex() { - throw new IllegalStateException(); + return this.endId; } @@ -172,6 +183,25 @@ public class HierarchicalBitSet { if (h.allocateNext() != i) { throw new IllegalStateException("At:" + i); } + if (h.endId != i) { + throw new IllegalStateException(); + } + } + for (int i = 0; i < 1<<18; i++) { + if (!h.free(i)) { + throw new IllegalStateException(); + } + } + for (int i = (1<<19)-1; i != (1<<18)-1; i--) { + if (h.endId != i) { + throw new IllegalStateException(); + } + if (!h.free(i)) { + throw new IllegalStateException(); + } + } + if (h.endId != -1) { + throw new IllegalStateException(); } } diff --git a/src/main/java/me/cortex/voxy/common/util/MemoryBuffer.java b/src/main/java/me/cortex/voxy/common/util/MemoryBuffer.java index 821ab14d..193b4449 100644 --- a/src/main/java/me/cortex/voxy/common/util/MemoryBuffer.java +++ b/src/main/java/me/cortex/voxy/common/util/MemoryBuffer.java @@ -61,7 +61,7 @@ public class MemoryBuffer extends TrackedObject { } public MemoryBuffer copy() { - var copy = new MemoryBuffer(false, this.size, size, freeable); + var copy = new MemoryBuffer(this.size); this.cpyTo(copy.address); return copy; } diff --git a/src/main/java/me/cortex/voxy/common/voxelization/WorldConversionFactory.java b/src/main/java/me/cortex/voxy/common/voxelization/WorldConversionFactory.java index ec02a8a0..7d378785 100644 --- a/src/main/java/me/cortex/voxy/common/voxelization/WorldConversionFactory.java +++ b/src/main/java/me/cortex/voxy/common/voxelization/WorldConversionFactory.java @@ -6,6 +6,7 @@ import me.cortex.voxy.common.util.Pair; import me.cortex.voxy.common.world.other.Mipper; import me.cortex.voxy.common.world.other.Mapper; import net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.block.BlockState; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.util.collection.EmptyPaletteStorage; @@ -16,6 +17,8 @@ import net.minecraft.world.chunk.*; import java.util.WeakHashMap; public class WorldConversionFactory { + private static final boolean LITHIUM_INSTALLED = FabricLoader.getInstance().isModLoaded("lithium"); + private static final class Cache { private final int[] biomeCache = new int[4*4*4]; private final WeakHashMap> localMapping = new WeakHashMap<>(); @@ -34,68 +37,71 @@ public class WorldConversionFactory { //TODO: create a mapping for world/mapper -> local mapping private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(Cache::new); - private static void setupLocalPalette(Palette vp,Reference2IntOpenHashMap blockCache, Mapper mapper, int[] pc) { - { - if (vp instanceof ArrayPalette) { - for (int i = 0; i < vp.getSize(); i++) { - var state = vp.get(i); - int blockId = -1; - if (state != null) { - blockId = blockCache.getOrDefault(state, -1); - if (blockId == -1) { - blockId = mapper.getIdForBlockState(state); - blockCache.put(state, blockId); - } + private static boolean setupLithiumLocalPallet(Palette vp, Reference2IntOpenHashMap blockCache, Mapper mapper, int[] pc) { + if (vp instanceof LithiumHashPalette) { + for (int i = 0; i < vp.getSize(); i++) { + BlockState state = null; + int blockId = -1; + try { state = vp.get(i); } catch (Exception e) {} + if (state != null) { + blockId = blockCache.getOrDefault(state, -1); + if (blockId == -1) { + blockId = mapper.getIdForBlockState(state); + blockCache.put(state, blockId); } - pc[i] = blockId; } - } else if (vp instanceof LithiumHashPalette) { - for (int i = 0; i < vp.getSize(); i++) { - BlockState state = null; - int blockId = -1; - try { state = vp.get(i); } catch (Exception e) {} - if (state != null) { - blockId = blockCache.getOrDefault(state, -1); - if (blockId == -1) { - blockId = mapper.getIdForBlockState(state); - blockCache.put(state, blockId); - } + pc[i] = blockId; + } + return true; + } + return false; + } + private static void setupLocalPalette(Palette vp, Reference2IntOpenHashMap blockCache, Mapper mapper, int[] pc) { + if (vp instanceof ArrayPalette) { + for (int i = 0; i < vp.getSize(); i++) { + var state = vp.get(i); + int blockId = -1; + if (state != null) { + blockId = blockCache.getOrDefault(state, -1); + if (blockId == -1) { + blockId = mapper.getIdForBlockState(state); + blockCache.put(state, blockId); } - pc[i] = blockId; } - } else { - if (vp instanceof BiMapPalette pal) { - //var map = pal.map; - //TODO: heavily optimize this by reading the map directly + pc[i] = blockId; + } + } else if (vp instanceof BiMapPalette pal) { + //var map = pal.map; + //TODO: heavily optimize this by reading the map directly - for (int i = 0; i < vp.getSize(); i++) { - BlockState state = null; - int blockId = -1; - try { state = vp.get(i); } catch (Exception e) {} - if (state != null) { - blockId = blockCache.getOrDefault(state, -1); - if (blockId == -1) { - blockId = mapper.getIdForBlockState(state); - blockCache.put(state, blockId); - } - } - pc[i] = blockId; + for (int i = 0; i < vp.getSize(); i++) { + BlockState state = null; + int blockId = -1; + try { state = vp.get(i); } catch (Exception e) {} + if (state != null) { + blockId = blockCache.getOrDefault(state, -1); + if (blockId == -1) { + blockId = mapper.getIdForBlockState(state); + blockCache.put(state, blockId); } - - } else if (vp instanceof SingularPalette) { - int blockId = -1; - var state = vp.get(0); - if (state != null) { - blockId = blockCache.getOrDefault(state, -1); - if (blockId == -1) { - blockId = mapper.getIdForBlockState(state); - blockCache.put(state, blockId); - } - } - pc[0] = blockId; - } else { - throw new IllegalStateException("Unknown palette type: " + vp); } + pc[i] = blockId; + } + + } else if (vp instanceof SingularPalette) { + int blockId = -1; + var state = vp.get(0); + if (state != null) { + blockId = blockCache.getOrDefault(state, -1); + if (blockId == -1) { + blockId = mapper.getIdForBlockState(state); + blockCache.put(state, blockId); + } + } + pc[0] = blockId; + } else { + if (!(LITHIUM_INSTALLED && setupLithiumLocalPallet(vp, blockCache, mapper, pc))) { + throw new IllegalStateException("Unknown palette type: " + vp); } } } diff --git a/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java b/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java index 19f4ccc8..0d3a208d 100644 --- a/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java +++ b/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java @@ -7,6 +7,7 @@ import me.cortex.voxy.common.world.other.Mapper; import org.jetbrains.annotations.Nullable; import java.util.Arrays; +import java.util.concurrent.locks.ReentrantLock; public class ActiveSectionTracker { @@ -16,6 +17,7 @@ public class ActiveSectionTracker { //Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane private final Long2ObjectOpenHashMap>[] loadedSectionCache; + private final ReentrantLock[] locks; private final SectionLoader loader; private final int maxLRUSectionPerSlice; @@ -34,10 +36,12 @@ public class ActiveSectionTracker { this.loader = loader; this.loadedSectionCache = new Long2ObjectOpenHashMap[1<(1024); this.lruSecondaryCache[i] = new Long2ObjectLinkedOpenHashMap<>(this.maxLRUSectionPerSlice); + this.locks[i] = new ReentrantLock(); } } @@ -48,10 +52,13 @@ public class ActiveSectionTracker { public WorldSection acquire(long key, boolean nullOnEmpty) { int index = this.getCacheArrayIndex(key); var cache = this.loadedSectionCache[index]; + final var lock = this.locks[index]; VolatileHolder holder = null; boolean isLoader = false; WorldSection section; - synchronized (cache) { + + lock.lock(); + { holder = cache.get(key); if (holder == null) { holder = new VolatileHolder<>(); @@ -61,12 +68,14 @@ public class ActiveSectionTracker { section = holder.obj; if (section != null) { section.acquire(); + lock.unlock(); return section; } if (isLoader) { section = this.lruSecondaryCache[index].remove(key); } } + lock.unlock(); //If this thread was the one to create the reference then its the thread to load the section if (isLoader) { @@ -107,19 +116,25 @@ public class ActiveSectionTracker { while ((section = holder.obj) == null) Thread.yield(); - synchronized (cache) { + //lock.lock(); + {//Dont think need to lock here if (section.tryAcquire()) { return section; } } + //lock.unlock(); return this.acquire(key, nullOnEmpty); } } void tryUnload(WorldSection section) { int index = this.getCacheArrayIndex(section.key); - var cache = this.loadedSectionCache[index]; - synchronized (cache) { + final var cache = this.loadedSectionCache[index]; + WorldSection prev = null; + WorldSection lruEntry = null; + final var lock = this.locks[index]; + lock.lock(); + { if (section.trySetFreed()) { var cached = cache.remove(section.key); var obj = cached.obj; @@ -129,16 +144,21 @@ public class ActiveSectionTracker { //Add section to secondary cache while primary is locked var lruCache = this.lruSecondaryCache[index]; - var prev = lruCache.put(section.key, section); - if (prev != null) { - prev._releaseArray(); - } + prev = lruCache.put(section.key, section); //If cache is bigger than its ment to be, remove the least recently used and free it if (this.maxLRUSectionPerSlice < lruCache.size()) { - lruCache.removeFirst()._releaseArray(); + lruEntry = lruCache.removeFirst(); } } } + lock.unlock(); + + if (prev != null) { + prev._releaseArray(); + } + if (lruEntry != null) { + lruEntry._releaseArray(); + } } private int getCacheArrayIndex(long pos) { diff --git a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java index 55496344..f4c32eda 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java @@ -8,7 +8,7 @@ import me.cortex.voxy.common.world.other.Mapper; import java.util.List; public class WorldEngine { - public static final int MAX_LOD_LAYERS = 5; + public static final int MAX_LOD_LAYER = 4; public static final int UPDATE_TYPE_BLOCK_BIT = 1; public static final int UPDATE_TYPE_CHILD_EXISTENCE_BIT = 2; @@ -38,7 +38,7 @@ public class WorldEngine { public Mapper getMapper() {return this.mapper;} public boolean isLive() {return this.isLive;} public WorldEngine(SectionStorage storage, int cacheCount) { - this(storage, MAX_LOD_LAYERS, cacheCount); + this(storage, MAX_LOD_LAYER+1, cacheCount);//The +1 is because its from 1 not from 0 } private WorldEngine(SectionStorage storage, int maxMipLayers, int cacheCount) { @@ -46,7 +46,7 @@ public class WorldEngine { this.storage = storage; this.mapper = new Mapper(this.storage); //5 cache size bits means that the section tracker has 32 separate maps that it uses - this.sectionTracker = new ActiveSectionTracker(7, storage::loadSection, cacheCount, this); + this.sectionTracker = new ActiveSectionTracker(10, storage::loadSection, cacheCount, this); } public WorldSection acquireIfExists(int lvl, int x, int y, int z) { diff --git a/src/main/java/me/cortex/voxy/common/world/WorldSection.java b/src/main/java/me/cortex/voxy/common/world/WorldSection.java index 2c34fdb1..8446fe5c 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldSection.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldSection.java @@ -35,7 +35,7 @@ public final class WorldSection { //TODO: should make it dynamically adjust the size allowance based on memory pressure/WorldSection allocation rate (e.g. is it doing a world import) - private static final int ARRAY_REUSE_CACHE_SIZE = 300;//500;//32*32*32*8*ARRAY_REUSE_CACHE_SIZE == number of bytes + private static final int ARRAY_REUSE_CACHE_SIZE = 400;//500;//32*32*32*8*ARRAY_REUSE_CACHE_SIZE == number of bytes //TODO: maybe just swap this to a ConcurrentLinkedDeque private static final AtomicInteger ARRAY_REUSE_CACHE_COUNT = new AtomicInteger(0); private static final ConcurrentLinkedDeque ARRAY_REUSE_CACHE = new ConcurrentLinkedDeque<>(); diff --git a/src/main/java/me/cortex/voxy/commonImpl/VoxyInstance.java b/src/main/java/me/cortex/voxy/commonImpl/VoxyInstance.java index 18a1368b..575a0889 100644 --- a/src/main/java/me/cortex/voxy/commonImpl/VoxyInstance.java +++ b/src/main/java/me/cortex/voxy/commonImpl/VoxyInstance.java @@ -1,7 +1,5 @@ package me.cortex.voxy.commonImpl; -import me.cortex.voxy.client.core.WorldImportWrapper; -import me.cortex.voxy.client.saver.ContextSelectionSystem; import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.config.section.SectionStorage; import me.cortex.voxy.common.thread.ServiceThreadPool; diff --git a/src/main/resources/assets/voxy/shaders/lod/hierarchical/cleaner/result_transformer.comp b/src/main/resources/assets/voxy/shaders/lod/hierarchical/cleaner/result_transformer.comp index 024984c5..4153668b 100644 --- a/src/main/resources/assets/voxy/shaders/lod/hierarchical/cleaner/result_transformer.comp +++ b/src/main/resources/assets/voxy/shaders/lod/hierarchical/cleaner/result_transformer.comp @@ -17,13 +17,19 @@ layout(binding = VISIBILITY_BUFFER_BINDING, std430) restrict buffer VisibilityBu uint[] visibility; }; +layout(location=0) uniform uint visibilityCounter; void main() { - uvec4 node = nodes[minVisIds[gl_LocalInvocationID.x]]; + uint id = minVisIds[gl_LocalInvocationID.x]; + uvec4 node = nodes[id]; uvec2 res = node.xy; if (all(equal(node, uvec4(0)))) {//If its a empty node, TODO: DONT THINK THIS IS ACTUALLY CORRECT res = uvec2(-1); } outputBuffer[gl_LocalInvocationID.x] = res;//Move the position of the node id into the output buffer - //outputBuffer[gl_LocalInvocationID.x].x = visibility[minVisIds[gl_LocalInvocationID.x]];// + //This is an evil hack to not spam the same node 500 times in a row + //TODO: maybe instead set this to the current frame index +// visibility[id] += 30; + visibility[id] = visibilityCounter; + } \ No newline at end of file diff --git a/src/main/resources/assets/voxy/shaders/lod/hierarchical/cleaner/sort_visibility.comp b/src/main/resources/assets/voxy/shaders/lod/hierarchical/cleaner/sort_visibility.comp index 14ee039e..ffdcb774 100644 --- a/src/main/resources/assets/voxy/shaders/lod/hierarchical/cleaner/sort_visibility.comp +++ b/src/main/resources/assets/voxy/shaders/lod/hierarchical/cleaner/sort_visibility.comp @@ -4,7 +4,7 @@ //#define OUTPUT_SIZE 128 -layout(local_size_x=128, local_size_y=1) in; +layout(local_size_x=WORK_SIZE, local_size_y=1) in; //256 workgroup #import @@ -54,9 +54,36 @@ void main() { return; } UnpackedNode node; - unpackNode(node, gl_GlobalInvocationID.x); + if (unpackNode(node, gl_GlobalInvocationID.x)==uvec4(-1)) { + return;//Unallocated node + } + if (isEmptyMesh(node) || (!hasMesh(node))) {//|| (!hasChildren(node)) return; } + //TODO: FIXME: DONT HARDCODE TOP LEVEL LOD LEVEL + if (node.lodLevel == 4) {// (!hasChildren(node)) -> Assume leaf node + return;//Cannot remove geometry from top level node + } + + /*THIS IS COMPLETLY WRONG, we need to check if all the children of the parent of the child are leaf nodes + // not this node + + //Very sneeky hack, ensure all children are leaf nodes + if (hasChildren(node)&&!childListIsEmpty(node)) { + uint ptr = getChildPtr(node); + uint cnt = getChildCount(node); + for (uint i = 0; i < cnt; i++) { + UnpackedNode child; + unpackNode(child, i+ptr); + if (hasChildren(child)) { + return; + } + } + } + */ + + + bubbleSort(0, gl_GlobalInvocationID.x); } \ No newline at end of file diff --git a/src/main/resources/assets/voxy/shaders/lod/hierarchical/node.glsl b/src/main/resources/assets/voxy/shaders/lod/hierarchical/node.glsl index 9532f8a1..2b21c84d 100644 --- a/src/main/resources/assets/voxy/shaders/lod/hierarchical/node.glsl +++ b/src/main/resources/assets/voxy/shaders/lod/hierarchical/node.glsl @@ -8,9 +8,6 @@ layout(binding = NODE_DATA_BINDING, std430) restrict buffer NodeData { //First 2 are joined to be the position - - - //All node access and setup into global variables //TODO: maybe make it global vars struct UnpackedNode { @@ -32,7 +29,7 @@ struct UnpackedNode { #define NULL_MESH ((1<<24)-1) #define EMPTY_MESH ((1<<24)-2) -void unpackNode(out UnpackedNode node, uint nodeId) { +uvec4 unpackNode(out UnpackedNode node, uint nodeId) { uvec4 compactedNode = nodes[nodeId]; node.nodeId = nodeId; node.lodLevel = compactedNode.x >> 28; @@ -50,6 +47,7 @@ void unpackNode(out UnpackedNode node, uint nodeId) { node.meshPtr = compactedNode.z&0xFFFFFFu; node.childPtr = compactedNode.w&0xFFFFFFu; node.flags = ((compactedNode.z>>24)&0xFFu) | (((compactedNode.w>>24)&0xFFu)<<8); + return compactedNode; } bool hasMesh(in UnpackedNode node) {