Rasterized occlusion culling
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[];
|
||||
};
|
||||
|
||||
@@ -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))));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user