From 1048d38b4d94e3a78fd3657044fef675a93e1e79 Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Tue, 28 Jan 2025 03:14:14 +1000 Subject: [PATCH] Work on geometry cleaning --- .../client/core/rendering/RenderService.java | 2 +- .../building/SectionUpdateRouter.java | 1 + .../hierachical/HierarchicalNodeManager.java | 448 ------------------ .../rendering/hierachical/NodeCleaner.java | 18 +- .../rendering/hierachical/NodeManager.java | 81 +++- .../core/rendering/hierachical/NodeStore.java | 4 +- .../AbstractSectionGeometryManager.java | 5 + .../section/BasicSectionGeometryManager.java | 5 + .../core/rendering/util/DownloadStream.java | 3 + .../client/saver/ContextSelectionSystem.java | 2 +- .../cortex/voxy/common/world/WorldEngine.java | 9 +- .../hierarchical/cleaner/sort_visibility.comp | 6 +- 12 files changed, 121 insertions(+), 463 deletions(-) delete mode 100644 src/main/java/me/cortex/voxy/client/core/rendering/hierachical/HierarchicalNodeManager.java 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 656a78fb..620ec94d 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 @@ -49,7 +49,7 @@ public class RenderService, J extends Vi //Max sections: ~500k //Max geometry: 1 gb - this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<20, (1L<<32)-1024); + this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<20, (1L<<30)-1024); //Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard var router = new SectionUpdateRouter(); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdateRouter.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdateRouter.java index f80130c5..71e5168a 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdateRouter.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdateRouter.java @@ -73,6 +73,7 @@ public class SectionUpdateRouter { set.remove(position); return true; } + set.put(position, current); return false; } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/HierarchicalNodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/HierarchicalNodeManager.java deleted file mode 100644 index a4893e62..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/HierarchicalNodeManager.java +++ /dev/null @@ -1,448 +0,0 @@ -package me.cortex.voxy.client.core.rendering.hierachical; - - -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.rendering.building.BuiltSection; -import me.cortex.voxy.client.core.rendering.building.SectionUpdateRouter; -import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager; -import me.cortex.voxy.client.core.rendering.util.UploadStream; -import me.cortex.voxy.client.core.util.ExpandingObjectAllocationList; -import me.cortex.voxy.common.Logger; -import me.cortex.voxy.common.world.WorldEngine; -import net.caffeinemc.mods.sodium.client.util.MathUtil; -import org.lwjgl.system.MemoryUtil; - -import static me.cortex.voxy.client.core.rendering.hierachical.NodeStore.EMPTY_GEOMETRY_ID; -import static me.cortex.voxy.client.core.rendering.hierachical.NodeStore.NODE_ID_MSK; - -//Contains no logic to interface with the gpu, nor does it contain any gpu buffers -public class HierarchicalNodeManager { - /** - * Rough explanation to help with confusion, - * All leaf nodes (i.e. nodes that have no children currently existing), all have a geometry node attached (if its non empty) - * note! on the gpu, it does not need the child ptr unless it tries to go down, that is, when creating/uploading to gpu - * dont strictly need all child nodes - * Internal nodes are nodes that have children and parents, they themself may or may not have geometry attached - * Top level nodes, dont have parents but other than that inherit all the properties of internal nodes - * - * The way the traversal works is as follows - * Top level nodes are source nodes, they are the inital queue - * * For each node in the queue, compute the screenspace size of the node, check against the hiz buffer - * * If the node is < rendering screensize - * * If the node has geometry and not empty - * * Queue the geometry for rendering - * * If the node is missing geometry and is marked as not empty - * * Attempt to put node ID into request queue - * * If node has children - * * put children into traversal queue - * * Else - * * Technically an error state, as we should _always_ either have children or geometry - * * Else - * * Dont do anything as section is explicit empty render data - * * Else if node is > rendering screensize, need to subdivide - * * If Child ptr is not empty (i.e. is an internal node) - * * Enqueue children into traversal queue - * * If child ptr is empty (i.e. is leaf node) and is not base LoD level - * * Attempt to put node ID into request queue - * * If node has geometry, or is explicitly marked as empty render data - * * Queue own geometry (if non empty) - * * Else - * * Technically an error state, as a leaf node should always have geometry (or marked as empty geometry data) - */ - - //There are a few properties of the class that can be used for verification - // a position cannot be both in a request and have an associated node, it must be none, in a request or a node - // leaf nodes cannot have a childptr - // updateRouter tracking child events should be the same set as activeSectionMap - // any allocated indices in requests are not finished/are in flight - // for all requests there must be an associated and valid node in the activeSectionMap - // for all requests the parent node must exist and cannot be ID_TYPE_REQUEST - // for all inner nodes, if there are no requests the children nodes must match the nodes childExistence bitset - // if there is a request, it should be the delta to get from the old children to the new children - // no node can have an empty childExistence (except for top level nodes) - - private static final int NO_NODE = -1; - - private static final int ID_TYPE_MSK = (3<<30); - private static final int ID_TYPE_LEAF = (3<<30); - private static final int ID_TYPE_INNER = 0; - private static final int ID_TYPE_REQUEST = (2<<30); - //private static final int ID_TYPE_TOP = (1<<30); - - public final int maxNodeCount; - private final IntOpenHashSet nodeUpdates = new IntOpenHashSet(); - private final NodeStore nodeData; - - //Map from position->id, the top 2 bits contains specifies the type of id - private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap(); - - private final ExpandingObjectAllocationList requests = new ExpandingObjectAllocationList<>(NodeChildRequest[]::new); - - private final AbstractSectionGeometryManager geometryManager; - private final SectionUpdateRouter updateRouter; - - public HierarchicalNodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, SectionUpdateRouter updateRouter) { - if (!MathUtil.isPowerOfTwo(maxNodeCount)) { - throw new IllegalArgumentException("Max node count must be a power of 2"); - } - if (maxNodeCount>(1<<24)) { - throw new IllegalArgumentException("Max node count cannot exceed 2^24"); - } - this.activeSectionMap.defaultReturnValue(NO_NODE); - this.updateRouter = updateRouter; - this.maxNodeCount = maxNodeCount; - this.nodeData = new NodeStore(maxNodeCount); - this.geometryManager = geometryManager; - } - - public void insertTopLevelNode(long position) { - if (this.activeSectionMap.containsKey(position)) { - throw new IllegalArgumentException("Position already in node set: " + WorldEngine.pprintPos(position)); - } - - int id = this.nodeData.allocate(); - this.nodeData.setNodePosition(id, position); - this.activeSectionMap.put(position, id|ID_TYPE_LEAF); this.nodeData.setNodeType(id, ID_TYPE_LEAF); //ID_TYPE_TOP - this.updateRouter.watch(position, WorldEngine.UPDATE_FLAGS); - } - - public void removeTopLevelNode(long position) { - if (!this.activeSectionMap.containsKey(position)) { - throw new IllegalArgumentException("Position not in node set: " + WorldEngine.pprintPos(position)); - } - } - - 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)); - } - - int type = (node & ID_TYPE_MSK); - node &= ~ID_TYPE_MSK; - if (type == ID_TYPE_REQUEST) { - //TODO: THIS - - } else if (type == ID_TYPE_INNER) {// || 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 - - } - - //============================================================================================================================================ - public void processRequestQueue(int count, long ptr) { - for (int requestIndex = 0; requestIndex < count; requestIndex++) { - int op = MemoryUtil.memGetInt(ptr + (requestIndex * 4L)); - this.processRequest(op); - } - } - - private void processRequest(int op) { - int node = op& NODE_ID_MSK; - if (!this.nodeData.nodeExists(node)) { - throw new IllegalStateException("Tried processing a node that doesnt exist: " + node); - } - if (this.nodeData.isNodeRequestInFlight(node)) { - throw new IllegalStateException("Tried processing a node that already has a request in flight: " + node + " pos: " + WorldEngine.pprintPos(this.nodeData.nodePosition(node))); - } - this.nodeData.markRequestInFlight(node); - - - //2 branches, either its a leaf node -> emit a leaf request - // or the nodes geometry must be empty (i.e. culled from the graph/tree) so add to tracker and watch - int type = this.nodeData.getNodeType(node); - if (type == ID_TYPE_LEAF) { - this.makeLeafRequest(node, this.nodeData.getNodeChildExistence(node)); - } else { - //Verify that the node section is not in the section store. if it is then it is a state desynchonization - // Note that a section can be "empty" but some of its children might not be - } - } - - //TODO: FIXME: so there is a fundamental issue with this, if the gpu requests from cpu before childExistance is set - // then everything explodes cause it wont get notified or updated - private void makeLeafRequest(int node, byte childExistence) { - long pos = this.nodeData.nodePosition(node); - - //Enqueue a leaf expansion request - var request = new NodeChildRequest(pos); - int requestId = this.requests.put(request); - - //Only request against the childExistence mask, since the guarantee is that if childExistence bit is not set then that child is guaranteed to be empty - for (int i = 0; i < 8; i++) { - if ((childExistence&(1<>1)&1), - (WorldEngine.getZ(basePos)<<1)|((addin>>2)&1)); - } -} 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 ed057e65..8eb49dc2 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 @@ -85,7 +85,7 @@ public class NodeCleaner { this.visibilityId++; this.clearIds(); - if (false) { + if (this.shouldCleanGeometry() & false) { glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); this.outputBuffer.fill(this.nodeManager.maxNodeCount-2);//TODO: maybe dont set to zero?? @@ -105,17 +105,27 @@ public class NodeCleaner { glDispatchCompute(1,1,1); DownloadStream.INSTANCE.download(this.outputBuffer, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT, this::onDownload); + + + this.visibilityBuffer.fill(-1); + } } + private boolean shouldCleanGeometry() { + // if there is less than 200mb of space, clean + return this.nodeManager.getGeometryManager().getRemainingCapacity() < 200_000_000L; + } + private void onDownload(long ptr, long size) { - StringBuilder b = new StringBuilder(); + //StringBuilder b = new StringBuilder(); for (int i = 0; i < 64; i++) { long pos = Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i))<<32; pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i + 4)); - b.append(", ").append(WorldEngine.pprintPos(pos));//.append(((int)((pos>>32)&0xFFFFFFFFL)));// + this.nodeManager.removeNodeGeometry(pos); + //b.append(", ").append(WorldEngine.pprintPos(pos));//.append(((int)((pos>>32)&0xFFFFFFFFL)));// } - System.out.println(b); + //System.out.println(b); } private void clearIds() { 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 f2024baa..e3f66fac 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 @@ -16,6 +16,7 @@ import net.caffeinemc.mods.sodium.client.util.MathUtil; import java.util.List; +import static me.cortex.voxy.common.world.WorldEngine.MAX_LOD_LAYERS; //TODO FIXME: CIRTICAL ISSUE: if a node is a top level section and is empty, when a child is tried to be made it explodes @@ -715,7 +716,7 @@ public class NodeManager { //Dont mark node as having an inflight request //TODO: assert that the node isnt already being watched for geometry, if it is, just spit out a warning? and ignore - Logger.error("TODO FINISH THIS"); + //Logger.error("TODO FINISH THIS"); // THis shouldent result in markRequestInFlight afak if (!this.updateRouter.watch(pos, WorldEngine.UPDATE_TYPE_BLOCK_BIT)) { @@ -766,12 +767,65 @@ public class NodeManager { //================================================================================================================== // Used by the cleaning system to ensure memory capacity in the geometry store - int markGeometryNull(int nodeId) { - return 0; - } + public void removeNodeGeometry(long pos) { + int nodeId = this.activeSectionMap.get(pos); + if (nodeId == -1) { + Logger.error("Got geometry removal for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!"); + return; + } + int nodeType = nodeId&NODE_TYPE_MSK; + nodeId &= NODE_ID_MSK; + if (nodeType == NODE_TYPE_REQUEST) { + Logger.error("Tried removing geometry for pos: " + WorldEngine.pprintPos(pos) + " but its type was a request, ignoring!"); + return; + } - //Removes, clears and frees itself, all children, requests and everything recursively - void removeNodeAndChildrenRecursive(int nodeId) { + if (nodeType == NODE_TYPE_LEAF) { + //If it is a leaf node, check that the parent has geometry, if it doesnt, request geometry for that parent + // if it DOES tho, remove all the children and make the parent a leaf node + // by requesting the geometry of the parent, it means that the system will automatically handle itself + // (if only a bit slow as needs to go roundabout in the pipeline) + // but what it means is the parent then gets geometry, and the child still has the clear request from this + // which means magically everything might maybe should work tm? + //Logger.warn("Tried removing geometry for leaf node: " + WorldEngine.pprintPos(pos) + " but this is not yet supported, ignoring!"); + + if (WorldEngine.getLevel(pos) == MAX_LOD_LAYERS-1) { + //Cannot remove top level nodes + Logger.info("Tried cleaning top level node " + WorldEngine.pprintPos(pos)); + return; + } + + long pPos = makeParentPos(pos); + int pId = this.activeSectionMap.get(pPos); + if (pId == -1) throw new IllegalStateException("Parent node must exist"); + if ((pId&NODE_TYPE_MSK)!=NODE_TYPE_INNER) throw new IllegalStateException("Parent node must be an inner node"); + pId &= NODE_ID_MSK; + + if (this.nodeData.getNodeGeometry(pId) == NULL_GEOMETRY_ID) { + //If the parent has null geometry we must first fill it before we can remove it + + //Logger.error("TODO: THIS"); + } else { + //Else make the parent node a leaf node and remove all the children + + //Logger.error("TODO: THIS 2"); + } + return; + } + + int geometryId = this.nodeData.getNodeGeometry(nodeId); + if (geometryId != NULL_GEOMETRY_ID && geometryId != EMPTY_GEOMETRY_ID) { + //Unwatch geometry updates + if (this.updateRouter.unwatch(pos, WorldEngine.UPDATE_TYPE_BLOCK_BIT)) { + throw new IllegalStateException("Unwatching position for geometry removal at: " + WorldEngine.pprintPos(pos) + " resulted in full removal"); + } + //Remove geometry and set to null + //TODO: download and remove instead of just removing, and store in ram cache for later!! + this.geometryManager.removeSection(geometryId); + this.nodeData.setNodeGeometry(nodeId, NULL_GEOMETRY_ID); + //this.cleaner + } + this.invalidateNode(nodeId); } @@ -812,6 +866,17 @@ public class NodeManager { (WorldEngine.getZ(basePos)<<1)|((addin>>1)&1)); } + private long makeParentPos(long pos) { + int lvl = WorldEngine.getLevel(pos); + if (lvl == MAX_LOD_LAYERS-1) { + throw new IllegalArgumentException("Cannot create a parent higher than LoD " + (MAX_LOD_LAYERS-1)); + } + return WorldEngine.getWorldSectionId(lvl+1, + WorldEngine.getX(pos)>>1, + WorldEngine.getY(pos)>>1, + WorldEngine.getZ(pos)>>1); + } + public void addDebug(List debug) { debug.add("NC/IF: " + this.activeSectionMap.size() + "/" + (this.singleRequests.count() + this.childRequests.count())); } @@ -819,4 +884,8 @@ public class NodeManager { public int getCurrentMaxNodeId() { return this.nodeData.getEndNodeId(); } + + public AbstractSectionGeometryManager getGeometryManager() { + return this.geometryManager; + } } 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 1d6ccad4..e48492cf 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 @@ -255,8 +255,10 @@ public final class NodeStore { { int geometry = this.getNodeGeometry(nodeId); - if (geometry == -1) { + if (geometry == -2) { z |= 0xFFFFFF-1;//This is a special case, which basically says to the renderer that the geometry is empty (not that it doesnt exist) + } else if (geometry == -1) { + z |= 0xFFFFFF;//Special case null } else { z |= geometry&0xFFFFFF;//TODO: check and ensure bounds } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionGeometryManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionGeometryManager.java index c0f4461e..2eb70c37 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionGeometryManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionGeometryManager.java @@ -28,4 +28,9 @@ public abstract class AbstractSectionGeometryManager { public void free() {} public abstract void downloadAndRemove(int id, Consumer callback); + + public abstract long getUsedCapacity(); + public long getRemainingCapacity() { + return this.geometryCapacity - this.getUsedCapacity(); + } } 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 6874db39..01dcd38b 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 @@ -157,4 +157,9 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager int getMetadataBufferId() { return this.sectionMetadataBuffer.id; } + + @Override + public long getUsedCapacity() { + return this.geometry.getUsedBytes(); + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/util/DownloadStream.java b/src/main/java/me/cortex/voxy/client/core/rendering/util/DownloadStream.java index 453f11e7..77df45e4 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/util/DownloadStream.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/util/DownloadStream.java @@ -96,6 +96,9 @@ public class DownloadStream { } this.downloadList.add(new DownloadData(buffer, addr, downloadOffset, size, resultConsumer)); + + //TODO: maybe not auto-commit + this.commit(); } diff --git a/src/main/java/me/cortex/voxy/client/saver/ContextSelectionSystem.java b/src/main/java/me/cortex/voxy/client/saver/ContextSelectionSystem.java index 089601a3..bb20fbfa 100644 --- a/src/main/java/me/cortex/voxy/client/saver/ContextSelectionSystem.java +++ b/src/main/java/me/cortex/voxy/client/saver/ContextSelectionSystem.java @@ -98,7 +98,7 @@ public class ContextSelectionSystem { } public WorldEngine createEngine(ServiceThreadPool serviceThreadPool) { - return new WorldEngine(this.createStorageBackend(), serviceThreadPool, 5); + return new WorldEngine(this.createStorageBackend(), serviceThreadPool); } //Saves the config for the world selection or something, need to figure out how to make it work with dimensional configs maybe? 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 4aa345c1..54f16d10 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java @@ -14,6 +14,8 @@ import java.util.Arrays; //Use an LMDB backend to store the world, use a local inmemory cache for lod sections // automatically manages and invalidates sections of the world as needed public class WorldEngine { + public static final int MAX_LOD_LAYERS = 5; + public static final int UPDATE_TYPE_BLOCK_BIT = 1; public static final int UPDATE_TYPE_CHILD_EXISTENCE_BIT = 2; public static final int UPDATE_FLAGS = UPDATE_TYPE_BLOCK_BIT | UPDATE_TYPE_CHILD_EXISTENCE_BIT; @@ -35,7 +37,12 @@ public class WorldEngine { public Mapper getMapper() {return this.mapper;} - public WorldEngine(StorageBackend storageBackend, ServiceThreadPool serviceThreadPool, int maxMipLayers) { + + public WorldEngine(StorageBackend storageBackend, ServiceThreadPool serviceThreadPool) { + this(storageBackend, serviceThreadPool, MAX_LOD_LAYERS); + } + + private WorldEngine(StorageBackend storageBackend, ServiceThreadPool serviceThreadPool, int maxMipLayers) { this.maxMipLevels = maxMipLayers; this.storage = storageBackend; this.mapper = new Mapper(this.storage); 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 6fcfec4b..7980751d 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 @@ -50,5 +50,9 @@ void main() { // minVisIds[gl_GlobalInvocationID.x] = visiblity[gl_GlobalInvocationID.x]; //} //First do a min sort/set of min OUTPUT_SIZE values of the set - bubbleSort(0, gl_GlobalInvocationID.x, visiblity[gl_GlobalInvocationID.x]); + uint vis = visiblity[gl_GlobalInvocationID.x]; + if (visiblity[minVisIds[OUTPUT_SIZE-1]] <= vis) { + return; + } + bubbleSort(0, gl_GlobalInvocationID.x, vis); } \ No newline at end of file