Wip on many things

Added partial inner node child update support
This commit is contained in:
mcrcortex
2024-12-22 18:10:28 +10:00
parent d9d433f47c
commit 5bb91bb1eb
10 changed files with 321 additions and 25 deletions

View File

@@ -88,6 +88,11 @@ public class Shader extends TrackedObject {
return this;
}
public Builder<T> define(String name, String value) {
this.defines.put(name, value);
return this;
}
public Builder<T> add(ShaderType type, String id) {
this.addSource(type, ShaderLoader.parse(id));
return this;

View File

@@ -1,5 +1,6 @@
package me.cortex.voxy.client.core.rendering;
import com.mojang.datafixers.util.Either;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
import me.cortex.voxy.client.core.model.ModelStore;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
@@ -15,11 +16,13 @@ import me.cortex.voxy.client.core.rendering.section.MDICSectionRenderer;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.util.MessageQueue;
import me.cortex.voxy.common.util.Pair;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.common.world.WorldSection;
import net.minecraft.client.render.Camera;
import java.io.File;
import java.util.Arrays;
import java.util.List;
@@ -49,7 +52,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
//Max sections: ~500k
//Max geometry: 1 gb
this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<20, (1L<<31)-1024);
this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<20, (1L<<32)-1024);
//Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard
var router = new SectionUpdateRouter();
@@ -93,16 +96,51 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
}
}
}*/
final int H_WIDTH = 20;
if (true) {
if (true) {
final int H_WIDTH = 10;
for (int x = -H_WIDTH; x <= H_WIDTH; x++) {
for (int y = -1; y <= 0; y++) {
for (int z = -H_WIDTH; z <= H_WIDTH; z++) {
for (int y = -1; y <= 0; y++) {
this.nodeManager.insertTopLevelNode(WorldEngine.getWorldSectionId(4, x, y, z));
}
}
}
} else {
for (int x = -5; x <= 20; x++) {
for (int z = -5; z <= 20; z++) {
for (int y = 0; y <= 1; y++) {
this.nodeManager.insertTopLevelNode(WorldEngine.getWorldSectionId(4, x, y, z));
}
}
}
}
} else {
/*
for (int x = -5; x <= 5; x++) {
for (int z = -5; z <= 5; z++) {
for (int y = -5; y <= 5; y++) {
this.nodeManager.insertTopLevelNode(WorldEngine.getWorldSectionId(4, x, y, z));
}
}
}
for (int x = -5; x <= 5; x++) {
for (int z = -5; z <= 5; z++) {
for (int y = -5; y <= 5; y++) {
this.nodeManager.insertTopLevelNode(WorldEngine.getWorldSectionId(4, x+16, y, z));
}
}
}*/
for (int x = -3; x <= 3; x++) {
for (int z = -3; z <= 3; z++) {
for (int y = -8; y <= 7; y++) {
this.nodeManager.insertTopLevelNode(WorldEngine.getWorldSectionId(4, x, y, z));
}
}
}
}
//this.nodeManager.insertTopLevelNode(WorldEngine.getWorldSectionId(4, 0,0,0));
}

View File

