IT WORKS!!!!!!

This commit is contained in:
mcrcortex
2024-09-22 23:57:08 +10:00
parent 78d5c224a8
commit aafc475843
14 changed files with 335 additions and 49 deletions

View File

@@ -75,6 +75,8 @@ public class VoxelCore {
this.postProcessing = new PostProcessing(); this.postProcessing = new PostProcessing();
System.out.println("Voxy core initialized"); System.out.println("Voxy core initialized");
//this.verifyTopNodeChildren(0,0,0);
} }

View File

@@ -3,6 +3,7 @@ package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.Voxy; import me.cortex.voxy.client.Voxy;
import me.cortex.voxy.client.core.gl.shader.IShaderProcessor; 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.PrintfInjector;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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> printfQueue2 = new ArrayList<>();
private static final List<String> printfQueue = new ArrayList<>(); private static final List<String> printfQueue = new ArrayList<>();
private static final IShaderProcessor PRINTF; public static final IShaderProcessor PRINTF_processor;
public static final PrintfInjector PRINTF_object; private static final PrintfInjector PRINTF_object;
static { static {
@@ -24,11 +25,18 @@ public final class PrintfDebugUtil {
} }
printfQueue.add(line); printfQueue.add(line);
}, printfQueue::clear); }, printfQueue::clear);
PRINTF = PRINTF_object; PRINTF_processor = PRINTF_object;
} else { } else {
PRINTF_object = null; PRINTF_object = null;
//Todo add a dummy processor that just removes all the printf calls //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");
}
};
} }
} }

View File

