Rasterized occlusion culling

This commit is contained in:
mcrcortex
2023-11-16 12:55:35 +10:00
parent 5d01a37192
commit 7e74d4fec8
11 changed files with 160 additions and 54 deletions

View File

@@ -24,10 +24,13 @@ public class DistanceTracker {
this.rings = new TransitionRing2D[rings+1];
this.tracker = tracker;
int DIST = 16;
//NOTE: This is in our render distance units, to convert to chunks at lvl 0 multiply by 2
int DIST = 24;
this.rings[0] = new TransitionRing2D(5, DIST, (x,z)->{
if (true) return;
if (true) {
return;
}
for (int y = -2; y < 10; y++) {
this.tracker.remLvl0(x, y, z);
}

View File

@@ -48,6 +48,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
.compile();
private final GlBuffer glCommandBuffer = new GlBuffer(100_000*5*4, 0);
private final GlBuffer glVisibilityBuffer = new GlBuffer(100_000*4, 0);
public Gl46FarWorldRenderer() {
super();
@@ -63,9 +64,10 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.glCommandBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.geometry.metaId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.stateDataBuffer.id);//State LUT
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, this.biomeDataBuffer.id);//Biome LUT
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, 0);//Lighting LUT
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.glVisibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, this.stateDataBuffer.id);//State LUT
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.biomeDataBuffer.id);//Biome LUT
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, 0);//Lighting LUT
glBindVertexArray(0);
}
@@ -91,11 +93,23 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT|GL_FRAMEBUFFER_BARRIER_BIT);
//TODO: add gpu occlusion culling here (after the lod drawing) (maybe, finish the rest of the PoC first)
cullShader.bind();
glColorMask(false,false,false,false);
glDepthMask(false);
glDrawElementsInstanced(GL_TRIANGLES, 6*2*3, GL_UNSIGNED_BYTE, (1<<16)*6*2, this.geometry.getSectionCount());
glDepthMask(true);
glColorMask(true,true,true,true);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
glBindVertexArray(0);
RenderLayer.getCutoutMipped().endDrawing();
}
//FIXME: dont do something like this as it breaks multiviewport mods
private int frameId = 0;
private void updateUniformBuffer(MatrixStack stack, double cx, double cy, double cz) {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
var mat = new Matrix4f(RenderSystem.getProjectionMatrix()).mul(stack.peek().getPositionMatrix());
@@ -111,6 +125,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
plane.getToAddress(ptr); ptr += 4*4;
}
innerTranslation.getToAddress(ptr); ptr += 4*3;
MemoryUtil.memPutInt(ptr, this.frameId++); ptr += 4;
}
@Override
@@ -119,6 +134,8 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
this.commandGen.free();
this.lodShader.free();
this.cullShader.free();
this.glCommandBuffer.free();
this.glVisibilityBuffer.free();
}
@Override

View File

