Major tinkering with improving frametimes
This commit is contained in:
@@ -4,7 +4,7 @@ import java.lang.invoke.VarHandle;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class TimingStatistics {
|
public class TimingStatistics {
|
||||||
public static double ROLLING_WEIGHT = 0.95;
|
public static double ROLLING_WEIGHT = 0.975;
|
||||||
private static final ArrayList<TimeSampler> allSamplers = new ArrayList<>();
|
private static final ArrayList<TimeSampler> allSamplers = new ArrayList<>();
|
||||||
public static final class TimeSampler {
|
public static final class TimeSampler {
|
||||||
private boolean running;
|
private boolean running;
|
||||||
@@ -70,10 +70,12 @@ public class TimingStatistics {
|
|||||||
TimingStatistics.allSamplers.forEach(TimeSampler::update);
|
TimingStatistics.allSamplers.forEach(TimeSampler::update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TimeSampler all = new TimeSampler();
|
||||||
public static TimeSampler setup = new TimeSampler();
|
public static TimeSampler setup = new TimeSampler();
|
||||||
public static TimeSampler main = new TimeSampler();
|
public static TimeSampler main = new TimeSampler();
|
||||||
public static TimeSampler dynamic = new TimeSampler();
|
public static TimeSampler dynamic = new TimeSampler();
|
||||||
|
|
||||||
|
|
||||||
public static void update() {
|
public static void update() {
|
||||||
updateSamplers();
|
updateSamplers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ package me.cortex.voxy.client.core.model;
|
|||||||
|
|
||||||
public class IdNotYetComputedException extends RuntimeException {
|
public class IdNotYetComputedException extends RuntimeException {
|
||||||
public final int id;
|
public final int id;
|
||||||
public IdNotYetComputedException(int id) {
|
public final boolean isIdBlockId;
|
||||||
|
public int auxBitMsk;
|
||||||
|
public long[] auxData;
|
||||||
|
public IdNotYetComputedException(int id, boolean isIdBlockId) {
|
||||||
super(null, null, false, false);
|
super(null, null, false, false);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.isIdBlockId = isIdBlockId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package me.cortex.voxy.client.core.model;
|
|||||||
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
|
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
|
||||||
|
import me.cortex.voxy.client.TimingStatistics;
|
||||||
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
import me.cortex.voxy.client.core.gl.GlFramebuffer;
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||||
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
||||||
@@ -25,15 +26,13 @@ public class ModelBakerySubsystem {
|
|||||||
//Redo to just make it request the block faces with the async texture download stream which
|
//Redo to just make it request the block faces with the async texture download stream which
|
||||||
// basicly solves all the render stutter due to the baking
|
// basicly solves all the render stutter due to the baking
|
||||||
|
|
||||||
|
|
||||||
private final RawDownloadStream textureDownStream = new RawDownloadStream(8*1024*1024);//8mb downstream
|
|
||||||
private final ModelStore storage = new ModelStore();
|
private final ModelStore storage = new ModelStore();
|
||||||
public final ModelFactory factory;
|
public final ModelFactory factory;
|
||||||
private final IntLinkedOpenHashSet blockIdQueue = new IntLinkedOpenHashSet();
|
private final ConcurrentLinkedDeque<Integer> blockIdQueue = new ConcurrentLinkedDeque<>();//TODO: replace with custom DS
|
||||||
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeQueue = new ConcurrentLinkedDeque<>();
|
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeQueue = new ConcurrentLinkedDeque<>();
|
||||||
|
|
||||||
public ModelBakerySubsystem(Mapper mapper) {
|
public ModelBakerySubsystem(Mapper mapper) {
|
||||||
this.factory = new ModelFactory(mapper, this.storage, this.textureDownStream);
|
this.factory = new ModelFactory(mapper, this.storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tick() {
|
public void tick() {
|
||||||
@@ -45,6 +44,7 @@ public class ModelBakerySubsystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
//There should be a method to access the frame time IIRC, if the user framecap is unlimited lock it to like 60 fps for computation
|
//There should be a method to access the frame time IIRC, if the user framecap is unlimited lock it to like 60 fps for computation
|
||||||
int BUDGET = 16;//TODO: make this computed based on the remaining free time in a frame (and like div by 2 to reduce overhead) (with a min of 1)
|
int BUDGET = 16;//TODO: make this computed based on the remaining free time in a frame (and like div by 2 to reduce overhead) (with a min of 1)
|
||||||
if (!this.blockIdQueue.isEmpty()) {
|
if (!this.blockIdQueue.isEmpty()) {
|
||||||
@@ -64,27 +64,39 @@ public class ModelBakerySubsystem {
|
|||||||
for (int j = 0; j < i; j++) {
|
for (int j = 0; j < i; j++) {
|
||||||
this.factory.addEntry(est[j]);
|
this.factory.addEntry(est[j]);
|
||||||
}
|
}
|
||||||
|
}*/
|
||||||
|
long totalBudget = 2_000_000;
|
||||||
|
//TimingStatistics.modelProcess.start();
|
||||||
|
long start = System.nanoTime();
|
||||||
|
VarHandle.fullFence();
|
||||||
|
{
|
||||||
|
long budget = Math.min(totalBudget-200_000, totalBudget-(this.factory.resultJobs.size()*20_000L))-200_000;
|
||||||
|
if (budget > 50_000) {
|
||||||
|
Integer i = this.blockIdQueue.poll();
|
||||||
|
while (i != null && (System.nanoTime() - start < budget)) {
|
||||||
|
this.factory.addEntry(i);
|
||||||
|
i = this.blockIdQueue.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Submit is effectively free if nothing is submitted
|
this.factory.tick();
|
||||||
this.textureDownStream.submit();
|
|
||||||
|
|
||||||
//Tick the download stream
|
while (!this.factory.resultJobs.isEmpty()) {
|
||||||
this.textureDownStream.tick();
|
this.factory.resultJobs.poll().run();
|
||||||
|
if (totalBudget<(System.nanoTime()-start))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//TimingStatistics.modelProcess.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
this.factory.free();
|
this.factory.free();
|
||||||
this.storage.free();
|
this.storage.free();
|
||||||
this.textureDownStream.free();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestBlockBake(int blockId) {
|
public void requestBlockBake(int blockId) {
|
||||||
synchronized (this.blockIdQueue) {
|
this.blockIdQueue.add(blockId);
|
||||||
if (this.blockIdQueue.add(blockId)) {
|
|
||||||
VarHandle.fullFence();//Ensure memory coherancy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addBiome(Mapper.BiomeEntry biomeEntry) {
|
public void addBiome(Mapper.BiomeEntry biomeEntry) {
|
||||||
@@ -92,7 +104,7 @@ public class ModelBakerySubsystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addDebugData(List<String> debug) {
|
public void addDebugData(List<String> debug) {
|
||||||
debug.add("MQ/IF/MC: " + this.blockIdQueue.size() + "/" + this.factory.getInflightCount() + "/" + this.factory.getBakedCount());//Model bake queue/in flight/model baked count
|
debug.add(String.format("MQ/IF/MC: %04d, %03d, %04d", this.blockIdQueue.size(), this.factory.getInflightCount(), this.factory.getBakedCount()));//Model bake queue/in flight/model baked count
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelStore getStore() {
|
public ModelStore getStore() {
|
||||||
@@ -102,4 +114,8 @@ public class ModelBakerySubsystem {
|
|||||||
public boolean areQueuesEmpty() {
|
public boolean areQueuesEmpty() {
|
||||||
return this.blockIdQueue.isEmpty() && this.factory.getInflightCount() == 0 && this.biomeQueue.isEmpty();
|
return this.blockIdQueue.isEmpty() && this.factory.getInflightCount() == 0 && this.biomeQueue.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getProcessingCount() {
|
||||||
|
return this.blockIdQueue.size() + this.factory.getInflightCount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,15 +115,16 @@ public class ModelFactory {
|
|||||||
|
|
||||||
private final Mapper mapper;
|
private final Mapper mapper;
|
||||||
private final ModelStore storage;
|
private final ModelStore storage;
|
||||||
private final RawDownloadStream downstream;
|
private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream
|
||||||
|
|
||||||
|
public final Deque<Runnable> resultJobs = new ArrayDeque<>();
|
||||||
|
|
||||||
|
|
||||||
//TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
|
//TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
|
||||||
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
|
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
|
||||||
public ModelFactory(Mapper mapper, ModelStore storage, RawDownloadStream downstream) {
|
public ModelFactory(Mapper mapper, ModelStore storage) {
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.downstream = downstream;
|
|
||||||
this.bakery = new ModelTextureBakery(MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
this.bakery = new ModelTextureBakery(MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
||||||
|
|
||||||
this.metadataCache = new long[1<<16];
|
this.metadataCache = new long[1<<16];
|
||||||
@@ -137,7 +138,9 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
this.downstream.tick();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean addEntry(int blockId) {
|
public boolean addEntry(int blockId) {
|
||||||
if (this.idMappings[blockId] != -1) {
|
if (this.idMappings[blockId] != -1) {
|
||||||
@@ -172,7 +175,7 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int TOTAL_FACES_TEXTURE_SIZE = MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6;// since both depth and colour are packed together, 6 faces, 4 bytes per pixel
|
int TOTAL_FACES_TEXTURE_SIZE = MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6;// since both depth and colour are packed together, 6 faces, 4 bytes per pixel
|
||||||
int allocation = this.downstream.download(TOTAL_FACES_TEXTURE_SIZE, ptr->{
|
int allocation = this.downstream.download(TOTAL_FACES_TEXTURE_SIZE, ptr -> {
|
||||||
ColourDepthTextureData[] textureData = new ColourDepthTextureData[6];
|
ColourDepthTextureData[] textureData = new ColourDepthTextureData[6];
|
||||||
final int FACE_SIZE = MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE;
|
final int FACE_SIZE = MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE;
|
||||||
for (int face = 0; face < 6; face++) {
|
for (int face = 0; face < 6; face++) {
|
||||||
@@ -189,18 +192,14 @@ public class ModelFactory {
|
|||||||
|
|
||||||
textureData[face] = new ColourDepthTextureData(colour, depth, MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
textureData[face] = new ColourDepthTextureData(colour, depth, MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
||||||
}
|
}
|
||||||
processTextureBakeResult(blockId, blockState, textureData);
|
this.resultJobs.add(()->processTextureBakeResult(blockId, blockState, textureData));
|
||||||
});
|
});
|
||||||
this.bakery.renderFacesToStream(blockState, 123456, isFluid, this.downstream.getBufferId(), allocation);
|
this.bakery.renderFacesToStream(blockState, 123456, isFluid, this.downstream.getBufferId(), allocation);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: what i need to do is seperate out fluid states from blockStates
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: so need a few things, per face sizes and offsets, the sizes should be computed from the pixels and find the minimum bounding pixel
|
|
||||||
// while the depth is computed from the depth buffer data
|
|
||||||
|
|
||||||
//This is
|
//This is
|
||||||
private void processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData) {
|
private void processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData) {
|
||||||
if (this.idMappings[blockId] != -1) {
|
if (this.idMappings[blockId] != -1) {
|
||||||
@@ -634,7 +633,7 @@ public class ModelFactory {
|
|||||||
public int getModelId(int blockId) {
|
public int getModelId(int blockId) {
|
||||||
int map = this.idMappings[blockId];
|
int map = this.idMappings[blockId];
|
||||||
if (map == -1) {
|
if (map == -1) {
|
||||||
throw new IdNotYetComputedException(blockId);
|
throw new IdNotYetComputedException(blockId, true);
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
@@ -646,7 +645,7 @@ public class ModelFactory {
|
|||||||
public int getFluidClientStateId(int clientBlockStateId) {
|
public int getFluidClientStateId(int clientBlockStateId) {
|
||||||
int map = this.fluidStateLUT[clientBlockStateId];
|
int map = this.fluidStateLUT[clientBlockStateId];
|
||||||
if (map == -1) {
|
if (map == -1) {
|
||||||
throw new IdNotYetComputedException(clientBlockStateId);
|
throw new IdNotYetComputedException(clientBlockStateId, false);
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
@@ -691,6 +690,7 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
|
this.downstream.free();
|
||||||
this.bakery.free();
|
this.bakery.free();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
|
this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
|
||||||
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool,
|
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool,
|
||||||
this.geometryUpdateQueue::push, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets,
|
this.geometryUpdateQueue::push, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets,
|
||||||
()->this.geometryUpdateQueue.count()<2000);
|
()->this.geometryUpdateQueue.count()<1000 && this.modelService.getProcessingCount()< 750);
|
||||||
|
|
||||||
router.setCallbacks(this.renderGen::enqueueTask, section -> {
|
router.setCallbacks(this.renderGen::enqueueTask, section -> {
|
||||||
section.acquire();
|
section.acquire();
|
||||||
@@ -132,26 +132,36 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
{
|
{
|
||||||
TimingStatistics.main.stop();
|
TimingStatistics.main.stop();
|
||||||
TimingStatistics.dynamic.start();
|
TimingStatistics.dynamic.start();
|
||||||
|
long start = System.nanoTime();
|
||||||
|
VarHandle.fullFence();
|
||||||
|
|
||||||
//Tick download stream
|
//Tick download stream
|
||||||
DownloadStream.INSTANCE.tick();
|
DownloadStream.INSTANCE.tick();
|
||||||
|
|
||||||
|
//Tick upload stream (this is ok to do here as upload ticking is just memory management)
|
||||||
|
UploadStream.INSTANCE.tick();
|
||||||
|
|
||||||
this.sectionUpdateQueue.consume(128);
|
this.sectionUpdateQueue.consume(128);
|
||||||
|
|
||||||
|
VarHandle.fullFence();
|
||||||
|
long updateBudget = Math.max(1_000_000-(System.nanoTime()-start), 0);
|
||||||
|
VarHandle.fullFence();
|
||||||
|
|
||||||
|
if (updateBudget > 50_000) {
|
||||||
//Cap the number of consumed sections per frame to 40 + 2% of the queue size, cap of 200
|
//Cap the number of consumed sections per frame to 40 + 2% of the queue size, cap of 200
|
||||||
//int geoUpdateCap = 20;//Math.max(100, Math.min((int)(0.15*this.geometryUpdateQueue.count()), 260));
|
//int geoUpdateCap = 20;//Math.max(100, Math.min((int)(0.15*this.geometryUpdateQueue.count()), 260));
|
||||||
this.geometryUpdateQueue.consumeMillis(1);
|
this.geometryUpdateQueue.consumeNano(updateBudget);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
|
||||||
|
|
||||||
if (this.nodeManager.writeChanges(this.traversal.getNodeBuffer())) {//TODO: maybe move the node buffer out of the traversal class
|
if (this.nodeManager.writeChanges(this.traversal.getNodeBuffer())) {//TODO: maybe move the node buffer out of the traversal class
|
||||||
UploadStream.INSTANCE.commit();
|
UploadStream.INSTANCE.commit();
|
||||||
}
|
}
|
||||||
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
|
|
||||||
|
|
||||||
//this needs to go after, due to geometry updates committed by the nodeManager
|
//this needs to go after, due to geometry updates committed by the nodeManager
|
||||||
this.sectionRenderer.getGeometryManager().tick();
|
this.sectionRenderer.getGeometryManager().tick();
|
||||||
|
|
||||||
//Tick upload stream
|
|
||||||
UploadStream.INSTANCE.tick();
|
|
||||||
|
|
||||||
TimingStatistics.dynamic.stop();
|
TimingStatistics.dynamic.stop();
|
||||||
TimingStatistics.main.start();
|
TimingStatistics.main.start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ public class VoxyRenderSystem {
|
|||||||
downstream.tick();
|
downstream.tick();
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
TimingStatistics.all.start();
|
||||||
TimingStatistics.setup.start();
|
TimingStatistics.setup.start();
|
||||||
this.renderDistanceTracker.setCenterAndProcess(camera.getBlockPos().getX(), camera.getBlockPos().getZ());
|
this.renderDistanceTracker.setCenterAndProcess(camera.getBlockPos().getX(), camera.getBlockPos().getZ());
|
||||||
|
|
||||||
@@ -112,6 +113,7 @@ public class VoxyRenderSystem {
|
|||||||
|
|
||||||
PrintfDebugUtil.tick();
|
PrintfDebugUtil.tick();
|
||||||
TimingStatistics.setup.stop();
|
TimingStatistics.setup.stop();
|
||||||
|
TimingStatistics.all.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Matrix4f makeProjectionMatrix(float near, float far) {
|
private static Matrix4f makeProjectionMatrix(float near, float far) {
|
||||||
@@ -140,6 +142,7 @@ public class VoxyRenderSystem {
|
|||||||
if (IrisUtil.irisShadowActive()) {
|
if (IrisUtil.irisShadowActive()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
TimingStatistics.all.start();
|
||||||
TimingStatistics.main.start();
|
TimingStatistics.main.start();
|
||||||
|
|
||||||
if (false) {
|
if (false) {
|
||||||
@@ -207,7 +210,7 @@ public class VoxyRenderSystem {
|
|||||||
this.postProcessing.renderPost(projection, RenderSystem.getProjectionMatrix(), boundFB);
|
this.postProcessing.renderPost(projection, RenderSystem.getProjectionMatrix(), boundFB);
|
||||||
glBindFramebuffer(GlConst.GL_FRAMEBUFFER, oldFB);
|
glBindFramebuffer(GlConst.GL_FRAMEBUFFER, oldFB);
|
||||||
TimingStatistics.main.stop();
|
TimingStatistics.main.stop();
|
||||||
|
TimingStatistics.all.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDebugInfo(List<String> debug) {
|
public void addDebugInfo(List<String> debug) {
|
||||||
@@ -215,8 +218,10 @@ public class VoxyRenderSystem {
|
|||||||
this.renderer.addDebugData(debug);
|
this.renderer.addDebugData(debug);
|
||||||
{
|
{
|
||||||
TimingStatistics.update();
|
TimingStatistics.update();
|
||||||
debug.add("Voxy frame runtime (millis): " + TimingStatistics.setup.pVal() + ", " + TimingStatistics.dynamic.pVal() + ", " + TimingStatistics.main.pVal());
|
debug.add("Voxy frame runtime (millis): " + TimingStatistics.setup.pVal() + ", " + TimingStatistics.dynamic.pVal() + ", " + TimingStatistics.main.pVal()+ ", " + TimingStatistics.all.pVal());
|
||||||
}
|
}
|
||||||
|
int val = RenderGenerationService.FC.getAndSet(0);
|
||||||
|
debug.add("FC: " + val);
|
||||||
PrintfDebugUtil.addToOut(debug);
|
PrintfDebugUtil.addToOut(debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.building;
|
package me.cortex.voxy.client.core.rendering.building;
|
||||||
|
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
|
import me.cortex.voxy.client.core.model.IdNotYetComputedException;
|
||||||
import me.cortex.voxy.client.core.model.ModelFactory;
|
import me.cortex.voxy.client.core.model.ModelFactory;
|
||||||
import me.cortex.voxy.client.core.model.ModelQueries;
|
import me.cortex.voxy.client.core.model.ModelQueries;
|
||||||
import me.cortex.voxy.client.core.util.Mesher2D;
|
import me.cortex.voxy.client.core.util.Mesher2D;
|
||||||
@@ -1230,9 +1231,14 @@ public class RenderDataFactory45 {
|
|||||||
int neighborMsk = this.prepareSectionData();
|
int neighborMsk = this.prepareSectionData();
|
||||||
this.acquireNeighborData(section, neighborMsk);
|
this.acquireNeighborData(section, neighborMsk);
|
||||||
|
|
||||||
|
try {
|
||||||
this.generateYZFaces();
|
this.generateYZFaces();
|
||||||
this.generateXFaces();
|
this.generateXFaces();
|
||||||
|
} catch (IdNotYetComputedException e) {
|
||||||
|
e.auxBitMsk = neighborMsk;
|
||||||
|
e.auxData = this.neighboringFaces;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
//TODO:NOTE! when doing face culling of translucent blocks,
|
//TODO:NOTE! when doing face culling of translucent blocks,
|
||||||
// if the connecting type of the translucent block is the same AND the face is full, discard it
|
// if the connecting type of the translucent block is the same AND the face is full, discard it
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import me.cortex.voxy.common.thread.ServiceSlice;
|
|||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -23,7 +24,8 @@ public class RenderGenerationService {
|
|||||||
private static final class BuildTask {
|
private static final class BuildTask {
|
||||||
WorldSection section;
|
WorldSection section;
|
||||||
final long position;
|
final long position;
|
||||||
boolean hasDoneModelRequest;
|
boolean hasDoneModelRequestInner;
|
||||||
|
boolean hasDoneModelRequestOuter;
|
||||||
private BuildTask(long position) {
|
private BuildTask(long position) {
|
||||||
this.position = position;
|
this.position = position;
|
||||||
}
|
}
|
||||||
@@ -52,8 +54,9 @@ public class RenderGenerationService {
|
|||||||
this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{
|
this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{
|
||||||
//Thread local instance of the factory
|
//Thread local instance of the factory
|
||||||
var factory = new RenderDataFactory45(this.world, this.modelBakery.factory, this.emitMeshlets);
|
var factory = new RenderDataFactory45(this.world, this.modelBakery.factory, this.emitMeshlets);
|
||||||
|
IntOpenHashSet seenMissed = new IntOpenHashSet(128);
|
||||||
return new Pair<>(() -> {
|
return new Pair<>(() -> {
|
||||||
this.processJob(factory);
|
this.processJob(factory, seenMissed);
|
||||||
}, factory::free);
|
}, factory::free);
|
||||||
}, taskLimiter);
|
}, taskLimiter);
|
||||||
}
|
}
|
||||||
@@ -61,14 +64,28 @@ public class RenderGenerationService {
|
|||||||
//NOTE: the biomes are always fully populated/kept up to date
|
//NOTE: the biomes are always fully populated/kept up to date
|
||||||
|
|
||||||
//Asks the Model system to bake all blocks that currently dont have a model
|
//Asks the Model system to bake all blocks that currently dont have a model
|
||||||
private void computeAndRequestRequiredModels(WorldSection section, int extraId) {
|
private void computeAndRequestRequiredModels(IntOpenHashSet seenMissedIds, int bitMsk, long[] auxData) {
|
||||||
var raw = section.copyData();//TODO: replace with copyDataTo and use a "thread local"/context array to reduce allocation rates
|
final var factory = this.modelBakery.factory;
|
||||||
IntOpenHashSet seen = new IntOpenHashSet(128);
|
for (int i = 0; i < 6; i++) {
|
||||||
seen.add(extraId);
|
if ((bitMsk&(1<<i))==0) continue;
|
||||||
for (long state : raw) {
|
for (int j = 0; j < 32*32; j++) {
|
||||||
|
int block = Mapper.getBlockId(auxData[j+(i*32*32)]);
|
||||||
|
if (block != 0 && !factory.hasModelForBlockId(block)) {
|
||||||
|
if (seenMissedIds.add(block)) {
|
||||||
|
this.modelBakery.requestBlockBake(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeAndRequestRequiredModels(IntOpenHashSet seenMissedIds, WorldSection section) {
|
||||||
|
//Know this is... very much not safe, however it reduces allocation rates and other garbage, am sure its "fine"
|
||||||
|
final var factory = this.modelBakery.factory;
|
||||||
|
for (long state : section._unsafeGetRawDataArray()) {
|
||||||
int block = Mapper.getBlockId(state);
|
int block = Mapper.getBlockId(state);
|
||||||
if (!this.modelBakery.factory.hasModelForBlockId(block)) {
|
if (block != 0 && !factory.hasModelForBlockId(block)) {
|
||||||
if (seen.add(block)) {
|
if (seenMissedIds.add(block)) {
|
||||||
this.modelBakery.requestBlockBake(block);
|
this.modelBakery.requestBlockBake(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,8 +96,14 @@ public class RenderGenerationService {
|
|||||||
return this.world.acquireIfExists(pos);
|
return this.world.acquireIfExists(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean putTaskFirst(long pos) {
|
||||||
|
//Level 3 or 4
|
||||||
|
return WorldEngine.getLevel(pos) > 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final AtomicInteger FC = new AtomicInteger(0);
|
||||||
//TODO: add a generated render data cache
|
//TODO: add a generated render data cache
|
||||||
private void processJob(RenderDataFactory45 factory) {
|
private void processJob(RenderDataFactory45 factory, IntOpenHashSet seenMissedIds) {
|
||||||
BuildTask task;
|
BuildTask task;
|
||||||
synchronized (this.taskQueue) {
|
synchronized (this.taskQueue) {
|
||||||
task = this.taskQueue.removeFirst();
|
task = this.taskQueue.removeFirst();
|
||||||
@@ -105,20 +128,33 @@ public class RenderGenerationService {
|
|||||||
try {
|
try {
|
||||||
mesh = factory.generateMesh(section);
|
mesh = factory.generateMesh(section);
|
||||||
} catch (IdNotYetComputedException e) {
|
} catch (IdNotYetComputedException e) {
|
||||||
|
if (e.isIdBlockId) {
|
||||||
//TODO: maybe move this to _after_ task as been readded to queue??
|
//TODO: maybe move this to _after_ task as been readded to queue??
|
||||||
|
|
||||||
if (!this.modelBakery.factory.hasModelForBlockId(e.id)) {
|
if (!this.modelBakery.factory.hasModelForBlockId(e.id)) {
|
||||||
|
if (seenMissedIds.add(e.id)) {
|
||||||
this.modelBakery.requestBlockBake(e.id);
|
this.modelBakery.requestBlockBake(e.id);
|
||||||
}
|
}
|
||||||
if (task.hasDoneModelRequest) {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.hasDoneModelRequestInner && task.hasDoneModelRequestOuter) {
|
||||||
|
FC.addAndGet(1);
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1);
|
Thread.sleep(1);
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
throw new RuntimeException(ex);
|
throw new RuntimeException(ex);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (!task.hasDoneModelRequestInner) {
|
||||||
//The reason for the extra id parameter is that we explicitly add/check against the exception id due to e.g. requesting accross a chunk boarder wont be captured in the request
|
//The reason for the extra id parameter is that we explicitly add/check against the exception id due to e.g. requesting accross a chunk boarder wont be captured in the request
|
||||||
this.computeAndRequestRequiredModels(section, e.id);
|
if (e.auxData == null)//the null check this is because for it to be, the inner must already be computed
|
||||||
|
this.computeAndRequestRequiredModels(seenMissedIds, section);
|
||||||
|
task.hasDoneModelRequestInner = true;
|
||||||
|
}
|
||||||
|
if ((!task.hasDoneModelRequestOuter) && e.auxData != null) {
|
||||||
|
this.computeAndRequestRequiredModels(seenMissedIds, e.auxBitMsk, e.auxData);
|
||||||
|
task.hasDoneModelRequestOuter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -130,12 +166,14 @@ public class RenderGenerationService {
|
|||||||
BuildTask queuedTask;
|
BuildTask queuedTask;
|
||||||
synchronized (this.taskQueue) {
|
synchronized (this.taskQueue) {
|
||||||
queuedTask = this.taskQueue.putIfAbsent(section.key, task);
|
queuedTask = this.taskQueue.putIfAbsent(section.key, task);
|
||||||
}
|
|
||||||
if (queuedTask == null) {
|
if (queuedTask == null) {
|
||||||
queuedTask = task;
|
queuedTask = task;
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedTask.hasDoneModelRequest = true;//Mark (or remark) the section as having chunks requested
|
if (queuedTask.hasDoneModelRequestInner && queuedTask.hasDoneModelRequestOuter && putTaskFirst(section.key)) {//Force higher priority
|
||||||
|
this.taskQueue.getAndMoveToFirst(section.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (queuedTask == task) {//use the == not .equal to see if we need to release a permit
|
if (queuedTask == task) {//use the == not .equal to see if we need to release a permit
|
||||||
if (this.threads.isAlive()) {//Only execute if were not dead
|
if (this.threads.isAlive()) {//Only execute if were not dead
|
||||||
@@ -145,8 +183,12 @@ public class RenderGenerationService {
|
|||||||
//If we did put it in the queue, dont release the section
|
//If we did put it in the queue, dont release the section
|
||||||
shouldFreeSection = false;
|
shouldFreeSection = false;
|
||||||
} else {
|
} else {
|
||||||
//This should no longer be a worry with LRU section cache
|
//Mark (or remark) the section as having models requested
|
||||||
//Logger.info("Funkyness happened and multiple tasks for same section where in queue");
|
if (task.hasDoneModelRequestInner)
|
||||||
|
queuedTask.hasDoneModelRequestInner = true;
|
||||||
|
|
||||||
|
if (task.hasDoneModelRequestOuter)
|
||||||
|
queuedTask.hasDoneModelRequestOuter = true;
|
||||||
|
|
||||||
//Things went bad, set section to null and ensure section is freed
|
//Things went bad, set section to null and ensure section is freed
|
||||||
task.section = null;
|
task.section = null;
|
||||||
@@ -172,7 +214,7 @@ public class RenderGenerationService {
|
|||||||
return new BuildTask(key);
|
return new BuildTask(key);
|
||||||
});
|
});
|
||||||
//Prioritize lower detail builds
|
//Prioritize lower detail builds
|
||||||
if (WorldEngine.getLevel(pos) > 2) {
|
if (putTaskFirst(pos)) {
|
||||||
this.taskQueue.getAndMoveToFirst(pos);
|
this.taskQueue.getAndMoveToFirst(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ public class RawDownloadStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void tick() {
|
public void tick() {
|
||||||
|
this.submit();
|
||||||
|
|
||||||
while (!this.frames.isEmpty()) {
|
while (!this.frames.isEmpty()) {
|
||||||
//If the first element is not signaled, none of the others will be signaled so break
|
//If the first element is not signaled, none of the others will be signaled so break
|
||||||
if (!this.frames.peek().fence.signaled()) {
|
if (!this.frames.peek().fence.signaled()) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package me.cortex.voxy.common.util;
|
package me.cortex.voxy.common.util;
|
||||||
|
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -39,18 +40,20 @@ public class MessageQueue <T> {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int consumeMillis(int millis) {
|
public int consumeNano(long budget) {
|
||||||
if (this.count.get() == 0) {
|
if (this.count.get() == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
VarHandle.fullFence();
|
||||||
long nano = System.nanoTime();
|
long nano = System.nanoTime();
|
||||||
|
VarHandle.fullFence();
|
||||||
do {
|
do {
|
||||||
var entry = this.queue.poll();
|
var entry = this.queue.poll();
|
||||||
if (entry == null) break;
|
if (entry == null) break;
|
||||||
i++;
|
i++;
|
||||||
this.consumer.accept(entry);
|
this.consumer.accept(entry);
|
||||||
} while ((System.nanoTime()-nano) < millis*1000_000L);
|
} while ((System.nanoTime()-nano) < budget);
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
this.count.addAndGet(-i);
|
this.count.addAndGet(-i);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user