diff --git a/src/main/java/me/cortex/voxy/client/core/Capabilities.java b/src/main/java/me/cortex/voxy/client/core/Capabilities.java index 468ec576..4d6d5ab3 100644 --- a/src/main/java/me/cortex/voxy/client/core/Capabilities.java +++ b/src/main/java/me/cortex/voxy/client/core/Capabilities.java @@ -5,12 +5,17 @@ import me.cortex.voxy.client.core.gl.shader.ShaderType; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL20C; +import static org.lwjgl.opengl.GL32.glGetInteger64; +import static org.lwjgl.opengl.GL32C.glGetInteger64i; +import static org.lwjgl.opengl.GL43C.GL_MAX_SHADER_STORAGE_BLOCK_SIZE; + public class Capabilities { public static final Capabilities INSTANCE = new Capabilities(); public final boolean meshShaders; public final boolean INT64_t; + public final long ssboMaxSize; public Capabilities() { var cap = GL.getCapabilities(); this.meshShaders = cap.GL_NV_mesh_shader && cap.GL_NV_representative_fragment_test; @@ -24,6 +29,8 @@ public class Capabilities { uint64_t a = 1234; } """); + + this.ssboMaxSize = glGetInteger64(GL_MAX_SHADER_STORAGE_BLOCK_SIZE); } public static void init() { 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 288d63f8..44621f68 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 @@ -21,13 +21,13 @@ import java.util.List; import static org.lwjgl.opengl.ARBDirectStateAccess.glGetNamedFramebufferAttachmentParameteri; import static org.lwjgl.opengl.GL42.*; -public class RenderService, J extends Viewport> { - private static AbstractSectionRenderer createSectionRenderer() { - return new MDICSectionRenderer(); +public class RenderService, J extends Viewport> { + private static AbstractSectionRenderer createSectionRenderer(int maxSectionCount, long geometryCapacity) { + return new MDICSectionRenderer(maxSectionCount, geometryCapacity); } private final ViewportSelector viewportSelector; - private final AbstractSectionRenderer sectionRenderer; + private final AbstractSectionRenderer sectionRenderer; private final HierarchicalNodeManager nodeManager; private final HierarchicalOcclusionTraverser traversal; @@ -37,15 +37,19 @@ public class RenderService, J extends Viewp public RenderService(WorldEngine world) { this.modelService = new ModelBakerySubsystem(world.getMapper()); + + //Max sections: ~500k + //Max geometry: 1 gb + this.sectionRenderer = (T) createSectionRenderer(1<<19, 1<<30); + this.nodeManager = new HierarchicalNodeManager(1<<21); - this.sectionRenderer = (T) createSectionRenderer(); this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport); - this.renderGen = new RenderGenerationService(world, this.modelService, VoxyConfig.CONFIG.renderThreads, this::consumeBuiltSection, this.sectionRenderer instanceof IUsesMeshlets); + this.renderGen = new RenderGenerationService(world, this.modelService, VoxyConfig.CONFIG.renderThreads, this::consumeBuiltSection, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets); this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, 512); - world.setDirtyCallback(section -> System.out.println("Section updated!!: " + WorldEngine.pprintPos(section.key))); + world.setDirtyCallback(this.nodeManager::sectionUpdate); /* for(int x = -200; x<=200;x++) { diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/geometry/AbstractGeometryRenderer.java b/src/main/java/me/cortex/voxy/client/core/rendering/geometry/AbstractGeometryRenderer.java deleted file mode 100644 index 5545d61b..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/geometry/AbstractGeometryRenderer.java +++ /dev/null @@ -1,6 +0,0 @@ -package me.cortex.voxy.client.core.rendering.geometry; - -//The geometry renderer, takes a list of section ids to render (gpu side buffer) and renders them -// Also manages base geometry info -public abstract class AbstractGeometryRenderer { -} 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 c596bf4a..bc082577 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 @@ -2,6 +2,7 @@ package me.cortex.voxy.client.core.rendering.hierachical2; import me.cortex.voxy.client.core.rendering.building.BuiltSection; +import me.cortex.voxy.common.world.WorldSection; import me.jellysquid.mods.sodium.client.util.MathUtil; import org.lwjgl.system.MemoryUtil; @@ -33,4 +34,9 @@ public class HierarchicalNodeManager { public void processBuildResult(BuiltSection section) { } + + //Called when a section is updated in the world engine + public void sectionUpdate(WorldSection section) { + + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionGeometryManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionGeometryManager.java index 0923f991..59c52e0e 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionGeometryManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionGeometryManager.java @@ -1,4 +1,26 @@ package me.cortex.voxy.client.core.rendering.section; +import me.cortex.voxy.client.core.rendering.building.BuiltSection; +import me.jellysquid.mods.sodium.client.util.MathUtil; + +//Does not care about the position of the sections, multiple sections that have the same position can be uploaded +// it is up to the traversal system to manage what sections exist in the geometry buffer +// the system is basicly "dumb" as in it just follows orders public abstract class AbstractSectionGeometryManager { + public final int maxSections; + public final long geometryCapacity; + protected AbstractSectionGeometryManager(int maxSections, long geometryCapacity) { + if (!MathUtil.isPowerOfTwo(maxSections)) {//TODO: Maybe not do this, as it isnt a strict requirement + throw new IllegalArgumentException("Max sections should be a power of 2"); + } + this.maxSections = maxSections; + this.geometryCapacity = geometryCapacity; + } + + public int uploadSection(BuiltSection section) {return this.uploadReplaceSection(-1, section);} + public abstract int uploadReplaceSection(int oldId, BuiltSection section); + public abstract void removeSection(int id); + public void free() { + + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionRenderer.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionRenderer.java index 2442a8ac..dbe731d3 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionRenderer.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/AbstractSectionRenderer.java @@ -3,12 +3,24 @@ package me.cortex.voxy.client.core.rendering.section; import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.rendering.Viewport; +import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractGeometryManager; //Takes in mesh ids from the hierachical traversal and may perform more culling then renders it -public abstract class AbstractSectionRenderer > { +public abstract class AbstractSectionRenderer , J extends AbstractSectionGeometryManager> { + private final J geometryManager; + protected AbstractSectionRenderer(J geometryManager) { + this.geometryManager = geometryManager; + } + public abstract void renderOpaque(T viewport); public abstract void buildDrawCallsAndRenderTemporal(T viewport, GlBuffer sectionRenderList); public abstract void renderTranslucent(T viewport); public abstract T createViewport(); - public abstract void free(); + public void free() { + this.geometryManager.free(); + } + + public J getGeometryManager() { + return this.geometryManager; + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/BasicSectionGeometryManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/BasicSectionGeometryManager.java new file mode 100644 index 00000000..10bc841c --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/BasicSectionGeometryManager.java @@ -0,0 +1,92 @@ +package me.cortex.voxy.client.core.rendering.section; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import me.cortex.voxy.client.core.gl.GlBuffer; +import me.cortex.voxy.client.core.rendering.building.BuiltSection; +import me.cortex.voxy.client.core.rendering.geometry.OLD.DefaultGeometryManager; +import me.cortex.voxy.client.core.rendering.util.BufferArena; +import me.cortex.voxy.common.util.HierarchicalBitSet; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import org.lwjgl.system.MemoryUtil; + +public class BasicSectionGeometryManager extends AbstractSectionGeometryManager { + private static final int SECTION_METADATA_SIZE = 32; + private final GlBuffer sectionMetadataBuffer; + private final BufferArena geometry; + private final HierarchicalBitSet allocationSet; + private final ObjectArrayList sectionMetadata = new ObjectArrayList<>(1<<15); + + //These are section ids that need to be written to the gpu buffer + private final IntOpenHashSet invalidatedSectionIds = new IntOpenHashSet(); + + public BasicSectionGeometryManager(int maxSectionCount, long geometryCapacity) { + super(maxSectionCount, geometryCapacity); + this.allocationSet = new HierarchicalBitSet(maxSectionCount); + this.sectionMetadataBuffer = new GlBuffer((long) maxSectionCount * SECTION_METADATA_SIZE); + this.geometry = new BufferArena(geometryCapacity, 8);//8 Cause a quad is 8 bytes + } + + @Override + public int uploadReplaceSection(int oldId, BuiltSection sectionData) { + //Free the old id and replace it with a new one + // if oldId is -1, then treat it as not previously existing + + //Free the old data if oldId is supplied + if (oldId != -1) { + //Its here just for future optimization potential + this.removeSection(oldId); + } + + int newId = this.allocationSet.allocateNext(); + if (newId == HierarchicalBitSet.SET_FULL) { + throw new IllegalStateException("Tried adding section when section count is already at capacity"); + } + + + + return newId; + } + + @Override + public void removeSection(int id) { + if (!this.allocationSet.free(id)) { + throw new IllegalStateException("Id was not already allocated"); + } + var oldMetadata = this.sectionMetadata.set(id, null); + this.geometry.free(oldMetadata.geometryPtr); + this.invalidatedSectionIds.add(id); + } + + private SectionMeta createMeta(BuiltSection geometry) { + int geometryPtr = (int) this.geometry.upload(geometry.geometryBuffer); + if (geometryPtr == -1) { + throw new IllegalStateException("Unable to upload section geometry as geometry buffer is full"); + } + //8 bytes per quad + return new SectionMeta(geometry.position, geometry.aabb, geometryPtr, (int) (geometry.geometryBuffer.size/8), geometry.offsets); + } + + private record SectionMeta(long position, int aabb, int geometryPtr, int itemCount, int[] offsets) { + public void writeMetadata(long ptr) { + //Split the long into 2 ints to solve endian issues + MemoryUtil.memPutInt(ptr, (int) (this.position>>32)); ptr += 4; + MemoryUtil.memPutInt(ptr, (int) this.position); ptr += 4; + MemoryUtil.memPutInt(ptr, (int) this.aabb); ptr += 4; + MemoryUtil.memPutInt(ptr, this.geometryPtr + this.offsets[0]); ptr += 4; + + MemoryUtil.memPutInt(ptr, (this.offsets[1]-this.offsets[0])|((this.offsets[2]-this.offsets[1])<<16)); ptr += 4; + MemoryUtil.memPutInt(ptr, (this.offsets[3]-this.offsets[2])|((this.offsets[4]-this.offsets[3])<<16)); ptr += 4; + MemoryUtil.memPutInt(ptr, (this.offsets[5]-this.offsets[4])|((this.offsets[6]-this.offsets[5])<<16)); ptr += 4; + MemoryUtil.memPutInt(ptr, (this.offsets[7]-this.offsets[6])|((this.itemCount -this.offsets[7])<<16)); ptr += 4; + } + } + + @Override + public void free() { + super.free(); + this.sectionMetadataBuffer.free(); + this.geometry.free(); + } +} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/IUsesMeshlets.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/IUsesMeshlets.java index 36f42128..d765522e 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/section/IUsesMeshlets.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/IUsesMeshlets.java @@ -1,5 +1,5 @@ package me.cortex.voxy.client.core.rendering.section; -//A dummy empty interface to mark that the class uses meshlets +//A dummy empty interface to mark that the geometry manager as using meshlets, so that the mesh generator emits as meshlets public interface IUsesMeshlets { } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/MDICSectionRenderer.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/MDICSectionRenderer.java index 386f9ff9..5015a316 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/section/MDICSectionRenderer.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/MDICSectionRenderer.java @@ -2,10 +2,15 @@ package me.cortex.voxy.client.core.rendering.section; import me.cortex.voxy.client.core.gl.GlBuffer; +import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractGeometryManager; import me.cortex.voxy.client.core.rendering.geometry.OLD.Gl46HierarchicalViewport; //Uses MDIC to render the sections -public class MDICSectionRenderer extends AbstractSectionRenderer { +public class MDICSectionRenderer extends AbstractSectionRenderer { + public MDICSectionRenderer(int maxSectionCount, long geometryCapacity) { + super(new BasicSectionGeometryManager(maxSectionCount, geometryCapacity)); + } + @Override public void renderOpaque(BasicViewport viewport) { @@ -28,6 +33,6 @@ public class MDICSectionRenderer extends AbstractSectionRenderer @Override public void free() { - + super.free(); } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/util/BufferArena.java b/src/main/java/me/cortex/voxy/client/core/rendering/util/BufferArena.java index 7485c68c..b98d30ab 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/util/BufferArena.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/util/BufferArena.java @@ -1,5 +1,6 @@ package me.cortex.voxy.client.core.rendering.util; +import me.cortex.voxy.client.core.Capabilities; import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.util.AllocationArena; import me.cortex.voxy.common.util.MemoryBuffer; @@ -17,6 +18,9 @@ public class BufferArena { if (capacity%elementSize != 0) { throw new IllegalArgumentException("Capacity not a multiple of element size"); } + if (capacity > Capabilities.INSTANCE.ssboMaxSize) { + throw new IllegalArgumentException("Buffer is bigger than max ssbo size"); + } this.size = capacity; this.elementSize = elementSize; this.buffer = new GlBuffer(capacity); 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 d45dac6b..4ecff0c4 100644 --- a/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java +++ b/src/main/java/me/cortex/voxy/common/util/HierarchicalBitSet.java @@ -1,6 +1,7 @@ package me.cortex.voxy.common.util; public class HierarchicalBitSet { + public static final int SET_FULL = -1; private final int limit; private int cnt; //If a bit is 1 it means all children are also set @@ -24,7 +25,7 @@ public class HierarchicalBitSet { return -1; } if (this.cnt+1>this.limit) { - return -2;//Limit reached + return -1;//Limit reached } int idx = Long.numberOfTrailingZeros(~this.A); long bp = this.B[idx];