From e58cbaa6deca744739c60510a8097020b64cbff2 Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Thu, 15 May 2025 21:39:32 +1000 Subject: [PATCH] Geometry cleaner works again --- .../voxy/client/core/model/ModelFactory.java | 77 +++++++++++++------ .../hierachical/AsyncNodeManager.java | 22 +++++- .../rendering/hierachical/NodeCleaner.java | 3 +- .../geometry/BasicAsyncGeometryManager.java | 8 +- .../geometry/BasicSectionGeometryData.java | 2 +- 5 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java b/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java index 338f294c..3d2ca7e8 100644 --- a/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java +++ b/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java @@ -8,6 +8,7 @@ import me.cortex.voxy.client.core.gl.Capabilities; import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery; import me.cortex.voxy.client.core.rendering.util.RawDownloadStream; import me.cortex.voxy.client.core.rendering.util.UploadStream; +import me.cortex.voxy.common.util.MemoryBuffer; import me.cortex.voxy.common.world.other.Mapper; import net.minecraft.block.Block; import net.minecraft.block.BlockState; @@ -35,6 +36,7 @@ import org.lwjgl.system.MemoryUtil; import java.util.*; import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE; +import static org.lwjgl.opengl.ARBDirectStateAccess.nglTextureSubImage2D; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL33.glDeleteSamplers; import static org.lwjgl.opengl.GL33.glGenSamplers; @@ -661,8 +663,54 @@ public class ModelFactory { return this.metadataCache[clientId]; } + + private static int computeSizeWithMips(int size) { + int total = 0; + for (;size!=0;size>>=1) total += size*size; + return total; + } + private static final MemoryBuffer SCRATCH_TEX = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4); + private static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE); //TODO: redo to batch blit, instead of 6 seperate blits, and also fix mipping private void putTextures(int id, ColourDepthTextureData[] textures) { + if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");} + + + //Copy all textures into scratch + final long addr = SCRATCH_TEX.address; + final int LENGTH_B = MODEL_TEXTURE_SIZE*3; + for (int i = 0; i < 6; i++) { + int x = (i>>1)*MODEL_TEXTURE_SIZE; + int y = (i&1)*MODEL_TEXTURE_SIZE; + int j = 0; + for (int t : textures[i].colour()) { + int o = ((y+(j>>LAYERS))*LENGTH_B + ((j&(MODEL_TEXTURE_SIZE-1))+x))*4; j++;//LAYERS here is just cause faster + MemoryUtil.memPutInt(addr+o, t); + } + } + + //Mip the scratch + long dAddr = addr; + for (int i = 0; i < LAYERS-1; i++) { + long sAddr = dAddr; + dAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(i<<1);//is.. i*2 because shrink both MODEL_TEXTURE_SIZE by >>i so is 2*i total shift + int width = (MODEL_TEXTURE_SIZE*3)>>(i+1); + int sWidth = (MODEL_TEXTURE_SIZE*3)>>i; + int height = (MODEL_TEXTURE_SIZE*2)>>(i+1); + //TODO: OPTIMZIE THIS + for (int px = 0; px < width; px++) { + for (int py = 0; py < height; py++) { + long bp = sAddr + (px*2 + py*2*sWidth)*4; + int C00 = MemoryUtil.memGetInt(bp); + int C01 = MemoryUtil.memGetInt(bp+sWidth*4); + int C10 = MemoryUtil.memGetInt(bp+4); + int C11 = MemoryUtil.memGetInt(bp+sWidth*4+4); + MemoryUtil.memPutInt(dAddr + (px+py*width) * 4L, TextureUtils.mipColours(C00, C01, C10, C11)); + } + } + } + + int X = (id&0xFF) * MODEL_TEXTURE_SIZE*3; int Y = ((id>>8)&0xFF) * MODEL_TEXTURE_SIZE*2; @@ -671,31 +719,10 @@ public class ModelFactory { glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - for (int subTex = 0; subTex < 6; subTex++) { - int x = X + (subTex>>1)*MODEL_TEXTURE_SIZE; - int y = Y + (subTex&1)*MODEL_TEXTURE_SIZE; - - var current = textures[subTex].colour(); - var next = new int[current.length>>1]; - final int layers = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE); - for (int i = 0; i < layers; i++) { - glTextureSubImage2D(this.storage.textures.id, i, x>>i, y>>i, MODEL_TEXTURE_SIZE>>i, MODEL_TEXTURE_SIZE>>i, GL_RGBA, GL_UNSIGNED_BYTE, current); - - int nSize = MODEL_TEXTURE_SIZE>>(i+1); - int size = MODEL_TEXTURE_SIZE>>i; - for (int pX = 0; pX < nSize; pX++) { - for (int pY = 0; pY < nSize; pY++) { - int C00 = current[(pY*2)*size+pX*2]; - int C01 = current[(pY*2+1)*size+pX*2]; - int C10 = current[(pY*2)*size+pX*2+1]; - int C11 = current[(pY*2+1)*size+pX*2+1]; - next[pY*nSize+pX] = TextureUtils.mipColours(C00, C01, C10, C11); - } - } - - current = next; - next = new int[current.length>>1]; - } + long cAddr = addr; + for (int lvl = 0; lvl < LAYERS; lvl++) { + nglTextureSubImage2D(this.storage.textures.id, lvl, X >> lvl, Y >> lvl, (MODEL_TEXTURE_SIZE*3) >> lvl, (MODEL_TEXTURE_SIZE*2) >> lvl, GL_RGBA, GL_UNSIGNED_BYTE, cAddr); + cAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(lvl<<1); } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/AsyncNodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/AsyncNodeManager.java index 2e4037ae..5622ba10 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/AsyncNodeManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/AsyncNodeManager.java @@ -17,7 +17,6 @@ import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.util.MemoryBuffer; import me.cortex.voxy.common.world.WorldSection; -import net.fabricmc.loader.impl.util.log.Log; import org.lwjgl.system.MemoryUtil; import java.lang.invoke.MethodHandles; @@ -27,7 +26,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.StampedLock; -import static me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData.SECTION_METADATA_SIZE; import static org.lwjgl.opengl.ARBUniformBufferObject.glBindBufferBase; import static org.lwjgl.opengl.GL30C.glUniform1ui; import static org.lwjgl.opengl.GL42C.GL_UNIFORM_BARRIER_BIT; @@ -57,6 +55,7 @@ public class AsyncNodeManager { private final Thread thread; public final int maxNodeCount; + private final long geometryCapacity; private volatile boolean running = true; private final NodeManager manager; @@ -70,7 +69,7 @@ public class AsyncNodeManager { private volatile SyncResults resultCache2 = new SyncResults(); //Yes. this is stupid. yes. it is a large amount of runtime. Is it profiler bias, probably - private ConcurrentLinkedDeque buffersToFreeQueue = new ConcurrentLinkedDeque<>(); + private final ConcurrentLinkedDeque buffersToFreeQueue = new ConcurrentLinkedDeque<>(); //locals for during iteration @@ -86,8 +85,10 @@ public class AsyncNodeManager { // it MUST ONLY be accessed on the render thread // AsyncNodeManager will use an AsyncGeometryManager as the manager for the data store, and sync the results on the render thread this.geometryData = geometryData; + this.geometryCapacity = ((BasicSectionGeometryData)geometryData).getGeometryCapacityBytes(); this.maxNodeCount = maxNodeCount; + this.thread = new Thread(()->{ try { while (this.running) { @@ -99,7 +100,7 @@ public class AsyncNodeManager { }); this.thread.setName("Async Node Manager"); - this.geometryManager = new BasicAsyncGeometryManager(((BasicSectionGeometryData)geometryData).getMaxSectionCount(), ((BasicSectionGeometryData)geometryData).getGeometryCapacity()); + this.geometryManager = new BasicAsyncGeometryManager(((BasicSectionGeometryData)geometryData).getMaxSectionCount(), this.geometryCapacity); this.manager = new NodeManager(maxNodeCount, this.geometryManager, watcher); //Dont do the move... is just to much effort this.manager.setClear(new NodeManager.ICleaner() { @@ -446,6 +447,7 @@ public class AsyncNodeManager { } results.geometrySectionCount = this.geometryManager.getSectionCount(); + results.usedGeometry = this.geometryManager.getGeometryUsedBytes(); results.currentMaxNodeId = this.manager.getCurrentMaxNodeId(); this.needsWaitForSync |= results.geometryUploads.size() > UPLOAD_LIMIT;//Max of 200 uploads per frame :( @@ -520,6 +522,7 @@ public class AsyncNodeManager { } this.currentMaxNodeId = results.currentMaxNodeId; + this.usedGeometryAmount = results.usedGeometry; //Insert the result set into the cache if (!RESULT_CACHE_1_HANDLE.compareAndSet(this, null, results)) { @@ -541,6 +544,16 @@ public class AsyncNodeManager { return this.currentMaxNodeId; } + private long usedGeometryAmount = 0; + public long getUsedGeometryCapacity() { + return this.usedGeometryAmount; + } + + public long getGeometryCapacity() { + return this.geometryCapacity; + } + + //================================================================================================================== //Incoming events @@ -700,6 +713,7 @@ public class AsyncNodeManager { //Deltas for geometry store private int geometrySectionCount; + private long usedGeometry; private final Int2ObjectOpenHashMap geometryUploads = new Int2ObjectOpenHashMap<>(); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeCleaner.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeCleaner.java index b6c62b84..1f51a0e2 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeCleaner.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/NodeCleaner.java @@ -136,7 +136,8 @@ public class NodeCleaner { //return this.nodeManager.getGeometryManager().getRemainingCapacity() < 1_000_000_000L; //If used more than 75% of geometry buffer - return false;//return 3<((double)this.nodeManager.getGeometryManager().getUsedCapacity())/((double)this.nodeManager.getGeometryManager().getRemainingCapacity()); + long used = this.nodeManager.getUsedGeometryCapacity(); + return 1<((double)used)/((double)(this.nodeManager.getGeometryCapacity()-used)); } public void updateIds(IntOpenHashSet collection) { diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/geometry/BasicAsyncGeometryManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/geometry/BasicAsyncGeometryManager.java index 4d557154..73ca8f2d 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/section/geometry/BasicAsyncGeometryManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/geometry/BasicAsyncGeometryManager.java @@ -27,6 +27,7 @@ public class BasicAsyncGeometryManager implements IGeometryManager { //Note!: the int part is an unsigned int ptr, must be scaled by GEOMETRY_ELEMENT_SIZE private final Int2ObjectOpenHashMap heapUploads = new Int2ObjectOpenHashMap<>(1024);//Uploads into the buffer at the given location private final IntOpenHashSet heapRemoveUploads = new IntOpenHashSet(1024);//Any removals are added here, so that it can be properly synced + private long usedCapacity = 0; public BasicAsyncGeometryManager(int maxSectionCount, long geometryCapacity) { this.allocationSet = new HierarchicalBitSet(maxSectionCount); @@ -85,7 +86,7 @@ public class BasicAsyncGeometryManager implements IGeometryManager { var oldMetadata = this.sectionMetadata.set(id, null); int ptr = oldMetadata.geometryPtr; //Free from the heap - this.allocationHeap.free(Integer.toUnsignedLong(ptr)); + this.usedCapacity -= this.allocationHeap.free(Integer.toUnsignedLong(ptr)); //Free the upload if it was uploading var buf = this.heapUploads.remove(ptr); if (buf != null) { @@ -100,6 +101,7 @@ public class BasicAsyncGeometryManager implements IGeometryManager { int size = (int) (section.geometryBuffer.size/GEOMETRY_ELEMENT_SIZE); //Address int addr = (int)this.allocationHeap.alloc(size); + this.usedCapacity += size; //Create upload if (this.heapUploads.put(addr, section.geometryBuffer) != null) { throw new IllegalStateException(); @@ -126,6 +128,10 @@ public class BasicAsyncGeometryManager implements IGeometryManager { return this.allocationSet.getCount(); } + public long getGeometryUsedBytes() { + return this.usedCapacity * GEOMETRY_ELEMENT_SIZE; + } + public IntOpenHashSet getUpdateIds() { return this.invalidatedIds; } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/geometry/BasicSectionGeometryData.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/geometry/BasicSectionGeometryData.java index db8ab888..53536d79 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/section/geometry/BasicSectionGeometryData.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/geometry/BasicSectionGeometryData.java @@ -40,7 +40,7 @@ public class BasicSectionGeometryData implements IGeometryData { return this.maxSectionCount; } - public long getGeometryCapacity() {//In bytes + public long getGeometryCapacityBytes() {//In bytes return this.geometryBuffer.size(); }