@@ -9,20 +9,22 @@ import java.util.Arrays;
public final class BuiltSection { public final class BuiltSection {
public static final boolean VERIFY_BUILT_SECTION_OFFSETS = VoxyCommon.isVerificationFlagOn("verifyBuiltSectionOffsets"); public static final boolean VERIFY_BUILT_SECTION_OFFSETS = VoxyCommon.isVerificationFlagOn("verifyBuiltSectionOffsets");
public final long position; public final long position;
public final byte childExistence;
public final int aabb; public final int aabb;
public final MemoryBuffer geometryBuffer; public final MemoryBuffer geometryBuffer;
public final int[] offsets; public final int[] offsets;
private BuiltSection(long position) { private BuiltSection(long position) {
this(position, -1, null, null); this(position, (byte) 0, -1, null, null);
} }
public static BuiltSection empty(long position) { public static BuiltSection empty(long position) {
return new BuiltSection(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.position = position;
this.childExistence = childExistence;
this.aabb = aabb; this.aabb = aabb;
this.geometryBuffer = geometryBuffer; this.geometryBuffer = geometryBuffer;
this.offsets = offsets; this.offsets = offsets;
@@ -37,7 +39,7 @@ public final class BuiltSection {
} }
public BuiltSection clone() { 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() { public void free() {

View File

@@ -321,7 +321,7 @@ public class RenderDataFactory {
aabb |= (this.maxY-this.minY)<<20; aabb |= (this.maxY-this.minY)<<20;
aabb |= (this.maxZ-this.minZ)<<25; aabb |= (this.maxZ-this.minZ)<<25;
return new BuiltSection(section.key, aabb, buff, offsets); return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets);
} }

View File

@@ -44,8 +44,9 @@ public class SectionUpdateRouter {
if (set.containsKey(position)) { if (set.containsKey(position)) {
current = set.get(position); current = set.get(position);
} }
delta = (byte) ((current&types)^types); delta = (byte) (current&types);
current |= (byte) types; current |= (byte) types;
delta ^= (byte) (current&types);
set.put(position, current); set.put(position, current);
} }
if ((delta&UPDATE_TYPE_BLOCK_BIT)!=0) { if ((delta&UPDATE_TYPE_BLOCK_BIT)!=0) {

View File

@@ -240,7 +240,7 @@ public class HierarchicalNodeManager {
nodeId &= ~ID_TYPE_MSK; nodeId &= ~ID_TYPE_MSK;
if (type == ID_TYPE_REQUEST) { if (type == ID_TYPE_REQUEST) {
//Doesnt result in an invalidation as we must wait for geometry to create a child //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 } else if (type == ID_TYPE_LEAF || type == ID_TYPE_INNER) {// || type == ID_TYPE_TOP
if (this.nodeData.getNodeChildExistence(nodeId) == childExistence) { if (this.nodeData.getNodeChildExistence(nodeId) == childExistence) {
//Dont need to update the internal state since it is the same //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); 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 //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 child = getChildIdx(position);
int prev = request.putChildResult(child, this.geometryManager.uploadSection(section)); int prev = request.setChildMesh(child, this.geometryManager.uploadSection(section));
if (prev != -1) { if (prev != -1) {
this.geometryManager.removeSection(prev); this.geometryManager.removeSection(prev);
} }

View File

@@ -15,7 +15,7 @@ import org.joml.Matrix4f;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.lwjgl.system.MemoryUtil; 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.GL11.*;
import static org.lwjgl.opengl.GL12.GL_UNPACK_IMAGE_HEIGHT; import static org.lwjgl.opengl.GL12.GL_UNPACK_IMAGE_HEIGHT;
import static org.lwjgl.opengl.GL12.GL_UNPACK_SKIP_IMAGES; 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 HiZBuffer hiZBuffer = new HiZBuffer();
private final int hizSampler = glGenSamplers(); 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) .defineIf("DEBUG", Voxy.SHADER_DEBUG)
.define("MAX_ITERATIONS", MAX_ITERATIONS) .define("MAX_ITERATIONS", MAX_ITERATIONS)
.define("LOCAL_SIZE_BITS", LOCAL_WORK_SIZE_BITS) .define("LOCAL_SIZE_BITS", LOCAL_WORK_SIZE_BITS)
@@ -103,6 +103,7 @@ public class HierarchicalOcclusionTraverser {
MemoryUtil.memPutInt(ptr, sx); ptr += 4; MemoryUtil.memPutInt(ptr, sx); ptr += 4;
MemoryUtil.memPutInt(ptr, sy); ptr += 4; MemoryUtil.memPutInt(ptr, sy); ptr += 4;
MemoryUtil.memPutInt(ptr, sz); ptr += 4; MemoryUtil.memPutInt(ptr, sz); ptr += 4;
MemoryUtil.memPutInt(ptr, viewport.width); 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))); 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 //Set the first entry
glClearNamedBufferSubData(this.queueMetaBuffer.id, GL_RGBA32UI, 0, 16, GL_RGBA, GL_UNSIGNED_INT, new int[]{firstDispatchSize,1,1,initialQueueSize}); 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); long ptr = UploadStream.INSTANCE.upload(this.queueMetaBuffer, 0, 16*5);
MemoryUtil.memPutInt(ptr + 0, firstDispatchSize); MemoryUtil.memPutInt(ptr + 0, firstDispatchSize);
MemoryUtil.memPutInt(ptr + 4, 1); MemoryUtil.memPutInt(ptr + 4, 1);
@@ -187,6 +188,12 @@ public class HierarchicalOcclusionTraverser {
MemoryUtil.memPutInt(ptr + (i*16)+ 8, 1); MemoryUtil.memPutInt(ptr + (i*16)+ 8, 1);
MemoryUtil.memPutInt(ptr + (i*16)+12, 0); 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(); UploadStream.INSTANCE.commit();
} }
@@ -229,7 +236,7 @@ public class HierarchicalOcclusionTraverser {
} }
private void forwardDownloadResult(long ptr, long size) { 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) { if (count < 0 || count > 50000) {
throw new IllegalStateException("Count unexpected extreme value: " + count); throw new IllegalStateException("Count unexpected extreme value: " + count);
} }

View File

@@ -10,28 +10,44 @@ class NodeChildRequest {
private byte results; private byte results;
private byte mask; private byte mask;
private byte existenceMask = 0;
NodeChildRequest(long nodePos) { NodeChildRequest(long nodePos) {
this.nodePos = nodePos; this.nodePos = nodePos;
} }
public int getChildMeshResult(int childIdx) { public int getChildMesh(int childIdx) {
if ((this.mask&(1<<childIdx))==0) { if ((this.mask&(1<<childIdx))==0) {
throw new IllegalStateException("Tried getting mesh result of child not in mask"); throw new IllegalStateException("Tried getting mesh result of child not in mask");
} }
return this.childStates[childIdx]; return this.childStates[childIdx];
} }
public void putChildChildExistence(int childIdx, byte childExistence) { public void setChildChildExistence(int childIdx, byte childExistence) {
if ((this.mask&(1<<childIdx))==0) { 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.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) { 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 //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; boolean isFirstInsert = (this.results&(1<<childIdx))==0;
@@ -54,6 +70,7 @@ class NodeChildRequest {
byte prev = this.results; byte prev = this.results;
this.results &= (byte) ~MSK; this.results &= (byte) ~MSK;
this.mask &= (byte) ~MSK; this.mask &= (byte) ~MSK;
this.existenceMask &= (byte) ~MSK;
int mesh = this.childStates[childIdx]; int mesh = this.childStates[childIdx];
this.childStates[childIdx] = -1; this.childStates[childIdx] = -1;
if ((prev&MSK)==0) { if ((prev&MSK)==0) {
@@ -82,4 +99,5 @@ class NodeChildRequest {
public byte getMsk() { public byte getMsk() {
return this.mask; return this.mask;
} }
} }

View File

@@ -2,7 +2,6 @@ package me.cortex.voxy.client.core.rendering.hierachical2;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; 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.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.building.BuiltSection; import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.SectionUpdateRouter; 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.client.core.util.ExpandingObjectAllocationList;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.world.WorldEngine; import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.commonImpl.VoxyCommon;
import me.jellysquid.mods.sodium.client.util.MathUtil; 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 { public class NodeManager2 {
//Assumptions: //Assumptions:
@@ -27,7 +22,10 @@ public class NodeManager2 {
// For the queue processing, will need a redirect node-value type // For the queue processing, will need a redirect node-value type
// since for inner node child resize gpu could take N frames to update // 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_MSK = 0b11<<30;
private static final int NODE_TYPE_LEAF = 0b00<<30; private static final int NODE_TYPE_LEAF = 0b00<<30;
private static final int NODE_TYPE_INNER = 0b01<<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) { if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_REQUEST) {
//For a request //For a request
if ((nodeId&REQUEST_TYPE_MSK)==REQUEST_TYPE_SINGLE) { 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) { } 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 { } else {
throw new IllegalStateException(); throw new IllegalStateException();
} }
} else if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_INNER || (nodeId&NODE_TYPE_MSK)==NODE_TYPE_LEAF) { } else if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_INNER || (nodeId&NODE_TYPE_MSK)==NODE_TYPE_LEAF) {
// Just doing a geometry update // Just doing a geometry update
if (this.updateNodeGeometry(nodeId&NODE_ID_MSK, sectionResult) != 0) {
this.nodeUpdates.add(nodeId&NODE_ID_MSK);
}
}
} }
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) {
public void processRequest(long pos) { int previousGeometry = this.nodeData.getNodeGeometry(node);
int nodeId = this.activeSectionMap.get(pos); int newGeometry = EMPTY_GEOMETRY_ID;
if (nodeId == -1) { if (previousGeometry != EMPTY_GEOMETRY_ID && previousGeometry != NULL_GEOMETRY_ID) {
Logger.error("Got request for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!"); if (!geometry.isEmpty()) {
return; 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) { public void processChildChange(long pos, byte childExistence) {
@@ -126,6 +178,159 @@ public class NodeManager2 {
return; 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(); this.nodeUpdates.clear();
return true; 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));
}
} }

View File

@@ -8,9 +8,9 @@ public final class NodeStore {
public static final int NODE_ID_MSK = ((1<<24)-1); public static final int NODE_ID_MSK = ((1<<24)-1);
public static final int REQUEST_ID_MSK = ((1<<16)-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 GEOMETRY_ID_MSK = (1<<24)-1;
public static final int MAX_GEOMETRY_ID = GEOMETRY_ID_MSK-2; public static final int MAX_GEOMETRY_ID = (1<<24)-3;
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_NULL_GEOMETRY_ID = (1<<24)-1;
private static final int SENTINEL_EMPTY_GEOMETRY_ID = GEOMETRY_ID_MSK; 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_NULL_NODE_ID = NODE_ID_MSK -1;
private static final int SENTINEL_REQUEST_ID = REQUEST_ID_MSK -1; private static final int SENTINEL_REQUEST_ID = REQUEST_ID_MSK -1;
private static final int LONGS_PER_NODE = 4; private static final int LONGS_PER_NODE = 4;
@@ -104,20 +104,33 @@ public final class NodeStore {
public int getNodeGeometry(int node) { public int getNodeGeometry(int node) {
long data = this.localNodeData[id2idx(node)+1]; long data = this.localNodeData[id2idx(node)+1];
int geometryPtr = (int) (data&GEOMETRY_ID_MSK); int geometryPtr = (int) (data&GEOMETRY_ID_MSK);
if (geometryPtr == SENTINEL_EMPTY_GEOMETRY_ID) { if (geometryPtr == SENTINEL_NULL_GEOMETRY_ID) {
return -1; return -1;
} }
if (geometryPtr == SENTINEL_EMPTY_GEOMETRY_ID) {
return -2;
}
return geometryPtr; return geometryPtr;
} }
public void setNodeGeometry(int node, int geometryId) { 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) { if (geometryId == -1) {
geometryId = SENTINEL_NULL_GEOMETRY_ID;
}
if (geometryId == -2) {
geometryId = SENTINEL_EMPTY_GEOMETRY_ID; geometryId = SENTINEL_EMPTY_GEOMETRY_ID;
} }
if (geometryId < 0) {
throw new IllegalArgumentException("Geometry ptr less than -1 : " + geometryId);
}
int idx = id2idx(node)+1; int idx = id2idx(node)+1;
long data = this.localNodeData[idx]; long data = this.localNodeData[idx];
data &= ~GEOMETRY_ID_MSK; data &= ~GEOMETRY_ID_MSK;
@@ -165,6 +178,9 @@ public final class NodeStore {
this.localNodeData[id2idx(nodeId)+1] |= 1L<<63; this.localNodeData[id2idx(nodeId)+1] |= 1L<<63;
} }
public void unmarkRequestInFlight(int nodeId) {
this.localNodeData[id2idx(nodeId)+1] &= ~(1L<<63);
}
public boolean isNodeRequestInFlight(int nodeId) { public boolean isNodeRequestInFlight(int nodeId) {
return ((this.localNodeData[id2idx(nodeId)+1]>>63)&1)!=0; return ((this.localNodeData[id2idx(nodeId)+1]>>63)&1)!=0;
} }

View File

@@ -8,6 +8,7 @@ class SingleNodeRequest {
SingleNodeRequest(long nodePos) { SingleNodeRequest(long nodePos) {
this.nodePos = nodePos; this.nodePos = nodePos;
this.mesh = -1;
} }
public void setChildExistence(byte childExistence) { public void setChildExistence(byte childExistence) {
@@ -37,4 +38,8 @@ class SingleNodeRequest {
public byte getChildExistence() { public byte getChildExistence() {
return this.childExistence; return this.childExistence;
} }
public boolean hasChildExistenceSet() {
return (this.setMsk&2)!=0;
}
} }

View File

@@ -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.world.service.VoxelIngestService;
import me.cortex.voxy.common.storage.StorageBackend; import me.cortex.voxy.common.storage.StorageBackend;
import me.cortex.voxy.common.thread.ServiceThreadPool; import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.commonImpl.VoxyCommon;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -151,6 +152,7 @@ public class WorldEngine {
long oldId = worldSection.set(x, y, z, newId); long oldId = worldSection.set(x, y, z, newId);
nonAirCountDelta += Mapper.isAir(oldId)==Mapper.isAir(newId)?0:(Mapper.isAir(newId)?-1:1 ); nonAirCountDelta += Mapper.isAir(oldId)==Mapper.isAir(newId)?0:(Mapper.isAir(newId)?-1:1 );
didStateChange |= newId != oldId; didStateChange |= newId != oldId;
//if (newId != oldId) {VoxyCommon.breakpoint();}
} }
} }
} }
@@ -163,9 +165,7 @@ public class WorldEngine {
} }
if (didStateChange||(emptinessStateChange!=0)) { if (didStateChange||(emptinessStateChange!=0)) {
//Mark the section as dirty (enqueuing saving and geometry rebuild) and move to parent mip level this.markDirty(worldSection, (didStateChange?UPDATE_TYPE_BLOCK_BIT:0)|(emptinessStateChange!=0?UPDATE_TYPE_CHILD_EXISTENCE_BIT:0));
//TODO: have an update type! so that then e.g. if the child empty set changes it doesnt cause chunk rebuilds!
this.markDirty(worldSection);
} }
//Need to release the section after using it //Need to release the section after using it

View File

@@ -29,6 +29,7 @@ struct UnpackedNode {
#define NULL_NODE ((1<<24)-1) #define NULL_NODE ((1<<24)-1)
#define NULL_MESH ((1<<24)-1) #define NULL_MESH ((1<<24)-1)
#define EMPTY_MESH ((1<<24)-2)
void unpackNode(out UnpackedNode node, uint nodeId) { void unpackNode(out UnpackedNode node, uint nodeId) {
uvec4 compactedNode = nodes[nodeId]; uvec4 compactedNode = nodes[nodeId];
@@ -55,7 +56,7 @@ bool hasMesh(in UnpackedNode node) {
} }
bool isEmptyMesh(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) { bool hasChildren(in UnpackedNode node) {

View File

@@ -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); //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) //Checks if the node is implicitly culled (outside frustum)
bool outsideFrustum() { bool outsideFrustum() {
//printf("Cull point (%f %f %f)x(%f %f %f)", maxBB.x, maxBB.y, maxBB.z, minBB.x, minBB.y, minBB.z); 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)); return any(lessThanEqual(maxBB, vec3(-1.0f, -1.0f, 0.0f))) || any(lessThanEqual(vec3(1.0f, 1.0f, 1.0f), minBB));
} }
bool isCulledByHiz() { bool isCulledByHiz() {
@@ -80,12 +80,12 @@ bool isCulledByHiz() {
} }
vec2 ssize = size.xy * vec2(ivec2(screenW, screenH)); vec2 ssize = size.xy * vec2(ivec2(screenW, screenH));
float miplevel = ceil(log2(max(max(ssize.x, ssize.y),1))); float miplevel = ceil(log2(max(max(ssize.x, ssize.y),1)));
vec2 midpoint = (maxBB.xy + minBB.xy)*0.5; vec2 midpoint = (maxBB.xy + minBB.xy)*0.5f;
return textureLod(hizDepthSampler, vec3(midpoint, minBB.z), miplevel) > 0.0001; return textureLod(hizDepthSampler, vec3(midpoint, minBB.z), miplevel) > 0.0001f;
} }
//Returns if we should decend into its children or not //Returns if we should decend into its children or not
bool shouldDecend() { 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; return (size.x*size.y*screenW*screenH) > minSSS;
} }