_basic_ Rendering

This commit is contained in:
mcrcortex
2024-08-04 12:52:33 +10:00
parent 7d883913bd
commit 0366c42cf3
18 changed files with 341 additions and 58 deletions

View File

@@ -74,4 +74,8 @@ public class ModelBakerySubsystem {
public void addDebugData(List<String> debug) { public void addDebugData(List<String> debug) {
debug.add("MQ/IF/MC: " + this.blockIdQueue.size() + "/" + this.factory.getInflightCount() + "/" + this.factory.getBakedCount());//Model bake queue/in flight/model baked count debug.add("MQ/IF/MC: " + this.blockIdQueue.size() + "/" + this.factory.getInflightCount() + "/" + this.factory.getBakedCount());//Model bake queue/in flight/model baked count
} }
public ModelStore getStore() {
return this.storage;
}
} }

View File

@@ -8,9 +8,11 @@ import static org.lwjgl.opengl.GL11C.GL_NEAREST;
import static org.lwjgl.opengl.GL11C.GL_NEAREST_MIPMAP_LINEAR; import static org.lwjgl.opengl.GL11C.GL_NEAREST_MIPMAP_LINEAR;
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MAX_LOD; import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MAX_LOD;
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MIN_LOD; import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MIN_LOD;
import static org.lwjgl.opengl.GL33.glDeleteSamplers; import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL33.glGenSamplers; import static org.lwjgl.opengl.GL33.*;
import static org.lwjgl.opengl.GL33C.glSamplerParameteri; import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
public class ModelStore { public class ModelStore {
public static final int MODEL_SIZE = 64; public static final int MODEL_SIZE = 64;
@@ -39,4 +41,12 @@ public class ModelStore {
this.textures.free(); this.textures.free();
glDeleteSamplers(this.blockSampler); glDeleteSamplers(this.blockSampler);
} }
public void bind(int modelBindingIndex, int colourBindingIndex, int textureBindingIndex) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, modelBindingIndex, this.modelBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, colourBindingIndex, this.modelColourBuffer.id);
glBindTextureUnit(textureBindingIndex, this.textures.id);
glBindSampler(textureBindingIndex, this.blockSampler);
}
} }

View File

@@ -0,0 +1,29 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import net.minecraft.client.MinecraftClient;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.ARBUniformBufferObject.glBindBufferBase;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
public class LightMapHelper {
private static final GlBuffer LIGHT_MAP_BUFFER = new GlBuffer(256*4);
public static void tickLightmap() {
long upload = UploadStream.INSTANCE.upload(LIGHT_MAP_BUFFER, 0, 256*4);
var lmt = MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().texture.getImage();
for (int light = 0; light < 256; light++) {
int x = light&0xF;
int y = ((light>>4)&0xF);
int sample = lmt.getColor(x,y);
sample = ((sample&0xFF0000)>>16)|(sample&0xFF00)|((sample&0xFF)<<16);
MemoryUtil.memPutInt(upload + (((x<<4)|(15-y))*4), sample|(0xFF<<28));//Skylight is inverted
}
}
public static void bind(int lightingBufferIndex) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, lightingBufferIndex, LIGHT_MAP_BUFFER.id);
}
}

View File

