Work on basic memory manager for section rendering

This commit is contained in:
mcrcortex
2024-08-03 13:26:08 +10:00
parent 3194f1d23e
commit 945cd68672
11 changed files with 166 additions and 19 deletions

View File

@@ -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() {

View File

@@ -21,13 +21,13 @@ import java.util.List;
import static org.lwjgl.opengl.ARBDirectStateAccess.glGetNamedFramebufferAttachmentParameteri;
import static org.lwjgl.opengl.GL42.*;
public class RenderService<T extends AbstractSectionRenderer<J>, J extends Viewport<J>> {
private static AbstractSectionRenderer<?> createSectionRenderer() {
return new MDICSectionRenderer();
public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Viewport<J>> {
private static AbstractSectionRenderer<?, ?> createSectionRenderer(int maxSectionCount, long geometryCapacity) {
return new MDICSectionRenderer(maxSectionCount, geometryCapacity);
}
private final ViewportSelector<?> viewportSelector;
private final AbstractSectionRenderer<J> sectionRenderer;
private final AbstractSectionRenderer<J, ?> sectionRenderer;
private final HierarchicalNodeManager nodeManager;
private final HierarchicalOcclusionTraverser traversal;
@@ -37,15 +37,19 @@ public class RenderService<T extends AbstractSectionRenderer<J>, 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++) {

View File

@@ -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 {
}

View File

@@ -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) {
}
}

View File

@@ -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() {
}
}

View File

@@ -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 <T extends Viewport<T>> {
public abstract class AbstractSectionRenderer <T extends Viewport<T>, 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;
}
}

View File

@@ -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<SectionMeta> 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();
}
}

View File

@@ -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 {
}

View File

@@ -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<BasicViewport> {
public class MDICSectionRenderer extends AbstractSectionRenderer<BasicViewport, BasicSectionGeometryManager> {
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<BasicViewport>
@Override
public void free() {
super.free();
}
}

View File

@@ -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);

View File

@@ -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];