@@ -167,19 +167,23 @@ public class RenderTracker {
}
public int getBuildFlagsOrAbort(WorldSection section) {
var cam = MinecraftClient.getInstance().cameraEntity;
if (cam == null) {
return 0;
}
var holder = this.activeSections.get(section.getKey());
int buildMask = 0;
if (holder != null) {
if (section.z< (((int)MinecraftClient.getInstance().cameraEntity.getPos().z)>>(5+section.lvl))+1) {
if (section.z<(((int)cam.getPos().z)>>(5+section.lvl))+1) {
buildMask |= 1<< Direction.SOUTH.getId();
}
if (section.z>(((int)MinecraftClient.getInstance().cameraEntity.getPos().z)>>(5+section.lvl))-1) {
if (section.z>(((int)cam.getPos().z)>>(5+section.lvl))-1) {
buildMask |= 1<<Direction.NORTH.getId();
}
if (section.x<(((int)MinecraftClient.getInstance().cameraEntity.getPos().x)>>(5+section.lvl))+1) {
if (section.x<(((int)cam.getPos().x)>>(5+section.lvl))+1) {
buildMask |= 1<<Direction.EAST.getId();
}
if (section.x>(((int)MinecraftClient.getInstance().cameraEntity.getPos().x)>>(5+section.lvl))-1) {
if (section.x>(((int)cam.getPos().x)>>(5+section.lvl))-1) {
buildMask |= 1<<Direction.WEST.getId();
}
buildMask |= 1<<Direction.UP.getId();

View File

@@ -1,39 +1,89 @@
package me.cortex.voxelmon.core.rendering;
import me.cortex.voxelmon.core.gl.GlBuffer;
import me.cortex.voxelmon.core.rendering.util.BufferArena;
import me.cortex.voxelmon.core.rendering.util.UploadStream;
import me.cortex.voxelmon.core.util.IndexUtil;
import me.cortex.voxelmon.core.util.MemoryBuffer;
import org.lwjgl.system.MemoryUtil;
//Has a base index buffer of 16380 quads, and also a 1 cube byte index buffer at the end
public class SharedIndexBuffer {
public static final SharedIndexBuffer INSTANCE = new SharedIndexBuffer();
private int commonIndexBufferOffset = -1;
private int commonIndexQuadCount;
private final BufferArena indexBuffer;
private final GlBuffer indexBuffer;
public SharedIndexBuffer() {
this.indexBuffer = new BufferArena((1L << 16)*(2*6), 2 * 6);
this.getSharedIndexBuffer(16380);
this.indexBuffer = new GlBuffer((1<<16)*6*2 + 6*2*3, 0);
var quadIndexBuff = IndexUtil.generateQuadIndicesShort(16380);
var cubeBuff = generateCubeIndexBuffer();
long ptr = UploadStream.INSTANCE.upload(this.indexBuffer, 0, this.indexBuffer.size());
quadIndexBuff.cpyTo(ptr);
cubeBuff.cpyTo((1<<16)*2*6 + ptr);
quadIndexBuff.free();
cubeBuff.free();
}
public int getSharedIndexBuffer(int newQuadCount) {
if (this.commonIndexBufferOffset == -1 || this.commonIndexQuadCount < newQuadCount) {
if (this.commonIndexBufferOffset != -1) {
this.indexBuffer.free(this.commonIndexBufferOffset);
}
var buffer = IndexUtil.generateQuadIndices(newQuadCount);
long offset = this.indexBuffer.upload(buffer);
if (offset >= 1L<<31) {
throw new IllegalStateException();
}
this.commonIndexBufferOffset = (int) offset;
buffer.free();
this.commonIndexQuadCount = newQuadCount;
}
return this.commonIndexBufferOffset * 6;
private static MemoryBuffer generateCubeIndexBuffer() {
var buffer = new MemoryBuffer(6*2*3);
long ptr = buffer.address;
MemoryUtil.memSet(ptr, 0, 6*2*3 );
//Bottom face
MemoryUtil.memPutByte(ptr++, (byte) 0);
MemoryUtil.memPutByte(ptr++, (byte) 1);
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 1);
//top face
MemoryUtil.memPutByte(ptr++, (byte) 6);
MemoryUtil.memPutByte(ptr++, (byte) 5);
MemoryUtil.memPutByte(ptr++, (byte) 4);
MemoryUtil.memPutByte(ptr++, (byte) 5);
MemoryUtil.memPutByte(ptr++, (byte) 6);
MemoryUtil.memPutByte(ptr++, (byte) 7);
//north face
MemoryUtil.memPutByte(ptr++, (byte) 0);
MemoryUtil.memPutByte(ptr++, (byte) 4);
MemoryUtil.memPutByte(ptr++, (byte) 1);
MemoryUtil.memPutByte(ptr++, (byte) 5);
MemoryUtil.memPutByte(ptr++, (byte) 1);
MemoryUtil.memPutByte(ptr++, (byte) 4);
//south face
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 6);
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 6);
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 7);
//west face
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 4);
MemoryUtil.memPutByte(ptr++, (byte) 0);
MemoryUtil.memPutByte(ptr++, (byte) 4);
MemoryUtil.memPutByte(ptr++, (byte) 2);
MemoryUtil.memPutByte(ptr++, (byte) 6);
//east face
MemoryUtil.memPutByte(ptr++, (byte) 1);
MemoryUtil.memPutByte(ptr++, (byte) 5);
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 7);
MemoryUtil.memPutByte(ptr++, (byte) 3);
MemoryUtil.memPutByte(ptr++, (byte) 5);
return buffer;
}
public int id() {
return this.indexBuffer.id();
return this.indexBuffer.id;
}
}

View File

@@ -115,19 +115,20 @@ public class RenderGenerationService {
return;
}
//Wait for the ingest to finish
while (this.taskCounter.availablePermits() != 0) {
Thread.onSpinWait();
}
//Shutdown
//Since this is just render data, dont care about any tasks needing to finish
this.running = false;
this.taskCounter.release(1000);
//Wait for thread to join
try {
for (var worker : this.workers) {
worker.join();
}
} catch (InterruptedException e) {throw new RuntimeException(e);}
//Cleanup any remaining data
while (!this.taskQueue.isEmpty()) {
this.taskQueue.pop();
}
}
}

View File