@@ -1,8 +1,8 @@
package me.cortex.voxy.client.core.rendering; package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem; 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; import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService; import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.hierachical2.HierarchicalNodeManager; import me.cortex.voxy.client.core.rendering.hierachical2.HierarchicalNodeManager;
@@ -15,15 +15,16 @@ import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.world.WorldEngine; import me.cortex.voxy.common.world.WorldEngine;
import net.minecraft.client.render.Camera; import net.minecraft.client.render.Camera;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import static org.lwjgl.opengl.ARBDirectStateAccess.glGetNamedFramebufferAttachmentParameteri;
import static org.lwjgl.opengl.GL42.*; import static org.lwjgl.opengl.GL42.*;
public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Viewport<J>> { public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Viewport<J>> {
private static AbstractSectionRenderer<?, ?> createSectionRenderer(int maxSectionCount, long geometryCapacity) { public static final int STATIC_VAO = glGenVertexArrays();
return new MDICSectionRenderer(maxSectionCount, geometryCapacity);
private static AbstractSectionRenderer<?, ?> createSectionRenderer(ModelStore store, int maxSectionCount, long geometryCapacity) {
return new MDICSectionRenderer(store, maxSectionCount, geometryCapacity);
} }
private final ViewportSelector<?> viewportSelector; private final ViewportSelector<?> viewportSelector;
@@ -34,26 +35,28 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
private final ModelBakerySubsystem modelService; private final ModelBakerySubsystem modelService;
private final RenderGenerationService renderGen; private final RenderGenerationService renderGen;
private final ConcurrentLinkedDeque<BuiltSection> sectionBuildResultQueue = new ConcurrentLinkedDeque<>();
public RenderService(WorldEngine world) { public RenderService(WorldEngine world) {
this.modelService = new ModelBakerySubsystem(world.getMapper()); this.modelService = new ModelBakerySubsystem(world.getMapper());
//Max sections: ~500k //Max sections: ~500k
//Max geometry: 1 gb //Max geometry: 1 gb
this.sectionRenderer = (T) createSectionRenderer(1<<19, 1<<30); this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<19, (1L<<30)-1024);
this.nodeManager = new HierarchicalNodeManager(1<<21); this.nodeManager = new HierarchicalNodeManager(1<<21, this.sectionRenderer.getGeometryManager());
this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport); this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
this.renderGen = new RenderGenerationService(world, this.modelService, VoxyConfig.CONFIG.renderThreads, this::consumeBuiltSection, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets); this.renderGen = new RenderGenerationService(world, this.modelService, VoxyConfig.CONFIG.renderThreads, this.sectionBuildResultQueue::add, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets);
this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, 512); this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, 512);
world.setDirtyCallback(this.nodeManager::sectionUpdate); world.setDirtyCallback(this.nodeManager::sectionUpdate);
for(int x = -200; x<=200;x++) { for(int x = -10; x<=10;x++) {
for (int z = -200; z <= 200; z++) { for (int z = -10; z <= 10; z++) {
for (int y = -3; y <= 3; y++) { for (int y = -3; y <= 3; y++) {
this.renderGen.enqueueTask(0, x, y, z); this.renderGen.enqueueTask(0, x, y, z);
} }
@@ -61,14 +64,13 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
} }
} }
//Cant do a lambda in the constructor cause "this.nodeManager" could be null??? even tho this does the exact same thing, java is stupid
private void consumeBuiltSection(BuiltSection section) {this.nodeManager.processBuildResult(section);}
public void setup(Camera camera) { public void setup(Camera camera) {
this.modelService.tick(); this.modelService.tick();
} }
public void renderFarAwayOpaque(J viewport) { public void renderFarAwayOpaque(J viewport) {
LightMapHelper.tickLightmap();
//Render previous geometry with the abstract renderer //Render previous geometry with the abstract renderer
//Execute the hieracial selector //Execute the hieracial selector
// render delta sections // render delta sections
@@ -78,9 +80,23 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
this.sectionRenderer.renderOpaque(viewport); this.sectionRenderer.renderOpaque(viewport);
//NOTE: need to do the upload and download tick here, after the section renderer renders the world, to ensure "stable" //NOTE: need to do the upload and download tick here, after the section renderer renders the world, to ensure "stable"
// sections // sections
DownloadStream.INSTANCE.tick();
//FIXME: we only want to tick once per full frame, this is due to how the data of sections is updated
// we basicly need the data to stay stable from one frame to the next, till after renderOpaque
// this is because e.g. shadows, cause this pipeline to be invoked multiple times
// which may cause the geometry to become outdated resulting in corruption rendering in renderOpaque
//TODO: Need to find a proper way to fix this (if there even is one)
if (true /* firstInvocationThisFrame */) {
DownloadStream.INSTANCE.tick();
//Process the build results here (this is done atomically/on the render thread)
while (!this.sectionBuildResultQueue.isEmpty()) {
this.nodeManager.processBuildResult(this.sectionBuildResultQueue.poll());
}
}
UploadStream.INSTANCE.tick(); UploadStream.INSTANCE.tick();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_PIXEL_BUFFER_BARRIER_BIT); glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_PIXEL_BUFFER_BARRIER_BIT);
@@ -98,6 +114,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
public void addDebugData(List<String> debug) { public void addDebugData(List<String> debug) {
this.modelService.addDebugData(debug); this.modelService.addDebugData(debug);
this.renderGen.addDebugData(debug); this.renderGen.addDebugData(debug);
this.sectionRenderer.addDebug(debug);
} }
public void shutdown() { public void shutdown() {
@@ -106,6 +123,9 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
this.viewportSelector.free(); this.viewportSelector.free();
this.sectionRenderer.free(); this.sectionRenderer.free();
this.traversal.free(); this.traversal.free();
//Release all the unprocessed built geometry
this.sectionBuildResultQueue.forEach(BuiltSection::free);
this.sectionBuildResultQueue.clear();
} }
public Viewport<?> getViewport() { public Viewport<?> getViewport() {

View File

@@ -25,6 +25,7 @@ public class SharedIndexBuffer {
quadIndexBuff.free(); quadIndexBuff.free();
cubeBuff.free(); cubeBuff.free();
UploadStream.INSTANCE.commit();
} }
private SharedIndexBuffer(boolean type2) { private SharedIndexBuffer(boolean type2) {

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.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
import me.cortex.voxy.common.world.WorldSection; import me.cortex.voxy.common.world.WorldSection;
import me.jellysquid.mods.sodium.client.util.MathUtil; import me.jellysquid.mods.sodium.client.util.MathUtil;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
@@ -11,8 +12,9 @@ public class HierarchicalNodeManager {
public static final int NODE_MSK = ((1<<24)-1); public static final int NODE_MSK = ((1<<24)-1);
public final int maxNodeCount; public final int maxNodeCount;
private final long[] localNodeData; private final long[] localNodeData;
private final AbstractSectionGeometryManager geometryManager;
public HierarchicalNodeManager(int maxNodeCount) { public HierarchicalNodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager) {
if (!MathUtil.isPowerOfTwo(maxNodeCount)) { if (!MathUtil.isPowerOfTwo(maxNodeCount)) {
throw new IllegalArgumentException("Max node count must be a power of 2"); throw new IllegalArgumentException("Max node count must be a power of 2");
} }
@@ -21,6 +23,7 @@ public class HierarchicalNodeManager {
} }
this.maxNodeCount = maxNodeCount; this.maxNodeCount = maxNodeCount;
this.localNodeData = new long[maxNodeCount*4]; this.localNodeData = new long[maxNodeCount*4];
this.geometryManager = geometryManager;
} }
public void processRequestQueue(int count, long ptr) { public void processRequestQueue(int count, long ptr) {
@@ -32,7 +35,11 @@ public class HierarchicalNodeManager {
} }
public void processBuildResult(BuiltSection section) { public void processBuildResult(BuiltSection section) {
section.free(); if (!section.isEmpty()) {
this.geometryManager.uploadSection(section);
} else {
section.free();
}
} }
//Called when a section is updated in the world engine //Called when a section is updated in the world engine

View File

@@ -17,10 +17,11 @@ public abstract class AbstractSectionGeometryManager {
this.geometryCapacity = geometryCapacity; this.geometryCapacity = geometryCapacity;
} }
//Note, calling uploadSection or uploadReplaceSection will free the supplied BuiltSection
public int uploadSection(BuiltSection section) {return this.uploadReplaceSection(-1, section);} public int uploadSection(BuiltSection section) {return this.uploadReplaceSection(-1, section);}
public abstract int uploadReplaceSection(int oldId, BuiltSection section); public abstract int uploadReplaceSection(int oldId, BuiltSection section);
public abstract void removeSection(int id); public abstract void removeSection(int id);
public void free() { void tick() {}
} public void free() {}
} }

