diff --git a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/FluidRenderHandler.java b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/FluidRenderHandler.java
index 5997b2a19..14e6bebc4 100644
--- a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/FluidRenderHandler.java
+++ b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/FluidRenderHandler.java
@@ -20,13 +20,14 @@ import org.jetbrains.annotations.Nullable;
 
 import net.minecraft.block.BlockState;
 import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.block.FluidRenderer;
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.client.texture.SpriteAtlasTexture;
 import net.minecraft.fluid.FluidState;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.BlockRenderView;
 
-import net.fabricmc.fabric.impl.client.rendering.fluid.FluidRenderHandlerRegistryImpl;
+import net.fabricmc.fabric.impl.client.rendering.fluid.FluidRenderingImpl;
 
 /**
  * Interface for handling the rendering of a FluidState.
@@ -67,14 +68,13 @@ public interface FluidRenderHandler {
 	}
 
 	/**
-	 * Tessellate your fluid. This method will be invoked before the default
-	 * fluid renderer. By default, it will call the default fluid renderer. Call
-	 * {@code FluidRenderHandler.super.renderFluid} if you want to render over
-	 * the default fluid renderer.
-	 *
-	 * <p>Note that this method must *only* return {@code true} if at least one
-	 * face is tessellated. If no faces are tessellated this method must return
-	 * {@code false}.
+	 * Tessellate your fluid. By default, this method will call the default
+	 * fluid renderer. Call {@code FluidRenderHandler.super.renderFluid} if
+	 * you want to render over the default fluid renderer. This is the
+	 * intended way to render default geometry; calling
+	 * {@link FluidRenderer#render} is not supported. When rendering default
+	 * geometry, the current handler will be used instead of looking up
+	 * a new one for the passed fluid state.
 	 *
 	 * @param pos The position in the world, of the fluid to render.
 	 * @param world The world the fluid is in
@@ -83,7 +83,7 @@ public interface FluidRenderHandler {
 	 * @param fluidState The fluid state being rendered.
 	 */
 	default void renderFluid(BlockPos pos, BlockRenderView world, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
-		((FluidRenderHandlerRegistryImpl) FluidRenderHandlerRegistry.INSTANCE).renderFluid(pos, world, vertexConsumer, blockState, fluidState);
+		FluidRenderingImpl.renderDefault(this, world, pos, vertexConsumer, blockState, fluidState);
 	}
 
 	/**
diff --git a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/FluidRendering.java b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/FluidRendering.java
new file mode 100644
index 000000000..eea7b3280
--- /dev/null
+++ b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/FluidRendering.java
@@ -0,0 +1,72 @@
+/*
+ * 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.api.client.render.fluid.v1;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.block.FluidRenderer;
+import net.minecraft.fluid.FluidState;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.BlockRenderView;
+
+import net.fabricmc.fabric.impl.client.rendering.fluid.FluidRenderingImpl;
+
+/**
+ * A class containing some utilities for rendering fluids.
+ */
+public final class FluidRendering {
+	private FluidRendering() {
+	}
+
+	/**
+	 * Renders a fluid using the given handler, default renderer, and context. Internally, this just invokes
+	 * {@link FluidRenderHandler#renderFluid}, but the passed default renderer is invoked instead of the vanilla
+	 * renderer whenever the handler requests default geometry to be rendered.
+	 *
+	 * @param handler the render handler to invoke {@link FluidRenderHandler#renderFluid} on
+	 * @param world the world
+	 * @param pos the pos
+	 * @param vertexConsumer the vertex consumer
+	 * @param blockState the block state
+	 * @param fluidState the fluid state
+	 * @param defaultRenderer the renderer to use whenever the handler requests default geometry
+	 */
+	public static void render(FluidRenderHandler handler, BlockRenderView world, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState, DefaultRenderer defaultRenderer) {
+		FluidRenderingImpl.render(handler, world, pos, vertexConsumer, blockState, fluidState, defaultRenderer);
+	}
+
+	public interface DefaultRenderer {
+		/**
+		 * Render the default geometry when it is requested by {@link FluidRenderHandler#renderFluid}. The default
+		 * implementation invokes the vanilla renderer. Calling {@link FluidRenderer#render} directly is not supported
+		 * but using {@code DefaultRenderer.super.render} is supported. Note that the parameter values passed to this
+		 * call are provided by the render handler, meaning they are not necessarily the same as those provided to the
+		 * initial rendering call. As per the documentation of {@link FluidRenderHandler#renderFluid}, a new handler
+		 * should not be retrieved and only the passed one should be used.
+		 *
+		 * @param handler the handler that {@link FluidRenderHandler#renderFluid} was invoked on
+		 * @param world the world
+		 * @param pos the pos
+		 * @param vertexConsumer the vertex consumer
+		 * @param blockState the block state
+		 * @param fluidState the fluid state
+		 */
+		default void render(FluidRenderHandler handler, BlockRenderView world, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
+			FluidRenderingImpl.renderVanillaDefault(handler, world, pos, vertexConsumer, blockState, fluidState);
+		}
+	}
+}
diff --git a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/SimpleFluidRenderHandler.java b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/SimpleFluidRenderHandler.java
index 47d55e085..0f28042bc 100644
--- a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/SimpleFluidRenderHandler.java
+++ b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/api/client/render/fluid/v1/SimpleFluidRenderHandler.java
@@ -85,7 +85,7 @@ public class SimpleFluidRenderHandler implements FluidRenderHandler {
 	 */
 	public SimpleFluidRenderHandler(Identifier stillTexture, Identifier flowingTexture, @Nullable Identifier overlayTexture, int tint) {
 		this.stillTexture = Objects.requireNonNull(stillTexture, "stillTexture");
-		this.flowingTexture = Objects.requireNonNull(flowingTexture, "flowingTexture");;
+		this.flowingTexture = Objects.requireNonNull(flowingTexture, "flowingTexture");
 		this.overlayTexture = overlayTexture;
 		this.sprites = new Sprite[overlayTexture == null ? 2 : 3];
 		this.tint = tint;
diff --git a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRendererHookContainer.java b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderHandlerInfo.java
similarity index 63%
rename from fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRendererHookContainer.java
rename to fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderHandlerInfo.java
index 759dc7e3b..10e50df98 100644
--- a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRendererHookContainer.java
+++ b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderHandlerInfo.java
@@ -16,7 +16,8 @@
 
 package net.fabricmc.fabric.impl.client.rendering.fluid;
 
-import net.minecraft.block.BlockState;
+import org.jetbrains.annotations.Nullable;
+
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.fluid.FluidState;
 import net.minecraft.util.math.BlockPos;
@@ -24,41 +25,32 @@ import net.minecraft.world.BlockRenderView;
 
 import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
 
-public class FluidRendererHookContainer {
-	public BlockRenderView view;
-	public BlockPos pos;
-	public BlockState blockState;
-	public FluidState fluidState;
-	public FluidRenderHandler handler;
+public class FluidRenderHandlerInfo {
 	public final Sprite[] sprites = new Sprite[2];
-	public Sprite overlay;
+	@Nullable
+	public FluidRenderHandler handler;
 	public boolean hasOverlay;
+	public Sprite overlaySprite;
 
-	public void getSprites(BlockRenderView world, BlockPos pos, FluidState fluidState) {
-		if (handler != null) {
-			Sprite[] sprites = handler.getFluidSprites(world, pos, fluidState);
+	public void setup(FluidRenderHandler handler, BlockRenderView world, BlockPos pos, FluidState fluidState) {
+		this.handler = handler;
 
-			this.sprites[0] = sprites[0];
-			this.sprites[1] = sprites[1];
+		Sprite[] sprites = handler.getFluidSprites(world, pos, fluidState);
 
-			if (sprites.length > 2) {
-				hasOverlay = true;
-				overlay = sprites[2];
-			}
-		} else {
-			hasOverlay = false;
+		this.sprites[0] = sprites[0];
+		this.sprites[1] = sprites[1];
+
+		if (sprites.length > 2) {
+			hasOverlay = true;
+			overlaySprite = sprites[2];
 		}
 	}
 
 	public void clear() {
-		view = null;
-		pos = null;
-		blockState = null;
-		fluidState = null;
-		handler = null;
 		sprites[0] = null;
 		sprites[1] = null;
-		overlay = null;
+		handler = null;
 		hasOverlay = false;
+		overlaySprite = null;
 	}
 }
diff --git a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderHandlerRegistryImpl.java b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderHandlerRegistryImpl.java
index 86737d1b4..bb54b63e7 100644
--- a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderHandlerRegistryImpl.java
+++ b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderHandlerRegistryImpl.java
@@ -18,16 +18,16 @@ package net.fabricmc.fabric.impl.client.rendering.fluid;
 
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import org.jetbrains.annotations.Nullable;
 
 import net.minecraft.block.Block;
-import net.minecraft.block.BlockState;
 import net.minecraft.block.LeavesBlock;
-import net.minecraft.block.TransparentBlock;
+import net.minecraft.block.TranslucentBlock;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.color.world.BiomeColors;
-import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.block.FluidRenderer;
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.client.texture.SpriteAtlasTexture;
@@ -43,15 +43,16 @@ import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
 import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
 
 public class FluidRenderHandlerRegistryImpl implements FluidRenderHandlerRegistry {
-	/**
-	 * The water color of {@link BiomeKeys#OCEAN}.
-	 */
-	private static final int DEFAULT_WATER_COLOR = 0x3f76e4;
 	private final Map<Fluid, FluidRenderHandler> handlers = new IdentityHashMap<>();
 	private final Map<Fluid, FluidRenderHandler> modHandlers = new IdentityHashMap<>();
-	private final Map<Block, Boolean> overlayBlocks = new IdentityHashMap<>();
+	private final ConcurrentMap<Block, Boolean> overlayBlocks = new ConcurrentHashMap<>();
 
-	private FluidRenderer fluidRenderer;
+	{
+		handlers.put(Fluids.WATER, WaterRenderHandler.INSTANCE);
+		handlers.put(Fluids.FLOWING_WATER, WaterRenderHandler.INSTANCE);
+		handlers.put(Fluids.LAVA, LavaRenderHandler.INSTANCE);
+		handlers.put(Fluids.FLOWING_LAVA, LavaRenderHandler.INSTANCE);
+	}
 
 	public FluidRenderHandlerRegistryImpl() {
 	}
@@ -81,42 +82,14 @@ public class FluidRenderHandlerRegistryImpl implements FluidRenderHandlerRegistr
 
 	@Override
 	public boolean isBlockTransparent(Block block) {
-		return overlayBlocks.computeIfAbsent(block, k -> k instanceof TransparentBlock || k instanceof LeavesBlock);
+		return overlayBlocks.computeIfAbsent(block, k -> k instanceof TranslucentBlock || k instanceof LeavesBlock);
 	}
 
 	public void onFluidRendererReload(FluidRenderer renderer, Sprite[] waterSprites, Sprite[] lavaSprites, Sprite waterOverlay) {
-		fluidRenderer = renderer;
+		FluidRenderingImpl.setVanillaRenderer(renderer);
 
-		Sprite[] waterSpritesFull = {waterSprites[0], waterSprites[1], waterOverlay};
-		FluidRenderHandler waterHandler = new FluidRenderHandler() {
-			@Override
-			public Sprite[] getFluidSprites(BlockRenderView view, BlockPos pos, FluidState state) {
-				return waterSpritesFull;
-			}
-
-			@Override
-			public int getFluidColor(BlockRenderView view, BlockPos pos, FluidState state) {
-				if (view != null && pos != null) {
-					return BiomeColors.getWaterColor(view, pos);
-				} else {
-					return DEFAULT_WATER_COLOR;
-				}
-			}
-		};
-
-		//noinspection Convert2Lambda
-		FluidRenderHandler lavaHandler = new FluidRenderHandler() {
-			@Override
-			public Sprite[] getFluidSprites(BlockRenderView view, BlockPos pos, FluidState state) {
-				return lavaSprites;
-			}
-		};
-
-		handlers.put(Fluids.WATER, waterHandler);
-		handlers.put(Fluids.FLOWING_WATER, waterHandler);
-		handlers.put(Fluids.LAVA, lavaHandler);
-		handlers.put(Fluids.FLOWING_LAVA, lavaHandler);
-		handlers.putAll(modHandlers);
+		WaterRenderHandler.INSTANCE.updateSprites(waterSprites, waterOverlay);
+		LavaRenderHandler.INSTANCE.updateSprites(lavaSprites);
 
 		SpriteAtlasTexture texture = MinecraftClient.getInstance()
 				.getBakedModelManager()
@@ -127,7 +100,49 @@ public class FluidRenderHandlerRegistryImpl implements FluidRenderHandlerRegistr
 		}
 	}
 
-	public void renderFluid(BlockPos pos, BlockRenderView world, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
-		fluidRenderer.render(world, pos, vertexConsumer, blockState, fluidState);
+	private static class WaterRenderHandler implements FluidRenderHandler {
+		public static final WaterRenderHandler INSTANCE = new WaterRenderHandler();
+
+		/**
+		 * The water color of {@link BiomeKeys#OCEAN}.
+		 */
+		private static final int DEFAULT_WATER_COLOR = 0x3f76e4;
+
+		private final Sprite[] sprites = new Sprite[3];
+
+		@Override
+		public Sprite[] getFluidSprites(@Nullable BlockRenderView view, @Nullable BlockPos pos, FluidState state) {
+			return sprites;
+		}
+
+		@Override
+		public int getFluidColor(@Nullable BlockRenderView view, @Nullable BlockPos pos, FluidState state) {
+			if (view != null && pos != null) {
+				return BiomeColors.getWaterColor(view, pos);
+			} else {
+				return DEFAULT_WATER_COLOR;
+			}
+		}
+
+		public void updateSprites(Sprite[] waterSprites, Sprite waterOverlay) {
+			sprites[0] = waterSprites[0];
+			sprites[1] = waterSprites[1];
+			sprites[2] = waterOverlay;
+		}
+	}
+
+	private static class LavaRenderHandler implements FluidRenderHandler {
+		public static final LavaRenderHandler INSTANCE = new LavaRenderHandler();
+
+		private Sprite[] sprites;
+
+		@Override
+		public Sprite[] getFluidSprites(@Nullable BlockRenderView view, @Nullable BlockPos pos, FluidState state) {
+			return sprites;
+		}
+
+		public void updateSprites(Sprite[] lavaSprites) {
+			sprites = lavaSprites;
+		}
 	}
 }
diff --git a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderingImpl.java b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderingImpl.java
new file mode 100644
index 000000000..9c7faaa2a
--- /dev/null
+++ b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/fluid/FluidRenderingImpl.java
@@ -0,0 +1,76 @@
+/*
+ * 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.rendering.fluid;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.block.FluidRenderer;
+import net.minecraft.fluid.FluidState;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.BlockRenderView;
+
+import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
+import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRendering;
+
+public class FluidRenderingImpl {
+	private static final ThreadLocal<FluidRendering.DefaultRenderer> CURRENT_DEFAULT_RENDERER = new ThreadLocal<>();
+	private static final ThreadLocal<FluidRenderHandlerInfo> CURRENT_INFO = ThreadLocal.withInitial(FluidRenderHandlerInfo::new);
+	private static FluidRenderer vanillaRenderer;
+
+	// Only invoked manually from FluidRendering#render
+	public static void render(FluidRenderHandler handler, BlockRenderView world, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState, FluidRendering.DefaultRenderer defaultRenderer) {
+		CURRENT_DEFAULT_RENDERER.set(defaultRenderer);
+
+		try {
+			handler.renderFluid(pos, world, vertexConsumer, blockState, fluidState);
+		} finally {
+			CURRENT_DEFAULT_RENDERER.remove();
+		}
+	}
+
+	// Only invoked when FluidRenderHandler#renderFluid calls super
+	public static void renderDefault(FluidRenderHandler handler, BlockRenderView world, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
+		FluidRendering.DefaultRenderer renderer = CURRENT_DEFAULT_RENDERER.get();
+
+		if (renderer != null) {
+			renderer.render(handler, world, pos, vertexConsumer, blockState, fluidState);
+		} else {
+			renderVanillaDefault(handler, world, pos, vertexConsumer, blockState, fluidState);
+		}
+	}
+
+	// Invoked when FluidRenderHandler#renderFluid is called directly without using FluidRendering#render (such as
+	// from vanilla FluidRenderer#render via mixin) or from the default implementation of DefaultRenderer#render
+	public static void renderVanillaDefault(FluidRenderHandler handler, BlockRenderView world, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
+		FluidRenderHandlerInfo info = CURRENT_INFO.get();
+		info.setup(handler, world, pos, fluidState);
+
+		try {
+			vanillaRenderer.render(world, pos, vertexConsumer, blockState, fluidState);
+		} finally {
+			info.clear();
+		}
+	}
+
+	public static void setVanillaRenderer(FluidRenderer vanillaRenderer) {
+		FluidRenderingImpl.vanillaRenderer = vanillaRenderer;
+	}
+
+	public static FluidRenderHandlerInfo getCurrentInfo() {
+		return CURRENT_INFO.get();
+	}
+}
diff --git a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/fluid/FluidRendererMixin.java b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/fluid/FluidRendererMixin.java
index 81735984e..89fc45490 100644
--- a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/fluid/FluidRendererMixin.java
+++ b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/fluid/FluidRendererMixin.java
@@ -20,7 +20,6 @@ import org.objectweb.asm.Opcodes;
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.ModifyVariable;
@@ -38,8 +37,9 @@ import net.minecraft.world.BlockRenderView;
 
 import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
 import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
+import net.fabricmc.fabric.impl.client.rendering.fluid.FluidRenderHandlerInfo;
 import net.fabricmc.fabric.impl.client.rendering.fluid.FluidRenderHandlerRegistryImpl;
-import net.fabricmc.fabric.impl.client.rendering.fluid.FluidRendererHookContainer;
+import net.fabricmc.fabric.impl.client.rendering.fluid.FluidRenderingImpl;
 
 @Mixin(FluidRenderer.class)
 public class FluidRendererMixin {
@@ -52,8 +52,6 @@ public class FluidRendererMixin {
 	@Shadow
 	private Sprite waterOverlaySprite;
 
-	private final ThreadLocal<FluidRendererHookContainer> fabric_renderHandler = ThreadLocal.withInitial(FluidRendererHookContainer::new);
-	private final ThreadLocal<Boolean> fabric_customRendering = ThreadLocal.withInitial(() -> false);
 	private final ThreadLocal<Block> fabric_neighborBlock = new ThreadLocal<>();
 
 	@Inject(at = @At("RETURN"), method = "onResourceReload")
@@ -63,78 +61,50 @@ public class FluidRendererMixin {
 	}
 
 	@Inject(at = @At("HEAD"), method = "render", cancellable = true)
-	public void tesselate(BlockRenderView view, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState, CallbackInfo info) {
-		if (!fabric_customRendering.get()) {
-			// Prevent recursively looking up custom fluid renderers when default behavior is being invoked
-			try {
-				fabric_customRendering.set(true);
-				tessellateViaHandler(view, pos, vertexConsumer, blockState, fluidState, info);
-			} finally {
-				fabric_customRendering.set(false);
+	public void onHeadRender(BlockRenderView view, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState, CallbackInfo ci) {
+		FluidRenderHandlerInfo info = FluidRenderingImpl.getCurrentInfo();
+
+		if (info.handler == null) {
+			FluidRenderHandler handler = FluidRenderHandlerRegistry.INSTANCE.get(fluidState.getFluid());
+
+			if (handler != null) {
+				handler.renderFluid(pos, view, vertexConsumer, blockState, fluidState);
+				ci.cancel();
 			}
 		}
-
-		if (info.isCancelled()) {
-			return;
-		}
-
-		FluidRendererHookContainer ctr = fabric_renderHandler.get();
-		ctr.getSprites(view, pos, fluidState);
-	}
-
-	@Unique
-	private void tessellateViaHandler(BlockRenderView view, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState, CallbackInfo info) {
-		FluidRendererHookContainer ctr = fabric_renderHandler.get();
-		FluidRenderHandler handler = FluidRenderHandlerRegistry.INSTANCE.get(fluidState.getFluid());
-
-		ctr.view = view;
-		ctr.pos = pos;
-		ctr.blockState = blockState;
-		ctr.fluidState = fluidState;
-		ctr.handler = handler;
-
-		if (handler != null) {
-			handler.renderFluid(pos, view, vertexConsumer, blockState, fluidState);
-			info.cancel();
-		}
-	}
-
-	@Inject(at = @At("RETURN"), method = "render")
-	public void tesselateReturn(BlockRenderView world, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState, CallbackInfo ci) {
-		fabric_renderHandler.get().clear();
 	}
 
 	@ModifyVariable(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/FluidRenderer;isSameFluid(Lnet/minecraft/fluid/FluidState;Lnet/minecraft/fluid/FluidState;)Z"), method = "render", ordinal = 0)
 	public boolean modLavaCheck(boolean chk) {
 		// First boolean local is set by vanilla according to 'matches lava'
 		// but uses the negation consistent with 'matches water'
-		// for determining if special water sprite should be used behind glass.
+		// for determining if overlay water sprite should be used behind glass.
 
 		// Has other uses but those are overridden by this mixin and have
-		// already happened by the time this hook is called
+		// already happened by the time this hook is called.
 
-		// If this fluid has an overlay texture, set this boolean too false
-		final FluidRendererHookContainer ctr = fabric_renderHandler.get();
-		return !ctr.hasOverlay;
+		// If this fluid has an overlay texture, set this boolean to false.
+		final FluidRenderHandlerInfo info = FluidRenderingImpl.getCurrentInfo();
+		return info.handler != null ? !info.hasOverlay : chk;
 	}
 
 	@ModifyVariable(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/FluidRenderer;isSameFluid(Lnet/minecraft/fluid/FluidState;Lnet/minecraft/fluid/FluidState;)Z"), method = "render", ordinal = 0)
 	public Sprite[] modSpriteArray(Sprite[] chk) {
-		FluidRendererHookContainer ctr = fabric_renderHandler.get();
-		return ctr.handler != null ? ctr.sprites : chk;
+		FluidRenderHandlerInfo info = FluidRenderingImpl.getCurrentInfo();
+		return info.handler != null ? info.sprites : chk;
+	}
+
+	@ModifyVariable(at = @At(value = "CONSTANT", args = "intValue=16", ordinal = 0, shift = At.Shift.BEFORE), method = "render", ordinal = 0)
+	public int modTintColor(int chk, BlockRenderView world, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
+		FluidRenderHandlerInfo info = FluidRenderingImpl.getCurrentInfo();
+		return info.handler != null ? info.handler.getFluidColor(world, pos, fluidState) : chk;
 	}
 
 	// Redirect redirects all 'waterOverlaySprite' gets in 'render' to this method, this is correct
 	@Redirect(at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraft/client/render/block/FluidRenderer;waterOverlaySprite:Lnet/minecraft/client/texture/Sprite;"), method = "render")
 	public Sprite modWaterOverlaySprite(FluidRenderer self) {
-		FluidRendererHookContainer ctr = fabric_renderHandler.get();
-		return ctr.handler != null && ctr.hasOverlay ? ctr.overlay : waterOverlaySprite;
-	}
-
-	@ModifyVariable(at = @At(value = "CONSTANT", args = "intValue=16", ordinal = 0, shift = At.Shift.BEFORE), method = "render", ordinal = 0)
-	public int modTintColor(int chk) {
-		FluidRendererHookContainer ctr = fabric_renderHandler.get();
-		return ctr.handler != null ? ctr.handler.getFluidColor(ctr.view, ctr.pos, ctr.fluidState) : chk;
+		FluidRenderHandlerInfo info = FluidRenderingImpl.getCurrentInfo();
+		return info.handler != null && info.hasOverlay ? info.overlaySprite : waterOverlaySprite;
 	}
 
 	@Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;getBlock()Lnet/minecraft/block/Block;"), method = "render")
@@ -152,8 +122,8 @@ public class FluidRendererMixin {
 		Block block = fabric_neighborBlock.get();
 
 		if (FluidRenderHandlerRegistry.INSTANCE.isBlockTransparent(block)) {
-			FluidRendererHookContainer ctr = fabric_renderHandler.get();
-			return ctr.handler != null && ctr.hasOverlay ? ctr.overlay : waterOverlaySprite;
+			FluidRenderHandlerInfo info = FluidRenderingImpl.getCurrentInfo();
+			return info.handler != null && info.hasOverlay ? info.overlaySprite : waterOverlaySprite;
 		}
 
 		return chk;
diff --git a/fabric-rendering-fluids-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/rendering/fluid/CustomizedFluidRenderer.java b/fabric-rendering-fluids-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/rendering/fluid/CustomizedFluidRenderer.java
index fea521b6a..0a57d5cb0 100644
--- a/fabric-rendering-fluids-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/rendering/fluid/CustomizedFluidRenderer.java
+++ b/fabric-rendering-fluids-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/rendering/fluid/CustomizedFluidRenderer.java
@@ -36,8 +36,8 @@ public class CustomizedFluidRenderer extends SimpleFluidRenderHandler {
 		int light = getLight(world, pos);
 		float u1 = sprites[2].getFrameU(0);
 		float v1 = sprites[2].getFrameV(0);
-		float u2 = sprites[2].getFrameU(16);
-		float v2 = sprites[2].getFrameV(16 * fluidState.getHeight(world, pos));
+		float u2 = sprites[2].getFrameU(1);
+		float v2 = sprites[2].getFrameV(fluidState.getHeight(world, pos));
 
 		float x1 = (pos.getX() & 15) + 0.1f;
 		float y1 = pos.getY() & 15;