diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/HierarchicalOcclusionRenderer.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/HierarchicalOcclusionRenderer.java deleted file mode 100644 index 4b759303..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/HierarchicalOcclusionRenderer.java +++ /dev/null @@ -1,165 +0,0 @@ -package me.cortex.voxy.client.core.rendering.hierarchical; - -import me.cortex.voxy.client.core.gl.GlBuffer; -import me.cortex.voxy.client.core.gl.shader.PrintfInjector; -import me.cortex.voxy.client.core.gl.shader.Shader; -import me.cortex.voxy.client.core.gl.shader.ShaderType; -import me.cortex.voxy.client.core.rendering.Viewport; -import me.cortex.voxy.client.core.rendering.util.HiZBuffer; -import me.cortex.voxy.client.core.rendering.util.UploadStream; -import net.minecraft.util.math.MathHelper; -import org.joml.Matrix4f; -import org.joml.Vector3f; -import org.lwjgl.system.MemoryUtil; - -import static org.lwjgl.opengl.ARBDirectStateAccess.nglClearNamedBufferSubData; -import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; -import static org.lwjgl.opengl.GL30.GL_R32UI; -import static org.lwjgl.opengl.GL30.glBindBufferBase; -import static org.lwjgl.opengl.GL33.glBindSampler; -import static org.lwjgl.opengl.GL33.glGenSamplers; -import static org.lwjgl.opengl.GL43.*; -import static org.lwjgl.opengl.GL45.glBindTextureUnit; - -public class HierarchicalOcclusionRenderer { - private final HiZBuffer hiz = new HiZBuffer(); - - private final int hizSampler = glGenSamplers(); - - public final NodeManager nodeManager; - private final Shader hierarchicalTraversal; - private final PrintfInjector printf; - - private final GlBuffer nodeQueueA; - private final GlBuffer nodeQueueB; - private final GlBuffer uniformBuffer; - - public HierarchicalOcclusionRenderer(INodeInteractor interactor, MeshManager mesh, PrintfInjector printf) { - this.nodeManager = new NodeManager(interactor, mesh); - this.nodeQueueA = new GlBuffer(1000000*4+4).zero(); - this.nodeQueueB = new GlBuffer(1000000*4+4).zero(); - this.uniformBuffer = new GlBuffer(1024).zero(); - this.printf = printf; - this.hierarchicalTraversal = Shader.make(printf) - .define("IS_DEBUG") - .add(ShaderType.COMPUTE, "voxy:lod/hierarchical/traversal.comp") - .compile(); - } - - - private void uploadUniform(Viewport viewport) { - long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024); - int sx = MathHelper.floor(viewport.cameraX)>>5; - int sy = MathHelper.floor(viewport.cameraY)>>5; - int sz = MathHelper.floor(viewport.cameraZ)>>5; - - new Matrix4f(viewport.projection).mul(viewport.modelView).getToAddress(ptr); ptr += 4*4*4; - - MemoryUtil.memPutInt(ptr, sx); ptr += 4; - MemoryUtil.memPutInt(ptr, sy); ptr += 4; - MemoryUtil.memPutInt(ptr, sz); ptr += 4; - MemoryUtil.memPutInt(ptr, viewport.width); ptr += 4; - - var innerTranslation = new Vector3f((float) (viewport.cameraX-(sx<<5)), (float) (viewport.cameraY-(sy<<5)), (float) (viewport.cameraZ-(sz<<5))); - innerTranslation.getToAddress(ptr); ptr += 4*3; - - MemoryUtil.memPutInt(ptr, viewport.height); ptr += 4; - - MemoryUtil.memPutInt(ptr, NodeManager.REQUEST_QUEUE_SIZE); ptr += 4; - MemoryUtil.memPutInt(ptr, 1000000); ptr += 4; - - //decendSSS (decend screen space size) - MemoryUtil.memPutFloat(ptr, 64*64); ptr += 4; - } - - public void doHierarchicalTraversalSelection(Viewport viewport, int depthBuffer, GlBuffer renderSelectionResult, GlBuffer debugNodeOutput) { - this.uploadUniform(viewport); - this.nodeManager.upload(); - - { - int cnt = this.nodeManager.rootPos2Id.size(); - long ptr = UploadStream.INSTANCE.upload(this.nodeQueueA, 0, 4+cnt*4L); - MemoryUtil.memPutInt(ptr, cnt); ptr += 4; - for (int i : this.nodeManager.rootPos2Id.values()) { - MemoryUtil.memPutInt(ptr, i); ptr += 4; - } - } - - - UploadStream.INSTANCE.commit(); - - //FIXME: need to have the hiz respect the stencil mask aswell to mask away normal terrain, (much increase perf) - - //Make hiz - this.hiz.buildMipChain(depthBuffer, viewport.width, viewport.height); - glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT); - this.hierarchicalTraversal.bind(); - - //Clear the render counter - nglClearNamedBufferSubData(renderSelectionResult.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); - nglClearNamedBufferSubData(debugNodeOutput.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); - - { - glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.nodeManager.nodeBuffer.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueA.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.nodeManager.requestQueue.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, renderSelectionResult.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueB.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, debugNodeOutput.id); - - //Bind the hiz buffer - glBindSampler(0, this.hizSampler); - glBindTextureUnit(0, this.hiz.getHizTextureId()); - } - this.printf.bind(); - { - //Dispatch hierarchies - nglClearNamedBufferSubData(this.nodeQueueB.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueA.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueB.id); - glDispatchCompute(21*21*2,1,1); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - nglClearNamedBufferSubData(this.nodeQueueA.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueB.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueA.id); - glDispatchCompute(21*21*8,1,1); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - nglClearNamedBufferSubData(this.nodeQueueB.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueA.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueB.id); - glDispatchCompute(21*21*32,1,1); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - nglClearNamedBufferSubData(this.nodeQueueA.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueB.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueA.id); - glDispatchCompute(21*21*128,1,1); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - nglClearNamedBufferSubData(this.nodeQueueB.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueA.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueB.id); - glDispatchCompute(21*21*8,1,1); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - } - - glBindSampler(0, 0); - glBindTextureUnit(0, 0); - this.nodeManager.download(); - } - - public void free() { - this.nodeQueueA.free(); - this.nodeQueueB.free(); - this.hiz.free(); - this.nodeManager.free(); - glDeleteSamplers(this.hizSampler); - } - - public GlBuffer getNodeDataBuffer() { - return this.nodeManager.nodeBuffer; - } -} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/INodeInteractor.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/INodeInteractor.java deleted file mode 100644 index 51e78aec..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/INodeInteractor.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.cortex.voxy.client.core.rendering.hierarchical; - -import me.cortex.voxy.client.core.rendering.building.BuiltSection; - -import java.util.function.Consumer; - -//Interface for node manager to interact with the outside world -public interface INodeInteractor { - void watchUpdates(long pos);//marks pos as watching for updates, i.e. any LoD updates will trigger a callback - void unwatchUpdates(long pos);//Unmarks a position for updates - - void requestMesh(long pos);//Explicitly requests a mesh at a position, run the callback - - void setMeshUpdateCallback(Consumer mesh); -} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/ITrimInterface.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/ITrimInterface.java deleted file mode 100644 index f69118a1..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/ITrimInterface.java +++ /dev/null @@ -1,12 +0,0 @@ -package me.cortex.voxy.client.core.rendering.hierarchical; - -public interface ITrimInterface { - //Last recorded/known use time of a nodes mesh, returns -1 if node doesnt have a mesh - int lastUsedTime(int node); - - //Returns an integer with the bottom 24 bits being the ptr top 8 bits being count or something - int getChildren(int node); - - //Returns a size of the nodes mesh, -1 if the node doesnt have a mesh - int getNodeSize(int node); -} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/MeshManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/MeshManager.java deleted file mode 100644 index 1ff4b025..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/MeshManager.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.cortex.voxy.client.core.rendering.hierarchical; - -import me.cortex.voxy.client.core.gl.GlBuffer; -import me.cortex.voxy.client.core.rendering.building.BuiltSection; -import me.cortex.voxy.client.core.rendering.util.BufferArena; - -//Manages the actual meshes, whether they are meshlets or whole meshes, the returned values are ids into a section -// array which contains metadata about the section -public class MeshManager { - private final BufferArena geometryArena; - private final GlBuffer sectionMetaBuffer; - - public MeshManager() { - this.geometryArena = null; - this.sectionMetaBuffer = null; - } - - //Uploads the section geometry to the arena, there can be multiple meshes for the same geometry in the arena at the same time - // it is not the MeshManagers responsiblity - //The return value is arbitary as long as it can identify the mesh its uploaded until it is freed - public int uploadMesh(BuiltSection section) { - return uploadReplaceMesh(-1, section); - } - - //Varient of uploadMesh that releases the previous mesh at the same time, this is a performance optimization - public int uploadReplaceMesh(int old, BuiltSection section) { - section.free(); - return 1; - } - - public void removeMesh(int mesh) { - - } - - public void free() { - - } -} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeLoadSystem.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeLoadSystem.java deleted file mode 100644 index e344a4ba..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeLoadSystem.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.cortex.voxy.client.core.rendering.hierarchical; - -//Uses a persistently mapped coherient buffer with off thread polling to pull in requests -public class NodeLoadSystem { -} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager.java deleted file mode 100644 index 1266f647..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager.java +++ /dev/null @@ -1,510 +0,0 @@ -package me.cortex.voxy.client.core.rendering.hierarchical; - -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import me.cortex.voxy.client.core.gl.GlBuffer; -import me.cortex.voxy.client.core.rendering.building.BuiltSection; -import me.cortex.voxy.client.core.rendering.util.DownloadStream; -import me.cortex.voxy.client.core.rendering.util.UploadStream; -import me.cortex.voxy.common.util.HierarchicalBitSet; -import me.cortex.voxy.common.world.WorldEngine; -import org.lwjgl.system.MemoryUtil; - -import java.util.Arrays; - -import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; -import static org.lwjgl.opengl.GL30.GL_R32UI; -import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER; -import static org.lwjgl.opengl.GL45.nglClearNamedBufferSubData; - - -//TODO:FIXME: TODO, Must fix/have some filtering for section updates based on time or something -// as there can be a cursed situation where an update occures requiring expensive meshing for a section but then in the same -// tick it becomes air requiring no meshing thus basicly instantly emitting a result - - -//TODO: Make it an sparse voxel octree, that is if all of the children bar 1 are empty/air, remove the self node and replace it with the non empty child - -public class NodeManager { - //A request for making a new child nodes - private static final class LeafRequest { - //LoD position identifier - public long position; - - //Node id of the node the leaf request is for, note! While there is a leaf request, the node should not be unloaded or removed - public int nodeId; - - //The mask of what child nodes are required - //public byte requiredChildMask; - - //The mask of currently supplied child node data - public byte currentChildMask; - - //Mesh ids for all the child nodes - private final int[] meshIds = new int[8]; - {Arrays.fill(this.meshIds, -1);} - - //Positions for all the child nodes, should make SVO easier - private final long[] childPositions = new long[8]; - {Arrays.fill(this.childPositions, -1);} - - - - //Reset/clear the request so that it may be reused - public void clear() { - this.position = 0; - this.nodeId = 0; - //this.requiredChildMask = 0; - this.currentChildMask = 0; - Arrays.fill(this.meshIds, -1); - Arrays.fill(this.childPositions, -1); - } - - //Returns true if the request is satisfied - public boolean isSatisfied() { - return this.currentChildMask == -1;//(this.requiredChildMask&this.currentChildMask)==this.requiredChildMask; - } - - public int getMeshId(int inner) { - if (!this.isSet(inner)) { - return -1; - } - return this.meshIds[inner]; - } - - public boolean isSet(int inner) { - return (this.currentChildMask&(1<>>32)&NODE_MSK); - } - - private int getNodeChildCnt(int node) { - return (int) ((this.localNodeData[node*3+1]>>>61)&7)+1; - } - - private long getNodePos(int node) { - return this.localNodeData[node*3]; - } - - private boolean isLeafNode(int node) { - //TODO: maybe make this flag based instead of checking the child ptr? - return this.getNodeChildPtr(node) == NODE_MSK; - } - - //Its ment to return if the node is just an empty mesh or if all the children are also empty - private boolean isEmptyNode(int node) { - return this.getNodeMesh(node)==EMPTY_MESH_ID;//Special case/reserved - } - - private void setNodePosition(int node, long position) { - this.localNodeData[node*3] = position; - } - - private void setMeshId(int node, int mesh) { - if (mesh > MESH_MSK) { - throw new IllegalArgumentException(); - } - long val = this.localNodeData[node*3+1]; - val &= ~MESH_MSK; - val |= mesh; - this.localNodeData[node*3+1] = val; - } - - private void setChildPtr(int node, int childPtr, int count) { - if (childPtr > NODE_MSK || ((childPtr!=NODE_MSK)&&count < 1)) { - throw new IllegalArgumentException(); - } - long val = this.localNodeData[node*3+1]; - //Put the count - val &= ~(0x7L<<61); - val |= Integer.toUnsignedLong(Math.max(count-1,0))<<61; - //Put the child ptr - val &= ~(Integer.toUnsignedLong(NODE_MSK)<<32); - val |= Integer.toUnsignedLong(childPtr) << 32; - this.localNodeData[node*3+1] = val; - } - - - - - - private static long makeChildPos(long basePos, int addin) { - int lvl = WorldEngine.getLevel(basePos); - if (lvl == 0) { - throw new IllegalArgumentException("Cannot create a child lower than lod level 0"); - } - return WorldEngine.getWorldSectionId(lvl-1, - (WorldEngine.getX(basePos)<<1)|(addin&1), - (WorldEngine.getY(basePos)<<1)|((addin>>1)&1), - (WorldEngine.getZ(basePos)<<1)|((addin>>2)&1)); - } - - - - //The idea is, since a graph node can be in effectivly only 3 states, if inner node -> may or may not have mesh, and, if leaf node -> has mesh, no children - // the request queue only needs to supply the node id, since if its an inner node, it must be requesting for a mesh, while if its a leaf node, it must be requesting for children - private void processRequestQueue(long ptr, long size) { - int count = MemoryUtil.memGetInt(ptr); ptr += 4; - if (count > REQUEST_QUEUE_SIZE*1.5) { - System.err.println("CORRUPTED PROCESS REQUEST, IGNORING (had count of: "+count+")"); - return; - } - - - for (int i = 0; i < count; i++) { - int requestOp = MemoryUtil.memGetInt(ptr + i*4L); - int node = requestOp&NODE_MSK; - //System.out.println("Got request for node: " + node); - if (WorldEngine.getLevel(this.getNodePos(node)) == 0) { - System.err.println("Got a request for node at level 0: " + node + " pos: " + this.getNodePos(node)); - continue; - } - if (this.isLeafNode(node)) { - //If its a leaf node and it has a request, it must need the children - if (this.getNodeMesh(node) == -1) { - throw new IllegalStateException("Leaf node doesnt have mesh"); - } - //Create a new request - int idx = this.leafRequests.allocate(); - var request = this.leafRequests.get(idx); - - { - long nodePos = this.getNodePos(node); - request.make(node, nodePos);//Request all child nodes - int requestIdx = idx|(1<<31);//First bit is set to 1 to indicate a request index instead of a node index - - //Loop over all child positions and insert them into the queue - for (int j = 0; j < 8; j++) { - long child = makeChildPos(nodePos, j); - int prev = this.pos2meshId.putIfAbsent(child, requestIdx); - if (prev != NO_NODE) { - throw new IllegalArgumentException("Node pos already in request map"); - } - //Mark the position as watching and force request an update - this.interactor.watchUpdates(child); - this.interactor.requestMesh(child); - } - } - //NOTE: dont unmark the node yet, as the request hasnt been satisfied - - } else { - //If its not a leaf node, it must be missing the inner mesh so request it - if (this.getNodeMesh(node) != MESH_MSK) { - //Node already has a mesh, ignore it, but might be a sign that an error has occured - throw new IllegalStateException("Requested a mesh for node, however the node already has a mesh"); - - //TODO: need to unmark the node that requested it, either that or only clear node data when a mesh has been removed - - } else { - //Put it into the map + watch and request - long pos = this.getNodePos(node); - long prev = this.pos2meshId.putIfAbsent(pos, node); - if (prev != NO_NODE) { - throw new IllegalStateException("Pos already has a node id attached"); - } - this.interactor.watchUpdates(pos); - this.interactor.requestMesh(pos); - } - } - } - } - - - - - //Tracking for nodes that specifically need meshes, if a node doesnt have or doesnt need a mesh node, it is not in the map - // the map should be identical to the currently watched set of sections - //NOTE: that if the id is negative its part of a mesh request - private final Long2IntOpenHashMap pos2meshId = new Long2IntOpenHashMap(); - private final LongOpenHashSet rootPosRequests = new LongOpenHashSet();// - private static final int NO_NODE = -1; - - //The request queue should be like some array that can reuse objects to prevent gc nightmare + like a bitset to find an avalible free slot - // hashmap might work bar the gc overhead - private final MarkedCachedObjectList leafRequests = new MarkedCachedObjectList<>(LeafRequest[]::new, LeafRequest::new); - - private static int pos2octnode(long pos) { - return (WorldEngine.getX(pos)&1)|((WorldEngine.getY(pos)&1)<<1)|((WorldEngine.getZ(pos)&1)<<2); - } - - //TODO: if all the children of a node become empty/removed traverse up the chain until a non empty parent node is hit and - // remove all from the chain - - - - - - - //TODO: FIXME: CRITICAL: if a section is empty when created, it wont get allocated a slot, however, the section might - // become unempty due to an update!!! THIS IS REALLY BAD. since it doesnt have an allocation - - //TODO: test and fix the possible race condition of if a section is not empty then becomes empty in the same tick - // that is, there is a request that is satisfied bar 1 section, that section is supplied as non emptpty but then becomes empty in the same tick - - //TODO: Fixme: need to fix/make it so that the system can know if every child (to lod0) is empty or if its just the current section - private void meshUpdate(BuiltSection mesh) { - int id = this.pos2meshId.get(mesh.position); - //TODO: FIXME!! if we get a node that has an update and is watched but no id for it, it could be an update state from - // an empty node to non empty node, this means we need to invalidate all the childrens positions and move them! - // then also update the parent pointer - //TODO: Also need a way to remove sections, requires shuffling stuff around - if (id == NO_NODE) { - //If its a top level node insertion request, insert the node - if (this.rootPosRequests.remove(mesh.position)) { - if (!mesh.isEmpty()) { - int top = this.nodeAllocations.allocateNext(); - this.rootPos2Id.put(mesh.position, top); - this.setNodePosition(top, mesh.position); - this.setChildPtr(top, NODE_MSK, 0); - this.setMeshId(top, this.meshManager.uploadMesh(mesh)); - this.pushNode(top); - } - } else { - //The built mesh section is no longer needed, discard it - // TODO: could probably?? cache the mesh in ram that way if its requested? it can be immediatly fetched while a newer mesh is built?? - //This might be a warning? or maybe info? - mesh.free(); - } - return; - } - - if ((id&(1<<31))!=0) { - //The mesh is part of a batched request - id = id^(1<<31);//Basically abs it - int innerId = pos2octnode(mesh.position); - - //There are a few cases for this branch - // the section could be replacing an existing mesh that is part of the request (due to an update) - // the section mesh could be new to the request - // in this case the section mesh could be the last entry needed to satisfy the request - // in which case! we must either A) mark the request as ready to be uploaded - // and then uploaded after all the mesh updates are processed, or upload it immediately - - LeafRequest request = this.leafRequests.get(id); - - //TODO: Get the mesh id if a mesh for the request at the same pos has already been submitted - // then call meshManager.uploadReplaceMesh to get the new id, then put that into the request - //TODO: could basicly make it a phase, where it then enqueues finished requests that then get uploaded later - // that is dont immediatly submit request results, wait until the end of the frame - // NOTE: COULD DO THIS WITH MESH RESULTS TOO, or could prefilter them per batch/tick - int meshId; - int prevMeshId = request.getMeshId(innerId); - if (mesh.isEmpty()) { - //since its empty, remove the previous mesh if it existed - if (prevMeshId != -1) { - this.meshManager.removeMesh(prevMeshId); - } - meshId = -1;//FIXME: this is a hack to still result in the mesh being put in, but it is an empty mesh upload - } else { - if (prevMeshId != -1) { - meshId = this.meshManager.uploadReplaceMesh(prevMeshId, mesh); - } else { - meshId = this.meshManager.uploadMesh(mesh); - } - } - - request.put(innerId, meshId, mesh.position); - - if (request.isSatisfied()) { - //If request is now satisfied update the internal nodes, create the children and reset + release the request set - this.completeLeafRequest(request); - - //Reset + release - request.clear(); - this.leafRequests.release(id); - } - //If the request is not yet satisfied, that is ok, continue ingesting new meshes until it is satisfied - - - } else { - //The mesh is an update for an existing node - - //Sanity check - if (this.getNodePos(id) != mesh.position) { - throw new IllegalStateException("Node position not same as mesh position"); - } - - int prevMesh = this.getNodeMesh(id); - // TODO: If the mesh to upload is air, the node should be removed (however i believe this is only true if all the children are air! fuuuuu) - if (prevMesh != -1) { - //Node has a mesh attached, remove and replace it - this.setMeshId(id, this.meshManager.uploadReplaceMesh(prevMesh, mesh)); - } else { - //Node didnt have a mesh attached, so just set the current mesh - this.setMeshId(id, this.meshManager.uploadMesh(mesh)); - } - //Push the updated node to the gpu - this.pushNode(id); - } - } - - - private void completeLeafRequest(LeafRequest request) { - //TODO: FIXME: need to make it so that if a nodes mesh is empty but there are children that exist that arnt empty - // then it needs to still add the node, but just with an empty mesh flag - int msk = Byte.toUnsignedInt(request.nonAirMask()); - int baseIdx = this.nodeAllocations.allocateNextConsecutiveCounted(Integer.bitCount(msk)); - int cnt = 0; - for (int i = 0; i < 8; i++) { - if ((msk&(1<>32)); dst += 4; - MemoryUtil.memPutInt(dst, (int) pos); dst += 4; - - int flags = 0; - flags |= this.isEmptyNode(id)?2:0; - flags |= Math.max(0, this.getNodeChildCnt(id)-1)<<2; - - int a = this.getNodeMesh(id)|((flags&0xFF)<<24); - int b = this.getNodeChildPtr(id)|(((flags>>8)&0xFF)<<24); - //System.out.println("Setting mesh " + this.getNodeMesh(id) + " for node " + id); - MemoryUtil.memPutInt(dst, a); dst += 4; - MemoryUtil.memPutInt(dst, b); dst += 4; - } - - public void upload() { - for (int i = 0; i < this.nodeUpdates.size(); i++) { - int node = this.nodeUpdates.getInt(i); - long ptr = UploadStream.INSTANCE.upload(this.nodeBuffer, node*16L, 16); - this.writeNode(ptr, node); - } - if (!this.nodeUpdates.isEmpty()) { - UploadStream.INSTANCE.commit();//Cause we actually uploaded something (do it after cause it allows batch comitting thing) - this.nodeUpdates.clear(); - } - } - - public void download() { - //this.pushNode(0); - //Download the request queue then clear the counter (first 4 bytes) - DownloadStream.INSTANCE.download(this.requestQueue, this::processRequestQueue); - DownloadStream.INSTANCE.commit(); - nglClearNamedBufferSubData(this.requestQueue.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); - } - - - public void free() { - this.requestQueue.free(); - this.nodeBuffer.free(); - } - -} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/TreeTrimmer.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/TreeTrimmer.java deleted file mode 100644 index e213ad1e..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/TreeTrimmer.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.cortex.voxy.client.core.rendering.hierarchical; - -//System to determine what nodes to remove from the hericial tree while retaining the property that all -// leaf nodes should have meshes -//This system is critical to prevent the geometry buffer from growing to large or for too many nodes to fill up -// the node system -public class TreeTrimmer { - //Used to interact with the outside world - private final ITrimInterface trimInterface; - - public TreeTrimmer(ITrimInterface trimInterface) { - this.trimInterface = trimInterface; - } - - public void computeTrimPoints() { - //Do a bfs to find ending points to trim needs to be based on some, last used, metric - - //First stratagy is to compute a bfs and or generate a list of nodes sorted by last use time - // the thing is that if we cull a mesh, it cannot be a leaf node - // if it is a leaf node its parent node must have a mesh loaded - - } -}