Work on geometry cleaning

This commit is contained in:
mcrcortex
2025-01-28 03:14:14 +10:00
parent 9ded4d4b13
commit 1048d38b4d
12 changed files with 121 additions and 463 deletions

View File

@@ -49,7 +49,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
//Max sections: ~500k //Max sections: ~500k
//Max geometry: 1 gb //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 //Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard
var router = new SectionUpdateRouter(); var router = new SectionUpdateRouter();

View File

@@ -73,6 +73,7 @@ public class SectionUpdateRouter {
set.remove(position); set.remove(position);
return true; return true;
} }
set.put(position, current);
return false; return false;
} }
} }

View File

@@ -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<NodeChildRequest> 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<<i))==0) {
//Dont watch or enqueue the child node cause it doesnt exist
continue;
}
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_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.updateRouter.watch(childPos, WorldEngine.UPDATE_FLAGS)) {
throw new IllegalStateException("Failed to watch childPos");
}
}
this.nodeData.setNodeRequest(node, requestId);
}
//============================================================================================================================================
public void processChildChange(long position, byte childExistence) {
if (childExistence == 0) {
Logger.error("Section at " + WorldEngine.pprintPos(position) + " had empty child existence!!");
}
int nodeId = this.activeSectionMap.get(position);
if (nodeId == NO_NODE) {
System.err.println("Received child change for section " + WorldEngine.pprintPos(position) + " however section position not in active in map! discarding");
} else {
int type = (nodeId & ID_TYPE_MSK);
nodeId &= ~ID_TYPE_MSK;
if (type == ID_TYPE_REQUEST) {
//Doesnt result in an invalidation as we must wait for geometry to create a child
this.requests.get(nodeId).setChildMesh(getChildIdx(position), childExistence);
} else if (type == ID_TYPE_LEAF || type == ID_TYPE_INNER) {// || type == ID_TYPE_TOP
if (this.nodeData.getNodeChildExistence(nodeId) == childExistence) {
//Dont need to update the internal state since it is the same
return;
}
//ALSO: TODO: HERE: if a child is removed, need to remove it and all children accociated
//If its an inner node and doesnt have an inflight request, create an empty request, this will get autofilled by the following part
if (type == ID_TYPE_INNER) {
}
//If its a top level node, it needs a request? aswell, no no it doesnt :tm: what could do tho, is if it has a request in progress, update it (already handled)
// _or_ if the top level node has a child ptr then also handle it??
// the issue is that top nodes probably need an extra state like "shouldMakeChildren" or something
// cause if a top level node, that has no children, requests a decent from the gpu
// but there are no children so no decent is made, if a child update is added to that section,
// the update will be ignored since the cpu doesnt know the gpu still wants it
//If its a leaf node, its fine, dont need to create a request, only need to update the node msk
if (this.nodeData.isNodeRequestInFlight(nodeId)) {
int reqId = this.nodeData.getNodeRequest(nodeId);
var request = this.requests.get(reqId);
byte reqMsk = request.getMsk();
if (reqMsk != childExistence) {
//Only need to change if its not the same
byte toRem = (byte) ((~childExistence)&reqMsk);
if (toRem != 0) {
//Remove nodes from the request
for (int i = 0; i < 8; i++) {//TODO: swap out for numberOfTrailingZeros loop thing
if ((toRem&(i<<1))==0) continue;
int geometry = request.removeAndUnRequire(i);
long cpos = makeChildPos(position, i);
//Remove from update router and activeSectionMap
int cid = this.activeSectionMap.remove(cpos);
if ((cid&ID_TYPE_MSK)!=ID_TYPE_REQUEST || (cid&~ID_TYPE_MSK)!=reqId) {
throw new IllegalStateException(WorldEngine.pprintPos(position)+" " + i + " " + cid + " " + reqId);
}
///Remove all from update router
if (!this.updateRouter.unwatch(cpos, WorldEngine.UPDATE_FLAGS)) {
throw new IllegalStateException();
}
//Release geometry if it had any
if (geometry != -1) {
this.geometryManager.removeSection(geometry);
}
}
}
byte toAdd = (byte) ((~reqMsk)&childExistence);
//This also needs to be with respect to the nodes current childexistance status as some sections are already watched/have nodes, dont change those
toAdd &= (byte) ~this.nodeData.getNodeChildExistence(nodeId);
if (toAdd != 0) {
//Add nodes to the request that dont exist in the node already
for (int i = 0; i < 8; i++) {//TODO: swap out for numberOfTrailingZeros loop thing
if ((toAdd & (i << 1)) == 0) continue;
request.addChildRequirement(i);
long cpos = makeChildPos(position, i);
int prev = this.activeSectionMap.put(cpos, reqId|ID_TYPE_REQUEST);
if (prev!=-1) {
throw new IllegalStateException("Child is already mapped to a node id " + WorldEngine.pprintPos(cpos) + " " + reqId + " " + prev);
}
if (!this.updateRouter.watch(cpos, WorldEngine.UPDATE_FLAGS)) {
throw new IllegalStateException("Couldn't watch chunk section");
}
}
}
if (request.getMsk() != childExistence) {
throw new IllegalStateException();
}
//If the request is satisfied after changes, consume it
if (request.isSatisfied()) {
this.consumeFinishedNodeChildRequest(nodeId, request);
}
}
}
//Need to update the node itself, only if the node has children does it need to update the child ptr information tho
} else {
throw new IllegalStateException("Should not reach here");
}
}
}
public void processGeometryResult(BuiltSection section) {
final long position = section.position;
int nodeId = this.activeSectionMap.get(position);
if (nodeId == NO_NODE) {
System.err.println("Received geometry for section " + WorldEngine.pprintPos(position) + " however section position not in active in map! discarding");
//Not tracked or mapped to a node!, discard it, it was probably in progress when it was removed from the map
section.free();
} else {
int type = (nodeId & ID_TYPE_MSK);
nodeId &= ~ID_TYPE_MSK;
if (type == ID_TYPE_REQUEST) {
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
int child = getChildIdx(position);
int prev = request.setChildMesh(child, this.geometryManager.uploadSection(section));
if (prev != -1) {
this.geometryManager.removeSection(prev);
}
if (request.isSatisfied()) {
this.consumeFinishedNodeChildRequest(nodeId, request);
}
} else if (type == ID_TYPE_INNER || type == ID_TYPE_LEAF) {//type == ID_TYPE_TOP ||
//Update the node geometry, and enqueue if it changed
if (this.updateNodeGeometry(nodeId, section) != 0) {//TODO: might need to mark the node as empty geometry or something
this.nodeUpdates.add(nodeId);
}
} else {
throw new IllegalStateException("Should not reach here");
}
}
}
private int updateNodeGeometry(int node, BuiltSection geometry) {
int previousGeometry = this.nodeData.getNodeGeometry(node);
int newGeometry = EMPTY_GEOMETRY_ID;
if (previousGeometry != EMPTY_GEOMETRY_ID) {
if (!geometry.isEmpty()) {
newGeometry = this.geometryManager.uploadReplaceSection(previousGeometry, geometry);
} else {
this.geometryManager.removeSection(previousGeometry);
}
} else {
if (!geometry.isEmpty()) {
newGeometry = this.geometryManager.uploadSection(geometry);
}
}
if (previousGeometry != newGeometry) {
this.nodeData.setNodeGeometry(node, newGeometry);
}
if (previousGeometry == newGeometry) {
return 0;//No change
} else if (previousGeometry == EMPTY_GEOMETRY_ID) {
return 1;//Became non-empty
} else {
return 2;//Became empty
}
}
//Process NodeChildRequest results
private void consumeFinishedNodeChildRequest(int nodeId, NodeChildRequest request) {
//TODO:!!! NOTE: DONT
int children = this.nodeData.getChildPtr(nodeId);
if (children != NO_NODE) {
//There are children already part of this node, so need to reallocate all the children which is _really_ bad as it can cause so many desyncs
// between gpu and cpu its not even funny
//TODO: what will need to be done is a fence be created, and to not release the ids until the gpu is done with them??
// that _might :tm:_ help with preventing desyncs???
// the issue is like a request right, sends id to cpu, that id might have changed and everything then proceeds to explode
int count = Integer.bitCount(Byte.toUnsignedInt(this.nodeData.getNodeChildExistence(nodeId)));
} else {
}
}
//============================================================================================
public boolean writeChanges(GlBuffer nodeBuffer) {
//TODO: use like compute based copy system or something
// since microcopies are bad
if (this.nodeUpdates.isEmpty()) {
return false;
}
for (int i : this.nodeUpdates) {
this.nodeData.writeNode(UploadStream.INSTANCE.upload(nodeBuffer, i*16L, 16L), i);
}
this.nodeUpdates.clear();
return true;
}
//============================================================================================================================================
private static int getChildIdx(long pos) {
int x = WorldEngine.getX(pos);
int y = WorldEngine.getY(pos);
int z = WorldEngine.getZ(pos);
return (x&1)|((y&1)<<1)|((z&1)<<2);
}
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));
}
}

