From 6bd39c990ed7f8aa66aab747f63f38665c292a8f Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+technici4n@users.noreply.github.com>
Date: Mon, 2 Jan 2023 13:05:49 +0000
Subject: [PATCH] Indigo and Renderer API fixes related to fallback consumers
 (#2775)

* Fix #2639: Indigo fallback consumer does not respect BlendMode or emissivity

* Change renderer testmod to test material change

* Remove presumably unneeded `quad.geometryFlags()` call

* Also test emissivity

* Call emitBlockQuads in the testmod

* Allow passing the block state explicitly to the fallback consumer. Fixes #1871

* Expand testmod to also test item models

* Also fix fallback consumer ignoring material for items

* Slight changes

* Introduce new interface for the expanded fallback consumer

* Add javadoc to ModelHelper

(cherry picked from commit 9f179aa14c7d4d1ab3799349316a1c83f29e8830)

(cherry picked from commit 2e5408b56300a62167f898c3b9f7006327dd4c40)
---
 .../renderer/v1/model/FabricBakedModel.java   |  4 +-
 .../api/renderer/v1/render/RenderContext.java | 58 ++++++++++-
 .../renderer/client/MixinBakedModel.java      |  5 +-
 .../simple/client/FrameBakedModel.java        | 96 +++++++++++++++----
 .../client/FrameModelResourceProvider.java    |  7 +-
 .../simple/client/RendererClientTest.java     | 10 ++
 .../renderer/render/AbstractMeshConsumer.java | 44 +--------
 .../renderer/render/AbstractQuadRenderer.java | 40 ++++++++
 .../renderer/render/BlockRenderContext.java   |  2 +-
 .../renderer/render/ItemRenderContext.java    | 27 +++---
 .../render/TerrainFallbackConsumer.java       | 38 +++-----
 .../renderer/render/TerrainRenderContext.java |  2 +-
 12 files changed, 220 insertions(+), 113 deletions(-)

diff --git a/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel.java b/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel.java
index b6b00a692..352df0182 100644
--- a/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel.java
+++ b/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel.java
@@ -36,6 +36,8 @@ import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
  * Can also be used to generate or customize outputs based on world state instead of
  * or in addition to block state when render chunks are rebuilt.
  *
+ * <p>Implementors should have a look at {@link ModelHelper} as it contains many useful functions.
+ *
  * <p>Note for {@link Renderer} implementors: Fabric causes BakedModel to extend this
  * interface with {@link #isVanillaAdapter()} == true and to produce standard vertex data.
  * This means any BakedModel instance can be safely cast to this interface without an instanceof check.
@@ -82,7 +84,7 @@ public interface FabricBakedModel {
 	 * parameter is normally initialized with the same seed prior to each face layer.
 	 * Model authors should note this method is called only once per block, and call the provided
 	 * Random supplier multiple times if re-seeding is necessary. For wrapped vanilla baked models,
-	 * it will probably be easier to use {@link RenderContext#fallbackConsumer} which handles
+	 * it will probably be easier to use {@link RenderContext#bakedModelConsumer()} which handles
 	 * re-seeding per face automatically.
 	 *
 	 * @param blockView Access to world state. Using {@link net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView#getBlockEntityRenderAttachment(BlockPos)} to
diff --git a/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/api/renderer/v1/render/RenderContext.java b/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/api/renderer/v1/render/RenderContext.java
index fa0de2bdf..946fb5f35 100644
--- a/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/api/renderer/v1/render/RenderContext.java
+++ b/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/api/renderer/v1/render/RenderContext.java
@@ -18,6 +18,9 @@ package net.fabricmc.fabric.api.renderer.v1.render;
 
 import java.util.function.Consumer;
 
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.block.BlockState;
 import net.minecraft.client.render.model.BakedModel;
 
 import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
@@ -39,11 +42,64 @@ public interface RenderContext {
 	Consumer<Mesh> meshConsumer();
 
 	/**
+	 * Fallback consumer that can process a vanilla {@link BakedModel}.
 	 * Fabric causes vanilla baked models to send themselves
 	 * via this interface. Can also be used by compound models that contain a mix
 	 * of vanilla baked models, packaged quads and/or dynamic elements.
 	 */
-	Consumer<BakedModel> fallbackConsumer();
+	default BakedModelConsumer bakedModelConsumer() {
+		// Default implementation is provided for compat with older renderer implementations,
+		// but they should always override this function.
+		Consumer<BakedModel> fallback = fallbackConsumer();
+		return new BakedModelConsumer() {
+			@Override
+			public void accept(BakedModel model) {
+				fallback.accept(model);
+			}
+
+			@Override
+			public void accept(BakedModel model, @Nullable BlockState state) {
+				fallback.accept(model);
+			}
+		};
+	}
+
+	interface BakedModelConsumer extends Consumer<BakedModel> {
+		/**
+		 * Render a baked model by processing its {@linkplain BakedModel#getQuads} using the rendered block state.
+		 *
+		 * <p>For block contexts, this will pass the block state being rendered to {@link BakedModel#getQuads}.
+		 * For item contexts, this will pass a {@code null} block state to {@link BakedModel#getQuads}.
+		 * {@link #accept(BakedModel, BlockState)} can be used instead to pass the block state explicitly.
+		 */
+		@Override
+		void accept(BakedModel model);
+
+		/**
+		 * Render a baked model by processing its {@linkplain BakedModel#getQuads} with an explicit block state.
+		 *
+		 * <p>This overload allows passing the block state (or {@code null} to query the item quads).
+		 * This is useful when a model is being wrapped, and expects a different
+		 * block state than the one of the block being rendered.
+		 *
+		 * <p>For item render contexts, you can use this function if you want to render the model with a specific block state.
+		 * Otherwise, use {@linkplain #accept(BakedModel)} the other overload} to render the usual item quads.
+		 */
+		void accept(BakedModel model, @Nullable BlockState state);
+	}
+
+	/**
+	 * Fabric causes vanilla baked models to send themselves
+	 * via this interface. Can also be used by compound models that contain a mix
+	 * of vanilla baked models, packaged quads and/or dynamic elements.
+	 *
+	 * @deprecated Prefer using the more flexible {@link #bakedModelConsumer}.
+	 */
+	@Deprecated
+	default Consumer<BakedModel> fallbackConsumer() {
+		// This default implementation relies on implementors overriding bakedModelConsumer().
+		return bakedModelConsumer();
+	}
 
 	/**
 	 * Returns a {@link QuadEmitter} instance that emits directly to the render buffer.
diff --git a/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/mixin/renderer/client/MixinBakedModel.java b/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/mixin/renderer/client/MixinBakedModel.java
index d76678150..ecc3c3ace 100644
--- a/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/mixin/renderer/client/MixinBakedModel.java
+++ b/fabric-renderer-api-v1/src/main/java/net/fabricmc/fabric/mixin/renderer/client/MixinBakedModel.java
@@ -42,11 +42,12 @@ public interface MixinBakedModel extends FabricBakedModel {
 
 	@Override
 	default void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
-		context.fallbackConsumer().accept((BakedModel) this);
+		context.bakedModelConsumer().accept((BakedModel) this, state);
 	}
 
 	@Override
 	default void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
-		context.fallbackConsumer().accept((BakedModel) this);
+		// Pass null state to enforce item quads in block render contexts
+		context.bakedModelConsumer().accept((BakedModel) this, null);
 	}
 }
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 1b994d15a..58c7a6cf3 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
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable;
 
 import net.minecraft.block.Block;
 import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.client.render.model.BakedQuad;
@@ -36,20 +37,29 @@ 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.RenderMaterial;
 import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
-import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
-import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
 import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
+import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
 import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
 import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
 
 final class FrameBakedModel implements BakedModel, FabricBakedModel {
 	private final Mesh frameMesh;
 	private final Sprite frameSprite;
+	private final RenderMaterial translucentMaterial;
+	private final RenderMaterial translucentEmissiveMaterial;
 
 	FrameBakedModel(Mesh frameMesh, Sprite frameSprite) {
 		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();
 	}
 
 	@Override
@@ -69,7 +79,7 @@ final class FrameBakedModel implements BakedModel, FabricBakedModel {
 
 	@Override
 	public boolean isSideLit() {
-		return false;
+		return true; // we want the block to be lit from the side when rendered as an item
 	}
 
 	@Override
@@ -84,7 +94,7 @@ final class FrameBakedModel implements BakedModel, FabricBakedModel {
 
 	@Override
 	public ModelTransformation getTransformation() {
-		return ModelTransformation.NONE;
+		return ModelHelper.MODEL_TRANSFORM_BLOCK;
 	}
 
 	@Override
@@ -112,27 +122,71 @@ final class FrameBakedModel implements BakedModel, FabricBakedModel {
 			return; // No inner block to render
 		}
 
-		Sprite sprite = MinecraftClient.getInstance().getBlockRenderManager().getModels().getModelManager().getBlockModels().getModelParticleSprite(data.getDefaultState());
-		QuadEmitter emitter = context.getEmitter();
+		BlockState innerState = data.getDefaultState();
 
-		// We can emit our quads outside of the mesh as the block being put in the frame is very much dynamic.
-		// Emit the quads for each face of the block inside the frame
-		for (Direction direction : Direction.values()) {
-			// Add a face, with an inset to give the appearance of the block being in a frame.
-			emitter.square(direction, 0.1F, 0.1F, 0.9F, 0.9F, 0.1F)
-					// Set the sprite of the fact, use whole texture via BAKE_LOCK_UV
-					.spriteBake(0, sprite, MutableQuadView.BAKE_LOCK_UV)
-					// Allow textures
-					// TODO: the magic values here are not documented at all and probably should be
-					.spriteColor(0, -1, -1, -1, -1)
-					// Emit the quad
-					.emit();
-		}
+		// Now, we emit a transparent scaled-down version of the inner model
+		// Try both emissive and non-emissive versions of the translucent material
+		RenderMaterial material = pos.getX() % 2 == 0 ? translucentMaterial : translucentEmissiveMaterial;
+
+		emitInnerQuads(context, material, () -> {
+			// Use emitBlockQuads to allow for Renderer API features
+			((FabricBakedModel) MinecraftClient.getInstance().getBlockRenderManager().getModel(innerState)).emitBlockQuads(blockView, innerState, pos, randomSupplier, context);
+		});
 	}
 
 	@Override
 	public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
-		// TODO: Implement an item test.
-		// For now we will just leave this as I have not added a block item yet
+		// Emit our frame mesh
+		context.meshConsumer().accept(this.frameMesh);
+
+		// Emit a scaled-down fence for testing, trying both materials again.
+		RenderMaterial material = stack.hasCustomName() ? translucentEmissiveMaterial : translucentMaterial;
+
+		BlockState innerState = Blocks.OAK_FENCE.getDefaultState();
+
+		emitInnerQuads(context, material, () -> {
+			// Need to use the fallback consumer directly:
+			// - we can't use emitBlockQuads because we don't have a blockView
+			// - we can't use emitItemQuads because multipart models don't have item quads
+			context.bakedModelConsumer().accept(MinecraftClient.getInstance().getBlockRenderManager().getModel(innerState), innerState);
+		});
+	}
+
+	/**
+	 * Emit a scaled-down version of the inner model.
+	 */
+	private void emitInnerQuads(RenderContext context, RenderMaterial material, Runnable innerModelEmitter) {
+		// Let's push a transform to scale the model down and make it transparent
+		context.pushTransform(quad -> {
+			// Scale model down
+			for (int vertex = 0; vertex < 4; ++vertex) {
+				float x = quad.x(vertex) * 0.8f + 0.1f;
+				float y = quad.y(vertex) * 0.8f + 0.1f;
+				float z = quad.z(vertex) * 0.8f + 0.1f;
+				quad.pos(vertex, x, y, z);
+			}
+
+			// Make the quad partially transparent
+			// Change material to translucent
+			quad.material(material);
+
+			// Change vertex colors to be partially transparent
+			for (int vertex = 0; vertex < 4; ++vertex) {
+				int color = quad.spriteColor(vertex, 0);
+				int alpha = (color >> 24) & 0xFF;
+				alpha = alpha * 3 / 4;
+				color = (color & 0xFFFFFF) | (alpha << 24);
+				quad.spriteColor(vertex, 0, color);
+			}
+
+			// Return true because we want the quad to be rendered
+			return true;
+		});
+
+		// Emit the inner block model
+		innerModelEmitter.run();
+
+		// Let's not forget to pop the transform!
+		context.popTransform();
 	}
 }
diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java
index d9a1844f3..34ff995ba 100644
--- a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java
+++ b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java
@@ -16,6 +16,9 @@
 
 package net.fabricmc.fabric.test.renderer.simple.client;
 
+import java.util.HashSet;
+import java.util.Set;
+
 import org.jetbrains.annotations.Nullable;
 
 import net.minecraft.client.render.model.UnbakedModel;
@@ -28,12 +31,12 @@ import net.fabricmc.fabric.api.client.model.ModelResourceProvider;
  * Provides the unbaked model for use with the frame block.
  */
 final class FrameModelResourceProvider implements ModelResourceProvider {
-	private static final Identifier FRAME_MODEL_ID = new Identifier("fabric-renderer-api-v1-testmod", "block/frame");
+	static final Set<Identifier> FRAME_MODELS = new HashSet<>();
 
 	@Nullable
 	@Override
 	public UnbakedModel loadModelResource(Identifier resourceId, ModelProviderContext context) {
-		if (resourceId.equals(FRAME_MODEL_ID)) {
+		if (FRAME_MODELS.contains(resourceId)) {
 			return new FrameUnbakedModel();
 		}
 
diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java
index e74daf9e4..75c785529 100644
--- a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java
+++ b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java
@@ -16,7 +16,10 @@
 
 package net.fabricmc.fabric.test.renderer.simple.client;
 
+import static net.fabricmc.fabric.test.renderer.simple.RendererTest.id;
+
 import net.minecraft.client.render.RenderLayer;
+import net.minecraft.util.registry.Registry;
 
 import net.fabricmc.api.ClientModInitializer;
 import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
@@ -31,7 +34,14 @@ public final class RendererClientTest implements ClientModInitializer {
 		ModelLoadingRegistry.INSTANCE.registerVariantProvider(manager -> new PillarModelVariantProvider());
 
 		for (FrameBlock frameBlock : RendererTest.FRAMES) {
+			// We don't specify a material for the frame mesh,
+			// so it will use the default material, i.e. the one from BlockRenderLayerMap.
 			BlockRenderLayerMap.INSTANCE.putBlock(frameBlock, RenderLayer.getCutoutMipped());
+
+			String itemPath = Registry.ITEM.getId(frameBlock.asItem()).getPath();
+			FrameModelResourceProvider.FRAME_MODELS.add(id("item/" + itemPath));
 		}
+
+		FrameModelResourceProvider.FRAME_MODELS.add(id("block/frame"));
 	}
 }
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java
index d0853abbf..bb35c4479 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java
@@ -26,7 +26,6 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
 import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
 import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
 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.aocalc.AoCalculator;
 import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat;
 import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MeshImpl;
@@ -55,7 +54,7 @@ public abstract class AbstractMeshConsumer extends AbstractQuadRenderer implemen
 		@Override
 		public Maker emit() {
 			computeGeometry();
-			renderQuad(this);
+			renderQuad(this, false);
 			clear();
 			return this;
 		}
@@ -74,7 +73,7 @@ public abstract class AbstractMeshConsumer extends AbstractQuadRenderer implemen
 			System.arraycopy(data, index, editorQuad.data(), 0, EncodingFormat.TOTAL_STRIDE);
 			editorQuad.load();
 			index += EncodingFormat.TOTAL_STRIDE;
-			renderQuad(editorQuad);
+			renderQuad(editorQuad, false);
 		}
 	}
 
@@ -82,43 +81,4 @@ public abstract class AbstractMeshConsumer extends AbstractQuadRenderer implemen
 		editorQuad.clear();
 		return editorQuad;
 	}
-
-	private void renderQuad(MutableQuadViewImpl quad) {
-		if (!transform.transform(quad)) {
-			return;
-		}
-
-		if (!blockInfo.shouldDrawFace(quad.cullFace())) {
-			return;
-		}
-
-		tessellateQuad(quad, 0);
-	}
-
-	/**
-	 * Determines color index and render layer, then routes to appropriate
-	 * tessellate routine based on material properties.
-	 */
-	private void tessellateQuad(MutableQuadViewImpl quad, int textureIndex) {
-		final RenderMaterialImpl.Value mat = quad.material();
-		final int colorIndex = mat.disableColorIndex(textureIndex) ? -1 : quad.colorIndex();
-		final RenderLayer renderLayer = blockInfo.effectiveRenderLayer(mat.blendMode(textureIndex));
-
-		if (blockInfo.defaultAo && !mat.disableAo(textureIndex)) {
-			// needs to happen before offsets are applied
-			aoCalc.compute(quad, false);
-
-			if (mat.emissive(textureIndex)) {
-				tessellateSmoothEmissive(quad, renderLayer, colorIndex);
-			} else {
-				tessellateSmooth(quad, renderLayer, colorIndex);
-			}
-		} else {
-			if (mat.emissive(textureIndex)) {
-				tessellateFlatEmissive(quad, renderLayer, colorIndex);
-			} else {
-				tessellateFlat(quad, renderLayer, colorIndex);
-			}
-		}
-	}
 }
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 0a7e9c27b..2fb815bd1 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
@@ -33,6 +33,7 @@ import net.minecraft.util.math.Matrix4f;
 import net.minecraft.util.math.Vec3f;
 
 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;
@@ -62,6 +63,45 @@ public abstract class AbstractQuadRenderer {
 		this.transform = transform;
 	}
 
+	protected void renderQuad(MutableQuadViewImpl quad, boolean isVanilla) {
+		if (!transform.transform(quad)) {
+			return;
+		}
+
+		if (!blockInfo.shouldDrawFace(quad.cullFace())) {
+			return;
+		}
+
+		tessellateQuad(quad, 0, isVanilla);
+	}
+
+	/**
+	 * Determines color index and render layer, then routes to appropriate
+	 * tessellate routine based on material properties.
+	 */
+	private void tessellateQuad(MutableQuadViewImpl quad, int textureIndex, boolean isVanilla) {
+		final RenderMaterialImpl.Value mat = quad.material();
+		final int colorIndex = mat.disableColorIndex(textureIndex) ? -1 : quad.colorIndex();
+		final RenderLayer renderLayer = blockInfo.effectiveRenderLayer(mat.blendMode(textureIndex));
+
+		if (blockInfo.defaultAo && !mat.disableAo(textureIndex)) {
+			// needs to happen before offsets are applied
+			aoCalc.compute(quad, isVanilla);
+
+			if (mat.emissive(textureIndex)) {
+				tessellateSmoothEmissive(quad, renderLayer, colorIndex);
+			} else {
+				tessellateSmooth(quad, renderLayer, colorIndex);
+			}
+		} else {
+			if (mat.emissive(textureIndex)) {
+				tessellateFlatEmissive(quad, renderLayer, colorIndex);
+			} else {
+				tessellateFlat(quad, renderLayer, colorIndex);
+			}
+		}
+	}
+
 	/** handles block color and red-blue swizzle, common to all renders. */
 	private void colorizeQuad(MutableQuadViewImpl q, int blockColorIndex) {
 		if (blockColorIndex == -1) {
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 40d013672..7d6b8831a 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
@@ -141,7 +141,7 @@ public class BlockRenderContext extends AbstractRenderContext {
 	}
 
 	@Override
-	public Consumer<BakedModel> fallbackConsumer() {
+	public BakedModelConsumer bakedModelConsumer() {
 		return fallbackConsumer;
 	}
 
diff --git a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java
index f454a51f6..7d982988a 100644
--- a/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java
+++ b/fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java
@@ -21,6 +21,8 @@ import java.util.Random;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
+import org.jetbrains.annotations.Nullable;
+
 import net.minecraft.block.BlockState;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.color.item.ItemColors;
@@ -265,21 +267,27 @@ public class ItemRenderContext extends AbstractRenderContext {
 		}
 	}
 
-	private class FallbackConsumer implements Consumer<BakedModel> {
+	private class FallbackConsumer implements BakedModelConsumer {
 		@Override
 		public void accept(BakedModel model) {
+			accept(model, null);
+		}
+
+		@Override
+		public void accept(BakedModel model, @Nullable BlockState state) {
 			if (hasTransform()) {
 				// if there's a transform in effect, convert to mesh-based quads so that we can apply it
 				for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) {
 					final Direction cullFace = ModelHelper.faceFromIndex(i);
 					random.setSeed(ITEM_RANDOM_SEED);
-					final List<BakedQuad> quads = model.getQuads(null, cullFace, random);
+					final List<BakedQuad> quads = model.getQuads(state, cullFace, random);
 					final int count = quads.size();
 
 					if (count != 0) {
 						for (int j = 0; j < count; j++) {
 							final BakedQuad q = quads.get(j);
-							renderQuadWithTransform(q, cullFace);
+							editorQuad.fromVanilla(q, IndigoRenderer.MATERIAL_STANDARD, cullFace);
+							renderMeshQuad(editorQuad);
 						}
 					}
 				}
@@ -287,17 +295,6 @@ public class ItemRenderContext extends AbstractRenderContext {
 				vanillaHandler.accept(model, itemStack, lightmap, overlay, matrixStack, modelVertexConsumer);
 			}
 		}
-
-		private void renderQuadWithTransform(BakedQuad quad, Direction cullFace) {
-			final Maker editorQuad = ItemRenderContext.this.editorQuad;
-			editorQuad.fromVanilla(quad, IndigoRenderer.MATERIAL_STANDARD, cullFace);
-
-			if (!transform(editorQuad)) {
-				return;
-			}
-
-			renderQuad(editorQuad, BlendMode.DEFAULT, editorQuad.colorIndex());
-		}
 	}
 
 	@Override
@@ -306,7 +303,7 @@ public class ItemRenderContext extends AbstractRenderContext {
 	}
 
 	@Override
-	public Consumer<BakedModel> fallbackConsumer() {
+	public BakedModelConsumer bakedModelConsumer() {
 		return fallbackConsumer;
 	}
 
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 25162bbd3..4dd8b7d9f 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
@@ -22,6 +22,8 @@ import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
+import org.jetbrains.annotations.Nullable;
+
 import net.minecraft.block.BlockState;
 import net.minecraft.client.render.RenderLayer;
 import net.minecraft.client.render.VertexConsumer;
@@ -31,6 +33,7 @@ import net.minecraft.util.math.Direction;
 
 import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
 import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
+import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
 import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
 import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer;
 import net.fabricmc.fabric.impl.client.indigo.renderer.RenderMaterialImpl.Value;
@@ -57,7 +60,7 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl;
  *  vertex data is sent to the byte buffer.  Generally POJO array access will be faster than
  *  manipulating the data via NIO.
  */
-public abstract class TerrainFallbackConsumer extends AbstractQuadRenderer implements Consumer<BakedModel> {
+public abstract class TerrainFallbackConsumer extends AbstractQuadRenderer implements RenderContext.BakedModelConsumer {
 	private static final Value MATERIAL_FLAT = (Value) IndigoRenderer.INSTANCE.materialFinder().disableAo(0, true).find();
 	private static final Value MATERIAL_SHADED = (Value) IndigoRenderer.INSTANCE.materialFinder().find();
 
@@ -79,10 +82,14 @@ public abstract class TerrainFallbackConsumer extends AbstractQuadRenderer imple
 	};
 
 	@Override
-	public void accept(BakedModel model) {
+	public void accept(BakedModel bakedModel) {
+		accept(bakedModel, blockInfo.blockState);
+	}
+
+	@Override
+	public void accept(BakedModel model, @Nullable BlockState blockState) {
 		final Supplier<Random> random = blockInfo.randomSupplier;
 		final Value defaultMaterial = blockInfo.defaultAo && model.useAmbientOcclusion() ? MATERIAL_SHADED : MATERIAL_FLAT;
-		final BlockState blockState = blockInfo.blockState;
 
 		for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) {
 			final Direction cullFace = ModelHelper.faceFromIndex(i);
@@ -102,29 +109,6 @@ public abstract class TerrainFallbackConsumer extends AbstractQuadRenderer imple
 		final MutableQuadViewImpl editorQuad = this.editorQuad;
 		editorQuad.fromVanilla(quad, defaultMaterial, cullFace);
 
-		if (!transform.transform(editorQuad)) {
-			return;
-		}
-
-		cullFace = editorQuad.cullFace();
-
-		if (cullFace != null && !blockInfo.shouldDrawFace(cullFace)) {
-			return;
-		}
-
-		if (!editorQuad.material().disableAo(0)) {
-			// needs to happen before offsets are applied
-			aoCalc.compute(editorQuad, true);
-			tessellateSmooth(editorQuad, blockInfo.defaultLayer, editorQuad.colorIndex());
-		} else {
-			// Recomputing whether the quad has a light face is only needed if it doesn't also have a cull face,
-			// as in those cases, the cull face will always be used to offset the light sampling position
-			if (cullFace == null) {
-				// Can't rely on lazy computation in tessellateFlat() because needs to happen before offsets are applied
-				editorQuad.geometryFlags();
-			}
-
-			tessellateFlat(editorQuad, blockInfo.defaultLayer, editorQuad.colorIndex());
-		}
+		renderQuad(editorQuad, true);
 	}
 }
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 25dc07a23..0df538529 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
@@ -120,7 +120,7 @@ public class TerrainRenderContext extends AbstractRenderContext {
 	}
 
 	@Override
-	public Consumer<BakedModel> fallbackConsumer() {
+	public BakedModelConsumer bakedModelConsumer() {
 		return fallbackConsumer;
 	}