diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java b/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java index cb8ed7fc..64278bed 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java @@ -55,7 +55,7 @@ public class RenderService, J extends Vi this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool, this.sectionUpdateQueue::add, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets); positionFilterForwarder.setCallbacks(this.renderGen::enqueueTask, section -> { - long time = System.nanoTime(); + long time = SectionUpdate.getTime(); byte childExistence = section.getNonEmptyChildren(); this.sectionUpdateQueue.add(new SectionUpdate(section.key, time, null, childExistence)); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java index b92b1bcd..9764557e 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java @@ -67,7 +67,7 @@ public class RenderGenerationService { synchronized (this.taskQueue) { task = this.taskQueue.removeFirst(); } - long time = System.nanoTime(); + long time = SectionUpdate.getTime(); var section = task.sectionSupplier.get(); if (section == null) { this.resultConsumer.accept(new SectionUpdate(task.position, time, BuiltSection.empty(task.position), (byte) 0)); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdate.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdate.java index b5208a58..0f337bc0 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdate.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/SectionUpdate.java @@ -3,4 +3,7 @@ package me.cortex.voxy.client.core.rendering.building; import org.jetbrains.annotations.Nullable; public record SectionUpdate(long position, long buildTime, @Nullable BuiltSection geometry, byte childExistence) { + public static long getTime() { + return System.currentTimeMillis(); + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java index c68b3ecc..b850f5ca 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java @@ -1,7 +1,9 @@ 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.rendering.building.BuiltSection; import me.cortex.voxy.client.core.rendering.building.SectionPositionUpdateFilterer; import me.cortex.voxy.client.core.rendering.building.SectionUpdate; import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager; @@ -16,6 +18,7 @@ public class HierarchicalNodeManager { public final int maxNodeCount; private final NodeStore nodeData; private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap(); + private final IntOpenHashSet nodeUpdates = new IntOpenHashSet(); private final ExpandingObjectAllocationList leafRequests = new ExpandingObjectAllocationList<>(LeafExpansionRequest[]::new); private final AbstractSectionGeometryManager geometryManager; private final SectionPositionUpdateFilterer updateFilterer; @@ -52,48 +55,51 @@ public class HierarchicalNodeManager { } this.nodeData.markRequestInFlight(node); - long pos = this.nodeData.nodePosition(node); //2 branches, either its a leaf node -> emit a leaf request // or the nodes geometry must be empty (i.e. culled from the graph/tree) so add to tracker and watch if (this.nodeData.isLeafNode(node)) { - //TODO: the localNodeData should have a bitset of what children are definitely empty - // use that to msk the request, HOWEVER there is a race condition e.g. - // leaf node is requested and has only 1 child marked as non empty - // however then an update occures and a different child now becomes non empty, - // this will trigger a processBuildResult for parent - // so need to ensure that when that happens, if the parent has an inflight leaf expansion request - // for the leaf request to be updated to account for the new maybe child node - // NOTE: a section can have empty geometry but some of its children might not, so need to mark and - // submit a node at that level but with empty section, (specially marked) so that the traversal - // can recurse into those children as needed - - //Enqueue a leaf expansion request - var request = new LeafExpansionRequest(pos); - int requestId = this.leafRequests.put(request); - - for (int i = 0; i < 8; i++) { - long childPos = makeChildPos(pos, i); - //Insert all the children into the tracking map with the node id - this.activeSectionMap.put(childPos, 0); - } + this.makeLeafRequest(node); } else { //Verify that the node section is not in the section store. if it is then it is a state desynchonization // Note that a section can be "empty" but some of its children might not be } } - public void processResult(SectionUpdate update) { - if (update.geometry() != null) { - if (!update.geometry().isEmpty()) { - HierarchicalOcclusionTraverser.HACKY_SECTION_COUNT++; - this.geometryManager.uploadSection(update.geometry()); - } else { - update.geometry().free(); - } + private void makeLeafRequest(int node, byte childExistence) { + long pos = this.nodeData.nodePosition(node); + //TODO: the localNodeData should have a bitset of what children are definitely empty + // use that to msk the request, HOWEVER there is a race condition e.g. + // leaf node is requested and has only 1 child marked as non empty + // however then an update occures and a different child now becomes non empty, + // this will trigger a processBuildResult for parent + // so need to ensure that when that happens, if the parent has an inflight leaf expansion request + // for the leaf request to be updated to account for the new maybe child node + // NOTE: a section can have empty geometry but some of its children might not, so need to mark and + // submit a node at that level but with empty section, (specially marked) so that the traversal + // can recurse into those children as needed + + //Enqueue a leaf expansion request + var request = new LeafExpansionRequest(pos); + int requestId = this.leafRequests.put(request); + + for (int i = 0; i < 8; i++) { + long childPos = makeChildPos(pos, i); + //Insert all the children into the tracking map with the node id + this.activeSectionMap.put(childPos, 0); } - if (true) - return; + } + + + public void processResult(SectionUpdate update) { + //Need to handle cases + // geometry update, leaf node, leaf request node, internal node + //Child emptiness update!!! this is the hard bit + // if it is an internal node + // if emptiness adds node, need to then send a mesh request and wait + // when mesh result, need to remove the old child allocation block and make a new block to fit the + // new count of children + int nodeId = this.activeSectionMap.get(update.position()); if (nodeId == -1) { @@ -104,15 +110,49 @@ public class HierarchicalNodeManager { } else { //Part of a request (top bit is set to 1) if ((nodeId&(1<<31))!=0) { + nodeId &= ~(1<<31); + var request = this.leafRequests.get(nodeId); } else { - //Not part of a request, just a node update, - // however could result in a reallocation if it needs to mark a child position as being possibly visible + //Not part of a request, just a node update, if node is currently a leaf node, it might have a + // leaf request associated with it, which might need an update if } } } + + + + private int updateNodeGeometry(int node, BuiltSection geometry) { + int previousGeometry = -1; + int newGeometry = -1; + if (this.nodeData.hasGeometry(node)) { + previousGeometry = this.nodeData.getNodeGeometry(node); + 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); + this.nodeUpdates.add(node); + } + if (previousGeometry == newGeometry) { + return 0;//No change + } else if (previousGeometry == -1) { + return 1;//Became non-empty + } else { + return 2;//Became empty + } + } + private static long makeChildPos(long basePos, int addin) { int lvl = WorldEngine.getLevel(basePos); if (lvl == 0) { diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalOcclusionTraverser.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalOcclusionTraverser.java index 90e12cdf..bc8fdc7a 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalOcclusionTraverser.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalOcclusionTraverser.java @@ -40,7 +40,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,6 +50,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); @@ -61,6 +61,7 @@ public class HierarchicalOcclusionTraverser { UploadStream.INSTANCE.commit(); } + */ this.downloadResetRequestQueue(); } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/LeafExpansionRequest.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/LeafExpansionRequest.java index e3144fef..67601033 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/LeafExpansionRequest.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/LeafExpansionRequest.java @@ -5,9 +5,59 @@ class LeafExpansionRequest { //Child states contain micrometadata in the top bits // such as isEmpty, and isEmptyButEventuallyHasNonEmptyChild private final long nodePos; - private final int[] childStates = new int[8]; + + private final int[] childStates = new int[]{-1,-1,-1,-1,-1,-1,-1,-1}; + + private byte results; + private byte mask; LeafExpansionRequest(long nodePos) { this.nodePos = nodePos; } + + public int putChildResult(int childIdx, int mesh) { + if ((this.mask&(1< this.localNodeData.length) { + int newSize = Math.min((index+INCREMENT_SIZE), this.allocationSet.getLimit()); + + long[] newStore = new long[newSize * LONGS_PER_NODE]; + System.arraycopy(this.localNodeData, 0, newStore, 0, this.localNodeData.length); + this.localNodeData = newStore; + } + } + + + + public long nodePosition(int nodeId) { return this.localNodeData[nodeId<<2]; } @@ -19,6 +57,17 @@ public final class NodeStore { } + public boolean hasGeometry(int node) { + return false; + } + public int getNodeGeometry(int node) { + return 0; + } + public void setNodeGeometry(int node, int geometryId) { + + } + + public void markRequestInFlight(int nodeId) { } @@ -31,10 +80,11 @@ public final class NodeStore { return false; } + public byte getNodeChildExistence(int nodeId) {return 0;} + //Writes out a nodes data to the ptr in the compacted/reduced format public void writeNode(long ptr, int nodeId) { - - } + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/util/HiZBuffer.java b/src/main/java/me/cortex/voxy/client/core/rendering/util/HiZBuffer.java index 451777c0..aac382c3 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/util/HiZBuffer.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/util/HiZBuffer.java @@ -107,8 +107,10 @@ public class HiZBuffer { public void free() { this.fb.free(); - this.texture.free(); - this.texture = null; + if (this.texture != null) { + this.texture.free(); + this.texture = null; + } glDeleteSamplers(this.sampler); this.hiz.free(); } diff --git a/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinShaderParser.java b/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinShaderParser.java new file mode 100644 index 00000000..3c633027 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinShaderParser.java @@ -0,0 +1,23 @@ +package me.cortex.voxy.client.mixin.sodium; + +import me.jellysquid.mods.sodium.client.gl.shader.ShaderParser; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.Collection; +import java.util.List; + +@Mixin(value = ShaderParser.class, remap = false) +public class MixinShaderParser { + /* + @Redirect(method = "parseShader(Ljava/lang/String;)Ljava/util/List;", at = @At(value = "INVOKE", target = "Ljava/util/List;addAll(Ljava/util/Collection;)Z")) + private static boolean injectLineNumbers(List lines, Collection add) { + lines.add("#line 1"); + int cc = lines.size(); + lines.addAll(add); + lines.add("#line " + cc); + return true; + } + */ +} diff --git a/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java b/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java index 4ecff0c4..18ff6072 100644 --- a/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java +++ b/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java @@ -132,6 +132,9 @@ public class HierarchicalBitSet { public int getCount() { return this.cnt; } + public int getLimit() { + return this.limit; + } public boolean isSet(int idx) { return (this.D[idx>>6]&(1L<<(idx&0x3f)))!=0; diff --git a/src/main/java/me/cortex/voxy/common/world/other/Mapper.java b/src/main/java/me/cortex/voxy/common/world/other/Mapper.java index 93d8891f..2761a377 100644 --- a/src/main/java/me/cortex/voxy/common/world/other/Mapper.java +++ b/src/main/java/me/cortex/voxy/common/world/other/Mapper.java @@ -5,6 +5,7 @@ import me.cortex.voxy.common.storage.StorageBackend; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; +import net.minecraft.block.RedstoneWireBlock; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtOps; @@ -138,7 +139,7 @@ public class Mapper { bentries.stream().sorted(Comparator.comparing(a->a.id)).forEach(entry -> { if (this.biomeId2biomeEntry.size() != entry.id) { - throw new IllegalStateException("Biome entry not ordered"); + throw new IllegalStateException("Biome entry not ordered. got " + entry.biome + " with id " + entry.id + " expected id " + this.biomeId2biomeEntry.size()); } this.biomeId2biomeEntry.add(entry); }); @@ -162,7 +163,7 @@ public class Mapper { } private synchronized BiomeEntry registerNewBiome(String biome) { - BiomeEntry entry = new BiomeEntry(this.biome2biomeEntry.size(), biome); + BiomeEntry entry = new BiomeEntry(this.biomeId2biomeEntry.size(), biome); //this.biome2biomeEntry.put(biome, entry); this.biomeId2biomeEntry.add(entry); @@ -248,7 +249,7 @@ public class Mapper { continue; } if (this.blockId2stateEntry.indexOf(entry) != entry.id) { - throw new IllegalStateException("State Id NOT THE SAME, very critically bad"); + throw new IllegalStateException("State Id NOT THE SAME, very critically bad. arr:" + this.blockId2stateEntry.indexOf(entry) + " entry: " + entry.id); } byte[] serialized = entry.serialize(); ByteBuffer buffer = MemoryUtil.memAlloc(serialized.length); diff --git a/src/main/resources/assets/voxy/shaders/lod/hierarchical/screenspace2.glsl b/src/main/resources/assets/voxy/shaders/lod/hierarchical/screenspace2.glsl new file mode 100644 index 00000000..418b2566 --- /dev/null +++ b/src/main/resources/assets/voxy/shaders/lod/hierarchical/screenspace2.glsl @@ -0,0 +1,67 @@ + +layout(binding = HIZ_BINDING_INDEX) uniform sampler2DShadow hizDepthSampler; + +vec3 minBB; +vec3 maxBB; +vec2 size; + + +//Sets up screenspace with the given node id, returns true on success false on failure/should not continue +//Accesses data that is setup in the main traversal and is just shared to here +void setupScreenspace(in UnpackedNode node) { + //TODO: implment transform support + Transform transform = transforms[getTransformIndex(node)]; + + + vec4 base = VP*vec4(vec3(((node.pos< 0.0001; +} + +//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)); + return (size.x*size.y*screenW*screenH) > decendSSS; +} \ No newline at end of file diff --git a/src/main/resources/assets/voxy/shaders/lod/hierarchical/traversal.comp b/src/main/resources/assets/voxy/shaders/lod/hierarchical/traversal.comp index 7e632dba..c4427859 100644 --- a/src/main/resources/assets/voxy/shaders/lod/hierarchical/traversal.comp +++ b/src/main/resources/assets/voxy/shaders/lod/hierarchical/traversal.comp @@ -61,15 +61,12 @@ layout(binding = 2, std430) restrict buffer QueueData { uint[] queue; } queue; */ -#line 1 #import -#line 1 + #import -#line 1 //Contains all the screenspace computation #import -#line 58 //If a request is successfully added to the RequestQueue, must update NodeData to mark that the node has been put into the request queue // to prevent it from being requested every frame and blocking the queue diff --git a/src/main/resources/voxy.mixins.json b/src/main/resources/voxy.mixins.json index efe79540..641a7221 100644 --- a/src/main/resources/voxy.mixins.json +++ b/src/main/resources/voxy.mixins.json @@ -11,7 +11,8 @@ "nvidium.MixinRenderPipeline", "sodium.MixinDefaultChunkRenderer", "sodium.MixinRenderSectionManager", - "sodium.MixinSodiumWorldRender" + "sodium.MixinSodiumWorldRender", + "sodium.MixinShaderParser" ], "injectors": { "defaultRequire": 1