From bf4864981b91d5b9d1019528aff33b3e85d80e47 Mon Sep 17 00:00:00 2001
From: PepperCode1 <44146161+peppercode1@users.noreply.github.com>
Date: Thu, 23 Feb 2023 10:13:38 +0000
Subject: [PATCH] Indigo shade related fixes and other changes (#2898)

* Apply disabled shade from vanilla quads directly to material

- Remove QuadViewImpl.shade

* Fix enhanced AO calculation and respect non-terrain culling state

- Fix AoCalculator using AO face data computed with a potentially different shade state
- Move non-cached computation code to separate method in AoCalculator
- Turn AoCalculator's brightnessFunc and aoFunc into abstract methods
- Do not check null check world in non-terrain AO calculation since it cannot be null
- Pass through lightFace and shade state as method arguments in AoCalculator methods to prevent additional lookups
- Do not check for the axis aligned flag in AbstractQuadRenderer.shadeFlatQuad
- Respect cull parameter passed to non-terrain rendering by merging TerrainBlockRenderInfo into BlockRenderInfo
- Use reusable search pos when calling Block.shouldDrawSide to prevent additional BlockPos allocation
- Change BlockRenderContext.render and TerrainRenderContext.tessellateBlock to return void since return value is no longer used
- Remove QuadViewImpl.vertexStart since it is unused

* Add suggestions

- Mark Direction parameter to BlockRenderInfo.shouldDrawFace as Nullable
- Reuse MaterialFinder in FrameBakedModel

(cherry picked from commit 3a95925af4ebaa352fcf631b9b3e6f99b14062b3)

(cherry picked from commit 1adbf277eefc4a542d67acca3e94aea3b3f0e6ef)
---
 .../simple/client/FrameBakedModel.java        |   9 +-
 .../indigo/renderer/RenderMaterialImpl.java   |   8 +
 .../indigo/renderer/aocalc/AoCalculator.java  | 391 +++++++++---------
 .../renderer/mesh/MutableQuadViewImpl.java    |   7 +-
 .../indigo/renderer/mesh/QuadViewImpl.java    |  12 +-
 .../renderer/render/AbstractQuadRenderer.java |   7 +-
 .../renderer/render/BlockRenderContext.java   |  36 +-
 .../renderer/render/BlockRenderInfo.java      |  39 +-
 .../render/TerrainBlockRenderInfo.java        |  56 ---
 .../render/TerrainFallbackConsumer.java       |   1 -
 .../renderer/render/TerrainRenderContext.java |  20 +-
 .../renderer/MixinBlockModelRenderer.java     |   6 +-
 12 files changed, 275 insertions(+), 317 deletions(-)
 delete mode 100644 fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainBlockRenderInfo.java

diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameBakedModel.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameBakedModel.java
index 58c7a6cf3..e96b77588 100644
--- a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameBakedModel.java
+++ b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameBakedModel.java
@@ -37,9 +37,9 @@ import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Direction;
 import net.minecraft.world.BlockRenderView;
 
-import net.fabricmc.fabric.api.renderer.v1.Renderer;
 import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
 import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
+import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
 import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
 import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
 import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
@@ -57,9 +57,10 @@ final class FrameBakedModel implements BakedModel, FabricBakedModel {
 		this.frameMesh = frameMesh;
 		this.frameSprite = frameSprite;
 
-		Renderer renderer = RendererAccess.INSTANCE.getRenderer();
-		this.translucentMaterial = renderer.materialFinder().blendMode(0, BlendMode.TRANSLUCENT).find();
-		this.translucentEmissiveMaterial = renderer.materialFinder().blendMode(0, BlendMode.TRANSLUCENT).emissive(0, true).find();
+		MaterialFinder finder = RendererAccess.INSTANCE.getRenderer().materialFinder();
+		this.translucentMaterial = finder.blendMode(0, BlendMode.TRANSLUCENT).find();
+		finder.clear();
+		this.translucentEmissiveMaterial = finder.blendMode(0, BlendMode.TRANSLUCENT).emissive(0, true).find();
 	}
 
 	@Override
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/RenderMaterialImpl.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/RenderMaterialImpl.java
index 0f5653045..4e86f40e0 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/RenderMaterialImpl.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/RenderMaterialImpl.java
@@ -52,6 +52,14 @@ public abstract class RenderMaterialImpl {
 		return VALUES[index];
 	}
 
+	public static Value setDisableDiffuse(Value material, int textureIndex, boolean disable) {
+		if (material.disableDiffuse(textureIndex) != disable) {
+			return byIndex(disable ? (material.bits | DIFFUSE_FLAG) : (material.bits & ~DIFFUSE_FLAG));
+		}
+
+		return material;
+	}
+
 	protected int bits;
 
 	public BlendMode blendMode(int textureIndex) {
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java
index 0991eae5e..32af3ef5b 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java
@@ -39,8 +39,6 @@ import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.Vec3f;
 import net.minecraft.world.BlockRenderView;
 
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
 import net.fabricmc.fabric.impl.client.indigo.Indigo;
 import net.fabricmc.fabric.impl.client.indigo.renderer.accessor.AccessAmbientOcclusionCalculator;
 import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoFace.WeightFunction;
@@ -52,19 +50,7 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
 /**
  * Adaptation of inner, non-static class in BlockModelRenderer that serves same purpose.
  */
-@Environment(EnvType.CLIENT)
-public class AoCalculator {
-	@FunctionalInterface
-	public interface BrightnessFunc {
-		int apply(BlockPos pos, BlockState state);
-	}
-
-	/** Used to receive a method reference in constructor for ao value lookup. */
-	@FunctionalInterface
-	public interface AoFunc {
-		float apply(BlockPos pos, BlockState state);
-	}
-
+public abstract class AoCalculator {
 	/**
 	 * Vanilla models with cubic quads have vertices in a certain order, which allows
 	 * us to map them using a lookup. Adapted from enum in vanilla AoCalculator.
@@ -84,12 +70,14 @@ public class AoCalculator {
 	private final AccessAmbientOcclusionCalculator vanillaCalc;
 	private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
 	private final BlockPos.Mutable searchPos = new BlockPos.Mutable();
-	private final BlockRenderInfo blockInfo;
-	private final BrightnessFunc brightnessFunc;
-	private final AoFunc aoFunc;
+	protected final BlockRenderInfo blockInfo;
+
+	public abstract int light(BlockPos pos, BlockState state);
+
+	public abstract float ao(BlockPos pos, BlockState state);
 
 	/** caches results of {@link #computeFace(Direction, boolean, boolean)} for the current block. */
-	private final AoFaceData[] faceData = new AoFaceData[12];
+	private final AoFaceData[] faceData = new AoFaceData[24];
 
 	/** indicates which elements of {@link #faceData} have been computed for the current block. */
 	private int completionFlags = 0;
@@ -101,13 +89,11 @@ public class AoCalculator {
 	public final float[] ao = new float[4];
 	public final int[] light = new int[4];
 
-	public AoCalculator(BlockRenderInfo blockInfo, BrightnessFunc brightnessFunc, AoFunc aoFunc) {
+	public AoCalculator(BlockRenderInfo blockInfo) {
 		this.blockInfo = blockInfo;
-		this.brightnessFunc = brightnessFunc;
-		this.aoFunc = aoFunc;
 		this.vanillaCalc = VanillaAoHelper.get();
 
-		for (int i = 0; i < 12; i++) {
+		for (int i = 0; i < 24; i++) {
 			faceData[i] = new AoFaceData();
 		}
 	}
@@ -123,12 +109,7 @@ public class AoCalculator {
 
 		switch (config) {
 		case VANILLA:
-			// prevent NPE in error case of failed reflection for vanilla calculator access
-			if (vanillaCalc == null) {
-				calcFastVanilla(quad);
-			} else {
-				calcVanilla(quad);
-			}
+			calcVanilla(quad);
 
 			// no point in comparing vanilla with itself
 			shouldCompare = false;
@@ -156,7 +137,7 @@ public class AoCalculator {
 			calcEnhanced(quad);
 		}
 
-		if (shouldCompare && vanillaCalc != null) {
+		if (shouldCompare) {
 			float[] vanillaAo = new float[4];
 			int[] vanillaLight = new int[4];
 			calcVanilla(quad, vanillaAo, vanillaLight);
@@ -188,11 +169,11 @@ public class AoCalculator {
 
 	private void calcVanilla(MutableQuadViewImpl quad, float[] aoDest, int[] lightDest) {
 		vanillaAoControlBits.clear();
-		final Direction face = quad.lightFace();
+		final Direction lightFace = quad.lightFace();
 		quad.toVanilla(0, vertexData, 0, false);
 
-		VanillaAoHelper.updateShape(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, face, vanillaAoData, vanillaAoControlBits);
-		vanillaCalc.fabric_apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, quad.lightFace(), vanillaAoData, vanillaAoControlBits, quad.hasShade());
+		VanillaAoHelper.updateShape(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, lightFace, vanillaAoData, vanillaAoControlBits);
+		vanillaCalc.fabric_apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, lightFace, vanillaAoData, vanillaAoControlBits, quad.hasShade());
 
 		System.arraycopy(vanillaCalc.fabric_colorMultiplier(), 0, aoDest, 0, 4);
 		System.arraycopy(vanillaCalc.fabric_brightness(), 0, lightDest, 0, 4);
@@ -207,9 +188,9 @@ public class AoCalculator {
 		}
 
 		if ((flags & CUBIC_FLAG) == 0) {
-			vanillaPartialFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
+			vanillaPartialFace(quad, quad.lightFace(), (flags & LIGHT_FACE_FLAG) != 0, quad.hasShade());
 		} else {
-			vanillaFullFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
+			vanillaFullFace(quad, quad.lightFace(), (flags & LIGHT_FACE_FLAG) != 0, quad.hasShade());
 		}
 	}
 
@@ -217,28 +198,26 @@ public class AoCalculator {
 		switch (quad.geometryFlags()) {
 		case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
 		case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
-			vanillaPartialFace(quad, true);
+			vanillaPartialFace(quad, quad.lightFace(), true, quad.hasShade());
 			break;
 
 		case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
 		case AXIS_ALIGNED_FLAG:
-			blendedPartialFace(quad);
+			blendedPartialFace(quad, quad.lightFace(), quad.hasShade());
 			break;
 
 		default:
-			irregularFace(quad);
+			irregularFace(quad, quad.hasShade());
 			break;
 		}
 	}
 
-	private void vanillaFullFace(QuadViewImpl quad, boolean isOnLightFace) {
-		final Direction lightFace = quad.lightFace();
-		computeFace(lightFace, isOnLightFace, quad.hasShade()).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
+	private void vanillaFullFace(QuadViewImpl quad, Direction lightFace, boolean isOnLightFace, boolean shade) {
+		computeFace(lightFace, isOnLightFace, shade).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
 	}
 
-	private void vanillaPartialFace(QuadViewImpl quad, boolean isOnLightFace) {
-		final Direction lightFace = quad.lightFace();
-		AoFaceData faceData = computeFace(lightFace, isOnLightFace, quad.hasShade());
+	private void vanillaPartialFace(QuadViewImpl quad, Direction lightFace, boolean isOnLightFace, boolean shade) {
+		AoFaceData faceData = computeFace(lightFace, isOnLightFace, shade);
 		final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
 		final float[] w = this.w;
 
@@ -249,36 +228,35 @@ public class AoCalculator {
 		}
 	}
 
-	/** used in {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} as return variable to avoid new allocation. */
+	/** used in {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade)} as return variable to avoid new allocation. */
 	AoFaceData tmpFace = new AoFaceData();
 
 	/** Returns linearly interpolated blend of outer and inner face based on depth of vertex in face. */
-	private AoFaceData blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace) {
+	private AoFaceData blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade) {
 		final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
 		final float w0 = 1 - w1;
-		return AoFaceData.weightedMean(computeFace(lightFace, true, quad.hasShade()), w0, computeFace(lightFace, false, quad.hasShade()), w1, tmpFace);
+		return AoFaceData.weightedMean(computeFace(lightFace, true, shade), w0, computeFace(lightFace, false, shade), w1, tmpFace);
 	}
 
 	/**
-	 * Like {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} but optimizes if depth is 0 or 1.
+	 * Like {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade)} but optimizes if depth is 0 or 1.
 	 * Used for irregular faces when depth varies by vertex to avoid unneeded interpolation.
 	 */
-	private AoFaceData gatherInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace) {
+	private AoFaceData gatherInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade) {
 		final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
 
 		if (MathHelper.approximatelyEquals(w1, 0)) {
-			return computeFace(lightFace, true, quad.hasShade());
+			return computeFace(lightFace, true, shade);
 		} else if (MathHelper.approximatelyEquals(w1, 1)) {
-			return computeFace(lightFace, false, quad.hasShade());
+			return computeFace(lightFace, false, shade);
 		} else {
 			final float w0 = 1 - w1;
-			return AoFaceData.weightedMean(computeFace(lightFace, true, quad.hasShade()), w0, computeFace(lightFace, false, quad.hasShade()), w1, tmpFace);
+			return AoFaceData.weightedMean(computeFace(lightFace, true, shade), w0, computeFace(lightFace, false, shade), w1, tmpFace);
 		}
 	}
 
-	private void blendedPartialFace(QuadViewImpl quad) {
-		final Direction lightFace = quad.lightFace();
-		AoFaceData faceData = blendedInsetFace(quad, 0, lightFace);
+	private void blendedPartialFace(QuadViewImpl quad, Direction lightFace, boolean shade) {
+		AoFaceData faceData = blendedInsetFace(quad, 0, lightFace, shade);
 		final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
 
 		for (int i = 0; i < 4; i++) {
@@ -291,7 +269,7 @@ public class AoCalculator {
 	/** used exclusively in irregular face to avoid new heap allocations each call. */
 	private final Vec3f vertexNormal = new Vec3f();
 
-	private void irregularFace(MutableQuadViewImpl quad) {
+	private void irregularFace(MutableQuadViewImpl quad, boolean shade) {
 		final Vec3f faceNorm = quad.faceNormal();
 		Vec3f normal;
 		final float[] w = this.w;
@@ -307,7 +285,7 @@ public class AoCalculator {
 
 			if (!MathHelper.approximatelyEquals(0f, x)) {
 				final Direction face = x > 0 ? Direction.EAST : Direction.WEST;
-				final AoFaceData fd = gatherInsetFace(quad, i, face);
+				final AoFaceData fd = gatherInsetFace(quad, i, face, shade);
 				AoFace.get(face).weightFunc.apply(quad, i, w);
 				final float n = x * x;
 				final float a = fd.weigtedAo(w);
@@ -325,7 +303,7 @@ public class AoCalculator {
 
 			if (!MathHelper.approximatelyEquals(0f, y)) {
 				final Direction face = y > 0 ? Direction.UP : Direction.DOWN;
-				final AoFaceData fd = gatherInsetFace(quad, i, face);
+				final AoFaceData fd = gatherInsetFace(quad, i, face, shade);
 				AoFace.get(face).weightFunc.apply(quad, i, w);
 				final float n = y * y;
 				final float a = fd.weigtedAo(w);
@@ -343,7 +321,7 @@ public class AoCalculator {
 
 			if (!MathHelper.approximatelyEquals(0f, z)) {
 				final Direction face = z > 0 ? Direction.SOUTH : Direction.NORTH;
-				final AoFaceData fd = gatherInsetFace(quad, i, face);
+				final AoFaceData fd = gatherInsetFace(quad, i, face, shade);
 				AoFace.get(face).weightFunc.apply(quad, i, w);
 				final float n = z * z;
 				final float a = fd.weigtedAo(w);
@@ -362,6 +340,19 @@ public class AoCalculator {
 		}
 	}
 
+	private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace, boolean shade) {
+		final int faceDataIndex = shade ? (isOnBlockFace ? lightFace.getId() : lightFace.getId() + 6) : (isOnBlockFace ? lightFace.getId() + 12 : lightFace.getId() + 18);
+		final int mask = 1 << faceDataIndex;
+		final AoFaceData result = faceData[faceDataIndex];
+
+		if ((completionFlags & mask) == 0) {
+			completionFlags |= mask;
+			computeFace(result, lightFace, isOnBlockFace, shade);
+		}
+
+		return result;
+	}
+
 	/**
 	 * Computes smoothed brightness and Ao shading for four corners of a block face.
 	 * Outer block face is what you normally see and what you get when the second
@@ -370,155 +361,145 @@ public class AoCalculator {
 	 * in vanilla logic for some blocks that aren't full opaque cubes.
 	 * Except for parameterization, the logic itself is practically identical to vanilla.
 	 */
-	private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace, boolean shade) {
-		final int faceDataIndex = isOnBlockFace ? lightFace.getId() : lightFace.getId() + 6;
-		final int mask = 1 << faceDataIndex;
-		final AoFaceData result = faceData[faceDataIndex];
+	private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlockFace, boolean shade) {
+		final BlockRenderView world = blockInfo.blockView;
+		final BlockPos pos = blockInfo.blockPos;
+		final BlockState blockState = blockInfo.blockState;
+		final BlockPos.Mutable lightPos = this.lightPos;
+		final BlockPos.Mutable searchPos = this.searchPos;
+		BlockState searchState;
 
-		if ((completionFlags & mask) == 0) {
-			completionFlags |= mask;
-
-			final BlockRenderView world = blockInfo.blockView;
-			final BlockPos pos = blockInfo.blockPos;
-			final BlockState blockState = blockInfo.blockState;
-			final BlockPos.Mutable lightPos = this.lightPos;
-			final BlockPos.Mutable searchPos = this.searchPos;
-			BlockState searchState;
-
-			if (isOnBlockFace) {
-				lightPos.set(pos, lightFace);
-			} else {
-				lightPos.set(pos);
-			}
-
-			AoFace aoFace = AoFace.get(lightFace);
-
-			// Vanilla was further offsetting the positions for opaque block checks in the
-			// direction of the light face, but it was actually mis-sampling and causing
-			// visible artifacts in certain situations
-
-			searchPos.set(lightPos, aoFace.neighbors[0]);
-			searchState = world.getBlockState(searchPos);
-			final int light0 = brightnessFunc.apply(searchPos, searchState);
-			final float ao0 = aoFunc.apply(searchPos, searchState);
-
-			if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
-				searchPos.move(lightFace);
-				searchState = world.getBlockState(searchPos);
-			}
-
-			final boolean isClear0 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0;
-
-			searchPos.set(lightPos, aoFace.neighbors[1]);
-			searchState = world.getBlockState(searchPos);
-			final int light1 = brightnessFunc.apply(searchPos, searchState);
-			final float ao1 = aoFunc.apply(searchPos, searchState);
-
-			if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
-				searchPos.move(lightFace);
-				searchState = world.getBlockState(searchPos);
-			}
-
-			final boolean isClear1 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0;
-
-			searchPos.set(lightPos, aoFace.neighbors[2]);
-			searchState = world.getBlockState(searchPos);
-			final int light2 = brightnessFunc.apply(searchPos, searchState);
-			final float ao2 = aoFunc.apply(searchPos, searchState);
-
-			if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
-				searchPos.move(lightFace);
-				searchState = world.getBlockState(searchPos);
-			}
-
-			final boolean isClear2 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0;
-
-			searchPos.set(lightPos, aoFace.neighbors[3]);
-			searchState = world.getBlockState(searchPos);
-			final int light3 = brightnessFunc.apply(searchPos, searchState);
-			final float ao3 = aoFunc.apply(searchPos, searchState);
-
-			if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
-				searchPos.move(lightFace);
-				searchState = world.getBlockState(searchPos);
-			}
-
-			final boolean isClear3 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0;
-
-			// c = corner - values at corners of face
-			int cLight0, cLight1, cLight2, cLight3;
-			float cAo0, cAo1, cAo2, cAo3;
-
-			// If neighbors on both sides of the corner are opaque, then apparently we use the light/shade
-			// from one of the sides adjacent to the corner.  If either neighbor is clear (no light subtraction)
-			// then we use values from the outwardly diagonal corner. (outwardly = position is one more away from light face)
-			if (!isClear2 && !isClear0) {
-				cAo0 = ao0;
-				cLight0 = light0;
-			} else {
-				searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[2]);
-				searchState = world.getBlockState(searchPos);
-				cAo0 = aoFunc.apply(searchPos, searchState);
-				cLight0 = brightnessFunc.apply(searchPos, searchState);
-			}
-
-			if (!isClear3 && !isClear0) {
-				cAo1 = ao0;
-				cLight1 = light0;
-			} else {
-				searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[3]);
-				searchState = world.getBlockState(searchPos);
-				cAo1 = aoFunc.apply(searchPos, searchState);
-				cLight1 = brightnessFunc.apply(searchPos, searchState);
-			}
-
-			if (!isClear2 && !isClear1) {
-				cAo2 = ao1;
-				cLight2 = light1;
-			} else {
-				searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[2]);
-				searchState = world.getBlockState(searchPos);
-				cAo2 = aoFunc.apply(searchPos, searchState);
-				cLight2 = brightnessFunc.apply(searchPos, searchState);
-			}
-
-			if (!isClear3 && !isClear1) {
-				cAo3 = ao1;
-				cLight3 = light1;
-			} else {
-				searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[3]);
-				searchState = world.getBlockState(searchPos);
-				cAo3 = aoFunc.apply(searchPos, searchState);
-				cLight3 = brightnessFunc.apply(searchPos, searchState);
-			}
-
-			// If on block face or neighbor isn't occluding, "center" will be neighbor brightness
-			// Doesn't use light pos because logic not based solely on this block's geometry
-			int lightCenter;
-			searchPos.set(pos, lightFace);
-			searchState = world.getBlockState(searchPos);
-
-			if (isOnBlockFace || !searchState.isOpaqueFullCube(world, searchPos)) {
-				lightCenter = brightnessFunc.apply(searchPos, searchState);
-			} else {
-				lightCenter = brightnessFunc.apply(pos, blockState);
-			}
-
-			float aoCenter = aoFunc.apply(lightPos, world.getBlockState(lightPos));
-			float worldBrightness = world.getBrightness(lightFace, shade);
-
-			result.a0 = ((ao3 + ao0 + cAo1 + aoCenter) * 0.25F) * worldBrightness;
-			result.a1 = ((ao2 + ao0 + cAo0 + aoCenter) * 0.25F) * worldBrightness;
-			result.a2 = ((ao2 + ao1 + cAo2 + aoCenter) * 0.25F) * worldBrightness;
-			result.a3 = ((ao3 + ao1 + cAo3 + aoCenter) * 0.25F) * worldBrightness;
-
-			result.l0(meanBrightness(light3, light0, cLight1, lightCenter));
-			result.l1(meanBrightness(light2, light0, cLight0, lightCenter));
-			result.l2(meanBrightness(light2, light1, cLight2, lightCenter));
-			result.l3(meanBrightness(light3, light1, cLight3, lightCenter));
+		if (isOnBlockFace) {
+			lightPos.set(pos, lightFace);
+		} else {
+			lightPos.set(pos);
 		}
 
-		return result;
+		AoFace aoFace = AoFace.get(lightFace);
+
+		// Vanilla was further offsetting the positions for opaque block checks in the
+		// direction of the light face, but it was actually mis-sampling and causing
+		// visible artifacts in certain situations
+
+		searchPos.set(lightPos, aoFace.neighbors[0]);
+		searchState = world.getBlockState(searchPos);
+		final int light0 = light(searchPos, searchState);
+		final float ao0 = ao(searchPos, searchState);
+
+		if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
+			searchPos.move(lightFace);
+			searchState = world.getBlockState(searchPos);
+		}
+
+		final boolean isClear0 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0;
+
+		searchPos.set(lightPos, aoFace.neighbors[1]);
+		searchState = world.getBlockState(searchPos);
+		final int light1 = light(searchPos, searchState);
+		final float ao1 = ao(searchPos, searchState);
+
+		if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
+			searchPos.move(lightFace);
+			searchState = world.getBlockState(searchPos);
+		}
+
+		final boolean isClear1 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0;
+
+		searchPos.set(lightPos, aoFace.neighbors[2]);
+		searchState = world.getBlockState(searchPos);
+		final int light2 = light(searchPos, searchState);
+		final float ao2 = ao(searchPos, searchState);
+
+		if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
+			searchPos.move(lightFace);
+			searchState = world.getBlockState(searchPos);
+		}
+
+		final boolean isClear2 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0;
+
+		searchPos.set(lightPos, aoFace.neighbors[3]);
+		searchState = world.getBlockState(searchPos);
+		final int light3 = light(searchPos, searchState);
+		final float ao3 = ao(searchPos, searchState);
+
+		if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
+			searchPos.move(lightFace);
+			searchState = world.getBlockState(searchPos);
+		}
+
+		final boolean isClear3 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0;
+
+		// c = corner - values at corners of face
+		int cLight0, cLight1, cLight2, cLight3;
+		float cAo0, cAo1, cAo2, cAo3;
+
+		// If neighbors on both sides of the corner are opaque, then apparently we use the light/shade
+		// from one of the sides adjacent to the corner.  If either neighbor is clear (no light subtraction)
+		// then we use values from the outwardly diagonal corner. (outwardly = position is one more away from light face)
+		if (!isClear2 && !isClear0) {
+			cAo0 = ao0;
+			cLight0 = light0;
+		} else {
+			searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[2]);
+			searchState = world.getBlockState(searchPos);
+			cAo0 = ao(searchPos, searchState);
+			cLight0 = light(searchPos, searchState);
+		}
+
+		if (!isClear3 && !isClear0) {
+			cAo1 = ao0;
+			cLight1 = light0;
+		} else {
+			searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[3]);
+			searchState = world.getBlockState(searchPos);
+			cAo1 = ao(searchPos, searchState);
+			cLight1 = light(searchPos, searchState);
+		}
+
+		if (!isClear2 && !isClear1) {
+			cAo2 = ao1;
+			cLight2 = light1;
+		} else {
+			searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[2]);
+			searchState = world.getBlockState(searchPos);
+			cAo2 = ao(searchPos, searchState);
+			cLight2 = light(searchPos, searchState);
+		}
+
+		if (!isClear3 && !isClear1) {
+			cAo3 = ao1;
+			cLight3 = light1;
+		} else {
+			searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[3]);
+			searchState = world.getBlockState(searchPos);
+			cAo3 = ao(searchPos, searchState);
+			cLight3 = light(searchPos, searchState);
+		}
+
+		// If on block face or neighbor isn't occluding, "center" will be neighbor brightness
+		// Doesn't use light pos because logic not based solely on this block's geometry
+		int lightCenter;
+		searchPos.set(pos, lightFace);
+		searchState = world.getBlockState(searchPos);
+
+		if (isOnBlockFace || !searchState.isOpaqueFullCube(world, searchPos)) {
+			lightCenter = light(searchPos, searchState);
+		} else {
+			lightCenter = light(pos, blockState);
+		}
+
+		float aoCenter = ao(lightPos, world.getBlockState(lightPos));
+		float worldBrightness = world.getBrightness(lightFace, shade);
+
+		result.a0 = ((ao3 + ao0 + cAo1 + aoCenter) * 0.25F) * worldBrightness;
+		result.a1 = ((ao2 + ao0 + cAo0 + aoCenter) * 0.25F) * worldBrightness;
+		result.a2 = ((ao2 + ao1 + cAo2 + aoCenter) * 0.25F) * worldBrightness;
+		result.a3 = ((ao3 + ao1 + cAo3 + aoCenter) * 0.25F) * worldBrightness;
+
+		result.l0(meanBrightness(light3, light0, cLight1, lightCenter));
+		result.l1(meanBrightness(light2, light0, cLight0, lightCenter));
+		result.l2(meanBrightness(light2, light1, cLight2, lightCenter));
+		result.l3(meanBrightness(light3, light1, cLight3, lightCenter));
 	}
 
 	/**
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java
index c02113a36..60e40ec7c 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java
@@ -38,6 +38,7 @@ import net.minecraft.util.math.Direction;
 import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
 import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
 import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer;
+import net.fabricmc.fabric.impl.client.indigo.renderer.RenderMaterialImpl;
 import net.fabricmc.fabric.impl.client.indigo.renderer.RenderMaterialImpl.Value;
 import net.fabricmc.fabric.impl.client.indigo.renderer.helper.NormalHelper;
 import net.fabricmc.fabric.impl.client.indigo.renderer.helper.TextureHelper;
@@ -116,9 +117,13 @@ public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEm
 		data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(0, cullFace);
 		nominalFace(quad.getFace());
 		colorIndex(quad.getColorIndex());
+
+		if (!quad.hasShade()) {
+			material = RenderMaterialImpl.setDisableDiffuse((Value) material, 0, true);
+		}
+
 		material(material);
 		tag(0);
-		shade(quad.hasShade());
 		isGeometryInvalid = true;
 		return this;
 	}
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/QuadViewImpl.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/QuadViewImpl.java
index c386e3a39..2e940f88b 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/QuadViewImpl.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/QuadViewImpl.java
@@ -18,7 +18,6 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.mesh;
 
 import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_BITS;
 import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_COLOR_INDEX;
-import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_STRIDE;
 import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_TAG;
 import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.QUAD_STRIDE;
 import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.VERTEX_COLOR;
@@ -52,7 +51,6 @@ public class QuadViewImpl implements QuadView {
 	/** True when geometry flags or light face may not match geometry. */
 	protected boolean isGeometryInvalid = true;
 	protected final Vec3f faceNormal = new Vec3f();
-	private boolean shade = true;
 
 	/** Size and where it comes from will vary in subtypes. But in all cases quad is fully encoded to array. */
 	protected int[] data;
@@ -268,15 +266,7 @@ public class QuadViewImpl implements QuadView {
 		return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_V]);
 	}
 
-	public int vertexStart() {
-		return baseIndex + HEADER_STRIDE;
-	}
-
 	public boolean hasShade() {
-		return shade && !material().disableDiffuse(0);
-	}
-
-	public void shade(boolean shade) {
-		this.shade = shade;
+		return !material().disableDiffuse(0);
 	}
 }
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java
index 2fb815bd1..e0fd7dddc 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java
@@ -36,7 +36,6 @@ import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
 import net.fabricmc.fabric.impl.client.indigo.renderer.RenderMaterialImpl;
 import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator;
 import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper;
-import net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper;
 import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl;
 
 /**
@@ -234,9 +233,9 @@ public abstract class AbstractQuadRenderer {
 	 * even for un-shaded quads. These are also applied with AO shading but that is done in AO calculator.
 	 */
 	private void shadeFlatQuad(MutableQuadViewImpl quad) {
-		if ((quad.geometryFlags() & GeometryHelper.AXIS_ALIGNED_FLAG) == 0 || quad.hasVertexNormals()) {
-			// Quads that aren't direction-aligned or that have vertex normals need to be shaded
-			// using interpolation - vanilla can't handle them. Generally only applies to modded models.
+		if (quad.hasVertexNormals()) {
+			// Quads that have vertex normals need to be shaded using interpolation - vanilla can't
+			// handle them. Generally only applies to modded models.
 			final float faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), quad.hasShade());
 
 			for (int i = 0; i < 4; i++) {
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java
index 7d6b8831a..0eaa957fd 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java
@@ -21,7 +21,6 @@ import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 import net.minecraft.block.BlockState;
-import net.minecraft.client.render.LightmapTextureManager;
 import net.minecraft.client.render.RenderLayer;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.WorldRenderer;
@@ -43,10 +42,20 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoLuminanceFix;
  */
 public class BlockRenderContext extends AbstractRenderContext {
 	private final BlockRenderInfo blockInfo = new BlockRenderInfo();
-	private final AoCalculator aoCalc = new AoCalculator(blockInfo, this::brightness, this::aoLevel);
+
+	private final AoCalculator aoCalc = new AoCalculator(blockInfo) {
+		@Override
+		public int light(BlockPos pos, BlockState state) {
+			return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, state, pos);
+		}
+
+		@Override
+		public float ao(BlockPos pos, BlockState state) {
+			return AoLuminanceFix.INSTANCE.apply(blockInfo.blockView, pos, state);
+		}
+	};
 
 	private VertexConsumer bufferBuilder;
-	private boolean didOutput = false;
 	// These are kept as fields to avoid the heap allocation for a supplier.
 	// BlockModelRenderer allows the caller to supply both the random object and seed.
 	private Random random;
@@ -94,25 +103,11 @@ public class BlockRenderContext extends AbstractRenderContext {
 		}
 	};
 
-	private int brightness(BlockPos pos, BlockState state) {
-		if (blockInfo.blockView == null) {
-			return LightmapTextureManager.MAX_LIGHT_COORDINATE;
-		}
-
-		return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, state, pos);
-	}
-
-	private float aoLevel(BlockPos pos, BlockState state) {
-		final BlockRenderView blockView = blockInfo.blockView;
-		return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos, state);
-	}
-
 	private VertexConsumer outputBuffer(RenderLayer renderLayer) {
-		didOutput = true;
 		return bufferBuilder;
 	}
 
