0.1.4, added render distance slider, disabled caching until better solution can be found

This commit is contained in:
mcrcortex
2024-02-22 16:21:09 +10:00
parent 59a3c81f37
commit 8cd0d418b5
9 changed files with 133 additions and 37 deletions

View File

@@ -91,11 +91,11 @@ public class VoxyConfigScreenFactory implements ModMenuApi {
.setDefaultValue(DEFAULT.maxSections)
.build());
//category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.renderDistance"), config.maxSections, 16, 2048)
// .setTooltip(Text.translatable("voxy.config.general.renderDistance.tooltip"))
// .setSaveConsumer(val -> config.renderDistance = val)
// .setDefaultValue(DEFAULT.renderDistance)
// .build());
category.addEntry(entryBuilder.startIntField(Text.translatable("voxy.config.general.renderDistance"), config.renderDistance)
.setTooltip(Text.translatable("voxy.config.general.renderDistance.tooltip"))
.setSaveConsumer(val -> config.renderDistance = val)
.setDefaultValue(DEFAULT.renderDistance)
.build());
//category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.compression"), config.savingCompressionLevel, 1, 21)
// .setTooltip(Text.translatable("voxy.config.general.compression.tooltip"))

View File

@@ -8,8 +8,6 @@ import me.cortex.voxy.client.core.rendering.RenderTracker;
import me.cortex.voxy.client.core.util.RingUtil;
import net.minecraft.client.MinecraftClient;
import java.util.stream.IntStream;
//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)
@@ -19,12 +17,13 @@ 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 cacheLoadDistance, int cacheUnloadDistance) {
public DistanceTracker(RenderTracker tracker, int[] lodRingScales, int renderDistance, int cacheDistance) {
this.loDRings = new TransitionRing2D[lodRingScales.length];
this.cacheLoadRings = new TransitionRing2D[lodRingScales.length];
this.cacheUnloadRings = new TransitionRing2D[lodRingScales.length];
@@ -34,30 +33,87 @@ public class DistanceTracker {
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(6+i, lodRingScales[i], (x, z) -> this.dec(capRing+1, x, z), (x, z) -> this.inc(capRing+1, x, z));
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
this.cacheLoadRings[i] = new TransitionRing2D(5+i, (lodRingScales[i]<<1) + cacheLoadDistance, (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) -> {});
this.cacheUnloadRings[i] = new TransitionRing2D(5+i, (lodRingScales[i]<<1) + cacheUnloadDistance, (x, z) -> {}, (x, z) -> {
//When exiting the cache unload ring, tell the cache to dump whatever mesh it has cached and not add any mesh from that position
for (int y = this.minYSection>>capRing; y <= this.maxYSection>>capRing; y++) {
this.tracker.removeCache(capRing, x, y, z);
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
/*
this.cacheLoadRings[i] = new TransitionRing2D(5 + i, (scale << 1) + cacheDistance, (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) -> {
int shift = capRing+1;
if (shift <= this.loDRings.length) {
for (int y = this.minYSection >> shift; y <= this.maxYSection >> shift; y++) {
this.tracker.removeCache(shift, x>>1, y, z>>1);
}
}
});
this.cacheUnloadRings[i] = new TransitionRing2D(5 + i, Math.max(1, (scale << 1) + cacheDistance), (x, z) -> {
int shift = capRing+1;
if (shift <= this.loDRings.length) {
for (int y = this.minYSection >> shift; y <= this.maxYSection >> shift; y++) {
this.tracker.addCache(shift, x>>1, y, z>>1);
}
}
}, (x, z) -> {
//When exiting the cache unload ring, tell the cache to dump whatever mesh it has cached and not add any mesh from that position
for (int y = this.minYSection >> capRing; y <= this.maxYSection >> capRing; y++) {
this.tracker.removeCache(capRing, x, y, 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;
}
}
@@ -73,6 +129,19 @@ public class DistanceTracker {
}
}
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
@@ -84,9 +153,14 @@ public class DistanceTracker {
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-- ) {
this.loDRings[i].update(x, z);
var ring = this.loDRings[i];
if (ring != null)
ring.update(x, z);
}
for (var ring : this.cacheUnloadRings) {
if (ring!=null)
@@ -109,18 +183,10 @@ public class DistanceTracker {
if (ring != null)
ring.setCenter(x, z);
}
if (this.mostOuterNonClampedRing!=null)
this.mostOuterNonClampedRing.setCenter(x, z);
var thread = new Thread(()-> {
//Radius of chunks to enqueue
int SIZE = 128;
//Insert highest LOD level
for (int ox = -SIZE; ox <= SIZE; ox++) {
for (int oz = -SIZE; oz <= SIZE; oz++) {
this.inc(4, (x >> (5 + this.loDRings.length)) + ox, (z >> (5 + this.loDRings.length)) + oz);
}
}
for (var ring : this.cacheLoadRings) {
if (ring != null)
ring.fill(x, z);
@@ -131,6 +197,14 @@ public class DistanceTracker {
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);

View File

@@ -74,7 +74,7 @@ public class VoxelCore {
//To get to chunk scale multiply the scale by 2, the scale is after how many chunks does the lods halve
int q = VoxyConfig.CONFIG.qualityScale;
//TODO: add an option for cache load and unload distance
this.distanceTracker = new DistanceTracker(this.renderTracker, new int[]{q,q,q,q}, VoxyConfig.CONFIG.renderDistance/2, 6, 6);
this.distanceTracker = new DistanceTracker(this.renderTracker, new int[]{q,q,q,q}, (VoxyConfig.CONFIG.renderDistance<0?VoxyConfig.CONFIG.renderDistance:((VoxyConfig.CONFIG.renderDistance+1)/2)), 3);
System.out.println("Distance tracker initialized");
this.postProcessing = new PostProcessing();
@@ -182,6 +182,7 @@ public class VoxelCore {
debug.add("Saving service tasks: " + this.world.savingService.getTaskCount());
debug.add("Render service tasks: " + this.renderGen.getTaskCount());
debug.add("Loaded cache sizes: " + Arrays.toString(this.world.getLoadedSectionCacheSizes()));
debug.add("Mesh cache count: " + this.renderGen.getMeshCacheCount());
this.renderer.addDebugData(debug);
}

View File

@@ -88,16 +88,17 @@ public class GeometryManager {
long ptr = UploadStream.INSTANCE.upload(this.sectionMetaBuffer, (long)SECTION_METADATA_SIZE * id, SECTION_METADATA_SIZE);
meta.writeMetadata(ptr);
} else {
//Add to the end of the array
id = this.sectionCount++;
this.pos2id.put(result.position, id);
this.id2pos.add(result.position);
//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);

View File

@@ -150,6 +150,15 @@ public class RenderTracker {
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));
this.renderGen.enqueueTask(lvl, x, y, z, this::shouldStillBuild);
}
//Called by the world engine when a section gets dirtied

View File

@@ -62,4 +62,8 @@ public class BuiltSectionMeshCache {
}
}
}
public int getCount() {
return this.renderCache.size();
}
}

View File

@@ -14,6 +14,7 @@ import java.util.function.ToIntFunction;
//TODO: Add a render cache
public class RenderGenerationService {
public interface TaskChecker {boolean check(int lvl, int x, int y, int z);}
private record BuildTask(Supplier<WorldSection> sectionSupplier) {}
@@ -82,6 +83,10 @@ public class RenderGenerationService {
}
}
public int getMeshCacheCount() {
return this.meshCache.getCount();
}
//TODO: Add a priority system, higher detail sections must always be updated before lower detail
// e.g. priorities NONE->lvl0 and lvl1 -> lvl0 over lvl0 -> lvl1
@@ -102,6 +107,7 @@ public class RenderGenerationService {
this.enqueueTask(lvl, x, y, z, (l,x1,y1,z1)->true);
}
public void enqueueTask(int lvl, int x, int y, int z, TaskChecker checker) {
long ikey = WorldEngine.getWorldSectionId(lvl, x, y, z);
{

View File

@@ -11,6 +11,7 @@ import java.util.concurrent.atomic.AtomicInteger;
// holds a 32x32x32 region of detail
public final class WorldSection {
private static final int ARRAY_REUSE_CACHE_SIZE = 256;
//TODO: maybe just swap this to a ConcurrentLinkedDeque
private static final Deque<long[]> ARRAY_REUSE_CACHE = new ArrayDeque<>(1024);

View File

@@ -16,7 +16,7 @@
"voxy.config.general.maxSections": "Max Sections",
"voxy.config.general.maxSections.tooltip": "The max number of sections the renderer can contain",
"voxy.config.general.renderDistance": "Render Distance",
"voxy.config.general.renderDistance.tooltip": "The render distance in chunks",
"voxy.config.general.renderDistance.tooltip": "The render distance in chunks (set to -1 to disable chunk unloading)",
"voxy.config.threads.ingest": "Ingest",
"voxy.config.threads.ingest.tooltip": "How many threads voxy will use for ingesting new chunks",