Stuff kinda works
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)];
|
||||||
|
|||||||
@@ -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, ()->{
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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<>();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user