-	public boolean render(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, VertexConsumer buffer, Random random, long seed, int overlay) {
+	public void render(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, VertexConsumer buffer, boolean cull, Random random, long seed, int overlay) {
 		this.bufferBuilder = buffer;
 		this.matrix = matrixStack.peek().getPositionMatrix();
 		this.normalMatrix = matrixStack.peek().getNormalMatrix();
@@ -120,9 +115,8 @@ public class BlockRenderContext extends AbstractRenderContext {
 		this.seed = seed;
 
 		this.overlay = overlay;
-		this.didOutput = false;
 		aoCalc.clear();
-		blockInfo.setBlockView(blockView);
+		blockInfo.prepareForWorld(blockView, cull);
 		blockInfo.prepareForBlock(state, pos, model.useAmbientOcclusion());
 
 		((FabricBakedModel) model).emitBlockQuads(blockView, state, pos, randomSupplier, this);
@@ -131,8 +125,6 @@ public class BlockRenderContext extends AbstractRenderContext {
 		this.bufferBuilder = null;
 		this.random = null;
 		this.seed = seed;
-
-		return didOutput;
 	}
 
 	@Override
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java
index d99696f74..cde47e1ab 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java
@@ -19,6 +19,9 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render;
 import java.util.Random;
 import java.util.function.Supplier;
 
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.block.Block;
 import net.minecraft.block.BlockState;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.color.block.BlockColors;
@@ -39,7 +42,8 @@ import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
  */
 public class BlockRenderInfo {
 	private final BlockColors blockColorMap = MinecraftClient.getInstance().getBlockColors();
-	public final Random random = new Random();
+	private final BlockPos.Mutable searchPos = new BlockPos.Mutable();
+	private final Random random = new Random();
 	public BlockRenderView blockView;
 	public BlockPos blockPos;
 	public BlockState blockState;
@@ -47,6 +51,10 @@ public class BlockRenderInfo {
 	boolean defaultAo;
 	RenderLayer defaultLayer;
 
+	private boolean enableCulling;
+	private int cullCompletionFlags;
+	private int cullResultFlags;
+
 	public final Supplier<Random> randomSupplier = () -> {
 		final Random result = random;
 		long seed = this.seed;
@@ -60,18 +68,22 @@ public class BlockRenderInfo {
 		return result;
 	};
 
-	public void setBlockView(BlockRenderView blockView) {
+	public void prepareForWorld(BlockRenderView blockView, boolean enableCulling) {
 		this.blockView = blockView;
+		this.enableCulling = enableCulling;
 	}
 
 	public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAO) {
 		this.blockPos = blockPos;
 		this.blockState = blockState;
-		// in the unlikely case seed actually matches this, we'll simply retrieve it more than one
+		// in the unlikely case seed actually matches this, we'll simply retrieve it more than once
 		seed = -1L;
 		defaultAo = modelAO && MinecraftClient.isAmbientOcclusionEnabled() && blockState.getLuminance() == 0;
 
 		defaultLayer = RenderLayers.getBlockLayer(blockState);
+
+		cullCompletionFlags = 0;
+		cullResultFlags = 0;
 	}
 
 	public void release() {
@@ -83,8 +95,25 @@ public class BlockRenderInfo {
 		return 0xFF000000 | blockColorMap.getColor(blockState, blockView, blockPos, colorIndex);
 	}
 
-	boolean shouldDrawFace(Direction face) {
-		return true;
+	boolean shouldDrawFace(@Nullable Direction face) {
+		if (face == null || !enableCulling) {
+			return true;
+		}
+
+		final int mask = 1 << face.getId();
+
+		if ((cullCompletionFlags & mask) == 0) {
+			cullCompletionFlags |= mask;
+
+			if (Block.shouldDrawSide(blockState, blockView, blockPos, face, searchPos.set(blockPos, face))) {
+				cullResultFlags |= mask;
+				return true;
+			} else {
+				return false;
+			}
+		} else {
+			return (cullResultFlags & mask) != 0;
+		}
 	}
 
 	RenderLayer effectiveRenderLayer(BlendMode blendMode) {
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainBlockRenderInfo.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainBlockRenderInfo.java
deleted file mode 100644
index c7404ea4b..000000000
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainBlockRenderInfo.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.fabricmc.fabric.impl.client.indigo.renderer.render;
-
-import net.minecraft.block.Block;
-import net.minecraft.block.BlockState;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.Direction;
-
-public class TerrainBlockRenderInfo extends BlockRenderInfo {
-	private int cullCompletionFlags;
-	private int cullResultFlags;
-
-	@Override
-	public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAO) {
-		super.prepareForBlock(blockState, blockPos, modelAO);
-		cullCompletionFlags = 0;
-		cullResultFlags = 0;
-	}
-
-	@Override
-	boolean shouldDrawFace(Direction face) {
-		if (face == null) {
-			return true;
-		}
-
-		final int mask = 1 << face.getId();
-
-		if ((cullCompletionFlags & mask) == 0) {
-			cullCompletionFlags |= mask;
-
-			if (Block.shouldDrawSide(blockState, blockView, blockPos, face, blockPos.offset(face))) {
-				cullResultFlags |= mask;
-				return true;
-			} else {
-				return false;
-			}
-		} else {
-			return (cullResultFlags & mask) != 0;
-		}
-	}
-}
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainFallbackConsumer.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainFallbackConsumer.java
index 4dd8b7d9f..d76b73e4a 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainFallbackConsumer.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainFallbackConsumer.java
@@ -18,7 +18,6 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render;
 
 import java.util.List;
 import java.util.Random;
-import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainRenderContext.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainRenderContext.java
index 0df538529..89d104248 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainRenderContext.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainRenderContext.java
@@ -20,8 +20,8 @@ import java.util.function.Consumer;
 
 import net.minecraft.block.BlockState;
 import net.minecraft.client.render.chunk.BlockBufferBuilderStorage;
+import net.minecraft.client.render.chunk.ChunkBuilder;
 import net.minecraft.client.render.chunk.ChunkBuilder.BuiltChunk;
-import net.minecraft.client.render.chunk.ChunkBuilder.ChunkData;
 import net.minecraft.client.render.chunk.ChunkRendererRegion;
 import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.client.util.math.MatrixStack;
@@ -46,9 +46,19 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator;
 public class TerrainRenderContext extends AbstractRenderContext {
 	public static final ThreadLocal<TerrainRenderContext> POOL = ThreadLocal.withInitial(TerrainRenderContext::new);
 
-	private final TerrainBlockRenderInfo blockInfo = new TerrainBlockRenderInfo();
+	private final BlockRenderInfo blockInfo = new BlockRenderInfo();
 	private final ChunkRenderInfo chunkInfo = new ChunkRenderInfo();
-	private final AoCalculator aoCalc = new AoCalculator(blockInfo, chunkInfo::cachedBrightness, chunkInfo::cachedAoLevel);
+	private final AoCalculator aoCalc = new AoCalculator(blockInfo) {
+		@Override
+		public int light(BlockPos pos, BlockState state) {
+			return chunkInfo.cachedBrightness(pos, state);
+		}
+
+		@Override
+		public float ao(BlockPos pos, BlockState state) {
+			return chunkInfo.cachedAoLevel(pos, state);
+		}
+	};
 
 	private final AbstractMeshConsumer meshConsumer = new AbstractMeshConsumer(blockInfo, chunkInfo::getInitializedBuffer, aoCalc, this::transform) {
 		@Override
@@ -84,8 +94,8 @@ public class TerrainRenderContext extends AbstractRenderContext {
 		}
 	};
 
-	public void prepare(ChunkRendererRegion blockView, BuiltChunk chunkRenderer, ChunkData chunkData, BlockBufferBuilderStorage builders) {
-		blockInfo.setBlockView(blockView);
+	public void prepare(ChunkRendererRegion blockView, BuiltChunk chunkRenderer, ChunkBuilder.ChunkData chunkData, BlockBufferBuilderStorage builders) {
+		blockInfo.prepareForWorld(blockView, true);
 		chunkInfo.prepare(blockView, chunkRenderer, chunkData, builders);
 	}
 
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/mixin/client/indigo/renderer/MixinBlockModelRenderer.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/mixin/client/indigo/renderer/MixinBlockModelRenderer.java
index 5ca38118a..ae1c9e497 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/mixin/client/indigo/renderer/MixinBlockModelRenderer.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/mixin/client/indigo/renderer/MixinBlockModelRenderer.java
@@ -50,11 +50,11 @@ public abstract class MixinBlockModelRenderer implements AccessBlockModelRendere
 	protected abstract void getQuadDimensions(BlockRenderView blockView, BlockState blockState, BlockPos blockPos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits);
 
 	@Inject(at = @At("HEAD"), method = "render(Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/client/render/model/BakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;ZLjava/util/Random;JI)Z", cancellable = true)
-	private void hookRender(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrix, VertexConsumer buffer, boolean checkSides, Random rand, long seed, int overlay, CallbackInfoReturnable<Boolean> ci) {
+	private void hookRender(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrix, VertexConsumer buffer, boolean cull, Random rand, long seed, int overlay, CallbackInfoReturnable<Boolean> ci) {
 		if (!((FabricBakedModel) model).isVanillaAdapter()) {
 			BlockRenderContext context = fabric_contexts.get();
-			// Note that we do not support face-culling here (so checkSides is ignored)
-			ci.setReturnValue(context.render(blockView, model, state, pos, matrix, buffer, rand, seed, overlay));
+			context.render(blockView, model, state, pos, matrix, buffer, cull, rand, seed, overlay);
+			ci.cancel();
 		}
 	}