@@ -100,11 +100,12 @@ public class RenderGenerationService {
try {
mesh = factory.generateMesh(section);
} catch (IdNotYetComputedException e) {
//TODO: maybe move this to _after_ task as been readded to queue??
if (!this.modelBakery.factory.hasModelForBlockId(e.id)) {
this.modelBakery.requestBlockBake(e.id);
}
if (task.hasDoneModelRequest) {
try {
Thread.sleep(1);
} catch (InterruptedException ex) {
@@ -114,6 +115,8 @@ public class RenderGenerationService {
//The reason for the extra id parameter is that we explicitly add/check against the exception id due to e.g. requesting accross a chunk boarder wont be captured in the request
this.computeAndRequestRequiredModels(section, e.id);
}
{//Keep the lock on the section, and attach it to the task, this prevents needing to re-aquire it later
task.section = section;
}

View File

@@ -41,8 +41,8 @@ public class HierarchicalOcclusionTraverser {
private final GlBuffer renderList = new GlBuffer(100_000 * 4 + 4).zero();//100k sections max to render, TODO: Maybe move to render service or somewhere else
private final GlBuffer queueMetaBuffer = new GlBuffer(4*4*5).zero();
private final GlBuffer scratchQueueA = new GlBuffer(50_000*4).zero();
private final GlBuffer scratchQueueB = new GlBuffer(50_000*4).zero();
private final GlBuffer scratchQueueA = new GlBuffer(100_000*4).zero();
private final GlBuffer scratchQueueB = new GlBuffer(100_000*4).zero();
private static final int LOCAL_WORK_SIZE_BITS = 5;
private static final int MAX_ITERATIONS = 5;

View File

@@ -6,6 +6,7 @@ import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader;
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.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import org.lwjgl.system.MemoryUtil;
@@ -63,6 +64,19 @@ public class NodeCleaner {
public void tick() {
this.clearIds();
if (false) {
this.outputBuffer.zero();//TODO: maybe dont set to zero??
this.sorter.bind();
//TODO: choose whether this is in nodeSpace or section/geometryId space
//glDispatchCompute(, 1, 1);
//DownloadStream.INSTANCE.download(this.outputBuffer, this::onDownload);
}
}
private void onDownload(long ptr, long size) {
}
private void clearIds() {

View File

@@ -11,6 +11,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 net.caffeinemc.mods.sodium.client.util.MathUtil;
import java.util.List;
@@ -24,6 +25,7 @@ import java.util.List;
public class NodeManager2 {
private static final boolean VERIFY_NODE_MANAGER_OPERATIONS = VoxyCommon.isVerificationFlagOn("nodeManager");
//Assumptions:
// all nodes have children (i.e. all nodes have at least one child existence bit set at all times)
// leaf nodes always contain geometry (empty geometry counts as geometry (it just doesnt take any memory to store))
@@ -243,8 +245,7 @@ public class NodeManager2 {
throw new IllegalStateException();
}
} else if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_INNER) {
//Very complex and painful operation
Logger.error("UNFINISHED OPERATION TODO: FIXME");
this.updateChildSectionsInner(pos, nodeId&NODE_ID_MSK, childExistence);
} else if ((nodeId&NODE_TYPE_MSK)==NODE_TYPE_LEAF) {
//We might be leaf but we still might be inflight
@@ -252,7 +253,7 @@ public class NodeManager2 {
// Logger.error("UNFINISHED OPERATION TODO: FIXME: painful operation, needs to account for both adding and removing, need to do the same with inner node, but also create requests, or cleanup children");
int requestId = this.nodeData.getNodeRequest(nodeId);
var request = this.childRequests.get(requestId);// TODO: do not assume request is childRequest (it will probably always be)
if (request.getPosition() != pos) throw new IllegalStateException("Request not in pos");
if (request.getPosition() != pos) throw new IllegalStateException("Request is not at pos");
{//Update the request
byte oldMsk = request.getMsk();
byte change = (byte) (oldMsk ^ childExistence);
@@ -293,16 +294,131 @@ public class NodeManager2 {
}
}
}
//If the request is now satisfied we need to finish it
if (request.isSatisfied()) {
this.finishRequest(requestId, request);
}
}
}
//Just need to update the child node data, nothing else
this.nodeData.setNodeChildExistence(nodeId&NODE_ID_MSK, childExistence);
//Need to resubmit to gpu
this.invalidateNode(nodeId&NODE_ID_MSK);
this.invalidateNode(nodeId&NODE_ID_MSK);//TODO:FIXME: Do we???
}
}
private void updateChildSectionsInner(long pos, int nodeId, byte childExistence) {
//Very complex and painful operation
/**
if (this.nodeData.isNodeRequestInFlight(nodeId&NODE_ID_MSK)) {
int requestId = this.nodeData.getNodeRequest(nodeId);
var request = this.childRequests.get(requestId);// TODO: do not assume request is childRequest (it will probably always be)
if (request.getPosition() != pos) throw new IllegalStateException("Request is not at pos");
byte oldMsk = request.getMsk();
byte change = (byte) (oldMsk ^ childExistence);
// {//Remove children that no longer exist, TODO: FIXME: THEY MIGHT NOT BE IN THE REQUEST
// byte rem = (byte) (change&childExistence);
// for (int i = 0; i < 8; i++) {
// if ((rem & (1 << i)) == 0) continue;
// int meshId = request.removeAndUnRequire(i);
// if (meshId != NULL_GEOMETRY_ID && meshId != EMPTY_GEOMETRY_ID) {
// this.geometryManager.removeSection(meshId);
// }
//
// //Remove child from being watched and activeSections
// long cPos = makeChildPos(pos, i);
// if (this.activeSectionMap.remove(cPos) == -1) {//TODO: verify the removed section is a request type of child and the request id matches this
// throw new IllegalStateException("Child pos was in a request but not in active section map");
// }
// if (!this.updateRouter.unwatch(cPos, WorldEngine.UPDATE_FLAGS)) {
// throw new IllegalStateException("Child pos was not being watched");
// }
//
//
// throw new IllegalStateException("UNFINISHED!: need to recursivly remove children");
// }
// }
{//Add new children
byte add = (byte) (change&childExistence);
for (int i = 0; i < 8; i++) {
if ((add&(1<<i))==0) continue;
//Add child to request
request.addChildRequirement(i);
//Add child to active tracker and put in updateRouter
long cPos = makeChildPos(pos, i);
if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) {
throw new IllegalStateException("Child pos was already in active section tracker but was part of a request");
}
if (!this.updateRouter.watch(cPos, WorldEngine.UPDATE_FLAGS)) {
throw new IllegalStateException("Child pos update router issue");
}
}
}
}
*/
//TODO: operation of needing to create a request node to add new sections
// (or modify the node to remove a child node (recursively probably ;-;))
//This works in 2 parts, adding and removing, adding is (surprisingly) much easier than removing
// adding, either adds to a request, or creates a new request
byte existence = this.nodeData.getNodeChildExistence(nodeId);
byte add = (byte) ((existence^childExistence)&childExistence);
if (add != 0) {//We have nodes to add
if (!this.nodeData.isNodeRequestInFlight(nodeId)) {//If there is not an existing request, create it
var request = new NodeChildRequest(pos);
int requestId = this.childRequests.put(request);
this.nodeData.markRequestInFlight(nodeId);
this.nodeData.setNodeRequest(nodeId, requestId);
this.activeNodeRequestCount++;
}
//It is guaranteed that at this point the node has a request
// so add the new nodes to it
int requestId = this.nodeData.getNodeRequest(nodeId);
var request = this.childRequests.get(requestId);// TODO: do not assume request is childRequest (it will probably always be)
if (request.getPosition() != pos) throw new IllegalStateException("Request is not at pos");
//Add all new children to the request
for (int i = 0; i < 8; i++) {
if ((add&(1<<i))==0) continue;
//Add child to request
request.addChildRequirement(i);
//Add child to active tracker and put in updateRouter
long cPos = makeChildPos(pos, i);
if (this.activeSectionMap.put(cPos, requestId|NODE_TYPE_REQUEST|REQUEST_TYPE_CHILD) != -1) {
throw new IllegalStateException("Child pos was already in active section tracker but was part of a request");
}
if (!this.updateRouter.watch(cPos, WorldEngine.UPDATE_FLAGS)) {
throw new IllegalStateException("Child pos update router issue");
}
}
}
// Do removals
byte rem = (byte) ((existence^childExistence)&existence);;
if (rem != 0) {
Logger.error("UNFINISHED OPERATION TODO: FIXME");
}
//Update the nodes existence msk to the new one
this.nodeData.setNodeChildExistence(nodeId&NODE_ID_MSK, childExistence);
}
//==================================================================================================================
private void finishRequest(SingleNodeRequest request) {
@@ -378,7 +494,108 @@ public class NodeManager2 {
this.invalidateNode(parentNodeId);
} else if (parentNodeType==NODE_TYPE_INNER) {
Logger.error("TODO: FIXME FINISH: finishRequest NODE_TYPE_INNER");
//Logger.error("TODO: FIXME FINISH: finishRequest NODE_TYPE_INNER");
//For this, only need to add the nodes to the existing child set thing (shuffle around whatever) dont ever have to remove nodes
int childPtr = this.nodeData.getChildPtr(parentNodeId);
int childCnt = this.nodeData.getChildPtrCount(parentNodeId);
if (childPtr == -1) {
throw new IllegalStateException();
}
//Ok so technically, it _is ok_ to just add to the end of the childPtr, however, imo that is stupid
// and it should follow the logical allocation with respect to the 8 child indices
// this means, need to extract the child indices already in the ptr (or technically could use the child existance? but having both and doing verification would be good)
int existingChildMsk = 0;
for (int i = 0; i < childCnt; i++) {
if (!this.nodeData.nodeExists(i+childPtr)) {
throw new IllegalStateException();
}
existingChildMsk |= 1<<getChildIdx(this.nodeData.nodePosition(i+childPtr));
}
int reqMsk = Byte.toUnsignedInt(request.getMsk());
if ((byte) (existingChildMsk|reqMsk) != this.nodeData.getNodeChildExistence(parentNodeId)) {
//System.out.println(Integer.toBinaryString(Byte.toUnsignedInt(this.nodeData.getNodeChildExistence(parentNodeId))));System.out.println(Integer.toBinaryString(existingChildMsk));System.out.println(Integer.toBinaryString(reqMsk));
throw new IllegalStateException("node data existence state does not match pointer mask");
}
if ((reqMsk&existingChildMsk)!=0) {
throw new IllegalStateException("Overlapping child data!!! BAD");
}
//Create the new allocation
int newMsk = reqMsk | existingChildMsk;
int newChildPtr = this.nodeData.allocate(Integer.bitCount(newMsk));
//Need to interlace the old and new data into the new allocation
// FOR OLD ALLOCATIONS, NEED TO UPDATE POINTERS
int childId = newChildPtr-1;
int prevChildId = childPtr-1;
for (int i = 0; i < 8; i++) {
if ((newMsk&(1<<i))==0) continue;
childId++;
if ((reqMsk&(1<<i))!=0) {
//Its an entry from the request
long childPos = makeChildPos(request.getPosition(), i);
this.nodeData.setNodePosition(childId, childPos);
byte childExistence = request.getChildChildExistence(i);
if (childExistence == 0) {
throw new IllegalStateException("Request result with child existence of 0");
}
this.nodeData.setNodeChildExistence(childId, childExistence);
this.nodeData.setNodeGeometry(childId, request.getChildMesh(i));
//Mark for update
this.invalidateNode(childId);
//Put in map
int pid = this.activeSectionMap.put(childPos, childId|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));
}
} else {
prevChildId++;
long pos = this.nodeData.nodePosition(prevChildId);
//Its a previous entry, copy it to its new location
this.nodeData.copyNode(prevChildId, childId);
int prevNodeId = this.activeSectionMap.get(pos);
if ((prevNodeId&NODE_TYPE_MSK) == NODE_TYPE_REQUEST) {
throw new IllegalStateException();
}
if ((prevNodeId&NODE_ID_MSK) != prevChildId) {
throw new IllegalStateException("State inconsistency");
}
this.activeSectionMap.put(pos, (prevNodeId&NODE_TYPE_MSK)|childId);
//Need to invalidate the old and the new
this.invalidateNode(prevChildId);
this.invalidateNode(childId);
}
}
//Do final steps
//Free the old child data
this.nodeData.free(childPtr, childCnt);
//Free request
this.childRequests.release(requestId);
//Update the parent
this.nodeData.setChildPtr(parentNodeId, newChildPtr);
this.nodeData.setChildPtrCount(parentNodeId, Integer.bitCount(newMsk));
this.nodeData.setNodeRequest(parentNodeId, 0);//TODO: create a better null request
this.activeNodeRequestCount--;
this.nodeData.unmarkRequestInFlight(parentNodeId);
//Invalidate parent
this.invalidateNode(parentNodeId);
} else {
throw new IllegalStateException();
}

View File

@@ -70,12 +70,13 @@ public final class NodeStore {
this.free(nodeId, 1);
}
private void free(int baseNodeId, int count) {
public void free(int baseNodeId, int count) {
for (int i = 0; i < count; i++) {
int nodeId = baseNodeId + i;
if (!this.allocationSet.free(nodeId)) {
if (!this.allocationSet.free(nodeId)) {//TODO: add batch free
throw new IllegalStateException("Node " + nodeId + " was not allocated!");
}
this.clear(nodeId);
}
}
@@ -89,6 +90,20 @@ public final class NodeStore {
this.localNodeData[idx+3] = 0;
}
//Copy from allocated index A to allocated index B
public void copyNode(int fromId, int toId) {
if (!(this.allocationSet.isSet(fromId)&&this.allocationSet.isSet(toId))) {
throw new IllegalArgumentException();
}
int f = id2idx(fromId);
int t = id2idx(toId);
this.localNodeData[t ] = this.localNodeData[f ];
this.localNodeData[t+1] = this.localNodeData[f+1];
this.localNodeData[t+2] = this.localNodeData[f+2];
this.localNodeData[t+3] = this.localNodeData[f+3];
}
public void setNodePosition(int node, long position) {
this.localNodeData[id2idx(node)] = position;
}
@@ -213,7 +228,7 @@ public final class NodeStore {
public int getChildPtrCount(int nodeId) {
long data = this.localNodeData[id2idx(nodeId)+1];
return (int) ((data>>56)&0x7);
return ((int)((data>>56)&0x7))+1;
}
public void setChildPtrCount(int nodeId, int count) {
@@ -236,7 +251,7 @@ public final class NodeStore {
short flags = 0;
flags |= (short) (this.isNodeRequestInFlight(nodeId)?1:0);
flags |= (short) (this.getChildPtrCount(nodeId)<<2);
flags |= (short) ((this.getChildPtrCount(nodeId)-1)<<2);
{
int geometry = this.getNodeGeometry(nodeId);
@@ -246,8 +261,9 @@ public final class NodeStore {
z |= geometry&0xFFFFFF;//TODO: check and ensure bounds
}
}
w |= this.getChildPtr(nodeId)&0xFFFFFF;//TODO: check and ensure bounds
int childPtr = this.getChildPtr(nodeId);
//TODO: check and ensure bounds
w |= childPtr&0xFFFFFF;
z |= (flags&0xFF)<<24;
w |= ((flags>>8)&0xFF)<<24;

View File

@@ -22,14 +22,18 @@ public class ServiceThreadPool {
private final ThreadGroup threadGroup;
public ServiceThreadPool(int threadCount) {
this.threadGroup = new ThreadGroup("Service job workers");
this(threadCount, 4);//Maybe change to 3
}
public ServiceThreadPool(int threadCount, int priority) {
this.threadGroup = new ThreadGroup("Service job workers");
this.workers = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
int threadId = i;
var worker = new Thread(this.threadGroup, ()->this.worker(threadId));
worker.setDaemon(false);
worker.setName("Service worker #" + i);
worker.setPriority(priority);
worker.start();
worker.setUncaughtExceptionHandler(this::handleUncaughtException);
this.workers[i] = worker;

View File

@@ -75,7 +75,6 @@ vec3 swizzelDataAxis(uint axis, vec3 data) {
void main() {
int cornerIdx = gl_VertexID&3;
Quad quad = quadData[uint(gl_VertexID)>>2];
vec3 innerPos = extractPos(quad);
uint face = extractFace(quad);
uint modelId = extractStateId(quad);
BlockModel model = modelData[modelId];

View File

@@ -87,8 +87,8 @@ bool isCulledByHiz() {
vec2 ssize = size * vec2(screenW, screenH);
float miplevel = ceil(log2(max(max(ssize.x, ssize.y),1)))-1;
miplevel = clamp(miplevel, 1, 10);
float miplevel = ceil(log2(max(max(ssize.x, ssize.y),1)));
miplevel = clamp(miplevel, 1, 20);
vec2 midpoint = (maxBB.xy + minBB.xy)*0.5f;
//TODO: maybe get rid of clamp
//Todo: replace with some rasterization, e.g. especially for request back to cpu