Work on geometry cleaning
This commit is contained in:
@@ -49,7 +49,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, 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();
|
||||
|
||||
@@ -73,6 +73,7 @@ public class SectionUpdateRouter {
|
||||
set.remove(position);
|
||||
return true;
|
||||
}
|
||||
set.put(position, current);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -28,4 +28,9 @@ public abstract class AbstractSectionGeometryManager {
|
||||
public void free() {}
|
||||
|
||||
public abstract void downloadAndRemove(int id, Consumer<BuiltSection> callback);
|
||||
|
||||
public abstract long getUsedCapacity();
|
||||
public long getRemainingCapacity() {
|
||||
return this.geometryCapacity - this.getUsedCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,4 +157,9 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager
|
||||
int getMetadataBufferId() {
|
||||
return this.sectionMetadataBuffer.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUsedCapacity() {
|
||||
return this.geometry.getUsedBytes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,9 @@ public class DownloadStream {
|
||||
}
|
||||
|
||||
this.downloadList.add(new DownloadData(buffer, addr, downloadOffset, size, resultConsumer));
|
||||
|
||||
//TODO: maybe not auto-commit
|
||||
this.commit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user