World and work stuff

This commit is contained in:
mcrcortex
2024-09-02 08:10:13 +10:00
parent c0cc236c40
commit 756431b581
16 changed files with 244 additions and 86 deletions

View File

@@ -22,11 +22,23 @@ repositories {
maven { url "https://maven.terraformersmc.com/releases/" }
}
def gitCommitHash = { ->
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', '--short', 'HEAD'
standardOutput = stdout
}
return stdout.toString().trim()
}
def buildtime = System.currentTimeSeconds()
processResources {
inputs.property "version", project.version
inputs.properties("version": project.version, "commit": gitCommitHash, "buildtime": buildtime)
archivesBaseName = "voxy"
filesMatching("fabric.mod.json") {
expand "version": project.version
expand "commit": gitCommitHash, "version": project.version, "buildtime": buildtime
}
}

View File

@@ -16,14 +16,6 @@ import net.minecraft.client.world.ClientWorld;
import java.util.Arrays;
public class Voxy implements ClientModInitializer {
public static final String VERSION;
static {
ModContainer mod = (ModContainer) FabricLoader.getInstance().getModContainer("voxy").orElseThrow(NullPointerException::new);
VERSION = mod.getMetadata().getVersion().getFriendlyString();
Serialization.init();
}
@Override
public void onInitializeClient() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {

View File

@@ -11,6 +11,7 @@ import me.cortex.voxy.client.saver.ContextSelectionSystem;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.client.importers.WorldImporter;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.hud.ClientBossBar;
import net.minecraft.client.render.Camera;
@@ -150,7 +151,7 @@ public class VoxelCore {
public void addDebugInfo(List<String> debug) {
debug.add("");
debug.add("");
debug.add("Voxy Core: " + Voxy.VERSION);
debug.add("Voxy Core: " + VoxyCommon.MOD_VERSION);
/*
debug.add("Ingest service tasks: " + this.world.ingestService.getTaskCount());
debug.add("Saving service tasks: " + this.world.savingService.getTaskCount());

View File

@@ -62,13 +62,17 @@ public class SectionUpdateRouter {
public boolean unwatch(long position, int types) {
var set = this.slices[getSliceIndex(position)];
synchronized (set) {
if (!set.containsKey(position)) {
throw new IllegalStateException("Section pos not in map!! " + WorldEngine.pprintPos(position));
}
byte current = set.get(position);
byte delta = (byte) (current&types);
current &= (byte) ~types;
if (current == 0) {
set.remove(position);
return true;
}
return delta!=0;
return false;
}
}

View File

@@ -3,14 +3,12 @@ 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.Voxy;
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.section.AbstractSectionGeometryManager;
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.common.world.WorldSection;
import me.jellysquid.mods.sodium.client.util.MathUtil;
import org.lwjgl.system.MemoryUtil;
@@ -52,10 +50,16 @@ public class HierarchicalNodeManager {
* * Technically an error state, as a leaf node should always have geometry (or marked as empty geometry data)
*/
//WARNING! need to solve a section geometry which is empty having its geometry removed, cause its empty
// so it wont send a request back cpu side
//There are a few properties of the class that can be used for verification
// a position cannot be both in a request and have an associated node, it must be none, in a request or a node
// leaf nodes cannot have a childptr
// updateRouter tracking child events should be the same set as activeSectionMap
// any allocated indices in requests are not finished/are in flight
// for all requests there must be an associated and valid node in the activeSectionMap
// for all requests the parent node must exist and cannot be ID_TYPE_REQUEST
// for all inner nodes, if there are no requests the children nodes must match the nodes childExistence bitset
// if there is a request, it should be the delta to get from the old children to the new children
// no node can have an empty childExistence (except for top level nodes)
private static final int NO_NODE = -1;
@@ -229,12 +233,82 @@ public class HierarchicalNodeManager {
int type = (nodeId & ID_TYPE_MSK);
nodeId &= ~ID_TYPE_MSK;
if (type == ID_TYPE_REQUEST) {
//Doesnt result in an invalidation as we must wait for geometry?
//Doesnt result in an invalidation as we must wait for geometry to create a child
this.requests.get(nodeId).putChildResult(getChildIdx(position), childExistence);
} else if (type == ID_TYPE_LEAF || type == ID_TYPE_NONE || type == ID_TYPE_TOP) {
//For all the types, if there is a request attatched, need to update the request data (and update the associated sections in the activeSectionMap)
// if there isnt a current request, and there are nodes that got added, create a request
//if any children dont exist anymore, need to delete them
//ALSO: TODO: HERE: if a child is removed, need to remove it and all children accociated
//If its an inner node and doesnt have an inflight request, create an empty request, this will get autofilled by the following part
if (type == ID_TYPE_NONE) {
}
//If its a top level node, it needs a request? aswell
//If its a leaf node, its fine, dont need to create a request, only need to update the node msk
if (this.nodeData.isNodeRequestInFlight(nodeId)) {
int reqId = this.nodeData.getNodeRequest(nodeId);
var request = this.requests.get(reqId);
byte reqMsk = request.getMsk();
if (reqMsk != childExistence) {
//Only need to change if its not the same
byte toRem = (byte) ((~childExistence)&reqMsk);
if (toRem != 0) {
//Remove nodes from the request
for (int i = 0; i < 8; i++) {//TODO: swap out for numberOfTrailingZeros loop thing
if ((toRem&(i<<1))==0) continue;
int geometry = request.removeAndUnRequire(i);
long cpos = makeChildPos(position, i);
//Remove from update router and activeSectionMap
int cid = this.activeSectionMap.remove(cpos);
if ((cid&ID_TYPE_MSK)!=ID_TYPE_REQUEST || (cid&~ID_TYPE_MSK)!=reqId) {
throw new IllegalStateException(WorldEngine.pprintPos(position)+" " + i + " " + cid + " " + reqId);
}
///Remove all from update router
if (!this.updateRouter.unwatch(cpos, WorldEngine.UPDATE_FLAGS)) {
throw new IllegalStateException();
}
//Release geometry if it had any
if (geometry != -1) {
this.geometryManager.removeSection(geometry);
}
}
}
byte toAdd = (byte) ((~reqMsk)&childExistence);
//This also needs to be with respect to the nodes current childexistance status as some sections are already watched/have nodes, dont change those
toAdd &= (byte) ~this.nodeData.getNodeChildExistence(nodeId);
if (toAdd != 0) {
//Add nodes to the request that dont exist in the node already
for (int i = 0; i < 8; i++) {//TODO: swap out for numberOfTrailingZeros loop thing
if ((toAdd & (i << 1)) == 0) continue;
request.addChildRequirement(i);
long cpos = makeChildPos(position, i);
int prev = this.activeSectionMap.put(cpos, ID_TYPE_REQUEST|reqId);
if (prev!=-1) {
throw new IllegalStateException("Child is already mapped to a node id " + WorldEngine.pprintPos(cpos) + " " + reqId + " " + prev);
}
if (!this.updateRouter.watch(cpos, WorldEngine.UPDATE_FLAGS)) {
throw new IllegalStateException("Couldn't watch chunk section");
}
}
}
if (request.getMsk() != childExistence) {
throw new IllegalStateException();
}
//If the request is satisfied after changes, consume it
if (request.isSatisfied()) {
this.consumeFinishedNodeChildRequest(nodeId, request);
}
}
}
//Need to update the node itself, only if the node has children does it need to update the child ptr information tho
} else {
throw new IllegalStateException("Should not reach here");
}
@@ -252,41 +326,27 @@ public class HierarchicalNodeManager {
int type = (nodeId & ID_TYPE_MSK);
nodeId &= ~ID_TYPE_MSK;
if (type == ID_TYPE_REQUEST) {
this.requestDataUpdate(nodeId);
} else if (type == ID_TYPE_NONE || type == ID_TYPE_TOP) {
//Not part of a request, just a node update,
//NOTE! be aware that if its an existance update and there is a request attached, need to check if the updated
// request becomes finished!!
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));
if (prev != -1) {
this.geometryManager.removeSection(prev);
}
if (request.isSatisfied()) {
this.consumeFinishedNodeChildRequest(nodeId, request);
}
} else if (type == ID_TYPE_NONE || type == ID_TYPE_TOP || type == ID_TYPE_LEAF) {
//Update the node geometry, and enqueue if it changed
if (this.updateNodeGeometry(nodeId, section) != 0) {//TODO: might need to mark the node as empty geometry or something
this.nodeUpdates.add(nodeId);
}
} else {
throw new IllegalStateException("Should not reach here");
}
}
}
private void requestDataUpdate(int 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
if (request.isSatisfied()) {
this.processFinishedNodeChildRequest(nodeId, request);
}
}
//Process NodeChildRequest results
private void processFinishedNodeChildRequest(int parent, NodeChildRequest request) {
int children = this.nodeData.getChildPtr(parent);
if (children != NO_NODE) {
//There are children already part of this node, so need to reallocate all the children
int count = Integer.bitCount(Byte.toUnsignedInt(this.nodeData.getNodeChildExistence(parent)));
} else {
}
}
private int updateNodeGeometry(int node, BuiltSection geometry) {
int previousGeometry = this.nodeData.getNodeGeometry(node);
int newGeometry = EMPTY_GEOMETRY_ID;
@@ -304,7 +364,6 @@ public class HierarchicalNodeManager {
if (previousGeometry != newGeometry) {
this.nodeData.setNodeGeometry(node, newGeometry);
this.nodeUpdates.add(node);
}
if (previousGeometry == newGeometry) {
return 0;//No change
@@ -315,6 +374,18 @@ public class HierarchicalNodeManager {
}
}
//Process NodeChildRequest results
private void consumeFinishedNodeChildRequest(int nodeId, NodeChildRequest request) {
int children = this.nodeData.getChildPtr(nodeId);
if (children != NO_NODE) {
//There are children already part of this node, so need to reallocate all the children
int count = Integer.bitCount(Byte.toUnsignedInt(this.nodeData.getNodeChildExistence(nodeId)));
} else {
}
}
//============================================================================================================================================
private static int getChildIdx(long pos) {

View File

@@ -23,12 +23,18 @@ public class HierarchicalOcclusionTraverser {
private final GlBuffer nodeBuffer;
private final GlBuffer uniformBuffer = new GlBuffer(1024).zero();
private final GlBuffer renderList = new GlBuffer(100_000 * 4 + 4).zero();//100k sections max to render
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 scratchBuffer = new GlBuffer(1024).zero();//Scratch utility buffer for small things to get the ordering right and memory overall
//Scratch queues for node traversal
private final GlBuffer scratchQueueA = new GlBuffer(10_000*4).zero();
private final GlBuffer scratchQueueB = new GlBuffer(10_000*4).zero();
private final HiZBuffer hiZBuffer = new HiZBuffer();
public HierarchicalOcclusionTraverser(HierarchicalNodeManager nodeManager, int requestBufferCount) {
this.nodeManager = nodeManager;
this.requestBuffer = new GlBuffer(requestBufferCount*4L+1024).zero();//The 1024 is to assist with race condition issues
@@ -40,7 +46,6 @@ public class HierarchicalOcclusionTraverser {
}
public static int HACKY_SECTION_COUNT = 0;
public void doTraversal(Viewport<?> viewport, int depthBuffer) {
//Compute the mip chain
this.hiZBuffer.buildMipChain(depthBuffer, viewport.width, viewport.height);
@@ -51,16 +56,7 @@ public class HierarchicalOcclusionTraverser {
//Use a chain of glDispatchComputeIndirect (5 times) with alternating read/write buffers
// TODO: swap to persistent gpu thread instead
if (HACKY_SECTION_COUNT != 0) {
long uploadPtr = UploadStream.INSTANCE.upload(this.renderList, 0, HACKY_SECTION_COUNT*4L+4);
MemoryUtil.memPutInt(uploadPtr, HACKY_SECTION_COUNT);
for (int i = 1; i < HACKY_SECTION_COUNT+1; i++) {
MemoryUtil.memPutInt(uploadPtr + 4L * i, i - 1);
}
UploadStream.INSTANCE.commit();
}
this.downloadResetRequestQueue();
}
@@ -98,5 +94,6 @@ public class HierarchicalOcclusionTraverser {
this.nodeBuffer.free();
this.uniformBuffer.free();
this.renderList.free();
this.scratchBuffer.free();
}
}

View File

@@ -78,4 +78,8 @@ class NodeChildRequest {
public long getPosition() {
return this.nodePos;
}
public byte getMsk() {
return this.mask;
}
}

View File

@@ -10,7 +10,13 @@ public class Logger {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger("Voxy");
public static void logError(Object... args) {
Throwable throwable = null;
for (var i : args) {
if (i instanceof Throwable) {
throwable = (Throwable) i;
}
}
var stackEntry = new Throwable().getStackTrace()[1];
LOGGER.error("["+stackEntry.getClassName()+"]: "+ Stream.of(args).map(Object::toString).collect(Collectors.joining(" ")));
LOGGER.error("["+stackEntry.getClassName()+"]: "+ Stream.of(args).map(Object::toString).collect(Collectors.joining(" ")), throwable);
}
}

View File

@@ -1,4 +0,0 @@
package me.cortex.voxy.commonImpl;
public interface IGetWorldInterface {
}

View File

@@ -0,0 +1,7 @@
package me.cortex.voxy.commonImpl;
import me.cortex.voxy.common.world.WorldEngine;
public interface IVoxyWorldGetter {
WorldEngine getWorldEngine();
}

View File

@@ -0,0 +1,7 @@
package me.cortex.voxy.commonImpl;
import me.cortex.voxy.common.world.WorldEngine;
public interface IVoxyWorldSetter {
void setWorldEngine(WorldEngine engine);
}

View File

@@ -0,0 +1,29 @@
package me.cortex.voxy.commonImpl;
import me.cortex.voxy.common.config.Serialization;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
public class VoxyCommon implements ModInitializer {
public static final String MOD_VERSION;
public static final boolean IS_DEDICATED_SERVER;
static {
ModContainer mod = (ModContainer) FabricLoader.getInstance().getModContainer("voxy").orElseThrow(NullPointerException::new);
var version = mod.getMetadata().getVersion().getFriendlyString();
var commit = mod.getMetadata().getCustomValue("commit").getAsString();
MOD_VERSION = version+"-"+commit;
IS_DEDICATED_SERVER = FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER;
Serialization.init();
}
@Override
public void onInitialize() {
//this.serviceThreadPool = new ServiceThreadPool(VoxyConfig.CONFIG.serviceThreads);
}
}

View File

@@ -1,5 +0,0 @@
package me.cortex.voxy.commonImpl;
public class VoxyWorldService {
}

View File

@@ -1,24 +1,51 @@
package me.cortex.voxy.commonImpl.mixin.minecraft;
import me.cortex.voxy.client.Voxy;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.commonImpl.IVoxyWorldGetter;
import me.cortex.voxy.commonImpl.IVoxyWorldSetter;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.MutableWorldProperties;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionType;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(World.class)
public class MixinWorld {
@Unique private WorldEngine voxyWorldEngine;
import java.util.function.Supplier;
@Mixin(World.class)
public class MixinWorld implements IVoxyWorldGetter, IVoxyWorldSetter {
@Unique private WorldEngine voxyWorld;
@Inject(method = "close", at = @At("HEAD"))
private void closeVoxyWorld(CallbackInfo ci) {
if (this.voxyWorldEngine != null) {
try {this.voxyWorldEngine.shutdown();} catch (Exception e) {
if (this.voxyWorld != null) {
try {this.voxyWorld.shutdown();this.voxyWorld = null;} catch (Exception e) {
Logger.logError("Failed to shutdown voxy world engine.", e);
}
}
}
@Override
public WorldEngine getWorldEngine() {
return this.voxyWorld;
}
@Override
public void setWorldEngine(WorldEngine engine) {
if (this.voxyWorld != null) {
throw new IllegalStateException("WorldEngine not null");
}
this.voxyWorld = engine;
}
}

View File

@@ -1,7 +1,9 @@
#version 460 core
//TODO: make this better than a single thread
layout(local_size_x=32, local_size_y=1) in;
//TODO: increase local size
#define LOCAL_SIZE_BITS 5
#define LOCAL_SIZE_MSK ((1<<LOCAL_SIZE_BITS)-1)
layout(local_size_x=(1<<LOCAL_SIZE_BITS), local_size_y=1) in;
#import <voxy:lod/hierarchical/binding_points.glsl>
#line 7
@@ -26,11 +28,6 @@ layout(binding = SCENE_UNIFORM_INDEX, std140) uniform SceneUniform {
float decendSSS;
};
layout(binding = NODE_QUEUE_INDEX, std430) restrict buffer NodeQueue {
uint nodeQueueSize;
uint[] nodeQueue;
};
layout(binding = REQUEST_QUEUE_INDEX, std430) restrict buffer RequestQueue {
uint requestQueueIndex;
uint[] requestQueue;
@@ -41,6 +38,11 @@ layout(binding = RENDER_QUEUE_INDEX, std430) restrict buffer RenderQueue {
uint[] renderQueue;
};
layout(binding = NODE_QUEUE_INDEX, std430) restrict buffer NodeQueue {
uint nodeQueueSize;
uint[] nodeQueue;
};
layout(binding = NEXT_NODE_QUEUE_INDEX, std430) restrict buffer NextNodeQueue {
uint nextNodeQueueIndex;
uint[] nextNodeQueue;
@@ -102,6 +104,7 @@ void enqueueChildren(in UnpackedNode node) {
uint children = getChildCount(node);
uint ptr = getChildPtr(node);
uint widx = atomicAdd(nextNodeQueueIndex, children);
for (int i = 0; i < children; i++) {
nextNodeQueue[widx+i] = ptr+i;
}

View File

@@ -2,6 +2,10 @@
"schemaVersion": 1,
"id": "voxy",
"version": "${version}",
"custom": {
"commit": "${commit}",
"buildtime": "${buildtime}"
},
"name": "Voxy",
"description": "Far distance rendering mod utilising LoDs",
"authors": [
@@ -21,6 +25,9 @@
],
"modmenu": [
"me.cortex.voxy.client.config.VoxyConfigScreenFactory"
],
"main": [
"me.cortex.voxy.commonImpl.VoxyCommon"
]
},
"mixins": [