Stuff kinda works

This commit is contained in:
mcrcortex
2025-03-21 22:47:16 +10:00
parent ac1427d125
commit eb7172aaba
20 changed files with 1092 additions and 125 deletions

View File

@@ -13,7 +13,7 @@ public class VoxyClientInstance extends VoxyInstance {
public WorldImportWrapper importWrapper; public WorldImportWrapper importWrapper;
public VoxyClientInstance() { public VoxyClientInstance() {
super(12); super(14);
} }
@Override @Override

View File

@@ -191,13 +191,14 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
if (true /* firstInvocationThisFrame */) { if (true /* firstInvocationThisFrame */) {
DownloadStream.INSTANCE.tick(); DownloadStream.INSTANCE.tick();
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
this.sectionUpdateQueue.consume(); this.sectionUpdateQueue.consume();
this.geometryUpdateQueue.consume(); this.geometryUpdateQueue.consume();
if (this.nodeManager.writeChanges(this.traversal.getNodeBuffer())) {//TODO: maybe move the node buffer out of the traversal class if (this.nodeManager.writeChanges(this.traversal.getNodeBuffer())) {//TODO: maybe move the node buffer out of the traversal class
UploadStream.INSTANCE.commit(); UploadStream.INSTANCE.commit();
} }
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
//this needs to go after, due to geometry updates committed by the nodeManager //this needs to go after, due to geometry updates committed by the nodeManager
this.sectionRenderer.getGeometryManager().tick(); this.sectionRenderer.getGeometryManager().tick();
} }

View File