View File

@@ -85,7 +85,7 @@ public class NodeCleaner {
this.visibilityId++; this.visibilityId++;
this.clearIds(); this.clearIds();
if (false) { if (this.shouldCleanGeometry() & false) {
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
this.outputBuffer.fill(this.nodeManager.maxNodeCount-2);//TODO: maybe dont set to zero?? this.outputBuffer.fill(this.nodeManager.maxNodeCount-2);//TODO: maybe dont set to zero??
@@ -105,17 +105,27 @@ public class NodeCleaner {
glDispatchCompute(1,1,1); glDispatchCompute(1,1,1);
DownloadStream.INSTANCE.download(this.outputBuffer, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT, this::onDownload); 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) { private void onDownload(long ptr, long size) {
StringBuilder b = new StringBuilder(); //StringBuilder b = new StringBuilder();
for (int i = 0; i < 64; i++) { for (int i = 0; i < 64; i++) {
long pos = Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i))<<32; long pos = Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i))<<32;
pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i + 4)); 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() { private void clearIds() {

View File

@@ -16,6 +16,7 @@ import net.caffeinemc.mods.sodium.client.util.MathUtil;
import java.util.List; 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 //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 //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 //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 // THis shouldent result in markRequestInFlight afak
if (!this.updateRouter.watch(pos, WorldEngine.UPDATE_TYPE_BLOCK_BIT)) { 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 // Used by the cleaning system to ensure memory capacity in the geometry store
int markGeometryNull(int nodeId) { public void removeNodeGeometry(long pos) {
return 0; 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 if (nodeType == NODE_TYPE_LEAF) {
void removeNodeAndChildrenRecursive(int nodeId) { //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)); (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<String> debug) { public void addDebug(List<String> debug) {
debug.add("NC/IF: " + this.activeSectionMap.size() + "/" + (this.singleRequests.count() + this.childRequests.count())); debug.add("NC/IF: " + this.activeSectionMap.size() + "/" + (this.singleRequests.count() + this.childRequests.count()));
} }
@@ -819,4 +884,8 @@ public class NodeManager {
public int getCurrentMaxNodeId() { public int getCurrentMaxNodeId() {
return this.nodeData.getEndNodeId(); return this.nodeData.getEndNodeId();
} }
public AbstractSectionGeometryManager getGeometryManager() {
return this.geometryManager;
}
} }

View File

@@ -255,8 +255,10 @@ public final class NodeStore {
{ {
int geometry = this.getNodeGeometry(nodeId); 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) 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 { } else {
z |= geometry&0xFFFFFF;//TODO: check and ensure bounds z |= geometry&0xFFFFFF;//TODO: check and ensure bounds
} }

View File

@@ -28,4 +28,9 @@ public abstract class AbstractSectionGeometryManager {
public void free() {} public void free() {}
public abstract void downloadAndRemove(int id, Consumer<BuiltSection> callback); public abstract void downloadAndRemove(int id, Consumer<BuiltSection> callback);
public abstract long getUsedCapacity();
public long getRemainingCapacity() {
return this.geometryCapacity - this.getUsedCapacity();
}
} }

View File

@@ -157,4 +157,9 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager
int getMetadataBufferId() { int getMetadataBufferId() {
return this.sectionMetadataBuffer.id; return this.sectionMetadataBuffer.id;
} }
@Override
public long getUsedCapacity() {
return this.geometry.getUsedBytes();
}
} }

View File

@@ -96,6 +96,9 @@ public class DownloadStream {
} }
this.downloadList.add(new DownloadData(buffer, addr, downloadOffset, size, resultConsumer)); this.downloadList.add(new DownloadData(buffer, addr, downloadOffset, size, resultConsumer));
//TODO: maybe not auto-commit
this.commit();
} }

View File

@@ -98,7 +98,7 @@ public class ContextSelectionSystem {
} }
public WorldEngine createEngine(ServiceThreadPool serviceThreadPool) { 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? //Saves the config for the world selection or something, need to figure out how to make it work with dimensional configs maybe?

View File

@@ -14,6 +14,8 @@ import java.util.Arrays;
//Use an LMDB backend to store the world, use a local inmemory cache for lod sections //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 // automatically manages and invalidates sections of the world as needed
public class WorldEngine { 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_BLOCK_BIT = 1;
public static final int UPDATE_TYPE_CHILD_EXISTENCE_BIT = 2; 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; 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 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.maxMipLevels = maxMipLayers;
this.storage = storageBackend; this.storage = storageBackend;
this.mapper = new Mapper(this.storage); this.mapper = new Mapper(this.storage);

View File

@@ -50,5 +50,9 @@ void main() {
// minVisIds[gl_GlobalInvocationID.x] = visiblity[gl_GlobalInvocationID.x]; // minVisIds[gl_GlobalInvocationID.x] = visiblity[gl_GlobalInvocationID.x];
//} //}
//First do a min sort/set of min OUTPUT_SIZE values of the set //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);
} }