diff --git a/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch b/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch index 3fb670e20b..c6f05cbada 100644 --- a/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/LevelRenderer.java.patch @@ -25,7 +25,7 @@ } this.addLateDebugPass(framegraphbuilder, this.levelRenderState.cameraRenderState, p_407881_, p_254120_); -+ net.minecraftforge.client.FramePassManager.insertForgePasses(framegraphbuilder, targets, this.levelRenderState); // Forge: Modded passes are inserted here. ++ net.minecraftforge.client.FramePassManager.insertForgePasses(framegraphbuilder, targets, this.levelRenderState, p_342180_); // Forge: Modded passes are inserted here. profilerfiller.popPush("executeFrameGraph"); 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 37b28c2f80..b16f24d7ab 100644 --- a/src/main/java/net/minecraftforge/client/FramePassManager.java +++ b/src/main/java/net/minecraftforge/client/FramePassManager.java @@ -7,12 +7,12 @@ package net.minecraftforge.client; import com.mojang.blaze3d.framegraph.FrameGraphBuilder; import com.mojang.blaze3d.framegraph.FramePass; -import com.mojang.blaze3d.resource.ResourceHandle; +import net.minecraft.client.DeltaTracker; import net.minecraft.client.renderer.LevelTargetBundle; import net.minecraft.client.renderer.state.LevelRenderState; import net.minecraft.resources.Identifier; import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.Nullable; +import org.jspecify.annotations.NullMarked; import java.util.ArrayList; import java.util.List; @@ -30,43 +30,95 @@ 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, LevelRenderState state) { + public static void insertForgePasses(FrameGraphBuilder graphBuilder, LevelTargetBundle bundle, LevelRenderState state, DeltaTracker deltaTracker) { for (PassInfo info : addedPasses) { FramePass pass = graphBuilder.addPass(info.name); PassDefinition forgePass = info.pass; - forgePass.extracts(bundle, pass); + forgePass.extracts(bundle, pass, deltaTracker); pass.executes(() -> forgePass.executes(state)); } } + + + /// ### 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 { /** * 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. + * @deprecated Prefer {@linkplain PassDefinition#extracts(LevelTargetBundle, FramePass, DeltaTracker)} */ + @Deprecated(forRemoval = true, since="1.21.10") + default void targets(LevelTargetBundle bundle, FramePass pass) {}; + + /** + * 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. + * @deprecated Prefer {@linkplain PassDefinition#extracts(LevelTargetBundle, FramePass, DeltaTracker)} + */ + @Deprecated(forRemoval = true, since="26.2") default void extracts(LevelTargetBundle bundle, FramePass pass) { targets(bundle, pass); } /** - * Prefer {@link PassDefinition#extracts} + * This method exists to do render state extraction. Your instance of a PassDefinition should have + * locals or some filled record instance that represents the render state created. + * The resulting render state should be consumed by {@linkplain PassDefinition#executes(LevelRenderState)} + * You must also use this to define which targets your pass will bind against. See PassDefinition javadocs for details. */ - @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. - */ - default void executes(LevelRenderState state) { - executes(); + default void extracts(LevelTargetBundle bundle, FramePass pass, DeltaTracker deltaTracker) { + targets(bundle, pass); }; /** - * Use to define what your pass does during the render stage, prefer {@link PassDefinition#executes(LevelRenderState)}. + * Use to define what your pass does during the render stage. This should consume the render state created + * during the extracts phase. */ - @Deprecated(forRemoval = true, since = "1.21.10") - default void executes(){}; + void executes(LevelRenderState state); + + /** + * Use to define what your pass does during the render stage, prefer {@link PassDefinition#executes(LevelRenderState)}. + */ + @Deprecated(forRemoval = true, since = "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 3031c9e835..c8e9ad9a94 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 1c9b4e8cf8..347c962eec 100644 --- a/src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java +++ b/src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java @@ -5,6 +5,7 @@ package net.minecraftforge.debug.client; +import net.minecraft.client.DeltaTracker; import net.minecraft.client.renderer.LevelTargetBundle; import net.minecraft.client.renderer.state.LevelRenderState; import net.minecraftforge.client.FramePassManager; @@ -23,6 +24,8 @@ import net.minecraft.world.phys.AABB; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.framegraph.FramePass; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; @GameTestNamespace("forge") @Mod(RenderFrameLayerTest.MODID) @@ -48,10 +51,11 @@ 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) { FramePassManager.PassDefinition def = new FramePassManager.PassDefinition() { @Override - public void extracts(LevelTargetBundle bundle, FramePass pass) { + public void extracts(LevelTargetBundle bundle, FramePass pass, DeltaTracker dt) { bundle.main = pass.readsAndWrites(bundle.main); } @@ -72,7 +76,7 @@ public class RenderFrameLayerTest extends BaseTestMod { event.addPass(rl(MODID), def); FramePassManager.PassDefinition def2 = new FramePassManager.PassDefinition() { @Override - public void extracts(LevelTargetBundle bundle, FramePass pass) { + public void extracts(@NotNull LevelTargetBundle bundle, FramePass pass, DeltaTracker dt) { bundle.main = pass.readsAndWrites(bundle.main); }