View File

@@ -2,14 +2,19 @@ package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.model.ModelStore;
import me.cortex.voxy.client.core.rendering.Viewport; import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractGeometryManager; import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractGeometryManager;
import java.util.List;
//Takes in mesh ids from the hierachical traversal and may perform more culling then renders it //Takes in mesh ids from the hierachical traversal and may perform more culling then renders it
public abstract class AbstractSectionRenderer <T extends Viewport<T>, J extends AbstractSectionGeometryManager> { public abstract class AbstractSectionRenderer <T extends Viewport<T>, J extends AbstractSectionGeometryManager> {
private final J geometryManager; protected final J geometryManager;
protected AbstractSectionRenderer(J geometryManager) { protected final ModelStore modelStore;
protected AbstractSectionRenderer(ModelStore modelStore, J geometryManager) {
this.geometryManager = geometryManager; this.geometryManager = geometryManager;
this.modelStore = modelStore;
} }
public abstract void renderOpaque(T viewport); public abstract void renderOpaque(T viewport);
@@ -23,4 +28,6 @@ public abstract class AbstractSectionRenderer <T extends Viewport<T>, J extends
public J getGeometryManager() { public J getGeometryManager() {
return this.geometryManager; return this.geometryManager;
} }
public void addDebug(List<String> lines) {}
} }

