From acdc0adecd3c8d649cad97c6932fb7ba418721e6 Mon Sep 17 00:00:00 2001 From: makamys Date: Mon, 11 Jul 2022 22:55:20 +0200 Subject: Be able to capture chunk meshes consisting of multiple tessellations Start of fix for #5 --- .../java/makamys/neodymium/MixinConfigPlugin.java | 3 +- .../java/makamys/neodymium/ducks/ITessellator.java | 7 ++ .../makamys/neodymium/mixin/MixinTessellator.java | 29 +++++++++ .../neodymium/mixin/MixinWorldRenderer.java | 27 +++++++- .../java/makamys/neodymium/renderer/ChunkMesh.java | 74 ++++++++++++++-------- 5 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 src/main/java/makamys/neodymium/ducks/ITessellator.java create mode 100644 src/main/java/makamys/neodymium/mixin/MixinTessellator.java (limited to 'src/main/java/makamys/neodymium') diff --git a/src/main/java/makamys/neodymium/MixinConfigPlugin.java b/src/main/java/makamys/neodymium/MixinConfigPlugin.java index a572d3c..6624a49 100644 --- a/src/main/java/makamys/neodymium/MixinConfigPlugin.java +++ b/src/main/java/makamys/neodymium/MixinConfigPlugin.java @@ -37,7 +37,8 @@ public class MixinConfigPlugin implements IMixinConfigPlugin { List mixins = new ArrayList<>(); mixins.addAll(Arrays.asList( "MixinRenderGlobal", - "MixinWorldRenderer")); + "MixinWorldRenderer", + "MixinTessellator")); if (OFUtil.isOptiFinePresent()) { System.out.println("Detected OptiFine"); diff --git a/src/main/java/makamys/neodymium/ducks/ITessellator.java b/src/main/java/makamys/neodymium/ducks/ITessellator.java new file mode 100644 index 0000000..1e2598f --- /dev/null +++ b/src/main/java/makamys/neodymium/ducks/ITessellator.java @@ -0,0 +1,7 @@ +package makamys.neodymium.ducks; + +public interface ITessellator { + + public void enableMeshCapturing(boolean enable); + +} diff --git a/src/main/java/makamys/neodymium/mixin/MixinTessellator.java b/src/main/java/makamys/neodymium/mixin/MixinTessellator.java new file mode 100644 index 0000000..15f71da --- /dev/null +++ b/src/main/java/makamys/neodymium/mixin/MixinTessellator.java @@ -0,0 +1,29 @@ +package makamys.neodymium.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import makamys.neodymium.ducks.ITessellator; +import makamys.neodymium.renderer.ChunkMesh; +import net.minecraft.client.renderer.Tessellator; + +@Mixin(Tessellator.class) +abstract class MixinTessellator implements ITessellator { + + private boolean nd$captureMeshes; + + @Inject(method = "draw", at = @At(value = "HEAD")) + private void preDraw(CallbackInfoReturnable cir) { + if(nd$captureMeshes) { + ChunkMesh.preTessellatorDraw((Tessellator)(Object)this); + } + } + + @Override + public void enableMeshCapturing(boolean enable) { + nd$captureMeshes = enable; + } + +} diff --git a/src/main/java/makamys/neodymium/mixin/MixinWorldRenderer.java b/src/main/java/makamys/neodymium/mixin/MixinWorldRenderer.java index 32b27a9..a794931 100644 --- a/src/main/java/makamys/neodymium/mixin/MixinWorldRenderer.java +++ b/src/main/java/makamys/neodymium/mixin/MixinWorldRenderer.java @@ -13,6 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.google.common.collect.Lists; import makamys.neodymium.Neodymium; +import makamys.neodymium.ducks.ITessellator; import makamys.neodymium.ducks.IWorldRenderer; import makamys.neodymium.renderer.ChunkMesh; import makamys.neodymium.renderer.NeoRenderer; @@ -81,12 +82,34 @@ abstract class MixinWorldRenderer implements IWorldRenderer { } } + @Inject(method = "preRenderBlocks", at = @At("HEAD")) + private void prePreRenderBlocks(int pass, CallbackInfo ci) { + if(Neodymium.isActive()) { + ((ITessellator)Tessellator.instance).enableMeshCapturing(true); + ChunkMesh cm = new ChunkMesh((WorldRenderer)(Object)this, pass); + nd$chunkMeshes.set(pass, cm); + ChunkMesh.setCaptureTarget(cm); + } + } + @Inject(method = "postRenderBlocks", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I")) private void prePostRenderBlocks(int pass, EntityLivingBase entity, CallbackInfo ci) { - if(Neodymium.isActive()) { + /*if(Neodymium.isActive()) { if(nd$chunkMeshes != null) { - nd$chunkMeshes.set(pass, ChunkMesh.fromTessellator(pass, WorldRenderer.class.cast(this), Tessellator.instance)); + if(nd$chunkMeshes.get(pass) == null) { + nd$chunkMeshes.set(pass, ChunkMesh.fromTessellator(pass, WorldRenderer.class.cast(this))); + } + nd$chunkMeshes.get(pass).addTessellatorData(Tessellator.instance); } + }*/ + } + + @Inject(method = "postRenderBlocks", at = @At("RETURN")) + private void postPostRenderBlocks(int pass, EntityLivingBase entity, CallbackInfo ci) { + if(Neodymium.isActive()) { + nd$chunkMeshes.get(pass).finishConstruction(); + ((ITessellator)Tessellator.instance).enableMeshCapturing(false); + ChunkMesh.setCaptureTarget(null); } } diff --git a/src/main/java/makamys/neodymium/renderer/ChunkMesh.java b/src/main/java/makamys/neodymium/renderer/ChunkMesh.java index c4956eb..a574765 100644 --- a/src/main/java/makamys/neodymium/renderer/ChunkMesh.java +++ b/src/main/java/makamys/neodymium/renderer/ChunkMesh.java @@ -1,5 +1,7 @@ package makamys.neodymium.renderer; +import static makamys.neodymium.Neodymium.LOGGER; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.IntBuffer; @@ -8,6 +10,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; import makamys.neodymium.config.Config; import makamys.neodymium.ducks.IWorldRenderer; @@ -22,7 +25,6 @@ import net.minecraft.tileentity.TileEntity; /** A mesh for a 16x16x16 region of the world. */ public class ChunkMesh extends Mesh { - Flags flags; WorldRenderer wr; private int[] subMeshStart = new int[NORMAL_ORDER.length]; @@ -32,10 +34,14 @@ public class ChunkMesh extends Mesh { private static RecyclingList quadBuf = new RecyclingList<>(() -> new MeshQuad()); + private static ChunkMesh meshCaptureTarget; + private static final QuadNormal[] NORMAL_ORDER = new QuadNormal[] {QuadNormal.NONE, QuadNormal.POSITIVE_Y, QuadNormal.POSITIVE_X, QuadNormal.POSITIVE_Z, QuadNormal.NEGATIVE_X, QuadNormal.NEGATIVE_Z, QuadNormal.NEGATIVE_Y}; private static final Comparator MESH_QUAD_RENDER_COMPARATOR = new MeshQuadRenderOrderComparator(); private static final int[] QUAD_NORMAL_TO_NORMAL_ORDER; + private static final Flags FLAGS = new Flags(true, true, true, false); + static { QUAD_NORMAL_TO_NORMAL_ORDER = new int[QuadNormal.values().length]; for(int i = 0; i < QuadNormal.values().length; i++) { @@ -47,39 +53,52 @@ public class ChunkMesh extends Mesh { } } - public ChunkMesh(WorldRenderer wr, Flags flags, int quadCount, List quads, int pass) { + public ChunkMesh(WorldRenderer wr, int pass) { this.x = wr.posX / 16; this.y = wr.posY / 16; this.z = wr.posZ / 16; this.wr = wr; - this.flags = flags; - this.quadCount = quadCount; this.pass = pass; Arrays.fill(subMeshStart, -1); - buffer = createBuffer(quads, quadCount); - usedRAM += buffer.limit(); instances++; + + if(!quadBuf.getAsList().isEmpty()) { + LOGGER.error("Invalid state: tried to construct a chunk mesh before the previous one has finished constructing!"); + } } - 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; + public static void preTessellatorDraw(Tessellator t) { + if(meshCaptureTarget != null) { + meshCaptureTarget.addTessellatorData(t); } + } + + private void addTessellatorData(Tessellator t) { + // TODO triangle support - int xOffset = wr.posX; - int yOffset = wr.posY; - int zOffset = wr.posZ; - - ChunkMesh.Flags flags = new ChunkMesh.Flags(t.hasTexture, t.hasBrightness, t.hasColor, t.hasNormals); - - quadBuf.reset(); + if(t.vertexCount == 0) { + // Sometimes the tessellator has no vertices and weird flags. Don't warn in this case, just silently return. + return; + } + if(t.vertexCount % 4 != 0) { + LOGGER.error("Error: Vertex count is not a multiple of 4"); + return; + } + if(t.drawMode != GL11.GL_QUADS) { + LOGGER.error("Error: Unsupported draw mode: " + t.drawMode); + } + if(!t.hasTexture || !t.hasBrightness || !t.hasColor || t.hasNormals) { + LOGGER.error("Error: Unsupported tessellator flags"); + return; + } for(int quadI = 0; quadI < t.vertexCount / 4; quadI++) { - quadBuf.next().setState(t.rawBuffer, quadI * 32, flags, (float)-t.xOffset, (float)-t.yOffset, (float)-t.zOffset); + quadBuf.next().setState(t.rawBuffer, quadI * 32, FLAGS, (float)-t.xOffset, (float)-t.yOffset, (float)-t.zOffset); } - + } + + public void finishConstruction() { List quads = quadBuf.getAsList(); if(Config.simplifyChunkMeshes) { @@ -110,16 +129,11 @@ public class ChunkMesh extends Mesh { } } - int quadCount = countValidQuads(quads); + quadCount = countValidQuads(quads); + buffer = createBuffer(quads, quadCount); + usedRAM += buffer.limit(); - if(quadCount > 0) { - return new ChunkMesh( - wr, - new ChunkMesh.Flags(t.hasTexture, t.hasBrightness, t.hasColor, t.hasNormals), - quadCount, quads, pass); - } else { - return null; - } + quadBuf.reset(); } private static void simplifyPlane(List planeQuads) { @@ -300,6 +314,10 @@ public class ChunkMesh extends Mesh { return player.getDistanceSq(centerX, centerY, centerZ); } + public static void setCaptureTarget(ChunkMesh cm) { + meshCaptureTarget = cm; + } + public static class Flags { boolean hasTexture; boolean hasBrightness; -- cgit