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