Wip on geometry cache
This commit is contained in:
@@ -346,8 +346,8 @@ public class VoxyRenderSystem {
|
||||
private void testFullMesh() {
|
||||
var modelService = new ModelBakerySubsystem(this.worldIn.getMapper());
|
||||
var completedCounter = new AtomicInteger();
|
||||
var generationService = new RenderGenerationService(this.worldIn, modelService, VoxyCommon.getInstance().getThreadPool(), a-> {completedCounter.incrementAndGet(); a.free();}, false);
|
||||
|
||||
var generationService = new RenderGenerationService(this.worldIn, modelService, VoxyCommon.getInstance().getThreadPool(), false);
|
||||
generationService.setResultConsumer(a-> {completedCounter.incrementAndGet(); a.free();});
|
||||
|
||||
var r = new Random(12345);
|
||||
{
|
||||
|
||||
@@ -3,8 +3,12 @@ package me.cortex.voxy.client.core.rendering;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
//CPU side cache for section geometry, not thread safe
|
||||
public class GeometryCache {
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private long maxCombinedSize;
|
||||
private long currentSize;
|
||||
private final Long2ObjectLinkedOpenHashMap<BuiltSection> cache = new Long2ObjectLinkedOpenHashMap<>();
|
||||
@@ -18,25 +22,37 @@ public class GeometryCache {
|
||||
|
||||
//Puts the section into the cache
|
||||
public void put(BuiltSection section) {
|
||||
this.lock.lock();
|
||||
var prev = this.cache.put(section.position, section);
|
||||
this.currentSize += section.geometryBuffer.size;
|
||||
if (prev != null) {
|
||||
this.currentSize -= prev.geometryBuffer.size;
|
||||
prev.free();
|
||||
}
|
||||
|
||||
while (this.maxCombinedSize <= this.currentSize) {
|
||||
var entry = this.cache.removeFirst();
|
||||
this.currentSize -= entry.geometryBuffer.size;
|
||||
entry.free();
|
||||
}
|
||||
this.lock.unlock();
|
||||
if (prev != null) {
|
||||
prev.free();
|
||||
}
|
||||
}
|
||||
|
||||
public BuiltSection remove(long position) {
|
||||
this.lock.lock();
|
||||
var section = this.cache.remove(position);
|
||||
if (section != null) {
|
||||
this.currentSize -= section.geometryBuffer.size;
|
||||
}
|
||||
this.lock.unlock();
|
||||
return section;
|
||||
}
|
||||
|
||||
public void clear(long position) {
|
||||
var sec = this.remove(position);
|
||||
if (sec != null) {
|
||||
sec.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,21 +75,18 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
|
||||
Logger.info("Using renderer: " + this.sectionRenderer.getClass().getSimpleName() + " with geometry buffer of: " + geometryCapacity + " bytes");
|
||||
|
||||
//Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard
|
||||
var router = new SectionUpdateRouter();
|
||||
|
||||
this.nodeManager = new AsyncNodeManager(1<<21, router, this.geometryData);
|
||||
this.nodeCleaner = new NodeCleaner(this.nodeManager);
|
||||
|
||||
this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
|
||||
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool,
|
||||
this.nodeManager::submitGeometryResult, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets,
|
||||
this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets,
|
||||
()->true);
|
||||
|
||||
router.setCallbacks(this.renderGen::enqueueTask, this.nodeManager::submitChildChange);
|
||||
this.nodeManager = new AsyncNodeManager(1<<21, this.geometryData, this.renderGen);
|
||||
this.nodeCleaner = new NodeCleaner(this.nodeManager);
|
||||
|
||||
this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, this.nodeCleaner);
|
||||
|
||||
world.setDirtyCallback(router::forwardEvent);
|
||||
world.setDirtyCallback(this.nodeManager::worldEvent);
|
||||
|
||||
Arrays.stream(world.getMapper().getBiomeEntries()).forEach(this.modelService::addBiome);
|
||||
world.getMapper().setBiomeCallback(this.modelService::addBiome);
|
||||
|
||||
@@ -22,13 +22,15 @@ public class SectionUpdateRouter implements ISectionWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private LongConsumer initialRenderMeshGen;
|
||||
private LongConsumer renderMeshGen;
|
||||
private IChildUpdate childUpdateCallback;
|
||||
|
||||
public void setCallbacks(LongConsumer renderMeshGen, IChildUpdate childUpdateCallback) {
|
||||
public void setCallbacks(LongConsumer initialRenderMeshGen, LongConsumer renderMeshGen, IChildUpdate childUpdateCallback) {
|
||||
if (this.renderMeshGen != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.initialRenderMeshGen = initialRenderMeshGen;
|
||||
this.renderMeshGen = renderMeshGen;
|
||||
this.childUpdateCallback = childUpdateCallback;
|
||||
}
|
||||
@@ -69,7 +71,7 @@ public class SectionUpdateRouter implements ISectionWatcher {
|
||||
}
|
||||
if ((delta&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||
//If we added it, immediately invoke for an update
|
||||
this.renderMeshGen.accept(position);
|
||||
this.initialRenderMeshGen.accept(position);
|
||||
}
|
||||
return delta!=0;
|
||||
}
|
||||
|
||||
@@ -57,21 +57,20 @@ public class RenderGenerationService {
|
||||
|
||||
private final WorldEngine world;
|
||||
private final ModelBakerySubsystem modelBakery;
|
||||
private final Consumer<BuiltSection> resultConsumer;
|
||||
private Consumer<BuiltSection> resultConsumer;
|
||||
private final boolean emitMeshlets;
|
||||
|
||||
private final ServiceSlice threads;
|
||||
|
||||
|
||||
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, Consumer<BuiltSection> consumer, boolean emitMeshlets) {
|
||||
this(world, modelBakery, serviceThreadPool, consumer, emitMeshlets, ()->true);
|
||||
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, boolean emitMeshlets) {
|
||||
this(world, modelBakery, serviceThreadPool, emitMeshlets, ()->true);
|
||||
}
|
||||
|
||||
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, Consumer<BuiltSection> consumer, boolean emitMeshlets, BooleanSupplier taskLimiter) {
|
||||
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, boolean emitMeshlets, BooleanSupplier taskLimiter) {
|
||||
this.emitMeshlets = emitMeshlets;
|
||||
this.world = world;
|
||||
this.modelBakery = modelBakery;
|
||||
this.resultConsumer = consumer;
|
||||
|
||||
this.threads = serviceThreadPool.createService("Section mesh generation service", 100, ()->{
|
||||
//Thread local instance of the factory
|
||||
@@ -83,6 +82,10 @@ public class RenderGenerationService {
|
||||
}, taskLimiter);
|
||||
}
|
||||
|
||||
public void setResultConsumer(Consumer<BuiltSection> consumer) {
|
||||
this.resultConsumer = consumer;
|
||||
}
|
||||
|
||||
//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
|
||||
@@ -139,7 +142,9 @@ public class RenderGenerationService {
|
||||
}
|
||||
|
||||
if (section == null) {
|
||||
this.resultConsumer.accept(BuiltSection.empty(task.position));
|
||||
if (this.resultConsumer != null) {
|
||||
this.resultConsumer.accept(BuiltSection.empty(task.position));
|
||||
}
|
||||
return;
|
||||
}
|
||||
section.assertNotFree();
|
||||
@@ -265,7 +270,11 @@ public class RenderGenerationService {
|
||||
}
|
||||
|
||||
if (mesh != null) {//If the mesh is null it means it didnt finish, so dont submit
|
||||
this.resultConsumer.accept(mesh);
|
||||
if (this.resultConsumer != null) {
|
||||
this.resultConsumer.accept(mesh);
|
||||
} else {
|
||||
mesh.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ 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.rendering.GeometryCache;
|
||||
import me.cortex.voxy.client.core.rendering.ISectionWatcher;
|
||||
import me.cortex.voxy.client.core.rendering.SectionUpdateRouter;
|
||||
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.section.geometry.BasicAsyncGeometryManager;
|
||||
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
||||
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
|
||||
@@ -16,6 +17,7 @@ import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.util.AllocationArena;
|
||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.cortex.voxy.common.world.WorldSection;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
@@ -61,6 +63,7 @@ public class AsyncNodeManager {
|
||||
private final NodeManager manager;
|
||||
private final BasicAsyncGeometryManager geometryManager;
|
||||
private final IGeometryData geometryData;
|
||||
private final SectionUpdateRouter router;
|
||||
|
||||
private final GeometryCache geometryCache = new GeometryCache(1L<<32);
|
||||
|
||||
@@ -77,7 +80,7 @@ public class AsyncNodeManager {
|
||||
|
||||
private boolean needsWaitForSync = false;
|
||||
|
||||
public AsyncNodeManager(int maxNodeCount, ISectionWatcher watcher, IGeometryData geometryData) {
|
||||
public AsyncNodeManager(int maxNodeCount, IGeometryData geometryData, RenderGenerationService renderService) {
|
||||
//Note the current implmentation of ISectionWatcher is threadsafe
|
||||
//Note: geometry data is the data store/source, not the management, it is just a raw store of data
|
||||
// it MUST ONLY be accessed on the render thread
|
||||
@@ -99,7 +102,20 @@ public class AsyncNodeManager {
|
||||
this.thread.setName("Async Node Manager");
|
||||
|
||||
this.geometryManager = new BasicAsyncGeometryManager(((BasicSectionGeometryData)geometryData).getMaxSectionCount(), this.geometryCapacity);
|
||||
this.manager = new NodeManager(maxNodeCount, this.geometryManager, watcher);
|
||||
|
||||
this.router = new SectionUpdateRouter();
|
||||
this.router.setCallbacks(pos->{//On initial render gen, try get from geometry cache
|
||||
var cachedGeometry = this.geometryCache.remove(pos);
|
||||
if (cachedGeometry != null) {//Use the cached geometry
|
||||
this.submitGeometryResult(cachedGeometry);
|
||||
} else {//Else we need to request it
|
||||
renderService.enqueueTask(pos);
|
||||
}
|
||||
}, renderService::enqueueTask, this::submitChildChange);
|
||||
renderService.setResultConsumer(this::submitGeometryResult);
|
||||
|
||||
this.manager = new NodeManager(maxNodeCount, this.geometryManager, this.router);
|
||||
|
||||
//Dont do the move... is just to much effort
|
||||
this.manager.setClear(new NodeManager.ICleaner() {
|
||||
@Override
|
||||
@@ -273,6 +289,12 @@ public class AsyncNodeManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pos == 0) {
|
||||
//THIS SHOULD BE IMPOSSIBLE
|
||||
//TODO: VVVVV MUCH MEGA FIX
|
||||
continue;
|
||||
}
|
||||
|
||||
this.manager.removeNodeGeometry(pos);
|
||||
}
|
||||
job.free();
|
||||
@@ -600,7 +622,7 @@ public class AsyncNodeManager {
|
||||
this.addWork();
|
||||
}
|
||||
|
||||
public void submitChildChange(WorldSection section) {
|
||||
private void submitChildChange(WorldSection section) {
|
||||
if (!this.running) {
|
||||
return;
|
||||
}
|
||||
@@ -609,7 +631,7 @@ public class AsyncNodeManager {
|
||||
this.addWork();
|
||||
}
|
||||
|
||||
public void submitGeometryResult(BuiltSection geometry) {
|
||||
private void submitGeometryResult(BuiltSection geometry) {
|
||||
if (!this.running) {
|
||||
geometry.free();
|
||||
return;
|
||||
@@ -724,6 +746,13 @@ public class AsyncNodeManager {
|
||||
return this.workCounter.get()!=0 && RESULT_HANDLE.get(this) != null;
|
||||
}
|
||||
|
||||
public void worldEvent(WorldSection section, int flags) {
|
||||
//If there is any change, we need to clear the geometry cache before emitting update
|
||||
this.geometryCache.clear(section.key);
|
||||
|
||||
this.router.forwardEvent(section, flags);
|
||||
}
|
||||
|
||||
//Results object, which is to be synced between the render thread and worker thread
|
||||
private static final class SyncResults {
|
||||
//Contains
|
||||
|
||||
Reference in New Issue
Block a user