Ref C
This commit is contained in:
@@ -1,165 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering.hierarchical;
|
||||
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
|
||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.nglClearNamedBufferSubData;
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
|
||||
import static org.lwjgl.opengl.GL30.GL_R32UI;
|
||||
import static org.lwjgl.opengl.GL30.glBindBufferBase;
|
||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||
import static org.lwjgl.opengl.GL33.glGenSamplers;
|
||||
import static org.lwjgl.opengl.GL43.*;
|
||||
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
||||
|
||||
public class HierarchicalOcclusionRenderer {
|
||||
private final HiZBuffer hiz = new HiZBuffer();
|
||||
|
||||
private final int hizSampler = glGenSamplers();
|
||||
|
||||
public final NodeManager nodeManager;
|
||||
private final Shader hierarchicalTraversal;
|
||||
private final PrintfInjector printf;
|
||||
|
||||
private final GlBuffer nodeQueueA;
|
||||
private final GlBuffer nodeQueueB;
|
||||
private final GlBuffer uniformBuffer;
|
||||
|
||||
public HierarchicalOcclusionRenderer(INodeInteractor interactor, MeshManager mesh, PrintfInjector printf) {
|
||||
this.nodeManager = new NodeManager(interactor, mesh);
|
||||
this.nodeQueueA = new GlBuffer(1000000*4+4).zero();
|
||||
this.nodeQueueB = new GlBuffer(1000000*4+4).zero();
|
||||
this.uniformBuffer = new GlBuffer(1024).zero();
|
||||
this.printf = printf;
|
||||
this.hierarchicalTraversal = Shader.make(printf)
|
||||
.define("IS_DEBUG")
|
||||
.add(ShaderType.COMPUTE, "voxy:lod/hierarchical/traversal.comp")
|
||||
.compile();
|
||||
}
|
||||
|
||||
|
||||
private void uploadUniform(Viewport<?> viewport) {
|
||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
|
||||
int sx = MathHelper.floor(viewport.cameraX)>>5;
|
||||
int sy = MathHelper.floor(viewport.cameraY)>>5;
|
||||
int sz = MathHelper.floor(viewport.cameraZ)>>5;
|
||||
|
||||
new Matrix4f(viewport.projection).mul(viewport.modelView).getToAddress(ptr); ptr += 4*4*4;
|
||||
|
||||
MemoryUtil.memPutInt(ptr, sx); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, sy); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, sz); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, viewport.width); ptr += 4;
|
||||
|
||||
var innerTranslation = new Vector3f((float) (viewport.cameraX-(sx<<5)), (float) (viewport.cameraY-(sy<<5)), (float) (viewport.cameraZ-(sz<<5)));
|
||||
innerTranslation.getToAddress(ptr); ptr += 4*3;
|
||||
|
||||
MemoryUtil.memPutInt(ptr, viewport.height); ptr += 4;
|
||||
|
||||
MemoryUtil.memPutInt(ptr, NodeManager.REQUEST_QUEUE_SIZE); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, 1000000); ptr += 4;
|
||||
|
||||
//decendSSS (decend screen space size)
|
||||
MemoryUtil.memPutFloat(ptr, 64*64); ptr += 4;
|
||||
}
|
||||
|
||||
public void doHierarchicalTraversalSelection(Viewport<?> viewport, int depthBuffer, GlBuffer renderSelectionResult, GlBuffer debugNodeOutput) {
|
||||
this.uploadUniform(viewport);
|
||||
this.nodeManager.upload();
|
||||
|
||||
{
|
||||
int cnt = this.nodeManager.rootPos2Id.size();
|
||||
long ptr = UploadStream.INSTANCE.upload(this.nodeQueueA, 0, 4+cnt*4L);
|
||||
MemoryUtil.memPutInt(ptr, cnt); ptr += 4;
|
||||
for (int i : this.nodeManager.rootPos2Id.values()) {
|
||||
MemoryUtil.memPutInt(ptr, i); ptr += 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UploadStream.INSTANCE.commit();
|
||||
|
||||
//FIXME: need to have the hiz respect the stencil mask aswell to mask away normal terrain, (much increase perf)
|
||||
|
||||
//Make hiz
|
||||
this.hiz.buildMipChain(depthBuffer, viewport.width, viewport.height);
|
||||
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT);
|
||||
this.hierarchicalTraversal.bind();
|
||||
|
||||
//Clear the render counter
|
||||
nglClearNamedBufferSubData(renderSelectionResult.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
nglClearNamedBufferSubData(debugNodeOutput.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
|
||||
{
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.nodeManager.nodeBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueA.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.nodeManager.requestQueue.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, renderSelectionResult.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueB.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, debugNodeOutput.id);
|
||||
|
||||
//Bind the hiz buffer
|
||||
glBindSampler(0, this.hizSampler);
|
||||
glBindTextureUnit(0, this.hiz.getHizTextureId());
|
||||
}
|
||||
this.printf.bind();
|
||||
{
|
||||
//Dispatch hierarchies
|
||||
nglClearNamedBufferSubData(this.nodeQueueB.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueA.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueB.id);
|
||||
glDispatchCompute(21*21*2,1,1);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
nglClearNamedBufferSubData(this.nodeQueueA.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueB.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueA.id);
|
||||
glDispatchCompute(21*21*8,1,1);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
nglClearNamedBufferSubData(this.nodeQueueB.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueA.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueB.id);
|
||||
glDispatchCompute(21*21*32,1,1);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
nglClearNamedBufferSubData(this.nodeQueueA.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueB.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueA.id);
|
||||
glDispatchCompute(21*21*128,1,1);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
nglClearNamedBufferSubData(this.nodeQueueB.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.nodeQueueA.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.nodeQueueB.id);
|
||||
glDispatchCompute(21*21*8,1,1);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
}
|
||||
|
||||
glBindSampler(0, 0);
|
||||
glBindTextureUnit(0, 0);
|
||||
this.nodeManager.download();
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.nodeQueueA.free();
|
||||
this.nodeQueueB.free();
|
||||
this.hiz.free();
|
||||
this.nodeManager.free();
|
||||
glDeleteSamplers(this.hizSampler);
|
||||
}
|
||||
|
||||
public GlBuffer getNodeDataBuffer() {
|
||||
return this.nodeManager.nodeBuffer;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering.hierarchical;
|
||||
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
//Interface for node manager to interact with the outside world
|
||||
public interface INodeInteractor {
|
||||
void watchUpdates(long pos);//marks pos as watching for updates, i.e. any LoD updates will trigger a callback
|
||||
void unwatchUpdates(long pos);//Unmarks a position for updates
|
||||
|
||||
void requestMesh(long pos);//Explicitly requests a mesh at a position, run the callback
|
||||
|
||||
void setMeshUpdateCallback(Consumer<BuiltSection> mesh);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering.hierarchical;
|
||||
|
||||
public interface ITrimInterface {
|
||||
//Last recorded/known use time of a nodes mesh, returns -1 if node doesnt have a mesh
|
||||
int lastUsedTime(int node);
|
||||
|
||||
//Returns an integer with the bottom 24 bits being the ptr top 8 bits being count or something
|
||||
int getChildren(int node);
|
||||
|
||||
//Returns a size of the nodes mesh, -1 if the node doesnt have a mesh
|
||||
int getNodeSize(int node);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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) {
|
||||
section.free();
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void removeMesh(int mesh) {
|
||||
|
||||
}
|
||||
|
||||
public void free() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering.hierarchical;
|
||||
|
||||
//Uses a persistently mapped coherient buffer with off thread polling to pull in requests
|
||||
public class NodeLoadSystem {
|
||||
}
|
||||
@@ -1,510 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering.hierarchical;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
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.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
|
||||
import static org.lwjgl.opengl.GL30.GL_R32UI;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL45.nglClearNamedBufferSubData;
|
||||
|
||||
|
||||
//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 NodeManager {
|
||||
//A request for making a new child nodes
|
||||
private static final class LeafRequest {
|
||||
//LoD position identifier
|
||||
public long position;
|
||||
|
||||
//Node id of the node the leaf request is for, note! While there is a leaf request, the node should not be unloaded or removed
|
||||
public int nodeId;
|
||||
|
||||
//The mask of what child nodes are required
|
||||
//public byte requiredChildMask;
|
||||
|
||||
//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.currentChildMask == -1;//(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;
|
||||
}
|
||||
|
||||
public void make(int id, long position) {
|
||||
this.nodeId = id;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public byte nonAirMask() {
|
||||
byte res = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (this.meshIds[i] != -1) {
|
||||
res |= (byte) (1<<i);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public static final int MAX_NODE_COUNT = 1<<22;
|
||||
public static final int MAX_MESH_ID = 1<<24;
|
||||
|
||||
public static final int REQUEST_QUEUE_SIZE = 1024;
|
||||
public static final int MESH_MSK = MAX_MESH_ID-1;
|
||||
public static final int EMPTY_MESH_ID = MESH_MSK-1;
|
||||
public static final int NODE_MSK = (1<<24)-1;//NOTE!! IS DIFFERENT FROM MAX_NODE_COUNT as the MAX_NODE_COUNT is for the buffers, the ACTUAL MAX is NODE_MSK+1
|
||||
|
||||
//Local data layout
|
||||
// first long is position
|
||||
// next long contains mesh position ig/id
|
||||
private final long[] localNodeData = new long[MAX_NODE_COUNT * 3];
|
||||
|
||||
private final HierarchicalBitSet nodeAllocations = new HierarchicalBitSet(MAX_NODE_COUNT);
|
||||
|
||||
private final INodeInteractor interactor;
|
||||
private final MeshManager meshManager;
|
||||
|
||||
public final GlBuffer nodeBuffer;
|
||||
public final GlBuffer requestQueue;
|
||||
|
||||
public NodeManager(INodeInteractor interactor, MeshManager meshManager) {
|
||||
this.interactor = interactor;
|
||||
this.pos2meshId.defaultReturnValue(NO_NODE);
|
||||
this.interactor.setMeshUpdateCallback(this::meshUpdate);
|
||||
this.meshManager = meshManager;
|
||||
this.nodeBuffer = new GlBuffer(MAX_NODE_COUNT*16);
|
||||
this.requestQueue = new GlBuffer(REQUEST_QUEUE_SIZE*4+4);
|
||||
Arrays.fill(this.localNodeData, 0);
|
||||
}
|
||||
|
||||
public final Long2IntOpenHashMap rootPos2Id = new Long2IntOpenHashMap();
|
||||
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
|
||||
this.rootPosRequests.add(position);
|
||||
this.interactor.watchUpdates(position);
|
||||
this.interactor.requestMesh(position);
|
||||
|
||||
}
|
||||
|
||||
public void removeTopLevelNode(long position) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//Returns the mesh offset/id for the given node or -1 if it doesnt exist
|
||||
private int getNodeMesh(int node) {
|
||||
return (int) (this.localNodeData[node*3+1]&MESH_MSK);
|
||||
}
|
||||
|
||||
private int getNodeChildPtr(int node) {
|
||||
return (int) ((this.localNodeData[node*3+1]>>>32)&NODE_MSK);
|
||||
}
|
||||
|
||||
private int getNodeChildCnt(int node) {
|
||||
return (int) ((this.localNodeData[node*3+1]>>>61)&7)+1;
|
||||
}
|
||||
|
||||
private long getNodePos(int node) {
|
||||
return this.localNodeData[node*3];
|
||||
}
|
||||
|
||||
private boolean isLeafNode(int node) {
|
||||
//TODO: maybe make this flag based instead of checking the child ptr?
|
||||
return this.getNodeChildPtr(node) == NODE_MSK;
|
||||
}
|
||||
|
||||
//Its ment to return if the node is just an empty mesh or if all the children are also empty
|
||||
private boolean isEmptyNode(int node) {
|
||||
return this.getNodeMesh(node)==EMPTY_MESH_ID;//Special case/reserved
|
||||
}
|
||||
|
||||
private void setNodePosition(int node, long position) {
|
||||
this.localNodeData[node*3] = position;
|
||||
}
|
||||
|
||||
private void setMeshId(int node, int mesh) {
|
||||
if (mesh > MESH_MSK) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
long val = this.localNodeData[node*3+1];
|
||||
val &= ~MESH_MSK;
|
||||
val |= mesh;
|
||||
this.localNodeData[node*3+1] = val;
|
||||
}
|
||||
|
||||
private void setChildPtr(int node, int childPtr, int count) {
|
||||
if (childPtr > NODE_MSK || ((childPtr!=NODE_MSK)&&count < 1)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
long val = this.localNodeData[node*3+1];
|
||||
//Put the count
|
||||
val &= ~(0x7L<<61);
|
||||
val |= Integer.toUnsignedLong(Math.max(count-1,0))<<61;
|
||||
//Put the child ptr
|
||||
val &= ~(Integer.toUnsignedLong(NODE_MSK)<<32);
|
||||
val |= Integer.toUnsignedLong(childPtr) << 32;
|
||||
this.localNodeData[node*3+1] = val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//The idea is, since a graph node can be in effectivly only 3 states, if inner node -> may or may not have mesh, and, if leaf node -> has mesh, no children
|
||||
// the request queue only needs to supply the node id, since if its an inner node, it must be requesting for a mesh, while if its a leaf node, it must be requesting for children
|
||||
private void processRequestQueue(long ptr, long size) {
|
||||
int count = MemoryUtil.memGetInt(ptr); ptr += 4;
|
||||
if (count > REQUEST_QUEUE_SIZE*1.5) {
|
||||
System.err.println("CORRUPTED PROCESS REQUEST, IGNORING (had count of: "+count+")");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int requestOp = MemoryUtil.memGetInt(ptr + i*4L);
|
||||
int node = requestOp&NODE_MSK;
|
||||
//System.out.println("Got request for node: " + node);
|
||||
if (WorldEngine.getLevel(this.getNodePos(node)) == 0) {
|
||||
System.err.println("Got a request for node at level 0: " + node + " pos: " + this.getNodePos(node));
|
||||
continue;
|
||||
}
|
||||
if (this.isLeafNode(node)) {
|
||||
//If its a leaf node and it has a request, it must need the children
|
||||
if (this.getNodeMesh(node) == -1) {
|
||||
throw new IllegalStateException("Leaf node doesnt have mesh");
|
||||
}
|
||||
//Create a new request
|
||||
int idx = this.leafRequests.allocate();
|
||||
var request = this.leafRequests.get(idx);
|
||||
|
||||
{
|
||||
long nodePos = this.getNodePos(node);
|
||||
request.make(node, nodePos);//Request all child nodes
|
||||
int requestIdx = idx|(1<<31);//First bit is set to 1 to indicate a request index instead of a node index
|
||||
|
||||
//Loop over all child positions and insert them into the queue
|
||||
for (int j = 0; j < 8; j++) {
|
||||
long child = makeChildPos(nodePos, j);
|
||||
int prev = this.pos2meshId.putIfAbsent(child, requestIdx);
|
||||
if (prev != NO_NODE) {
|
||||
throw new IllegalArgumentException("Node pos already in request map");
|
||||
}
|
||||
//Mark the position as watching and force request an update
|
||||
this.interactor.watchUpdates(child);
|
||||
this.interactor.requestMesh(child);
|
||||
}
|
||||
}
|
||||
//NOTE: dont unmark the node yet, as the request hasnt been satisfied
|
||||
|
||||
} else {
|
||||
//If its not a leaf node, it must be missing the inner mesh so request it
|
||||
if (this.getNodeMesh(node) != MESH_MSK) {
|
||||
//Node already has a mesh, ignore it, but might be a sign that an error has occured
|
||||
throw new IllegalStateException("Requested a mesh for node, however the node already has a mesh");
|
||||
|
||||
//TODO: need to unmark the node that requested it, either that or only clear node data when a mesh has been removed
|
||||
|
||||
} else {
|
||||
//Put it into the map + watch and request
|
||||
long pos = this.getNodePos(node);
|
||||
long prev = this.pos2meshId.putIfAbsent(pos, node);
|
||||
if (prev != NO_NODE) {
|
||||
throw new IllegalStateException("Pos already has a node id attached");
|
||||
}
|
||||
this.interactor.watchUpdates(pos);
|
||||
this.interactor.requestMesh(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//Tracking for nodes that specifically need meshes, if a node doesnt have or doesnt need a mesh node, it is not in the map
|
||||
// the map should be identical to the currently watched set of sections
|
||||
//NOTE: that if the id is negative its part of a mesh request
|
||||
private final Long2IntOpenHashMap pos2meshId = new Long2IntOpenHashMap();
|
||||
private final LongOpenHashSet rootPosRequests = new LongOpenHashSet();//
|
||||
private static final int NO_NODE = -1;
|
||||
|
||||
//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
|
||||
private final MarkedCachedObjectList<LeafRequest> leafRequests = new MarkedCachedObjectList<>(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: FIXME: CRITICAL: if a section is empty when created, it wont get allocated a slot, however, the section might
|
||||
// become unempty due to an update!!! THIS IS REALLY BAD. since it doesnt have an allocation
|
||||
|
||||
//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
|
||||
|
||||
//TODO: Fixme: need to fix/make it so that the system can know if every child (to lod0) is empty or if its just the current section
|
||||
private void meshUpdate(BuiltSection mesh) {
|
||||
int id = this.pos2meshId.get(mesh.position);
|
||||
//TODO: FIXME!! if we get a node that has an update and is watched but no id for it, it could be an update state from
|
||||
// an empty node to non empty node, this means we need to invalidate all the childrens positions and move them!
|
||||
// then also update the parent pointer
|
||||
//TODO: Also need a way to remove sections, requires shuffling stuff around
|
||||
if (id == NO_NODE) {
|
||||
//If its a top level node insertion request, insert the node
|
||||
if (this.rootPosRequests.remove(mesh.position)) {
|
||||
if (!mesh.isEmpty()) {
|
||||
int top = this.nodeAllocations.allocateNext();
|
||||
this.rootPos2Id.put(mesh.position, top);
|
||||
this.setNodePosition(top, mesh.position);
|
||||
this.setChildPtr(top, NODE_MSK, 0);
|
||||
this.setMeshId(top, this.meshManager.uploadMesh(mesh));
|
||||
this.pushNode(top);
|
||||
}
|
||||
} else {
|
||||
//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)
|
||||
// the section mesh could be new to the request
|
||||
// in this case the section mesh could be the last entry needed to satisfy the request
|
||||
// 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
|
||||
|
||||
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.completeLeafRequest(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
|
||||
|
||||
//Sanity check
|
||||
if (this.getNodePos(id) != mesh.position) {
|
||||
throw new IllegalStateException("Node position not same as mesh position");
|
||||
}
|
||||
|
||||
int prevMesh = this.getNodeMesh(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
|
||||
this.setMeshId(id, this.meshManager.uploadReplaceMesh(prevMesh, mesh));
|
||||
} else {
|
||||
//Node didnt have a mesh attached, so just set the current mesh
|
||||
this.setMeshId(id, this.meshManager.uploadMesh(mesh));
|
||||
}
|
||||
//Push the updated node to the gpu
|
||||
this.pushNode(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void completeLeafRequest(LeafRequest request) {
|
||||
//TODO: FIXME: need to make it so that if a nodes mesh is empty but there are children that exist that arnt empty
|
||||
// then it needs to still add the node, but just with an empty mesh flag
|
||||
int msk = Byte.toUnsignedInt(request.nonAirMask());
|
||||
int baseIdx = this.nodeAllocations.allocateNextConsecutiveCounted(Integer.bitCount(msk));
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((msk&(1<<i))!=0) {
|
||||
//It means the section actually exists, so add and upload it
|
||||
// aswell as add it to the mapping + push the node
|
||||
int id = baseIdx+(cnt++);
|
||||
long pos = request.childPositions[i];
|
||||
|
||||
//Put it in the mapping
|
||||
this.pos2meshId.putIfAbsent(pos, id);
|
||||
this.setNodePosition(id, pos);
|
||||
this.setMeshId(id, request.getMeshId(i));
|
||||
this.setChildPtr(id, NODE_MSK, 0);
|
||||
|
||||
this.pushNode(id);//request it to be uploaded
|
||||
} else {
|
||||
//The section was empty, so just remove/skip it, but remove it from the map
|
||||
this.pos2meshId.remove(request.childPositions[i]);
|
||||
}
|
||||
}
|
||||
if (cnt == 0) {
|
||||
//This means that every child node didnt have a mesh, this is not good
|
||||
//throw new IllegalStateException("Every child node empty for node at " + request.position);
|
||||
System.err.println("Every child node empty for node at " + request.position);
|
||||
//this.setChildPtr(request.nodeId, baseIdx, 0);
|
||||
} else {
|
||||
//Set the ptr
|
||||
this.setChildPtr(request.nodeId, baseIdx, cnt);
|
||||
this.pushNode(request.nodeId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final IntArrayList nodeUpdates = new IntArrayList();
|
||||
|
||||
//Invalidates the node and tells it to be pushed to the gpu next slot, NOTE: pushing a node, clears any gpu side flags
|
||||
private void pushNode(int node) {
|
||||
//TODO: update the local struct with the current frame id to prevent it from being put in the queue multiple times
|
||||
this.nodeUpdates.add(node);
|
||||
}
|
||||
|
||||
private void writeNode(long dst, int id) {
|
||||
long pos = this.localNodeData[id*3];
|
||||
MemoryUtil.memPutInt(dst, (int) (pos>>32)); dst += 4;
|
||||
MemoryUtil.memPutInt(dst, (int) pos); dst += 4;
|
||||
|
||||
int flags = 0;
|
||||
flags |= this.isEmptyNode(id)?2:0;
|
||||
flags |= Math.max(0, this.getNodeChildCnt(id)-1)<<2;
|
||||
|
||||
int a = this.getNodeMesh(id)|((flags&0xFF)<<24);
|
||||
int b = this.getNodeChildPtr(id)|(((flags>>8)&0xFF)<<24);
|
||||
//System.out.println("Setting mesh " + this.getNodeMesh(id) + " for node " + id);
|
||||
MemoryUtil.memPutInt(dst, a); dst += 4;
|
||||
MemoryUtil.memPutInt(dst, b); dst += 4;
|
||||
}
|
||||
|
||||
public void upload() {
|
||||
for (int i = 0; i < this.nodeUpdates.size(); i++) {
|
||||
int node = this.nodeUpdates.getInt(i);
|
||||
long ptr = UploadStream.INSTANCE.upload(this.nodeBuffer, node*16L, 16);
|
||||
this.writeNode(ptr, node);
|
||||
}
|
||||
if (!this.nodeUpdates.isEmpty()) {
|
||||
UploadStream.INSTANCE.commit();//Cause we actually uploaded something (do it after cause it allows batch comitting thing)
|
||||
this.nodeUpdates.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void download() {
|
||||
//this.pushNode(0);
|
||||
//Download the request queue then clear the counter (first 4 bytes)
|
||||
DownloadStream.INSTANCE.download(this.requestQueue, this::processRequestQueue);
|
||||
DownloadStream.INSTANCE.commit();
|
||||
nglClearNamedBufferSubData(this.requestQueue.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
|
||||
|
||||
public void free() {
|
||||
this.requestQueue.free();
|
||||
this.nodeBuffer.free();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package me.cortex.voxy.client.core.rendering.hierarchical;
|
||||
|
||||
//System to determine what nodes to remove from the hericial tree while retaining the property that all
|
||||
// leaf nodes should have meshes
|
||||
//This system is critical to prevent the geometry buffer from growing to large or for too many nodes to fill up
|
||||
// the node system
|
||||
public class TreeTrimmer {
|
||||
//Used to interact with the outside world
|
||||
private final ITrimInterface trimInterface;
|
||||
|
||||
public TreeTrimmer(ITrimInterface trimInterface) {
|
||||
this.trimInterface = trimInterface;
|
||||
}
|
||||
|
||||
public void computeTrimPoints() {
|
||||
//Do a bfs to find ending points to trim needs to be based on some, last used, metric
|
||||
|
||||
//First stratagy is to compute a bfs and or generate a list of nodes sorted by last use time
|
||||
// the thing is that if we cull a mesh, it cannot be a leaf node
|
||||
// if it is a leaf node its parent node must have a mesh loaded
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user