0.1.4, added render distance slider, disabled caching until better solution can be found
This commit is contained in:
@@ -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"))
|
||||
|
||||
@@ -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) -> {
|
||||
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) -> {});
|
||||
this.cacheUnloadRings[i] = new TransitionRing2D(5+i, (lodRingScales[i]<<1) + cacheUnloadDistance, (x, z) -> {}, (x, 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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -62,4 +62,8 @@ public class BuiltSectionMeshCache {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.renderCache.size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user