From 4d28a5a8c98add691e298712caad056e8272f3bb Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:12:50 +1000 Subject: [PATCH] Fixed directional culling and added AABB raster --- .../client/core/model/ModelManager.java | 7 +- .../rendering/building/RenderDataFactory.java | 338 +++++++++++++----- .../zenith/shaders/lod/gl46/cmdgen.comp | 90 ++++- 3 files changed, 325 insertions(+), 110 deletions(-) diff --git a/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java b/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java index 86fb24a5..3627c834 100644 --- a/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java +++ b/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java @@ -277,9 +277,12 @@ public class ModelManager { return false; } + public static boolean isDoubleSided(long metadata) { + return ((metadata>>(8*6))&4) != 0; + } + public static boolean isTranslucent(long metadata) { - //TODO: THIS - return false; + return ((metadata>>(8*6))&2) != 0; } diff --git a/src/main/java/me/cortex/zenith/client/core/rendering/building/RenderDataFactory.java b/src/main/java/me/cortex/zenith/client/core/rendering/building/RenderDataFactory.java index 80a34718..226c958b 100644 --- a/src/main/java/me/cortex/zenith/client/core/rendering/building/RenderDataFactory.java +++ b/src/main/java/me/cortex/zenith/client/core/rendering/building/RenderDataFactory.java @@ -7,19 +7,32 @@ import me.cortex.zenith.common.util.MemoryBuffer; import me.cortex.zenith.common.world.WorldEngine; import me.cortex.zenith.common.world.WorldSection; import me.cortex.zenith.common.world.other.Mapper; -import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import org.lwjgl.system.MemoryUtil; -import java.util.Map; - public class RenderDataFactory { private final WorldEngine world; private final ModelManager modelMan; private final QuadEncoder encoder; + + private final Mesher2D negativeMesher = new Mesher2D(5, 15); + private final Mesher2D positiveMesher = new Mesher2D(5, 15); + private final long[] sectionCache = new long[32*32*32]; private final long[] connectedSectionCache = new long[32*32*32]; + + private final LongArrayList doubleSidedQuadCollector = new LongArrayList(); + private final LongArrayList translucentQuadCollector = new LongArrayList(); + private final LongArrayList[] directionalQuadCollectors = new LongArrayList[]{new LongArrayList(), new LongArrayList(), new LongArrayList(), new LongArrayList(), new LongArrayList(), new LongArrayList()}; + + + private int minX; + private int minY; + private int minZ; + private int maxX; + private int maxY; + private int maxZ; public RenderDataFactory(WorldEngine world, ModelManager modelManager) { this.world = world; this.modelMan = modelManager; @@ -37,12 +50,234 @@ public class RenderDataFactory { // its neigbor or not (0 if it does 1 if it doesnt (0 is default behavior)) public BuiltSection generateMesh(WorldSection section, int buildMask) { section.copyDataTo(this.sectionCache); + this.translucentQuadCollector.clear(); + this.doubleSidedQuadCollector.clear(); + for (var collector : this.directionalQuadCollectors) { + collector.clear(); + } + 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; //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 + this.generateMeshForAxis(section, 0);//Direction.Axis.Y + this.generateMeshForAxis(section, 1);//Direction.Axis.Z + this.generateMeshForAxis(section, 2);//Direction.Axis.X + + int quadCount = this.doubleSidedQuadCollector.size() + this.translucentQuadCollector.size(); + for (var collector : this.directionalQuadCollectors) { + quadCount += collector.size(); + } + + if (quadCount == 0) { + return new BuiltSection(section.getKey()); + } + + var buff = new MemoryBuffer(quadCount*8L); + long ptr = buff.address; + int[] offsets = new int[8]; + int coff = 0; + + //Ordering is: translucent, double sided quads, directional quads + offsets[0] = coff; + for (long data : this.translucentQuadCollector) { + MemoryUtil.memPutLong(ptr + ((coff++)*8L), data); + } + + offsets[1] = coff; + for (long data : this.doubleSidedQuadCollector) { + MemoryUtil.memPutLong(ptr + ((coff++)*8L), data); + } + + for (int face = 0; face < 6; face++) { + offsets[face+2] = coff; + for (long data : this.directionalQuadCollectors[face]) { + 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.getKey(), aabb, buff, offsets); + } + + + private void generateMeshForAxis(WorldSection section, int axisId) { + int aX = axisId==2?1:0; + int aY = axisId==0?1:0; + int aZ = axisId==1?1:0; + + //Note the way the connectedSectionCache works is that it reuses the section cache because we know we dont need the connectedSection + // when we are on the other direction + boolean obtainedOppositeSection0 = false; + boolean obtainedOppositeSection31 = false; + + + for (int primary = 0; primary < 32; primary++) { + this.negativeMesher.reset(); + this.positiveMesher.reset(); + + for (int a = 0; a < 32; a++) { + for (int b = 0; b < 32; b++) { + int x = axisId==2?primary:a; + int y = axisId==0?primary:(axisId==1?b:a); + int z = axisId==1?primary:b; + long self = this.sectionCache[WorldSection.getIndex(x,y,z)]; + if (Mapper.isAir(self)) continue; + + int selfBlockId = Mapper.getBlockId(self); + long selfMetadata = this.modelMan.getModelMetadata(selfBlockId); + + boolean putFace = false; + + //Branch into 2 paths, the + direction and -direction, doing it at once makes it much faster as it halves the number of loops + + if (ModelManager.faceExists(selfMetadata, axisId<<1)) {//- direction + long facingState = Mapper.AIR; + //Need to access the other connecting section + if (primary == 0) { + if (!obtainedOppositeSection0) { + var connectedSection = this.world.acquire(section.lvl, section.x - aX, section.y - aY, section.z - aZ); + connectedSection.copyDataTo(this.connectedSectionCache); + connectedSection.release(); + obtainedOppositeSection0 = true; + } + facingState = this.connectedSectionCache[WorldSection.getIndex(x*(1-aX)+(31*aX), y*(1-aY)+(31*aY), z*(1-aZ)+(31*aZ))]; + } else { + facingState = this.sectionCache[WorldSection.getIndex(x-aX, y-aY, z-aZ)]; + } + + putFace |= this.putFaceIfCan(this.negativeMesher, (axisId<<1), (axisId<<1)|1, self, selfMetadata, selfBlockId, facingState, a, b); + } + if (ModelManager.faceExists(selfMetadata, axisId<<1)) {//+ direction + long facingState = Mapper.AIR; + //Need to access the other connecting section + if (primary == 31) { + if (!obtainedOppositeSection31) { + var connectedSection = this.world.acquire(section.lvl, section.x + aX, section.y + aY, section.z + aZ); + connectedSection.copyDataTo(this.connectedSectionCache); + connectedSection.release(); + obtainedOppositeSection31 = true; + } + facingState = this.connectedSectionCache[WorldSection.getIndex(x*(1-aX), y*(1-aY), z*(1-aZ))]; + } else { + facingState = this.sectionCache[WorldSection.getIndex(x+aX, y+aY, z+aZ)]; + } + + putFace |= this.putFaceIfCan(this.positiveMesher, (axisId<<1)|1, (axisId<<1), self, selfMetadata, selfBlockId, facingState, a, b); + } + + if (putFace) { + this.minX = Math.min(this.minX, x); + this.minY = Math.min(this.minY, y); + this.minZ = Math.min(this.minZ, z); + this.maxX = Math.max(this.maxX, x); + this.maxY = Math.max(this.maxY, y); + this.maxZ = Math.max(this.maxZ, z); + } + } + } + + processMeshedFace(this.negativeMesher, axisId<<1, primary, this.directionalQuadCollectors[(axisId<<1)]); + processMeshedFace(this.positiveMesher, (axisId<<1)|1, primary, this.directionalQuadCollectors[(axisId<<1)|1]); + } + } + + //Returns true if a face was placed + private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfBlockId, long facingState, int a, int b) { + long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState)); + + //If face can be occluded and is occluded from the facing block, then dont render the face + if (ModelManager.faceCanBeOccluded(metadata, face) && ModelManager.faceOccludes(facingMetadata, opposingFace)) { + return false; + } + + if (ModelManager.isTranslucent(metadata) && selfBlockId == Mapper.getBlockId(facingState)) { + //If we are facing a block, and are translucent and it is the same block as us, cull the quad + return false; + } + + int clientModelId = this.modelMan.getModelId(selfBlockId); + long otherFlags = 0; + otherFlags |= ModelManager.isTranslucent(metadata)?1L<<33:0; + otherFlags |= ModelManager.isDoubleSided(metadata)?1L<<34:0; + mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(facingState))<<16) | (((long) Mapper.getBiomeId(self))<<24) | otherFlags); + return true; + } + + private void processMeshedFace(Mesher2D mesher, int face, int otherAxis, LongArrayList axisOutputGeometry) { + //TODO: encode translucents and double sided quads to different global buffers + + int count = mesher.process(); + var array = mesher.getArray(); + for (int i = 0; i < count; i++) { + int quad = array[i]; + long data = mesher.getDataFromQuad(quad); + long encodedQuad = Integer.toUnsignedLong(QuadEncoder.encodePosition(face, otherAxis, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46); + + + if ((data&(1L<<33))!=0) { + this.translucentQuadCollector.add(encodedQuad); + } else if ((data&(1L<<34))!=0) { + this.doubleSidedQuadCollector.add(encodedQuad); + } else { + axisOutputGeometry.add(encodedQuad); + } + } + } +} + + +/* + private static long encodeRaw(int face, int width, int height, int x, int y, int z, int blockId, int biomeId, int lightId) { + return ((long)face) | (((long) width)<<3) | (((long) height)<<7) | (((long) z)<<11) | (((long) y)<<16) | (((long) x)<<21) | (((long) blockId)<<26) | (((long) biomeId)<<46) | (((long) lightId)<<55); + } + */ + + +/* + + //outData.clear(); + + //var buff = new MemoryBuffer(8*8); + //MemoryUtil.memPutLong(buff.address, encodeRaw(2, 0,1,0,0,0,159,0, 0));//92 515 + //MemoryUtil.memPutLong(buff.address+8, encodeRaw(3, 0,1,0,0,0,159,0, 0));//92 515 + //MemoryUtil.memPutLong(buff.address+16, encodeRaw(4, 1,2,0,0,0,159,0, 0));//92 515 + //MemoryUtil.memPutLong(buff.address+24, encodeRaw(5, 1,2,0,0,0,159,0, 0));//92 515 + //MemoryUtil.memPutLong(buff.address+32, encodeRaw(2, 0,1,0,0,1,159,0, 0));//92 515 + //MemoryUtil.memPutLong(buff.address+40, encodeRaw(3, 0,1,0,0,1,159,0, 0));//92 515 + //MemoryUtil.memPutLong(buff.address+48, encodeRaw(2, 0,1,0,0,2,159,0, 0));//92 515 + //MemoryUtil.memPutLong(buff.address+56, encodeRaw(3, 0,1,0,0,2,159,0, 0));//92 515 + + //int modelId = this.modelMan.getModelId(this.world.getMapper().getIdFromBlockState(Blocks.OAK_FENCE.getDefaultState())); + //int modelId = this.modelMan.getModelId(this.world.getMapper().getIdFromBlockState(Blocks.OAK_FENCE.getDefaultState())); + + //outData.add(encodeRaw(0, 0,0,0,0,0, modelId,0, 0)); + //outData.add(encodeRaw(1, 0,0,0,0,0, modelId,0, 0)); + //outData.add(encodeRaw(2, 0,0,0,0,0, modelId,0, 0)); + //outData.add(encodeRaw(3, 0,0,0,0,0, modelId,0, 0)); + //outData.add(encodeRaw(4, 0,0,0,0,0, modelId,0, 0)); + //outData.add(encodeRaw(5, 0,0,0,0,0, modelId,0, 0)); + */ + + + +/* + + Mesher2D mesher = new Mesher2D(5,15); LongArrayList outData = new LongArrayList(1000); @@ -282,99 +517,4 @@ public class RenderDataFactory { outData.add(Integer.toUnsignedLong(QuadEncoder.encodePosition(5, x, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46)); } } - - - - - - //var buff = new MemoryBuffer(8*8); - //MemoryUtil.memPutLong(buff.address, encodeRaw(2, 0,1,0,0,0,159,0, 0));//92 515 - //MemoryUtil.memPutLong(buff.address+8, encodeRaw(3, 0,1,0,0,0,159,0, 0));//92 515 - //MemoryUtil.memPutLong(buff.address+16, encodeRaw(4, 1,2,0,0,0,159,0, 0));//92 515 - //MemoryUtil.memPutLong(buff.address+24, encodeRaw(5, 1,2,0,0,0,159,0, 0));//92 515 - //MemoryUtil.memPutLong(buff.address+32, encodeRaw(2, 0,1,0,0,1,159,0, 0));//92 515 - //MemoryUtil.memPutLong(buff.address+40, encodeRaw(3, 0,1,0,0,1,159,0, 0));//92 515 - //MemoryUtil.memPutLong(buff.address+48, encodeRaw(2, 0,1,0,0,2,159,0, 0));//92 515 - //MemoryUtil.memPutLong(buff.address+56, encodeRaw(3, 0,1,0,0,2,159,0, 0));//92 515 - if (outData.isEmpty()) { - return new BuiltSection(section.getKey()); - } - //outData.clear(); - - //int modelId = this.modelMan.getModelId(this.world.getMapper().getIdFromBlockState(Blocks.OAK_FENCE.getDefaultState())); - int modelId = this.modelMan.getModelId(this.world.getMapper().getIdFromBlockState(Blocks.OAK_FENCE.getDefaultState())); - - //outData.add(encodeRaw(0, 0,0,0,0,0, modelId,0, 0)); - //outData.add(encodeRaw(1, 0,0,0,0,0, modelId,0, 0)); - //outData.add(encodeRaw(2, 0,0,0,0,0, modelId,0, 0)); - //outData.add(encodeRaw(3, 0,0,0,0,0, modelId,0, 0)); - //outData.add(encodeRaw(4, 0,0,0,0,0, modelId,0, 0)); - //outData.add(encodeRaw(5, 0,0,0,0,0, modelId,0, 0)); - - var buff = new MemoryBuffer(outData.size()*8L); - long ptr = buff.address; - for (long data : outData) { - MemoryUtil.memPutLong(ptr, data); ptr+=8; - } - - return new BuiltSection(section.getKey(), (31<<15)|(31<<20)|(31<<25), buff, new int[]{0, outData.size(), outData.size(), outData.size(), outData.size(), outData.size(), outData.size(), outData.size()}); - } - - - private static long encodeRaw(int face, int width, int height, int x, int y, int z, int blockId, int biomeId, int lightId) { - return ((long)face) | (((long) width)<<3) | (((long) height)<<7) | (((long) z)<<11) | (((long) y)<<16) | (((long) x)<<21) | (((long) blockId)<<26) | (((long) biomeId)<<46) | (((long) lightId)<<55); - } - - - /* - private void generateMeshForAxis() { - - //Up direction - for (int y = 0; y < 32; y++) { - mesher.reset(); - for (int x = 0; x < 32; x++) { - for (int z = 0; z < 32; z++) { - long self = this.sectionCache[WorldSection.getIndex(x, y, z)]; - if (Mapper.isAir(self)) continue; - - int selfBlockId = Mapper.getBlockId(self); - long metadata = this.modelMan.getModelMetadata(selfBlockId); - - //If the model doesnt have a face, then just skip it - if (!ModelManager.faceExists(metadata, 1)) { - continue; - } - - long facingState = Mapper.AIR; - //Need to access the other connecting section - if (y == 31) { - - } else { - facingState = this.sectionCache[WorldSection.getIndex(x, y+1, z)]; - } - - long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState)); - - //If face can be occluded and is occluded from the facing block, then dont render the face - if (ModelManager.faceCanBeOccluded(metadata, 1) && ModelManager.faceOccludes(facingMetadata, 0)) { - continue; - } - - int clientModelId = this.modelMan.getModelId(selfBlockId); - - mesher.put(x, z, ((long)clientModelId) | (((long) Mapper.getLightId(facingState))<<16) | (((long) Mapper.getBiomeId(self))<<24)); - } - } - - //TODO: encode translucents and double sided quads to different global buffers - int count = mesher.process(); - var array = mesher.getArray(); - for (int i = 0; i < count; i++) { - int quad = array[i]; - long data = mesher.getDataFromQuad(quad); - outData.add(Integer.toUnsignedLong(QuadEncoder.encodePosition(1, y, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46)); - } - } - }*/ - -} + */ \ No newline at end of file diff --git a/src/main/resources/assets/zenith/shaders/lod/gl46/cmdgen.comp b/src/main/resources/assets/zenith/shaders/lod/gl46/cmdgen.comp index 3ddb5f6a..3ee62f7b 100644 --- a/src/main/resources/assets/zenith/shaders/lod/gl46/cmdgen.comp +++ b/src/main/resources/assets/zenith/shaders/lod/gl46/cmdgen.comp @@ -7,6 +7,7 @@ layout(local_size_x = 128, local_size_y = 1, local_size_x = 1) in; #import #import #import +#line 11 //https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_16bit_storage.txt // adds support for uint8_t which can use for compact visibility buffer @@ -27,7 +28,16 @@ uint encodeLocalLodPos(uint detail, ivec3 pos) { } -//TODO: swap to a multidraw indirect counted +//Note: if i want reverse indexing i need to use the index buffer offset to offset +void writeCmd(uint idx, uint encodedPos, uint offset, uint quadCount) { + DrawCommand cmd; + cmd.count = quadCount * 6; + cmd.instanceCount = 1; + cmd.firstIndex = 0; + cmd.baseVertex = int(offset)<<2; + cmd.baseInstance = encodedPos; + cmdBuffer[idx] = cmd; +} void main() { if (gl_GlobalInvocationID.x >= sectionCount) { @@ -55,14 +65,76 @@ void main() { } if (shouldRender) { - uint basePtr = extractQuadStart(meta); + uint encodedPos = encodeLocalLodPos(detail, ipos); + uint ptr = extractQuadStart(meta); + ivec3 relative = ipos-(baseSectionPos>>detail); - DrawCommand cmd; - cmd.count = (meta.cntA&0xFFFF) * 6; - cmd.instanceCount = 1; - cmd.firstIndex = 0; - cmd.baseVertex = int(basePtr)<<2; - cmd.baseInstance = encodeLocalLodPos(detail, ipos); - cmdBuffer[atomicAdd(opaqueDrawCount, 1)] = cmd; + + + //TODO:FIXME: Figure out why these are in such a weird order + uint msk = 0; + msk |= uint(relative.y>-1)<<0; + msk |= uint(relative.y<1 )<<1; + msk |= uint(relative.z>-1)<<2; + msk |= uint(relative.z<1 )<<3; + msk |= uint(relative.x>-1)<<4; + msk |= uint(relative.x<1 )<<5; + + + uint cmdPtr = atomicAdd(opaqueDrawCount, bitCount(msk)+1); + + + uint count = 0; + //Translucency + count = meta.cntA&0xFFFF; + + ptr += count; + + //Double sided quads + count = (meta.cntA>>16)&0xFFFF; + writeCmd(cmdPtr++, encodedPos, ptr, count); + ptr += count; + + //Down + count = (meta.cntB)&0xFFFF; + if ((msk&(1<<0))!=0) { + writeCmd(cmdPtr++, encodedPos, ptr, count); + } + ptr += count; + + //Up + count = (meta.cntB>>16)&0xFFFF; + if ((msk&(1<<1))!=0) { + writeCmd(cmdPtr++, encodedPos, ptr, count); + } + ptr += count; + + //North + count = (meta.cntC)&0xFFFF; + if ((msk&(1<<2))!=0) { + writeCmd(cmdPtr++, encodedPos, ptr, count); + } + ptr += count; + + //South + count = (meta.cntC>>16)&0xFFFF; + if ((msk&(1<<3))!=0) { + writeCmd(cmdPtr++, encodedPos, ptr, count); + } + ptr += count; + + //West + count = (meta.cntD)&0xFFFF; + if ((msk&(1<<4))!=0) { + writeCmd(cmdPtr++, encodedPos, ptr, count); + } + ptr += count; + + //East + count = (meta.cntD>>16)&0xFFFF; + if ((msk&(1<<5))!=0) { + writeCmd(cmdPtr++, encodedPos, ptr, count); + } + ptr += count; } } \ No newline at end of file