View File

@@ -1,11 +1,13 @@
package me.cortex.voxy.client.core.rendering.section; package me.cortex.voxy.client.core.rendering.section;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.building.BuiltSection; 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.geometry.OLD.DefaultGeometryManager;
import me.cortex.voxy.client.core.rendering.util.BufferArena; import me.cortex.voxy.client.core.rendering.util.BufferArena;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.util.HierarchicalBitSet; import me.cortex.voxy.common.util.HierarchicalBitSet;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text; import net.minecraft.text.Text;
@@ -30,6 +32,10 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager
@Override @Override
public int uploadReplaceSection(int oldId, BuiltSection sectionData) { public int uploadReplaceSection(int oldId, BuiltSection sectionData) {
if (sectionData.isEmpty()) {
throw new IllegalArgumentException("sectionData is empty, cannot upload nothing");
}
//Free the old id and replace it with a new one //Free the old id and replace it with a new one
// if oldId is -1, then treat it as not previously existing // if oldId is -1, then treat it as not previously existing
@@ -43,9 +49,22 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager
if (newId == HierarchicalBitSet.SET_FULL) { if (newId == HierarchicalBitSet.SET_FULL) {
throw new IllegalStateException("Tried adding section when section count is already at capacity"); throw new IllegalStateException("Tried adding section when section count is already at capacity");
} }
if (newId > this.sectionMetadata.size()) {
throw new IllegalStateException();
}
var newMeta = createMeta(sectionData);
//Release the section data as its not needed anymore
sectionData.free();
if (newId == this.sectionMetadata.size()) {
this.sectionMetadata.add(newMeta);
} else {
this.sectionMetadata.set(newId, newMeta);
}
//Invalidate the section id
this.invalidatedSectionIds.add(newId);
return newId; return newId;
} }
@@ -83,10 +102,44 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager
} }
} }
@Override
void tick() {
//Upload all invalidated bits
if (!this.invalidatedSectionIds.isEmpty()) {
for (int id : this.invalidatedSectionIds) {
var meta = this.sectionMetadata.get(id);
long ptr = UploadStream.INSTANCE.upload(this.sectionMetadataBuffer, (long) id *SECTION_METADATA_SIZE, SECTION_METADATA_SIZE);
if (meta == null) {//We need to clear the gpu side buffer
MemoryUtil.memSet(ptr, 0, SECTION_METADATA_SIZE);
} else {
meta.writeMetadata(ptr);
}
}
this.invalidatedSectionIds.clear();
UploadStream.INSTANCE.commit();
}
}
@Override @Override
public void free() { public void free() {
super.free(); super.free();
this.sectionMetadataBuffer.free(); this.sectionMetadataBuffer.free();
this.geometry.free(); this.geometry.free();
} }
int getSectionCount() {
return this.allocationSet.getCount();
}
long getGeometryUsed() {
return this.geometry.getUsedBytes();
}
int getGeometryBufferId() {
return this.geometry.id();
}
int getMetadataBufferId() {
return this.sectionMetadataBuffer.id;
}
} }

View File

