Work on nodes
This commit is contained in:
@@ -2,12 +2,11 @@ package me.cortex.voxy.client.core.rendering.hierachical2;
|
|||||||
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
||||||
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.building.SectionPositionUpdateFilterer;
|
import me.cortex.voxy.client.core.rendering.building.SectionPositionUpdateFilterer;
|
||||||
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
|
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
|
||||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
import me.cortex.voxy.client.core.util.ExpandingObjectAllocationList;
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.jellysquid.mods.sodium.client.util.MathUtil;
|
import me.jellysquid.mods.sodium.client.util.MathUtil;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
@@ -15,10 +14,10 @@ import org.lwjgl.system.MemoryUtil;
|
|||||||
public class HierarchicalNodeManager {
|
public class HierarchicalNodeManager {
|
||||||
public static final int NODE_MSK = ((1<<24)-1);
|
public static final int NODE_MSK = ((1<<24)-1);
|
||||||
public final int maxNodeCount;
|
public final int maxNodeCount;
|
||||||
private final long[] localNodeData;
|
private final NodeStore nodeData;
|
||||||
private final AbstractSectionGeometryManager geometryManager;
|
|
||||||
private final HierarchicalBitSet allocationSet;
|
|
||||||
private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap();
|
private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap();
|
||||||
|
private final ExpandingObjectAllocationList<LeafExpansionRequest> leafRequests = new ExpandingObjectAllocationList<>(LeafExpansionRequest[]::new);
|
||||||
|
private final AbstractSectionGeometryManager geometryManager;
|
||||||
private final SectionPositionUpdateFilterer updateFilterer;
|
private final SectionPositionUpdateFilterer updateFilterer;
|
||||||
public HierarchicalNodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, SectionPositionUpdateFilterer updateFilterer) {
|
public HierarchicalNodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, SectionPositionUpdateFilterer updateFilterer) {
|
||||||
if (!MathUtil.isPowerOfTwo(maxNodeCount)) {
|
if (!MathUtil.isPowerOfTwo(maxNodeCount)) {
|
||||||
@@ -27,36 +26,105 @@ public class HierarchicalNodeManager {
|
|||||||
if (maxNodeCount>(1<<24)) {
|
if (maxNodeCount>(1<<24)) {
|
||||||
throw new IllegalArgumentException("Max node count cannot exceed 2^24");
|
throw new IllegalArgumentException("Max node count cannot exceed 2^24");
|
||||||
}
|
}
|
||||||
|
this.activeSectionMap.defaultReturnValue(-1);
|
||||||
this.updateFilterer = updateFilterer;
|
this.updateFilterer = updateFilterer;
|
||||||
this.allocationSet = new HierarchicalBitSet(maxNodeCount);
|
|
||||||
this.maxNodeCount = maxNodeCount;
|
this.maxNodeCount = maxNodeCount;
|
||||||
this.localNodeData = new long[maxNodeCount*4];
|
this.nodeData = new NodeStore(maxNodeCount);
|
||||||
this.geometryManager = geometryManager;
|
this.geometryManager = geometryManager;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
new Thread(()->{
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
for(int x = -1; x<=1;x++) {
|
for(int x = -1; x<=1;x++) {
|
||||||
for (int z = -1; z <= 1; z++) {
|
for (int z = -1; z <= 1; z++) {
|
||||||
for (int y = -3; y <= 3; y++) {
|
for (int y = -3; y <= 3; y++) {
|
||||||
updateFilterer.watch(0,x,y,z);
|
updateFilterer.watch(4,x,y,z);
|
||||||
|
updateFilterer.unwatch(4,x,y,z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void processRequestQueue(int count, long ptr) {
|
public void processRequestQueue(int count, long ptr) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int requestIndex = 0; requestIndex < count; requestIndex++) {
|
||||||
int op = MemoryUtil.memGetInt(ptr+(i*4L));
|
int op = MemoryUtil.memGetInt(ptr + (requestIndex * 4L));
|
||||||
int node = op&NODE_MSK;
|
this.processRequest(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRequest(int op) {
|
||||||
|
int node = op&NODE_MSK;
|
||||||
|
if (!this.nodeData.nodeExists(node)) {
|
||||||
|
throw new IllegalStateException("Tried processing a node that doesnt exist: " + node);
|
||||||
|
}
|
||||||
|
if (this.nodeData.nodeRequestInFlight(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);
|
||||||
|
|
||||||
|
long pos = this.nodeData.nodePosition(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
|
||||||
|
if (this.nodeData.isLeafNode(node)) {
|
||||||
|
//TODO: the localNodeData should have a bitset of what children are definitely empty
|
||||||
|
// use that to msk the request, HOWEVER there is a race condition e.g.
|
||||||
|
// leaf node is requested and has only 1 child marked as non empty
|
||||||
|
// however then an update occures and a different child now becomes non empty,
|
||||||
|
// this will trigger a processBuildResult for parent
|
||||||
|
// so need to ensure that when that happens, if the parent has an inflight leaf expansion request
|
||||||
|
// for the leaf request to be updated to account for the new maybe child node
|
||||||
|
// NOTE: a section can have empty geometry but some of its children might not, so need to mark and
|
||||||
|
// submit a node at that level but with empty section, (specially marked) so that the traversal
|
||||||
|
// can recurse into those children as needed
|
||||||
|
|
||||||
|
//Enqueue a leaf expansion request
|
||||||
|
var request = new LeafExpansionRequest(pos);
|
||||||
|
int requestId = this.leafRequests.put(request);
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
long childPos = makeChildPos(pos, i);
|
||||||
|
//Insert all the children into the tracking map with the node id
|
||||||
|
this.activeSectionMap.put(childPos, 0);
|
||||||
|
}
|
||||||
|
} 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processBuildResult(BuiltSection section) {
|
public void processBuildResult(BuiltSection section) {
|
||||||
if (!section.isEmpty()) {
|
int nodeId = this.activeSectionMap.get(section.position);
|
||||||
this.geometryManager.uploadSection(section);
|
if (nodeId == -1) {
|
||||||
|
//Not tracked or mapped to a node!!!
|
||||||
} else {
|
} else {
|
||||||
section.free();
|
//Part of a request (top bit is set to 1)
|
||||||
|
if ((nodeId&(1<<31))!=0) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Not part of a request, just a node update,
|
||||||
|
// however could result in a reallocation if it needs to mark a child position as being possibly visible
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package me.cortex.voxy.client.core.rendering.hierachical2;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||||
|
|
||||||
|
public final class NodeStore {
|
||||||
|
private final HierarchicalBitSet allocationSet;
|
||||||
|
private final long[] localNodeData;
|
||||||
|
public NodeStore(int maxNodeCount) {
|
||||||
|
this.localNodeData = new long[maxNodeCount*4];
|
||||||
|
this.allocationSet = new HierarchicalBitSet(maxNodeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long nodePosition(int nodeId) {
|
||||||
|
return this.localNodeData[nodeId<<2];
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean nodeExists(int nodeId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void markRequestInFlight(int nodeId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean nodeRequestInFlight(int nodeId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLeafNode(int nodeId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
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.BuiltSection;
|
||||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||||
import me.cortex.voxy.client.core.rendering.util.MarkedObjectList;
|
import me.cortex.voxy.client.core.util.MarkedCachedObjectList;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
@@ -302,7 +302,7 @@ public class NodeManager {
|
|||||||
|
|
||||||
//The request queue should be like some array that can reuse objects to prevent gc nightmare + like a bitset to find an avalible free slot
|
//The request queue should be like some array that can reuse objects to prevent gc nightmare + like a bitset to find an avalible free slot
|
||||||
// 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 MarkedCachedObjectList<LeafRequest> leafRequests = new MarkedCachedObjectList<>(LeafRequest[]::new, LeafRequest::new);
|
||||||
|
|
||||||
private static int pos2octnode(long pos) {
|
private static int pos2octnode(long pos) {
|
||||||
return (WorldEngine.getX(pos)&1)|((WorldEngine.getY(pos)&1)<<1)|((WorldEngine.getZ(pos)&1)<<2);
|
return (WorldEngine.getX(pos)&1)|((WorldEngine.getY(pos)&1)<<1)|((WorldEngine.getZ(pos)&1)<<2);
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
|
|||||||
glBindVertexArray(RenderService.STATIC_VAO);//Needs to be before binding
|
glBindVertexArray(RenderService.STATIC_VAO);//Needs to be before binding
|
||||||
this.bindRenderingBuffers();
|
this.bindRenderingBuffers();
|
||||||
|
|
||||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 4*3, Math.min((int)(this.geometryManager.getSectionCount()*4.4), 400_000), 0);
|
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 4*3, Math.min((int)(this.geometryManager.getSectionCount()*4.4+128), 400_000), 0);
|
||||||
|
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package me.cortex.voxy.client.core.util;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
|
||||||
|
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||||
|
|
||||||
|
public class ExpandingObjectAllocationList<T> {
|
||||||
|
private static final float GROWTH_FACTOR = 0.75f;
|
||||||
|
|
||||||
|
private final Int2ObjectFunction<T[]> arrayGenerator;
|
||||||
|
private final HierarchicalBitSet bitSet = new HierarchicalBitSet();
|
||||||
|
private T[] objects;//Should maybe make a getter function instead
|
||||||
|
|
||||||
|
public ExpandingObjectAllocationList(Int2ObjectFunction<T[]> arrayGenerator) {
|
||||||
|
this.arrayGenerator = arrayGenerator;
|
||||||
|
this.objects = this.arrayGenerator.apply(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int put(T obj) {
|
||||||
|
//Gets an unused id for some entry in objects, if its null fill it
|
||||||
|
int id = this.bitSet.allocateNext();
|
||||||
|
if (this.objects.length <= id) {
|
||||||
|
//Resize and copy over the objects array
|
||||||
|
int newLen = this.objects.length + (int)Math.ceil(this.objects.length*GROWTH_FACTOR);
|
||||||
|
T[] newArr = this.arrayGenerator.apply(newLen);
|
||||||
|
System.arraycopy(this.objects, 0, newArr, 0, this.objects.length);
|
||||||
|
this.objects = newArr;
|
||||||
|
}
|
||||||
|
this.objects[id] = obj;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release(int id) {
|
||||||
|
if (!this.bitSet.free(id)) {
|
||||||
|
throw new IllegalArgumentException("Index " + id + " was already released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T get(int index) {
|
||||||
|
//Make the checking that index is allocated optional, as it might cause overhead due to multiple cacheline misses
|
||||||
|
if (!this.bitSet.isSet(index)) {
|
||||||
|
throw new IllegalArgumentException("Index " + index + " is not allocated");
|
||||||
|
}
|
||||||
|
return this.objects[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.util;
|
package me.cortex.voxy.client.core.util;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
|
||||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class MarkedObjectList<T> {
|
public class MarkedCachedObjectList<T> {
|
||||||
private static final float GROWTH_FACTOR = 0.75f;
|
private static final float GROWTH_FACTOR = 0.75f;
|
||||||
|
|
||||||
private final Int2ObjectFunction<T[]> arrayGenerator;
|
private final Int2ObjectFunction<T[]> arrayGenerator;
|
||||||
@@ -13,7 +13,7 @@ public class MarkedObjectList<T> {
|
|||||||
private final HierarchicalBitSet bitSet = new HierarchicalBitSet();
|
private final HierarchicalBitSet bitSet = new HierarchicalBitSet();
|
||||||
private T[] objects;//Should maybe make a getter function instead
|
private T[] objects;//Should maybe make a getter function instead
|
||||||
|
|
||||||
public MarkedObjectList(Int2ObjectFunction<T[]> arrayGenerator, Supplier<T> nullSupplier) {
|
public MarkedCachedObjectList(Int2ObjectFunction<T[]> arrayGenerator, Supplier<T> nullSupplier) {
|
||||||
this.arrayGenerator = arrayGenerator;
|
this.arrayGenerator = arrayGenerator;
|
||||||
this.nullSupplier = nullSupplier;
|
this.nullSupplier = nullSupplier;
|
||||||
this.objects = this.arrayGenerator.apply(16);
|
this.objects = this.arrayGenerator.apply(16);
|
||||||
Reference in New Issue
Block a user