Remove old render stuff
This commit is contained in:
@@ -1,460 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core;
|
|
||||||
|
|
||||||
//Contains the logic to determine what is loaded and at what LoD level, dispatches render changes
|
|
||||||
// also determines what faces are built etc
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
|
||||||
import me.cortex.voxy.client.core.rendering.RenderTracker;
|
|
||||||
import me.cortex.voxy.client.core.util.RingUtil;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
|
|
||||||
//Can use ring logic
|
|
||||||
// i.e. when a player moves the rings of each lod change (how it was doing in the original attempt)
|
|
||||||
// also have it do directional quad culling and rebuild the chunk if needed (this shouldent happen very often) (the reason is to significantly reduce draw calls)
|
|
||||||
// make the rebuild range like +-5 chunks along each axis (that means at higher levels, should only need to rebuild like)
|
|
||||||
// 4 sections or something
|
|
||||||
public class DistanceTracker {
|
|
||||||
private final TransitionRing2D[] loDRings;
|
|
||||||
private final TransitionRing2D[] cacheLoadRings;
|
|
||||||
private final TransitionRing2D[] cacheUnloadRings;
|
|
||||||
private final TransitionRing2D mostOuterNonClampedRing;
|
|
||||||
private final RenderTracker tracker;
|
|
||||||
private final int minYSection;
|
|
||||||
private final int maxYSection;
|
|
||||||
private final int renderDistance;
|
|
||||||
|
|
||||||
public DistanceTracker(RenderTracker tracker, int[] lodRingScales, int renderDistance, int minY, int maxY) {
|
|
||||||
this.loDRings = new TransitionRing2D[lodRingScales.length];
|
|
||||||
this.cacheLoadRings = new TransitionRing2D[lodRingScales.length];
|
|
||||||
this.cacheUnloadRings = new TransitionRing2D[lodRingScales.length];
|
|
||||||
this.tracker = tracker;
|
|
||||||
this.minYSection = minY;
|
|
||||||
this.maxYSection = maxY;
|
|
||||||
this.renderDistance = renderDistance;
|
|
||||||
|
|
||||||
|
|
||||||
boolean wasRdClamped = false;
|
|
||||||
//The rings 0+ start at 64 vanilla rd, no matter what the game is set at, that is if the game is set to 32 rd
|
|
||||||
// there will still be 32 chunks untill the first lod drop
|
|
||||||
// if the game is set to 16, then there will be 48 chunks until the drop
|
|
||||||
for (int i = 0; i < this.loDRings.length; i++) {
|
|
||||||
int scaleP = lodRingScales[i];
|
|
||||||
boolean isTerminatingRing = ((lodRingScales[i]+2)<<(1+i) >= renderDistance)&&renderDistance>0;
|
|
||||||
if (isTerminatingRing) {
|
|
||||||
scaleP = Math.max(renderDistance >> (1+i), 1);
|
|
||||||
wasRdClamped = true;
|
|
||||||
}
|
|
||||||
int scale = scaleP;
|
|
||||||
|
|
||||||
//TODO: FIXME: check that the level shift is right when inc/dec
|
|
||||||
int capRing = i;
|
|
||||||
this.loDRings[i] = new TransitionRing2D((isTerminatingRing?5:6)+i, isTerminatingRing?scale<<1:scale, (x, z) -> {
|
|
||||||
if (isTerminatingRing) {
|
|
||||||
add(capRing, x, z);
|
|
||||||
} else
|
|
||||||
this.dec(capRing+1, x, z);
|
|
||||||
}, (x, z) -> {
|
|
||||||
if (isTerminatingRing) {
|
|
||||||
remove(capRing, x, z);
|
|
||||||
//remove(capRing, (x<<1), (z<<1));
|
|
||||||
} else
|
|
||||||
this.inc(capRing+1, x, z);
|
|
||||||
});
|
|
||||||
|
|
||||||
//TODO:FIXME i think the radius is wrong and (lodRingScales[i]) needs to be (lodRingScales[i]<<1) since the transition ring (the thing above)
|
|
||||||
// acts on LoD level + 1
|
|
||||||
|
|
||||||
//TODO: check this is actually working lmao and make it generate parent level lods on the exit instead of entry so it looks correct when flying backwards
|
|
||||||
if (!isTerminatingRing) {
|
|
||||||
//TODO: COMPLETLY REDO THE CACHING SYSTEM CAUSE THE LOGIC IS COMPLETLY INCORRECT
|
|
||||||
// we want basicly 2 rings offset by an amount such that when a position is near an lod transition point
|
|
||||||
// it will be meshed (both the higher and lower quality lods), enabling semless loading
|
|
||||||
// the issue is when to uncache these methods
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
//TODO: FIX AND FINISH!!!
|
|
||||||
this.cacheLoadRings[i] = new TransitionRing2D(5 + i, (scale << 1) + 2, (x, z) -> {
|
|
||||||
//When entering a cache ring, trigger a mesh op and inject into cache
|
|
||||||
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
|
|
||||||
this.tracker.addCache(capRing, x, y, z);
|
|
||||||
}
|
|
||||||
}, (x, z) -> {
|
|
||||||
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
|
|
||||||
this.tracker.removeCache(capRing, x, y, z);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.cacheUnloadRings[i] = new TransitionRing2D(5 + i, Math.max(1, (scale << 1) - 2), (x, z) -> {
|
|
||||||
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
|
|
||||||
this.tracker.removeCache(capRing, x, y, z);
|
|
||||||
}
|
|
||||||
}, (x, z) -> {
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTerminatingRing) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!wasRdClamped) {
|
|
||||||
this.mostOuterNonClampedRing = new TransitionRing2D(5+this.loDRings.length, Math.max(renderDistance, 2048)>>this.loDRings.length, (x,z)->
|
|
||||||
add(this.loDRings.length, x, z), (x,z)->{
|
|
||||||
if (renderDistance > 0) {
|
|
||||||
remove(this.loDRings.length,x,z);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.mostOuterNonClampedRing = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void inc(int lvl, int x, int z) {
|
|
||||||
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
|
|
||||||
this.tracker.inc(lvl, x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dec(int lvl, int x, int z) {
|
|
||||||
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
|
|
||||||
this.tracker.dec(lvl, x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add(int lvl, int x, int z) {
|
|
||||||
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
|
|
||||||
this.tracker.add(lvl, x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void remove(int lvl, int x, int z) {
|
|
||||||
for (int y = this.minYSection>>lvl; y <= this.maxYSection>>lvl; y++) {
|
|
||||||
this.tracker.remove(lvl, x, y, z);
|
|
||||||
this.tracker.removeCache(lvl, x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//How it works is there are N ring zones (one zone for each lod boundary)
|
|
||||||
// the transition zone is what determines what lods are rendered etc (and it biases higher lod levels cause its easier)
|
|
||||||
// the transition zone is only ever checked when the player moves 1<<(4+lodlvl) blocks, its position is set
|
|
||||||
|
|
||||||
//if the center suddenly changes (say more than 1<<(7+lodlvl) block) then invalidate the entire ring and recompute
|
|
||||||
// the lod sections
|
|
||||||
public void setCenter(int x, int y, int z) {
|
|
||||||
for (var ring : this.cacheLoadRings) {
|
|
||||||
if (ring!=null)
|
|
||||||
ring.update(x, z);
|
|
||||||
}
|
|
||||||
if (this.mostOuterNonClampedRing!=null)
|
|
||||||
this.mostOuterNonClampedRing.update(x, z);
|
|
||||||
|
|
||||||
//Update in reverse order (biggest lod to smallest lod)
|
|
||||||
for (int i = this.loDRings.length-1; -1<i; i-- ) {
|
|
||||||
var ring = this.loDRings[i];
|
|
||||||
if (ring != null)
|
|
||||||
ring.update(x, z);
|
|
||||||
}
|
|
||||||
for (var ring : this.cacheUnloadRings) {
|
|
||||||
if (ring!=null)
|
|
||||||
ring.update(x, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init(int x, int z) {
|
|
||||||
for (var ring : this.cacheLoadRings) {
|
|
||||||
if (ring != null)
|
|
||||||
ring.setCenter(x, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var ring : this.cacheUnloadRings) {
|
|
||||||
if (ring != null)
|
|
||||||
ring.setCenter(x, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var ring : this.loDRings) {
|
|
||||||
if (ring != null)
|
|
||||||
ring.setCenter(x, z);
|
|
||||||
}
|
|
||||||
if (this.mostOuterNonClampedRing!=null)
|
|
||||||
this.mostOuterNonClampedRing.setCenter(x, z);
|
|
||||||
|
|
||||||
var thread = new Thread(()-> {
|
|
||||||
for (var ring : this.cacheLoadRings) {
|
|
||||||
if (ring != null)
|
|
||||||
ring.fill(x, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var ring : this.cacheUnloadRings) {
|
|
||||||
if (ring != null)
|
|
||||||
ring.fill(x, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
//This is an ungodly terrible hack to make the lods load in a semi ok order
|
|
||||||
for (var ring : this.loDRings)
|
|
||||||
if (ring != null)
|
|
||||||
ring.fill(x, z);
|
|
||||||
|
|
||||||
if (this.mostOuterNonClampedRing!=null)
|
|
||||||
this.mostOuterNonClampedRing.fill(x, z);
|
|
||||||
|
|
||||||
for (int i = this.loDRings.length - 1; 0 <= i; i--) {
|
|
||||||
if (this.loDRings[i] != null) {
|
|
||||||
this.loDRings[i].fill(x, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.setName("LoD Ring Initializer");
|
|
||||||
thread.start();
|
|
||||||
//TODO: FIXME: need to destory on shutdown
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: add a new class thing that can track the central axis point so that
|
|
||||||
// geometry can be rebuilt with new flags with correct facing geometry built
|
|
||||||
// (could also make it so that it emits 3x the amount of draw calls, but that seems very bad idea)
|
|
||||||
|
|
||||||
|
|
||||||
private interface Transition2DCallback {
|
|
||||||
void callback(int x, int z);
|
|
||||||
}
|
|
||||||
private static final class TransitionRing2D {
|
|
||||||
private final int triggerRangeSquared;
|
|
||||||
private final int shiftSize;
|
|
||||||
private final Transition2DCallback enter;
|
|
||||||
private final Transition2DCallback exit;
|
|
||||||
private final int[] cornerPoints;
|
|
||||||
private final int radius;
|
|
||||||
|
|
||||||
private int lastUpdateX;
|
|
||||||
private int lastUpdateZ;
|
|
||||||
|
|
||||||
private int currentX;
|
|
||||||
private int currentZ;
|
|
||||||
|
|
||||||
//Note radius is in shiftScale
|
|
||||||
private TransitionRing2D(int shiftSize, int radius, Transition2DCallback onEntry, Transition2DCallback onExit) {
|
|
||||||
this(shiftSize, radius, onEntry, onExit, 0, 0, 0);
|
|
||||||
}
|
|
||||||
private TransitionRing2D(int shiftSize, int radius, Transition2DCallback onEntry, Transition2DCallback onExit, int ix, int iy, int iz) {
|
|
||||||
//trigger just less than every shiftSize scale
|
|
||||||
this.triggerRangeSquared = 1<<((shiftSize<<1) - 1);
|
|
||||||
this.shiftSize = shiftSize;
|
|
||||||
this.enter = onEntry;
|
|
||||||
this.exit = onExit;
|
|
||||||
this.cornerPoints = RingUtil.generatingBoundingCorner2D(radius);
|
|
||||||
this.radius = radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long Prel(int x, int z) {
|
|
||||||
return (Integer.toUnsignedLong(this.currentZ + z)<<32)|Integer.toUnsignedLong(this.currentX + x);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(int x, int z) {
|
|
||||||
long dx = this.lastUpdateX - x;
|
|
||||||
long dz = this.lastUpdateZ - z;
|
|
||||||
long distSquared = dx*dx + dz*dz;
|
|
||||||
if (distSquared < this.triggerRangeSquared) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update the last update position
|
|
||||||
int maxStep = this.triggerRangeSquared/2;
|
|
||||||
this.lastUpdateX += Math.min(maxStep,Math.max(-maxStep, x-this.lastUpdateX));
|
|
||||||
this.lastUpdateZ += Math.min(maxStep,Math.max(-maxStep, z-this.lastUpdateZ));
|
|
||||||
|
|
||||||
//Compute movement if it happened
|
|
||||||
int nx = x>>this.shiftSize;
|
|
||||||
int nz = z>>this.shiftSize;
|
|
||||||
|
|
||||||
if (nx == this.currentX && nz == this.currentZ) {
|
|
||||||
//No movement
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//FIXME: not right, needs to only call load/unload on entry and exit, cause atm its acting like a loaded circle
|
|
||||||
|
|
||||||
Long2IntOpenHashMap ops = new Long2IntOpenHashMap();
|
|
||||||
while (true) {
|
|
||||||
int dir = nz < this.currentZ ? -1 : 1;
|
|
||||||
if (nz != this.currentZ) {
|
|
||||||
for (int corner : this.cornerPoints) {
|
|
||||||
int cx = corner >>> 16;
|
|
||||||
int cz = corner & 0xFFFF;
|
|
||||||
|
|
||||||
ops.addTo(Prel(cx, cz + Math.max(0, dir)), dir);
|
|
||||||
ops.addTo(Prel(cx, -cz + Math.min(0, dir)), -dir);
|
|
||||||
if (cx != 0) {
|
|
||||||
ops.addTo(Prel(-cx, cz + Math.max(0, dir)), dir);
|
|
||||||
ops.addTo(Prel(-cx, -cz + Math.min(0, dir)), -dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentZ += dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
dir = nx < this.currentX ? -1 : 1;
|
|
||||||
if (nx != this.currentX) {
|
|
||||||
for (int corner : this.cornerPoints) {
|
|
||||||
int cx = corner & 0xFFFF;
|
|
||||||
int cz = corner >>> 16;
|
|
||||||
|
|
||||||
ops.addTo(Prel(cx + Math.max(0, dir), cz), dir);
|
|
||||||
ops.addTo(Prel(-cx + Math.min(0, dir), cz), -dir);
|
|
||||||
if (cz != 0) {
|
|
||||||
ops.addTo(Prel(cx + Math.max(0, dir), -cz), dir);
|
|
||||||
ops.addTo(Prel(-cx + Math.min(0, dir), -cz), -dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentX += dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Only break once the coords match
|
|
||||||
if (nx == this.currentX && nz == this.currentZ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ops.forEach((pos,val)->{
|
|
||||||
if (val > 0) {
|
|
||||||
this.enter.callback((int) (long)pos, (int) (pos>>32));
|
|
||||||
}
|
|
||||||
if (val < 0) {
|
|
||||||
this.exit.callback((int) (long)pos, (int) (pos>>32));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ops.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fill(int x, int z) {
|
|
||||||
this.fill(x, z, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fill(int x, int z, Transition2DCallback outsideCallback) {
|
|
||||||
int cx = x>>this.shiftSize;
|
|
||||||
int cz = z>>this.shiftSize;
|
|
||||||
|
|
||||||
int r2 = this.radius*this.radius;
|
|
||||||
for (int a = -this.radius; a <= this.radius; a++) {
|
|
||||||
//IntStream.range(-this.radius, this.radius+1).parallel().forEach(a->{
|
|
||||||
int b = (int) Math.floor(Math.sqrt(r2-(a*a)));
|
|
||||||
for (int c = -b; c <= b; c++) {
|
|
||||||
this.enter.callback(a + cx, c + cz);
|
|
||||||
}
|
|
||||||
if (outsideCallback != null) {
|
|
||||||
for (int c = -this.radius; c < -b; c++) {
|
|
||||||
outsideCallback.callback(a + cx, c + cz);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int c = b+1; c <= this.radius; c++) {
|
|
||||||
outsideCallback.callback(a + cx, c + cz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}//);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCenter(int x, int z) {
|
|
||||||
int cx = x>>this.shiftSize;
|
|
||||||
int cz = z>>this.shiftSize;
|
|
||||||
this.currentX = cx;
|
|
||||||
this.currentZ = cz;
|
|
||||||
this.lastUpdateX = x + (((int)(Math.random()*4))<<(this.shiftSize-4));
|
|
||||||
this.lastUpdateZ = z + (((int)(Math.random()*4))<<(this.shiftSize-4));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
public void update(int x, int z) {
|
|
||||||
int MAX_STEPS_PER_UPDATE = 1;
|
|
||||||
|
|
||||||
|
|
||||||
long dx = this.lastUpdateX - x;
|
|
||||||
long dz = this.lastUpdateZ - z;
|
|
||||||
long distSquared = dx*dx + dz*dz;
|
|
||||||
if (distSquared < this.triggerRangeSquared) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: fixme: this last update needs to be incremented by a delta since
|
|
||||||
|
|
||||||
//Update the last update position
|
|
||||||
int maxStep = this.triggerRangeSquared/2;
|
|
||||||
this.lastUpdateX += Math.min(maxStep,Math.max(-maxStep, x-this.lastUpdateX));
|
|
||||||
this.lastUpdateZ += Math.min(maxStep,Math.max(-maxStep, z-this.lastUpdateZ));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Compute movement if it happened
|
|
||||||
int nx = x>>this.shiftSize;
|
|
||||||
int nz = z>>this.shiftSize;
|
|
||||||
|
|
||||||
if (nx == this.currentX && nz == this.currentZ) {
|
|
||||||
//No movement
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//FIXME: not right, needs to only call load/unload on entry and exit, cause atm its acting like a loaded circle
|
|
||||||
|
|
||||||
Long2IntOpenHashMap ops = new Long2IntOpenHashMap();
|
|
||||||
|
|
||||||
int zcount = MAX_STEPS_PER_UPDATE;
|
|
||||||
int dir = nz<this.currentZ?-1:1;
|
|
||||||
while (nz != this.currentZ) {
|
|
||||||
for (int corner : this.cornerPoints) {
|
|
||||||
int cx = corner>>>16;
|
|
||||||
int cz = corner&0xFFFF;
|
|
||||||
|
|
||||||
ops.addTo(Prel( cx, cz+Math.max(0, dir)), dir);
|
|
||||||
ops.addTo(Prel( cx,-cz+Math.min(0, dir)),-dir);
|
|
||||||
if (cx != 0) {
|
|
||||||
ops.addTo(Prel(-cx, cz+Math.max(0, dir)), dir);
|
|
||||||
ops.addTo(Prel(-cx,-cz+Math.min(0, dir)),-dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//ops.addTo(Prel(0, this.radius+Math.max(0, dir)), dir);
|
|
||||||
//ops.addTo(Prel(0, -this.radius+Math.min(0, dir)), -dir);
|
|
||||||
|
|
||||||
this.currentZ += dir;
|
|
||||||
|
|
||||||
if (--zcount == 0) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int xcount = MAX_STEPS_PER_UPDATE;
|
|
||||||
dir = nx<this.currentX?-1:1;
|
|
||||||
while (nx != this.currentX) {
|
|
||||||
|
|
||||||
for (int corner : this.cornerPoints) {
|
|
||||||
int cx = corner&0xFFFF;
|
|
||||||
int cz = corner>>>16;
|
|
||||||
|
|
||||||
ops.addTo(Prel( cx+Math.max(0, dir), cz), dir);
|
|
||||||
ops.addTo(Prel(-cx+Math.min(0, dir), cz),-dir);
|
|
||||||
if (cz != 0) {
|
|
||||||
ops.addTo(Prel(cx + Math.max(0, dir), -cz), dir);
|
|
||||||
ops.addTo(Prel(-cx + Math.min(0, dir), -cz), -dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentX += dir;
|
|
||||||
|
|
||||||
if (--xcount == 0) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ops.forEach((pos,val)->{
|
|
||||||
if (val > 0) {
|
|
||||||
this.enter.callback((int) (long)pos, (int) (pos>>32));
|
|
||||||
}
|
|
||||||
if (val < 0) {
|
|
||||||
this.exit.callback((int) (long)pos, (int) (pos>>32));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ops.clear();
|
|
||||||
}*/
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
|
||||||
import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractFarWorldRenderer;
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
|
||||||
|
|
||||||
//Tracks active sections, dispatches updates to the build system, everything related to rendering flows through here
|
|
||||||
public class RenderTracker {
|
|
||||||
private final WorldEngine world;
|
|
||||||
private RenderGenerationService renderGen;
|
|
||||||
private final AbstractFarWorldRenderer renderer;
|
|
||||||
private final LongSet[] sets;
|
|
||||||
|
|
||||||
|
|
||||||
public RenderTracker(WorldEngine world, AbstractFarWorldRenderer renderer) {
|
|
||||||
this.world = world;
|
|
||||||
this.renderer = renderer;
|
|
||||||
this.sets = new LongSet[1<<4];
|
|
||||||
for (int i = 0; i < this.sets.length; i++) {
|
|
||||||
this.sets[i] = new LongOpenHashSet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRenderGen(RenderGenerationService renderGen) {
|
|
||||||
this.renderGen = renderGen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long mixStafford13(long seed) {
|
|
||||||
seed = (seed ^ seed >>> 30) * -4658895280553007687L;
|
|
||||||
seed = (seed ^ seed >>> 27) * -7723592293110705685L;
|
|
||||||
return seed ^ seed >>> 31;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LongSet getSet(long key) {
|
|
||||||
return this.sets[(int) (mixStafford13(key) & (this.sets.length-1))];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void put(long key) {
|
|
||||||
var set = this.getSet(key);
|
|
||||||
synchronized (set) {
|
|
||||||
set.add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void remove(long key) {
|
|
||||||
var set = this.getSet(key);
|
|
||||||
synchronized (set) {
|
|
||||||
set.remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean contains(long key) {
|
|
||||||
var set = this.getSet(key);
|
|
||||||
synchronized (set) {
|
|
||||||
return set.contains(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
|
||||||
|
|
||||||
//Adds a lvl 0 section into the world renderer
|
|
||||||
public void addLvl0(int x, int y, int z) {
|
|
||||||
this.put(WorldEngine.getWorldSectionId(0, x, y, z));
|
|
||||||
this.renderGen.enqueueTask(0, x, y, z, this::shouldStillBuild);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Removes a lvl 0 section from the world renderer
|
|
||||||
public void remLvl0(int x, int y, int z) {
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(0, x, y, z));
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(0, x, y, z)));
|
|
||||||
this.renderGen.removeTask(0, x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Increases from lvl-1 to lvl at the coordinates (which are in lvl space)
|
|
||||||
public void inc(int lvl, int x, int y, int z) {
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)));
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1));
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)));
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1));
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)));
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1));
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)));
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1));
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
|
||||||
|
|
||||||
//TODO: make a seperate object to hold the build data and link it with the location in a
|
|
||||||
// concurrent hashmap or something, this is so that e.g. the build data position
|
|
||||||
// can be updated
|
|
||||||
|
|
||||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
|
||||||
this.renderGen.enqueueTask(lvl, x, y, z, this::shouldStillBuild);
|
|
||||||
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1))));
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1)));
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1))));
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1)));
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1))));
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1)));
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1))));
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1)));
|
|
||||||
|
|
||||||
|
|
||||||
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1), (z<<1));
|
|
||||||
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1), (z<<1)+1);
|
|
||||||
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1)+1, (z<<1));
|
|
||||||
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1);
|
|
||||||
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1), (z<<1));
|
|
||||||
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1);
|
|
||||||
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1));
|
|
||||||
this.renderGen.removeTask(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Decreases from lvl to lvl-1 at the coordinates (which are in lvl space)
|
|
||||||
public void dec(int lvl, int x, int y, int z) {
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)));
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1));
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)));
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1));
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)));
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1));
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)));
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1));
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
|
||||||
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl, x, y, z)));
|
|
||||||
this.renderGen.removeTask(lvl, x, y, z);
|
|
||||||
|
|
||||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
|
||||||
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1), (z<<1), this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1), (z<<1)+1, this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1)+1, (z<<1), this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1)+1, (z<<1)+1, this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1), (z<<1), this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1), (z<<1)+1, this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1)+1, (z<<1), this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(lvl - 1, (x<<1)+1, (y<<1)+1, (z<<1)+1, this::shouldStillBuild);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Enqueues a renderTask for a section to cache the result
|
|
||||||
public void addCache(int lvl, int x, int y, int z) {
|
|
||||||
this.renderGen.markCache(lvl, x, y, z);
|
|
||||||
this.renderGen.enqueueTask(lvl, x, y, z, ((lvl1, x1, y1, z1) -> true));//TODO: replace the true identity lambda with a callback check to the render cache
|
|
||||||
}
|
|
||||||
|
|
||||||
//Removes the position from the cache
|
|
||||||
public void removeCache(int lvl, int x, int y, int z) {
|
|
||||||
this.renderGen.unmarkCache(lvl, x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove(int lvl, int x, int y, int z) {
|
|
||||||
this.remove(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
|
||||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl, x, y, z)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(int lvl, int x, int y, int z) {
|
|
||||||
this.put(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
|
||||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
|
||||||
this.renderGen.enqueueTask(lvl, x, y, z, this::shouldStillBuild);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Called by the world engine when a section gets dirtied
|
|
||||||
public void sectionUpdated(WorldSection section) {
|
|
||||||
if (this.contains(section.key)) {
|
|
||||||
//TODO:FIXME: if the section gets updated, that means that its neighbors might need to be updated aswell
|
|
||||||
// (due to block occlusion)
|
|
||||||
|
|
||||||
//TODO: FIXME: REBUILDING THE ENTIRE NEIGHBORS when probably only the internal layout changed is NOT SMART
|
|
||||||
this.renderGen.clearCache(section.lvl, section.x, section.y, section.z);
|
|
||||||
this.renderGen.clearCache(section.lvl, section.x-1, section.y, section.z);
|
|
||||||
this.renderGen.clearCache(section.lvl, section.x+1, section.y, section.z);
|
|
||||||
this.renderGen.clearCache(section.lvl, section.x, section.y, section.z-1);
|
|
||||||
this.renderGen.clearCache(section.lvl, section.x, section.y, section.z+1);
|
|
||||||
//TODO: replace this:: with a class cached lambda ref (cause doing this:: still does a lambda allocation)
|
|
||||||
this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z, this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(section.lvl, section.x-1, section.y, section.z, this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(section.lvl, section.x+1, section.y, section.z, this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z-1, this::shouldStillBuild);
|
|
||||||
this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z+1, this::shouldStillBuild);
|
|
||||||
}
|
|
||||||
//this.renderGen.enqueueTask(section);
|
|
||||||
}
|
|
||||||
|
|
||||||
//called by the RenderGenerationService about built geometry, the RenderTracker checks if it can use the result (e.g. the LoD hasnt changed/still correct etc)
|
|
||||||
// and dispatches it to the renderer
|
|
||||||
// it also batch collects the geometry sections until all the geometry for an operation is collected, then it executes the operation, its removes flickering
|
|
||||||
public void processBuildResult(BuiltSection section) {
|
|
||||||
//Check that we still want the section
|
|
||||||
if (this.contains(section.position)) {
|
|
||||||
this.renderer.enqueueResult(section);
|
|
||||||
} else {
|
|
||||||
section.free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean shouldStillBuild(int lvl, int x, int y, int z) {
|
|
||||||
return this.contains(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.geometry.OLD;
|
|
||||||
|
|
||||||
//NOTE: an idea on how to do it is so that any render section, we _keep_ aquired (yes this will be very memory intensive)
|
|
||||||
// could maybe tosomething else
|
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelFactory;
|
|
||||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.render.Camera;
|
|
||||||
import net.minecraft.client.render.Frustum;
|
|
||||||
import net.minecraft.registry.RegistryKeys;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
import org.joml.FrustumIntersection;
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBMultiDrawIndirect.glMultiDrawElementsIndirect;
|
|
||||||
import static org.lwjgl.opengl.GL30.*;
|
|
||||||
|
|
||||||
//can make it so that register the key of the sections we have rendered, then when a section changes and is registered,
|
|
||||||
// dispatch an update to the render section data builder which then gets consumed by the render system and updates
|
|
||||||
// the rendered data
|
|
||||||
|
|
||||||
//Contains all the logic to render the world and manage gpu memory
|
|
||||||
// processes section load,unload,update render data and renders the world each frame
|
|
||||||
|
|
||||||
|
|
||||||
//Todo: tinker with having the compute shader where each thread is a position to render? maybe idk
|
|
||||||
public abstract class AbstractFarWorldRenderer <T extends Viewport, J extends AbstractGeometryManager> {
|
|
||||||
public static final int STATIC_VAO = glGenVertexArrays();
|
|
||||||
|
|
||||||
protected final GlBuffer uniformBuffer;
|
|
||||||
protected final J geometry;
|
|
||||||
protected final ModelFactory models;
|
|
||||||
protected final GlBuffer lightDataBuffer;
|
|
||||||
|
|
||||||
protected final int maxSections;
|
|
||||||
|
|
||||||
//Current camera base level section position
|
|
||||||
protected int sx;
|
|
||||||
protected int sy;
|
|
||||||
protected int sz;
|
|
||||||
|
|
||||||
protected FrustumIntersection frustum;
|
|
||||||
|
|
||||||
//private final List<T> viewports = new ArrayList<>();
|
|
||||||
|
|
||||||
protected IntArrayList updatedSectionIds;
|
|
||||||
|
|
||||||
private final ConcurrentLinkedDeque<Mapper.StateEntry> blockStateUpdates = new ConcurrentLinkedDeque<>();
|
|
||||||
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeUpdates = new ConcurrentLinkedDeque<>();
|
|
||||||
public AbstractFarWorldRenderer(ModelFactory models, J geometry) {
|
|
||||||
this.maxSections = geometry.getMaxSections();
|
|
||||||
this.uniformBuffer = new GlBuffer(1024);
|
|
||||||
this.lightDataBuffer = new GlBuffer(256*4);//256 of uint
|
|
||||||
this.geometry = geometry;
|
|
||||||
this.models = models;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setupRender(Frustum frustum, Camera camera) {
|
|
||||||
this.frustum = frustum.frustumIntersection;
|
|
||||||
|
|
||||||
this.sx = camera.getBlockPos().getX() >> 5;
|
|
||||||
this.sy = camera.getBlockPos().getY() >> 5;
|
|
||||||
this.sz = camera.getBlockPos().getZ() >> 5;
|
|
||||||
|
|
||||||
//TODO: move this to a render function that is only called
|
|
||||||
// once per frame when using multi viewport mods
|
|
||||||
//it shouldent matter if its called multiple times a frame however, as its synced with fences
|
|
||||||
UploadStream.INSTANCE.tick();
|
|
||||||
DownloadStream.INSTANCE.tick();
|
|
||||||
|
|
||||||
//Update the lightmap
|
|
||||||
{
|
|
||||||
long upload = UploadStream.INSTANCE.upload(this.lightDataBuffer, 0, 256*4);
|
|
||||||
var lmt = MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().texture.getImage();
|
|
||||||
for (int light = 0; light < 256; light++) {
|
|
||||||
int x = light&0xF;
|
|
||||||
int y = ((light>>4)&0xF);
|
|
||||||
int sample = lmt.getColor(x,y);
|
|
||||||
sample = ((sample&0xFF0000)>>16)|(sample&0xFF00)|((sample&0xFF)<<16);
|
|
||||||
MemoryUtil.memPutInt(upload + (((x<<4)|(15-y))*4), sample|(0xFF<<28));//Skylight is inverted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Upload any new geometry
|
|
||||||
this.updatedSectionIds = this.geometry.uploadResults();
|
|
||||||
{
|
|
||||||
boolean didHaveBiomeChange = false;
|
|
||||||
|
|
||||||
//Do any BiomeChanges
|
|
||||||
while (!this.biomeUpdates.isEmpty()) {
|
|
||||||
var update = this.biomeUpdates.pop();
|
|
||||||
var biomeReg = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME);
|
|
||||||
this.models.addBiome(update.id, biomeReg.get(Identifier.of(update.biome)));
|
|
||||||
didHaveBiomeChange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (didHaveBiomeChange) {
|
|
||||||
UploadStream.INSTANCE.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxUpdatesPerFrame = 40;
|
|
||||||
|
|
||||||
//Do any BlockChanges
|
|
||||||
while ((!this.blockStateUpdates.isEmpty()) && (maxUpdatesPerFrame-- > 0)) {
|
|
||||||
var update = this.blockStateUpdates.pop();
|
|
||||||
//this.models.addEntry(update.id, update.state);
|
|
||||||
System.err.println("DEFUNKED: " + update);
|
|
||||||
}
|
|
||||||
//this.models.bakery.renderFaces(Blocks.ROSE_BUSH.getDefaultState(), 1234, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: fix this in a better way than this ungodly hacky stuff, causes clouds to dissapear
|
|
||||||
//RenderSystem.setShaderFogColor(1f, 1f, 1f, 0f);
|
|
||||||
RenderSystem.setShaderFogEnd(99999999);
|
|
||||||
RenderSystem.setShaderFogStart(9999999);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void renderFarAwayOpaque(T viewport);
|
|
||||||
|
|
||||||
public abstract void renderFarAwayTranslucent(T viewport);
|
|
||||||
|
|
||||||
public void enqueueResult(BuiltSection result) {
|
|
||||||
this.geometry.enqueueResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addBlockState(Mapper.StateEntry entry) {
|
|
||||||
this.blockStateUpdates.add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addBiome(Mapper.BiomeEntry entry) {
|
|
||||||
this.biomeUpdates.add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addDebugData(List<String> debug) {
|
|
||||||
debug.add("Geometry buffer usage: " + ((float)Math.round((this.geometry.getGeometryBufferUsage()*100000))/1000) + "%");
|
|
||||||
debug.add("Render Sections: " + this.geometry.getSectionCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
this.geometry.free();
|
|
||||||
this.uniformBuffer.free();
|
|
||||||
this.lightDataBuffer.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final T createViewport() {
|
|
||||||
var viewport = createViewport0();
|
|
||||||
//this.viewports.add(viewport);
|
|
||||||
return viewport;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract T createViewport0();
|
|
||||||
|
|
||||||
public boolean generateMeshlets() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.geometry.OLD;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
|
||||||
|
|
||||||
public abstract class AbstractGeometryManager {
|
|
||||||
protected int sectionCount = 0;
|
|
||||||
protected final int maxSections;
|
|
||||||
protected final ConcurrentLinkedDeque<BuiltSection> buildResults = new ConcurrentLinkedDeque<>();
|
|
||||||
|
|
||||||
protected AbstractGeometryManager(int maxSections) {
|
|
||||||
this.maxSections = maxSections;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract IntArrayList uploadResults();
|
|
||||||
|
|
||||||
int getMaxSections() {
|
|
||||||
return this.maxSections;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enqueueResult(BuiltSection sectionGeometry) {
|
|
||||||
this.buildResults.add(sectionGeometry);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract float getGeometryBufferUsage();
|
|
||||||
|
|
||||||
public int getSectionCount() {
|
|
||||||
return this.sectionCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void free();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.geometry.OLD;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.BufferArena;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
public class DefaultGeometryManager extends AbstractGeometryManager {
|
|
||||||
private static final int SECTION_METADATA_SIZE = 32;
|
|
||||||
private final Long2IntOpenHashMap pos2id = new Long2IntOpenHashMap();
|
|
||||||
private final LongArrayList id2pos = new LongArrayList();
|
|
||||||
private final ObjectArrayList<SectionMeta> sectionMetadata = new ObjectArrayList<>();
|
|
||||||
private final IntArrayList markSectionIds = new IntArrayList();//Section ids to mark as visible (either due to being new, or swapping)
|
|
||||||
|
|
||||||
private final GlBuffer sectionMetaBuffer;
|
|
||||||
private final BufferArena geometryBuffer;
|
|
||||||
|
|
||||||
private final int geometryElementSize;
|
|
||||||
|
|
||||||
public DefaultGeometryManager(long geometryBufferSize, int maxSections) {
|
|
||||||
this(geometryBufferSize, maxSections, 8);//8 is default quad size
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultGeometryManager(long geometryBufferSize, int maxSections, int elementSize) {
|
|
||||||
super(maxSections);
|
|
||||||
this.sectionMetaBuffer = new GlBuffer(((long) maxSections) * SECTION_METADATA_SIZE);
|
|
||||||
this.geometryBuffer = new BufferArena(geometryBufferSize, elementSize);
|
|
||||||
this.pos2id.defaultReturnValue(-1);
|
|
||||||
this.geometryElementSize = elementSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
IntArrayList uploadResults() {
|
|
||||||
this.markSectionIds.clear();
|
|
||||||
while (!this.buildResults.isEmpty()) {
|
|
||||||
var result = this.buildResults.pop();
|
|
||||||
boolean isDelete = result.isEmpty();
|
|
||||||
if (isDelete) {
|
|
||||||
int id = -1;
|
|
||||||
if ((id = this.pos2id.remove(result.position)) != -1) {
|
|
||||||
if (this.id2pos.getLong(id) != result.position) {
|
|
||||||
throw new IllegalStateException("Removed position id not the same requested");
|
|
||||||
}
|
|
||||||
|
|
||||||
var meta = this.sectionMetadata.get(id);
|
|
||||||
this.freeMeta(meta);
|
|
||||||
|
|
||||||
|
|
||||||
this.sectionCount--;
|
|
||||||
if (id == this.sectionCount) {
|
|
||||||
//if we are at the end of the array dont have to do anything (maybe just upload a blank data, just to be sure)
|
|
||||||
|
|
||||||
//Remove the last element
|
|
||||||
this.sectionMetadata.remove(id);
|
|
||||||
this.id2pos.removeLong(id);
|
|
||||||
} else {
|
|
||||||
long swapLodPos = this.id2pos.getLong(this.sectionCount);
|
|
||||||
this.pos2id.put(swapLodPos, id);
|
|
||||||
this.id2pos.set(id, swapLodPos);
|
|
||||||
//Remove from the lists
|
|
||||||
this.id2pos.removeLong(this.sectionCount);
|
|
||||||
var swapMeta = this.sectionMetadata.remove(this.sectionCount);
|
|
||||||
this.sectionMetadata.set(id, swapMeta);
|
|
||||||
if (swapMeta.position != swapLodPos) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long) SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
|
|
||||||
swapMeta.writeMetadata(ptr);
|
|
||||||
this.markSectionIds.add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int id = -1;
|
|
||||||
if ((id = this.pos2id.get(result.position)) != -1) {
|
|
||||||
//Update the existing data
|
|
||||||
var meta = this.sectionMetadata.get(id);
|
|
||||||
if (meta.position != result.position) {
|
|
||||||
throw new IllegalStateException("Meta position != result position");
|
|
||||||
}
|
|
||||||
//Delete the old data
|
|
||||||
this.freeMeta(meta);
|
|
||||||
|
|
||||||
//Create the new meta
|
|
||||||
meta = this.createMeta(result);
|
|
||||||
if (meta == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.sectionMetadata.set(id, meta);
|
|
||||||
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long)SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
|
|
||||||
meta.writeMetadata(ptr);
|
|
||||||
} else {
|
|
||||||
//Create the new meta
|
|
||||||
var meta = this.createMeta(result);
|
|
||||||
if (meta == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add to the end of the array
|
|
||||||
id = this.sectionCount++;
|
|
||||||
this.pos2id.put(result.position, id);
|
|
||||||
this.id2pos.add(result.position);
|
|
||||||
|
|
||||||
this.sectionMetadata.add(meta);
|
|
||||||
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long)SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
|
|
||||||
meta.writeMetadata(ptr);
|
|
||||||
this.markSectionIds.add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Assert some invarients
|
|
||||||
if (this.id2pos.size() != this.sectionCount || this.sectionCount != this.pos2id.size()) {
|
|
||||||
throw new IllegalStateException("Invariants broken");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.free();
|
|
||||||
}
|
|
||||||
return this.markSectionIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void free() {
|
|
||||||
while (!this.buildResults.isEmpty()) {
|
|
||||||
this.buildResults.pop().free();
|
|
||||||
}
|
|
||||||
this.sectionMetaBuffer.free();
|
|
||||||
this.geometryBuffer.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int geometryId() {
|
|
||||||
return this.geometryBuffer.id();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int metaId() {
|
|
||||||
return this.sectionMetaBuffer.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getGeometryBufferUsage() {
|
|
||||||
return this.geometryBuffer.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//=========================================================================================================================================================================================
|
|
||||||
//=========================================================================================================================================================================================
|
|
||||||
//=========================================================================================================================================================================================
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: pack the offsets of each axis so that implicit face culling can work
|
|
||||||
//Note! the opaquePreDataCount and translucentPreDataCount are never writen to the meta buffer, as they are indexed in reverse relative to the base opaque and translucent geometry
|
|
||||||
protected record SectionMeta(long position, int aabb, int geometryPtr, int size, int[] offsets) {
|
|
||||||
public void writeMetadata(long ptr) {
|
|
||||||
//THIS IS DUE TO ENDIANNESS and that we are splitting a long into 2 ints
|
|
||||||
MemoryUtil.memPutInt(ptr, (int) (this.position>>32)); ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(ptr, (int) this.position); ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(ptr, (int) this.aabb); ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(ptr, this.geometryPtr + this.offsets[0]); ptr += 4;
|
|
||||||
|
|
||||||
MemoryUtil.memPutInt(ptr, (this.offsets[1]-this.offsets[0])|((this.offsets[2]-this.offsets[1])<<16)); ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(ptr, (this.offsets[3]-this.offsets[2])|((this.offsets[4]-this.offsets[3])<<16)); ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(ptr, (this.offsets[5]-this.offsets[4])|((this.offsets[6]-this.offsets[5])<<16)); ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(ptr, (this.offsets[7]-this.offsets[6])|((this.size -this.offsets[7])<<16)); ptr += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SectionMeta createMeta(BuiltSection geometry) {
|
|
||||||
int geometryPtr = (int) this.geometryBuffer.upload(geometry.geometryBuffer);
|
|
||||||
if (geometryPtr == -1) {
|
|
||||||
String msg = "Buffer arena out of memory, please increase it in settings or decrease LoD quality";
|
|
||||||
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg));
|
|
||||||
System.err.println(msg);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new SectionMeta(geometry.position, geometry.aabb, geometryPtr, (int) (geometry.geometryBuffer.size/this.geometryElementSize), geometry.offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void freeMeta(SectionMeta meta) {
|
|
||||||
if (meta.geometryPtr != -1) {
|
|
||||||
this.geometryBuffer.free(meta.geometryPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.geometry.OLD;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelFactory;
|
|
||||||
import me.cortex.voxy.client.core.rendering.SharedIndexBuffer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
|
||||||
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
|
|
||||||
import net.minecraft.client.render.RenderLayer;
|
|
||||||
import org.joml.Matrix4f;
|
|
||||||
import org.joml.Vector3f;
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
|
|
||||||
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
|
|
||||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
|
|
||||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_SHORT;
|
|
||||||
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
|
||||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
|
||||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
|
||||||
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
|
|
||||||
import static org.lwjgl.opengl.GL42.*;
|
|
||||||
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
|
|
||||||
import static org.lwjgl.opengl.GL43.*;
|
|
||||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
|
||||||
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
|
|
||||||
import static org.lwjgl.opengl.GL45.glClearNamedBufferData;
|
|
||||||
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
|
|
||||||
|
|
||||||
public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer<Gl46Viewport, DefaultGeometryManager> {
|
|
||||||
private final Shader commandGen = Shader.make()
|
|
||||||
.add(ShaderType.COMPUTE, "voxy:lod/gl46/cmdgen.comp")
|
|
||||||
.compile();
|
|
||||||
|
|
||||||
private final Shader lodShader = Shader.make()
|
|
||||||
.add(ShaderType.VERTEX, "voxy:lod/gl46/quads2.vert")
|
|
||||||
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/quads.frag")
|
|
||||||
.compile();
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: Note the cull shader needs a different element array since its rastering cubes not quads
|
|
||||||
private final Shader cullShader = Shader.make()
|
|
||||||
.add(ShaderType.VERTEX, "voxy:lod/gl46/cull/raster.vert")
|
|
||||||
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/cull/raster.frag")
|
|
||||||
.compile();
|
|
||||||
|
|
||||||
private final GlBuffer glCommandBuffer;
|
|
||||||
private final GlBuffer glCommandCountBuffer;
|
|
||||||
|
|
||||||
public Gl46FarWorldRenderer(ModelFactory modelManager, int geometryBuffer, int maxSections) {
|
|
||||||
super(modelManager, new DefaultGeometryManager(geometryBuffer*8L, maxSections));
|
|
||||||
this.glCommandBuffer = new GlBuffer(maxSections*5L*4 * 6);
|
|
||||||
this.glCommandCountBuffer = new GlBuffer(4*2);
|
|
||||||
nglClearNamedBufferData(this.glCommandBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void bindResources(Gl46Viewport viewport) {
|
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.glCommandBuffer.id);
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.glCommandCountBuffer.id);
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometry.metaId());
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, viewport.visibilityBuffer.id);
|
|
||||||
//glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.models.getBufferId());
|
|
||||||
//glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.models.getColourBufferId());
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, this.lightDataBuffer.id);//Lighting LUT
|
|
||||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.glCommandBuffer.id);
|
|
||||||
glBindBuffer(GL_PARAMETER_BUFFER_ARB, this.glCommandCountBuffer.id);
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
|
|
||||||
|
|
||||||
//Bind the texture atlas
|
|
||||||
//glBindSampler(0, this.models.getSamplerId());
|
|
||||||
//glBindTextureUnit(0, this.models.getTextureId());
|
|
||||||
}
|
|
||||||
|
|
||||||
//FIXME: dont do something like this as it breaks multiviewport mods
|
|
||||||
// the issue is that voxy expects the counter to be incremented by one each frame (the compute shader generating the commands)
|
|
||||||
// checks `frameId - 1`, this means in a multiviewport, effectivly only the stuff that was marked visible by the last viewport
|
|
||||||
// would be visible in the current viewport (if there are 2 viewports)
|
|
||||||
|
|
||||||
//To fix the issue, need to make a viewport api, that independently tracks the frameId and has its own glVisibilityBuffer buffer
|
|
||||||
private void updateUniformBuffer(Gl46Viewport viewport) {
|
|
||||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
|
|
||||||
|
|
||||||
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
|
|
||||||
var innerTranslation = new Vector3f((float) (viewport.cameraX-(this.sx<<5)), (float) (viewport.cameraY-(this.sy<<5)), (float) (viewport.cameraZ-(this.sz<<5)));
|
|
||||||
mat.translate(-innerTranslation.x, -innerTranslation.y, -innerTranslation.z);
|
|
||||||
mat.getToAddress(ptr); ptr += 4*4*4;
|
|
||||||
MemoryUtil.memPutInt(ptr, this.sx); ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(ptr, this.sy); ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(ptr, this.sz); ptr += 4;
|
|
||||||
MemoryUtil.memPutInt(ptr, this.geometry.getSectionCount()); ptr += 4;
|
|
||||||
var planes = ((AccessFrustumIntersection)this.frustum).getPlanes();
|
|
||||||
for (var plane : planes) {
|
|
||||||
plane.getToAddress(ptr); ptr += 4*4;
|
|
||||||
}
|
|
||||||
innerTranslation.getToAddress(ptr); ptr += 4*3;
|
|
||||||
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderFarAwayOpaque(Gl46Viewport viewport) {
|
|
||||||
if (this.geometry.getSectionCount() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
{//Mark all of the updated sections as being visible from last frame
|
|
||||||
for (int id : this.updatedSectionIds) {
|
|
||||||
long ptr = UploadStream.INSTANCE.upload(viewport.visibilityBuffer, id * 4L, 4);
|
|
||||||
MemoryUtil.memPutInt(ptr, viewport.frameId - 1);//(visible from last frame)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
|
|
||||||
|
|
||||||
//this.models.bakery.renderFaces(Blocks.WATER.getDefaultState().with(FluidBlock.LEVEL, 1), 1234, true);
|
|
||||||
//this.models.bakery.renderFaces(Blocks.CHEST.getDefaultState(), 1234, false);
|
|
||||||
|
|
||||||
|
|
||||||
RenderLayer.getCutoutMipped().startDrawing();
|
|
||||||
//RenderSystem.enableBlend();
|
|
||||||
//RenderSystem.defaultBlendFunc();
|
|
||||||
|
|
||||||
this.updateUniformBuffer(viewport);
|
|
||||||
UploadStream.INSTANCE.commit();
|
|
||||||
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
|
|
||||||
|
|
||||||
nglClearNamedBufferData(this.glCommandCountBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
|
|
||||||
this.commandGen.bind();
|
|
||||||
this.bindResources(viewport);
|
|
||||||
glDispatchCompute((this.geometry.getSectionCount()+127)/128, 1, 1);
|
|
||||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_UNIFORM_BARRIER_BIT);
|
|
||||||
|
|
||||||
this.lodShader.bind();
|
|
||||||
this.bindResources(viewport);
|
|
||||||
glDisable(GL_CULL_FACE);
|
|
||||||
//glPointSize(10);
|
|
||||||
//TODO: replace glMultiDrawElementsIndirectCountARB with glMultiDrawElementsIndirect on intel gpus, since it performs so much better
|
|
||||||
//glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, drawCnt, 0);
|
|
||||||
//glLineWidth(2);
|
|
||||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 0, (int) (this.geometry.getSectionCount()*4.4), 0);
|
|
||||||
glEnable(GL_CULL_FACE);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
DownloadStream.INSTANCE.download(this.glCommandCountBuffer, 0, 4, (ptr, siz) -> {
|
|
||||||
int cnt = MemoryUtil.memGetInt(ptr);
|
|
||||||
drawCnt = cnt;
|
|
||||||
});
|
|
||||||
DownloadStream.INSTANCE.commit();
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
|
||||||
|
|
||||||
this.cullShader.bind();
|
|
||||||
this.bindResources(viewport);
|
|
||||||
|
|
||||||
glColorMask(false, false, false, false);
|
|
||||||
glDepthMask(false);
|
|
||||||
|
|
||||||
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 16) * 6 * 2, this.geometry.getSectionCount());
|
|
||||||
|
|
||||||
glDepthMask(true);
|
|
||||||
glColorMask(true, true, true, true);
|
|
||||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: need to do temporal rasterization here
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glBindSampler(0, 0);
|
|
||||||
glBindTextureUnit(0, 0);
|
|
||||||
RenderLayer.getCutoutMipped().endDrawing();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFarAwayTranslucent(Gl46Viewport viewport) {
|
|
||||||
RenderLayer.getTranslucent().startDrawing();
|
|
||||||
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
|
|
||||||
glDisable(GL_CULL_FACE);
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
|
|
||||||
//TODO: maybe change this so the alpha isnt applied in the same way or something?? since atm the texture bakery uses a very hacky
|
|
||||||
// blend equation to make it avoid double applying translucency
|
|
||||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
||||||
|
|
||||||
|
|
||||||
//glBindSampler(0, this.models.getSamplerId());
|
|
||||||
//glBindTextureUnit(0, this.models.getTextureId());
|
|
||||||
|
|
||||||
//RenderSystem.blendFunc(GlStateManager.SrcFactor.ONE, GlStateManager.DstFactor.ONE);
|
|
||||||
this.lodShader.bind();
|
|
||||||
this.bindResources(viewport);
|
|
||||||
|
|
||||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 400_000 * 4 * 5, 4, this.geometry.getSectionCount(), 0);
|
|
||||||
|
|
||||||
glEnable(GL_CULL_FACE);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
|
|
||||||
glBindSampler(0, 0);
|
|
||||||
glBindTextureUnit(0, 0);
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
|
|
||||||
RenderLayer.getTranslucent().endDrawing();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Gl46Viewport createViewport0() {
|
|
||||||
return new Gl46Viewport(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
super.shutdown();
|
|
||||||
this.commandGen.free();
|
|
||||||
this.lodShader.free();
|
|
||||||
this.cullShader.free();
|
|
||||||
this.glCommandBuffer.free();
|
|
||||||
this.glCommandCountBuffer.free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.geometry.OLD;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelFactory;
|
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
|
||||||
import me.cortex.voxy.client.core.rendering.hierarchical.DebugRenderer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.hierarchical.HierarchicalOcclusionRenderer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.hierarchical.INodeInteractor;
|
|
||||||
import me.cortex.voxy.client.core.rendering.hierarchical.MeshManager;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.render.Camera;
|
|
||||||
import net.minecraft.client.render.Frustum;
|
|
||||||
import net.minecraft.registry.RegistryKeys;
|
|
||||||
import net.minecraft.util.Identifier;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
|
|
||||||
import static org.lwjgl.opengl.GL40.glDrawElementsIndirect;
|
|
||||||
import static org.lwjgl.opengl.GL43.*;
|
|
||||||
|
|
||||||
public class Gl46HierarchicalRenderer {
|
|
||||||
private final HierarchicalOcclusionRenderer sectionSelector;
|
|
||||||
private final MeshManager meshManager = new MeshManager();
|
|
||||||
|
|
||||||
private final List<String> printfQueue = new ArrayList<>();
|
|
||||||
private final PrintfInjector printf = new PrintfInjector(100000, 10, line->{
|
|
||||||
if (line.startsWith("LOG")) {
|
|
||||||
System.err.println(line);
|
|
||||||
}
|
|
||||||
this.printfQueue.add(line);
|
|
||||||
}, this.printfQueue::clear);
|
|
||||||
|
|
||||||
private final GlBuffer renderSections = new GlBuffer(100_000 * 4 + 4).zero();
|
|
||||||
private final GlBuffer debugNodeQueue = new GlBuffer(1000000*4+4).zero();
|
|
||||||
|
|
||||||
|
|
||||||
private final DebugRenderer debugRenderer = new DebugRenderer();
|
|
||||||
|
|
||||||
private final ConcurrentLinkedDeque<Mapper.StateEntry> blockStateUpdates = new ConcurrentLinkedDeque<>();
|
|
||||||
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeUpdates = new ConcurrentLinkedDeque<>();
|
|
||||||
|
|
||||||
protected final ConcurrentLinkedDeque<BuiltSection> buildResults = new ConcurrentLinkedDeque<>();
|
|
||||||
|
|
||||||
private final ModelFactory modelManager;
|
|
||||||
private RenderGenerationService sectionGenerationService;
|
|
||||||
private Consumer<BuiltSection> resultConsumer;
|
|
||||||
|
|
||||||
public Gl46HierarchicalRenderer(ModelFactory model) {
|
|
||||||
this.modelManager = model;
|
|
||||||
|
|
||||||
this.sectionSelector = new HierarchicalOcclusionRenderer(new INodeInteractor() {
|
|
||||||
|
|
||||||
public void watchUpdates(long pos) {
|
|
||||||
//System.err.println("Watch: " + pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void unwatchUpdates(long pos) {
|
|
||||||
//System.err.println("Unwatch: " + pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void requestMesh(long pos) {
|
|
||||||
Gl46HierarchicalRenderer.this.sectionGenerationService.enqueueTask(
|
|
||||||
WorldEngine.getLevel(pos),
|
|
||||||
WorldEngine.getX(pos),
|
|
||||||
WorldEngine.getY(pos),
|
|
||||||
WorldEngine.getZ(pos)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setMeshUpdateCallback(Consumer<BuiltSection> mesh) {
|
|
||||||
Gl46HierarchicalRenderer.this.resultConsumer = mesh;
|
|
||||||
}
|
|
||||||
}, this.meshManager, this.printf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setupRender(Frustum frustum, Camera camera) {
|
|
||||||
{//Tick upload and download queues
|
|
||||||
UploadStream.INSTANCE.tick();
|
|
||||||
DownloadStream.INSTANCE.tick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void renderFarAwayOpaque(Gl46HierarchicalViewport viewport) {
|
|
||||||
//Process all the build results
|
|
||||||
while (!this.buildResults.isEmpty()) {
|
|
||||||
this.resultConsumer.accept(this.buildResults.pop());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Render terrain from previous frame (renderSections)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (true) {//Run the hierarchical selector over the buffer to generate the set of render sections
|
|
||||||
var i = new int[1];
|
|
||||||
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, i);
|
|
||||||
this.sectionSelector.doHierarchicalTraversalSelection(viewport, i[0], this.renderSections, this.debugNodeQueue);
|
|
||||||
|
|
||||||
this.debugRenderer.render(viewport, this.sectionSelector.getNodeDataBuffer(), this.debugNodeQueue);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.printf.download();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void renderFarAwayTranslucent(Gl46HierarchicalViewport viewport) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addDebugData(List<String> debug) {
|
|
||||||
debug.add("Printf Queue: ");
|
|
||||||
debug.addAll(this.printfQueue);
|
|
||||||
this.printfQueue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void addBlockState(Mapper.StateEntry stateEntry) {
|
|
||||||
this.blockStateUpdates.add(stateEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addBiome(Mapper.BiomeEntry biomeEntry) {
|
|
||||||
this.biomeUpdates.add(biomeEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void processBuildResult(BuiltSection section) {
|
|
||||||
this.buildResults.add(section);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initPosition(int X, int Z) {
|
|
||||||
for (int x = -10; x <= 10; x++) {
|
|
||||||
for (int z = -10; z <= 10; z++) {
|
|
||||||
for (int y = -1; y <= 0; y++) {
|
|
||||||
long pos = WorldEngine.getWorldSectionId(4, x,y,z);
|
|
||||||
this.sectionSelector.nodeManager.insertTopLevelNode(pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public boolean generateMeshlets() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRenderGen(RenderGenerationService renderService) {
|
|
||||||
this.sectionGenerationService = renderService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Gl46HierarchicalViewport createViewport() {
|
|
||||||
return new Gl46HierarchicalViewport(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
this.meshManager.free();
|
|
||||||
this.sectionSelector.free();
|
|
||||||
this.printf.free();
|
|
||||||
this.debugRenderer.free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.geometry.OLD;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
|
||||||
|
|
||||||
public class Gl46HierarchicalViewport extends Viewport<Gl46HierarchicalViewport> {
|
|
||||||
public Gl46HierarchicalViewport(Gl46HierarchicalRenderer renderer) {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void delete0() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.geometry.OLD;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
|
||||||
import me.cortex.voxy.client.core.rendering.Viewport;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
|
|
||||||
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
|
|
||||||
|
|
||||||
public class Gl46Viewport extends Viewport<Gl46Viewport> {
|
|
||||||
GlBuffer visibilityBuffer;
|
|
||||||
public Gl46Viewport(Gl46FarWorldRenderer renderer) {
|
|
||||||
this.visibilityBuffer = new GlBuffer(renderer.maxSections*4L).zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void delete0() {
|
|
||||||
this.visibilityBuffer.free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,9 @@ package me.cortex.voxy.client.core.rendering.hierarchical;
|
|||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractFarWorldRenderer;
|
import me.cortex.voxy.client.core.rendering.RenderService;
|
||||||
import me.cortex.voxy.client.core.rendering.geometry.OLD.Gl46HierarchicalViewport;
|
|
||||||
import me.cortex.voxy.client.core.rendering.SharedIndexBuffer;
|
import me.cortex.voxy.client.core.rendering.SharedIndexBuffer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.util.math.MathHelper;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
@@ -36,7 +36,7 @@ public class DebugRenderer {
|
|||||||
private final GlBuffer uniformBuffer = new GlBuffer(1024).zero();
|
private final GlBuffer uniformBuffer = new GlBuffer(1024).zero();
|
||||||
private final GlBuffer drawBuffer = new GlBuffer(1024).zero();
|
private final GlBuffer drawBuffer = new GlBuffer(1024).zero();
|
||||||
|
|
||||||
private void uploadUniform(Gl46HierarchicalViewport viewport) {
|
private void uploadUniform(Viewport<?> viewport) {
|
||||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
|
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
|
||||||
int sx = MathHelper.floor(viewport.cameraX)>>5;
|
int sx = MathHelper.floor(viewport.cameraX)>>5;
|
||||||
int sy = MathHelper.floor(viewport.cameraY)>>5;
|
int sy = MathHelper.floor(viewport.cameraY)>>5;
|
||||||
@@ -55,7 +55,7 @@ public class DebugRenderer {
|
|||||||
MemoryUtil.memPutInt(ptr, viewport.height); ptr += 4;
|
MemoryUtil.memPutInt(ptr, viewport.height); ptr += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(Gl46HierarchicalViewport viewport, GlBuffer nodeData, GlBuffer nodeList) {
|
public void render(Viewport<?> viewport, GlBuffer nodeData, GlBuffer nodeList) {
|
||||||
this.uploadUniform(viewport);
|
this.uploadUniform(viewport);
|
||||||
UploadStream.INSTANCE.commit();
|
UploadStream.INSTANCE.commit();
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public class DebugRenderer {
|
|||||||
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
this.debugShader.bind();
|
this.debugShader.bind();
|
||||||
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
|
glBindVertexArray(RenderService.STATIC_VAO);
|
||||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.drawBuffer.id);
|
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.drawBuffer.id);
|
||||||
GL15.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BYTE.id());
|
GL15.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BYTE.id());
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import me.cortex.voxy.client.core.gl.GlBuffer;
|
|||||||
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
|
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
import me.cortex.voxy.client.core.rendering.geometry.OLD.Gl46HierarchicalViewport;
|
import me.cortex.voxy.client.core.rendering.Viewport;
|
||||||
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.util.math.MathHelper;
|
||||||
@@ -46,7 +46,8 @@ public class HierarchicalOcclusionRenderer {
|
|||||||
.compile();
|
.compile();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uploadUniform(Gl46HierarchicalViewport viewport) {
|
|
||||||
|
private void uploadUniform(Viewport<?> viewport) {
|
||||||
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
|
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 1024);
|
||||||
int sx = MathHelper.floor(viewport.cameraX)>>5;
|
int sx = MathHelper.floor(viewport.cameraX)>>5;
|
||||||
int sy = MathHelper.floor(viewport.cameraY)>>5;
|
int sy = MathHelper.floor(viewport.cameraY)>>5;
|
||||||
@@ -71,7 +72,7 @@ public class HierarchicalOcclusionRenderer {
|
|||||||
MemoryUtil.memPutFloat(ptr, 64*64); ptr += 4;
|
MemoryUtil.memPutFloat(ptr, 64*64); ptr += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doHierarchicalTraversalSelection(Gl46HierarchicalViewport viewport, int depthBuffer, GlBuffer renderSelectionResult, GlBuffer debugNodeOutput) {
|
public void doHierarchicalTraversalSelection(Viewport<?> viewport, int depthBuffer, GlBuffer renderSelectionResult, GlBuffer debugNodeOutput) {
|
||||||
this.uploadUniform(viewport);
|
this.uploadUniform(viewport);
|
||||||
this.nodeManager.upload();
|
this.nodeManager.upload();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
|||||||
import me.cortex.voxy.client.core.gl.GlTexture;
|
import me.cortex.voxy.client.core.gl.GlTexture;
|
||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractFarWorldRenderer;
|
import me.cortex.voxy.client.core.rendering.RenderService;
|
||||||
import org.lwjgl.opengl.GL11;
|
import org.lwjgl.opengl.GL11;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
|
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
|
||||||
@@ -67,7 +67,7 @@ public class HiZBuffer {
|
|||||||
}
|
}
|
||||||
this.alloc(width, height);
|
this.alloc(width, height);
|
||||||
}
|
}
|
||||||
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
|
glBindVertexArray(RenderService.STATIC_VAO);
|
||||||
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
|
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
|
||||||
this.hiz.bind();
|
this.hiz.bind();
|
||||||
this.fb.bind(GL_DEPTH_ATTACHMENT, this.texture, 0).verify();
|
this.fb.bind(GL_DEPTH_ATTACHMENT, this.texture, 0).verify();
|
||||||
|
|||||||
Reference in New Issue
Block a user