From 39f41fcda9ab667c1346fd43e32539db290d993a Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 25 Jul 2025 13:29:42 -0400 Subject: [PATCH] Add event for adding framepasses (#10502) --- .../client/renderer/LevelRenderer.java.patch | 8 ++ .../client/event/AddFramePassEvent.java | 56 +++++++++++ .../client/event/ForgeEventFactoryClient.java | 6 ++ .../debug/client/RenderFrameLayerTest.java | 96 +++++++++++++++++++ .../META-INF/mods.toml | 1 + 5 files changed, 167 insertions(+) create mode 100644 src/main/java/net/minecraftforge/client/event/AddFramePassEvent.java create mode 100644 src/test/java/net/minecraftforge/debug/client/RenderFrameLayerTest.java create mode 100644 src/test/resources/render_frame_layer_test/META-INF/mods.toml 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