Wip on geometry cache
This commit is contained in:
@@ -346,8 +346,8 @@ public class VoxyRenderSystem {
|
|||||||
private void testFullMesh() {
|
private void testFullMesh() {
|
||||||
var modelService = new ModelBakerySubsystem(this.worldIn.getMapper());
|
var modelService = new ModelBakerySubsystem(this.worldIn.getMapper());
|
||||||
var completedCounter = new AtomicInteger();
|
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);
|
var r = new Random(12345);
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ package me.cortex.voxy.client.core.rendering;
|
|||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
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
|
//CPU side cache for section geometry, not thread safe
|
||||||
public class GeometryCache {
|
public class GeometryCache {
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
private long maxCombinedSize;
|
private long maxCombinedSize;
|
||||||
private long currentSize;
|
private long currentSize;
|
||||||
private final Long2ObjectLinkedOpenHashMap<BuiltSection> cache = new Long2ObjectLinkedOpenHashMap<>();
|
private final Long2ObjectLinkedOpenHashMap<BuiltSection> cache = new Long2ObjectLinkedOpenHashMap<>();
|
||||||
@@ -18,25 +22,37 @@ public class GeometryCache {
|
|||||||
|
|
||||||
//Puts the section into the cache
|
//Puts the section into the cache
|
||||||
public void put(BuiltSection section) {
|
public void put(BuiltSection section) {
|
||||||
|
this.lock.lock();
|
||||||
var prev = this.cache.put(section.position, section);
|
var prev = this.cache.put(section.position, section);
|
||||||
this.currentSize += section.geometryBuffer.size;
|
this.currentSize += section.geometryBuffer.size;
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
this.currentSize -= prev.geometryBuffer.size;
|
this.currentSize -= prev.geometryBuffer.size;
|
||||||
prev.free();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (this.maxCombinedSize <= this.currentSize) {
|
while (this.maxCombinedSize <= this.currentSize) {
|
||||||
var entry = this.cache.removeFirst();
|
var entry = this.cache.removeFirst();
|
||||||
this.currentSize -= entry.geometryBuffer.size;
|
this.currentSize -= entry.geometryBuffer.size;
|
||||||
entry.free();
|
entry.free();
|
||||||
}
|
}
|
||||||
|
this.lock.unlock();
|
||||||
|
if (prev != null) {
|
||||||
|
prev.free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuiltSection remove(long position) {
|
public BuiltSection remove(long position) {
|
||||||
|
this.lock.lock();
|
||||||
var section = this.cache.remove(position);
|
var section = this.cache.remove(position);
|
||||||
if (section != null) {
|
if (section != null) {
|
||||||
this.currentSize -= section.geometryBuffer.size;
|
this.currentSize -= section.geometryBuffer.size;
|
||||||
}
|
}
|
||||||
|
this.lock.unlock();
|
||||||
return section;
|
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");
|
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
|
//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.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
|
||||||
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool,
|
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool,
|
||||||
this.nodeManager::submitGeometryResult, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets,
|
this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets,
|
||||||
()->true);
|
()->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);
|
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);
|
Arrays.stream(world.getMapper().getBiomeEntries()).forEach(this.modelService::addBiome);
|
||||||
world.getMapper().setBiomeCallback(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 LongConsumer renderMeshGen;
|
||||||
private IChildUpdate childUpdateCallback;
|
private IChildUpdate childUpdateCallback;
|
||||||
|
|
||||||
public void setCallbacks(LongConsumer renderMeshGen, IChildUpdate childUpdateCallback) {
|
public void setCallbacks(LongConsumer initialRenderMeshGen, LongConsumer renderMeshGen, IChildUpdate childUpdateCallback) {
|
||||||
if (this.renderMeshGen != null) {
|
if (this.renderMeshGen != null) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
this.initialRenderMeshGen = initialRenderMeshGen;
|
||||||
this.renderMeshGen = renderMeshGen;
|
this.renderMeshGen = renderMeshGen;
|
||||||
this.childUpdateCallback = childUpdateCallback;
|
this.childUpdateCallback = childUpdateCallback;
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,7 @@ public class SectionUpdateRouter implements ISectionWatcher {
|
|||||||
}
|
}
|
||||||
if ((delta&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
if ((delta&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||||
//If we added it, immediately invoke for an update
|
//If we added it, immediately invoke for an update
|
||||||
this.renderMeshGen.accept(position);
|
this.initialRenderMeshGen.accept(position);
|
||||||
}
|
}
|
||||||
return delta!=0;
|
return delta!=0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,21 +57,20 @@ public class RenderGenerationService {
|
|||||||
|
|
||||||
private final WorldEngine world;
|
private final WorldEngine world;
|
||||||
private final ModelBakerySubsystem modelBakery;
|
private final ModelBakerySubsystem modelBakery;
|
||||||
private final Consumer<BuiltSection> resultConsumer;
|
private Consumer<BuiltSection> resultConsumer;
|
||||||
private final boolean emitMeshlets;
|
private final boolean emitMeshlets;
|
||||||
|
|
||||||
private final ServiceSlice threads;
|
private final ServiceSlice threads;
|
||||||
|
|
||||||
|
|
||||||
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, Consumer<BuiltSection> consumer, boolean emitMeshlets) {
|
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, boolean emitMeshlets) {
|
||||||
this(world, modelBakery, serviceThreadPool, consumer, emitMeshlets, ()->true);
|
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.emitMeshlets = emitMeshlets;
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.modelBakery = modelBakery;
|
this.modelBakery = modelBakery;
|
||||||
this.resultConsumer = consumer;
|
|
||||||
|
|
||||||
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
|
||||||
@@ -83,6 +82,10 @@ public class RenderGenerationService {
|
|||||||
}, taskLimiter);
|
}, taskLimiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResultConsumer(Consumer<BuiltSection> consumer) {
|
||||||
|
this.resultConsumer = consumer;
|
||||||
|
}
|
||||||
|
|
||||||
//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
|
||||||
@@ -139,7 +142,9 @@ public class RenderGenerationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (section == null) {
|
if (section == null) {
|
||||||
|
if (this.resultConsumer != null) {
|
||||||
this.resultConsumer.accept(BuiltSection.empty(task.position));
|
this.resultConsumer.accept(BuiltSection.empty(task.position));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
section.assertNotFree();
|
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
|
if (mesh != null) {//If the mesh is null it means it didnt finish, so dont submit
|
||||||
|
if (this.resultConsumer != null) {
|
||||||
this.resultConsumer.accept(mesh);
|
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.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.GeometryCache;
|
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.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.BasicAsyncGeometryManager;
|
||||||
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
|
||||||
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
|
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.Logger;
|
||||||
import me.cortex.voxy.common.util.AllocationArena;
|
import me.cortex.voxy.common.util.AllocationArena;
|
||||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
@@ -61,6 +63,7 @@ public class AsyncNodeManager {
|
|||||||
private final NodeManager manager;
|
private final NodeManager manager;
|
||||||
private final BasicAsyncGeometryManager geometryManager;
|
private final BasicAsyncGeometryManager geometryManager;
|
||||||
private final IGeometryData geometryData;
|
private final IGeometryData geometryData;
|
||||||
|
private final SectionUpdateRouter router;
|
||||||
|
|
||||||
private final GeometryCache geometryCache = new GeometryCache(1L<<32);
|
private final GeometryCache geometryCache = new GeometryCache(1L<<32);
|
||||||
|
|
||||||
@@ -77,7 +80,7 @@ public class AsyncNodeManager {
|
|||||||
|
|
||||||
private boolean needsWaitForSync = false;
|
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 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
|
//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
|
// it MUST ONLY be accessed on the render thread
|
||||||
@@ -99,7 +102,20 @@ public class AsyncNodeManager {
|
|||||||
this.thread.setName("Async Node Manager");
|
this.thread.setName("Async Node Manager");
|
||||||
|
|
||||||
this.geometryManager = new BasicAsyncGeometryManager(((BasicSectionGeometryData)geometryData).getMaxSectionCount(), this.geometryCapacity);
|
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
|
//Dont do the move... is just to much effort
|
||||||
this.manager.setClear(new NodeManager.ICleaner() {
|
this.manager.setClear(new NodeManager.ICleaner() {
|
||||||
@Override
|
@Override
|
||||||
@@ -273,6 +289,12 @@ public class AsyncNodeManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pos == 0) {
|
||||||
|
//THIS SHOULD BE IMPOSSIBLE
|
||||||
|
//TODO: VVVVV MUCH MEGA FIX
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
this.manager.removeNodeGeometry(pos);
|
this.manager.removeNodeGeometry(pos);
|
||||||
}
|
}
|
||||||
job.free();
|
job.free();
|
||||||
@@ -600,7 +622,7 @@ public class AsyncNodeManager {
|
|||||||
this.addWork();
|
this.addWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void submitChildChange(WorldSection section) {
|
private void submitChildChange(WorldSection section) {
|
||||||
if (!this.running) {
|
if (!this.running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -609,7 +631,7 @@ public class AsyncNodeManager {
|
|||||||
this.addWork();
|
this.addWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void submitGeometryResult(BuiltSection geometry) {
|
private void submitGeometryResult(BuiltSection geometry) {
|
||||||
if (!this.running) {
|
if (!this.running) {
|
||||||
geometry.free();
|
geometry.free();
|
||||||
return;
|
return;
|
||||||
@@ -724,6 +746,13 @@ public class AsyncNodeManager {
|
|||||||
return this.workCounter.get()!=0 && RESULT_HANDLE.get(this) != null;
|
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
|
//Results object, which is to be synced between the render thread and worker thread
|
||||||
private static final class SyncResults {
|
private static final class SyncResults {
|
||||||
//Contains
|
//Contains
|
||||||
|
|||||||
Reference in New Issue
Block a user