@@ -2,23 +2,131 @@ package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.model.ModelStore;
import me.cortex.voxy.client.core.rendering.LightMapHelper;
import me.cortex.voxy.client.core.rendering.RenderService;
import me.cortex.voxy.client.core.rendering.SharedIndexBuffer;
import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractFarWorldRenderer;
import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractGeometryManager; import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractGeometryManager;
import me.cortex.voxy.client.core.rendering.geometry.OLD.Gl46HierarchicalViewport; import me.cortex.voxy.client.core.rendering.geometry.OLD.Gl46HierarchicalViewport;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.math.MathHelper;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL32.glDrawElementsInstancedBaseVertex;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.glDrawElementsInstancedBaseVertexBaseInstance;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
//Uses MDIC to render the sections //Uses MDIC to render the sections
public class MDICSectionRenderer extends AbstractSectionRenderer<BasicViewport, BasicSectionGeometryManager> { public class MDICSectionRenderer extends AbstractSectionRenderer<BasicViewport, BasicSectionGeometryManager> {
public MDICSectionRenderer(int maxSectionCount, long geometryCapacity) { private final Shader terrainShader = Shader.make()
super(new BasicSectionGeometryManager(maxSectionCount, geometryCapacity)); .add(ShaderType.VERTEX, "voxy:lod/gl46/quads2.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/quads.frag")
.compile();
private final GlBuffer uniform = new GlBuffer(1024).zero();
public MDICSectionRenderer(ModelStore modelStore, int maxSectionCount, long geometryCapacity) {
super(modelStore, new BasicSectionGeometryManager(maxSectionCount, geometryCapacity));
}
private void uploadUniformBuffer(BasicViewport viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniform, 0, 1024);
int sx = MathHelper.floor(viewport.cameraX)>>5;
int sy = MathHelper.floor(viewport.cameraY)>>5;
int sz = MathHelper.floor(viewport.cameraZ)>>5;
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
var innerTranslation = new Vector3f((float) (viewport.cameraX-(sx<<5)), (float) (viewport.cameraY-(sy<<5)), (float) (viewport.cameraZ-(sz<<5)));
mat.translate(-innerTranslation.x, -innerTranslation.y, -innerTranslation.z);
mat.getToAddress(ptr); ptr += 4*4*4;
MemoryUtil.memPutInt(ptr, sx); ptr += 4;
MemoryUtil.memPutInt(ptr, sy); ptr += 4;
MemoryUtil.memPutInt(ptr, sz); ptr += 4;
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
innerTranslation.getToAddress(ptr); ptr += 4*3;
UploadStream.INSTANCE.commit();
}
private void bindRenderingBuffers() {
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometryManager.getGeometryBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometryManager.getMetadataBufferId());
this.modelStore.bind(3, 4, 0);
LightMapHelper.bind(5);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
//glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.glCommandBuffer.id);
//glBindBuffer(GL_PARAMETER_BUFFER_ARB, this.glCommandCountBuffer.id);
}
//Prep the terrain draw calls for this frame, also sets up the
// remaining render pipeline for this frame
private void prepTerrainCallsAndPrep() {
}
private void renderTerrain() {
RenderLayer.getCutoutMipped().startDrawing();
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
this.terrainShader.bind();
glBindVertexArray(RenderService.STATIC_VAO);//Needs to be before binding
this.bindRenderingBuffers();
glDrawElementsInstancedBaseVertexBaseInstance(GL_TRIANGLES, 1000*6, GL_UNSIGNED_SHORT, 0,1,0,0);
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
RenderLayer.getCutoutMipped().endDrawing();
} }
@Override @Override
public void renderOpaque(BasicViewport viewport) { public void renderOpaque(BasicViewport viewport) {
if (this.geometryManager.getSectionCount() == 0) return;
this.uploadUniformBuffer(viewport);
} }
@Override @Override
public void buildDrawCallsAndRenderTemporal(BasicViewport viewport, GlBuffer sectionRenderList) { public void buildDrawCallsAndRenderTemporal(BasicViewport viewport, GlBuffer sectionRenderList) {
//Can do a sneeky trick, since the sectionRenderList is a list to things to render, it invokes the culler
// which only marks visible sections
//Tick the geometry manager to upload all invalidated metadata changes to the gpu
this.geometryManager.tick();
this.renderTerrain();
} }
@Override @Override
@@ -26,6 +134,12 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<BasicViewport,
} }
@Override
public void addDebug(List<String> lines) {
super.addDebug(lines);
lines.add("NC/GS: " + this.geometryManager.getSectionCount() + "/" + (this.geometryManager.getGeometryUsed()/(1024*1024)));//Node count/geometry size (MB)
}
@Override @Override
public BasicViewport createViewport() { public BasicViewport createViewport() {
return new BasicViewport(); return new BasicViewport();
@@ -34,5 +148,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<BasicViewport,
@Override @Override
public void free() { public void free() {
super.free(); super.free();
this.uniform.free();
this.terrainShader.free();
} }
} }

