From 306956839af3f6c2e0359960088e270a922a6422 Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Fri, 16 Aug 2024 16:22:22 +1000 Subject: [PATCH] Continue work --- .../client/core/rendering/RenderService.java | 4 +- .../building/RenderGenerationService.java | 6 +- ...Filterer.java => SectionUpdateRouter.java} | 2 +- .../hierachical2/HierarchicalNodeManager.java | 122 +++++++++++++---- .../HierarchicalOcclusionTraverser.java | 3 +- .../hierachical2/NodeChildRequest.java | 4 + .../rendering/hierachical2/NodeStore.java | 127 +++++++++++++----- .../section/BasicSectionGeometryManager.java | 3 + 8 files changed, 211 insertions(+), 60 deletions(-) rename src/main/java/me/cortex/voxy/client/core/rendering/building/{SectionPositionUpdateFilterer.java => SectionUpdateRouter.java} (98%) 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 746a9f95..a19107db 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 @@ -3,7 +3,7 @@ package me.cortex.voxy.client.core.rendering; import me.cortex.voxy.client.core.model.ModelBakerySubsystem; import me.cortex.voxy.client.core.model.ModelStore; import me.cortex.voxy.client.core.rendering.building.RenderGenerationService; -import me.cortex.voxy.client.core.rendering.building.SectionPositionUpdateFilterer; +import me.cortex.voxy.client.core.rendering.building.SectionUpdateRouter; import me.cortex.voxy.client.core.rendering.building.SectionUpdate; import me.cortex.voxy.client.core.rendering.hierachical2.HierarchicalNodeManager; import me.cortex.voxy.client.core.rendering.hierachical2.HierarchicalOcclusionTraverser; @@ -47,7 +47,7 @@ public class RenderService, J extends Vi this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<19, (1L<<30)-1024); //Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard - var positionFilterForwarder = new SectionPositionUpdateFilterer(); + var positionFilterForwarder = new SectionUpdateRouter(); this.nodeManager = new HierarchicalNodeManager(1<<21, this.sectionRenderer.getGeometryManager(), positionFilterForwarder); 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 9764557e..11800afb 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 @@ -105,8 +105,10 @@ public class RenderGenerationService { byte childMask = section.getNonEmptyChildren(); section.release(); - //Time is the time at the start of the update - this.resultConsumer.accept(new SectionUpdate(section.key, time, mesh, childMask)); + if (mesh != null) { + //Time is the time at the start of the update + this.resultConsumer.accept(new SectionUpdate(section.key, time, mesh, childMask)); + } } public void enqueueTask(int lvl, int x, int y, int z) { diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionPositionUpdateFilterer.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdateRouter.java similarity index 98% rename from src/main/java/me/cortex/voxy/client/core/rendering/building/SectionPositionUpdateFilterer.java rename to src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdateRouter.java index 40d6af4a..306c6cdb 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionPositionUpdateFilterer.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdateRouter.java @@ -6,7 +6,7 @@ import me.cortex.voxy.common.world.WorldSection; import java.util.function.LongConsumer; -public class SectionPositionUpdateFilterer { +public class SectionUpdateRouter { private static final int SLICES = 1<<2; public interface IChildUpdate {void accept(WorldSection section);} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java index 3d360880..d0aff411 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java @@ -4,7 +4,7 @@ package me.cortex.voxy.client.core.rendering.hierachical2; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import me.cortex.voxy.client.core.rendering.building.BuiltSection; -import me.cortex.voxy.client.core.rendering.building.SectionPositionUpdateFilterer; +import me.cortex.voxy.client.core.rendering.building.SectionUpdateRouter; import me.cortex.voxy.client.core.rendering.building.SectionUpdate; import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager; import me.cortex.voxy.client.core.util.ExpandingObjectAllocationList; @@ -12,15 +12,17 @@ import me.cortex.voxy.common.world.WorldEngine; import me.jellysquid.mods.sodium.client.util.MathUtil; import org.lwjgl.system.MemoryUtil; +import static me.cortex.voxy.client.core.rendering.hierachical2.NodeStore.EMPTY_GEOMETRY_ID; +import static me.cortex.voxy.client.core.rendering.hierachical2.NodeStore.NODE_ID_MSK; + //Contains no logic to interface with the gpu, nor does it contain any gpu buffers public class HierarchicalNodeManager { - public static final int NODE_MSK = ((1<<24)-1); private static final int NO_NODE = -1; private static final int SENTINAL_TOP_NODE_INFLIGHT = -2; private static final int ID_TYPE_MSK = (3<<30); private static final int ID_TYPE_NONE = 0; - private static final int ID_TYPE_LEAF = (2<<30); + private static final int ID_TYPE_REQUEST = (2<<30); private static final int ID_TYPE_TOP = (1<<30); public final int maxNodeCount; @@ -33,9 +35,9 @@ public class HierarchicalNodeManager { private final ExpandingObjectAllocationList requests = new ExpandingObjectAllocationList<>(NodeChildRequest[]::new); private final AbstractSectionGeometryManager geometryManager; - private final SectionPositionUpdateFilterer updateFilterer; + private final SectionUpdateRouter updateRouter; - public HierarchicalNodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, SectionPositionUpdateFilterer updateFilterer) { + public HierarchicalNodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, SectionUpdateRouter updateRouter) { if (!MathUtil.isPowerOfTwo(maxNodeCount)) { throw new IllegalArgumentException("Max node count must be a power of 2"); } @@ -43,7 +45,7 @@ public class HierarchicalNodeManager { throw new IllegalArgumentException("Max node count cannot exceed 2^24"); } this.activeSectionMap.defaultReturnValue(NO_NODE); - this.updateFilterer = updateFilterer; + this.updateRouter = updateRouter; this.maxNodeCount = maxNodeCount; this.nodeData = new NodeStore(maxNodeCount); this.geometryManager = geometryManager; @@ -54,15 +56,13 @@ public class HierarchicalNodeManager { throw new IllegalArgumentException("Position already in node set: " + WorldEngine.pprintPos(position)); } this.activeSectionMap.put(position, SENTINAL_TOP_NODE_INFLIGHT); - this.updateFilterer.watch(position); + this.updateRouter.watch(position); } public void removeTopLevelNode(long position) { if (!this.activeSectionMap.containsKey(position)) { throw new IllegalArgumentException("Position not in node set: " + WorldEngine.pprintPos(position)); } - - } public void processRequestQueue(int count, long ptr) { @@ -73,7 +73,7 @@ public class HierarchicalNodeManager { } private void processRequest(int op) { - int node = op&NODE_MSK; + int node = op& NODE_ID_MSK; if (!this.nodeData.nodeExists(node)) { throw new IllegalStateException("Tried processing a node that doesnt exist: " + node); } @@ -109,12 +109,12 @@ public class HierarchicalNodeManager { long childPos = makeChildPos(pos, i); request.addChildRequirement(i); //Insert all the children into the tracking map with the node id - if (this.activeSectionMap.put(childPos, requestId|ID_TYPE_LEAF) != NO_NODE) { + if (this.activeSectionMap.put(childPos, requestId|ID_TYPE_REQUEST) != NO_NODE) { throw new IllegalStateException("Leaf request creation failed to insert child into map as a mapping already existed for the node!"); } //Watch and request the child node at the given position - if (!this.updateFilterer.watch(childPos)) { + if (!this.updateRouter.watch(childPos)) { throw new IllegalStateException("Failed to watch childPos"); } } @@ -123,6 +123,64 @@ public class HierarchicalNodeManager { } + private void removeSectionInternal(long position) { + int node = this.activeSectionMap.remove(position); + if (node == NO_NODE) { + throw new IllegalArgumentException("Tried removing node but it didnt exist: " + WorldEngine.pprintPos(position)); + } + + if (node == SENTINAL_TOP_NODE_INFLIGHT) { + System.err.println("WARN: Removing inflight top level node: " + WorldEngine.pprintPos(position)); + return; + } else { + int type = (node & ID_TYPE_MSK); + node &= ~ID_TYPE_MSK; + if (type == ID_TYPE_REQUEST) { + //TODO: THIS + + } else if (type == ID_TYPE_NONE || type == ID_TYPE_TOP) { + if (!this.nodeData.nodeExists(node)) { + throw new IllegalStateException("Section in active map but not in node data"); + } + if (this.nodeData.isNodeRequestInFlight(node)) { + int requestId = this.nodeData.getNodeRequest(node); + var request = this.requests.get(requestId); + if (request.getPosition() != position) { + throw new IllegalStateException("Position != request.position"); + } + + //Recurse into all child requests and remove them, free any geometry along the way + //this.removeSectionInternal(position) + } + + //Recurse into all allocated, children and remove + int children = this.nodeData.getChildPtr(node); + if (children != NO_NODE) { + int count = Integer.bitCount(Byte.toUnsignedInt(this.nodeData.getNodeChildExistence(node))); + for (int i = 0; i < count; i++) { + int cid = children + i; + if (!this.nodeData.nodeExists(cid)) { + throw new IllegalStateException("Child node doesnt exist!"); + } + } + } + + int geometry = this.nodeData.getNodeGeometry(node); + if (geometry != EMPTY_GEOMETRY_ID) { + this.geometryManager.removeSection(geometry); + } + } + + //After its been removed, if its _not_ a top level node or inflight request but just a normal node, + // go up to parent and remove node from the parent allocation and free node id + + } + } + + + + //TODO: need to add a flag that says should do geometry uploads or something I.E. need to watch geometry + // and existance updates seperatly, since cull geometry public void processResult(SectionUpdate update) { //Need to handle cases // geometry update, leaf node, leaf request node, internal node @@ -132,6 +190,9 @@ public class HierarchicalNodeManager { // when mesh result, need to remove the old child allocation block and make a new block to fit the // new count of children + //If the sections child existance bits fully empty, then the section should be removed + + final long position = update.position(); final var geometryData = update.geometry(); int nodeId = this.activeSectionMap.get(position); @@ -164,10 +225,13 @@ public class HierarchicalNodeManager { } else { int type = (nodeId & ID_TYPE_MSK); nodeId &= ~ID_TYPE_MSK; - if (type == ID_TYPE_LEAF) { - this.leafDataUpdate(nodeId, update); + if (type == ID_TYPE_REQUEST) { + this.requestDataUpdate(nodeId, update); } else if (type == ID_TYPE_NONE || type == ID_TYPE_TOP) { - //Not part of a request, just a node update + //Not part of a request, just a node update, + + //NOTE! be aware that if its an existance update and there is a request attached, need to check if the updated + // request becomes finished!! } else { throw new IllegalStateException("Should not reach here"); } @@ -181,17 +245,33 @@ public class HierarchicalNodeManager { this.nodeData.setNodeChildExistence(node, childExistence); } - private void leafDataUpdate(int nodeId, SectionUpdate update) { + private void requestDataUpdate(int nodeId, SectionUpdate update) { var request = this.requests.get(nodeId); + //Update for section part of a request, the request may be a leaf request update or an inner node update + + + if (request.isSatisfied()) { + this.processFinishedNodeChildRequest(nodeId, request); + } } + //Process NodeChildRequest results + private void processFinishedNodeChildRequest(int parent, NodeChildRequest request) { + int children = this.nodeData.getChildPtr(parent); + if (children != NO_NODE) { + //There are children already part of this node, so need to reallocate all the children + int count = Integer.bitCount(Byte.toUnsignedInt(this.nodeData.getNodeChildExistence(parent))); + } else { + + } + } private int updateNodeGeometry(int node, BuiltSection geometry) { int previousGeometry = this.nodeData.getNodeGeometry(node); - int newGeometry = -1; - if (previousGeometry != -1) { + int newGeometry = EMPTY_GEOMETRY_ID; + if (previousGeometry != EMPTY_GEOMETRY_ID) { if (!geometry.isEmpty()) { newGeometry = this.geometryManager.uploadReplaceSection(previousGeometry, geometry); } else { @@ -209,17 +289,13 @@ public class HierarchicalNodeManager { } if (previousGeometry == newGeometry) { return 0;//No change - } else if (previousGeometry == -1) { + } else if (previousGeometry == EMPTY_GEOMETRY_ID) { return 1;//Became non-empty } else { return 2;//Became empty } } - private void createSingleNode() { - - } - private static int getChildIdx(long pos) { int x = WorldEngine.getX(pos); int y = WorldEngine.getY(pos); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalOcclusionTraverser.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalOcclusionTraverser.java index bc8fdc7a..90e12cdf 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalOcclusionTraverser.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalOcclusionTraverser.java @@ -40,6 +40,7 @@ public class HierarchicalOcclusionTraverser { } + public static int HACKY_SECTION_COUNT = 0; public void doTraversal(Viewport viewport, int depthBuffer) { //Compute the mip chain this.hiZBuffer.buildMipChain(depthBuffer, viewport.width, viewport.height); @@ -50,7 +51,6 @@ public class HierarchicalOcclusionTraverser { //Use a chain of glDispatchComputeIndirect (5 times) with alternating read/write buffers // TODO: swap to persistent gpu thread instead - /* if (HACKY_SECTION_COUNT != 0) { long uploadPtr = UploadStream.INSTANCE.upload(this.renderList, 0, HACKY_SECTION_COUNT*4L+4); @@ -61,7 +61,6 @@ public class HierarchicalOcclusionTraverser { UploadStream.INSTANCE.commit(); } - */ this.downloadResetRequestQueue(); } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeChildRequest.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeChildRequest.java index ffad0586..1adaaa96 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeChildRequest.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeChildRequest.java @@ -66,4 +66,8 @@ class NodeChildRequest { public boolean isSatisfied() { return (this.results&this.mask)==this.mask; } + + public long getPosition() { + return this.nodePos; + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeStore.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeStore.java index 470be516..1998d545 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeStore.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeStore.java @@ -3,11 +3,22 @@ package me.cortex.voxy.client.core.rendering.hierachical2; import me.cortex.voxy.common.util.HierarchicalBitSet; public final class NodeStore { + public static final int EMPTY_GEOMETRY_ID = -1; + public static final int NODE_ID_MSK = ((1<<24)-1); + public static final int REQUEST_ID_MSK = ((1<<16)-1); + public static final int GEOMETRY_ID_MSK = (1<<24)-1; + public static final int MAX_GEOMETRY_ID = GEOMETRY_ID_MSK-1; + private static final int SENTINEL_EMPTY_GEOMETRY_ID = GEOMETRY_ID_MSK; + private static final int SENTINEL_NULL_NODE_ID = NODE_ID_MSK -1; + private static final int SENTINEL_REQUEST_ID = REQUEST_ID_MSK -1; private static final int LONGS_PER_NODE = 4; private static final int INCREMENT_SIZE = 1<<16; private final HierarchicalBitSet allocationSet; private long[] localNodeData; public NodeStore(int maxNodeCount) { + if (maxNodeCount>=SENTINEL_NULL_NODE_ID) { + throw new IllegalArgumentException("Max count too large"); + } //Initial count is 1024 this.localNodeData = new long[INCREMENT_SIZE*LONGS_PER_NODE]; this.allocationSet = new HierarchicalBitSet(maxNodeCount); @@ -54,20 +65,28 @@ public final class NodeStore { } private void free(int nodeId) { - if (!this.allocationSet.free(nodeId)) { - throw new IllegalStateException("Node " + nodeId + " was not allocated!"); - } + this.free(nodeId, 1); + } + private void free(int baseNodeId, int count) { + for (int i = 0; i < count; i++) { + int nodeId = baseNodeId + i; + if (!this.allocationSet.free(nodeId)) { + throw new IllegalStateException("Node " + nodeId + " was not allocated!"); + } + } } private void clear(int nodeId) { - + int idx = id2idx(nodeId); + this.localNodeData[idx] = -1;//Position + this.localNodeData[idx+1] = 0; + this.localNodeData[idx+2] = 0; + this.localNodeData[idx+3] = 0; } - - public void setNodePosition(int node, long position) { this.localNodeData[id2idx(node)] = position; } @@ -81,40 +100,88 @@ public final class NodeStore { } public int getNodeGeometry(int node) { - return -1; + long data = this.localNodeData[id2idx(node)+1]; + int geometryPtr = (int) (data&GEOMETRY_ID_MSK); + if (geometryPtr == SENTINEL_EMPTY_GEOMETRY_ID) { + return -1; + } + return geometryPtr; } + public void setNodeGeometry(int node, int geometryId) { + if (geometryId>MAX_GEOMETRY_ID || geometryId<-1) { + throw new IllegalArgumentException("Geometry ptr greater than MAX_GEOMETRY_ID or less than -1 : " + geometryId); + } + if (geometryId == -1) { + geometryId = SENTINEL_EMPTY_GEOMETRY_ID; + } - } - - public void setNodeRequest(int node, int requestId) { - - } - - public void markRequestInFlight(int nodeId) { - - } - - public boolean isNodeRequestInFlight(int nodeId) { - return false; - } - - public boolean isLeafNode(int nodeId) { - return false; - } - - public byte getNodeChildExistence(int nodeId) {return 0;} - - public void setNodeChildExistence(int node, byte existence) { - + int idx = id2idx(node)+1; + long data = this.localNodeData[idx]; + data &= ~GEOMETRY_ID_MSK; + data |= geometryId; + this.localNodeData[idx] = data; } public int getChildPtr(int nodeId) { - return -1; + long data = this.localNodeData[id2idx(nodeId)+1]; + int nodePtr = (int) ((data>>24)&NODE_ID_MSK); + if (nodePtr == SENTINEL_NULL_NODE_ID) { + return -1; + } + return nodePtr; } public void setChildPtr(int nodeId, int ptr) { + if (ptr>=NODE_ID_MSK || ptr<-1) { + throw new IllegalArgumentException("Node child ptr greater GEQ NODE_ID_MSK or less than -1 : " + ptr); + } + if (ptr == -1) { + ptr = SENTINEL_NULL_NODE_ID; + } + int idx = id2idx(nodeId)+1; + long data = this.localNodeData[idx]; + data &= ~(((long)NODE_ID_MSK)<<24); + data |= ((long)ptr)<<24; + this.localNodeData[idx] = data; + } + + public void setNodeRequest(int node, int requestId) { + int id = id2idx(node)+2; + long data = this.localNodeData[id]; + data &= ~REQUEST_ID_MSK; + data |= requestId; + this.localNodeData[id] = data; + } + + public int getNodeRequest(int node) { + return (int) (this.localNodeData[id2idx(node)+2]&REQUEST_ID_MSK); + } + + public void markRequestInFlight(int nodeId) { + this.localNodeData[id2idx(nodeId)+1] |= 1L<<63; + } + + public boolean isNodeRequestInFlight(int nodeId) { + return ((this.localNodeData[id2idx(nodeId)+1]>>63)&1)!=0; + } + + public boolean isLeafNode(int nodeId) { + return ((this.localNodeData[id2idx(nodeId)+1]>>62)&1)!=0; + } + + public byte getNodeChildExistence(int nodeId) { + long data = this.localNodeData[id2idx(nodeId)+1]; + return (byte) ((data>>48)&0xFF); + } + + public void setNodeChildExistence(int nodeId, byte existence) { + int idx = id2idx(nodeId)+1; + long data = this.localNodeData[idx]; + data &= ~(0xFFL<<48); + data |= Byte.toUnsignedLong(existence)<<48; + this.localNodeData[idx] = data; } //Writes out a nodes data to the ptr in the compacted/reduced format 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 fdd92170..667b8331 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 @@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.rendering.building.BuiltSection; +import me.cortex.voxy.client.core.rendering.hierachical2.HierarchicalOcclusionTraverser; import me.cortex.voxy.client.core.rendering.util.BufferArena; import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.common.util.HierarchicalBitSet; @@ -64,6 +65,8 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager //Invalidate the section id this.invalidatedSectionIds.add(newId); + + HierarchicalOcclusionTraverser.HACKY_SECTION_COUNT = this.allocationSet.getCount(); return newId; }