diff options
Diffstat (limited to 'src/main/java/makamys/neodymium/renderer')
10 files changed, 2625 insertions, 0 deletions
diff --git a/src/main/java/makamys/neodymium/renderer/ChunkMesh.java b/src/main/java/makamys/neodymium/renderer/ChunkMesh.java new file mode 100644 index 0000000..6c4cd59 --- /dev/null +++ b/src/main/java/makamys/neodymium/renderer/ChunkMesh.java @@ -0,0 +1,455 @@ +package makamys.neodymium.renderer; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.lwjgl.BufferUtils; + +import makamys.neodymium.LODMod; +import makamys.neodymium.MixinConfigPlugin; +import makamys.neodymium.ducks.IWorldRenderer; +import makamys.neodymium.util.BufferWriter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.entity.Entity; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagByteArray; +import net.minecraft.tileentity.TileEntity; + +public class ChunkMesh extends Mesh { + + Flags flags; + + // TODO move this somewhere else + List<String> nameList = (List<String>) ((TextureMap)Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture)).mapUploadedSprites.keySet().stream().collect(Collectors.toList()); + + public static int usedRAM = 0; + public static int instances = 0; + + public ChunkMesh(int x, int y, int z, Flags flags, int quadCount, ByteBuffer buffer, int pass) { + this.x = x; + this.y = y; + this.z = z; + this.flags = flags; + this.quadCount = quadCount; + this.pass = pass; + + this.buffer = buffer; + usedRAM += buffer.limit(); + instances++; + } + + public ChunkMesh(int x, int y, int z, Flags flags, int quadCount, List<MeshQuad> quads, int pass) { + this.x = x; + this.y = y; + this.z = z; + this.flags = flags; + this.quadCount = quadCount; + this.pass = pass; + + NBTBase nbtData = toNBT(quads, quadCount); + buffer = createBuffer(((NBTTagByteArray)nbtData).func_150292_c(), nameList); + usedRAM += buffer.limit(); + instances++; + } + + private static int totalOriginalQuadCount = 0; + private static int totalSimplifiedQuadCount = 0; + + public static ChunkMesh fromTessellator(int pass, WorldRenderer wr, Tessellator t) { + if(t.vertexCount % 4 != 0) { + System.out.println("Error: Vertex count is not a multiple of 4"); + return null; + } + + int xOffset = wr.posX; + int yOffset = wr.posY; + int zOffset = wr.posZ; + + boolean fr = MixinConfigPlugin.isOptiFinePresent() && LODMod.ofFastRender; + int tessellatorXOffset = fr ? xOffset : 0; + int tessellatorYOffset = fr ? yOffset : 0; + int tessellatorZOffset = fr ? zOffset : 0; + + boolean optimize = LODMod.optimizeChunkMeshes; + + ChunkMesh.Flags flags = new ChunkMesh.Flags(t.hasTexture, t.hasBrightness, t.hasColor, t.hasNormals); + + if(optimize) { + List<MeshQuad> quads = new ArrayList<>(); + + for(int quadI = 0; quadI < t.vertexCount / 4; quadI++) { + MeshQuad quad = new MeshQuad(t.rawBuffer, quadI * 32, flags, tessellatorXOffset, tessellatorYOffset, tessellatorZOffset); + //if(quad.bUs[0] == quad.bUs[1] && quad.bUs[1] == quad.bUs[2] && quad.bUs[2] == quad.bUs[3] && quad.bUs[3] == quad.bVs[0] && quad.bVs[0] == quad.bVs[1] && quad.bVs[1] == quad.bVs[2] && quad.bVs[2] == quad.bVs[3] && quad.bVs[3] == 0) { + // quad.deleted = true; + //} + if(quad.plane == quad.PLANE_XZ && !quad.isClockwiseXZ()) { + // water hack + quad.deleted = true; + } + quads.add(quad); + } + + ArrayList<ArrayList<MeshQuad>> quadsByPlaneDir = new ArrayList<>(); // XY, XZ, YZ + for(int i = 0; i < 3; i++) { + quadsByPlaneDir.add(new ArrayList<MeshQuad>()); + } + for(MeshQuad quad : quads) { + if(quad.plane != MeshQuad.PLANE_NONE) { + quadsByPlaneDir.get(quad.plane).add(quad); + } + } + for(int plane = 0; plane < 3; plane++) { + quadsByPlaneDir.get(plane).sort(MeshQuad.QuadPlaneComparator.quadPlaneComparators[plane]); + } + + for(int plane = 0; plane < 3; plane++) { + List<MeshQuad> planeDirQuads = quadsByPlaneDir.get(plane); + int planeStart = 0; + for(int quadI = 0; quadI < planeDirQuads.size(); quadI++) { + MeshQuad quad = planeDirQuads.get(quadI); + MeshQuad nextQuad = quadI == planeDirQuads.size() - 1 ? null : planeDirQuads.get(quadI + 1); + if(!quad.onSamePlaneAs(nextQuad)) { + simplifyPlane(planeDirQuads.subList(planeStart, quadI)); + planeStart = quadI + 1; + } + } + } + + int quadCount = countValidQuads(quads); + + totalOriginalQuadCount += quads.size(); + totalSimplifiedQuadCount += quadCount; + //System.out.println("simplified quads " + totalOriginalQuadCount + " -> " + totalSimplifiedQuadCount + " (ratio: " + ((float)totalSimplifiedQuadCount / (float)totalOriginalQuadCount) + ") totalMergeCountByPlane: " + Arrays.toString(totalMergeCountByPlane)); + + if(quadCount > 0) { + return new ChunkMesh( + (int)(xOffset / 16), (int)(yOffset / 16), (int)(zOffset / 16), + new ChunkMesh.Flags(t.hasTexture, t.hasBrightness, t.hasColor, t.hasNormals), + quadCount, quads, pass); + } else { + return null; + } + } else { + int quadCount = t.vertexCount / 4; + ByteBuffer buffer = BufferUtils.createByteBuffer(quadCount * 6 * 7 * 4); + BufferWriter out = new BufferWriter(buffer); + + try { + for(int i = 0; i < quadCount; i++) { + writeBufferQuad(t, i * 32, out, -tessellatorXOffset + xOffset, -tessellatorYOffset + yOffset, -tessellatorZOffset + zOffset); + } + } catch(IOException e) { + e.printStackTrace(); + } + buffer.flip(); + + if(quadCount > 0) { + return new ChunkMesh( + (int)(xOffset / 16), (int)(yOffset / 16), (int)(zOffset / 16), + flags, + quadCount, buffer, pass); + } else { + return null; + } + } + } + + private static void writeBufferQuad(Tessellator t, int offset, BufferWriter out, float offsetX, float offsetY, float offsetZ) throws IOException { + for(int vertexI = 0; vertexI < 6; vertexI++) { + + int vi = new int[]{0, 1, 2, 0, 2, 3}[vertexI]; + + int i = offset + vi * 8; + + float x = Float.intBitsToFloat(t.rawBuffer[i + 0]) + offsetX; + float y = Float.intBitsToFloat(t.rawBuffer[i + 1]) + offsetY; + float z = Float.intBitsToFloat(t.rawBuffer[i + 2]) + offsetZ; + + out.writeFloat(x); + out.writeFloat(y); + out.writeFloat(z); + + float u = Float.intBitsToFloat(t.rawBuffer[i + 3]); + float v = Float.intBitsToFloat(t.rawBuffer[i + 4]); + + out.writeFloat(u); + out.writeFloat(v); + + int brightness = t.rawBuffer[i + 7]; + out.writeInt(brightness); + + int color = t.rawBuffer[i + 5]; + out.writeInt(color); + + i += 8; + } + } + + private static void simplifyPlane(List<MeshQuad> planeQuads) { + MeshQuad lastQuad = null; + // Pass 1: merge quads to create rows + for(MeshQuad quad : planeQuads) { + if(lastQuad != null) { + lastQuad.tryToMerge(quad); + } + if(quad.isValid(quad)) { + lastQuad = quad; + } + } + + // Pass 2: merge rows to create rectangles + // TODO optimize? + for(int i = 0; i < planeQuads.size(); i++) { + for(int j = i + 1; j < planeQuads.size(); j++) { + planeQuads.get(i).tryToMerge(planeQuads.get(j)); + } + } + } + + private static int countValidQuads(List<MeshQuad> quads) { + int quadCount = 0; + for(MeshQuad quad : quads) { + if(!quad.deleted) { + quadCount++; + } + } + return quadCount; + } + + private NBTBase toNBT(List<? extends MeshQuad> quads, int quadCount) { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(quadCount * (2 + 4 * (3 + 2 + 2 + 4))); + DataOutputStream out = new DataOutputStream(byteOut); + try { + for(int pass = 0; pass <= 9; pass++){ + for(MeshQuad quad : quads) { + quad.writeToDisk(out, pass); + } + } + } catch(IOException e) {} + + NBTTagByteArray arr = new NBTTagByteArray(byteOut.toByteArray()); + usedRAM += arr.func_150292_c().length; + return arr; + } + + void destroy() { + if(buffer != null) { + usedRAM -= buffer.limit(); + instances--; + buffer = null; + + if(gpuStatus == Mesh.GPUStatus.SENT) { + gpuStatus = Mesh.GPUStatus.PENDING_DELETE; + } + } + } + + @Override + public void destroyBuffer() { + destroy(); + } + + private ByteBuffer createBuffer(byte[] data, List<String> stringTable) { + if(!(flags.hasTexture && flags.hasColor && flags.hasBrightness && !flags.hasNormals)) { + // for simplicity's sake we just assume this setup + System.out.println("invalid mesh properties, expected a chunk"); + return null; + } + int coordsOffset = quadCount * 2; + int textureOffset = quadCount * (2 + 4 + 4 + 4); + int brightnessOffset = quadCount * (2 + 4 + 4 + 4 + 4 + 4); + int colorOffset = quadCount * (2 + 4 + 4 + 4 + 4 + 4 + 4 + 4); + + ByteBuffer buffer = BufferUtils.createByteBuffer(quadCount * 6 * getStride()); + FloatBuffer floatBuffer = buffer.asFloatBuffer(); + ShortBuffer shortBuffer = buffer.asShortBuffer(); + IntBuffer intBuffer = buffer.asIntBuffer(); + + try { + for(int quadI = 0; quadI < quadCount; quadI++) { + short spriteIndex = readShortAt(data, quadI * 2); + String spriteName = stringTable.get(spriteIndex); + + TextureAtlasSprite tas = ((TextureMap)Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture)).getAtlasSprite(spriteName); + + for (int vertexNum = 0; vertexNum < 6; vertexNum++) { + int vi = new int[]{0, 1, 3, 1, 2, 3}[vertexNum]; + int vertexI = 4 * quadI + vi; + int offset = vertexI * getStride(); + int simpleX = Byte.toUnsignedInt(data[coordsOffset + 0 * 4 * quadCount + 4 * quadI + vi]); + if(simpleX == 255) simpleX = 256; + int simpleY = Byte.toUnsignedInt(data[coordsOffset + 1 * 4 * quadCount + 4 * quadI + vi]); + if(simpleY == 255) simpleY = 256; + int simpleZ = Byte.toUnsignedInt(data[coordsOffset + 2 * 4 * quadCount + 4 * quadI + vi]); + if(simpleZ == 255) simpleZ = 256; + floatBuffer.put(x * 16 + simpleX / 16f); // x + floatBuffer.put(y * 16 + simpleY / 16f); // y + floatBuffer.put(z * 16 + simpleZ / 16f); // z + + byte relU = data[textureOffset + 0 * 4 * quadCount + 4 * quadI + vi]; + byte relV = data[textureOffset + 1 * 4 * quadCount + 4 * quadI + vi]; + + floatBuffer.put(tas.getMinU() + (tas.getMaxU() - tas.getMinU()) * (relU / 16f)); // u + floatBuffer.put(tas.getMinV() + (tas.getMaxV() - tas.getMinV()) * (relV / 16f)); // v + + shortBuffer.position(floatBuffer.position() * 2); + + shortBuffer.put((short)Byte.toUnsignedInt(data[brightnessOffset + 0 * 4 * quadCount + 4 * quadI + vi])); // bU + shortBuffer.put((short)Byte.toUnsignedInt(data[brightnessOffset + 1 * 4 * quadCount + 4 * quadI + vi])); // bV + + intBuffer.position(shortBuffer.position() / 2); + + int integet = readIntAt(data, colorOffset + 4 * 4 * quadI + 4 * vi); + intBuffer.put(integet); // c + + floatBuffer.position(intBuffer.position()); + } + } + } catch(Exception e) { + e.printStackTrace(); + } + + buffer.position(floatBuffer.position() * 4); + buffer.flip(); + + usedRAM += buffer.limit(); + + return buffer; + } + + public void update() { + } + + // Java is weird. + public static short readShortAt(DataInputStream in, int offset) { + try { + in.reset(); + in.skip(offset); + return in.readShort(); + } catch(IOException e) { + return -1; + } + } + + public static short readShortAt(byte[] data, int offset) { + return (short)(Byte.toUnsignedInt(data[offset]) << 8 | Byte.toUnsignedInt(data[offset + 1])); + } + + public static int readIntAt(DataInputStream in, int offset) { + try { + in.reset(); + in.skip(offset); + return in.readInt(); + } catch(IOException e) { + return -1; + } + } + + public static int readIntAt(byte[] data, int offset) { + return (int)(Byte.toUnsignedLong(data[offset]) << 24 | Byte.toUnsignedLong(data[offset + 1]) << 16 | Byte.toUnsignedLong(data[offset + 2]) << 8 | Byte.toUnsignedLong(data[offset + 3])); + } + + public int getStride() { + return (3 * 4 + (flags.hasTexture ? 8 : 0) + (flags.hasBrightness ? 4 : 0) + (flags.hasColor ? 4 : 0) + (flags.hasNormals ? 4 : 0)); + } + + static void saveChunks(List<Integer> coords) { + System.out.println("saving " + (coords.size() / 3) + " cchunks"); + for(int i = 0; i < coords.size(); i += 3) { + if(i % 300 == 0) { + System.out.println((i / 3) + " / " + (coords.size() / 3)); + } + int theX = coords.get(i); + int theY = coords.get(i + 1); + int theZ = coords.get(i + 2); + + WorldRenderer wr = new WorldRenderer(Minecraft.getMinecraft().theWorld, new ArrayList<TileEntity>(), theX * 16, theY * 16, theZ * 16, 100000); + /* + if (this.occlusionEnabled) + { + this.worldRenderers[(var6 * this.renderChunksTall + var5) * this.renderChunksWide + var4].glOcclusionQuery = this.glOcclusionQueryBase.get(var3); + }*/ + + wr.isWaitingOnOcclusionQuery = false; + wr.isVisible = true; + wr.isInFrustum = true; + wr.chunkIndex = 0; + wr.markDirty(); + wr.updateRenderer(Minecraft.getMinecraft().thePlayer); + } + //Tessellator.endSave(); + } + + static List<ChunkMesh> getChunkMesh(int theX, int theY, int theZ) { + WorldRenderer wr = new WorldRenderer(Minecraft.getMinecraft().theWorld, new ArrayList<TileEntity>(), theX * 16, theY * 16, theZ * 16, 100000); + + wr.isWaitingOnOcclusionQuery = false; + wr.isVisible = true; + wr.isInFrustum = true; + wr.chunkIndex = 0; + wr.markDirty(); + wr.updateRenderer(Minecraft.getMinecraft().thePlayer); + return ((IWorldRenderer)wr).getChunkMeshes(); + } + + public double distSq(Entity player) { + int centerX = x * 16 + 8; + int centerY = y * 16 + 8; + int centerZ = z * 16 + 8; + + return player.getDistanceSq(centerX, centerY, centerZ); + } + + public static class Flags { + boolean hasTexture; + boolean hasBrightness; + boolean hasColor; + boolean hasNormals; + + public Flags(byte flags) { + hasTexture = (flags & 1) != 0; + hasBrightness = (flags & 2) != 0; + hasColor = (flags & 4) != 0; + hasNormals = (flags & 8) != 0; + } + + public Flags(boolean hasTexture, boolean hasBrightness, boolean hasColor, boolean hasNormals) { + this.hasTexture = hasTexture; + this.hasBrightness = hasBrightness; + this.hasColor = hasColor; + this.hasNormals = hasNormals; + } + + public byte toByte() { + byte flags = 0; + if(hasTexture) { + flags |= 1; + } + if(hasBrightness) { + flags |= 2; + } + if(hasColor) { + flags |= 4; + } + if(hasNormals) { + flags |= 8; + } + return flags; + } + } + +} + diff --git a/src/main/java/makamys/neodymium/renderer/FarChunkCache.java b/src/main/java/makamys/neodymium/renderer/FarChunkCache.java new file mode 100644 index 0000000..21dd5a6 --- /dev/null +++ b/src/main/java/makamys/neodymium/renderer/FarChunkCache.java @@ -0,0 +1,12 @@ +package makamys.neodymium.renderer; + +import net.minecraft.world.ChunkCache; +import net.minecraft.world.World; + +public class FarChunkCache extends ChunkCache { + + public FarChunkCache(World p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8) { + super(p1, p2, p3, p4, p5, p6, p7, p8); + } + +} diff --git a/src/main/java/makamys/neodymium/renderer/FarWorldRenderer.java b/src/main/java/makamys/neodymium/renderer/FarWorldRenderer.java new file mode 100644 index 0000000..a15d8b9 --- /dev/null +++ b/src/main/java/makamys/neodymium/renderer/FarWorldRenderer.java @@ -0,0 +1,14 @@ +package makamys.neodymium.renderer; + +import java.util.List; + +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.world.World; + +public class FarWorldRenderer extends WorldRenderer { + + public FarWorldRenderer(World p1, List p2, int p3, int p4, int p5, int p6) { + super(p1, p2, p3, p4, p5, p6); + } + +} diff --git a/src/main/java/makamys/neodymium/renderer/GPUMemoryManager.java b/src/main/java/makamys/neodymium/renderer/GPUMemoryManager.java new file mode 100644 index 0000000..75b1f64 --- /dev/null +++ b/src/main/java/makamys/neodymium/renderer/GPUMemoryManager.java @@ -0,0 +1,215 @@ +package makamys.neodymium.renderer; + +import static org.lwjgl.opengl.GL15.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import makamys.neodymium.LODMod; +import makamys.neodymium.renderer.Mesh.GPUStatus; +import makamys.neodymium.util.GuiHelper; + +public class GPUMemoryManager { + + private int bufferSize; + + public int VBO; + + private int nextMesh; + + private List<Mesh> sentMeshes = new ArrayList<>(); + + public GPUMemoryManager() { + VBO = glGenBuffers(); + + bufferSize = LODMod.VRAMSize * 1024 * 1024; + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + + glBufferData(GL_ARRAY_BUFFER, bufferSize, GL_DYNAMIC_DRAW); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + public void runGC(boolean full) { + glBindBuffer(GL_ARRAY_BUFFER, VBO); + + int moved = 0; + int timesReachedEnd = 0; + int checksLeft = sentMeshes.size(); + + while((!full && (moved < 4 && checksLeft-- > 0)) || (full && timesReachedEnd < 2) && !sentMeshes.isEmpty()) { + nextMesh++; + if(nextMesh >= sentMeshes.size()) { + nextMesh = 0; + timesReachedEnd++; + } + Mesh mesh = sentMeshes.get(nextMesh); + + if(mesh.gpuStatus == GPUStatus.SENT) { + int offset = nextMesh == 0 ? 0 : sentMeshes.get(nextMesh - 1).getEnd(); + if(mesh.offset != offset) { + glBufferSubData(GL_ARRAY_BUFFER, offset, mesh.buffer); + moved++; + } + mesh.iFirst = offset / mesh.getStride(); + mesh.offset = offset; + } else if(mesh.gpuStatus == GPUStatus.PENDING_DELETE) { + mesh.iFirst = mesh.offset = -1; + mesh.visible = false; + mesh.gpuStatus = GPUStatus.UNSENT; + + sentMeshes.remove(nextMesh); + + mesh.destroyBuffer(); + + if(nextMesh > 0) { + nextMesh--; + } + } + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + private int malloc(int size) { + int nextBase = 0; + if(!sentMeshes.isEmpty()) { + if(nextMesh < sentMeshes.size() - 1) { + Mesh next = sentMeshes.get(nextMesh); + Mesh nextnext = sentMeshes.get(nextMesh + 1); + if(nextnext.offset - next.getEnd() >= size) { + return next.getEnd(); + } + } + + nextBase = sentMeshes.get(sentMeshes.size() - 1).getEnd(); + } + + if(nextBase + size >= bufferSize) { + return -1; + } else { + return nextBase; + } + } + + private int end() { + return (sentMeshes.isEmpty() ? 0 : sentMeshes.get(sentMeshes.size() - 1).getEnd()); + } + + public void sendMeshToGPU(Mesh mesh) { + if(mesh == null || mesh.buffer == null) { + return; + } + + if(end() + mesh.bufferSize() >= bufferSize) { + runGC(true); + } + + if(end() + mesh.bufferSize() >= bufferSize) { + System.out.println("VRAM is full! Try increasing the allocated VRAM in the config, if possible. Reverting to vanilla renderer."); + LODMod.renderer.destroyPending = true; + // TODO restart renderer with more VRAM allocated when this happens. + return; + } + + int size = mesh.bufferSize(); + int insertIndex = -1; + + int nextBase = -1; + if(!sentMeshes.isEmpty()) { + if(nextMesh < sentMeshes.size() - 1) { + Mesh next = sentMeshes.get(nextMesh); + Mesh nextnext = null; + for(int i = nextMesh + 1; i < sentMeshes.size(); i++) { + Mesh m = sentMeshes.get(i); + if(m.gpuStatus == Mesh.GPUStatus.SENT) { + nextnext = m; + break; + } + } + if(nextnext != null && nextnext.offset - next.getEnd() >= size) { + nextBase = next.getEnd(); + insertIndex = nextMesh + 1; + } + } + + if(nextBase == -1) { + nextBase = sentMeshes.get(sentMeshes.size() - 1).getEnd(); + } + } + if(nextBase == -1) nextBase = 0; + + + if(mesh.gpuStatus == GPUStatus.UNSENT) { + mesh.prepareBuffer(); + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + + glBufferSubData(GL_ARRAY_BUFFER, nextBase, mesh.buffer); + mesh.iFirst = nextBase / mesh.getStride(); + mesh.iCount = mesh.quadCount * 6; + mesh.offset = nextBase; + + if(insertIndex == -1) { + sentMeshes.add(mesh); + } else { + sentMeshes.add(insertIndex, mesh); + nextMesh = insertIndex; + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + mesh.gpuStatus = GPUStatus.SENT; + } + + public void deleteMeshFromGPU(Mesh mesh) { + if(mesh == null || mesh.gpuStatus == GPUStatus.UNSENT) { + return; + } + mesh.gpuStatus = GPUStatus.PENDING_DELETE; + } + + public void destroy() { + glDeleteBuffers(VBO); + } + + public List<String> getDebugText() { + return Arrays.asList("VRAM: " + (end() / 1024 / 1024) + "MB / " + (bufferSize / 1024 / 1024) + "MB"); + } + + public void drawInfo() { + int scale = 10000; + int rowLength = 512; + int yOff = 20; + + int height = (bufferSize / scale) / rowLength; + GuiHelper.drawRectangle(0, yOff, rowLength, height, 0x000000, 50); + + int meshI = 0; + for(Mesh mesh : sentMeshes) { + + int o = mesh.offset / 10000; + int o2 = (mesh.offset + mesh.bufferSize()) / 10000; + if(o / rowLength == o2 / rowLength) { + if(mesh.gpuStatus != Mesh.GPUStatus.PENDING_DELETE) { + GuiHelper.drawRectangle(o % rowLength, o / rowLength + yOff, mesh.buffer.limit() / scale + 1, 1, meshI == nextMesh ? 0x00FF00 : 0xFFFFFF); + } + } else { + for(int i = o; i < o2; i++) { + int x = i % rowLength; + int y = i / rowLength; + if(mesh.gpuStatus != Mesh.GPUStatus.PENDING_DELETE) { + GuiHelper.drawRectangle(x, y + yOff, 1, 1, 0xFFFFFF); + } + } + } + meshI++; + } + GuiHelper.drawRectangle(0 % rowLength, 0 + yOff, 4, 4, 0x00FF00); + GuiHelper.drawRectangle((bufferSize / scale) % rowLength, (bufferSize / scale) / rowLength + yOff, 4, 4, 0xFF0000); + } + +} diff --git a/src/main/java/makamys/neodymium/renderer/LODChunk.java b/src/main/java/makamys/neodymium/renderer/LODChunk.java new file mode 100644 index 0000000..5dd3762 --- /dev/null +++ b/src/main/java/makamys/neodymium/renderer/LODChunk.java @@ -0,0 +1,184 @@ +package makamys.neodymium.renderer; + +import java.util.List; + +import makamys.neodymium.LODMod; +import net.minecraft.entity.Entity; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagEnd; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.world.chunk.Chunk; + +public class LODChunk { + + int x, z; + public boolean needsChunk = true; + int lod = 0; + boolean visible; + boolean dirty; + boolean discardedMesh; + + SimpleChunkMesh[] simpleMeshes = new SimpleChunkMesh[2]; + ChunkMesh[] chunkMeshes = new ChunkMesh[32]; + + public boolean[] isSectionVisible = new boolean[16]; + + LODRenderer renderer = LODMod.renderer; + + public LODChunk(int x, int z) { + this.x = x; + this.z = z; + } + /* + public LODChunk(NBTTagCompound nbt, List<String> spriteList) { + this.x = nbt.getInteger("x"); + this.z = nbt.getInteger("z"); + + loadChunkMeshesNBT(nbt.getCompoundTag("chunkMeshes"), spriteList); + } + + private void loadChunkMeshesNBT(NBTTagCompound chunkMeshesCompound, List<String> spriteList) { + for(Object o : chunkMeshesCompound.func_150296_c()) { + String key = (String)o; + int keyInt = Integer.parseInt(key); + + byte[] data = chunkMeshesCompound.getByteArray(key); + + chunkMeshes[keyInt] = new ChunkMesh(x, keyInt / 2, z, new ChunkMesh.Flags(true, true, true, false), data.length / (2 + 4 * (3 + 2 + 2 + 4)), data, spriteList, keyInt % 2); + } + } + */ + @Override + public String toString() { + return "LODChunk(" + x + ", " + z + ")"; + } + + public double distSq(Entity entity) { + return Math.pow(entity.posX - x * 16, 2) + Math.pow(entity.posZ - z * 16, 2); + } + + public void putChunkMeshes(int cy, List<ChunkMesh> newChunkMeshes) { + for(int i = 0; i < 2; i++) { + ChunkMesh newChunkMesh = newChunkMeshes.size() > i ? newChunkMeshes.get(i) : null; + if(chunkMeshes[cy * 2 + i] != null) { + if(newChunkMesh != null) { + newChunkMesh.pass = i; + } + + renderer.removeMesh(chunkMeshes[cy * 2 + i]); + chunkMeshes[cy * 2 + i].destroy(); + } + chunkMeshes[cy * 2 + i] = newChunkMesh; + } + LODMod.renderer.lodChunkChanged(this); + dirty = true; + discardedMesh = false; + } + + // nice copypasta + public void putSimpleMeshes(List<SimpleChunkMesh> newSimpleMeshes) { + for(int i = 0; i < 2; i++) { + SimpleChunkMesh newSimpleMesh = newSimpleMeshes.size() > i ? newSimpleMeshes.get(i) : null; + if(simpleMeshes[i] != null) { + if(newSimpleMesh != null) { + newSimpleMesh.pass = i; + } + + renderer.setMeshVisible(simpleMeshes[i], false); + simpleMeshes[i].destroy(); + } + simpleMeshes[i] = newSimpleMesh; + } + LODMod.renderer.lodChunkChanged(this); + } + + public boolean hasChunkMeshes() { + for(ChunkMesh cm : chunkMeshes) { + if(cm != null) { + return true; + } + } + return false; + } + + public void tick(Entity player) { + double distSq = distSq(player); + if(LODMod.disableSimpleMeshes || distSq < Math.pow((LODMod.renderer.renderRange / 2) * 16, 2)) { + setLOD(2); + } else if(distSq < Math.pow((LODMod.renderer.renderRange) * 16, 2)) { + setLOD(1); + } else { + setLOD(0); + } + } + + public void setLOD(int lod) { + if(lod == this.lod) return; + + this.lod = lod; + LODMod.renderer.lodChunkChanged(this); + if(!dirty) { + if(lod < 2) { + for(int i = 0; i < chunkMeshes.length; i++) { + if(chunkMeshes[i] != null) { + chunkMeshes[i].destroy(); + chunkMeshes[i] = null; + discardedMesh = true; + } + } + } + } + } + /* + public NBTTagCompound saveToNBT(NBTTagCompound oldNbt, List<String> oldStringTable) { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setInteger("x", x); + nbt.setInteger("z", z); + + NBTTagCompound chunkMeshesCompound = oldNbt == null ? new NBTTagCompound() : oldNbt.getCompoundTag("chunkMeshes"); + if(!discardedMesh) { + for(int i = 0; i < chunkMeshes.length; i++) { + if(chunkMeshes[i] != null) { + chunkMeshesCompound.setTag(String.valueOf(i), chunkMeshes[i].nbtData); + } + } + } else if(oldNbt != null && discardedMesh && lod == 2) { + loadChunkMeshesNBT(chunkMeshesCompound, oldStringTable); + LODMod.renderer.lodChunkChanged(this); + } + nbt.setTag("chunkMeshes", chunkMeshesCompound); + dirty = false; + return nbt; + } + */ + public void destroy() { + for(SimpleChunkMesh scm: simpleMeshes) { + if(scm != null) { + scm.destroy(); + } + } + for(ChunkMesh cm: chunkMeshes) { + if(cm != null) { + cm.destroy(); + } + } + LODMod.renderer.setVisible(this, false); + } + + public void receiveChunk(Chunk chunk) { + if(!LODMod.disableSimpleMeshes) { + putSimpleMeshes(SimpleChunkMesh.generateSimpleMeshes(chunk)); + } + } + + public boolean isFullyVisible() { + if(!visible) return false; + for(boolean b : isSectionVisible) { + if(!b) { + return false; + } + } + return true; + } + +} |