View File

@@ -19,7 +19,7 @@ public class BufferArena {
throw new IllegalArgumentException("Capacity not a multiple of element size"); throw new IllegalArgumentException("Capacity not a multiple of element size");
} }
if (capacity > Capabilities.INSTANCE.ssboMaxSize) { if (capacity > Capabilities.INSTANCE.ssboMaxSize) {
throw new IllegalArgumentException("Buffer is bigger than max ssbo size"); throw new IllegalArgumentException("Buffer is bigger than max ssbo size (requested " + capacity + " but has max of " + Capabilities.INSTANCE.ssboMaxSize+")");
} }
this.size = capacity; this.size = capacity;
this.elementSize = elementSize; this.elementSize = elementSize;
@@ -58,4 +58,8 @@ public class BufferArena {
public float usage() { public float usage() {
return (float) ((double)this.used/(this.buffer.size()/this.elementSize)); return (float) ((double)this.used/(this.buffer.size()/this.elementSize));
} }
public long getUsedBytes() {
return this.used*this.elementSize;
}
} }

View File

@@ -118,6 +118,7 @@ public class WorldEngine {
public void insertUpdate(VoxelizedSection section) {//TODO: add a bitset of levels to update and if it should force update public void insertUpdate(VoxelizedSection section) {//TODO: add a bitset of levels to update and if it should force update
//The >>1 is cause the world sections size is 32x32x32 vs the 16x16x16 of the voxelized section //The >>1 is cause the world sections size is 32x32x32 vs the 16x16x16 of the voxelized section
for (int lvl = 0; lvl < this.maxMipLevels; lvl++) { for (int lvl = 0; lvl < this.maxMipLevels; lvl++) {
int nonAirCountDelta = 0;
var worldSection = this.acquire(lvl, section.x >> (lvl + 1), section.y >> (lvl + 1), section.z >> (lvl + 1)); var worldSection = this.acquire(lvl, section.x >> (lvl + 1), section.y >> (lvl + 1), section.z >> (lvl + 1));
int msk = (1<<(lvl+1))-1; int msk = (1<<(lvl+1))-1;
int bx = (section.x&msk)<<(4-lvl); int bx = (section.x&msk)<<(4-lvl);
@@ -129,11 +130,19 @@ public class WorldEngine {
for (int x = bx; x < (16>>lvl)+bx; x++) { for (int x = bx; x < (16>>lvl)+bx; x++) {
long newId = section.get(lvl, x-bx, y-by, z-bz); long newId = section.get(lvl, x-bx, y-by, z-bz);
long oldId = worldSection.set(x, y, z, newId); long oldId = worldSection.set(x, y, z, newId);
nonAirCountDelta += Mapper.isAir(oldId)==Mapper.isAir(newId)?0:(Mapper.isAir(newId)?-1:1 );
didChange |= newId != oldId; didChange |= newId != oldId;
} }
} }
} }
//Branch into 2 paths, if at lod 0, update the atomic count, if that update resulted in a state transition
// then aquire the next lod, lock it, recheck our counter, if it is still ok, then atomically update the parent metadata
//if not lod 0 check that the current occupied state matches the parent lod bit
// if it doesnt, aquire and lock the next lod level
// and do the update propagation
//Need to release the section after using it //Need to release the section after using it
if (didChange) { if (didChange) {
//Mark the section as dirty (enqueuing saving and geometry rebuild) and move to parent mip level //Mark the section as dirty (enqueuing saving and geometry rebuild) and move to parent mip level

View File

@@ -6,6 +6,8 @@ import java.util.Arrays;
import java.util.Deque; import java.util.Deque;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
//Represents a loaded world section at a specific detail level //Represents a loaded world section at a specific detail level
// holds a 32x32x32 region of detail // holds a 32x32x32 region of detail
@@ -21,7 +23,16 @@ public final class WorldSection {
public final int z; public final int z;
public final long key; public final long key;
//Serialized states
long metadata;
long[] data = null; long[] data = null;
//Computed on load, updated on insertion
private final AtomicInteger nonAirCount = new AtomicInteger(0);
private final ActiveSectionTracker tracker; private final ActiveSectionTracker tracker;
public final AtomicBoolean inSaveQueue = new AtomicBoolean(); public final AtomicBoolean inSaveQueue = new AtomicBoolean();

View File

@@ -52,7 +52,9 @@ public class Mapper {
public static boolean isAir(long id) { public static boolean isAir(long id) {
return ((id>>27)&((1<<20)-1)) == 0; int bId = getBlockId(id);
//Note: air can mean void, cave or normal air, as the block state is remapped during ingesting
return bId == 0;
} }
public static int getBlockId(long id) { public static int getBlockId(long id) {
@@ -185,6 +187,9 @@ public class Mapper {
//TODO: replace lambda with a class cached lambda ref (cause doing this:: still does a lambda allocation) //TODO: replace lambda with a class cached lambda ref (cause doing this:: still does a lambda allocation)
public int getIdForBlockState(BlockState state) { public int getIdForBlockState(BlockState state) {
if (state.isAir()) {
return 0;
}
return this.block2stateEntry.computeIfAbsent(state, this::registerNewBlockState).id; return this.block2stateEntry.computeIfAbsent(state, this::registerNewBlockState).id;
} }

View File

@@ -1,15 +1,10 @@
#line 1 #line 1
struct Frustum {
vec4 planes[6];
};
layout(binding = 0, std140) uniform SceneUniform { layout(binding = 0, std140) uniform SceneUniform {
mat4 MVP; mat4 MVP;
ivec3 baseSectionPos; ivec3 baseSectionPos;
int sectionCount;
Frustum frustum;
vec3 cameraSubPos;
uint frameId; uint frameId;
vec3 cameraSubPos;
}; };
struct BlockModel { struct BlockModel {
@@ -39,44 +34,63 @@ struct DrawCommand {
uint baseInstance; uint baseInstance;
}; };
layout(binding = 0) uniform sampler2D blockModelAtlas;
#ifdef BLOCK_MODEL_TEXTURE_BINDING
layout(binding = BLOCK_MODEL_TEXTURE_BINDING) uniform sampler2D blockModelAtlas;
#endif
#ifndef Quad #ifndef Quad
#define Quad ivec2 #define Quad ivec2
#endif #endif
layout(binding = 1, std430) readonly restrict buffer QuadBuffer { #ifdef QUAD_BUFFER_BINDING
layout(binding = QUAD_BUFFER_BINDING, std430) readonly restrict buffer QuadBuffer {
Quad quadData[]; Quad quadData[];
}; };
#endif
layout(binding = 2, std430) writeonly restrict buffer DrawBuffer { #ifdef DRAW_BUFFER_BINDING
layout(binding = DRAW_BUFFER_BINDING, std430) writeonly restrict buffer DrawBuffer {
DrawCommand cmdBuffer[]; DrawCommand cmdBuffer[];
}; };
#endif
layout(binding = 3, std430) restrict buffer DrawCommandCountBuffer { #ifdef DRAW_COUNT_BUFFER_BINDING
layout(binding = DRAW_COUNT_BUFFER_BINDING, std430) restrict buffer DrawCommandCountBuffer {
uint opaqueDrawCount; uint opaqueDrawCount;
uint translucentDrawCount; uint translucentDrawCount;
}; };
#endif
layout(binding = 4, std430) readonly restrict buffer SectionBuffer { #ifdef SECTION_METADA_BUFFER_BINDING
layout(binding = SECTION_METADA_BUFFER_BINDING, std430) readonly restrict buffer SectionBuffer {
SectionMeta sectionData[]; SectionMeta sectionData[];
}; };
#endif
#ifndef VISIBILITY_ACCESS #ifndef VISIBILITY_ACCESS
#define VISIBILITY_ACCESS readonly #define VISIBILITY_ACCESS readonly
#endif #endif
layout(binding = 5, std430) VISIBILITY_ACCESS restrict buffer VisibilityBuffer { #ifdef VISIBILITY_BUFFER_BINDING
layout(binding = VISIBILITY_BUFFER_BINDING, std430) VISIBILITY_ACCESS restrict buffer VisibilityBuffer {
uint visibilityData[]; uint visibilityData[];
}; };
#endif
layout(binding = 6, std430) readonly restrict buffer ModelBuffer { #ifdef MODEL_BUFFER_BINDING
layout(binding = MODEL_BUFFER_BINDING, std430) readonly restrict buffer ModelBuffer {
BlockModel modelData[]; BlockModel modelData[];
}; };
#endif
layout(binding = 7, std430) readonly restrict buffer ModelColourBuffer { #ifdef MODEL_COLOUR_BUFFER_BINDING
layout(binding = MODEL_COLOUR_BUFFER_BINDING, std430) readonly restrict buffer ModelColourBuffer {
uint colourData[]; uint colourData[];
}; };
#endif
layout(binding = 8, std430) readonly restrict buffer LightingBuffer { #ifdef LIGHTING_BUFFER_BINDING
layout(binding = LIGHTING_BUFFER_BINDING, std430) readonly restrict buffer LightingBuffer {
uint lightData[]; uint lightData[];
}; };
@@ -86,5 +100,5 @@ vec4 getLighting(uint index) {
arr = arr & uvec4(0xFF); arr = arr & uvec4(0xFF);
return vec4(arr)*vec4(1.0f/255.0f); return vec4(arr)*vec4(1.0f/255.0f);
} }
#endif

View File

@@ -5,7 +5,6 @@ layout(local_size_x = 128) in;
#import <voxy:lod/quad_format.glsl> #import <voxy:lod/quad_format.glsl>
#import <voxy:lod/gl46/bindings.glsl> #import <voxy:lod/gl46/bindings.glsl>
#import <voxy:lod/gl46/frustum.glsl>
#import <voxy:lod/section.glsl> #import <voxy:lod/section.glsl>
#line 11 #line 11

View File

@@ -1,13 +0,0 @@
bool testFrustumPoint(vec4 plane, vec3 min, vec3 max) {
vec3 point = mix(max, min, lessThan(plane.xyz, vec3(0))) * plane.xyz;
return (point.x + point.y + point.z) >= -plane.w;
}
bool testFrustum(Frustum frust, vec3 min, vec3 max) {
return testFrustumPoint(frust.planes[0], min, max) &&
testFrustumPoint(frust.planes[1], min, max) &&
testFrustumPoint(frust.planes[2], min, max) &&
testFrustumPoint(frust.planes[3], min, max) &&
testFrustumPoint(frust.planes[4], min, max) &&
testFrustumPoint(frust.planes[5], min, max);
}

View File

@@ -1,10 +1,16 @@
#version 460 core #version 460 core
#extension GL_ARB_gpu_shader_int64 : enable #extension GL_ARB_gpu_shader_int64 : enable
#define QUAD_BUFFER_BINDING 1
#define SECTION_METADA_BUFFER_BINDING 2
#define MODEL_BUFFER_BINDING 3
#define MODEL_COLOUR_BUFFER_BINDING 4
#define LIGHTING_BUFFER_BINDING 5
#import <voxy:lod/quad_format.glsl> #import <voxy:lod/quad_format.glsl>
#import <voxy:lod/gl46/bindings.glsl> #import <voxy:lod/gl46/bindings.glsl>
#import <voxy:lod/block_model.glsl> #import <voxy:lod/block_model.glsl>
#line 8
//#define DEBUG_RENDER //#define DEBUG_RENDER