From c26373137e06bd0bc0298622eb2712381b86c3d1 Mon Sep 17 00:00:00 2001
From: grondag <grondag@users.noreply.github.com>
Date: Wed, 30 Dec 2020 08:44:23 -0800
Subject: [PATCH] Add WorldRenderEvents (#1182)

* Add WorldRenderEvents

* Fix typos

* Incorporate PR feedback

* Simplify context and block outline events

* Update implementation

* Ensure the BLOCK_OUTLINE mixin does nothing if BEFORE_BLOCK_OUTLINE mixin is disabled

* Document event order in class header

* Update fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java

Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com>

* Add environment tag to nested type

* More envionment tags

Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com>
---
 fabric-rendering-v1/build.gradle              |   2 +-
 .../rendering/v1/WorldRenderContext.java      | 138 ++++++++
 .../rendering/v1/WorldRenderEvents.java       | 327 ++++++++++++++++++
 .../rendering/WorldRenderContextImpl.java     | 221 ++++++++++++
 .../client/rendering/MixinWorldRenderer.java  | 133 +++++++
 5 files changed, 820 insertions(+), 1 deletion(-)
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderContext.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/WorldRenderContextImpl.java

diff --git a/fabric-rendering-v1/build.gradle b/fabric-rendering-v1/build.gradle
index 9c7afb287..0f6f68051 100644
--- a/fabric-rendering-v1/build.gradle
+++ b/fabric-rendering-v1/build.gradle
@@ -1,5 +1,5 @@
 archivesBaseName = "fabric-rendering-v1"
-version = getSubprojectVersion(project, "1.4.0")
+version = getSubprojectVersion(project, "1.5.0")
 
 moduleDependencies(project, [
 		'fabric-api-base'
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderContext.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderContext.java
new file mode 100644
index 000000000..4eebdd015
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderContext.java
@@ -0,0 +1,138 @@
+/*
+ * 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.rendering.v1;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.Camera;
+import net.minecraft.client.render.Frustum;
+import net.minecraft.client.render.GameRenderer;
+import net.minecraft.client.render.LightmapTextureManager;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.WorldRenderer;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.entity.Entity;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Matrix4f;
+import net.minecraft.util.profiler.Profiler;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+
+/**
+ * Except as noted below, the properties exposed here match the parameters passed to
+ * {@link WorldRenderer#render(MatrixStack, float, long, boolean, Camera, GameRenderer, LightmapTextureManager, Matrix4f)}.
+ */
+@Environment(EnvType.CLIENT)
+public interface WorldRenderContext {
+	/**
+	 * The world renderer instance doing the rendering and invoking the event.
+	 *
+	 * @return WorldRenderer instance invoking the event
+	 */
+	WorldRenderer worldRenderer();
+
+	MatrixStack matrixStack();
+
+	float tickDelta();
+
+	long limitTime();
+
+	boolean blockOutlines();
+
+	Camera camera();
+
+	GameRenderer gameRenderer();
+
+	LightmapTextureManager lightmapTextureManager();
+
+	Matrix4f projectionMatrix();
+
+	/**
+	 * Convenient access to {WorldRenderer.world}.
+	 *
+	 * @return world renderer's client world instance
+	 */
+	ClientWorld world();
+
+	/**
+	 * Convenient access to game performance profiler.
+	 *
+	 * @return the active profiler
+	 */
+	Profiler profiler();
+
+	/**
+	 * Test to know if "fabulous" graphics mode is enabled.
+	 *
+	 * <p>Use this for renders that need to render on top of all translucency to activate or deactivate different
+	 * event handlers to get optimal depth testing results. When fabulous is off, it may be better to render
+	 * during {@code WorldRenderLastCallback} after clouds and weather are drawn. Conversely, when fabulous mode is on,
+	 * it may be better to draw during {@code WorldRenderPostTranslucentCallback}, before the fabulous mode composite
+	 * shader runs, depending on which translucent buffer is being targeted.
+	 *
+	 * @return {@code true} when "fabulous" graphics mode is enabled.
+	 */
+	boolean advancedTranslucency();
+
+	/**
+	 * The {@code VertexConsumerProvider} instance being used by the world renderer for most non-terrain renders.
+	 * Generally this will be better for most use cases because quads for the same layer can be buffered
+	 * incrementally and then drawn all at once by the world renderer.
+	 *
+	 * <p>IMPORTANT - all vertex coordinates sent to consumers should be relative to the camera to
+	 * be consistent with other quads emitted by the world renderer and other mods.  If this isn't
+	 * possible, caller should use a separate "immediate" instance.
+	 *
+	 * <p>This property is {@code null} before {@link WorldRenderEvents#BEFORE_ENTITIES} and after
+	 * {@link WorldRenderEvents#BEFORE_DEBUG_RENDER} because the consumer buffers are not available before or
+	 * drawn after that in vanilla world rendering.  Renders that cannot draw in one of the supported events
+	 * must be drawn directly to the frame buffer, preferably in {@link WorldRenderEvents#LAST} to avoid being
+	 * overdrawn or cleared.
+	 */
+	@Nullable VertexConsumerProvider consumers();
+
+	/**
+	 * View frustum, after it is initialized. Will be {@code null} during
+	 * {@link WorldRenderEvents#START}.
+	 */
+	@Nullable Frustum frustum();
+
+	/**
+	 * Used in {@code BLOCK_OUTLINE} to convey the parameters normally sent to
+	 * {@code WorldRenderer.drawBlockOutline}.
+	 */
+	@Environment(EnvType.CLIENT)
+	public interface BlockOutlineContext {
+		VertexConsumer vertexConsumer();
+
+		Entity entity();
+
+		double cameraX();
+
+		double cameraY();
+
+		double cameraZ();
+
+		BlockPos blockPos();
+
+		BlockState blockState();
+	}
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java
new file mode 100644
index 000000000..7d45d9d99
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java
@@ -0,0 +1,327 @@
+/*
+ * 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.rendering.v1;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.render.WorldRenderer;
+import net.minecraft.util.hit.HitResult;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext.BlockOutlineContext;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+/**
+ * Mods should use these events to introduce custom rendering during {@link WorldRenderer#render(net.minecraft.client.util.math.MatrixStack, float, long, boolean, net.minecraft.client.render.Camera, net.minecraft.client.render.GameRenderer, net.minecraft.client.render.LightmapTextureManager, net.minecraft.util.math.Matrix4f)}
+ * without adding complicated and conflict-prone injections there.  Using these events also enables 3rd-party renderers
+ * that make large-scale changes to rendering maintain compatibility by calling any broken event invokers directly.
+ *
+ * <p>The order of events each frame is as follows:
+ * <ul><li>START
+ * <li>AFTER_SETUP
+ * <li>BEFORE_ENTITIES
+ * <li>AFTER_ENTITIES
+ * <li>BEFORE_BLOCK_OUTLINE
+ * <li>BLOCK_OUTLINE  (If not cancelled in BEFORE_BLOCK_OUTLINE)
+ * <li>BEFORE_DEBUG_RENDER
+ * <li>AFTER_TRANSLUCENT
+ * <li>LAST
+ * <li>END</ul>
+ *
+ * <p>These events are not dependent on the Fabric rendering API or Indigo but work when those are present.
+ */
+@Environment(EnvType.CLIENT)
+public final class WorldRenderEvents {
+	private WorldRenderEvents() { }
+
+	/**
+	 * Called before world rendering executes. Input parameters are available but frustum is not.
+	 * Use this event instead of injecting to the HEAD of {@link WorldRenderer#render} to avoid
+	 * compatibility problems with 3rd-party renderer implementations.
+	 *
+	 * <p>Use for setup of state that is needed during the world render call that
+	 * does not depend on the view frustum.
+	 */
+	public static final Event<Start> START = EventFactory.createArrayBacked(Start.class, context -> { }, callbacks -> context -> {
+		for (final Start callback : callbacks) {
+			callback.onStart(context);
+		}
+	});
+
+	/**
+	 * Called after view Frustum is computed and all render chunks to be rendered are
+	 * identified and rebuilt but before chunks are uploaded to GPU.
+	 *
+	 * <p>Use for setup of state that depends on view frustum.
+	 */
+	public static final Event<AfterSetup> AFTER_SETUP = EventFactory.createArrayBacked(AfterSetup.class, context -> { }, callbacks -> context -> {
+		for (final AfterSetup callback : callbacks) {
+			callback.afterSetup(context);
+		}
+	});
+
+	/**
+	 * Called after the Solid, Cutout and Cutout Mipped terrain layers have been output to the framebuffer.
+	 *
+	 * <p>Use to render non-translucent terrain to the framebuffer.
+	 *
+	 * <p>Note that 3rd-party renderers may combine these passes or otherwise alter the
+	 * rendering pipeline for sake of performance or features. This can break direct writes to the
+	 * framebuffer.  Use this event for cases that cannot be satisfied by FabricBakedModel,
+	 * BlockEntityRenderer or other existing abstraction. If at all possible, use an existing terrain
+	 * RenderLayer instead of outputting to the framebuffer directly with GL calls.
+	 *
+	 * <p>The consumer is responsible for setup and tear down of GL state appropriate for the intended output.
+	 *
+	 * <p>Because solid and cutout quads are depth-tested, order of output does not matter except to improve
+	 * culling performance, which should not be significant after primary terrain rendering. This means
+	 * mods that currently hook calls to individual render layers can simply execute them all at once when
+	 * the event is called.
+	 *
+	 * <p>This event fires before entities and block entities are rendered and may be useful to prepare them.
+	 */
+	public static final Event<BeforeEntities> BEFORE_ENTITIES = EventFactory.createArrayBacked(BeforeEntities.class, context -> { }, callbacks -> context -> {
+		for (final BeforeEntities callback : callbacks) {
+			callback.beforeEntities(context);
+		}
+	});
+
+	/**
+	 * Called after entities are rendered and solid entity layers
+	 * have been drawn to the main frame buffer target, before
+	 * block entity rendering begins.
+	 *
+	 * <p>Use for global block entity render setup, or
+	 * to append block-related quads to the entity consumers using the
+	 * {@VertexConsumerProvider} from the provided context. This
+	 * will generally give better (if not perfect) results
+	 * for non-terrain translucency vs. drawing directly later on.
+	 */
+	public static final Event<AfterEntities> AFTER_ENTITIES = EventFactory.createArrayBacked(AfterEntities.class, context -> { }, callbacks -> context -> {
+		for (final AfterEntities callback : callbacks) {
+			callback.afterEntities(context);
+		}
+	});
+
+	/**
+	 * Called before default block outline rendering and before checks are
+	 * done to determine if it should happen. Can optionally cancel the default
+	 * rendering but all event handlers will always be called.
+	 *
+	 * <p>Use this to decorate or replace the default block outline rendering
+	 * for specific modded blocks or when the need for a block outline render
+	 * would not be detected.  Normally, outline rendering will not happen for
+	 * entities, fluids, or other game objects that do not register a block-type hit.
+	 *
+	 * <p>Returning false from any event subscriber will cancel the default block
+	 * outline render and suppress the {@code BLOCK_RENDER} event.  This has no
+	 * effect on other subscribers to this event - all subscribers will always be called.
+	 * Canceling here is appropriate when there is still a valid block hit (with a fluid,
+	 * for example) and you don't want the block outline render to appear.
+	 *
+	 * <p>This event should NOT be used for general-purpose replacement of
+	 * the default block outline rendering because it will interfere with mod-specific
+	 * renders.  Mods that replace the default block outline for specific blocks
+	 * should instead subscribe to {@link #BLOCK_OUTLINE}.
+	 */
+	public static final Event<BeforeBlockOutline> BEFORE_BLOCK_OUTLINE = EventFactory.createArrayBacked(BeforeBlockOutline.class, (context, hit) -> true, callbacks -> (context, hit) -> {
+		boolean shouldRender = true;
+
+		for (final BeforeBlockOutline callback : callbacks) {
+			if (!callback.beforeBlockOutline(context, hit)) {
+				shouldRender = false;
+			}
+		}
+
+		return shouldRender;
+	});
+
+	/**
+	 * Called after block outline render checks are made and before the
+	 * default block outline render runs.  Will NOT be called if the default outline
+	 * render was cancelled in {@link #BEFORE_BLOCK_OUTLINE}.
+	 *
+	 * <p>Use this to replace the default block outline rendering for specific blocks that
+	 * need special outline rendering or to add information that doesn't replace the block outline.
+	 * Subscribers cannot affect each other or detect if another subscriber is also
+	 * handling a specific block.  If two subscribers render for the same block, both
+	 * renders will appear.
+	 *
+	 * <p>Returning false from any event subscriber will cancel the default block
+	 * outline render.  This has no effect on other subscribers to this event -
+	 * all subscribers will always be called.  Canceling is appropriate when the
+	 * subscriber replacing the default block outline render for a specific block.
+	 *
+	 * <p>This event is not appropriate for mods that replace the default block
+	 * outline render for <em>all</em> blocks because all event subscribers will
+	 * always render - only the default outline render can be cancelled.  That should
+	 * be accomplished by mixin to the block outline render routine itself, typically
+	 * by targeting {@link WorldRenderer#drawShapeOutline}.
+	 */
+	public static final Event<BlockOutline> BLOCK_OUTLINE = EventFactory.createArrayBacked(BlockOutline.class, (worldRenderContext, blockOutlieContext) -> true, callbacks -> (worldRenderContext, blockOutlieContext) -> {
+		boolean shouldRender = true;
+
+		for (final BlockOutline callback : callbacks) {
+			if (!callback.onBlockOutline(worldRenderContext, blockOutlieContext)) {
+				shouldRender = false;
+			}
+		}
+
+		return shouldRender;
+	});
+
+	/**
+	 * Called before vanilla debug renderers are output to the framebuffer.
+	 * This happens very soon after entities, block breaking and most other
+	 * non-translucent renders but before translucency is drawn.
+	 *
+	 * <p>Unlike most other events, renders in this event are expected to be drawn
+	 * directly and immediately to the framebuffer. The OpenGL render state view
+	 * matrix will be transformed to match the camera view before the event is called.
+	 *
+	 * <p>Use to drawn lines, overlays and other content similar to vanilla
+	 * debug renders.
+	 */
+	public static final Event<DebugRender> BEFORE_DEBUG_RENDER = EventFactory.createArrayBacked(DebugRender.class, context -> { }, callbacks -> context -> {
+		for (final DebugRender callback : callbacks) {
+			callback.beforeDebugRender(context);
+		}
+	});
+
+	/**
+	 * Called after entity, terrain, and particle translucent layers have been
+	 * drawn to the framebuffer but before translucency combine has happened
+	 * in fabulous mode.
+	 *
+	 * <p>Use for drawing overlays or other effects on top of those targets
+	 * (or the main target when fabulous isn't active) before clouds and weather
+	 * are drawn.  However, note that {@code WorldRenderPostEntityCallback} will
+	 * offer better results in most use cases.
+	 *
+	 * <p>Vertex consumers are not available in this event because all buffered quads
+	 * are drawn before this event is called.  Any rendering here must be drawn
+	 * directly to the frame buffer.  The render state matrix will not include
+	 * camera transformation, so {@link #LAST} may be preferable if that is wanted.
+	 */
+	public static final Event<AfterTranslucent> AFTER_TRANSLUCENT = EventFactory.createArrayBacked(AfterTranslucent.class, context -> { }, callbacks -> context -> {
+		for (final AfterTranslucent callback : callbacks) {
+			callback.afterTranslucent(context);
+		}
+	});
+
+	/**
+	 * Called after all framebuffer writes are complete but before all world
+	 * rendering is torn down.
+	 *
+	 * <p>Unlike most other events, renders in this event are expected to be drawn
+	 * directly and immediately to the framebuffer. The OpenGL render state view
+	 * matrix will be transformed to match the camera view before the event is called.
+	 *
+	 * <p>Use to draw content that should appear on top of the world before hand and GUI rendering occur.
+	 */
+	public static final Event<Last> LAST = EventFactory.createArrayBacked(Last.class, context -> { }, callbacks -> context -> {
+		for (final Last callback : callbacks) {
+			callback.onLast(context);
+		}
+	});
+
+	/**
+	 * Called after all world rendering is complete and changes to GL state are unwound.
+	 *
+	 * <p>Use to draw overlays that handle GL state management independently or to tear
+	 * down transient state in event handlers or as a hook that precedes hand/held item
+	 * and GUI rendering.
+	 */
+	public static final Event<End> END = EventFactory.createArrayBacked(End.class, context -> { }, callbacks -> context -> {
+		for (final End callback : callbacks) {
+			callback.onEnd(context);
+		}
+	});
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface Start {
+		void onStart(WorldRenderContext context);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterSetup {
+		void afterSetup(WorldRenderContext context);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeEntities {
+		void beforeEntities(WorldRenderContext context);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterEntities {
+		void afterEntities(WorldRenderContext context);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeBlockOutline {
+		/**
+		 * Event signature for {@link WorldRenderEvents#BEFORE_BLOCK_OUTLINE}.
+		 *
+		 * @param context  Access to state and parameters available during world rendering.
+		 * @param hitResult The game object currently under the crosshair target.
+		 * Normally equivalent to {@link MinecraftClient#crosshairTarget}. Provided for convenience.
+		 * @return true if vanilla block outline rendering should happen.
+		 * Returning false prevents {@link WorldRenderEvents#BLOCK_OUTLINE} from invoking
+		 * and also skips the vanilla block outline render, but has no effect on other subscribers to this event.
+		 */
+		boolean beforeBlockOutline(WorldRenderContext context, @Nullable HitResult hitResult);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BlockOutline {
+		boolean onBlockOutline(WorldRenderContext worldRenderContext, BlockOutlineContext blockOutlieContext);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface DebugRender {
+		void beforeDebugRender(WorldRenderContext context);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterTranslucent {
+		void afterTranslucent(WorldRenderContext context);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface Last {
+		void onLast(WorldRenderContext context);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface End {
+		void onEnd(WorldRenderContext context);
+	}
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/WorldRenderContextImpl.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/WorldRenderContextImpl.java
new file mode 100644
index 000000000..4d47cedda
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/WorldRenderContextImpl.java
@@ -0,0 +1,221 @@
+/*
+ * 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;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.Camera;
+import net.minecraft.client.render.Frustum;
+import net.minecraft.client.render.GameRenderer;
+import net.minecraft.client.render.LightmapTextureManager;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.WorldRenderer;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.entity.Entity;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Matrix4f;
+import net.minecraft.util.profiler.Profiler;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+
+@Environment(EnvType.CLIENT)
+public final class WorldRenderContextImpl implements WorldRenderContext.BlockOutlineContext, WorldRenderContext {
+	private WorldRenderer worldRenderer;
+	private MatrixStack matrixStack;
+	private float tickDelta;
+	private long limitTime;
+	private boolean blockOutlines;
+	private Camera camera;
+	private Frustum frustum;
+	private GameRenderer gameRenderer;
+	private LightmapTextureManager lightmapTextureManager;
+	private Matrix4f projectionMatrix;
+	private VertexConsumerProvider consumers;
+	private Profiler profiler;
+	private boolean advancedTranslucency;
+	private ClientWorld world;
+
+	private VertexConsumer vertexConsumer;
+	private Entity entity;
+	private double cameraX;
+	private double cameraY;
+	private double cameraZ;
+	private BlockPos blockPos;
+	private BlockState blockState;
+
+	public boolean renderBlockOutline = true;
+
+	public void prepare(
+			WorldRenderer worldRenderer,
+			MatrixStack matrixStack,
+			float tickDelta,
+			long limitTime,
+			boolean blockOutlines,
+			Camera camera,
+			GameRenderer gameRenderer,
+			LightmapTextureManager lightmapTextureManager,
+			Matrix4f projectionMatrix,
+			VertexConsumerProvider consumers,
+			Profiler profiler,
+			boolean advancedTranslucency,
+			ClientWorld world
+	) {
+		this.worldRenderer = worldRenderer;
+		this.matrixStack = matrixStack;
+		this.tickDelta = tickDelta;
+		this.limitTime = limitTime;
+		this.blockOutlines = blockOutlines;
+		this.camera = camera;
+		this.gameRenderer = gameRenderer;
+		this.lightmapTextureManager = lightmapTextureManager;
+		this.projectionMatrix = projectionMatrix;
+		this.consumers = consumers;
+		this.profiler = profiler;
+		this.advancedTranslucency = advancedTranslucency;
+		this.world = world;
+	}
+
+	public void setFrustum(Frustum frustum) {
+		this.frustum = frustum;
+	}
+
+	public void prepareBlockOutline(
+			VertexConsumer vertexConsumer,
+			Entity entity,
+			double cameraX,
+			double cameraY,
+			double cameraZ,
+			BlockPos blockPos,
+			BlockState blockState
+	) {
+		this.vertexConsumer = vertexConsumer;
+		this.entity = entity;
+		this.cameraX = cameraX;
+		this.cameraY = cameraY;
+		this.cameraZ = cameraZ;
+		this.blockPos = blockPos;
+		this.blockState = blockState;
+	}
+
+	@Override
+	public WorldRenderer worldRenderer() {
+		return worldRenderer;
+	}
+
+	@Override
+	public MatrixStack matrixStack() {
+		return matrixStack;
+	}
+
+	@Override
+	public float tickDelta() {
+		return tickDelta;
+	}
+
+	@Override
+	public long limitTime() {
+		return limitTime;
+	}
+
+	@Override
+	public boolean blockOutlines() {
+		return blockOutlines;
+	}
+
+	@Override
+	public Camera camera() {
+		return camera;
+	}
+
+	@Override
+	public Matrix4f projectionMatrix() {
+		return projectionMatrix;
+	}
+
+	@Override
+	public ClientWorld world() {
+		return world;
+	}
+
+	@Override
+	public Frustum frustum() {
+		return frustum;
+	}
+
+	@Override
+	public VertexConsumerProvider consumers() {
+		return consumers;
+	}
+
+	@Override
+	public GameRenderer gameRenderer() {
+		return gameRenderer;
+	}
+
+	@Override
+	public LightmapTextureManager lightmapTextureManager() {
+		return lightmapTextureManager;
+	}
+
+	@Override
+	public Profiler profiler() {
+		return profiler;
+	}
+
+	@Override
+	public boolean advancedTranslucency() {
+		return advancedTranslucency;
+	}
+
+	@Override
+	public VertexConsumer vertexConsumer() {
+		return vertexConsumer;
+	}
+
+	@Override
+	public Entity entity() {
+		return entity;
+	}
+
+	@Override
+	public double cameraX() {
+		return cameraX;
+	}
+
+	@Override
+	public double cameraY() {
+		return cameraY;
+	}
+
+	@Override
+	public double cameraZ() {
+		return cameraZ;
+	}
+
+	@Override
+	public BlockPos blockPos() {
+		return blockPos;
+	}
+
+	@Override
+	public BlockState blockState() {
+		return blockState;
+	}
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinWorldRenderer.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinWorldRenderer.java
index 07ee7d968..dacc11957 100644
--- a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinWorldRenderer.java
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinWorldRenderer.java
@@ -17,16 +17,149 @@
 package net.fabricmc.fabric.mixin.client.rendering;
 
 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.At.Shift;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
+import net.minecraft.block.BlockState;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gl.ShaderEffect;
+import net.minecraft.client.render.BufferBuilderStorage;
+import net.minecraft.client.render.Camera;
+import net.minecraft.client.render.Frustum;
+import net.minecraft.client.render.GameRenderer;
+import net.minecraft.client.render.LightmapTextureManager;
+import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.WorldRenderer;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.entity.Entity;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Matrix4f;
 
 import net.fabricmc.fabric.api.client.rendering.v1.InvalidateRenderStateCallback;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.impl.client.rendering.WorldRenderContextImpl;
 
 @Mixin(WorldRenderer.class)
 public abstract class MixinWorldRenderer {
+	@Shadow private BufferBuilderStorage bufferBuilders;
+	@Shadow private ClientWorld world;
+	@Shadow private ShaderEffect transparencyShader;
+	@Shadow private MinecraftClient client;
+	@Unique private final WorldRenderContextImpl context = new WorldRenderContextImpl();
+	@Unique private boolean didRenderParticles;
+
+	@Inject(method = "render", at = @At("HEAD"))
+	private void beforeRender(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, CallbackInfo ci) {
+		context.prepare((WorldRenderer) (Object) this, matrices, tickDelta, limitTime, renderBlockOutline, camera, gameRenderer, lightmapTextureManager, matrix4f, bufferBuilders.getEntityVertexConsumers(), world.getProfiler(), transparencyShader != null, world);
+		WorldRenderEvents.START.invoker().onStart(context);
+		didRenderParticles = false;
+	}
+
+	@Inject(method = "setupTerrain", at = @At("RETURN"))
+	private void afterTerrainSetup(Camera camera, Frustum frustum, boolean hasForcedFrustum, int frame, boolean spectator, CallbackInfo ci) {
+		context.setFrustum(frustum);
+		WorldRenderEvents.AFTER_SETUP.invoker().afterSetup(context);
+	}
+
+	@Inject(
+			method = "render",
+			at = @At(
+				value = "INVOKE",
+				target = "Lnet/minecraft/client/render/WorldRenderer;renderLayer(Lnet/minecraft/client/render/RenderLayer;Lnet/minecraft/client/util/math/MatrixStack;DDD)V",
+				ordinal = 2,
+				shift = Shift.AFTER
+			)
+	)
+	private void afterTerrainSolid(CallbackInfo ci) {
+		WorldRenderEvents.BEFORE_ENTITIES.invoker().beforeEntities(context);
+	}
+
+	@Inject(method = "render", at = @At(value = "CONSTANT", args = "stringValue=blockentities", ordinal = 0))
+	private void afterEntities(CallbackInfo ci) {
+		WorldRenderEvents.AFTER_ENTITIES.invoker().afterEntities(context);
+	}
+
+	@Inject(
+			method = "render",
+			at = @At(
+				value = "FIELD",
+				target = "Lnet/minecraft/client/MinecraftClient;crosshairTarget:Lnet/minecraft/util/hit/HitResult;",
+				shift = At.Shift.AFTER,
+				ordinal = 1
+			)
+	)
+	private void beforeRenderOutline(CallbackInfo ci) {
+		context.renderBlockOutline = WorldRenderEvents.BEFORE_BLOCK_OUTLINE.invoker().beforeBlockOutline(context, client.crosshairTarget);
+	}
+
+	@Inject(method = "drawBlockOutline", at = @At("HEAD"), cancellable = true)
+	private void onDrawBlockOutline(MatrixStack matrixStack, VertexConsumer vertexConsumer, Entity entity, double cameraX, double cameraY, double cameraZ, BlockPos blockPos, BlockState blockState, CallbackInfo ci) {
+		if (!context.renderBlockOutline) {
+			// Was cancelled before we got here, so do not
+			// fire the BLOCK_OUTLINE event per contract of the API.
+			ci.cancel();
+		} else {
+			context.prepareBlockOutline(vertexConsumer, entity, cameraX, cameraY, cameraZ, blockPos, blockState);
+
+			if (!WorldRenderEvents.BLOCK_OUTLINE.invoker().onBlockOutline(context, context)) {
+				ci.cancel();
+			}
+		}
+	}
+
+	@Inject(
+			method = "render",
+			at = @At(
+				value = "INVOKE",
+				target = "Lnet/minecraft/client/render/debug/DebugRenderer;render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;DDD)V",
+				ordinal = 0
+			)
+	)
+	private void beforeDebugRender(CallbackInfo ci) {
+		WorldRenderEvents.BEFORE_DEBUG_RENDER.invoker().beforeDebugRender(context);
+	}
+
+	@Inject(
+			method = "render",
+			at = @At(
+				value = "INVOKE",
+				target = "Lnet/minecraft/client/particle/ParticleManager;renderParticles(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;Lnet/minecraft/client/render/LightmapTextureManager;Lnet/minecraft/client/render/Camera;F)V"
+			)
+	)
+	private void onRenderParticles(CallbackInfo ci) {
+		// set a flag so we know the next pushMatrix call is after particles
+		didRenderParticles = true;
+	}
+
+	@Inject(method = "render", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;pushMatrix()V"))
+	private void beforeClouds(CallbackInfo ci) {
+		if (didRenderParticles) {
+			didRenderParticles = false;
+			WorldRenderEvents.AFTER_TRANSLUCENT.invoker().afterTranslucent(context);
+		}
+	}
+
+	@Inject(
+			method = "render",
+			at = @At(
+				value = "INVOKE",
+				target = "Lnet/minecraft/client/render/WorldRenderer;renderChunkDebugInfo(Lnet/minecraft/client/render/Camera;)V"
+			)
+	)
+	private void onChunkDebugRender(CallbackInfo ci) {
+		WorldRenderEvents.LAST.invoker().onLast(context);
+	}
+
+	@Inject(method = "render", at = @At("RETURN"))
+	private void afterRender(CallbackInfo ci) {
+		WorldRenderEvents.END.invoker().onEnd(context);
+	}
+
 	@Inject(method = "reload", at = @At("HEAD"))
 	private void onReload(CallbackInfo ci) {
 		InvalidateRenderStateCallback.EVENT.invoker().onInvalidate();