diff --git a/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch b/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch
index 4d00a9a971..c5f21fad8b 100644
--- a/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch
+++ b/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch
@@ -18,6 +18,14 @@
CloudStatus cloudstatus = this.minecraft.options.getCloudsType();
if (cloudstatus != CloudStatus.OFF) {
float f2 = this.level.effects().getCloudHeight();
+@@ -499,6 +_,7 @@
+ }
+
+ this.addLateDebugPass(framegraphbuilder, vec3, fogparameters);
++ net.minecraftforge.client.event.ForgeEventFactoryClient.onLastFramePassAddition(framegraphbuilder, targets);
+ profilerfiller.popPush("framegraph");
+ framegraphbuilder.execute(p_367325_, new FrameGraphBuilder.Inspector() {
+ @Override
@@ -564,7 +_,11 @@
double d2 = vec3.z();
p_369478_.push("terrain");
diff --git a/src/main/java/net/minecraftforge/client/event/AddFramePassEvent.java b/src/main/java/net/minecraftforge/client/event/AddFramePassEvent.java
new file mode 100644
index 0000000000..59ac820015
--- /dev/null
+++ b/src/main/java/net/minecraftforge/client/event/AddFramePassEvent.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Forge Development LLC and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+package net.minecraftforge.client.event;
+
+import com.mojang.blaze3d.framegraph.FrameGraphBuilder;
+import com.mojang.blaze3d.framegraph.FramePass;
+import net.minecraft.client.renderer.LevelTargetBundle;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraftforge.eventbus.api.Event;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Fired after all vanilla frame passes are added into the pass list.
+ *
+ *
This event is not {@linkplain net.minecraftforge.eventbus.api.Cancelable cancellable}, and does not
+ * {@linkplain HasResult have a result}.
+ *
+ * This event is fired on the {@linkplain net.minecraftforge.common.MinecraftForge#EVENT_BUS main Forge event bus},
+ * only on the {@linkplain net.minecraftforge.fml.LogicalSide#CLIENT logical client}.
+ */
+public final class AddFramePassEvent extends Event {
+ private final FrameGraphBuilder builder;
+ private final LevelTargetBundle bundle;
+
+ private final Set addedNames = new HashSet<>();
+
+ @ApiStatus.Internal
+ public AddFramePassEvent(FrameGraphBuilder builder, LevelTargetBundle bundle) {
+ this.builder = builder;
+ this.bundle = bundle;
+ }
+
+ /**
+ * Adds a frame pass to the end of the pass list.
+ *
+ * @param rl Resource location for frame pass name. Use RLs to avoid duplicate names.
+ * @return Reference to pass to add modder rendering to.
+ * @throws IllegalArgumentException If the name is a duplicate.
+ */
+ public FramePass createPass(ResourceLocation rl) {
+ String name = rl.toString();
+ if (!this.addedNames.add(name)) // false if the set didn't change
+ throw new IllegalArgumentException("Cannot create a frame pass with a duplicate name: " + name);
+
+ return this.builder.addPass(name);
+ }
+
+ public LevelTargetBundle getBundle() {
+ return bundle;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/minecraftforge/client/event/ForgeEventFactoryClient.java b/src/main/java/net/minecraftforge/client/event/ForgeEventFactoryClient.java
index 8bdd26e18a..ed1e8c882e 100644
--- a/src/main/java/net/minecraftforge/client/event/ForgeEventFactoryClient.java
+++ b/src/main/java/net/minecraftforge/client/event/ForgeEventFactoryClient.java
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap;
import com.mojang.blaze3d.audio.Channel;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.framegraph.FrameGraphBuilder;
import net.minecraft.client.Camera;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.DeltaTracker;
@@ -51,6 +52,7 @@ import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
import net.minecraft.client.renderer.entity.state.LivingEntityRenderState;
import net.minecraft.client.renderer.entity.state.PlayerRenderState;
import net.minecraft.client.renderer.special.SpecialModelRenderer;
+import net.minecraft.client.renderer.LevelTargetBundle;
import net.minecraft.client.resources.PlayerSkin.Model;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.SoundEngine;
@@ -303,4 +305,8 @@ public final class ForgeEventFactoryClient {
// This is on the mod bus because it happens during the initial texture reload, which is while the Forge bus is shut down.
return fireModBus(new ModelEvent.RegisterModelStateDefinitions());
}
+
+ public static void onLastFramePassAddition(FrameGraphBuilder builder, LevelTargetBundle bundle) {
+ post(new AddFramePassEvent(builder, bundle));
+ }
}
diff --git a/src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java b/src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java
new file mode 100644
index 0000000000..50d19d728f
--- /dev/null
+++ b/src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) Forge Development LLC and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+package net.minecraftforge.debug.client;
+
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.client.event.AddFramePassEvent;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
+import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
+import net.minecraftforge.gametest.GameTestHolder;
+import net.minecraftforge.test.BaseTestMod;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.ShapeRenderer;
+import net.minecraft.gametest.framework.GameTest;
+import net.minecraft.gametest.framework.GameTestHelper;
+import net.minecraft.resources.ResourceLocation;
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.framegraph.FramePass;
+
+
+@Mod(RenderFrameLayerTest.MODID)
+@GameTestHolder("forge." + RenderFrameLayerTest.MODID)
+public class RenderFrameLayerTest extends BaseTestMod {
+ public static final String MODID = "render_frame_layer_test";
+
+ public RenderFrameLayerTest(FMLJavaModLoadingContext context) {
+ super(context);
+ MinecraftForge.EVENT_BUS.addListener(RenderFrameLayerTest::renderTest);
+ }
+
+ private static ResourceLocation rl(String path) {
+ return ResourceLocation.fromNamespaceAndPath(MODID, path);
+ }
+
+ @GameTest(template = "forge:empty3x3x3")
+ public static void frame_passes_run(GameTestHelper helper) {
+ var passOne = helper.boolFlag("passOne");
+ var passTwo = helper.boolFlag("passOne");
+
+ helper.addEventListener(event -> {
+ FramePass pass = event.createPass(rl(MODID + "test_1") );
+ event.getBundle().main = pass.readsAndWrites(event.getBundle().main);
+ pass.executes(() -> passOne.set(true));
+
+ pass = event.createPass(rl(MODID + "test_2"));
+ event.getBundle().main = pass.readsAndWrites(event.getBundle().main);
+ pass.executes(() -> passTwo.set(true));
+ });
+
+ helper.runAfterDelay(2, () -> {
+ helper.assertTrue(passOne.getBool(), "Pass One was not executed");
+ helper.assertTrue(passTwo.getBool(), "Pass Two was not executed");
+ helper.succeed();
+ });
+ }
+
+ /**
+ * If this is working, two white line box cubes will be rendered at ground level in a superflat world around (0,0)
+ */
+ @SubscribeEvent
+ public static void renderTest(AddFramePassEvent event) {
+ var mainCamera = Minecraft.getInstance().gameRenderer.getMainCamera();
+ FramePass pass = event.createPass(rl(MODID));
+ event.getBundle().main = pass.readsAndWrites(event.getBundle().main);
+
+ pass.executes(() -> {
+ PoseStack ps = new PoseStack();
+ ps.translate(mainCamera.getPosition().multiply(-1,-1,-1));
+ ps.pushPose();
+ var buffSource = Minecraft.getInstance().renderBuffers().bufferSource();
+ var vc = buffSource.getBuffer(RenderType.lines());
+
+ ShapeRenderer.renderLineBox(ps, vc, 0, -60, 0, 10, -50, 10, 1f, 1f, 1f, 1f);
+ buffSource.endBatch();
+ ps.popPose();
+ });
+
+ pass = event.createPass(rl(MODID+"2"));
+ event.getBundle().main = pass.readsAndWrites(event.getBundle().main);
+
+ pass.executes(() -> {
+ PoseStack ps = new PoseStack();
+ ps.translate(mainCamera.getPosition().multiply(-1,-1,-1));
+ ps.pushPose();
+ var buffSource = Minecraft.getInstance().renderBuffers().bufferSource();
+ var vc = buffSource.getBuffer(RenderType.lines());
+
+ ShapeRenderer.renderLineBox(ps, vc, 1, -61, 1, 9, -49, 9, 1f, 1f, 1f, 1f);
+ buffSource.endBatch();
+ ps.popPose();
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/render_frame_layer_test/META-INF/mods.toml b/src/test/resources/render_frame_layer_test/META-INF/mods.toml
new file mode 100644
index 0000000000..c361869e8e
--- /dev/null
+++ b/src/test/resources/render_frame_layer_test/META-INF/mods.toml
@@ -0,0 +1 @@
+clientSideOnly=true
\ No newline at end of file