diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/DefaultGeometryManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/DefaultGeometryManager.java index ac685683..8dbc6036 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/DefaultGeometryManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/DefaultGeometryManager.java @@ -42,7 +42,7 @@ public class DefaultGeometryManager extends AbstractGeometryManager { this.markSectionIds.clear(); while (!this.buildResults.isEmpty()) { var result = this.buildResults.pop(); - boolean isDelete = result.geometryBuffer == null; + boolean isDelete = result.isEmpty(); if (isDelete) { int id = -1; if ((id = this.pos2id.remove(result.position)) != -1) { diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/BuiltSection.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/BuiltSection.java index bbac07ef..79b3cb05 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/BuiltSection.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/BuiltSection.java @@ -39,4 +39,8 @@ public final class BuiltSection { this.geometryBuffer.free(); } } + + public boolean isEmpty() { + return this.geometryBuffer == null; + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/MeshManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/MeshManager.java new file mode 100644 index 00000000..9348fc01 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/MeshManager.java @@ -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) { + + } +} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager2.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager2.java index e6249b58..eac32761 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager2.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager2.java @@ -3,6 +3,17 @@ package me.cortex.voxy.client.core.rendering.hierarchical; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import me.cortex.voxy.client.core.rendering.building.BuiltSection; 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 { //A request for making a new child nodes @@ -19,9 +30,46 @@ public class NodeManager2 { //The mask of currently supplied child node data 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 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< 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) { int id = this.pos2meshId.get(mesh.position); if (id == NO_NODE) { //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?? + + //This might be a warning? or maybe info? mesh.free(); return; } + if ((id&(1<<31))!=0) { //The mesh is part of a batched request id = id^(1<<31);//Basically abs it + int innerId = pos2octnode(mesh.position); //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) @@ -84,14 +150,46 @@ public class NodeManager2 { // 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 - //The lower 3 bits of the id specify the quadrant (8 pos) of the node in the request - LeafRequest request = this.leafRequests.get(id>>3); + LeafRequest request = this.leafRequests.get(id); + + //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 { //The mesh is an update for an existing node - 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) { //Node has a mesh attached, remove and replace it } 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 + } + }