Stuff kinda works
This commit is contained in:
@@ -13,7 +13,7 @@ public class VoxyClientInstance extends VoxyInstance {
|
||||
public WorldImportWrapper importWrapper;
|
||||
|
||||
public VoxyClientInstance() {
|
||||
super(12);
|
||||
super(14);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -191,13 +191,14 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
||||
if (true /* firstInvocationThisFrame */) {
|
||||
DownloadStream.INSTANCE.tick();
|
||||
|
||||
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
|
||||
|
||||
this.sectionUpdateQueue.consume();
|
||||
this.geometryUpdateQueue.consume();
|
||||
if (this.nodeManager.writeChanges(this.traversal.getNodeBuffer())) {//TODO: maybe move the node buffer out of the traversal class
|
||||
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.sectionRenderer.getGeometryManager().tick();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
final long position = section.key;
|
||||
var set = this.slices[getSliceIndex(position)];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class RenderGenerationService {
|
||||
|
||||
this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{
|
||||
//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<>(() -> {
|
||||
this.processJob(factory);
|
||||
}, factory::free);
|
||||
@@ -75,7 +75,7 @@ public class RenderGenerationService {
|
||||
}
|
||||
|
||||
//TODO: add a generated render data cache
|
||||
private void processJob(RenderDataFactory4 factory) {
|
||||
private void processJob(RenderDataFactory45 factory) {
|
||||
BuildTask task;
|
||||
synchronized (this.taskQueue) {
|
||||
task = this.taskQueue.removeFirst();
|
||||
@@ -133,7 +133,9 @@ public class RenderGenerationService {
|
||||
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
|
||||
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
|
||||
shouldFreeSection = false;
|
||||
@@ -186,11 +188,25 @@ public class RenderGenerationService {
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
//Cleanup any remaining data
|
||||
while (!this.taskQueue.isEmpty()) {
|
||||
this.taskQueue.removeFirst();
|
||||
var task = this.taskQueue.removeFirst();
|
||||
if (task.section != null) {
|
||||
task.section.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.IntArrayFIFOQueue;
|
||||
import it.unimi.dsi.fastutil.ints.IntCollection;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
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 org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glCopyNamedBufferSubData;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1i;
|
||||
import static org.lwjgl.opengl.GL30C.glBindBufferRange;
|
||||
import static org.lwjgl.opengl.GL42C.glMemoryBarrier;
|
||||
@@ -30,11 +33,16 @@ import static org.lwjgl.opengl.GL43C.*;
|
||||
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)
|
||||
|
||||
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 final AutoBindingShader sorter = Shader.makeAuto(PrintfDebugUtil.PRINTF_processor)
|
||||
.define("WORK_SIZE", SORTING_WORKER_SIZE)
|
||||
.define("OUTPUT_SIZE", OUTPUT_COUNT)
|
||||
.define("VISIBILITY_BUFFER_BINDING", 1)
|
||||
.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 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;
|
||||
int visibilityId = 0;
|
||||
@@ -81,18 +90,35 @@ public class NodeCleaner {
|
||||
.ssbo("VISIBILITY_BUFFER_BINDING", this.visibilityBuffer)
|
||||
.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) {
|
||||
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);
|
||||
this.outputBuffer.fill(this.nodeManager.maxNodeCount-2);//TODO: maybe dont set to zero??
|
||||
|
||||
@@ -100,9 +126,10 @@ public class NodeCleaner {
|
||||
this.sorter.bind();
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, nodeDataBuffer.id);
|
||||
|
||||
|
||||
//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);
|
||||
|
||||
this.resultTransformer.bind();
|
||||
@@ -110,29 +137,38 @@ public class NodeCleaner {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, nodeDataBuffer.id);
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 2, this.outputBuffer.id, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.visibilityBuffer.id);
|
||||
glUniform1ui(0, this.visibilityId);
|
||||
|
||||
glDispatchCompute(1,1,1);
|
||||
//glFinish();
|
||||
|
||||
DownloadStream.INSTANCE.download(this.outputBuffer, 4*OUTPUT_COUNT, 8*OUTPUT_COUNT, this::onDownload);
|
||||
//glFinish();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCleanGeometry() {
|
||||
// if there is less than 200mb of space, clean
|
||||
return this.nodeManager.getGeometryManager().getRemainingCapacity() < 3_000_000_000L;
|
||||
//// if there is less than 200mb of space, clean
|
||||
//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) {
|
||||
//StringBuilder b = new StringBuilder();
|
||||
Long2IntOpenHashMap aa = new Long2IntOpenHashMap();
|
||||
//Long2IntOpenHashMap aa = new Long2IntOpenHashMap();
|
||||
for (int i = 0; i < OUTPUT_COUNT; i++) {
|
||||
long pos = Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i))<<32;
|
||||
pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i + 4));
|
||||
aa.addTo(pos, 1);
|
||||
pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + 8 * i + 4));
|
||||
//aa.addTo(pos, 1);
|
||||
if (pos == -1) {
|
||||
//TODO: investigate how or what this happens
|
||||
continue;
|
||||
}
|
||||
//if (WorldEngine.getLevel(pos) == 4 && WorldEngine.getX(pos)<-32) {
|
||||
// int a = 0;
|
||||
//}
|
||||
this.nodeManager.removeNodeGeometry(pos);
|
||||
//b.append(", ").append(WorldEngine.pprintPos(pos));//.append(((int)((pos>>32)&0xFFFFFFFFL)));//
|
||||
}
|
||||
@@ -141,19 +177,20 @@ public class NodeCleaner {
|
||||
//System.out.println(b);
|
||||
}
|
||||
|
||||
private void clearIds() {
|
||||
if (!this.idsToClear.isEmpty()) {
|
||||
private void setIds(IntOpenHashSet collection, int setTo) {
|
||||
if (!collection.isEmpty()) {
|
||||
this.batchClear.bind();
|
||||
|
||||
while (!this.idsToClear.isEmpty()) {
|
||||
int cnt = Math.min(this.idsToClear.size(), BATCH_SET_SIZE);
|
||||
var iter = collection.iterator();
|
||||
while (iter.hasNext()) {
|
||||
int cnt = Math.min(collection.size(), BATCH_SET_SIZE);
|
||||
long ptr = UploadStream.INSTANCE.upload(this.scratchBuffer, 0, cnt * 4L);
|
||||
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();
|
||||
glUniform1ui(0, cnt);
|
||||
glUniform1ui(1, this.visibilityId);
|
||||
glUniform1ui(1, setTo);
|
||||
glDispatchCompute((cnt+127)/128, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
@@ -90,10 +91,16 @@ public class NodeManager {
|
||||
private final LongOpenHashSet topLevelNodes = new LongOpenHashSet();
|
||||
private int activeNodeRequestCount;
|
||||
|
||||
public interface ClearIdCallback {void clearId(int id);}
|
||||
private ClearIdCallback clearIdCallback;
|
||||
public void setClearIdCallback(ClearIdCallback callback) {this.clearIdCallback = callback;}
|
||||
private void clearId(int id) { if (this.clearIdCallback != null) this.clearIdCallback.clearId(id); }
|
||||
public interface ICleaner {
|
||||
void alloc(int id);
|
||||
void move(int from, int to);
|
||||
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) {
|
||||
if (!MathUtil.isPowerOfTwo(maxNodeCount)) {
|
||||
@@ -153,7 +160,7 @@ public class NodeManager {
|
||||
long pos = sectionResult.position;
|
||||
int nodeId = this.activeSectionMap.get(pos);
|
||||
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();
|
||||
return;
|
||||
}
|
||||
@@ -495,6 +502,10 @@ public class NodeManager {
|
||||
//copy the previous entry to its new location
|
||||
this.nodeData.copyNode(prevChildId, newChildId);
|
||||
|
||||
this.clearAllocId(newChildId);
|
||||
this.clearMoveId(prevChildId, newChildId);
|
||||
this.clearFreeId(prevChildId);
|
||||
|
||||
int prevNodeId = this.activeSectionMap.get(cPos);
|
||||
if ((prevNodeId & NODE_TYPE_MSK) == NODE_TYPE_REQUEST) {
|
||||
throw new IllegalStateException();
|
||||
@@ -503,6 +514,7 @@ public class NodeManager {
|
||||
throw new IllegalStateException("State inconsistency");
|
||||
}
|
||||
this.activeSectionMap.put(cPos, (prevNodeId & NODE_TYPE_MSK) | newChildId);
|
||||
|
||||
//Release the old entry
|
||||
this.nodeData.free(prevChildId);
|
||||
//Need to invalidate the old and the new
|
||||
@@ -722,7 +734,7 @@ public class NodeManager {
|
||||
this.geometryManager.removeSection(geometry);
|
||||
|
||||
this.nodeData.free(nodeId);
|
||||
this.clearId(nodeId);
|
||||
this.clearFreeId(nodeId);
|
||||
this.invalidateNode(nodeId);
|
||||
|
||||
//Unwatch position
|
||||
@@ -756,6 +768,7 @@ public class NodeManager {
|
||||
//Assume that this is always a top node
|
||||
// FIXME: DONT DO THIS
|
||||
this.topLevelNodeIds.add(id);
|
||||
this.clearAllocId(id);
|
||||
}
|
||||
|
||||
private void finishRequest(int requestId, NodeChildRequest request) {
|
||||
@@ -813,13 +826,15 @@ public class NodeManager {
|
||||
this.nodeData.setNodeGeometry(childNodeId, request.getChildMesh(childIdx));
|
||||
//Mark for update
|
||||
this.invalidateNode(childNodeId);
|
||||
this.clearId(childNodeId);//Clear the id
|
||||
//this.clearId(childNodeId);//Clear the id
|
||||
|
||||
//Put in map
|
||||
int pid = this.activeSectionMap.put(childPos, childNodeId|NODE_TYPE_LEAF);
|
||||
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));
|
||||
}
|
||||
|
||||
this.clearAllocId(childNodeId);
|
||||
}
|
||||
//Free request
|
||||
this.childRequests.release(requestId);
|
||||
@@ -912,6 +927,7 @@ public class NodeManager {
|
||||
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));
|
||||
}
|
||||
this.clearAllocId(childId);
|
||||
} else {
|
||||
prevChildId++;
|
||||
|
||||
@@ -920,6 +936,10 @@ public class NodeManager {
|
||||
//Its a previous entry, copy it to its new location
|
||||
this.nodeData.copyNode(prevChildId, childId);
|
||||
|
||||
this.clearAllocId(childId);
|
||||
this.clearMoveId(prevChildId, childId);
|
||||
this.clearFreeId(prevChildId);
|
||||
|
||||
int prevNodeId = this.activeSectionMap.get(pos);
|
||||
if ((prevNodeId&NODE_TYPE_MSK) == NODE_TYPE_REQUEST) {
|
||||
throw new IllegalStateException();
|
||||
@@ -1113,7 +1133,7 @@ public class NodeManager {
|
||||
public void removeNodeGeometry(long pos) {
|
||||
int nodeId = this.activeSectionMap.get(pos);
|
||||
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;
|
||||
}
|
||||
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!");
|
||||
return;
|
||||
}
|
||||
this.clearId(nodeId);
|
||||
//this.clearId(nodeId);
|
||||
|
||||
if (nodeType == NODE_TYPE_INNER) {
|
||||
this.clearGeometryInternal(pos, nodeId);
|
||||
@@ -1157,7 +1177,8 @@ public class NodeManager {
|
||||
throw new IllegalStateException("Parent node must be an inner node");
|
||||
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);
|
||||
if (cPtr != SENTINEL_EMPTY_CHILD_PTR) {
|
||||
if (cPtr == -1) {
|
||||
@@ -1172,8 +1193,11 @@ public class NodeManager {
|
||||
if (cn==-1)
|
||||
throw new IllegalStateException();
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1185,7 +1209,7 @@ public class NodeManager {
|
||||
} else {
|
||||
//Convert to leaf node
|
||||
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);
|
||||
if (old == -1)
|
||||
@@ -1552,9 +1576,9 @@ public class NodeManager {
|
||||
}
|
||||
|
||||
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 traverse from top (root positions) down
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
if (nodes != null) {
|
||||
if (!nodes.containsAll(seenNodes)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!seenNodes.containsAll(nodes)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,13 @@ public final class NodeStore {
|
||||
|
||||
//Writes out a nodes data to the ptr in the compacted/reduced format
|
||||
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);
|
||||
MemoryUtil.memPutInt(ptr, (int) (pos>>32)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (int) pos); ptr += 4;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.cortex.voxy.client.core.rendering.hierachical;
|
||||
|
||||
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.Long2IntFunction;
|
||||
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 final Long2ByteOpenHashMap updateTypes = new Long2ByteOpenHashMap();
|
||||
|
||||
@@ -137,11 +160,14 @@ public class TestNodeManager {
|
||||
public final MemoryGeometryManager geometryManager;
|
||||
public final NodeManager nodeManager;
|
||||
public final Watcher watcher;
|
||||
public final CleanerImp cleaner;
|
||||
|
||||
public TestBase() {
|
||||
this.watcher = new Watcher();
|
||||
this.cleaner = new CleanerImp();
|
||||
this.geometryManager = new MemoryGeometryManager(1<<20, 1<<30);
|
||||
this.nodeManager = new NodeManager(1 << 21, this.geometryManager, this.watcher);
|
||||
this.nodeManager.setClear(this.cleaner);
|
||||
}
|
||||
|
||||
public void putTopPos(long pos) {
|
||||
@@ -208,7 +234,7 @@ public class TestNodeManager {
|
||||
}
|
||||
|
||||
public void verifyIntegrity() {
|
||||
this.nodeManager.verifyIntegrity(this.watcher.updateTypes.keySet());
|
||||
this.nodeManager.verifyIntegrity(this.watcher.updateTypes.keySet(), this.cleaner.active);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ public class BasicSectionGeometryManager extends AbstractSectionGeometryManager
|
||||
this.geometry.downloadRemove(oldMetadata.geometryPtr, buffer ->
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ public class HierarchicalBitSet {
|
||||
}
|
||||
}
|
||||
|
||||
private int endId = -1;
|
||||
|
||||
public HierarchicalBitSet() {
|
||||
this(1<<(6*4));
|
||||
}
|
||||
@@ -55,11 +57,12 @@ public class HierarchicalBitSet {
|
||||
}
|
||||
}
|
||||
this.cnt++;
|
||||
|
||||
this.endId += ret==(this.endId+1)?1:0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void set(int idx) {
|
||||
this.endId += idx==(this.endId+1)?1:0;
|
||||
long dp = this.D[idx>>6] |= 1L<<(idx&0x3f);
|
||||
if (dp==-1) {
|
||||
idx >>= 6;
|
||||
@@ -139,6 +142,13 @@ public class HierarchicalBitSet {
|
||||
long v = this.D[idx>>6];
|
||||
boolean wasSet = (v&(1L<<(idx&0x3f)))!=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));
|
||||
idx >>= 6;
|
||||
this.C[idx>>6] &= ~(1L<<(idx&0x3f));
|
||||
@@ -146,6 +156,7 @@ public class HierarchicalBitSet {
|
||||
this.B[idx>>6] &= ~(1L<<(idx&0x3f));
|
||||
idx >>= 6;
|
||||
this.A &= ~(1L<<(idx&0x3f));
|
||||
|
||||
return wasSet;
|
||||
}
|
||||
|
||||
@@ -162,7 +173,7 @@ public class HierarchicalBitSet {
|
||||
|
||||
|
||||
public int getMaxIndex() {
|
||||
throw new IllegalStateException();
|
||||
return this.endId;
|
||||
}
|
||||
|
||||
|
||||
@@ -172,6 +183,25 @@ public class HierarchicalBitSet {
|
||||
if (h.allocateNext() != 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public class MemoryBuffer extends TrackedObject {
|
||||
}
|
||||
|
||||
public MemoryBuffer copy() {
|
||||
var copy = new MemoryBuffer(false, this.size, size, freeable);
|
||||
var copy = new MemoryBuffer(this.size);
|
||||
this.cpyTo(copy.address);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@@ -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.Mapper;
|
||||
import net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.registry.entry.RegistryEntry;
|
||||
import net.minecraft.util.collection.EmptyPaletteStorage;
|
||||
@@ -16,6 +17,8 @@ import net.minecraft.world.chunk.*;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public class WorldConversionFactory {
|
||||
private static final boolean LITHIUM_INSTALLED = FabricLoader.getInstance().isModLoaded("lithium");
|
||||
|
||||
private static final class Cache {
|
||||
private final int[] biomeCache = new int[4*4*4];
|
||||
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
|
||||
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) {
|
||||
{
|
||||
if (vp instanceof ArrayPalette<BlockState>) {
|
||||
for (int i = 0; i < vp.getSize(); i++) {
|
||||
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);
|
||||
}
|
||||
private static boolean setupLithiumLocalPallet(Palette<BlockState> vp, Reference2IntOpenHashMap<BlockState> blockCache, Mapper mapper, int[] pc) {
|
||||
if (vp instanceof LithiumHashPalette<BlockState>) {
|
||||
for (int i = 0; i < vp.getSize(); i++) {
|
||||
BlockState state = null;
|
||||
int blockId = -1;
|
||||
try { state = vp.get(i); } catch (Exception e) {}
|
||||
if (state != null) {
|
||||
blockId = blockCache.getOrDefault(state, -1);
|
||||
if (blockId == -1) {
|
||||
blockId = mapper.getIdForBlockState(state);
|
||||
blockCache.put(state, blockId);
|
||||
}
|
||||
pc[i] = blockId;
|
||||
}
|
||||
} else if (vp instanceof LithiumHashPalette<BlockState>) {
|
||||
for (int i = 0; i < vp.getSize(); i++) {
|
||||
BlockState state = null;
|
||||
int blockId = -1;
|
||||
try { state = vp.get(i); } catch (Exception e) {}
|
||||
if (state != null) {
|
||||
blockId = blockCache.getOrDefault(state, -1);
|
||||
if (blockId == -1) {
|
||||
blockId = mapper.getIdForBlockState(state);
|
||||
blockCache.put(state, blockId);
|
||||
}
|
||||
pc[i] = blockId;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static void setupLocalPalette(Palette<BlockState> vp, Reference2IntOpenHashMap<BlockState> blockCache, Mapper mapper, int[] pc) {
|
||||
if (vp instanceof ArrayPalette<BlockState>) {
|
||||
for (int i = 0; i < vp.getSize(); i++) {
|
||||
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 {
|
||||
if (vp instanceof BiMapPalette<BlockState> pal) {
|
||||
//var map = pal.map;
|
||||
//TODO: heavily optimize this by reading the map directly
|
||||
pc[i] = blockId;
|
||||
}
|
||||
} else if (vp instanceof BiMapPalette<BlockState> pal) {
|
||||
//var map = pal.map;
|
||||
//TODO: heavily optimize this by reading the map directly
|
||||
|
||||
for (int i = 0; i < vp.getSize(); i++) {
|
||||
BlockState state = null;
|
||||
int blockId = -1;
|
||||
try { state = vp.get(i); } catch (Exception e) {}
|
||||
if (state != null) {
|
||||
blockId = blockCache.getOrDefault(state, -1);
|
||||
if (blockId == -1) {
|
||||
blockId = mapper.getIdForBlockState(state);
|
||||
blockCache.put(state, blockId);
|
||||
}
|
||||
}
|
||||
pc[i] = blockId;
|
||||
for (int i = 0; i < vp.getSize(); i++) {
|
||||
BlockState state = null;
|
||||
int blockId = -1;
|
||||
try { state = vp.get(i); } catch (Exception e) {}
|
||||
if (state != null) {
|
||||
blockId = blockCache.getOrDefault(state, -1);
|
||||
if (blockId == -1) {
|
||||
blockId = mapper.getIdForBlockState(state);
|
||||
blockCache.put(state, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import me.cortex.voxy.common.world.other.Mapper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class ActiveSectionTracker {
|
||||
|
||||
@@ -16,6 +17,7 @@ public class ActiveSectionTracker {
|
||||
//Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane
|
||||
|
||||
private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache;
|
||||
private final ReentrantLock[] locks;
|
||||
private final SectionLoader loader;
|
||||
|
||||
private final int maxLRUSectionPerSlice;
|
||||
@@ -34,10 +36,12 @@ public class ActiveSectionTracker {
|
||||
this.loader = loader;
|
||||
this.loadedSectionCache = new Long2ObjectOpenHashMap[1<<numSlicesBits];
|
||||
this.lruSecondaryCache = new Long2ObjectLinkedOpenHashMap[1<<numSlicesBits];
|
||||
this.locks = new ReentrantLock[1<<numSlicesBits];
|
||||
this.maxLRUSectionPerSlice = (cacheSize+(1<<numSlicesBits)-1)/(1<<numSlicesBits);
|
||||
for (int i = 0; i < this.loadedSectionCache.length; i++) {
|
||||
this.loadedSectionCache[i] = new Long2ObjectOpenHashMap<>(1024);
|
||||
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) {
|
||||
int index = this.getCacheArrayIndex(key);
|
||||
var cache = this.loadedSectionCache[index];
|
||||
final var lock = this.locks[index];
|
||||
VolatileHolder<WorldSection> holder = null;
|
||||
boolean isLoader = false;
|
||||
WorldSection section;
|
||||
synchronized (cache) {
|
||||
|
||||
lock.lock();
|
||||
{
|
||||
holder = cache.get(key);
|
||||
if (holder == null) {
|
||||
holder = new VolatileHolder<>();
|
||||
@@ -61,12 +68,14 @@ public class ActiveSectionTracker {
|
||||
section = holder.obj;
|
||||
if (section != null) {
|
||||
section.acquire();
|
||||
lock.unlock();
|
||||
return section;
|
||||
}
|
||||
if (isLoader) {
|
||||
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 (isLoader) {
|
||||
@@ -107,19 +116,25 @@ public class ActiveSectionTracker {
|
||||
while ((section = holder.obj) == null)
|
||||
Thread.yield();
|
||||
|
||||
synchronized (cache) {
|
||||
//lock.lock();
|
||||
{//Dont think need to lock here
|
||||
if (section.tryAcquire()) {
|
||||
return section;
|
||||
}
|
||||
}
|
||||
//lock.unlock();
|
||||
return this.acquire(key, nullOnEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
void tryUnload(WorldSection section) {
|
||||
int index = this.getCacheArrayIndex(section.key);
|
||||
var cache = this.loadedSectionCache[index];
|
||||
synchronized (cache) {
|
||||
final var cache = this.loadedSectionCache[index];
|
||||
WorldSection prev = null;
|
||||
WorldSection lruEntry = null;
|
||||
final var lock = this.locks[index];
|
||||
lock.lock();
|
||||
{
|
||||
if (section.trySetFreed()) {
|
||||
var cached = cache.remove(section.key);
|
||||
var obj = cached.obj;
|
||||
@@ -129,16 +144,21 @@ public class ActiveSectionTracker {
|
||||
|
||||
//Add section to secondary cache while primary is locked
|
||||
var lruCache = this.lruSecondaryCache[index];
|
||||
var prev = lruCache.put(section.key, section);
|
||||
if (prev != null) {
|
||||
prev._releaseArray();
|
||||
}
|
||||
prev = lruCache.put(section.key, section);
|
||||
//If cache is bigger than its ment to be, remove the least recently used and free it
|
||||
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) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import me.cortex.voxy.common.world.other.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
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_CHILD_EXISTENCE_BIT = 2;
|
||||
@@ -38,7 +38,7 @@ public class WorldEngine {
|
||||
public Mapper getMapper() {return this.mapper;}
|
||||
public boolean isLive() {return this.isLive;}
|
||||
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) {
|
||||
@@ -46,7 +46,7 @@ public class WorldEngine {
|
||||
this.storage = storage;
|
||||
this.mapper = new Mapper(this.storage);
|
||||
//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) {
|
||||
|
||||
@@ -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)
|
||||
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
|
||||
private static final AtomicInteger ARRAY_REUSE_CACHE_COUNT = new AtomicInteger(0);
|
||||
private static final ConcurrentLinkedDeque<long[]> ARRAY_REUSE_CACHE = new ConcurrentLinkedDeque<>();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
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.config.section.SectionStorage;
|
||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||
|
||||
@@ -17,13 +17,19 @@ layout(binding = VISIBILITY_BUFFER_BINDING, std430) restrict buffer VisibilityBu
|
||||
uint[] visibility;
|
||||
};
|
||||
|
||||
layout(location=0) uniform uint visibilityCounter;
|
||||
void main() {
|
||||
uvec4 node = nodes[minVisIds[gl_LocalInvocationID.x]];
|
||||
uint id = minVisIds[gl_LocalInvocationID.x];
|
||||
uvec4 node = nodes[id];
|
||||
uvec2 res = node.xy;
|
||||
if (all(equal(node, uvec4(0)))) {//If its a empty node, TODO: DONT THINK THIS IS ACTUALLY CORRECT
|
||||
res = uvec2(-1);
|
||||
}
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
//#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
|
||||
|
||||
#import <voxy:lod/hierarchical/node.glsl>
|
||||
@@ -54,9 +54,36 @@ void main() {
|
||||
return;
|
||||
}
|
||||
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))
|
||||
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);
|
||||
}
|
||||
@@ -8,9 +8,6 @@ layout(binding = NODE_DATA_BINDING, std430) restrict buffer NodeData {
|
||||
|
||||
//First 2 are joined to be the position
|
||||
|
||||
|
||||
|
||||
|
||||
//All node access and setup into global variables
|
||||
//TODO: maybe make it global vars
|
||||
struct UnpackedNode {
|
||||
@@ -32,7 +29,7 @@ struct UnpackedNode {
|
||||
#define NULL_MESH ((1<<24)-1)
|
||||
#define EMPTY_MESH ((1<<24)-2)
|
||||
|
||||
void unpackNode(out UnpackedNode node, uint nodeId) {
|
||||
uvec4 unpackNode(out UnpackedNode node, uint nodeId) {
|
||||
uvec4 compactedNode = nodes[nodeId];
|
||||
node.nodeId = nodeId;
|
||||
node.lodLevel = compactedNode.x >> 28;
|
||||
@@ -50,6 +47,7 @@ void unpackNode(out UnpackedNode node, uint nodeId) {
|
||||
node.meshPtr = compactedNode.z&0xFFFFFFu;
|
||||
node.childPtr = compactedNode.w&0xFFFFFFu;
|
||||
node.flags = ((compactedNode.z>>24)&0xFFu) | (((compactedNode.w>>24)&0xFFu)<<8);
|
||||
return compactedNode;
|
||||
}
|
||||
|
||||
bool hasMesh(in UnpackedNode node) {
|
||||
|
||||
Reference in New Issue
Block a user