From 9f604dfc2e9f7ba1a527cf9e1a2c71206514fb62 Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Fri, 28 Mar 2025 10:05:20 +1000 Subject: [PATCH] Changes and removals --- .../me/cortex/voxy/client/core/VoxelCore.java | 150 +--- .../core/rendering/VoxyRenderSystem.java | 137 ++++ .../building/RenderDataFactory4.java | 670 ---------------- .../building/RenderDataFactory5.java | 721 ------------------ .../cortex/voxy/common/world/WorldEngine.java | 15 +- .../cortex/voxy/commonImpl/VoxyInstance.java | 2 +- 6 files changed, 149 insertions(+), 1546 deletions(-) delete mode 100644 src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory4.java delete mode 100644 src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory5.java diff --git a/src/main/java/me/cortex/voxy/client/core/VoxelCore.java b/src/main/java/me/cortex/voxy/client/core/VoxelCore.java index 0bc27889..f13fa727 100644 --- a/src/main/java/me/cortex/voxy/client/core/VoxelCore.java +++ b/src/main/java/me/cortex/voxy/client/core/VoxelCore.java @@ -1,45 +1,18 @@ package me.cortex.voxy.client.core; -import com.mojang.blaze3d.systems.RenderSystem; import me.cortex.voxy.client.config.VoxyConfig; -import me.cortex.voxy.client.core.gl.Capabilities; -import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.model.ModelBakerySubsystem; -import me.cortex.voxy.client.core.rendering.*; -import me.cortex.voxy.client.core.rendering.building.RenderDataFactory4; +import me.cortex.voxy.client.core.rendering.building.RenderDataFactory45; import me.cortex.voxy.client.core.rendering.building.RenderGenerationService; -import me.cortex.voxy.client.core.rendering.post.PostProcessing; -import me.cortex.voxy.client.core.rendering.util.DownloadStream; -import me.cortex.voxy.client.core.util.IrisUtil; import me.cortex.voxy.client.saver.ContextSelectionSystem; -import me.cortex.voxy.client.taskbar.Taskbar; import me.cortex.voxy.common.Logger; -import me.cortex.voxy.common.util.MemoryBuffer; import me.cortex.voxy.common.world.WorldEngine; -import me.cortex.voxy.commonImpl.importers.WorldImporter; import me.cortex.voxy.common.thread.ServiceThreadPool; import me.cortex.voxy.common.world.WorldSection; import me.cortex.voxy.common.world.other.Mapper; -import me.cortex.voxy.commonImpl.VoxyCommon; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.hud.ClientBossBar; -import net.minecraft.client.render.Camera; -import net.minecraft.client.render.Frustum; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.entity.boss.BossBar; -import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; -import net.minecraft.world.World; -import net.minecraft.world.chunk.WorldChunk; -import org.joml.Matrix4f; -import org.lwjgl.opengl.GL11; -import java.io.File; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Predicate; import static org.lwjgl.opengl.GL30C.*; @@ -162,60 +135,6 @@ public class VoxelCore { - private void testMeshingPerformance() { - var modelService = new ModelBakerySubsystem(this.world.getMapper()); - var factory = new RenderDataFactory4(this.world, modelService.factory, false); - - List sections = new ArrayList<>(); - - System.out.println("Loading sections"); - for (int x = -17; x <= 17; x++) { - for (int z = -17; z <= 17; z++) { - for (int y = -1; y <= 4; y++) { - var section = this.world.acquire(0, x, y, z); - - int nonAir = 0; - for (long state : section.copyData()) { - nonAir += Mapper.isAir(state)?0:1; - modelService.requestBlockBake(Mapper.getBlockId(state)); - } - - if (nonAir > 500 && Math.abs(x) <= 16 && Math.abs(z) <= 16) { - sections.add(section); - } else { - section.release(); - } - } - } - } - - System.out.println("Baking models"); - { - //Bake everything - while (!modelService.areQueuesEmpty()) { - modelService.tick(); - glFinish(); - } - } - - System.out.println("Ready!"); - - { - int iteration = 0; - while (true) { - long start = System.currentTimeMillis(); - for (var section : sections) { - var mesh = factory.generateMesh(section); - - mesh.free(); - } - long delta = System.currentTimeMillis() - start; - System.out.println("Iteration: " + (iteration++) + " took " + delta + "ms, for an average of " + ((float)delta/sections.size()) + "ms per section"); - //System.out.println("Quad count: " + factory.quadCount); - } - } - - } private void testDbPerformance() { @@ -238,71 +157,4 @@ public class VoxelCore { long delta = System.currentTimeMillis() - start; System.out.println("Total "+delta+"ms " + ((double)delta/c) + "ms average" ); } - - - - private void testFullMesh() { - var modelService = new ModelBakerySubsystem(this.world.getMapper()); - var completedCounter = new AtomicInteger(); - var generationService = new RenderGenerationService(this.world, modelService, this.serviceThreadPool, a-> {completedCounter.incrementAndGet(); a.free();}, false); - - - var r = new Random(12345); - { - for (int i = 0; i < 10_000; i++) { - int x = (r.nextInt(256*2+2)-256)>>1;//-32 - int z = (r.nextInt(256*2+2)-256)>>1;//-32 - int y = r.nextInt(10)-2; - int lvl = 0;//r.nextInt(5); - long key = WorldEngine.getWorldSectionId(lvl, x>>lvl, y>>lvl, z>>lvl); - generationService.enqueueTask(key); - } - int i = 0; - while (true) { - modelService.tick(); - if (i++%5000==0) - System.out.println(completedCounter.get()); - glFinish(); - List a = new ArrayList<>(); - generationService.addDebugData(a); - if (a.getFirst().endsWith(" 0")) { - break; - } - } - } - - System.out.println("Running benchmark"); - while (true) - { - completedCounter.set(0); - long start = System.currentTimeMillis(); - int C = 200_000; - for (int i = 0; i < C; i++) { - int x = (r.nextInt(256 * 2 + 2) - 256) >> 1;//-32 - int z = (r.nextInt(256 * 2 + 2) - 256) >> 1;//-32 - int y = r.nextInt(10) - 2; - int lvl = 0;//r.nextInt(5); - long key = WorldEngine.getWorldSectionId(lvl, x >> lvl, y >> lvl, z >> lvl); - generationService.enqueueTask(key); - } - //int i = 0; - while (true) { - //if (i++%5000==0) - // System.out.println(completedCounter.get()); - modelService.tick(); - glFinish(); - List a = new ArrayList<>(); - generationService.addDebugData(a); - if (a.getFirst().endsWith(" 0")) { - break; - } - } - long delta = (System.currentTimeMillis()-start); - System.out.println("Time "+delta+"ms count: " + completedCounter.get() + " avg per mesh: " + ((double)delta/completedCounter.get()) + "ms"); - if (false) - break; - } - generationService.shutdown(); - modelService.shutdown(); - } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/VoxyRenderSystem.java b/src/main/java/me/cortex/voxy/client/core/rendering/VoxyRenderSystem.java index faa6ae48..e6a03453 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/VoxyRenderSystem.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/VoxyRenderSystem.java @@ -5,7 +5,10 @@ import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.client.core.gl.Capabilities; import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.model.ColourDepthTextureData; +import me.cortex.voxy.client.core.model.ModelBakerySubsystem; import me.cortex.voxy.client.core.model.ModelTextureBakery; +import me.cortex.voxy.client.core.rendering.building.RenderDataFactory45; +import me.cortex.voxy.client.core.rendering.building.RenderGenerationService; import me.cortex.voxy.client.core.rendering.post.PostProcessing; import me.cortex.voxy.client.core.rendering.util.DownloadStream; import me.cortex.voxy.client.core.rendering.util.RawDownloadStream; @@ -13,6 +16,9 @@ import me.cortex.voxy.client.core.util.IrisUtil; import me.cortex.voxy.common.Logger; import me.cortex.voxy.common.thread.ServiceThreadPool; import me.cortex.voxy.common.world.WorldEngine; +import me.cortex.voxy.common.world.WorldSection; +import me.cortex.voxy.common.world.other.Mapper; +import me.cortex.voxy.commonImpl.VoxyCommon; import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.Camera; @@ -22,19 +28,25 @@ import org.joml.Matrix4f; import org.lwjgl.opengl.GL11; import org.lwjgl.system.MemoryUtil; +import java.util.ArrayList; import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import static org.lwjgl.opengl.GL11C.glFinish; import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING; public class VoxyRenderSystem { private final RenderService renderer; private final PostProcessing postProcessing; + private final WorldEngine worldIn; public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) { //Trigger the shared index buffer loading SharedIndexBuffer.INSTANCE.id(); Capabilities.init();//Ensure clinit is called + this.worldIn = world; this.renderer = new RenderService(world, threadPool); this.postProcessing = new PostProcessing(); } @@ -173,4 +185,129 @@ public class VoxyRenderSystem { Logger.info("Shutting down post processor"); if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {Logger.error("Error shutting down post processor", e);}} } + + + + + + + + private void testMeshingPerformance() { + var modelService = new ModelBakerySubsystem(this.worldIn.getMapper()); + var factory = new RenderDataFactory45(this.worldIn, modelService.factory, false); + + List sections = new ArrayList<>(); + + System.out.println("Loading sections"); + for (int x = -17; x <= 17; x++) { + for (int z = -17; z <= 17; z++) { + for (int y = -1; y <= 4; y++) { + var section = this.worldIn.acquire(0, x, y, z); + + int nonAir = 0; + for (long state : section.copyData()) { + nonAir += Mapper.isAir(state)?0:1; + modelService.requestBlockBake(Mapper.getBlockId(state)); + } + + if (nonAir > 500 && Math.abs(x) <= 16 && Math.abs(z) <= 16) { + sections.add(section); + } else { + section.release(); + } + } + } + } + + System.out.println("Baking models"); + { + //Bake everything + while (!modelService.areQueuesEmpty()) { + modelService.tick(); + glFinish(); + } + } + + System.out.println("Ready!"); + + { + int iteration = 0; + while (true) { + long start = System.currentTimeMillis(); + for (var section : sections) { + var mesh = factory.generateMesh(section); + + mesh.free(); + } + long delta = System.currentTimeMillis() - start; + System.out.println("Iteration: " + (iteration++) + " took " + delta + "ms, for an average of " + ((float)delta/sections.size()) + "ms per section"); + //System.out.println("Quad count: " + factory.quadCount); + } + } + } + + 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 r = new Random(12345); + { + for (int i = 0; i < 10_000; i++) { + int x = (r.nextInt(256*2+2)-256)>>1;//-32 + int z = (r.nextInt(256*2+2)-256)>>1;//-32 + int y = r.nextInt(10)-2; + int lvl = 0;//r.nextInt(5); + long key = WorldEngine.getWorldSectionId(lvl, x>>lvl, y>>lvl, z>>lvl); + generationService.enqueueTask(key); + } + int i = 0; + while (true) { + modelService.tick(); + if (i++%5000==0) + System.out.println(completedCounter.get()); + glFinish(); + List a = new ArrayList<>(); + generationService.addDebugData(a); + if (a.getFirst().endsWith(" 0")) { + break; + } + } + } + + System.out.println("Running benchmark"); + while (true) + { + completedCounter.set(0); + long start = System.currentTimeMillis(); + int C = 200_000; + for (int i = 0; i < C; i++) { + int x = (r.nextInt(256 * 2 + 2) - 256) >> 1;//-32 + int z = (r.nextInt(256 * 2 + 2) - 256) >> 1;//-32 + int y = r.nextInt(10) - 2; + int lvl = 0;//r.nextInt(5); + long key = WorldEngine.getWorldSectionId(lvl, x >> lvl, y >> lvl, z >> lvl); + generationService.enqueueTask(key); + } + //int i = 0; + while (true) { + //if (i++%5000==0) + // System.out.println(completedCounter.get()); + modelService.tick(); + glFinish(); + List a = new ArrayList<>(); + generationService.addDebugData(a); + if (a.getFirst().endsWith(" 0")) { + break; + } + } + long delta = (System.currentTimeMillis()-start); + System.out.println("Time "+delta+"ms count: " + completedCounter.get() + " avg per mesh: " + ((double)delta/completedCounter.get()) + "ms"); + if (false) + break; + } + generationService.shutdown(); + modelService.shutdown(); + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory4.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory4.java deleted file mode 100644 index 7568048e..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory4.java +++ /dev/null @@ -1,670 +0,0 @@ -package me.cortex.voxy.client.core.rendering.building; - -import me.cortex.voxy.client.core.gl.Capabilities; -import me.cortex.voxy.client.core.model.ModelFactory; -import me.cortex.voxy.client.core.model.ModelQueries; -import me.cortex.voxy.client.core.util.Mesher2D; -import me.cortex.voxy.client.core.util.ScanMesher2D; -import me.cortex.voxy.common.util.MemoryBuffer; -import me.cortex.voxy.common.util.UnsafeUtil; -import me.cortex.voxy.common.world.WorldEngine; -import me.cortex.voxy.common.world.WorldSection; -import me.cortex.voxy.common.world.other.Mapper; -import me.cortex.voxy.commonImpl.VoxyCommon; -import org.lwjgl.system.MemoryUtil; - -import java.util.Arrays; - - -public class RenderDataFactory4 { - private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing"); - - private final WorldEngine world; - private final ModelFactory modelMan; - - //private final long[] sectionData = new long[32*32*32*2]; - private final long[] sectionData = new long[32*32*32*2]; - - private final int[] opaqueMasks = new int[32*32]; - - //TODO: emit directly to memory buffer instead of long arrays - - //Each axis gets a max quad count of 2^16 (65536 quads) since that is the max the basic geometry manager can handle - private final MemoryBuffer directionalQuadBuffer = new MemoryBuffer(6*(8*(1<<16))); - private final long directionalQuadBufferPtr = this.directionalQuadBuffer.address; - private final int[] directionalQuadCounters = new int[6];//Maybe change to short? (or long /w raw pointers) - - - private int minX; - private int minY; - private int minZ; - private int maxX; - private int maxY; - private int maxZ; - - private int quadCount = 0; - - - //Wont work for double sided quads - private final class Mesher extends ScanMesher2D { - public int auxiliaryPosition = 0; - public boolean doAuxiliaryFaceOffset = true; - public int axis = 0; - - //Note x, z are in top right - @Override - protected void emitQuad(int x, int z, int length, int width, long data) { - RenderDataFactory4.this.quadCount++; - - if (VERIFY_MESHING) { - if (length<1||length>16) { - throw new IllegalStateException("length out of bounds: " + length); - } - if (width<1||width>16) { - throw new IllegalStateException("width out of bounds: " + width); - } - if (x<0||x>31) { - throw new IllegalStateException("x out of bounds: " + x); - } - if (z<0||z>31) { - throw new IllegalStateException("z out of bounds: " + z); - } - if (x-(length-1)<0 || z-(width-1)<0) { - throw new IllegalStateException("dim out of bounds: " + (x-(length-1))+", " + (z-(width-1))); - } - - } - x -= length-1; - z -= width-1; - - if (this.axis == 2) { - //Need to swizzle the data if on x axis - int tmp = x; - x = z; - z = tmp; - - tmp = length; - length = width; - width = tmp; - } - - //Lower 26 bits can be auxiliary data since that is where quad position information goes; - int auxData = (int) (data&((1<<26)-1)); - int faceSide = auxData&1; - data &= ~(data&((1<<26)-1)); - - final int axis = this.axis; - int face = (axis<<1)|faceSide; - int encodedPosition = face; - - //Shift up if is negative axis - int auxPos = this.auxiliaryPosition; - auxPos += this.doAuxiliaryFaceOffset?(1-faceSide):0;//Shift - - if (VERIFY_MESHING) { - if (auxPos > 31) { - throw new IllegalStateException("OOB face: " + auxPos + ", " + faceSide); - } - } - - encodedPosition |= ((width - 1) << 7) | ((length - 1) << 3); - - encodedPosition |= x << (axis==2?16:21); - encodedPosition |= z << (axis==1?16:11); - encodedPosition |= auxPos << (axis==0?16:(axis==1?11:21)); - - long quad = data | encodedPosition; - - - MemoryUtil.memPutLong(RenderDataFactory4.this.directionalQuadBufferPtr + (RenderDataFactory4.this.directionalQuadCounters[face]++)*8L + face*8L*(1<<16), quad); - } - } - - - private final Mesher blockMesher = new Mesher(); - - public RenderDataFactory4(WorldEngine world, ModelFactory modelManager, boolean emitMeshlets) { - this.world = world; - this.modelMan = modelManager; - } - - - //TODO: MAKE a render cache that caches each WorldSection directional face generation, cause then can just pull that directly - // instead of needing to regen the entire thing - - - //Ok so the idea for fluid rendering is to make it use a seperate mesher and use a different code path for it - // since fluid states are explicitly overlays over the base block - // can do funny stuff like double rendering - - private static final boolean USE_UINT64 = Capabilities.INSTANCE.INT64_t; - public static final int QUADS_PER_MESHLET = 14; - private static void writePos(long ptr, long pos) { - if (USE_UINT64) { - MemoryUtil.memPutLong(ptr, pos); - } else { - MemoryUtil.memPutInt(ptr, (int) (pos>>32)); - MemoryUtil.memPutInt(ptr + 4, (int)pos); - } - } - - private void prepareSectionData() { - final var sectionData = this.sectionData; - int opaque = 0; - - for (int i = 0; i < 32*32*32;) { - long block = sectionData[i + 32 * 32 * 32];//Get the block mapping - - int modelId = this.modelMan.getModelId(Mapper.getBlockId(block)); - long modelMetadata = this.modelMan.getModelMetadataFromClientId(modelId); - - sectionData[i * 2] = modelId | ((long) (Mapper.getLightId(block)) << 16) | (ModelQueries.isBiomeColoured(modelMetadata)?(((long) (Mapper.getBiomeId(block))) << 24):0); - sectionData[i * 2 + 1] = modelMetadata; - - boolean isFullyOpaque = ModelQueries.isFullyOpaque(modelMetadata); - opaque |= (isFullyOpaque ? 1:0) << (i & 31); - - //TODO: here also do bitmask of what neighboring sections are needed to compute (may be getting rid of this in future) - - //Do increment here - i++; - - if ((i & 31) == 0) { - this.opaqueMasks[(i >> 5) - 1] = opaque; - opaque = 0; - } - } - } - - - private void generateYZFaces() { - for (int axis = 0; axis < 2; axis++) { - this.blockMesher.axis = axis; - for (int layer = 0; layer < 31; layer++) { - this.blockMesher.auxiliaryPosition = layer; - for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections - int pidx = axis==0 ?(layer*32+other):(other*32+layer); - int skipAmount = axis==0?32:1; - - int current = this.opaqueMasks[pidx]; - int next = this.opaqueMasks[pidx + skipAmount]; - - int msk = current ^ next; - if (msk == 0) { - this.blockMesher.skip(32); - continue; - } - - //TODO: For boarder sections, should NOT EMIT neighbors faces - int faceForwardMsk = msk & current; - int cIdx = -1; - while (msk != 0) { - int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index - int delta = index - cIdx - 1; - cIdx = index; //index--; - if (delta != 0) this.blockMesher.skip(delta); - msk &= ~Integer.lowestOneBit(msk); - - int facingForward = ((faceForwardMsk >> index) & 1); - - { - int idx = index + (pidx*32); - - //TODO: swap this out for something not getting the next entry - long A = this.sectionData[idx * 2]; - long B = this.sectionData[(idx + skipAmount * 32) * 2]; - - //Flip data with respect to facing direction - long selfModel = facingForward == 1 ? A : B; - long nextModel = facingForward == 1 ? B : A; - - //Example thing thats just wrong but as example - this.blockMesher.putNext(((long) facingForward) |//Facing - ((selfModel & 0xFFFF) << 26) | //ModelId - (((nextModel>>16)&0xFF) << 55) |//Lighting - ((selfModel&(0x1FFL<<24))<<(46-24))//biomeId - ); - } - } - this.blockMesher.endRow(); - } - this.blockMesher.finish(); - } - - if (true) { - this.blockMesher.doAuxiliaryFaceOffset = false; - //Hacky generate section side faces (without check neighbor section) - for (int side = 0; side < 2; side++) { - int layer = side == 0 ? 0 : 31; - this.blockMesher.auxiliaryPosition = layer; - for (int other = 0; other < 32; other++) { - int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer); - int msk = this.opaqueMasks[pidx]; - if (msk == 0) { - this.blockMesher.skip(32); - continue; - } - - int cIdx = -1; - while (msk != 0) { - int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index - int delta = index - cIdx - 1; - cIdx = index; //index--; - if (delta != 0) this.blockMesher.skip(delta); - msk &= ~Integer.lowestOneBit(msk); - - { - int idx = index + (pidx * 32); - - //TODO: swap this out for something not getting the next entry - long A = this.sectionData[idx * 2]; - - //Example thing thats just wrong but as example - this.blockMesher.putNext((long) (side == 0 ? 0L : 1L) | - ((A & 0xFFFFL) << 26) | - (((0xFFL) & 0xFF) << 55) | - ((A&(0x1FFL<<24))<<(46-24)) - ); - } - } - this.blockMesher.endRow(); - } - - this.blockMesher.finish(); - } - this.blockMesher.doAuxiliaryFaceOffset = true; - } - } - } - - - private final Mesher[] xAxisMeshers = new Mesher[32]; - { - for (int i = 0; i < 32; i++) { - var mesher = new Mesher(); - mesher.auxiliaryPosition = i; - mesher.axis = 2;//X axis - this.xAxisMeshers[i] = mesher; - } - } - - private static final long X_I_MSK = 0x4210842108421L; - private void generateXFaces() { - for (int y = 0; y < 32; y++) { - long sumA = 0; - long sumB = 0; - long sumC = 0; - int partialHasCount = -1; - int msk = 0; - for (int z = 0; z < 32; z++) { - int lMsk = this.opaqueMasks[y*32+z]; - msk = (lMsk^(lMsk>>>1)); - msk &= -1>>>1;//Remove top bit as we dont actually know/have the data for that slice - - //Always increment cause can do funny trick (i.e. -1 on skip amount) - sumA += X_I_MSK; - sumB += X_I_MSK; - sumC += X_I_MSK; - - partialHasCount &= ~msk; - - if (z == 30 && partialHasCount != 0) {//Hackfix for incremental count overflow issue - int cmsk = partialHasCount; - while (cmsk!=0) { - int index = Integer.numberOfTrailingZeros(cmsk); - cmsk &= ~Integer.lowestOneBit(cmsk); - //TODO: fixme! check this is correct or if should be 30 - this.xAxisMeshers[index].skip(31); - } - //Clear the sum - sumA &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount), X_I_MSK)*0x1F); - sumB &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>11, X_I_MSK)*0x1F); - sumC &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>22, X_I_MSK)*0x1F); - } - - if (msk == 0) { - continue; - } - - /* - {//Dont need this as can just increment everything then -1 in mask - //Compute and increment skips for indexes - long imsk = Integer.toUnsignedLong(~msk);// we only want to increment where there isnt a face - sumA += Long.expand(imsk, X_I_MSK); - sumB += Long.expand(imsk>>11, X_I_MSK); - sumC += Long.expand(imsk>>22, X_I_MSK); - }*/ - - int faceForwardMsk = msk&lMsk; - int iter = msk; - while (iter!=0) { - int index = Integer.numberOfTrailingZeros(iter); - iter &= ~Integer.lowestOneBit(iter); - - var mesher = this.xAxisMeshers[index]; - - int skipCount;//Compute the skip count - {//TODO: Branch-less - //Compute skip and clear - if (index<11) { - skipCount = (int) (sumA>>(index*5)); - sumA &= ~(0x1FL<<(index*5)); - } else if (index<22) { - skipCount = (int) (sumB>>((index-11)*5)); - sumB &= ~(0x1FL<<((index-11)*5)); - } else { - skipCount = (int) (sumC>>((index-22)*5)); - sumC &= ~(0x1FL<<((index-22)*5)); - } - skipCount &= 0x1F; - skipCount--; - } - - if (skipCount != 0) { - mesher.skip(skipCount); - } - - int facingForward = ((faceForwardMsk>>index)&1); - { - int idx = index + (z * 32) + (y * 32 * 32); - //TODO: swap this out for something not getting the next entry - long A = this.sectionData[idx * 2]; - long B = this.sectionData[(idx + 1) * 2]; - - //Flip data with respect to facing direction - long selfModel = facingForward==1?A:B; - long nextModel = facingForward==1?B:A; - - //Example thing thats just wrong but as example - mesher.putNext(((long) facingForward) |//Facing - ((selfModel & 0xFFFF) << 26) | //ModelId - (((nextModel>>16)&0xFF) << 55) |//Lighting - ((selfModel&(0x1FFL<<24))<<(46-24))//biomeId - ); - } - } - } - - //Need to skip the remaining entries in the skip array - { - msk = ~msk;//Invert the mask as we only need to set stuff that isnt 0 - while (msk!=0) { - int index = Integer.numberOfTrailingZeros(msk); - msk &= ~Integer.lowestOneBit(msk); - int skipCount; - if (index < 11) { - skipCount = (int) (sumA>>(index*5)); - } else if (index<22) { - skipCount = (int) (sumB>>((index-11)*5)); - } else { - skipCount = (int) (sumC>>((index-22)*5)); - } - skipCount &= 0x1F; - - if (skipCount != 0) { - this.xAxisMeshers[index].skip(skipCount); - } - } - } - } - - //Generate the side faces, hackily, using 0 and 1 mesher - if (true) { - var ma = this.xAxisMeshers[0]; - var mb = this.xAxisMeshers[31]; - ma.finish(); - mb.finish(); - ma.doAuxiliaryFaceOffset = false; - mb.doAuxiliaryFaceOffset = false; - for (int y = 0; y < 32; y++) { - int skipA = 0; - int skipB = 0; - for (int z = 0; z < 32; z++) { - int i = y*32+z; - int msk = this.opaqueMasks[i]; - if ((msk & 1) != 0) { - ma.skip(skipA); skipA = 0; - - long A = this.sectionData[(i<<5) * 2]; - - ma.putNext(0L | ((A&0xFFFF)<<26) | (((0xFFL)&0xFF)<<55)|((A&(0x1FFL<<24))<<(46-24))); - } else {skipA++;} - - if ((msk & (1<<31)) != 0) { - mb.skip(skipB); skipB = 0; - - long A = this.sectionData[(i*32+31) * 2]; - - mb.putNext(1L | ((A&0xFFFF)<<26) | (((0xFFL)&0xFF)<<55)|((A&(0x1FFL<<24))<<(46-24))); - } else {skipB++;} - } - ma.skip(skipA); - mb.skip(skipB); - } - ma.finish(); - mb.finish(); - ma.doAuxiliaryFaceOffset = true; - mb.doAuxiliaryFaceOffset = true; - } - - for (var mesher : this.xAxisMeshers) { - mesher.finish(); - } - } - - /* - private static long createQuad() { - ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(metadata)?1:0)) | otherFlags - - - long data = Integer.toUnsignedLong(array[i*3+1]); - data |= ((long) array[i*3+2])<<32; - long encodedQuad = Integer.toUnsignedLong(QuadEncoder.encodePosition(face, otherAxis, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46); - }*/ - - //section is already acquired and gets released by the parent - public BuiltSection generateMesh(WorldSection section) { - //Copy section data to end of array so that can mutate array while reading safely - section.copyDataTo(this.sectionData, 32*32*32); - - this.quadCount = 0; - - this.minX = Integer.MAX_VALUE; - this.minY = Integer.MAX_VALUE; - this.minZ = Integer.MAX_VALUE; - this.maxX = Integer.MIN_VALUE; - this.maxY = Integer.MIN_VALUE; - this.maxZ = Integer.MIN_VALUE; - - Arrays.fill(this.directionalQuadCounters, (short) 0); - - - this.world.acquire(section.lvl, section.x+1, section.y, section.z).release(); - this.world.acquire(section.lvl, section.x-1, section.y, section.z).release(); - - this.world.acquire(section.lvl, section.x, section.y+1, section.z).release(); - this.world.acquire(section.lvl, section.x, section.y-1, section.z).release(); - - this.world.acquire(section.lvl, section.x, section.y, section.z+1).release(); - this.world.acquire(section.lvl, section.x, section.y, section.z-1).release(); - - - - //Prepare everything - this.prepareSectionData(); - - - this.generateYZFaces(); - this.generateXFaces(); - - - //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 - // this stops e.g. multiple layers of glass (and ocean) from having 3000 layers of quads etc - - if (this.quadCount == 0) { - return BuiltSection.emptyWithChildren(section.key, section.getNonEmptyChildren()); - } - - //TODO: FIXME AND OPTIMIZE, get rid of the stupid quad collector bullshit - - int[] offsets = new int[8]; - var buff = new MemoryBuffer(this.quadCount * 8L); - long ptr = buff.address; - int coff = 0; - - for (int face = 0; face < 6; face++) { - offsets[face + 2] = coff; - int size = this.directionalQuadCounters[face]; - UnsafeUtil.memcpy(this.directionalQuadBufferPtr + (face*(8*(1<<16))), ptr + coff*8L, (size* 8L)); - coff += size; - } - - - int aabb = 0; - aabb |= 0; - aabb |= 0<<5; - aabb |= 0<<10; - aabb |= (31)<<15; - aabb |= (31)<<20; - aabb |= (31)<<25; - - return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets); - - /* - buff = new MemoryBuffer(bufferSize * 8L); - long ptr = buff.address; - int coff = 0; - - //Ordering is: translucent, double sided quads, directional quads - offsets[0] = coff; - int size = this.translucentQuadCollector.size(); - LongArrayList arrayList = this.translucentQuadCollector; - for (int i = 0; i < size; i++) { - long data = arrayList.getLong(i); - MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data); - } - - offsets[1] = coff; - size = this.doubleSidedQuadCollector.size(); - arrayList = this.doubleSidedQuadCollector; - for (int i = 0; i < size; i++) { - long data = arrayList.getLong(i); - MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data); - } - - for (int face = 0; face < 6; face++) { - offsets[face + 2] = coff; - final LongArrayList faceArray = this.directionalQuadCollectors[face]; - size = faceArray.size(); - for (int i = 0; i < size; i++) { - long data = faceArray.getLong(i); - MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data); - } - } - - int aabb = 0; - aabb |= this.minX; - aabb |= this.minY<<5; - aabb |= this.minZ<<10; - aabb |= (this.maxX-this.minX)<<15; - aabb |= (this.maxY-this.minY)<<20; - aabb |= (this.maxZ-this.minZ)<<25; - - return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets); - */ - } - - public void free() { - this.directionalQuadBuffer.free(); - } - - - - - - - - - - - - - - - //Returns true if a face was placed - private boolean putFluidFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfClientModelId, int selfBlockId, long facingState, long facingMetadata, int facingClientModelId, int a, int b) { - int selfFluidClientId = this.modelMan.getFluidClientStateId(selfClientModelId); - long selfFluidMetadata = this.modelMan.getModelMetadataFromClientId(selfFluidClientId); - - int facingFluidClientId = -1; - if (ModelQueries.containsFluid(facingMetadata)) { - facingFluidClientId = this.modelMan.getFluidClientStateId(facingClientModelId); - } - - //If both of the states are the same, then dont render the fluid face - if (selfFluidClientId == facingFluidClientId) { - return false; - } - - if (facingFluidClientId != -1) { - //TODO: OPTIMIZE - if (this.world.getMapper().getBlockStateFromBlockId(selfBlockId).getBlock() == this.world.getMapper().getBlockStateFromBlockId(Mapper.getBlockId(facingState)).getBlock()) { - return false; - } - } - - - if (ModelQueries.faceOccludes(facingMetadata, opposingFace)) { - return false; - } - - //if the model has a fluid state but is not a liquid need to see if the solid state had a face rendered and that face is occluding, if so, dont render the fluid state face - if ((!ModelQueries.isFluid(metadata)) && ModelQueries.faceOccludes(metadata, face)) { - return false; - } - - - - //TODO:FIXME SOMEHOW THIS IS CRITICAL!!!!!!!!!!!!!!!!!! - // so there is one more issue need to be fixed, if water is layered ontop of eachother, the side faces depend on the water state ontop - // this has been hackfixed in the model texture bakery but a proper solution that doesnt explode the sides of the water textures needs to be done - // the issue is that the fluid rendering depends on the up state aswell not just the face state which is really really painful to account for - // e.g the sides of a full water is 8 high or something, not the full block height, this results in a gap between water layers - - - - long otherFlags = 0; - otherFlags |= ModelQueries.isTranslucent(selfFluidMetadata)?1L<<33:0; - otherFlags |= ModelQueries.isDoubleSided(selfFluidMetadata)?1L<<34:0; - mesher.put(a, b, ((long)selfFluidClientId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(selfFluidMetadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(selfFluidMetadata)?1:0)) | otherFlags); - return true; - } - - //Returns true if a face was placed - private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long metadata, int clientModelId, int selfBiome, long facingModelId, long facingMetadata, int selfLight, int facingLight, int a, int b) { - if (ModelQueries.cullsSame(metadata) && clientModelId == facingModelId) { - //If we are facing a block, and we are both the same state, dont render that face - return false; - } - - //If face can be occluded and is occluded from the facing block, then dont render the face - if (ModelQueries.faceCanBeOccluded(metadata, face) && ModelQueries.faceOccludes(facingMetadata, opposingFace)) { - return false; - } - - long otherFlags = 0; - otherFlags |= ModelQueries.isTranslucent(metadata)?1L<<33:0; - otherFlags |= ModelQueries.isDoubleSided(metadata)?1L<<34:0; - mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?selfLight:facingLight))<<16) | (ModelQueries.isBiomeColoured(metadata)?(((long) Mapper.getBiomeId(selfBiome))<<24):0) | otherFlags); - return true; - } - - private static int getMeshletHoldingCount(int quads, int quadsPerMeshlet, int meshletSize) { - return ((quads+(quadsPerMeshlet-1))/quadsPerMeshlet)*meshletSize; - } - - public static int alignUp(int n, int alignment) { - return (n + alignment - 1) & -alignment; - } -} \ No newline at end of file diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory5.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory5.java deleted file mode 100644 index 343173ca..00000000 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory5.java +++ /dev/null @@ -1,721 +0,0 @@ -package me.cortex.voxy.client.core.rendering.building; - -import me.cortex.voxy.client.core.gl.Capabilities; -import me.cortex.voxy.client.core.model.ModelFactory; -import me.cortex.voxy.client.core.model.ModelQueries; -import me.cortex.voxy.client.core.util.Mesher2D; -import me.cortex.voxy.client.core.util.ScanMesher2D; -import me.cortex.voxy.common.util.MemoryBuffer; -import me.cortex.voxy.common.util.UnsafeUtil; -import me.cortex.voxy.common.world.WorldEngine; -import me.cortex.voxy.common.world.WorldSection; -import me.cortex.voxy.common.world.other.Mapper; -import me.cortex.voxy.commonImpl.VoxyCommon; -import org.lwjgl.system.MemoryUtil; - -import java.util.Arrays; - - -public class RenderDataFactory5 { - private static final boolean VERIFY_MESHING = VoxyCommon.isVerificationFlagOn("verifyMeshing"); - - private final WorldEngine world; - private final ModelFactory modelMan; - - //private final long[] sectionData = new long[32*32*32*2]; - private final long[] sectionData = new long[32*32*32*2]; - - private final int[] opaqueMasks = new int[32*32]; - private final int[] nonOpaqueMasks = new int[32*32]; - - - //TODO: emit directly to memory buffer instead of long arrays - - //Each axis gets a max quad count of 2^16 (65536 quads) since that is the max the basic geometry manager can handle - private final MemoryBuffer directionalQuadBuffer = new MemoryBuffer(6*(8*(1<<16))); - private final long directionalQuadBufferPtr = this.directionalQuadBuffer.address; - private final int[] directionalQuadCounters = new int[6];//Maybe change to short? (or long /w raw pointers) - - - private int minX; - private int minY; - private int minZ; - private int maxX; - private int maxY; - private int maxZ; - - private int quadCount = 0; - - - //Wont work for double sided quads - private final class Mesher extends ScanMesher2D { - public int auxiliaryPosition = 0; - public boolean doAuxiliaryFaceOffset = true; - public int axis = 0; - - //Note x, z are in top right - @Override - protected void emitQuad(int x, int z, int length, int width, long data) { - RenderDataFactory5.this.quadCount++; - - if (VERIFY_MESHING) { - if (length<1||length>16) { - throw new IllegalStateException("length out of bounds: " + length); - } - if (width<1||width>16) { - throw new IllegalStateException("width out of bounds: " + width); - } - if (x<0||x>31) { - throw new IllegalStateException("x out of bounds: " + x); - } - if (z<0||z>31) { - throw new IllegalStateException("z out of bounds: " + z); - } - if (x-(length-1)<0 || z-(width-1)<0) { - throw new IllegalStateException("dim out of bounds: " + (x-(length-1))+", " + (z-(width-1))); - } - - } - x -= length-1; - z -= width-1; - - if (this.axis == 2) { - //Need to swizzle the data if on x axis - int tmp = x; - x = z; - z = tmp; - - tmp = length; - length = width; - width = tmp; - } - - //Lower 26 bits can be auxiliary data since that is where quad position information goes; - int auxData = (int) (data&((1<<26)-1)); - int faceSide = auxData&1; - data &= ~(data&((1<<26)-1)); - - final int axis = this.axis; - int face = (axis<<1)|faceSide; - int encodedPosition = face; - - //Shift up if is negative axis - int auxPos = this.auxiliaryPosition; - auxPos += this.doAuxiliaryFaceOffset?(1-faceSide):0;//Shift - - if (VERIFY_MESHING) { - if (auxPos > 31) { - throw new IllegalStateException("OOB face: " + auxPos + ", " + faceSide); - } - } - - encodedPosition |= ((width - 1) << 7) | ((length - 1) << 3); - - encodedPosition |= x << (axis==2?16:21); - encodedPosition |= z << (axis==1?16:11); - encodedPosition |= auxPos << (axis==0?16:(axis==1?11:21)); - - long quad = data | encodedPosition; - - - MemoryUtil.memPutLong(RenderDataFactory5.this.directionalQuadBufferPtr + (RenderDataFactory5.this.directionalQuadCounters[face]++)*8L + face*8L*(1<<16), quad); - } - } - - - private final Mesher blockMesher = new Mesher(); - private final Mesher partiallyOpaqueMesher = new Mesher(); - - public RenderDataFactory5(WorldEngine world, ModelFactory modelManager, boolean emitMeshlets) { - this.world = world; - this.modelMan = modelManager; - } - - private int prepareSectionData() { - final var sectionData = this.sectionData; - int opaque = 0; - int notEmpty = 0; - - int neighborAcquireMsk = 0;//-+x, -+y, -+Z - for (int i = 0; i < 32*32*32;) { - long block = sectionData[i + 32 * 32 * 32];//Get the block mapping - - int modelId = this.modelMan.getModelId(Mapper.getBlockId(block)); - long modelMetadata = this.modelMan.getModelMetadataFromClientId(modelId); - - sectionData[i * 2] = modelId | ((long) (Mapper.getLightId(block)) << 16) | (ModelQueries.isBiomeColoured(modelMetadata)?(((long) (Mapper.getBiomeId(block))) << 24):0); - sectionData[i * 2 + 1] = modelMetadata; - - boolean isFullyOpaque = ModelQueries.isFullyOpaque(modelMetadata); - opaque |= (isFullyOpaque ? 1:0) << (i & 31); - notEmpty |= (modelId!=0 ? 1:0) << (i & 31); - - //TODO: here also do bitmask of what neighboring sections are needed to compute (may be getting rid of this in future) - - //Do increment here - i++; - - if ((i & 31) == 0) { - this.opaqueMasks[(i >> 5) - 1] = opaque; - this.nonOpaqueMasks[(i >> 5) - 1] = notEmpty^opaque; - - int neighborMsk = 0; - //-+x - neighborMsk |= notEmpty&1; - neighborMsk |= (notEmpty>>>30)&0b10; - - //notEmpty = (notEmpty != 0)?1:0; - neighborMsk |= notEmpty!=0&&((i>>5)&0x1F)==0?0b100:0;//-z - neighborMsk |= notEmpty!=0&&((i>>5)&0x1F)==31?0b1000:0;//+z - neighborMsk |= notEmpty!=0&&(i>>10)==0?0b10000:0;//-y - neighborMsk |= notEmpty!=0&&(i>>10)==31?0b100000:0;//+y - - neighborAcquireMsk |= neighborMsk; - - - opaque = 0; - notEmpty = 0; - } - } - return neighborAcquireMsk; - } - - - private void generateYZFaces() { - for (int axis = 0; axis < 2; axis++) { - this.blockMesher.axis = axis; - for (int layer = 0; layer < 31; layer++) { - this.blockMesher.auxiliaryPosition = layer; - for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections - int pidx = axis==0 ?(layer*32+other):(other*32+layer); - int skipAmount = axis==0?32:1; - - int current = this.opaqueMasks[pidx]; - int next = this.opaqueMasks[pidx + skipAmount]; - - int msk = current ^ next; - if (msk == 0) { - this.blockMesher.skip(32); - continue; - } - - //TODO: For boarder sections, should NOT EMIT neighbors faces - int faceForwardMsk = msk & current; - int cIdx = -1; - while (msk != 0) { - int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index - int delta = index - cIdx - 1; - cIdx = index; //index--; - if (delta != 0) this.blockMesher.skip(delta); - msk &= ~Integer.lowestOneBit(msk); - - int facingForward = ((faceForwardMsk >> index) & 1); - - { - int idx = index + (pidx*32); - - //TODO: swap this out for something not getting the next entry - long A = this.sectionData[idx * 2]; - long B = this.sectionData[(idx + skipAmount * 32) * 2]; - - //Flip data with respect to facing direction - long selfModel = facingForward == 1 ? A : B; - long nextModel = facingForward == 1 ? B : A; - - //Example thing thats just wrong but as example - this.blockMesher.putNext(((long) facingForward) |//Facing - ((selfModel & 0xFFFF) << 26) | //ModelId - (((nextModel>>16)&0xFF) << 55) |//Lighting - ((selfModel&(0x1FFL<<24))<<(46-24))//biomeId - ); - } - } - this.blockMesher.endRow(); - } - this.blockMesher.finish(); - } - - if (true) { - this.blockMesher.doAuxiliaryFaceOffset = false; - //Hacky generate section side faces (without check neighbor section) - for (int side = 0; side < 2; side++) { - int layer = side == 0 ? 0 : 31; - this.blockMesher.auxiliaryPosition = layer; - for (int other = 0; other < 32; other++) { - int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer); - int msk = this.opaqueMasks[pidx]; - if (msk == 0) { - this.blockMesher.skip(32); - continue; - } - - int cIdx = -1; - while (msk != 0) { - int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index - int delta = index - cIdx - 1; - cIdx = index; //index--; - if (delta != 0) this.blockMesher.skip(delta); - msk &= ~Integer.lowestOneBit(msk); - - { - int idx = index + (pidx * 32); - - //TODO: swap this out for something not getting the next entry - long A = this.sectionData[idx * 2]; - - //Example thing thats just wrong but as example - this.blockMesher.putNext((long) (side == 0 ? 0L : 1L) | - ((A & 0xFFFFL) << 26) | - (((0xFFL) & 0xFF) << 55) | - ((A&(0x1FFL<<24))<<(46-24)) - ); - } - } - this.blockMesher.endRow(); - } - - this.blockMesher.finish(); - } - this.blockMesher.doAuxiliaryFaceOffset = true; - } - - - {//Non fully opaque geometry - //Note: think is ok to just reuse.. blockMesher - this.blockMesher.axis = axis; - for (int layer = 0; layer < 32; layer++) { - this.blockMesher.auxiliaryPosition = layer; - for (int other = 0; other < 32; other++) {//TODO: need to do the faces that border sections - int pidx = axis == 0 ? (layer * 32 + other) : (other * 32 + layer); - - int msk = this.nonOpaqueMasks[pidx]; - - if (msk == 0) { - this.blockMesher.skip(32); - continue; - } - - - int cIdx = -1; - while (msk != 0) { - int index = Integer.numberOfTrailingZeros(msk);//Is also the x-axis index - int delta = index - cIdx - 1; - cIdx = index; //index--; - if (delta != 0) this.blockMesher.skip(delta); - msk &= ~Integer.lowestOneBit(msk); - - { - int idx = index + (pidx * 32); - - //TODO: swap this out for something not getting the next entry - long A = this.sectionData[idx * 2]; - - //Example thing thats just wrong but as example - this.blockMesher.putNext((long) (false ? 0L : 1L) | - ((A & 0xFFFFL) << 26) | - (((0xFFL) & 0xFF) << 55) | - ((A&(0x1FFL<<24))<<(46-24)) - ); - } - } - this.blockMesher.endRow(); - } - this.blockMesher.finish(); - } - } - - - } - } - - - private final Mesher[] xAxisMeshers = new Mesher[32]; - { - for (int i = 0; i < 32; i++) { - var mesher = new Mesher(); - mesher.auxiliaryPosition = i; - mesher.axis = 2;//X axis - this.xAxisMeshers[i] = mesher; - } - } - - private static final long X_I_MSK = 0x4210842108421L; - private void generateXFaces() { - for (int y = 0; y < 32; y++) { - long sumA = 0; - long sumB = 0; - long sumC = 0; - int partialHasCount = -1; - int msk = 0; - for (int z = 0; z < 32; z++) { - int lMsk = this.opaqueMasks[y*32+z]; - msk = (lMsk^(lMsk>>>1)); - msk &= -1>>>1;//Remove top bit as we dont actually know/have the data for that slice - - //Always increment cause can do funny trick (i.e. -1 on skip amount) - sumA += X_I_MSK; - sumB += X_I_MSK; - sumC += X_I_MSK; - - partialHasCount &= ~msk; - - if (z == 30 && partialHasCount != 0) {//Hackfix for incremental count overflow issue - int cmsk = partialHasCount; - while (cmsk!=0) { - int index = Integer.numberOfTrailingZeros(cmsk); - cmsk &= ~Integer.lowestOneBit(cmsk); - //TODO: fixme! check this is correct or if should be 30 - this.xAxisMeshers[index].skip(31); - } - //Clear the sum - sumA &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount), X_I_MSK)*0x1F); - sumB &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>11, X_I_MSK)*0x1F); - sumC &= ~(Long.expand(Integer.toUnsignedLong(partialHasCount)>>22, X_I_MSK)*0x1F); - } - - if (msk == 0) { - continue; - } - - /* - {//Dont need this as can just increment everything then -1 in mask - //Compute and increment skips for indexes - long imsk = Integer.toUnsignedLong(~msk);// we only want to increment where there isnt a face - sumA += Long.expand(imsk, X_I_MSK); - sumB += Long.expand(imsk>>11, X_I_MSK); - sumC += Long.expand(imsk>>22, X_I_MSK); - }*/ - - int faceForwardMsk = msk&lMsk; - int iter = msk; - while (iter!=0) { - int index = Integer.numberOfTrailingZeros(iter); - iter &= ~Integer.lowestOneBit(iter); - - var mesher = this.xAxisMeshers[index]; - - int skipCount;//Compute the skip count - {//TODO: Branch-less - //Compute skip and clear - if (index<11) { - skipCount = (int) (sumA>>(index*5)); - sumA &= ~(0x1FL<<(index*5)); - } else if (index<22) { - skipCount = (int) (sumB>>((index-11)*5)); - sumB &= ~(0x1FL<<((index-11)*5)); - } else { - skipCount = (int) (sumC>>((index-22)*5)); - sumC &= ~(0x1FL<<((index-22)*5)); - } - skipCount &= 0x1F; - skipCount--; - } - - if (skipCount != 0) { - mesher.skip(skipCount); - } - - int facingForward = ((faceForwardMsk>>index)&1); - { - int idx = index + (z * 32) + (y * 32 * 32); - //TODO: swap this out for something not getting the next entry - long A = this.sectionData[idx * 2]; - long B = this.sectionData[(idx + 1) * 2]; - - //Flip data with respect to facing direction - long selfModel = facingForward==1?A:B; - long nextModel = facingForward==1?B:A; - - //Example thing thats just wrong but as example - mesher.putNext(((long) facingForward) |//Facing - ((selfModel & 0xFFFF) << 26) | //ModelId - (((nextModel>>16)&0xFF) << 55) |//Lighting - ((selfModel&(0x1FFL<<24))<<(46-24))//biomeId - ); - } - } - } - - //Need to skip the remaining entries in the skip array - { - msk = ~msk;//Invert the mask as we only need to set stuff that isnt 0 - while (msk!=0) { - int index = Integer.numberOfTrailingZeros(msk); - msk &= ~Integer.lowestOneBit(msk); - int skipCount; - if (index < 11) { - skipCount = (int) (sumA>>(index*5)); - } else if (index<22) { - skipCount = (int) (sumB>>((index-11)*5)); - } else { - skipCount = (int) (sumC>>((index-22)*5)); - } - skipCount &= 0x1F; - - if (skipCount != 0) { - this.xAxisMeshers[index].skip(skipCount); - } - } - } - } - - //Generate the side faces, hackily, using 0 and 1 mesher - if (true) { - var ma = this.xAxisMeshers[0]; - var mb = this.xAxisMeshers[31]; - ma.finish(); - mb.finish(); - ma.doAuxiliaryFaceOffset = false; - mb.doAuxiliaryFaceOffset = false; - for (int y = 0; y < 32; y++) { - int skipA = 0; - int skipB = 0; - for (int z = 0; z < 32; z++) { - int i = y*32+z; - int msk = this.opaqueMasks[i]; - if ((msk & 1) != 0) { - ma.skip(skipA); skipA = 0; - - long A = this.sectionData[(i<<5) * 2]; - - ma.putNext(0L | ((A&0xFFFF)<<26) | (((0xFFL)&0xFF)<<55)|((A&(0x1FFL<<24))<<(46-24))); - } else {skipA++;} - - if ((msk & (1<<31)) != 0) { - mb.skip(skipB); skipB = 0; - - long A = this.sectionData[(i*32+31) * 2]; - - mb.putNext(1L | ((A&0xFFFF)<<26) | (((0xFFL)&0xFF)<<55)|((A&(0x1FFL<<24))<<(46-24))); - } else {skipB++;} - } - ma.skip(skipA); - mb.skip(skipB); - } - ma.finish(); - mb.finish(); - ma.doAuxiliaryFaceOffset = true; - mb.doAuxiliaryFaceOffset = true; - } - - for (var mesher : this.xAxisMeshers) { - mesher.finish(); - } - } - - /* - private static long createQuad() { - ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(metadata)?1:0)) | otherFlags - - - long data = Integer.toUnsignedLong(array[i*3+1]); - data |= ((long) array[i*3+2])<<32; - long encodedQuad = Integer.toUnsignedLong(QuadEncoder.encodePosition(face, otherAxis, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46); - }*/ - - //section is already acquired and gets released by the parent - public BuiltSection generateMesh(WorldSection section) { - //Copy section data to end of array so that can mutate array while reading safely - section.copyDataTo(this.sectionData, 32*32*32); - - this.quadCount = 0; - - this.minX = Integer.MAX_VALUE; - this.minY = Integer.MAX_VALUE; - this.minZ = Integer.MAX_VALUE; - this.maxX = Integer.MIN_VALUE; - this.maxY = Integer.MIN_VALUE; - this.maxZ = Integer.MIN_VALUE; - - Arrays.fill(this.directionalQuadCounters, (short) 0); - - - this.world.acquire(section.lvl, section.x+1, section.y, section.z).release(); - this.world.acquire(section.lvl, section.x-1, section.y, section.z).release(); - - this.world.acquire(section.lvl, section.x, section.y+1, section.z).release(); - this.world.acquire(section.lvl, section.x, section.y-1, section.z).release(); - - this.world.acquire(section.lvl, section.x, section.y, section.z+1).release(); - this.world.acquire(section.lvl, section.x, section.y, section.z-1).release(); - - - - //Prepare everything - this.prepareSectionData(); - - - this.generateYZFaces(); - this.generateXFaces(); - - - //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 - // this stops e.g. multiple layers of glass (and ocean) from having 3000 layers of quads etc - - if (this.quadCount == 0) { - return BuiltSection.emptyWithChildren(section.key, section.getNonEmptyChildren()); - } - - //TODO: FIXME AND OPTIMIZE, get rid of the stupid quad collector bullshit - - int[] offsets = new int[8]; - var buff = new MemoryBuffer(this.quadCount * 8L); - long ptr = buff.address; - int coff = 0; - - for (int face = 0; face < 6; face++) { - offsets[face + 2] = coff; - int size = this.directionalQuadCounters[face]; - UnsafeUtil.memcpy(this.directionalQuadBufferPtr + (face*(8*(1<<16))), ptr + coff*8L, (size* 8L)); - coff += size; - } - - - int aabb = 0; - aabb |= 0; - aabb |= 0<<5; - aabb |= 0<<10; - aabb |= (31)<<15; - aabb |= (31)<<20; - aabb |= (31)<<25; - - return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets); - - /* - buff = new MemoryBuffer(bufferSize * 8L); - long ptr = buff.address; - int coff = 0; - - //Ordering is: translucent, double sided quads, directional quads - offsets[0] = coff; - int size = this.translucentQuadCollector.size(); - LongArrayList arrayList = this.translucentQuadCollector; - for (int i = 0; i < size; i++) { - long data = arrayList.getLong(i); - MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data); - } - - offsets[1] = coff; - size = this.doubleSidedQuadCollector.size(); - arrayList = this.doubleSidedQuadCollector; - for (int i = 0; i < size; i++) { - long data = arrayList.getLong(i); - MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data); - } - - for (int face = 0; face < 6; face++) { - offsets[face + 2] = coff; - final LongArrayList faceArray = this.directionalQuadCollectors[face]; - size = faceArray.size(); - for (int i = 0; i < size; i++) { - long data = faceArray.getLong(i); - MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data); - } - } - - int aabb = 0; - aabb |= this.minX; - aabb |= this.minY<<5; - aabb |= this.minZ<<10; - aabb |= (this.maxX-this.minX)<<15; - aabb |= (this.maxY-this.minY)<<20; - aabb |= (this.maxZ-this.minZ)<<25; - - return new BuiltSection(section.key, section.getNonEmptyChildren(), aabb, buff, offsets); - */ - } - - public void free() { - this.directionalQuadBuffer.free(); - } - - - - - - - - - - - - - - - //Returns true if a face was placed - private boolean putFluidFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfClientModelId, int selfBlockId, long facingState, long facingMetadata, int facingClientModelId, int a, int b) { - int selfFluidClientId = this.modelMan.getFluidClientStateId(selfClientModelId); - long selfFluidMetadata = this.modelMan.getModelMetadataFromClientId(selfFluidClientId); - - int facingFluidClientId = -1; - if (ModelQueries.containsFluid(facingMetadata)) { - facingFluidClientId = this.modelMan.getFluidClientStateId(facingClientModelId); - } - - //If both of the states are the same, then dont render the fluid face - if (selfFluidClientId == facingFluidClientId) { - return false; - } - - if (facingFluidClientId != -1) { - //TODO: OPTIMIZE - if (this.world.getMapper().getBlockStateFromBlockId(selfBlockId).getBlock() == this.world.getMapper().getBlockStateFromBlockId(Mapper.getBlockId(facingState)).getBlock()) { - return false; - } - } - - - if (ModelQueries.faceOccludes(facingMetadata, opposingFace)) { - return false; - } - - //if the model has a fluid state but is not a liquid need to see if the solid state had a face rendered and that face is occluding, if so, dont render the fluid state face - if ((!ModelQueries.isFluid(metadata)) && ModelQueries.faceOccludes(metadata, face)) { - return false; - } - - - - //TODO:FIXME SOMEHOW THIS IS CRITICAL!!!!!!!!!!!!!!!!!! - // so there is one more issue need to be fixed, if water is layered ontop of eachother, the side faces depend on the water state ontop - // this has been hackfixed in the model texture bakery but a proper solution that doesnt explode the sides of the water textures needs to be done - // the issue is that the fluid rendering depends on the up state aswell not just the face state which is really really painful to account for - // e.g the sides of a full water is 8 high or something, not the full block height, this results in a gap between water layers - - - - long otherFlags = 0; - otherFlags |= ModelQueries.isTranslucent(selfFluidMetadata)?1L<<33:0; - otherFlags |= ModelQueries.isDoubleSided(selfFluidMetadata)?1L<<34:0; - mesher.put(a, b, ((long)selfFluidClientId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(selfFluidMetadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(selfFluidMetadata)?1:0)) | otherFlags); - return true; - } - - //Returns true if a face was placed - private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long metadata, int clientModelId, int selfBiome, long facingModelId, long facingMetadata, int selfLight, int facingLight, int a, int b) { - if (ModelQueries.cullsSame(metadata) && clientModelId == facingModelId) { - //If we are facing a block, and we are both the same state, dont render that face - return false; - } - - //If face can be occluded and is occluded from the facing block, then dont render the face - if (ModelQueries.faceCanBeOccluded(metadata, face) && ModelQueries.faceOccludes(facingMetadata, opposingFace)) { - return false; - } - - long otherFlags = 0; - otherFlags |= ModelQueries.isTranslucent(metadata)?1L<<33:0; - otherFlags |= ModelQueries.isDoubleSided(metadata)?1L<<34:0; - mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?selfLight:facingLight))<<16) | (ModelQueries.isBiomeColoured(metadata)?(((long) Mapper.getBiomeId(selfBiome))<<24):0) | otherFlags); - return true; - } - - private static int getMeshletHoldingCount(int quads, int quadsPerMeshlet, int meshletSize) { - return ((quads+(quadsPerMeshlet-1))/quadsPerMeshlet)*meshletSize; - } - - public static int alignUp(int n, int alignment) { - return (n + alignment - 1) & -alignment; - } -} \ No newline at end of file diff --git a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java index b6e1fd53..21f8208e 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java @@ -5,6 +5,8 @@ import me.cortex.voxy.common.config.section.SectionStorage; import me.cortex.voxy.common.util.TrackedObject; import me.cortex.voxy.common.voxelization.VoxelizedSection; import me.cortex.voxy.common.world.other.Mapper; +import me.cortex.voxy.commonImpl.VoxyInstance; +import org.jetbrains.annotations.Nullable; import java.util.List; public class WorldEngine { @@ -24,7 +26,6 @@ public class WorldEngine { private final ActiveSectionTracker sectionTracker; private ISectionChangeCallback dirtyCallback; private ISectionSaveCallback saveCallback; - private final int maxMipLevels; private volatile boolean isLive = true; public void setDirtyCallback(ISectionChangeCallback callback) { @@ -37,12 +38,16 @@ public class WorldEngine { public Mapper getMapper() {return this.mapper;} public boolean isLive() {return this.isLive;} + + public final @Nullable VoxyInstance instanceIn; + public WorldEngine(SectionStorage storage, int cacheCount) { - this(storage, MAX_LOD_LAYER+1, cacheCount);//The +1 is because its from 1 not from 0 + this(storage, cacheCount, null); } - private WorldEngine(SectionStorage storage, int maxMipLayers, int cacheCount) { - this.maxMipLevels = maxMipLayers; + public WorldEngine(SectionStorage storage, int cacheCount, @Nullable VoxyInstance instance) { + this.instanceIn = instance; + this.storage = storage; this.mapper = new Mapper(this.storage); //5 cache size bits means that the section tracker has 32 separate maps that it uses @@ -119,7 +124,7 @@ public class WorldEngine { boolean shouldCheckEmptiness = false; WorldSection previousSection = null; - for (int lvl = 0; lvl < this.maxMipLevels; lvl++) { + for (int lvl = 0; lvl < MAX_LOD_LAYER+1; lvl++) { var worldSection = this.acquire(lvl, section.x >> (lvl + 1), section.y >> (lvl + 1), section.z >> (lvl + 1)); int emptinessStateChange = 0; diff --git a/src/main/java/me/cortex/voxy/commonImpl/VoxyInstance.java b/src/main/java/me/cortex/voxy/commonImpl/VoxyInstance.java index 63a792fe..6049f561 100644 --- a/src/main/java/me/cortex/voxy/commonImpl/VoxyInstance.java +++ b/src/main/java/me/cortex/voxy/commonImpl/VoxyInstance.java @@ -99,7 +99,7 @@ public class VoxyInstance { } protected WorldEngine createWorld(SectionStorage storage) { - var world = new WorldEngine(storage, 2048); + var world = new WorldEngine(storage, 2048, this); world.setSaveCallback(this.savingService::enqueueSave); this.activeWorlds.add(world); return world;