@@ -3,7 +3,7 @@ package me.cortex.voxelmon.core.util;
import org.lwjgl.system.MemoryUtil;
public class IndexUtil {
public static MemoryBuffer generateQuadIndices(int quadCount) {
public static MemoryBuffer generateQuadIndicesShort(int quadCount) {
if ((quadCount*4) >= 1<<16) {
throw new IllegalArgumentException("Quad count to large");
}
@@ -22,4 +22,19 @@ public class IndexUtil {
return buffer;
}
public static MemoryBuffer generateQuadIndicesInt(int quadCount) {
MemoryBuffer buffer = new MemoryBuffer(quadCount * 6L * 2);
long ptr = buffer.address;
for(int i = 0; i < quadCount*4; i += 4) {
MemoryUtil.memPutInt(ptr + (0*4), i);
MemoryUtil.memPutInt(ptr + (1*4), (i + 1));
MemoryUtil.memPutInt(ptr + (2*4), (i + 2));
MemoryUtil.memPutInt(ptr + (3*4), (i + 1));
MemoryUtil.memPutInt(ptr + (4*4), (i + 3));
MemoryUtil.memPutInt(ptr + (5*4), (i + 2));
ptr += 6 * 4;
}
return buffer;
}
}

View File

@@ -17,12 +17,11 @@ import java.util.concurrent.atomic.AtomicReference;
//Use an LMDB backend to store the world, use a local inmemory cache for lod sections
// automatically manages and invalidates sections of the world as needed
public class WorldEngine {
private static final int ACTIVE_CACHE_SIZE = 10;
public final StorageBackend storage;
private final Mapper mapper;
private final ActiveSectionTracker sectionTracker;
public final VoxelIngestService ingestService = new VoxelIngestService(this);
public final VoxelIngestService ingestService;
public final SectionSavingService savingService;
private RenderTracker renderTracker;
@@ -42,6 +41,7 @@ public class WorldEngine {
this.sectionTracker = new ActiveSectionTracker(maxMipLayers, this::unsafeLoadSection);
this.savingService = new SectionSavingService(this, savingServiceWorkers);
this.ingestService = new VoxelIngestService(this);
}
private boolean unsafeLoadSection(WorldSection into) {
@@ -69,7 +69,7 @@ public class WorldEngine {
}
//Marks a section as dirty, enqueuing it for saving and or render data rebuilding
private void markDirty(WorldSection section) {
public void markDirty(WorldSection section) {
this.renderTracker.sectionUpdated(section);
//TODO: add an option for having synced saving, that is when call enqueueSave, that will instead, instantly
// save to the db, this can be useful for just reducing the amount of thread pools in total

View File

@@ -50,17 +50,21 @@ layout(binding = 3, std430) readonly restrict buffer SectionBuffer {
SectionMeta sectionData[];
};
layout(binding = 4, std430) readonly restrict buffer StateBuffer {
State stateData[];
};
layout(binding = 5, std430) readonly restrict buffer BiomeBuffer {
Biome biomeData[];
};
#ifndef VISIBILITY_ACCESS
#define VISIBILITY_ACCESS readonly
#endif
layout(binding = 6, std430) VISIBILITY_ACCESS restrict buffer VisibilityBuffer {
layout(binding = 4, std430) VISIBILITY_ACCESS restrict buffer VisibilityBuffer {
uint visibilityData[];
};
layout(binding = 5, std430) readonly restrict buffer StateBuffer {
State stateData[];
};
layout(binding = 6, std430) readonly restrict buffer BiomeBuffer {
Biome biomeData[];
};
layout(binding = 7, std430) readonly restrict buffer LightingBuffer {
vec4 lightData[];
};

View File

@@ -34,12 +34,20 @@ void main() {
uint detail = extractDetail(meta);
ivec3 ipos = extractPosition(meta);
//TODO: fixme; i dont think this is correct
vec3 cornerPos = vec3(((ipos<<detail)-baseSectionPos)<<5)-cameraSubPos;
bool shouldRender = testFrustum(frustum, cornerPos, cornerPos+vec3(1<<(detail+5)));
//Check the occlusion data from last frame
if (visibilityData[gl_GlobalInvocationID.x] != frameId - 1) {
shouldRender = false;
}
//Clear the occlusion data (not strictly? needed? i think???)
//visibilityData[gl_GlobalInvocationID.x] = 0;
//TODO: need to make it check that only if it was also in the frustum last frame does it apply the visibilityData check!
//This prevents overflow of the relative position encoder
if (shouldRender) {
shouldRender = !any(lessThan(ivec3(254), abs(ipos-(baseSectionPos>>detail))));

View File

@@ -4,7 +4,9 @@
#import <voxelmon:lod/gl46/bindings.glsl>
flat in uint id;
flat in uint value;
//out vec4 colour;
void main() {
visibilityData[id] = value;
//colour = vec4(float(id&7)/7, float((id>>3)&7)/7, float((id>>6)&7)/7, 1);
}

View File

@@ -8,18 +8,20 @@ flat out uint id;
flat out uint value;
void main() {
SectionMeta section = sectionData[gl_VertexID>>3];
uint sid = gl_InstanceID;
SectionMeta section = sectionData[sid];
uint detail = extractDetail(section);
ivec3 ipos = extractPosition(section);
//Transform ipos with respect to the vertex corner
ipos += ivec3(gl_VertexID&1, (gl_VertexID>>1)&1, (gl_VertexID>>2)&1);
ipos += ivec3(gl_VertexID&1, (gl_VertexID>>2)&1, (gl_VertexID>>1)&1);
vec3 cornerPos = vec3(((ipos<<detail)-baseSectionPos)<<5);
gl_Position = MVP * vec4(cornerPos,1);
//Write to this id
id = gl_VertexID>>3;
id = sid;
value = frameId;
}