IT WORKS!!!!!!
This commit is contained in:
@@ -75,6 +75,8 @@ public class VoxelCore {
|
||||
this.postProcessing = new PostProcessing();
|
||||
|
||||
System.out.println("Voxy core initialized");
|
||||
|
||||
//this.verifyTopNodeChildren(0,0,0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package me.cortex.voxy.client.core.rendering;
|
||||
import me.cortex.voxy.client.Voxy;
|
||||
import me.cortex.voxy.client.core.gl.shader.IShaderProcessor;
|
||||
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
|
||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -12,8 +13,8 @@ public final class PrintfDebugUtil {
|
||||
|
||||
private static final List<String> printfQueue2 = new ArrayList<>();
|
||||
private static final List<String> printfQueue = new ArrayList<>();
|
||||
private static final IShaderProcessor PRINTF;
|
||||
public static final PrintfInjector PRINTF_object;
|
||||
public static final IShaderProcessor PRINTF_processor;
|
||||
private static final PrintfInjector PRINTF_object;
|
||||
|
||||
|
||||
static {
|
||||
@@ -24,11 +25,18 @@ public final class PrintfDebugUtil {
|
||||
}
|
||||
printfQueue.add(line);
|
||||
}, printfQueue::clear);
|
||||
PRINTF = PRINTF_object;
|
||||
PRINTF_processor = PRINTF_object;
|
||||
} else {
|
||||
PRINTF_object = null;
|
||||
//Todo add a dummy processor that just removes all the printf calls
|
||||
PRINTF = null;
|
||||
PRINTF_processor = new IShaderProcessor() {
|
||||
@Override
|
||||
public String process(ShaderType type, String src) {
|
||||
//TODO: replace with https://stackoverflow.com/questions/47162098/is-it-possible-to-match-nested-brackets-with-a-regex-without-using-recursion-or/47162099#47162099
|
||||
// to match on printf with balanced bracing
|
||||
return src.replace("printf", "//printf");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,20 +9,22 @@ import java.util.Arrays;
|
||||
public final class BuiltSection {
|
||||
public static final boolean VERIFY_BUILT_SECTION_OFFSETS = VoxyCommon.isVerificationFlagOn("verifyBuiltSectionOffsets");
|
||||
public final long position;
|
||||
public final byte childExistence;
|
||||
public final int aabb;
|
||||
public final MemoryBuffer geometryBuffer;
|
||||
public final int[] offsets;
|
||||
|
||||
private BuiltSection(long position) {
|
||||
this(position, -1, null, null);
|
||||
this(position, (byte) 0, -1, null, null);
|
||||
}
|
||||
|
||||
public static BuiltSection empty(long position) {
|
||||
return new BuiltSection(position);
|
||||
}
|
||||
|
||||
public BuiltSection(long position, int aabb, MemoryBuffer geometryBuffer, int[] offsets) {
|
||||
public BuiltSection(long position, byte childExistence, int aabb, MemoryBuffer geometryBuffer, int[] offsets) {
|
||||
this.position = position;
|
||||
this.childExistence = childExistence;
|
||||
this.aabb = aabb;
|
||||
this.geometryBuffer = geometryBuffer;
|
||||
this.offsets = offsets;
|
||||
@@ -37,7 +39,7 @@ public final class BuiltSection {
|
||||
}
|
||||
|
||||
public BuiltSection clone() {
|
||||
return new BuiltSection(this.position, this.aabb, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.offsets!=null?Arrays.copyOf(this.offsets, this.offsets.length):null);
|
||||
return new BuiltSection(this.position, this.childExistence, this.aabb, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.offsets!=null?Arrays.copyOf(this.offsets, this.offsets.length):null);
|
||||
}
|
||||
|
||||
public void free() {
|
||||
|
||||
@@ -321,7 +321,7 @@ public class RenderDataFactory {
|
||||
aabb |= (this.maxY-this.minY)<<20;
|
||||
aabb |= (this.maxZ-this.minZ)<<25;
|
||||
|
||||
return new BuiltSection(section.key, aabb, buff, offsets);
|
||||
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -44,8 +44,9 @@ public class SectionUpdateRouter {
|
||||
if (set.containsKey(position)) {
|
||||
current = set.get(position);
|
||||
}
|
||||
delta = (byte) ((current&types)^types);
|
||||
delta = (byte) (current&types);
|
||||
current |= (byte) types;
|
||||
delta ^= (byte) (current&types);
|
||||
set.put(position, current);
|
||||
}
|
||||
if ((delta&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||
|
||||
@@ -240,7 +240,7 @@ public class HierarchicalNodeManager {
|
||||
nodeId &= ~ID_TYPE_MSK;
|
||||
if (type == ID_TYPE_REQUEST) {
|
||||
//Doesnt result in an invalidation as we must wait for geometry to create a child
|
||||
this.requests.get(nodeId).putChildResult(getChildIdx(position), childExistence);
|
||||
this.requests.get(nodeId).setChildMesh(getChildIdx(position), childExistence);
|
||||
} else if (type == ID_TYPE_LEAF || type == ID_TYPE_INNER) {// || type == ID_TYPE_TOP
|
||||
if (this.nodeData.getNodeChildExistence(nodeId) == childExistence) {
|
||||
//Dont need to update the internal state since it is the same
|
||||
@@ -345,7 +345,7 @@ public class HierarchicalNodeManager {
|
||||
var request = this.requests.get(nodeId);
|
||||
//Update for section part of a request, the request may be a leaf request update or an inner node update
|
||||
int child = getChildIdx(position);
|
||||
int prev = request.putChildResult(child, this.geometryManager.uploadSection(section));
|
||||
int prev = request.setChildMesh(child, this.geometryManager.uploadSection(section));
|
||||
if (prev != -1) {
|
||||
this.geometryManager.removeSection(prev);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static me.cortex.voxy.client.core.rendering.PrintfDebugUtil.PRINTF_object;
|
||||
import static me.cortex.voxy.client.core.rendering.PrintfDebugUtil.PRINTF_processor;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL12.GL_UNPACK_IMAGE_HEIGHT;
|
||||
import static org.lwjgl.opengl.GL12.GL_UNPACK_SKIP_IMAGES;
|
||||
@@ -56,7 +56,7 @@ public class HierarchicalOcclusionTraverser {
|
||||
private final HiZBuffer hiZBuffer = new HiZBuffer();
|
||||
private final int hizSampler = glGenSamplers();
|
||||
|
||||
private final Shader traversal = Shader.make(PRINTF_object)
|
||||
private final Shader traversal = Shader.make(PRINTF_processor)
|
||||
.defineIf("DEBUG", Voxy.SHADER_DEBUG)
|
||||
.define("MAX_ITERATIONS", MAX_ITERATIONS)
|
||||
.define("LOCAL_SIZE_BITS", LOCAL_WORK_SIZE_BITS)
|
||||
@@ -103,6 +103,7 @@ public class HierarchicalOcclusionTraverser {
|
||||
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)));
|
||||
@@ -175,7 +176,7 @@ public class HierarchicalOcclusionTraverser {
|
||||
//Set the first entry
|
||||
glClearNamedBufferSubData(this.queueMetaBuffer.id, GL_RGBA32UI, 0, 16, GL_RGBA, GL_UNSIGNED_INT, new int[]{firstDispatchSize,1,1,initialQueueSize});
|
||||
*/
|
||||
{
|
||||
{//TODO:FIXME: THIS IS BULLSHIT BY INTEL need to fix the clearing
|
||||
long ptr = UploadStream.INSTANCE.upload(this.queueMetaBuffer, 0, 16*5);
|
||||
MemoryUtil.memPutInt(ptr + 0, firstDispatchSize);
|
||||
MemoryUtil.memPutInt(ptr + 4, 1);
|
||||
@@ -187,6 +188,12 @@ public class HierarchicalOcclusionTraverser {
|
||||
MemoryUtil.memPutInt(ptr + (i*16)+ 8, 1);
|
||||
MemoryUtil.memPutInt(ptr + (i*16)+12, 0);
|
||||
}
|
||||
//TODO: Move the first queue to a persistent list so its not updated every frame
|
||||
|
||||
ptr = UploadStream.INSTANCE.upload(this.scratchQueueA, 0, 4L*initialQueueSize);
|
||||
for (int i = 0; i < initialQueueSize; i++) {
|
||||
MemoryUtil.memPutInt(ptr + 4L*i, 0);
|
||||
}
|
||||
|
||||
UploadStream.INSTANCE.commit();
|
||||
}
|
||||
@@ -229,7 +236,7 @@ public class HierarchicalOcclusionTraverser {
|
||||
}
|
||||
|
||||
private void forwardDownloadResult(long ptr, long size) {
|
||||
int count = MemoryUtil.memGetInt(ptr);
|
||||
int count = MemoryUtil.memGetInt(ptr);ptr += 8;//its 8 since we need to skip the second value (which is empty)
|
||||
if (count < 0 || count > 50000) {
|
||||
throw new IllegalStateException("Count unexpected extreme value: " + count);
|
||||
}
|
||||
|
||||
@@ -10,28 +10,44 @@ class NodeChildRequest {
|
||||
|
||||
private byte results;
|
||||
private byte mask;
|
||||
private byte existenceMask = 0;
|
||||
|
||||
NodeChildRequest(long nodePos) {
|
||||
this.nodePos = nodePos;
|
||||
}
|
||||
|
||||
public int getChildMeshResult(int childIdx) {
|
||||
public int getChildMesh(int childIdx) {
|
||||
if ((this.mask&(1<<childIdx))==0) {
|
||||
throw new IllegalStateException("Tried getting mesh result of child not in mask");
|
||||
}
|
||||
return this.childStates[childIdx];
|
||||
}
|
||||
|
||||
public void putChildChildExistence(int childIdx, byte childExistence) {
|
||||
public void setChildChildExistence(int childIdx, byte childExistence) {
|
||||
if ((this.mask&(1<<childIdx))==0) {
|
||||
throw new IllegalStateException("Tried putting child into request which isnt in mask");
|
||||
throw new IllegalStateException("Tried setting child child existence in request when child isnt in mask");
|
||||
}
|
||||
this.childChildExistence[childIdx] = childExistence;
|
||||
this.existenceMask |= (byte) (1<<childIdx);
|
||||
}
|
||||
|
||||
public int putChildResult(int childIdx, int mesh) {
|
||||
public boolean hasChildChildExistence(int childId) {
|
||||
if ((this.mask&(1<<childId))==0) {
|
||||
throw new IllegalStateException("Tried getting child child existence set of child not in mask");
|
||||
}
|
||||
return (this.existenceMask&(1<<childId))!=0;
|
||||
}
|
||||
|
||||
public byte getChildChildExistence(int childIdx) {
|
||||
if (!this.hasChildChildExistence(childIdx)) {
|
||||
throw new IllegalStateException("Tried getting child child existence when child child existence for child was not set");
|
||||
}
|
||||
return this.childChildExistence[childIdx];
|
||||
}
|
||||
|
||||
public int setChildMesh(int childIdx, int mesh) {
|
||||
if ((this.mask&(1<<childIdx))==0) {
|
||||
throw new IllegalStateException("Tried putting child into request which isnt in mask");
|
||||
throw new IllegalStateException("Tried setting child mesh when child isnt in mask");
|
||||
}
|
||||
//Note the mesh can be -ve meaning empty mesh, but we should still mark that node as having a result
|
||||
boolean isFirstInsert = (this.results&(1<<childIdx))==0;
|
||||
@@ -54,6 +70,7 @@ class NodeChildRequest {
|
||||
byte prev = this.results;
|
||||
this.results &= (byte) ~MSK;
|
||||
this.mask &= (byte) ~MSK;
|
||||
this.existenceMask &= (byte) ~MSK;
|
||||
int mesh = this.childStates[childIdx];
|
||||
this.childStates[childIdx] = -1;
|
||||
if ((prev&MSK)==0) {
|
||||
@@ -82,4 +99,5 @@ class NodeChildRequest {
|
||||
public byte getMsk() {
|
||||
return this.mask;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package me.cortex.voxy.client.core.rendering.hierachical2;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import me.cortex.voxy.client.core.VoxelCore;
|
||||
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.SectionUpdateRouter;
|
||||
@@ -11,11 +10,7 @@ import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.client.core.util.ExpandingObjectAllocationList;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
import me.jellysquid.mods.sodium.client.util.MathUtil;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static me.cortex.voxy.client.core.rendering.hierachical2.NodeStore.NODE_ID_MSK;
|
||||
|
||||
public class NodeManager2 {
|
||||
//Assumptions:
|
||||
@@ -27,7 +22,10 @@ public class NodeManager2 {
|
||||
// For the queue processing, will need a redirect node-value type
|
||||
// since for inner node child resize gpu could take N frames to update
|
||||
|
||||
public static final int NULL_GEOMETRY_ID = -1;
|
||||
public static final int EMPTY_GEOMETRY_ID = -2;
|
||||
|
||||
public static final int NODE_ID_MSK = ((1<<24)-1);
|
||||
private static final int NODE_TYPE_MSK = 0b11<<30;
|
||||
private static final int NODE_TYPE_LEAF = 0b00<<30;
|
||||
private static final int NODE_TYPE_INNER = 0b01<<30;
|
||||
@@ -95,28 +93,82 @@ public class NodeManager2 {
|
||||
if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_REQUEST) {
|
||||
//For a request
|
||||
if ((nodeId&REQUEST_TYPE_MSK)==REQUEST_TYPE_SINGLE) {
|
||||
var request = this.singleRequests.get(nodeId&NODE_ID_MSK);
|
||||
request.setMesh(this.uploadReplaceSection(request.getMesh(), sectionResult));
|
||||
|
||||
//sectionResult has a cheeky childExistence field that we can use to set the request too, this is just
|
||||
// because processChildChange is only ever invoked when child existence changes, so we still need to
|
||||
// populate the request somehow, it will only set it if it hasnt been set before
|
||||
if (!request.hasChildExistenceSet()) {
|
||||
request.setChildExistence(sectionResult.childExistence);
|
||||
}
|
||||
|
||||
if (request.isSatisfied()) {
|
||||
this.singleRequests.release(nodeId&NODE_ID_MSK);
|
||||
this.finishRequest(request);
|
||||
}
|
||||
} else if ((nodeId&REQUEST_TYPE_MSK)==REQUEST_TYPE_CHILD) {
|
||||
var request = this.childRequests.get(nodeId&NODE_ID_MSK);
|
||||
int childId = getChildIdx(pos);
|
||||
request.setChildMesh(childId, this.uploadReplaceSection(request.getChildMesh(childId), sectionResult));
|
||||
if (!request.hasChildChildExistence(childId)) {
|
||||
request.setChildChildExistence(childId, sectionResult.childExistence);
|
||||
}
|
||||
|
||||
if (request.isSatisfied()) {
|
||||
this.finishRequest(nodeId&NODE_ID_MSK, request);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} else if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_INNER || (nodeId&NODE_TYPE_MSK)==NODE_TYPE_LEAF) {
|
||||
// Just doing a geometry update
|
||||
if (this.updateNodeGeometry(nodeId&NODE_ID_MSK, sectionResult) != 0) {
|
||||
this.nodeUpdates.add(nodeId&NODE_ID_MSK);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//==================================================================================================================
|
||||
public void processRequest(long pos) {
|
||||
int nodeId = this.activeSectionMap.get(pos);
|
||||
if (nodeId == -1) {
|
||||
Logger.error("Got request for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!");
|
||||
return;
|
||||
private int uploadReplaceSection(int meshId, BuiltSection section) {
|
||||
if (section.isEmpty()) {
|
||||
if (meshId != NULL_GEOMETRY_ID && meshId != EMPTY_GEOMETRY_ID) {
|
||||
this.geometryManager.removeSection(meshId);
|
||||
}
|
||||
section.free();
|
||||
return EMPTY_GEOMETRY_ID;
|
||||
}
|
||||
|
||||
if (meshId != NULL_GEOMETRY_ID && meshId != EMPTY_GEOMETRY_ID) {
|
||||
return this.geometryManager.uploadReplaceSection(meshId, section);
|
||||
}
|
||||
return this.geometryManager.uploadSection(section);
|
||||
}
|
||||
|
||||
private int updateNodeGeometry(int node, BuiltSection geometry) {
|
||||
int previousGeometry = this.nodeData.getNodeGeometry(node);
|
||||
int newGeometry = EMPTY_GEOMETRY_ID;
|
||||
if (previousGeometry != EMPTY_GEOMETRY_ID && previousGeometry != NULL_GEOMETRY_ID) {
|
||||
if (!geometry.isEmpty()) {
|
||||
newGeometry = this.geometryManager.uploadReplaceSection(previousGeometry, geometry);
|
||||
} else {
|
||||
this.geometryManager.removeSection(previousGeometry);
|
||||
}
|
||||
} else {
|
||||
if (!geometry.isEmpty()) {
|
||||
newGeometry = this.geometryManager.uploadSection(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
if (previousGeometry != newGeometry) {
|
||||
this.nodeData.setNodeGeometry(node, newGeometry);
|
||||
}
|
||||
if (previousGeometry == newGeometry) {
|
||||
return 0;//No change
|
||||
} else if (previousGeometry == EMPTY_GEOMETRY_ID || previousGeometry == NULL_GEOMETRY_ID) {
|
||||
return 1;//Became non-empty/non-null
|
||||
} else {
|
||||
return 2;//Became empty
|
||||
}
|
||||
}
|
||||
//==================================================================================================================
|
||||
|
||||
public void processChildChange(long pos, byte childExistence) {
|
||||
@@ -126,6 +178,159 @@ public class NodeManager2 {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_REQUEST) {
|
||||
//For a request
|
||||
if ((nodeId&REQUEST_TYPE_MSK)==REQUEST_TYPE_SINGLE) {
|
||||
var request = this.singleRequests.get(nodeId&NODE_ID_MSK);
|
||||
request.setChildExistence(childExistence);
|
||||
if (request.isSatisfied()) {
|
||||
this.singleRequests.release(nodeId&NODE_ID_MSK);
|
||||
this.finishRequest(request);
|
||||
}
|
||||
} else if ((nodeId&REQUEST_TYPE_MSK)==REQUEST_TYPE_CHILD) {
|
||||
var request = this.childRequests.get(nodeId&NODE_ID_MSK);
|
||||
request.setChildChildExistence(getChildIdx(pos), childExistence);
|
||||
if (request.isSatisfied()) {
|
||||
this.finishRequest(nodeId&NODE_ID_MSK, request);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} else if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_INNER) {
|
||||
//Very complex and painful operation
|
||||
|
||||
} else if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_LEAF) {
|
||||
//Just need to update the child node data, nothing else
|
||||
this.nodeData.setNodeChildExistence(nodeId&NODE_ID_MSK, childExistence);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================================
|
||||
|
||||
private void finishRequest(SingleNodeRequest request) {
|
||||
int id = this.nodeData.allocate();
|
||||
this.nodeData.setNodePosition(id, request.getPosition());
|
||||
this.nodeData.setNodeGeometry(id, request.getMesh());
|
||||
this.nodeData.setNodeChildExistence(id, request.getChildExistence());
|
||||
//TODO: this (or remove)
|
||||
//this.nodeData.setNodeType();
|
||||
this.activeSectionMap.put(request.getPosition(), id|NODE_TYPE_LEAF);//Assume that the result of any single request type is a leaf node
|
||||
this.nodeUpdates.add(id);
|
||||
}
|
||||
|
||||
private void finishRequest(int requestId, NodeChildRequest request) {
|
||||
int parentNodeId = this.activeSectionMap.get(request.getPosition());
|
||||
if (parentNodeId == -1 || (parentNodeId&NODE_TYPE_MSK)==NODE_TYPE_REQUEST) {
|
||||
throw new IllegalStateException("CRITICAL BAD STATE!!! finishRequest tried to finish for a node that no longer exists in the map or has become a request type somehow?!!?!!" + WorldEngine.pprintPos(request.getPosition()) + " " + parentNodeId);
|
||||
}
|
||||
|
||||
if ((parentNodeId&NODE_TYPE_MSK)==NODE_TYPE_LEAF) {
|
||||
int msk = Byte.toUnsignedInt(request.getMsk());
|
||||
int base = this.nodeData.allocate(Integer.bitCount(msk));
|
||||
int offset = -1;
|
||||
for (int childIdx = 0; childIdx < 8; childIdx++) {
|
||||
if ((msk&(1<<childIdx)) == 0) {
|
||||
continue;
|
||||
}
|
||||
offset++;
|
||||
|
||||
long childPos = makeChildPos(request.getPosition(), childIdx);
|
||||
int childNodeId = base+offset;
|
||||
//Fill in node
|
||||
this.nodeData.setNodePosition(childNodeId, childPos);
|
||||
this.nodeData.setNodeChildExistence(childNodeId, request.getChildChildExistence(childIdx));
|
||||
this.nodeData.setNodeGeometry(childNodeId, request.getChildMesh(childIdx));
|
||||
//Mark for update
|
||||
this.nodeUpdates.add(childNodeId);
|
||||
//Put in map
|
||||
int pid = this.activeSectionMap.put(childPos, childNodeId|NODE_TYPE_LEAF);
|
||||
if ((pid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) {
|
||||
throw new IllegalStateException("Put node in map from request but type was not request: " + pid + " " + WorldEngine.pprintPos(childPos));
|
||||
}
|
||||
}
|
||||
//Free request
|
||||
this.childRequests.release(requestId);
|
||||
//Update the parent
|
||||
this.nodeData.setChildPtr(parentNodeId, base);
|
||||
this.nodeData.setChildPtrCount(parentNodeId, offset+1);
|
||||
this.nodeData.setNodeRequest(parentNodeId, 0);//TODO: create a better null request
|
||||
this.nodeData.unmarkRequestInFlight(parentNodeId);
|
||||
this.nodeUpdates.add(parentNodeId);
|
||||
} else if ((parentNodeId&NODE_TYPE_MSK)==NODE_TYPE_INNER) {
|
||||
System.err.println("TODO: FIXME FINISH: finishRequest NODE_TYPE_INNER");
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================================
|
||||
public void processRequest(long pos) {
|
||||
int nodeId = this.activeSectionMap.get(pos);
|
||||
if (nodeId == -1) {
|
||||
Logger.error("Got request for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!");
|
||||
return;
|
||||
}
|
||||
int nodeType = nodeId&NODE_TYPE_MSK;
|
||||
nodeId &= NODE_ID_MSK;
|
||||
if (nodeType == NODE_TYPE_REQUEST) {
|
||||
Logger.error("Tried processing request for pos: " + WorldEngine.pprintPos(pos) + " but its type was a request, ignoring!");
|
||||
return;
|
||||
} else if (nodeType != NODE_TYPE_LEAF && nodeType != NODE_TYPE_INNER ) {
|
||||
throw new IllegalStateException("Unknown node type: " + nodeType);
|
||||
}
|
||||
|
||||
if (this.nodeData.isNodeRequestInFlight(nodeId)) {
|
||||
Logger.warn("Tried processing a node that already has a request in flight: " + nodeId + " pos: " + WorldEngine.pprintPos(pos) + " ignoring");
|
||||
return;
|
||||
}
|
||||
this.nodeData.markRequestInFlight(nodeId);
|
||||
|
||||
if (nodeType == NODE_TYPE_LEAF) {
|
||||
//The hard one of processRequest, spin up a new request for the node
|
||||
this.makeLeafChildRequest(nodeId);
|
||||
|
||||
} else {//nodeType == NODE_TYPE_INNER
|
||||
//TODO: assert that the node isnt already being watched for geometry, if it is, just spit out a warning? and ignore
|
||||
|
||||
if (!this.updateRouter.watch(pos, WorldEngine.UPDATE_TYPE_BLOCK_BIT)) {
|
||||
//FIXME: i think this can occur accidently? when removing nodes or something creating leaf nodes
|
||||
// or other, the node might be wanted to be watched by gpu, but cpu already started watching it a few frames ago
|
||||
Logger.warn("Node: " + nodeId + " at pos: " + WorldEngine.pprintPos(pos) + " got update request, but geometry was already being watched");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeLeafChildRequest(int nodeId) {
|
||||
long pos = this.nodeData.nodePosition(nodeId);
|
||||
byte childExistence = this.nodeData.getNodeChildExistence(nodeId);
|
||||
|
||||
//Enqueue a leaf expansion request
|
||||
var request = new NodeChildRequest(pos);
|
||||
int requestId = this.childRequests.put(request);
|
||||
|
||||
//Only request against the childExistence mask, since the guarantee is that if childExistence bit is not set then that child is guaranteed to be empty
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((childExistence&(1<<i))==0) {
|
||||
//Dont watch or enqueue the child node cause it doesnt exist
|
||||
continue;
|
||||
}
|
||||
long childPos = makeChildPos(pos, i);
|
||||
request.addChildRequirement(i);
|
||||
|
||||
//Insert all the children into the tracking map with the node id
|
||||
int pid = this.activeSectionMap.put(childPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD);
|
||||
if (pid != -1) {
|
||||
throw new IllegalStateException("Leaf request creation failed to insert child into map as a mapping already existed for the node! pos: " + WorldEngine.pprintPos(childPos) + " id: " + pid);
|
||||
}
|
||||
|
||||
//Watch and request the child node at the given position
|
||||
if (!this.updateRouter.watch(childPos, WorldEngine.UPDATE_FLAGS)) {
|
||||
throw new IllegalStateException("Failed to watch childPos");
|
||||
}
|
||||
}
|
||||
|
||||
this.nodeData.setNodeRequest(nodeId, requestId);
|
||||
}
|
||||
|
||||
//==================================================================================================================
|
||||
@@ -141,4 +346,25 @@ public class NodeManager2 {
|
||||
this.nodeUpdates.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==================================================================================================================
|
||||
private static int getChildIdx(long pos) {
|
||||
int x = WorldEngine.getX(pos);
|
||||
int y = WorldEngine.getY(pos);
|
||||
int z = WorldEngine.getZ(pos);
|
||||
return (x&1)|((y&1)<<2)|((z&1)<<1);
|
||||
}
|
||||
|
||||
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>>2)&1),
|
||||
(WorldEngine.getZ(basePos)<<1)|((addin>>1)&1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ public final class NodeStore {
|
||||
public static final int NODE_ID_MSK = ((1<<24)-1);
|
||||
public static final int REQUEST_ID_MSK = ((1<<16)-1);
|
||||
public static final int GEOMETRY_ID_MSK = (1<<24)-1;
|
||||
public static final int MAX_GEOMETRY_ID = GEOMETRY_ID_MSK-2;
|
||||
public static final int ABSENT_GEOMETRY_ID = GEOMETRY_ID_MSK-1;//Value for if want to clear geometry and make gpu request it if its needed
|
||||
private static final int SENTINEL_EMPTY_GEOMETRY_ID = GEOMETRY_ID_MSK;
|
||||
public static final int MAX_GEOMETRY_ID = (1<<24)-3;
|
||||
private static final int SENTINEL_NULL_GEOMETRY_ID = (1<<24)-1;
|
||||
private static final int SENTINEL_EMPTY_GEOMETRY_ID = (1<<24)-2;
|
||||
private static final int SENTINEL_NULL_NODE_ID = NODE_ID_MSK -1;
|
||||
private static final int SENTINEL_REQUEST_ID = REQUEST_ID_MSK -1;
|
||||
private static final int LONGS_PER_NODE = 4;
|
||||
@@ -104,20 +104,33 @@ public final class NodeStore {
|
||||
public int getNodeGeometry(int node) {
|
||||
long data = this.localNodeData[id2idx(node)+1];
|
||||
int geometryPtr = (int) (data&GEOMETRY_ID_MSK);
|
||||
if (geometryPtr == SENTINEL_EMPTY_GEOMETRY_ID) {
|
||||
if (geometryPtr == SENTINEL_NULL_GEOMETRY_ID) {
|
||||
return -1;
|
||||
}
|
||||
if (geometryPtr == SENTINEL_EMPTY_GEOMETRY_ID) {
|
||||
return -2;
|
||||
}
|
||||
return geometryPtr;
|
||||
}
|
||||
|
||||
public void setNodeGeometry(int node, int geometryId) {
|
||||
if (geometryId>MAX_GEOMETRY_ID || geometryId<-1) {
|
||||
throw new IllegalArgumentException("Geometry ptr greater than MAX_GEOMETRY_ID or less than -1 : " + geometryId);
|
||||
|
||||
if (geometryId>MAX_GEOMETRY_ID) {
|
||||
throw new IllegalArgumentException("Geometry ptr greater than MAX_GEOMETRY_ID: " + geometryId);
|
||||
}
|
||||
|
||||
if (geometryId == -1) {
|
||||
geometryId = SENTINEL_NULL_GEOMETRY_ID;
|
||||
}
|
||||
|
||||
if (geometryId == -2) {
|
||||
geometryId = SENTINEL_EMPTY_GEOMETRY_ID;
|
||||
}
|
||||
|
||||
if (geometryId < 0) {
|
||||
throw new IllegalArgumentException("Geometry ptr less than -1 : " + geometryId);
|
||||
}
|
||||
|
||||
int idx = id2idx(node)+1;
|
||||
long data = this.localNodeData[idx];
|
||||
data &= ~GEOMETRY_ID_MSK;
|
||||
@@ -165,6 +178,9 @@ public final class NodeStore {
|
||||
this.localNodeData[id2idx(nodeId)+1] |= 1L<<63;
|
||||
}
|
||||
|
||||
public void unmarkRequestInFlight(int nodeId) {
|
||||
this.localNodeData[id2idx(nodeId)+1] &= ~(1L<<63);
|
||||
}
|
||||
public boolean isNodeRequestInFlight(int nodeId) {
|
||||
return ((this.localNodeData[id2idx(nodeId)+1]>>63)&1)!=0;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ class SingleNodeRequest {
|
||||
|
||||
SingleNodeRequest(long nodePos) {
|
||||
this.nodePos = nodePos;
|
||||
this.mesh = -1;
|
||||
}
|
||||
|
||||
public void setChildExistence(byte childExistence) {
|
||||
@@ -37,4 +38,8 @@ class SingleNodeRequest {
|
||||
public byte getChildExistence() {
|
||||
return this.childExistence;
|
||||
}
|
||||
|
||||
public boolean hasChildExistenceSet() {
|
||||
return (this.setMsk&2)!=0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import me.cortex.voxy.common.world.service.SectionSavingService;
|
||||
import me.cortex.voxy.common.world.service.VoxelIngestService;
|
||||
import me.cortex.voxy.common.storage.StorageBackend;
|
||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
@@ -151,6 +152,7 @@ public class WorldEngine {
|
||||
long oldId = worldSection.set(x, y, z, newId);
|
||||
nonAirCountDelta += Mapper.isAir(oldId)==Mapper.isAir(newId)?0:(Mapper.isAir(newId)?-1:1 );
|
||||
didStateChange |= newId != oldId;
|
||||
//if (newId != oldId) {VoxyCommon.breakpoint();}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,9 +165,7 @@ public class WorldEngine {
|
||||
}
|
||||
|
||||
if (didStateChange||(emptinessStateChange!=0)) {
|
||||
//Mark the section as dirty (enqueuing saving and geometry rebuild) and move to parent mip level
|
||||
//TODO: have an update type! so that then e.g. if the child empty set changes it doesnt cause chunk rebuilds!
|
||||
this.markDirty(worldSection);
|
||||
this.markDirty(worldSection, (didStateChange?UPDATE_TYPE_BLOCK_BIT:0)|(emptinessStateChange!=0?UPDATE_TYPE_CHILD_EXISTENCE_BIT:0));
|
||||
}
|
||||
|
||||
//Need to release the section after using it
|
||||
|
||||
@@ -29,6 +29,7 @@ struct UnpackedNode {
|
||||
|
||||
#define NULL_NODE ((1<<24)-1)
|
||||
#define NULL_MESH ((1<<24)-1)
|
||||
#define EMPTY_MESH ((1<<24)-2)
|
||||
|
||||
void unpackNode(out UnpackedNode node, uint nodeId) {
|
||||
uvec4 compactedNode = nodes[nodeId];
|
||||
@@ -55,7 +56,7 @@ bool hasMesh(in UnpackedNode node) {
|
||||
}
|
||||
|
||||
bool isEmptyMesh(in UnpackedNode node) {
|
||||
return node.meshPtr == (NULL_MESH-1);//Specialcase
|
||||
return node.meshPtr == EMPTY_MESH;//Specialcase
|
||||
}
|
||||
|
||||
bool hasChildren(in UnpackedNode node) {
|
||||
|
||||
@@ -64,14 +64,14 @@ void setupScreenspace(in UnpackedNode node) {
|
||||
|
||||
//printf("Screenspace MIN: %f, %f, %f MAX: %f, %f, %f", minBB.x,minBB.y,minBB.z, maxBB.x,maxBB.y,maxBB.z);
|
||||
|
||||
size = maxBB.xy - minBB.xy;
|
||||
size = (maxBB.xy - minBB.xy)*0.5f;//We half it for implicit conversion to screenspace
|
||||
|
||||
}
|
||||
|
||||
//Checks if the node is implicitly culled (outside frustum)
|
||||
bool outsideFrustum() {
|
||||
//printf("Cull point (%f %f %f)x(%f %f %f)", maxBB.x, maxBB.y, maxBB.z, minBB.x, minBB.y, minBB.z);
|
||||
return any(lessThanEqual(maxBB, vec3(-1f, -1f, 0f))) || any(lessThanEqual(vec3(1f, 1f, 1f), minBB));
|
||||
printf("Cull point (%f %f %f)x(%f %f %f)", maxBB.x, maxBB.y, maxBB.z, minBB.x, minBB.y, minBB.z);
|
||||
return any(lessThanEqual(maxBB, vec3(-1.0f, -1.0f, 0.0f))) || any(lessThanEqual(vec3(1.0f, 1.0f, 1.0f), minBB));
|
||||
}
|
||||
|
||||
bool isCulledByHiz() {
|
||||
@@ -80,12 +80,12 @@ bool isCulledByHiz() {
|
||||
}
|
||||
vec2 ssize = size.xy * vec2(ivec2(screenW, screenH));
|
||||
float miplevel = ceil(log2(max(max(ssize.x, ssize.y),1)));
|
||||
vec2 midpoint = (maxBB.xy + minBB.xy)*0.5;
|
||||
return textureLod(hizDepthSampler, vec3(midpoint, minBB.z), miplevel) > 0.0001;
|
||||
vec2 midpoint = (maxBB.xy + minBB.xy)*0.5f;
|
||||
return textureLod(hizDepthSampler, vec3(midpoint, minBB.z), miplevel) > 0.0001f;
|
||||
}
|
||||
|
||||
//Returns if we should decend into its children or not
|
||||
bool shouldDecend() {
|
||||
printf("Screen area %f: %f, %f", (size.x*size.y*float(screenW)*float(screenH)), float(screenW), float(screenH));
|
||||
printf("Screen area %f: %f, %f", (size.x*size.y*float(screenW)*float(screenH)), float(size.x), float(size.y));
|
||||
return (size.x*size.y*screenW*screenH) > minSSS;
|
||||
}
|
||||
Reference in New Issue
Block a user