@@ -76,6 +76,13 @@ public class SectionUpdateRouter implements ISectionWatcher {
} }
} }
public int get(long position) {
var set = this.slices[getSliceIndex(position)];
synchronized (set) {
return set.getOrDefault(position, (byte) 0);
}
}
public void forwardEvent(WorldSection section, int type) { public void forwardEvent(WorldSection section, int type) {
final long position = section.key; final long position = section.key;
var set = this.slices[getSliceIndex(position)]; var set = this.slices[getSliceIndex(position)];

View File

@@ -0,0 +1,756 @@
package me.cortex.voxy.client.core.rendering.building;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.model.ModelFactory;
import me.cortex.voxy.client.core.model.ModelQueries;
import me.cortex.voxy.client.core.util.Mesher2D;
import me.cortex.voxy.client.core.util.ScanMesher2D;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.UnsafeUtil;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import me.cortex.voxy.common.world.other.Mapper;
import me.cortex.voxy.commonImpl.VoxyCommon;
import org.lwjgl.system.MemoryUtil;
import java.util.Arrays;
import java.util.Map;
public class RenderDataFactory45 {
private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing");
private final WorldEngine world;
private final ModelFactory modelMan;
//private final long[] sectionData = new long[32*32*32*2];
private final long[] sectionData = new long[32*32*32*2];
private final long[] neighboringFaces = new long[32*32*6];
//private final int[] neighboringOpaqueMasks = new int[32*6];
private final int[] opaqueMasks = new int[32*32];
private final int[] nonOpaqueMasks = new int[32*32];
//TODO: emit directly to memory buffer instead of long arrays
//Each axis gets a max quad count of 2^16 (65536 quads) since that is the max the basic geometry manager can handle
private final MemoryBuffer directionalQuadBuffer = new MemoryBuffer(6*(8*(1<<16)));
private final long directionalQuadBufferPtr = this.directionalQuadBuffer.address;
private final int[] directionalQuadCounters = new int[6];//Maybe change to short? (or long /w raw pointers)
private int minX;
private int minY;
private int minZ;
private int maxX;
private int maxY;
private int maxZ;
private int quadCount = 0;
//Wont work for double sided quads
private final class Mesher extends ScanMesher2D {
public int auxiliaryPosition = 0;
public boolean doAuxiliaryFaceOffset = true;
public int axis = 0;
//Note x, z are in top right
@Override
protected void emitQuad(int x, int z, int length, int width, long data) {
RenderDataFactory45.this.quadCount++;
if (VERIFY_MESHING) {
if (length<1||length>16) {
throw new IllegalStateException("length out of bounds: " + length);
}
if (width<1||width>16) {
throw new IllegalStateException("width out of bounds: " + width);
}
if (x<0||x>31) {
throw new IllegalStateException("x out of bounds: " + x);
}
if (z<0||z>31) {
throw new IllegalStateException("z out of bounds: " + z);
}
if (x-(length-1)<0 || z-(width-1)<0) {
throw new IllegalStateException("dim out of bounds: " + (x-(length-1))+", " + (z-(width-1)));
}
}
x -= length-1;
z -= width-1;
if (this.axis == 2) {
//Need to swizzle the data if on x axis
int tmp = x;
x = z;
z = tmp;
tmp = length;
length = width;
width = tmp;
}
//Lower 26 bits can be auxiliary data since that is where quad position information goes;
int auxData = (int) (data&((1<<26)-1));
int faceSide = auxData&1;
data &= ~(data&((1<<26)-1));
final int axis = this.axis;
int face = (axis<<1)|faceSide;
int encodedPosition = face;
//Shift up if is negative axis
int auxPos = this.auxiliaryPosition;
auxPos += this.doAuxiliaryFaceOffset?(1-faceSide):0;//Shift
if (VERIFY_MESHING) {
if (auxPos > 31) {
throw new IllegalStateException("OOB face: " + auxPos + ", " + faceSide);
}
}
encodedPosition |= ((width - 1) << 7) | ((length - 1) << 3);
encodedPosition |= x << (axis==2?16:21);
encodedPosition |= z << (axis==1?16:11);
encodedPosition |= auxPos << (axis==0?16:(axis==1?11:21));
long quad = data | encodedPosition;
MemoryUtil.memPutLong(RenderDataFactory45.this.directionalQuadBufferPtr + (RenderDataFactory45.this.directionalQuadCounters[face]++)*8L + face*8L*(1<<16), quad);
}
}
private final Mesher blockMesher = new Mesher();
public RenderDataFactory45(WorldEngine world, ModelFactory modelManager, boolean emitMeshlets) {
this.world = world;
this.modelMan = modelManager;
}
private int prepareSectionData() {
final var sectionData = this.sectionData;
int opaque = 0;
int notEmpty = 0;
int neighborAcquireMsk = 0;//-+x, -+y, -+Z
for (int i = 0; i < 32*32*32;) {
long block = sectionData[i + 32 * 32 * 32];//Get the block mapping
int modelId = this.modelMan.getModelId(Mapper.getBlockId(block));
long modelMetadata = this.modelMan.getModelMetadataFromClientId(modelId);
sectionData[i * 2] = modelId | ((long) (Mapper.getLightId(block)) << 16) | (ModelQueries.isBiomeColoured(modelMetadata)?(((long) (Mapper.getBiomeId(block))) << 24):0);
sectionData[i * 2 + 1] = modelMetadata;
boolean isFullyOpaque = ModelQueries.isFullyOpaque(modelMetadata);
opaque |= (isFullyOpaque ? 1:0) << (i & 31);
notEmpty |= (modelId!=0 ? 1:0) << (i & 31);
//TODO: here also do bitmask of what neighboring sections are needed to compute (may be getting rid of this in future)
//Do increment here
i++;
if ((i & 31) == 0) {
this.opaqueMasks[(i >> 5) - 1] = opaque;
this.nonOpaqueMasks[(i >> 5) - 1] = notEmpty^opaque;
int neighborMsk = 0;
//-+x
neighborMsk |= notEmpty&1;//-x
neighborMsk |= (notEmpty>>>30)&0b10;//+x
//notEmpty = (notEmpty != 0)?1:0;
neighborMsk |= notEmpty!=0&&((i-1)>>10)==0?0b100:0;//-y
neighborMsk |= notEmpty!=0&&((i-1)>>10)==31?0b1000:0;//+y
neighborMsk |= notEmpty!=0&&(((i-1)>>5)&0x1F)==0?0b10000:0;//-z
neighborMsk |= notEmpty!=0&&(((i-1)>>5)&0x1F)==31?0b100000:0;//+z
neighborAcquireMsk |= neighborMsk;
opaque = 0;
notEmpty = 0;
}
}
return neighborAcquireMsk;
}
private void acquireNeighborData(WorldSection section, int msk) {
//TODO: fixme!!! its probably more efficent to just access the raw section array on demand instead of copying it
if ((msk&1)!=0) {//-x
var sec = this.world.acquire(section.lvl, section.x - 1, section.y, section.z);
//Note this is not thread safe! (but eh, fk it)
var raw = sec._unsafeGetRawDataArray();
for (int i = 0; i < 32*32; i++) {
this.neighboringFaces[i] = raw[(i<<5)+31];//pull the +x faces from the section
}
sec.release();
}
if ((msk&2)!=0) {//+x
var sec = this.world.acquire(section.lvl, section.x + 1, section.y, section.z);
//Note this is not thread safe! (but eh, fk it)
var raw = sec._unsafeGetRawDataArray();
for (int i = 0; i < 32*32; i++) {
this.neighboringFaces[i+32*32] = raw[(i<<5)];//pull the -x faces from the section
}
sec.release();
}
if ((msk&4)!=0) {//-y
var sec = this.world.acquire(section.lvl, section.x, section.y - 1, section.z);
//Note this is not thread safe! (but eh, fk it)
var raw = sec._unsafeGetRawDataArray();
for (int i = 0; i < 32*32; i++) {
this.neighboringFaces[i+32*32*2] = raw[i|(0x1F<<10)];//pull the +y faces from the section
}
sec.release();
}
if ((msk&8)!=0) {//+y
var sec = this.world.acquire(section.lvl, section.x, section.y + 1, section.z);
//Note this is not thread safe! (but eh, fk it)
var raw = sec._unsafeGetRawDataArray();
for (int i = 0; i < 32*32; i++) {
this.neighboringFaces[i+32*32*3] = raw[i];//pull the -y faces from the section
}
sec.release();
}
if ((msk&16)!=0) {//-z
var sec = this.world.acquire(section.lvl, section.x, section.y, section.z - 1);
//Note this is not thread safe! (but eh, fk it)
var raw = sec._unsafeGetRawDataArray();
for (int i = 0; i < 32*32; i++) {
this.neighboringFaces[i+32*32*4] = raw[Integer.expand(i,0b11111_00000_11111)|(0x1F<<5)];//pull the +z faces from the section
}
sec.release();
}
if ((msk&32)!=0) {//+z
var sec = this.world.acquire(section.lvl, section.x, section.y, section.z + 1);
//Note this is not thread safe! (but eh, fk it)
var raw = sec._unsafeGetRawDataArray();
for (int i = 0; i < 32*32; i++) {
this.neighboringFaces[i+32*32*5] = raw[Integer.expand(i,0b11111_00000_11111)];//pull the -z faces from the section
}
sec.release();
}
}
private void generateYZFaces() {
for (int axis = 0; axis < 2; axis++) {//Y then Z
this.blockMesher.axis = axis;
if (true) {
for (int layer = 0; layer < 31; layer++) {
this.blockMesher.auxiliaryPosition = layer;
for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections
int pidx = axis==0 ?(layer*32+other):(other*32+layer);
int skipAmount = axis==0?32:1;
int current = this.opaqueMasks[pidx];
int next = this.opaqueMasks[pidx + skipAmount];
int msk = current ^ next;
if (msk == 0) {
this.blockMesher.skip(32);
continue;
}
//TODO: For boarder sections, should NOT EMIT neighbors faces
int faceForwardMsk = msk & current;
int cIdx = -1;
while (msk != 0) {
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
int delta = index - cIdx - 1;
cIdx = index; //index--;
if (delta != 0) this.blockMesher.skip(delta);
msk &= ~Integer.lowestOneBit(msk);
int facingForward = ((faceForwardMsk >> index) & 1);
{
int idx = index + (pidx*32);
//TODO: swap this out for something not getting the next entry
long A = this.sectionData[idx * 2];
long B = this.sectionData[(idx + skipAmount * 32) * 2];
//Flip data with respect to facing direction
long selfModel = facingForward == 1 ? A : B;
long nextModel = facingForward == 1 ? B : A;
//Example thing thats just wrong but as example
this.blockMesher.putNext(((long) facingForward) |//Facing
((selfModel & 0xFFFF) << 26) | //ModelId
(((nextModel>>16)&0xFF) << 55) |//Lighting
((selfModel&(0x1FFL<<24))<<(46-24))//biomeId
);
}
}
this.blockMesher.endRow();
}
this.blockMesher.finish();
}
}
if (true) {
this.blockMesher.doAuxiliaryFaceOffset = false;
//Hacky generate section side faces (without check neighbor section)
for (int side = 0; side < 2; side++) {//-, +
int layer = side == 0 ? 0 : 31;
this.blockMesher.auxiliaryPosition = layer;
for (int other = 0; other < 32; other++) {
int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer);
int msk = this.opaqueMasks[pidx];
if (msk == 0) {
this.blockMesher.skip(32);
continue;
}
int cIdx = -1;
while (msk != 0) {
int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index
int delta = index - cIdx - 1;
cIdx = index; //index--;
if (delta != 0) this.blockMesher.skip(delta);
msk &= ~Integer.lowestOneBit(msk);
{
int idx = index + (pidx * 32);
int neighborIdx = ((axis+1)*32*32 * 2)+(side)*32*32;
long neighborId = this.neighboringFaces[neighborIdx + (other*32) + index];
if (Mapper.getBlockId(neighborId) != 0) {//Not air
long meta = this.modelMan.getModelMetadataFromClientId(this.modelMan.getModelId(Mapper.getBlockId(neighborId)));
if (ModelQueries.isFullyOpaque(meta)) {//Dont mesh this face
this.blockMesher.putNext(0);
continue;
}
}
//TODO: swap this out for something not getting the next entry
long A = this.sectionData[idx * 2];
//Example thing thats just wrong but as example
this.blockMesher.putNext((side == 0 ? 0L : 1L) |
((A & 0xFFFFL) << 26) |
(((long)Mapper.getLightId(neighborId)) << 55) |
((A&(0x1FFL<<24))<<(46-24))
);
}
}
this.blockMesher.endRow();
}
this.blockMesher.finish();
}
this.blockMesher.doAuxiliaryFaceOffset = true;
}
}
}
private final Mesher[] xAxisMeshers = new Mesher[32];
{
for (int i = 0; i < 32; i++) {
var mesher = new Mesher();
mesher.auxiliaryPosition = i;
mesher.axis = 2;//X axis
this.xAxisMeshers[i] = mesher;
}
}
private static final long X_I_MSK = 0x4210842108421L;
private void generateXFaces() {
for (int y = 0; y < 32; y++) {
long sumA = 0;
long sumB = 0;
long sumC = 0;
int partialHasCount = -1;
int msk = 0;
for (int z = 0; z < 32; z++) {
int lMsk = this.opaqueMasks[y*32+z];
msk = (lMsk^(lMsk>>>1));
msk &= -1>>>1;//Remove top bit as we dont actually know/have the data for that slice
//Always increment cause can do funny trick (i.e. -1 on skip amount)
sumA += X_I_MSK;
sumB += X_I_MSK;
sumC += X_I_MSK;
partialHasCount &= ~msk;
if (z == 30 && partialHasCount != 0) {//Hackfix for incremental count overflow issue
int cmsk = partialHasCount;
while (cmsk!=0) {
int index = Integer.numberOfTrailingZeros(cmsk);
cmsk &= ~Integer.lowestOneBit(cmsk);
//TODO: fixme! check this is correct or if should be 30
this.xAxisMeshers[index].skip(31);
}
//Clear the sum
sumA &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount), X_I_MSK)*0x1F);
sumB &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>11, X_I_MSK)*0x1F);
sumC &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>22, X_I_MSK)*0x1F);
}
if (msk == 0) {
continue;
}
/*
{//Dont need this as can just increment everything then -1 in mask
//Compute and increment skips for indexes
long imsk = Integer.toUnsignedLong(~msk);// we only want to increment where there isnt a face
sumA += Long.expand(imsk, X_I_MSK);
sumB += Long.expand(imsk>>11, X_I_MSK);
sumC += Long.expand(imsk>>22, X_I_MSK);
}*/
int faceForwardMsk = msk&lMsk;
int iter = msk;
while (iter!=0) {
int index = Integer.numberOfTrailingZeros(iter);
iter &= ~Integer.lowestOneBit(iter);
var mesher = this.xAxisMeshers[index];
int skipCount;//Compute the skip count
{//TODO: Branch-less
//Compute skip and clear
if (index<11) {
skipCount = (int) (sumA>>(index*5));
sumA &= ~(0x1FL<<(index*5));
} else if (index<22) {
skipCount = (int) (sumB>>((index-11)*5));
sumB &= ~(0x1FL<<((index-11)*5));
} else {
skipCount = (int) (sumC>>((index-22)*5));
sumC &= ~(0x1FL<<((index-22)*5));
}
skipCount &= 0x1F;
skipCount--;
}
if (skipCount != 0) {
mesher.skip(skipCount);
}
int facingForward = ((faceForwardMsk>>index)&1);
{
int idx = index + (z * 32) + (y * 32 * 32);
//TODO: swap this out for something not getting the next entry
long A = this.sectionData[idx * 2];
long B = this.sectionData[(idx + 1) * 2];
//Flip data with respect to facing direction
long selfModel = facingForward==1?A:B;
long nextModel = facingForward==1?B:A;
//Example thing thats just wrong but as example
mesher.putNext(((long) facingForward) |//Facing
((selfModel & 0xFFFF) << 26) | //ModelId
(((nextModel>>16)&0xFF) << 55) |//Lighting
((selfModel&(0x1FFL<<24))<<(46-24))//biomeId
);
}
}
}
//Need to skip the remaining entries in the skip array
{
msk = ~msk;//Invert the mask as we only need to set stuff that isnt 0
while (msk!=0) {
int index = Integer.numberOfTrailingZeros(msk);
msk &= ~Integer.lowestOneBit(msk);
int skipCount;
if (index < 11) {
skipCount = (int) (sumA>>(index*5));
} else if (index<22) {
skipCount = (int) (sumB>>((index-11)*5));
} else {
skipCount = (int) (sumC>>((index-22)*5));
}
skipCount &= 0x1F;
if (skipCount != 0) {
this.xAxisMeshers[index].skip(skipCount);
}
}
}
}
//Generate the side faces, hackily, using 0 and 1 mesher
if (true) {
var ma = this.xAxisMeshers[0];
var mb = this.xAxisMeshers[31];
ma.finish();
mb.finish();
ma.doAuxiliaryFaceOffset = false;
mb.doAuxiliaryFaceOffset = false;
for (int y = 0; y < 32; y++) {
int skipA = 0;
int skipB = 0;
for (int z = 0; z < 32; z++) {
int i = y*32+z;
int msk = this.opaqueMasks[i];
if ((msk & 1) != 0) {//-x
long neighborId = this.neighboringFaces[i];
boolean oki = true;
if (Mapper.getBlockId(neighborId) != 0) {//Not air
long meta = this.modelMan.getModelMetadataFromClientId(this.modelMan.getModelId(Mapper.getBlockId(neighborId)));
if (ModelQueries.isFullyOpaque(meta)) {
oki = false;
}
}
if (oki) {
ma.skip(skipA); skipA = 0;
long A = this.sectionData[(i<<5) * 2];
ma.putNext(0L | ((A&0xFFFF)<<26) | (((long)Mapper.getLightId(neighborId))<<55)|((A&(0x1FFL<<24))<<(46-24)));
} else {skipA++;}
} else {skipA++;}
if ((msk & (1<<31)) != 0) {//+x
long neighborId = this.neighboringFaces[i+32*32];
boolean oki = true;
if (Mapper.getBlockId(neighborId) != 0) {//Not air
long meta = this.modelMan.getModelMetadataFromClientId(this.modelMan.getModelId(Mapper.getBlockId(neighborId)));
if (ModelQueries.isFullyOpaque(meta)) {
oki = false;
}
}
if (oki) {
mb.skip(skipB); skipB = 0;
long A = this.sectionData[(i*32+31) * 2];
mb.putNext(1L | ((A&0xFFFF)<<26) | (((long)Mapper.getLightId(neighborId))<<55)|((A&(0x1FFL<<24))<<(46-24)));
} else {skipB++;}
} else {skipB++;}
}
ma.skip(skipA);
mb.skip(skipB);
}
ma.finish();
mb.finish();
ma.doAuxiliaryFaceOffset = true;
mb.doAuxiliaryFaceOffset = true;
}
for (var mesher : this.xAxisMeshers) {
mesher.finish();
}
}
/*
private static long createQuad() {
((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(metadata)?1:0)) | otherFlags
long data = Integer.toUnsignedLong(array[i*3+1]);
data |= ((long) array[i*3+2])<<32;
long encodedQuad = Integer.toUnsignedLong(QuadEncoder.encodePosition(face, otherAxis, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46);
}*/
//section is already acquired and gets released by the parent
public BuiltSection generateMesh(WorldSection section) {
//Copy section data to end of array so that can mutate array while reading safely
section.copyDataTo(this.sectionData, 32*32*32);
this.quadCount = 0;
this.minX = Integer.MAX_VALUE;
this.minY = Integer.MAX_VALUE;
this.minZ = Integer.MAX_VALUE;
this.maxX = Integer.MIN_VALUE;
this.maxY = Integer.MIN_VALUE;
this.maxZ = Integer.MIN_VALUE;
Arrays.fill(this.directionalQuadCounters, (short) 0);
//Prepare everything
int neighborMsk = this.prepareSectionData();
this.acquireNeighborData(section, neighborMsk);
this.generateYZFaces();
this.generateXFaces();
//TODO:NOTE! when doing face culling of translucent blocks,
// if the connecting type of the translucent block is the same AND the face is full, discard it
// this stops e.g. multiple layers of glass (and ocean) from having 3000 layers of quads etc
if (this.quadCount == 0) {
return BuiltSection.emptyWithChildren(section.key, section.getNonEmptyChildren());
}
//TODO: FIXME AND OPTIMIZE, get rid of the stupid quad collector bullshit
int[] offsets = new int[8];
var buff = new MemoryBuffer(this.quadCount * 8L);
long ptr = buff.address;
int coff = 0;
for (int face = 0; face < 6; face++) {
offsets[face + 2] = coff;
int size = this.directionalQuadCounters[face];
UnsafeUtil.memcpy(this.directionalQuadBufferPtr + (face*(8*(1<<16))), ptr + coff*8L, (size* 8L));
coff += size;
}
int aabb = 0;
aabb |= 0;
aabb |= 0<<5;
aabb |= 0<<10;
aabb |= (31)<<15;
aabb |= (31)<<20;
aabb |= (31)<<25;
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets);
/*
buff = new MemoryBuffer(bufferSize * 8L);
long ptr = buff.address;
int coff = 0;
//Ordering is: translucent, double sided quads, directional quads
offsets[0] = coff;
int size = this.translucentQuadCollector.size();
LongArrayList arrayList = this.translucentQuadCollector;
for (int i = 0; i < size; i++) {
long data = arrayList.getLong(i);
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
}
offsets[1] = coff;
size = this.doubleSidedQuadCollector.size();
arrayList = this.doubleSidedQuadCollector;
for (int i = 0; i < size; i++) {
long data = arrayList.getLong(i);
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
}
for (int face = 0; face < 6; face++) {
offsets[face + 2] = coff;
final LongArrayList faceArray = this.directionalQuadCollectors[face];
size = faceArray.size();
for (int i = 0; i < size; i++) {
long data = faceArray.getLong(i);
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
}
}
int aabb = 0;
aabb |= this.minX;
aabb |= this.minY<<5;
aabb |= this.minZ<<10;
aabb |= (this.maxX-this.minX)<<15;
aabb |= (this.maxY-this.minY)<<20;
aabb |= (this.maxZ-this.minZ)<<25;
return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets);
*/
}
public void free() {
this.directionalQuadBuffer.free();
}
//Returns true if a face was placed
private boolean putFluidFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfClientModelId, int selfBlockId, long facingState, long facingMetadata, int facingClientModelId, int a, int b) {
int selfFluidClientId = this.modelMan.getFluidClientStateId(selfClientModelId);
long selfFluidMetadata = this.modelMan.getModelMetadataFromClientId(selfFluidClientId);
int facingFluidClientId = -1;
if (ModelQueries.containsFluid(facingMetadata)) {
facingFluidClientId = this.modelMan.getFluidClientStateId(facingClientModelId);
}
//If both of the states are the same, then dont render the fluid face
if (selfFluidClientId == facingFluidClientId) {
return false;
}
if (facingFluidClientId != -1) {
//TODO: OPTIMIZE
if (this.world.getMapper().getBlockStateFromBlockId(selfBlockId).getBlock() == this.world.getMapper().getBlockStateFromBlockId(Mapper.getBlockId(facingState)).getBlock()) {
return false;
}
}
if (ModelQueries.faceOccludes(facingMetadata, opposingFace)) {
return false;
}
//if the model has a fluid state but is not a liquid need to see if the solid state had a face rendered and that face is occluding, if so, dont render the fluid state face
if ((!ModelQueries.isFluid(metadata)) && ModelQueries.faceOccludes(metadata, face)) {
return false;
}
//TODO:FIXME SOMEHOW THIS IS CRITICAL!!!!!!!!!!!!!!!!!!
// so there is one more issue need to be fixed, if water is layered ontop of eachother, the side faces depend on the water state ontop
// this has been hackfixed in the model texture bakery but a proper solution that doesnt explode the sides of the water textures needs to be done
// the issue is that the fluid rendering depends on the up state aswell not just the face state which is really really painful to account for
// e.g the sides of a full water is 8 high or something, not the full block height, this results in a gap between water layers
long otherFlags = 0;
otherFlags |= ModelQueries.isTranslucent(selfFluidMetadata)?1L<<33:0;
otherFlags |= ModelQueries.isDoubleSided(selfFluidMetadata)?1L<<34:0;
mesher.put(a, b, ((long)selfFluidClientId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(selfFluidMetadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(selfFluidMetadata)?1:0)) | otherFlags);
return true;
}
//Returns true if a face was placed
private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long metadata, int clientModelId, int selfBiome, long facingModelId, long facingMetadata, int selfLight, int facingLight, int a, int b) {
if (ModelQueries.cullsSame(metadata) && clientModelId == facingModelId) {
//If we are facing a block, and we are both the same state, dont render that face
return false;
}
//If face can be occluded and is occluded from the facing block, then dont render the face
if (ModelQueries.faceCanBeOccluded(metadata, face) && ModelQueries.faceOccludes(facingMetadata, opposingFace)) {
return false;
}
long otherFlags = 0;
otherFlags |= ModelQueries.isTranslucent(metadata)?1L<<33:0;
otherFlags |= ModelQueries.isDoubleSided(metadata)?1L<<34:0;
mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?selfLight:facingLight))<<16) | (ModelQueries.isBiomeColoured(metadata)?(((long) Mapper.getBiomeId(selfBiome))<<24):0) | otherFlags);
return true;
}
private static int getMeshletHoldingCount(int quads, int quadsPerMeshlet, int meshletSize) {
return ((quads+(quadsPerMeshlet-1))/quadsPerMeshlet)*meshletSize;
}
public static int alignUp(int n, int alignment) {
return (n + alignment - 1) & -alignment;
}
}

