More node stuff

This commit is contained in:
mcrcortex
2024-06-16 08:53:32 +10:00
parent 25ddb83d22
commit 90b8143808
4 changed files with 146 additions and 6 deletions

View File

@@ -42,7 +42,7 @@ public class DefaultGeometryManager extends AbstractGeometryManager {
this.markSectionIds.clear(); this.markSectionIds.clear();
while (!this.buildResults.isEmpty()) { while (!this.buildResults.isEmpty()) {
var result = this.buildResults.pop(); var result = this.buildResults.pop();
boolean isDelete = result.geometryBuffer == null; boolean isDelete = result.isEmpty();
if (isDelete) { if (isDelete) {
int id = -1; int id = -1;
if ((id = this.pos2id.remove(result.position)) != -1) { if ((id = this.pos2id.remove(result.position)) != -1) {

View File

@@ -39,4 +39,8 @@ public final class BuiltSection {
this.geometryBuffer.free(); this.geometryBuffer.free();
} }
} }
public boolean isEmpty() {
return this.geometryBuffer == null;
}
} }

View File

@@ -0,0 +1,33 @@
package me.cortex.voxy.client.core.rendering.hierarchical;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.util.BufferArena;
//Manages the actual meshes, whether they are meshlets or whole meshes, the returned values are ids into a section
// array which contains metadata about the section
public class MeshManager {
private final BufferArena geometryArena;
private final GlBuffer sectionMetaBuffer;
public MeshManager() {
this.geometryArena = null;
this.sectionMetaBuffer = null;
}
//Uploads the section geometry to the arena, there can be multiple meshes for the same geometry in the arena at the same time
// it is not the MeshManagers responsiblity
//The return value is arbitary as long as it can identify the mesh its uploaded until it is freed
public int uploadMesh(BuiltSection section) {
return uploadReplaceMesh(-1, section);
}
//Varient of uploadMesh that releases the previous mesh at the same time, this is a performance optimization
public int uploadReplaceMesh(int old, BuiltSection section) {
return -1;
}
public void removeMesh(int mesh) {
}
}

View File

@@ -3,6 +3,17 @@ package me.cortex.voxy.client.core.rendering.hierarchical;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import me.cortex.voxy.client.core.rendering.building.BuiltSection; import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.util.MarkedObjectList; import me.cortex.voxy.client.core.rendering.util.MarkedObjectList;
import me.cortex.voxy.common.world.WorldEngine;
import java.util.Arrays;
//TODO:FIXME: TODO, Must fix/have some filtering for section updates based on time or something
// as there can be a cursed situation where an update occures requiring expensive meshing for a section but then in the same
// tick it becomes air requiring no meshing thus basicly instantly emitting a result
//TODO: Make it an sparse voxel octree, that is if all of the children bar 1 are empty/air, remove the self node and replace it with the non empty child
public class NodeManager2 { public class NodeManager2 {
//A request for making a new child nodes //A request for making a new child nodes
@@ -19,9 +30,46 @@ public class NodeManager2 {
//The mask of currently supplied child node data //The mask of currently supplied child node data
public byte currentChildMask; public byte currentChildMask;
//Mesh ids for all the child nodes
private final int[] meshIds = new int[8];
{Arrays.fill(this.meshIds, -1);}
//Positions for all the child nodes, should make SVO easier
private final long[] childPositions = new long[8];
{Arrays.fill(this.childPositions, -1);}
//Reset/clear the request so that it may be reused //Reset/clear the request so that it may be reused
public void clear() { public void clear() {
this.position = 0;
this.nodeId = 0;
this.requiredChildMask = 0;
this.currentChildMask = 0;
Arrays.fill(this.meshIds, -1);
Arrays.fill(this.childPositions, -1);
}
//Returns true if the request is satisfied
public boolean isSatisfied() {
return (this.requiredChildMask&this.currentChildMask)==this.requiredChildMask;
}
public int getMeshId(int inner) {
if (!this.isSet(inner)) {
return -1;
}
return this.meshIds[inner];
}
public boolean isSet(int inner) {
return (this.currentChildMask&(1<<inner))!=0;
}
public void put(int innerId, int mesh, long position) {
this.currentChildMask |= (byte) (1<<innerId);
this.meshIds[innerId] = mesh;
this.childPositions[innerId] = position;
} }
} }
@@ -33,15 +81,20 @@ public class NodeManager2 {
private final long[] localNodeData = new long[MAX_NODE_COUNT * 3]; private final long[] localNodeData = new long[MAX_NODE_COUNT * 3];
private final INodeInteractor interactor; private final INodeInteractor interactor;
private final MeshManager meshManager;
public NodeManager2(INodeInteractor interactor) { public NodeManager2(INodeInteractor interactor, MeshManager meshManager) {
this.interactor = interactor; this.interactor = interactor;
this.pos2meshId.defaultReturnValue(NO_NODE); this.pos2meshId.defaultReturnValue(NO_NODE);
this.interactor.setMeshUpdateCallback(this::meshUpdate); this.interactor.setMeshUpdateCallback(this::meshUpdate);
this.meshManager = meshManager;
} }
public void insertTopLevelNode(long position) { public void insertTopLevelNode(long position) {
//NOTE! when initally adding a top level node, set it to air and request a meshing of the mesh
// (if the mesh returns as air uhhh idk what to do cause a top level air node is kinda... not valid but eh)
// that way the node will replace itself with its meshed varient when its ready aswell as prevent
// the renderer from exploding, as it should ignore the empty sections entirly
} }
public void removeTopLevelNode(long position) { public void removeTopLevelNode(long position) {
@@ -64,18 +117,31 @@ public class NodeManager2 {
// hashmap might work bar the gc overhead // hashmap might work bar the gc overhead
private final MarkedObjectList<LeafRequest> leafRequests = new MarkedObjectList<>(LeafRequest[]::new, LeafRequest::new); private final MarkedObjectList<LeafRequest> leafRequests = new MarkedObjectList<>(LeafRequest[]::new, LeafRequest::new);
private static int pos2octnode(long pos) {
return (WorldEngine.getX(pos)&1)|((WorldEngine.getY(pos)&1)<<1)|((WorldEngine.getZ(pos)&1)<<2);
}
//TODO: if all the children of a node become empty/removed traverse up the chain until a non empty parent node is hit and
// remove all from the chain
//TODO: test and fix the possible race condition of if a section is not empty then becomes empty in the same tick
// that is, there is a request that is satisfied bar 1 section, that section is supplied as non emptpty but then becomes empty in the same tick
private void meshUpdate(BuiltSection mesh) { private void meshUpdate(BuiltSection mesh) {
int id = this.pos2meshId.get(mesh.position); int id = this.pos2meshId.get(mesh.position);
if (id == NO_NODE) { if (id == NO_NODE) {
//The built mesh section is no longer needed, discard it //The built mesh section is no longer needed, discard it
// TODO: could probably?? cache the mesh in ram that way if its requested? it can be immediatly fetched while a newer mesh is built?? // TODO: could probably?? cache the mesh in ram that way if its requested? it can be immediatly fetched while a newer mesh is built??
//This might be a warning? or maybe info?
mesh.free(); mesh.free();
return; return;
} }
if ((id&(1<<31))!=0) { if ((id&(1<<31))!=0) {
//The mesh is part of a batched request //The mesh is part of a batched request
id = id^(1<<31);//Basically abs it id = id^(1<<31);//Basically abs it
int innerId = pos2octnode(mesh.position);
//There are a few cases for this branch //There are a few cases for this branch
// the section could be replacing an existing mesh that is part of the request (due to an update) // the section could be replacing an existing mesh that is part of the request (due to an update)
@@ -84,14 +150,46 @@ public class NodeManager2 {
// in which case! we must either A) mark the request as ready to be uploaded // in which case! we must either A) mark the request as ready to be uploaded
// and then uploaded after all the mesh updates are processed, or upload it immediately // and then uploaded after all the mesh updates are processed, or upload it immediately
//The lower 3 bits of the id specify the quadrant (8 pos) of the node in the request LeafRequest request = this.leafRequests.get(id);
LeafRequest request = this.leafRequests.get(id>>3);
//TODO: Get the mesh id if a mesh for the request at the same pos has already been submitted
// then call meshManager.uploadReplaceMesh to get the new id, then put that into the request
//TODO: could basicly make it a phase, where it then enqueues finished requests that then get uploaded later
// that is dont immediatly submit request results, wait until the end of the frame
// NOTE: COULD DO THIS WITH MESH RESULTS TOO, or could prefilter them per batch/tick
int meshId;
int prevMeshId = request.getMeshId(innerId);
if (mesh.isEmpty()) {
//since its empty, remove the previous mesh if it existed
if (prevMeshId != -1) {
this.meshManager.removeMesh(prevMeshId);
}
meshId = -1;//FIXME: this is a hack to still result in the mesh being put in, but it is an empty mesh upload
} else {
if (prevMeshId != -1) {
meshId = this.meshManager.uploadReplaceMesh(prevMeshId, mesh);
} else {
meshId = this.meshManager.uploadMesh(mesh);
}
}
request.put(innerId, meshId, mesh.position);
if (request.isSatisfied()) {
//If request is now satisfied update the internal nodes, create the children and reset + release the request set
this.completeRequest(request);
//Reset + release
request.clear();
this.leafRequests.release(id);
}
//If the request is not yet satisfied, that is ok, continue ingesting new meshes until it is satisfied
} else { } else {
//The mesh is an update for an existing node //The mesh is an update for an existing node
int prevMesh = this.getMeshForNode(id); int prevMesh = this.getMeshForNode(id);
// TODO: If the mesh to upload is air, the node should be removed (however i believe this is only true if all the children are air! fuuuuu)
if (prevMesh != -1) { if (prevMesh != -1) {
//Node has a mesh attached, remove and replace it //Node has a mesh attached, remove and replace it
} else { } else {
@@ -100,4 +198,9 @@ public class NodeManager2 {
} }
} }
private void completeRequest(LeafRequest request) {
//TODO: need to actually update all of the pos2meshId of the children to point to there new nodes
}
} }