From a42ea9bc2c61c86a2689f86cedd60dbc392e47ff Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 21 Jul 2026 19:09:23 -0400 Subject: [PATCH] Add DeltaTracker (partialTick) context to PassDefinitions (#10831) (#10851) --- .../client/renderer/LevelRenderer.java.patch | 2 +- .../client/FramePassManager.java | 67 +++++++++++++++++-- .../client/event/AddFramePassEvent.java | 5 +- .../debug/client/RenderFrameLayerTest.java | 8 ++- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch b/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch index 7dc9a5d23c..fa1fba7870 100644 --- a/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch @@ -30,7 +30,7 @@ } this.addLateDebugPass(framegraphbuilder, vec3, p_407881_); -+ net.minecraftforge.client.FramePassManager.insertForgePasses(framegraphbuilder, targets); // Forge: Modded passes are inserted here. ++ net.minecraftforge.client.FramePassManager.insertForgePasses(framegraphbuilder, targets, p_342180_); // Forge: Modded passes are inserted here. profilerfiller.popPush("framegraph"); framegraphbuilder.execute(p_367325_, new FrameGraphBuilder.Inspector() { @Override diff --git a/src/main/java/net/minecraftforge/client/FramePassManager.java b/src/main/java/net/minecraftforge/client/FramePassManager.java index 1330764962..21f9afd63a 100644 --- a/src/main/java/net/minecraftforge/client/FramePassManager.java +++ b/src/main/java/net/minecraftforge/client/FramePassManager.java @@ -7,9 +7,11 @@ package net.minecraftforge.client; import com.mojang.blaze3d.framegraph.FrameGraphBuilder; import com.mojang.blaze3d.framegraph.FramePass; +import net.minecraft.client.DeltaTracker; import net.minecraft.client.renderer.LevelTargetBundle; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; import java.util.ArrayList; import java.util.List; @@ -27,18 +29,75 @@ public class FramePassManager { // Note: Pass order is determined automatically within FrameGraphBuilder. It's unclear what must be done to guarantee ordering. @ApiStatus.Internal - public static void insertForgePasses(FrameGraphBuilder graphBuilder, LevelTargetBundle bundle) { + public static void insertForgePasses(FrameGraphBuilder graphBuilder, LevelTargetBundle bundle, DeltaTracker tracker) { for (PassInfo info : addedPasses) { FramePass pass = graphBuilder.addPass(info.name); PassDefinition forgePass = info.pass; - forgePass.targets(bundle, pass); + forgePass.extracts(bundle, pass, tracker); pass.executes(forgePass::executes); } } + + + /// ### A PassDefinition must satisfy 3 things. + /// + /// 1. The rendering order for the purpose of translucency sorting. Read further for specific details. + /// 2. A render state definition supplier; "what" will be rendered. + /// 3. A render state definition consumer; "how" it will be rendered. + /// + /// + /// To satisfy #1, all FramePasses (and thus PassDefinitions) must bind against at least one target. + /// The list of targets can be found in {@linkplain LevelTargetBundle}. + /// HOWEVER!!! Only {@linkplain LevelTargetBundle#main} is used if the graphics mode is not set to Fabulous! + /// So at minimum, all passes must guarantee a fallback binding to the main target, like the below. + /// ``` + /// @Override + /// void extracts(LevelTargetBundle bundle, FramePass pass, DeltaTracker deltaTracker) { + /// if (bundle.clouds != null) { // Perhaps we want to bind to clouds if Fabulous! is on. + /// bundle.clouds = pass.readsAndWrites(bundle.clouds); + /// } + /// // An else clause could be used, but is not mandatory. It is fine to bind to more than one target. + /// bundle.main = pass.readsAndWrites(bundle.main); + /// } + /// ``` + /// + /// + /// It is up to the user to decide how their pass should bind to targets, but it must always bind to at least one. + /// Failure to satisfy this requirement will result in {@linkplain FrameGraphBuilder#resolvePassOrder} exploding. + /// Custom render targets are possible but not documented, see the implementation of {@linkplain LevelTargetBundle} + /// if you want to take a crack at it. + /// + /// Satisfying #2 is simple. Any state information you need to extract + /// should be done during {@linkplain PassDefinition#extracts(LevelTargetBundle, FramePass, DeltaTracker)}. + /// No actual rendering should be done at this time. This can happen before or after binding to a target. + /// The specific implementation of your render state is up to you, it can even be done with some instance variables. + /// + /// Satisfying #3 is also simple, this is the actual rendering that will consume the state created + /// during the extracts phase. + @NullMarked public interface PassDefinition { - void targets(LevelTargetBundle bundle, FramePass pass); - void executes(); + /** + * Use to define which targets your pass will bind against, see {@link FramePass#reads} and {@link FramePass#readsAndWrites} + * A FramePass must bind to at least ONE target. Otherwise, you get freaky issues with >1 modded passes. + * Additionally, this method should be used for extracting render states into instance variables if desired. + */ + default void extracts(LevelTargetBundle bundle, FramePass pass, DeltaTracker tracker) { + targets(bundle, pass); + } + + /** + * @deprecated Prefer {@link PassDefinition#extracts} + */ + @Deprecated(forRemoval = true, since = "1.21.10") + default void targets(LevelTargetBundle bundle, FramePass pass) {} + + /** + * Use to define what your pass does during the render stage + * @apiNote In 1.21.10+, LevelRenderState is used. Because that does not exist in 1.21.8, use the client level instead. + * This method is informally deprecated due to rendering changes in versions 1.21.10+ + */ + default void executes(){}; } private record PassInfo(String name, PassDefinition pass){} diff --git a/src/main/java/net/minecraftforge/client/event/AddFramePassEvent.java b/src/main/java/net/minecraftforge/client/event/AddFramePassEvent.java index 02d323a59e..7ee2c08138 100644 --- a/src/main/java/net/minecraftforge/client/event/AddFramePassEvent.java +++ b/src/main/java/net/minecraftforge/client/event/AddFramePassEvent.java @@ -13,7 +13,7 @@ import net.minecraftforge.eventbus.api.event.RecordEvent; import org.jspecify.annotations.NullMarked; /** - * Fired after all vanilla frame passes are added into the pass list. + * Fired during the construction of {@linkplain net.minecraft.client.renderer.LevelRenderer}. * *

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}. @@ -24,8 +24,9 @@ public record AddFramePassEvent() implements RecordEvent { /** * Adds a frame pass to pass list. * Create a new {@linkplain FramePassManager.PassDefinition} to handle render targets and render code. + * See javadocs for details on what creating a PassDefinition entails. * - * @param rl Resource location for frame pass name. Use RLs to avoid duplicate names. + * @param rl Identifier for frame pass name. Use RLs to avoid duplicate names. * @param definition see usages of {@linkplain com.mojang.blaze3d.framegraph.FramePass} in {@linkplain net.minecraft.client.renderer.LevelRenderer} * @throws IllegalArgumentException If the name is a duplicate. */ diff --git a/src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java b/src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java index 5ad401bde8..329909cc57 100644 --- a/src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java +++ b/src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java @@ -5,11 +5,11 @@ package net.minecraftforge.debug.client; +import net.minecraft.client.DeltaTracker; import net.minecraft.client.renderer.LevelTargetBundle; import net.minecraftforge.client.FramePassManager; import net.minecraftforge.client.event.AddFramePassEvent; import net.minecraftforge.common.extensions.IForgeGameTestHelper; -import net.minecraftforge.eventbus.api.listener.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.gametest.GameTest; @@ -21,6 +21,7 @@ import net.minecraft.client.renderer.ShapeRenderer; import net.minecraft.gametest.framework.GameTestHelper; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.framegraph.FramePass; +import org.jspecify.annotations.NullMarked; @GameTestNamespace("forge") @@ -47,11 +48,12 @@ public class RenderFrameLayerTest extends BaseTestMod { /** * If this is working, two white line box cubes will be rendered at ground level in a superflat world around (0,0) */ + @NullMarked public static void renderTest(AddFramePassEvent event) { var mainCamera = Minecraft.getInstance().gameRenderer.getMainCamera(); FramePassManager.PassDefinition def = new FramePassManager.PassDefinition() { @Override - public void targets(LevelTargetBundle bundle, FramePass pass) { + public void extracts(LevelTargetBundle bundle, FramePass pass, DeltaTracker deltaTracker) { bundle.main = pass.readsAndWrites(bundle.main); } @@ -72,7 +74,7 @@ public class RenderFrameLayerTest extends BaseTestMod { event.addPass(rl(MODID), def); FramePassManager.PassDefinition def2 = new FramePassManager.PassDefinition() { @Override - public void targets(LevelTargetBundle bundle, FramePass pass) { + public void extracts(LevelTargetBundle bundle, FramePass pass, DeltaTracker deltaTracker) { bundle.main = pass.readsAndWrites(bundle.main); }