View File

@@ -46,7 +46,7 @@ public class RenderGenerationService {
this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{ this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{
//Thread local instance of the factory //Thread local instance of the factory
var factory = new RenderDataFactory4(this.world, this.modelBakery.factory, this.emitMeshlets); var factory = new RenderDataFactory45(this.world, this.modelBakery.factory, this.emitMeshlets);
return new Pair<>(() -> { return new Pair<>(() -> {
this.processJob(factory); this.processJob(factory);
}, factory::free); }, factory::free);
@@ -75,7 +75,7 @@ public class RenderGenerationService {
} }
//TODO: add a generated render data cache //TODO: add a generated render data cache
private void processJob(RenderDataFactory4 factory) { private void processJob(RenderDataFactory45 factory) {
BuildTask task; BuildTask task;
synchronized (this.taskQueue) { synchronized (this.taskQueue) {
task = this.taskQueue.removeFirst(); task = this.taskQueue.removeFirst();
@@ -133,7 +133,9 @@ public class RenderGenerationService {
queuedTask.hasDoneModelRequest = true;//Mark (or remark) the section as having chunks requested queuedTask.hasDoneModelRequest = true;//Mark (or remark) the section as having chunks requested
if (queuedTask == task) {//use the == not .equal to see if we need to release a permit if (queuedTask == task) {//use the == not .equal to see if we need to release a permit
this.threads.execute();//Since we put in queue, release permit if (this.threads.isAlive()) {//Only execute if were not dead
this.threads.execute();//Since we put in queue, release permit
}
//If we did put it in the queue, dont release the section //If we did put it in the queue, dont release the section
shouldFreeSection = false; shouldFreeSection = false;
@@ -186,11 +188,25 @@ public class RenderGenerationService {
*/ */
public void shutdown() { public void shutdown() {
//Steal and free as much work as possible
while (this.threads.steal()) {
synchronized (this.taskQueue) {
var task = this.taskQueue.removeFirst();
if (task.section != null) {
task.section.release();
}
}
}
//Shutdown the threads
this.threads.shutdown(); this.threads.shutdown();
//Cleanup any remaining data //Cleanup any remaining data
while (!this.taskQueue.isEmpty()) { while (!this.taskQueue.isEmpty()) {
this.taskQueue.removeFirst(); var task = this.taskQueue.removeFirst();
if (task.section != null) {
task.section.release();
}
} }
} }

View File

@@ -2,6 +2,8 @@ package me.cortex.voxy.client.core.rendering.hierachical;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue; import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import it.unimi.dsi.fastutil.ints.IntCollection;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader; import me.cortex.voxy.client.core.gl.shader.AutoBindingShader;
@@ -14,6 +16,7 @@ import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.world.WorldEngine; import me.cortex.voxy.common.world.WorldEngine;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.ARBDirectStateAccess.glCopyNamedBufferSubData;
import static org.lwjgl.opengl.GL20.glUniform1i; import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL30C.glBindBufferRange; import static org.lwjgl.opengl.GL30C.glBindBufferRange;
import static org.lwjgl.opengl.GL42C.glMemoryBarrier; import static org.lwjgl.opengl.GL42C.glMemoryBarrier;
@@ -30,11 +33,16 @@ import static org.lwjgl.opengl.GL43C.*;
public class NodeCleaner { public class NodeCleaner {
//TODO: use batch_visibility_set to clear visibility data when nodes are removed!! (TODO: nodeManager will need to forward info to this) //TODO: use batch_visibility_set to clear visibility data when nodes are removed!! (TODO: nodeManager will need to forward info to this)
private static final int OUTPUT_COUNT = 512;
private static final int SORTING_WORKER_SIZE = 64;
private static final int OUTPUT_COUNT = 256;
private static final int BATCH_SET_SIZE = 2048; private static final int BATCH_SET_SIZE = 2048;
private final AutoBindingShader sorter = Shader.makeAuto(PrintfDebugUtil.PRINTF_processor) private final AutoBindingShader sorter = Shader.makeAuto(PrintfDebugUtil.PRINTF_processor)
.define("WORK_SIZE", SORTING_WORKER_SIZE)
.define("OUTPUT_SIZE", OUTPUT_COUNT) .define("OUTPUT_SIZE", OUTPUT_COUNT)
.define("VISIBILITY_BUFFER_BINDING", 1) .define("VISIBILITY_BUFFER_BINDING", 1)
.define("OUTPUT_BUFFER_BINDING", 2) .define("OUTPUT_BUFFER_BINDING", 2)
@@ -62,7 +70,8 @@ public class NodeCleaner {
private final GlBuffer outputBuffer = new GlBuffer(OUTPUT_COUNT*4+OUTPUT_COUNT*8);//Scratch + output private final GlBuffer outputBuffer = new GlBuffer(OUTPUT_COUNT*4+OUTPUT_COUNT*8);//Scratch + output
private final GlBuffer scratchBuffer = new GlBuffer(BATCH_SET_SIZE*4);//Scratch buffer for setting ids with private final GlBuffer scratchBuffer = new GlBuffer(BATCH_SET_SIZE*4);//Scratch buffer for setting ids with
private final IntArrayFIFOQueue idsToClear = new IntArrayFIFOQueue(); private final IntOpenHashSet allocIds = new IntOpenHashSet();
private final IntOpenHashSet freeIds = new IntOpenHashSet();
private final NodeManager nodeManager; private final NodeManager nodeManager;
int visibilityId = 0; int visibilityId = 0;
@@ -81,18 +90,35 @@ public class NodeCleaner {
.ssbo("VISIBILITY_BUFFER_BINDING", this.visibilityBuffer) .ssbo("VISIBILITY_BUFFER_BINDING", this.visibilityBuffer)
.ssbo("OUTPUT_BUFFER_BINDING", this.outputBuffer); .ssbo("OUTPUT_BUFFER_BINDING", this.outputBuffer);
this.nodeManager.setClearIdCallback(this::clearId); this.nodeManager.setClear(new NodeManager.ICleaner() {
@Override
public void alloc(int id) {
NodeCleaner.this.allocIds.add(id);
NodeCleaner.this.freeIds.remove(id);
}
@Override
public void move(int from, int to) {
NodeCleaner.this.allocIds.remove(to);
glCopyNamedBufferSubData(NodeCleaner.this.visibilityBuffer.id, NodeCleaner.this.visibilityBuffer.id, 4L*from, 4L*to, 4);
}
@Override
public void free(int id) {
NodeCleaner.this.freeIds.add(id);
NodeCleaner.this.allocIds.remove(id);
}
});
} }
public void clearId(int id) {
this.idsToClear.enqueue(id);
}
public void tick(GlBuffer nodeDataBuffer) { public void tick(GlBuffer nodeDataBuffer) {
this.visibilityId++; this.visibilityId++;
this.clearIds();
if (this.shouldCleanGeometry() && false ) { this.setIds(this.allocIds, this.visibilityId);
this.setIds(this.freeIds, -1);
if (this.shouldCleanGeometry()) {
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
this.outputBuffer.fill(this.nodeManager.maxNodeCount-2);//TODO: maybe dont set to zero?? this.outputBuffer.fill(this.nodeManager.maxNodeCount-2);//TODO: maybe dont set to zero??
@@ -100,9 +126,10 @@ public class NodeCleaner {
this.sorter.bind(); this.sorter.bind();
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, nodeDataBuffer.id); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, nodeDataBuffer.id);
//TODO: choose whether this is in nodeSpace or section/geometryId space //TODO: choose whether this is in nodeSpace or section/geometryId space
//this.nodeManager.getCurrentMaxNodeId() //
glDispatchCompute((200_000+127)/128, 1, 1); glDispatchCompute((this.nodeManager.getCurrentMaxNodeId()+SORTING_WORKER_SIZE-1)/SORTING_WORKER_SIZE, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
this.resultTransformer.bind(); this.resultTransformer.bind();
@@ -110,29 +137,38 @@ public class NodeCleaner {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, nodeDataBuffer.id); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, nodeDataBuffer.id);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 2, this.outputBuffer.id, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT); glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 2, this.outputBuffer.id, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.visibilityBuffer.id); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.visibilityBuffer.id);
glUniform1ui(0, this.visibilityId);
glDispatchCompute(1,1,1); glDispatchCompute(1,1,1);
//glFinish();
DownloadStream.INSTANCE.download(this.outputBuffer, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT, this::onDownload); DownloadStream.INSTANCE.download(this.outputBuffer, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT, this::onDownload);
//glFinish();
} }
} }
private boolean shouldCleanGeometry() { private boolean shouldCleanGeometry() {
// if there is less than 200mb of space, clean //// if there is less than 200mb of space, clean
return this.nodeManager.getGeometryManager().getRemainingCapacity() < 3_000_000_000L; //return this.nodeManager.getGeometryManager().getRemainingCapacity() < 1_000_000_000L;
//If used more than 75% of geometry buffer
return 3<((double)this.nodeManager.getGeometryManager().getUsedCapacity())/((double)this.nodeManager.getGeometryManager().getRemainingCapacity());
} }
private void onDownload(long ptr, long size) { private void onDownload(long ptr, long size) {
//StringBuilder b = new StringBuilder(); //StringBuilder b = new StringBuilder();
Long2IntOpenHashMap aa = new Long2IntOpenHashMap(); //Long2IntOpenHashMap aa = new Long2IntOpenHashMap();
for (int i = 0; i < OUTPUT_COUNT; i++) { for (int i = 0; i < OUTPUT_COUNT; i++) {
long pos = Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i))<<32; long pos = Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i))<<32;
pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i + 4)); pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i + 4));
aa.addTo(pos, 1); //aa.addTo(pos, 1);
if (pos == -1) { if (pos == -1) {
//TODO: investigate how or what this happens //TODO: investigate how or what this happens
continue; continue;
} }
//if (WorldEngine.getLevel(pos) == 4 && WorldEngine.getX(pos)<-32) {
// int a = 0;
//}
this.nodeManager.removeNodeGeometry(pos); this.nodeManager.removeNodeGeometry(pos);
//b.append(", ").append(WorldEngine.pprintPos(pos));//.append(((int)((pos>>32)&0xFFFFFFFFL)));// //b.append(", ").append(WorldEngine.pprintPos(pos));//.append(((int)((pos>>32)&0xFFFFFFFFL)));//
} }
@@ -141,19 +177,20 @@ public class NodeCleaner {
//System.out.println(b); //System.out.println(b);
} }
private void clearIds() { private void setIds(IntOpenHashSet collection, int setTo) {
if (!this.idsToClear.isEmpty()) { if (!collection.isEmpty()) {
this.batchClear.bind(); this.batchClear.bind();
var iter = collection.iterator();
while (!this.idsToClear.isEmpty()) { while (iter.hasNext()) {
int cnt = Math.min(this.idsToClear.size(), BATCH_SET_SIZE); int cnt = Math.min(collection.size(), BATCH_SET_SIZE);
long ptr = UploadStream.INSTANCE.upload(this.scratchBuffer, 0, cnt * 4L); long ptr = UploadStream.INSTANCE.upload(this.scratchBuffer, 0, cnt * 4L);
for (int i = 0; i < cnt; i++) { for (int i = 0; i < cnt; i++) {
MemoryUtil.memPutInt(ptr + i * 4, this.idsToClear.dequeueInt()); MemoryUtil.memPutInt(ptr + i * 4, iter.nextInt());
iter.remove();
} }
UploadStream.INSTANCE.commit(); UploadStream.INSTANCE.commit();
glUniform1ui(0, cnt); glUniform1ui(0, cnt);
glUniform1ui(1, this.visibilityId); glUniform1ui(1, setTo);
glDispatchCompute((cnt+127)/128, 1, 1); glDispatchCompute((cnt+127)/128, 1, 1);
} }
} }

View File

@@ -2,6 +2,7 @@ package me.cortex.voxy.client.core.rendering.hierachical;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.LongSet;
@@ -90,10 +91,16 @@ public class NodeManager {
private final LongOpenHashSet topLevelNodes = new LongOpenHashSet(); private final LongOpenHashSet topLevelNodes = new LongOpenHashSet();
private int activeNodeRequestCount; private int activeNodeRequestCount;
public interface ClearIdCallback {void clearId(int id);} public interface ICleaner {
private ClearIdCallback clearIdCallback; void alloc(int id);
public void setClearIdCallback(ClearIdCallback callback) {this.clearIdCallback = callback;} void move(int from, int to);
private void clearId(int id) { if (this.clearIdCallback != null) this.clearIdCallback.clearId(id); } void free(int id);
}
private ICleaner cleanerInterface;
public void setClear(ICleaner callback) {this.cleanerInterface = callback;}
private void clearAllocId(int id) { if (this.cleanerInterface != null) this.cleanerInterface.alloc(id); }
private void clearMoveId(int from, int to) { if (this.cleanerInterface != null) this.cleanerInterface.move(from, to); }
private void clearFreeId(int id) { if (this.cleanerInterface != null) this.cleanerInterface.free(id); }
public NodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, ISectionWatcher watcher) { public NodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, ISectionWatcher watcher) {
if (!MathUtil.isPowerOfTwo(maxNodeCount)) { if (!MathUtil.isPowerOfTwo(maxNodeCount)) {
@@ -153,7 +160,7 @@ public class NodeManager {
long pos = sectionResult.position; long pos = sectionResult.position;
int nodeId = this.activeSectionMap.get(pos); int nodeId = this.activeSectionMap.get(pos);
if (nodeId == -1) { if (nodeId == -1) {
Logger.warn("Got geometry update for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, discarding!"); //Logger.warn("Got geometry update for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, discarding!");
sectionResult.free(); sectionResult.free();
return; return;
} }
@@ -495,6 +502,10 @@ public class NodeManager {
//copy the previous entry to its new location //copy the previous entry to its new location
this.nodeData.copyNode(prevChildId, newChildId); this.nodeData.copyNode(prevChildId, newChildId);
this.clearAllocId(newChildId);
this.clearMoveId(prevChildId, newChildId);
this.clearFreeId(prevChildId);
int prevNodeId = this.activeSectionMap.get(cPos); int prevNodeId = this.activeSectionMap.get(cPos);
if ((prevNodeId & NODE_TYPE_MSK) == NODE_TYPE_REQUEST) { if ((prevNodeId & NODE_TYPE_MSK) == NODE_TYPE_REQUEST) {
throw new IllegalStateException(); throw new IllegalStateException();
@@ -503,6 +514,7 @@ public class NodeManager {
throw new IllegalStateException("State inconsistency"); throw new IllegalStateException("State inconsistency");
} }
this.activeSectionMap.put(cPos, (prevNodeId & NODE_TYPE_MSK) | newChildId); this.activeSectionMap.put(cPos, (prevNodeId & NODE_TYPE_MSK) | newChildId);
//Release the old entry //Release the old entry
this.nodeData.free(prevChildId); this.nodeData.free(prevChildId);
//Need to invalidate the old and the new //Need to invalidate the old and the new
@@ -722,7 +734,7 @@ public class NodeManager {
this.geometryManager.removeSection(geometry); this.geometryManager.removeSection(geometry);
this.nodeData.free(nodeId); this.nodeData.free(nodeId);
this.clearId(nodeId); this.clearFreeId(nodeId);
this.invalidateNode(nodeId); this.invalidateNode(nodeId);
//Unwatch position //Unwatch position
@@ -756,6 +768,7 @@ public class NodeManager {
//Assume that this is always a top node //Assume that this is always a top node
// FIXME: DONT DO THIS // FIXME: DONT DO THIS
this.topLevelNodeIds.add(id); this.topLevelNodeIds.add(id);
this.clearAllocId(id);
} }
private void finishRequest(int requestId, NodeChildRequest request) { private void finishRequest(int requestId, NodeChildRequest request) {
@@ -813,13 +826,15 @@ public class NodeManager {
this.nodeData.setNodeGeometry(childNodeId, request.getChildMesh(childIdx)); this.nodeData.setNodeGeometry(childNodeId, request.getChildMesh(childIdx));
//Mark for update //Mark for update
this.invalidateNode(childNodeId); this.invalidateNode(childNodeId);
this.clearId(childNodeId);//Clear the id //this.clearId(childNodeId);//Clear the id
//Put in map //Put in map
int pid = this.activeSectionMap.put(childPos, childNodeId|NODE_TYPE_LEAF); int pid = this.activeSectionMap.put(childPos, childNodeId|NODE_TYPE_LEAF);
if ((pid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) { if ((pid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) {
throw new IllegalStateException("Put node in map from request but type was not request: " + pid + " " + WorldEngine.pprintPos(childPos)); throw new IllegalStateException("Put node in map from request but type was not request: " + pid + " " + WorldEngine.pprintPos(childPos));
} }
this.clearAllocId(childNodeId);
} }
//Free request //Free request
this.childRequests.release(requestId); this.childRequests.release(requestId);
@@ -912,6 +927,7 @@ public class NodeManager {
if ((pid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) { if ((pid&NODE_TYPE_MSK) != NODE_TYPE_REQUEST) {
throw new IllegalStateException("Put node in map from request but type was not request: " + pid + " " + WorldEngine.pprintPos(childPos)); throw new IllegalStateException("Put node in map from request but type was not request: " + pid + " " + WorldEngine.pprintPos(childPos));
} }
this.clearAllocId(childId);
} else { } else {
prevChildId++; prevChildId++;
@@ -920,6 +936,10 @@ public class NodeManager {
//Its a previous entry, copy it to its new location //Its a previous entry, copy it to its new location
this.nodeData.copyNode(prevChildId, childId); this.nodeData.copyNode(prevChildId, childId);
this.clearAllocId(childId);
this.clearMoveId(prevChildId, childId);
this.clearFreeId(prevChildId);
int prevNodeId = this.activeSectionMap.get(pos); int prevNodeId = this.activeSectionMap.get(pos);
if ((prevNodeId&NODE_TYPE_MSK) == NODE_TYPE_REQUEST) { if ((prevNodeId&NODE_TYPE_MSK) == NODE_TYPE_REQUEST) {
throw new IllegalStateException(); throw new IllegalStateException();
@@ -1113,7 +1133,7 @@ public class NodeManager {
public void removeNodeGeometry(long pos) { public void removeNodeGeometry(long pos) {
int nodeId = this.activeSectionMap.get(pos); int nodeId = this.activeSectionMap.get(pos);
if (nodeId == -1) { if (nodeId == -1) {
Logger.warn("Got geometry removal for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!"); //Logger.warn("Got geometry removal for pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, ignoring!");
return; return;
} }
int nodeType = nodeId&NODE_TYPE_MSK; int nodeType = nodeId&NODE_TYPE_MSK;
@@ -1122,7 +1142,7 @@ public class NodeManager {
Logger.error("Tried removing geometry for pos: " + WorldEngine.pprintPos(pos) + " but its type was a request, ignoring!"); Logger.error("Tried removing geometry for pos: " + WorldEngine.pprintPos(pos) + " but its type was a request, ignoring!");
return; return;
} }
this.clearId(nodeId); //this.clearId(nodeId);
if (nodeType == NODE_TYPE_INNER) { if (nodeType == NODE_TYPE_INNER) {
this.clearGeometryInternal(pos, nodeId); this.clearGeometryInternal(pos, nodeId);
@@ -1157,7 +1177,8 @@ public class NodeManager {
throw new IllegalStateException("Parent node must be an inner node"); throw new IllegalStateException("Parent node must be an inner node");
pId &= NODE_ID_MSK; pId &= NODE_ID_MSK;
{//Check all children are leaf nodes if (false) {//Check all children are leaf nodes
//TODO: make a better way to do this (i.e. gpu driven)
int cPtr = this.nodeData.getChildPtr(pId); int cPtr = this.nodeData.getChildPtr(pId);
if (cPtr != SENTINEL_EMPTY_CHILD_PTR) { if (cPtr != SENTINEL_EMPTY_CHILD_PTR) {
if (cPtr == -1) { if (cPtr == -1) {
@@ -1172,8 +1193,11 @@ public class NodeManager {
if (cn==-1) if (cn==-1)
throw new IllegalStateException(); throw new IllegalStateException();
//If a child is not a leaf, return //If a child is not a leaf, return
if ((cn&NODE_TYPE_MSK)!=NODE_TYPE_LEAF) if ((cn&NODE_TYPE_MSK)!=NODE_TYPE_LEAF) {
this.clearAllocId(this.activeSectionMap.get(cPos)&NODE_ID_MSK);
return; return;
}
} }
} }
} }
@@ -1185,7 +1209,7 @@ public class NodeManager {
} else { } else {
//Convert to leaf node //Convert to leaf node
this.recurseRemoveChildNodes(pPos);//TODO: make this download/fetch the data instead of just deleting it this.recurseRemoveChildNodes(pPos);//TODO: make this download/fetch the data instead of just deleting it
this.clearId(pId); //this.clearId(pId);
int old = this.activeSectionMap.put(pPos, NODE_TYPE_LEAF|pId); int old = this.activeSectionMap.put(pPos, NODE_TYPE_LEAF|pId);
if (old == -1) if (old == -1)
@@ -1552,9 +1576,9 @@ public class NodeManager {
} }
public void verifyIntegrity() { public void verifyIntegrity() {
this.verifyIntegrity(null); this.verifyIntegrity(null, null);
} }
public void verifyIntegrity(LongSet watchingPosSet) { public void verifyIntegrity(LongSet watchingPosSet, IntSet nodes) {
//Should verify integrity of node manager, everything //Should verify integrity of node manager, everything
// should traverse from top (root positions) down // should traverse from top (root positions) down
// after it should check if there is anything it hasnt tracked, if so thats badd // after it should check if there is anything it hasnt tracked, if so thats badd
@@ -1594,5 +1618,13 @@ public class NodeManager {
throw new IllegalStateException(); throw new IllegalStateException();
} }
} }
if (nodes != null) {
if (!nodes.containsAll(seenNodes)) {
throw new IllegalStateException();
}
if (!seenNodes.containsAll(nodes)) {
throw new IllegalStateException();
}
}
} }
} }

View File

@@ -250,6 +250,13 @@ public final class NodeStore {
//Writes out a nodes data to the ptr in the compacted/reduced format //Writes out a nodes data to the ptr in the compacted/reduced format
public void writeNode(long ptr, int nodeId) { public void writeNode(long ptr, int nodeId) {
if (!this.nodeExists(nodeId)) {
MemoryUtil.memPutLong(ptr, -1);
MemoryUtil.memPutLong(ptr + 8, -1);
MemoryUtil.memPutLong(ptr + 16, -1);
MemoryUtil.memPutLong(ptr + 24, -1);
return;
}
long pos = this.nodePosition(nodeId); long pos = this.nodePosition(nodeId);
MemoryUtil.memPutInt(ptr, (int) (pos>>32)); ptr += 4; MemoryUtil.memPutInt(ptr, (int) (pos>>32)); ptr += 4;
MemoryUtil.memPutInt(ptr, (int) pos); ptr += 4; MemoryUtil.memPutInt(ptr, (int) pos); ptr += 4;

View File

@@ -1,6 +1,7 @@
package me.cortex.voxy.client.core.rendering.hierachical; package me.cortex.voxy.client.core.rendering.hierachical;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2IntFunction; import it.unimi.dsi.fastutil.longs.Long2IntFunction;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
@@ -76,6 +77,28 @@ public class TestNodeManager {
} }
} }
private static class CleanerImp implements NodeManager.ICleaner {
private final IntOpenHashSet active = new IntOpenHashSet();
@Override
public void alloc(int id) {
if (!this.active.add(id)) {
throw new IllegalStateException();
}
}
@Override
public void move(int from, int to) {
}
@Override
public void free(int id) {
if (!this.active.remove(id)) {
throw new IllegalStateException();
}
}
}
private static class Watcher implements ISectionWatcher { private static class Watcher implements ISectionWatcher {
private final Long2ByteOpenHashMap updateTypes = new Long2ByteOpenHashMap(); private final Long2ByteOpenHashMap updateTypes = new Long2ByteOpenHashMap();
@@ -137,11 +160,14 @@ public class TestNodeManager {
public final MemoryGeometryManager geometryManager; public final MemoryGeometryManager geometryManager;
public final NodeManager nodeManager; public final NodeManager nodeManager;
public final Watcher watcher; public final Watcher watcher;
public final CleanerImp cleaner;
public TestBase() { public TestBase() {
this.watcher = new Watcher(); this.watcher = new Watcher();
this.cleaner = new CleanerImp();
this.geometryManager = new MemoryGeometryManager(1<<20, 1<<30); this.geometryManager = new MemoryGeometryManager(1<<20, 1<<30);
this.nodeManager = new NodeManager(1 << 21, this.geometryManager, this.watcher); this.nodeManager = new NodeManager(1 << 21, this.geometryManager, this.watcher);
this.nodeManager.setClear(this.cleaner);
} }
public void putTopPos(long pos) { public void putTopPos(long pos) {
@@ -208,7 +234,7 @@ public class TestNodeManager {
} }
public void verifyIntegrity() { public void verifyIntegrity() {
this.nodeManager.verifyIntegrity(this.watcher.updateTypes.keySet()); this.nodeManager.verifyIntegrity(this.watcher.updateTypes.keySet(), this.cleaner.active);
} }
} }

View File

@@ -137,7 +137,7 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager
this.geometry.downloadRemove(oldMetadata.geometryPtr, buffer -> this.geometry.downloadRemove(oldMetadata.geometryPtr, buffer ->
callback.accept(new BuiltSection(oldMetadata.position, oldMetadata.childExistence, oldMetadata.aabb, buffer.copy(), oldMetadata.offsets)) callback.accept(new BuiltSection(oldMetadata.position, oldMetadata.childExistence, oldMetadata.aabb, buffer.copy(), oldMetadata.offsets))
); );
this.geometry.free(oldMetadata.geometryPtr); //this.geometry.free(oldMetadata.geometryPtr);
this.invalidatedSectionIds.add(id); this.invalidatedSectionIds.add(id);
} }

View File

@@ -18,6 +18,8 @@ public class HierarchicalBitSet {
} }
} }
private int endId = -1;
public HierarchicalBitSet() { public HierarchicalBitSet() {
this(1<<(6*4)); this(1<<(6*4));
} }
@@ -55,11 +57,12 @@ public class HierarchicalBitSet {
} }
} }
this.cnt++; this.cnt++;
this.endId += ret==(this.endId+1)?1:0;
return ret; return ret;
} }
private void set(int idx) { private void set(int idx) {
this.endId += idx==(this.endId+1)?1:0;
long dp = this.D[idx>>6] |= 1L<<(idx&0x3f); long dp = this.D[idx>>6] |= 1L<<(idx&0x3f);
if (dp==-1) { if (dp==-1) {
idx >>= 6; idx >>= 6;
@@ -139,6 +142,13 @@ public class HierarchicalBitSet {
long v = this.D[idx>>6]; long v = this.D[idx>>6];
boolean wasSet = (v&(1L<<(idx&0x3f)))!=0; boolean wasSet = (v&(1L<<(idx&0x3f)))!=0;
this.cnt -= wasSet?1:0; this.cnt -= wasSet?1:0;
if (wasSet && idx == this.endId) {
//Need to go back until we find the endIdx bit
for (this.endId--; this.endId>=0 && !this.isSet(this.endId); this.endId--);
//this.endId++;
}
this.D[idx>>6] = v&~(1L<<(idx&0x3f)); this.D[idx>>6] = v&~(1L<<(idx&0x3f));
idx >>= 6; idx >>= 6;
this.C[idx>>6] &= ~(1L<<(idx&0x3f)); this.C[idx>>6] &= ~(1L<<(idx&0x3f));
@@ -146,6 +156,7 @@ public class HierarchicalBitSet {
this.B[idx>>6] &= ~(1L<<(idx&0x3f)); this.B[idx>>6] &= ~(1L<<(idx&0x3f));
idx >>= 6; idx >>= 6;
this.A &= ~(1L<<(idx&0x3f)); this.A &= ~(1L<<(idx&0x3f));
return wasSet; return wasSet;
} }
@@ -162,7 +173,7 @@ public class HierarchicalBitSet {
public int getMaxIndex() { public int getMaxIndex() {
throw new IllegalStateException(); return this.endId;
} }
@@ -172,6 +183,25 @@ public class HierarchicalBitSet {
if (h.allocateNext() != i) { if (h.allocateNext() != i) {
throw new IllegalStateException("At:" + i); throw new IllegalStateException("At:" + i);
} }
if (h.endId != i) {
throw new IllegalStateException();
}
}
for (int i = 0; i < 1<<18; i++) {
if (!h.free(i)) {
throw new IllegalStateException();
}
}
for (int i = (1<<19)-1; i != (1<<18)-1; i--) {
if (h.endId != i) {
throw new IllegalStateException();
}
if (!h.free(i)) {
throw new IllegalStateException();
}
}
if (h.endId != -1) {
throw new IllegalStateException();
} }
} }

View File

@@ -61,7 +61,7 @@ public class MemoryBuffer extends TrackedObject {
} }
public MemoryBuffer copy() { public MemoryBuffer copy() {
var copy = new MemoryBuffer(false, this.size, size, freeable); var copy = new MemoryBuffer(this.size);
this.cpyTo(copy.address); this.cpyTo(copy.address);
return copy; return copy;
} }

View File

@@ -6,6 +6,7 @@ import me.cortex.voxy.common.util.Pair;
import me.cortex.voxy.common.world.other.Mipper; import me.cortex.voxy.common.world.other.Mipper;
import me.cortex.voxy.common.world.other.Mapper; import me.cortex.voxy.common.world.other.Mapper;
import net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette; import net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.collection.EmptyPaletteStorage; import net.minecraft.util.collection.EmptyPaletteStorage;
@@ -16,6 +17,8 @@ import net.minecraft.world.chunk.*;
import java.util.WeakHashMap; import java.util.WeakHashMap;
public class WorldConversionFactory { public class WorldConversionFactory {
private static final boolean LITHIUM_INSTALLED = FabricLoader.getInstance().isModLoaded("lithium");
private static final class Cache { private static final class Cache {
private final int[] biomeCache = new int[4*4*4]; private final int[] biomeCache = new int[4*4*4];
private final WeakHashMap<Mapper, Reference2IntOpenHashMap<BlockState>> localMapping = new WeakHashMap<>(); private final WeakHashMap<Mapper, Reference2IntOpenHashMap<BlockState>> localMapping = new WeakHashMap<>();
@@ -34,68 +37,71 @@ public class WorldConversionFactory {
//TODO: create a mapping for world/mapper -> local mapping //TODO: create a mapping for world/mapper -> local mapping
private static final ThreadLocal<Cache> THREAD_LOCAL = ThreadLocal.withInitial(Cache::new); private static final ThreadLocal<Cache> THREAD_LOCAL = ThreadLocal.withInitial(Cache::new);
private static void setupLocalPalette(Palette<BlockState> vp,Reference2IntOpenHashMap<BlockState> blockCache, Mapper mapper, int[] pc) { private static boolean setupLithiumLocalPallet(Palette<BlockState> vp, Reference2IntOpenHashMap<BlockState> blockCache, Mapper mapper, int[] pc) {
{ if (vp instanceof LithiumHashPalette<BlockState>) {
if (vp instanceof ArrayPalette<BlockState>) { for (int i = 0; i < vp.getSize(); i++) {
for (int i = 0; i < vp.getSize(); i++) { BlockState state = null;
var state = vp.get(i); int blockId = -1;
int blockId = -1; try { state = vp.get(i); } catch (Exception e) {}
if (state != null) { if (state != null) {
blockId = blockCache.getOrDefault(state, -1); blockId = blockCache.getOrDefault(state, -1);
if (blockId == -1) { if (blockId == -1) {
blockId = mapper.getIdForBlockState(state); blockId = mapper.getIdForBlockState(state);
blockCache.put(state, blockId); blockCache.put(state, blockId);
}
} }
pc[i] = blockId;
} }
} else if (vp instanceof LithiumHashPalette<BlockState>) { pc[i] = blockId;
for (int i = 0; i < vp.getSize(); i++) { }
BlockState state = null; return true;
int blockId = -1; }
try { state = vp.get(i); } catch (Exception e) {} return false;
if (state != null) { }
blockId = blockCache.getOrDefault(state, -1); private static void setupLocalPalette(Palette<BlockState> vp, Reference2IntOpenHashMap<BlockState> blockCache, Mapper mapper, int[] pc) {
if (blockId == -1) { if (vp instanceof ArrayPalette<BlockState>) {
blockId = mapper.getIdForBlockState(state); for (int i = 0; i < vp.getSize(); i++) {
blockCache.put(state, blockId); var state = vp.get(i);
} int blockId = -1;
if (state != null) {
blockId = blockCache.getOrDefault(state, -1);
if (blockId == -1) {
blockId = mapper.getIdForBlockState(state);
blockCache.put(state, blockId);
} }
pc[i] = blockId;
} }
} else { pc[i] = blockId;
if (vp instanceof BiMapPalette<BlockState> pal) { }
//var map = pal.map; } else if (vp instanceof BiMapPalette<BlockState> pal) {
//TODO: heavily optimize this by reading the map directly //var map = pal.map;
//TODO: heavily optimize this by reading the map directly
for (int i = 0; i < vp.getSize(); i++) { for (int i = 0; i < vp.getSize(); i++) {
BlockState state = null; BlockState state = null;
int blockId = -1; int blockId = -1;
try { state = vp.get(i); } catch (Exception e) {} try { state = vp.get(i); } catch (Exception e) {}
if (state != null) { if (state != null) {
blockId = blockCache.getOrDefault(state, -1); blockId = blockCache.getOrDefault(state, -1);
if (blockId == -1) { if (blockId == -1) {
blockId = mapper.getIdForBlockState(state); blockId = mapper.getIdForBlockState(state);
blockCache.put(state, blockId); blockCache.put(state, blockId);
}
}
pc[i] = blockId;
} }
} else if (vp instanceof SingularPalette<BlockState>) {
int blockId = -1;
var state = vp.get(0);
if (state != null) {
blockId = blockCache.getOrDefault(state, -1);
if (blockId == -1) {
blockId = mapper.getIdForBlockState(state);
blockCache.put(state, blockId);
}
}
pc[0] = blockId;
} else {
throw new IllegalStateException("Unknown palette type: " + vp);
} }
pc[i] = blockId;
}
} else if (vp instanceof SingularPalette<BlockState>) {
int blockId = -1;
var state = vp.get(0);
if (state != null) {
blockId = blockCache.getOrDefault(state, -1);
if (blockId == -1) {
blockId = mapper.getIdForBlockState(state);
blockCache.put(state, blockId);
}
}
pc[0] = blockId;
} else {
if (!(LITHIUM_INSTALLED && setupLithiumLocalPallet(vp, blockCache, mapper, pc))) {
throw new IllegalStateException("Unknown palette type: " + vp);
} }
} }
} }

View File

@@ -7,6 +7,7 @@ import me.cortex.voxy.common.world.other.Mapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
public class ActiveSectionTracker { public class ActiveSectionTracker {
@@ -16,6 +17,7 @@ public class ActiveSectionTracker {
//Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane //Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane
private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache; private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache;
private final ReentrantLock[] locks;
private final SectionLoader loader; private final SectionLoader loader;
private final int maxLRUSectionPerSlice; private final int maxLRUSectionPerSlice;
@@ -34,10 +36,12 @@ public class ActiveSectionTracker {
this.loader = loader; this.loader = loader;
this.loadedSectionCache = new Long2ObjectOpenHashMap[1<<numSlicesBits]; this.loadedSectionCache = new Long2ObjectOpenHashMap[1<<numSlicesBits];
this.lruSecondaryCache = new Long2ObjectLinkedOpenHashMap[1<<numSlicesBits]; this.lruSecondaryCache = new Long2ObjectLinkedOpenHashMap[1<<numSlicesBits];
this.locks = new ReentrantLock[1<<numSlicesBits];
this.maxLRUSectionPerSlice = (cacheSize+(1<<numSlicesBits)-1)/(1<<numSlicesBits); this.maxLRUSectionPerSlice = (cacheSize+(1<<numSlicesBits)-1)/(1<<numSlicesBits);
for (int i = 0; i < this.loadedSectionCache.length; i++) { for (int i = 0; i < this.loadedSectionCache.length; i++) {
this.loadedSectionCache[i] = new Long2ObjectOpenHashMap<>(1024); this.loadedSectionCache[i] = new Long2ObjectOpenHashMap<>(1024);
this.lruSecondaryCache[i] = new Long2ObjectLinkedOpenHashMap<>(this.maxLRUSectionPerSlice); this.lruSecondaryCache[i] = new Long2ObjectLinkedOpenHashMap<>(this.maxLRUSectionPerSlice);
this.locks[i] = new ReentrantLock();
} }
} }
@@ -48,10 +52,13 @@ public class ActiveSectionTracker {
public WorldSection acquire(long key, boolean nullOnEmpty) { public WorldSection acquire(long key, boolean nullOnEmpty) {
int index = this.getCacheArrayIndex(key); int index = this.getCacheArrayIndex(key);
var cache = this.loadedSectionCache[index]; var cache = this.loadedSectionCache[index];
final var lock = this.locks[index];
VolatileHolder<WorldSection> holder = null; VolatileHolder<WorldSection> holder = null;
boolean isLoader = false; boolean isLoader = false;
WorldSection section; WorldSection section;
synchronized (cache) {
lock.lock();
{
holder = cache.get(key); holder = cache.get(key);
if (holder == null) { if (holder == null) {
holder = new VolatileHolder<>(); holder = new VolatileHolder<>();
@@ -61,12 +68,14 @@ public class ActiveSectionTracker {
section = holder.obj; section = holder.obj;
if (section != null) { if (section != null) {
section.acquire(); section.acquire();
lock.unlock();
return section; return section;
} }
if (isLoader) { if (isLoader) {
section = this.lruSecondaryCache[index].remove(key); section = this.lruSecondaryCache[index].remove(key);
} }
} }
lock.unlock();
//If this thread was the one to create the reference then its the thread to load the section //If this thread was the one to create the reference then its the thread to load the section
if (isLoader) { if (isLoader) {
@@ -107,19 +116,25 @@ public class ActiveSectionTracker {
while ((section = holder.obj) == null) while ((section = holder.obj) == null)
Thread.yield(); Thread.yield();
synchronized (cache) { //lock.lock();
{//Dont think need to lock here
if (section.tryAcquire()) { if (section.tryAcquire()) {
return section; return section;
} }
} }
//lock.unlock();
return this.acquire(key, nullOnEmpty); return this.acquire(key, nullOnEmpty);
} }
} }
void tryUnload(WorldSection section) { void tryUnload(WorldSection section) {
int index = this.getCacheArrayIndex(section.key); int index = this.getCacheArrayIndex(section.key);
var cache = this.loadedSectionCache[index]; final var cache = this.loadedSectionCache[index];
synchronized (cache) { WorldSection prev = null;
WorldSection lruEntry = null;
final var lock = this.locks[index];
lock.lock();
{
if (section.trySetFreed()) { if (section.trySetFreed()) {
var cached = cache.remove(section.key); var cached = cache.remove(section.key);
var obj = cached.obj; var obj = cached.obj;
@@ -129,16 +144,21 @@ public class ActiveSectionTracker {
//Add section to secondary cache while primary is locked //Add section to secondary cache while primary is locked
var lruCache = this.lruSecondaryCache[index]; var lruCache = this.lruSecondaryCache[index];
var prev = lruCache.put(section.key, section); prev = lruCache.put(section.key, section);
if (prev != null) {
prev._releaseArray();
}
//If cache is bigger than its ment to be, remove the least recently used and free it //If cache is bigger than its ment to be, remove the least recently used and free it
if (this.maxLRUSectionPerSlice < lruCache.size()) { if (this.maxLRUSectionPerSlice < lruCache.size()) {
lruCache.removeFirst()._releaseArray(); lruEntry = lruCache.removeFirst();
} }
} }
} }
lock.unlock();
if (prev != null) {
prev._releaseArray();
}
if (lruEntry != null) {
lruEntry._releaseArray();
}
} }
private int getCacheArrayIndex(long pos) { private int getCacheArrayIndex(long pos) {

View File

@@ -8,7 +8,7 @@ import me.cortex.voxy.common.world.other.Mapper;
import java.util.List; import java.util.List;
public class WorldEngine { public class WorldEngine {
public static final int MAX_LOD_LAYERS = 5; public static final int MAX_LOD_LAYER = 4;
public static final int UPDATE_TYPE_BLOCK_BIT = 1; public static final int UPDATE_TYPE_BLOCK_BIT = 1;
public static final int UPDATE_TYPE_CHILD_EXISTENCE_BIT = 2; public static final int UPDATE_TYPE_CHILD_EXISTENCE_BIT = 2;
@@ -38,7 +38,7 @@ public class WorldEngine {
public Mapper getMapper() {return this.mapper;} public Mapper getMapper() {return this.mapper;}
public boolean isLive() {return this.isLive;} public boolean isLive() {return this.isLive;}
public WorldEngine(SectionStorage storage, int cacheCount) { public WorldEngine(SectionStorage storage, int cacheCount) {
this(storage, MAX_LOD_LAYERS, cacheCount); this(storage, MAX_LOD_LAYER+1, cacheCount);//The +1 is because its from 1 not from 0
} }
private WorldEngine(SectionStorage storage, int maxMipLayers, int cacheCount) { private WorldEngine(SectionStorage storage, int maxMipLayers, int cacheCount) {
@@ -46,7 +46,7 @@ public class WorldEngine {
this.storage = storage; this.storage = storage;
this.mapper = new Mapper(this.storage); this.mapper = new Mapper(this.storage);
//5 cache size bits means that the section tracker has 32 separate maps that it uses //5 cache size bits means that the section tracker has 32 separate maps that it uses
this.sectionTracker = new ActiveSectionTracker(7, storage::loadSection, cacheCount, this); this.sectionTracker = new ActiveSectionTracker(10, storage::loadSection, cacheCount, this);
} }
public WorldSection acquireIfExists(int lvl, int x, int y, int z) { public WorldSection acquireIfExists(int lvl, int x, int y, int z) {

View File

@@ -35,7 +35,7 @@ public final class WorldSection {
//TODO: should make it dynamically adjust the size allowance based on memory pressure/WorldSection allocation rate (e.g. is it doing a world import) //TODO: should make it dynamically adjust the size allowance based on memory pressure/WorldSection allocation rate (e.g. is it doing a world import)
private static final int ARRAY_REUSE_CACHE_SIZE = 300;//500;//32*32*32*8*ARRAY_REUSE_CACHE_SIZE == number of bytes private static final int ARRAY_REUSE_CACHE_SIZE = 400;//500;//32*32*32*8*ARRAY_REUSE_CACHE_SIZE == number of bytes
//TODO: maybe just swap this to a ConcurrentLinkedDeque //TODO: maybe just swap this to a ConcurrentLinkedDeque
private static final AtomicInteger ARRAY_REUSE_CACHE_COUNT = new AtomicInteger(0); private static final AtomicInteger ARRAY_REUSE_CACHE_COUNT = new AtomicInteger(0);
private static final ConcurrentLinkedDeque<long[]> ARRAY_REUSE_CACHE = new ConcurrentLinkedDeque<>(); private static final ConcurrentLinkedDeque<long[]> ARRAY_REUSE_CACHE = new ConcurrentLinkedDeque<>();

View File

@@ -1,7 +1,5 @@
package me.cortex.voxy.commonImpl; package me.cortex.voxy.commonImpl;
import me.cortex.voxy.client.core.WorldImportWrapper;
import me.cortex.voxy.client.saver.ContextSelectionSystem;
import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.config.section.SectionStorage; import me.cortex.voxy.common.config.section.SectionStorage;
import me.cortex.voxy.common.thread.ServiceThreadPool; import me.cortex.voxy.common.thread.ServiceThreadPool;

View File

@@ -17,13 +17,19 @@ layout(binding = VISIBILITY_BUFFER_BINDING, std430) restrict buffer VisibilityBu
uint[] visibility; uint[] visibility;
}; };
layout(location=0) uniform uint visibilityCounter;
void main() { void main() {
uvec4 node = nodes[minVisIds[gl_LocalInvocationID.x]]; uint id = minVisIds[gl_LocalInvocationID.x];
uvec4 node = nodes[id];
uvec2 res = node.xy; uvec2 res = node.xy;
if (all(equal(node, uvec4(0)))) {//If its a empty node, TODO: DONT THINK THIS IS ACTUALLY CORRECT if (all(equal(node, uvec4(0)))) {//If its a empty node, TODO: DONT THINK THIS IS ACTUALLY CORRECT
res = uvec2(-1); res = uvec2(-1);
} }
outputBuffer[gl_LocalInvocationID.x] = res;//Move the position of the node id into the output buffer outputBuffer[gl_LocalInvocationID.x] = res;//Move the position of the node id into the output buffer
//outputBuffer[gl_LocalInvocationID.x].x = visibility[minVisIds[gl_LocalInvocationID.x]];// //This is an evil hack to not spam the same node 500 times in a row
//TODO: maybe instead set this to the current frame index
// visibility[id] += 30;
visibility[id] = visibilityCounter;
} }

View File

@@ -4,7 +4,7 @@
//#define OUTPUT_SIZE 128 //#define OUTPUT_SIZE 128
layout(local_size_x=128, local_size_y=1) in; layout(local_size_x=WORK_SIZE, local_size_y=1) in;
//256 workgroup //256 workgroup
#import <voxy:lod/hierarchical/node.glsl> #import <voxy:lod/hierarchical/node.glsl>
@@ -54,9 +54,36 @@ void main() {
return; return;
} }
UnpackedNode node; UnpackedNode node;
unpackNode(node, gl_GlobalInvocationID.x); if (unpackNode(node, gl_GlobalInvocationID.x)==uvec4(-1)) {
return;//Unallocated node
}
if (isEmptyMesh(node) || (!hasMesh(node))) {//|| (!hasChildren(node)) if (isEmptyMesh(node) || (!hasMesh(node))) {//|| (!hasChildren(node))
return; return;
} }
//TODO: FIXME: DONT HARDCODE TOP LEVEL LOD LEVEL
if (node.lodLevel == 4) {// (!hasChildren(node)) -> Assume leaf node
return;//Cannot remove geometry from top level node
}
/*THIS IS COMPLETLY WRONG, we need to check if all the children of the parent of the child are leaf nodes
// not this node
//Very sneeky hack, ensure all children are leaf nodes
if (hasChildren(node)&&!childListIsEmpty(node)) {
uint ptr = getChildPtr(node);
uint cnt = getChildCount(node);
for (uint i = 0; i < cnt; i++) {
UnpackedNode child;
unpackNode(child, i+ptr);
if (hasChildren(child)) {
return;
}
}
}
*/
bubbleSort(0, gl_GlobalInvocationID.x); bubbleSort(0, gl_GlobalInvocationID.x);
} }

View File

@@ -8,9 +8,6 @@ layout(binding = NODE_DATA_BINDING, std430) restrict buffer NodeData {
//First 2 are joined to be the position //First 2 are joined to be the position
//All node access and setup into global variables //All node access and setup into global variables
//TODO: maybe make it global vars //TODO: maybe make it global vars
struct UnpackedNode { struct UnpackedNode {
@@ -32,7 +29,7 @@ struct UnpackedNode {
#define NULL_MESH ((1<<24)-1) #define NULL_MESH ((1<<24)-1)
#define EMPTY_MESH ((1<<24)-2) #define EMPTY_MESH ((1<<24)-2)
void unpackNode(out UnpackedNode node, uint nodeId) { uvec4 unpackNode(out UnpackedNode node, uint nodeId) {
uvec4 compactedNode = nodes[nodeId]; uvec4 compactedNode = nodes[nodeId];
node.nodeId = nodeId; node.nodeId = nodeId;
node.lodLevel = compactedNode.x >> 28; node.lodLevel = compactedNode.x >> 28;
@@ -50,6 +47,7 @@ void unpackNode(out UnpackedNode node, uint nodeId) {
node.meshPtr = compactedNode.z&0xFFFFFFu; node.meshPtr = compactedNode.z&0xFFFFFFu;
node.childPtr = compactedNode.w&0xFFFFFFu; node.childPtr = compactedNode.w&0xFFFFFFu;
node.flags = ((compactedNode.z>>24)&0xFFu) | (((compactedNode.w>>24)&0xFFu)<<8); node.flags = ((compactedNode.z>>24)&0xFFu) | (((compactedNode.w>>24)&0xFFu)<<8);
return compactedNode;
} }
bool hasMesh(in UnpackedNode node) { bool hasMesh(in UnpackedNode node) {