diff --git a/patches/minecraft/net/minecraft/client/gui/GuiGraphics.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiGraphics.java.patch index 0935af9335..4fb3cb3257 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiGraphics.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/GuiGraphics.java.patch @@ -127,3 +127,18 @@ j1 += clienttooltipcomponent2.getHeight(p_283128_) + (j2 == 0 ? 2 : 0); } +@@ -1217,6 +_,14 @@ + + public void submitProfilerChartRenderState(List p_408565_, int p_410147_, int p_406156_, int p_407365_, int p_408180_) { + this.guiRenderState.submitPicturesInPictureState(new GuiProfilerChartRenderState(p_408565_, p_410147_, p_406156_, p_407365_, p_408180_, this.scissorStack.peek())); ++ } ++ ++ public GuiRenderState getRenderState() { ++ return this.guiRenderState; ++ } ++ ++ public GuiGraphics.ScissorStack getScissorStack() { ++ return this.scissorStack; + } + + @OnlyIn(Dist.CLIENT) diff --git a/patches/minecraft/net/minecraft/client/gui/render/GuiRenderer.java.patch b/patches/minecraft/net/minecraft/client/gui/render/GuiRenderer.java.patch new file mode 100644 index 0000000000..b0f4689786 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/gui/render/GuiRenderer.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/client/gui/render/GuiRenderer.java ++++ b/net/minecraft/client/gui/render/GuiRenderer.java +@@ -134,6 +_,7 @@ + builder.put((Class)pictureinpicturerenderer.getRenderStateClass(), pictureinpicturerenderer); + } + ++ net.minecraftforge.client.ForgeHooksClient.onRegisterPictureInPictureRenderers(p_410565_, p_410441_, builder); + this.pictureInPictureRenderers = builder.buildOrThrow(); + } + diff --git a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java index 317523d375..ad8ed37dd6 100644 --- a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java +++ b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java @@ -26,6 +26,8 @@ import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.ChatComponent; import net.minecraft.client.gui.components.LerpingBossEvent; +import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; +import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState; import net.minecraft.client.gui.screens.ConfirmScreen; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; @@ -96,6 +98,7 @@ import net.minecraftforge.client.event.ClientChatEvent; import net.minecraftforge.client.event.ClientChatReceivedEvent; import net.minecraftforge.client.event.ClientPauseChangeEvent; import net.minecraftforge.client.event.ClientPlayerChangeGameTypeEvent; +import net.minecraftforge.client.event.RegisterPictureInPictureRendererEvent; import net.minecraftforge.client.event.SystemMessageReceivedEvent; import net.minecraftforge.client.event.CustomizeGuiOverlayEvent; import net.minecraftforge.client.event.EntityRenderersEvent; @@ -151,6 +154,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -596,6 +600,12 @@ public class ForgeHooksClient { ModLoader.get().postEvent(new RegisterKeyMappingsEvent(options)); } + public static void onRegisterPictureInPictureRenderers(List> renderers, + MultiBufferSource.BufferSource bufferSource, + ImmutableMap.Builder, PictureInPictureRenderer> builder) { + RegisterPictureInPictureRendererEvent.BUS.post(new RegisterPictureInPictureRendererEvent(renderers, bufferSource, builder)); + } + @Nullable public static Component onClientChat(ChatType.Bound boundChatType, Component message, UUID sender) { ClientChatReceivedEvent event = new ClientChatReceivedEvent(boundChatType, message, sender); diff --git a/src/main/java/net/minecraftforge/client/event/RegisterPictureInPictureRendererEvent.java b/src/main/java/net/minecraftforge/client/event/RegisterPictureInPictureRendererEvent.java new file mode 100644 index 0000000000..ad1df59263 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/event/RegisterPictureInPictureRendererEvent.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.client.event; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; +import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraftforge.eventbus.api.bus.EventBus; +import net.minecraftforge.eventbus.api.event.MutableEvent; +import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing; +import net.minecraftforge.fml.LogicalSide; +import org.jetbrains.annotations.ApiStatus; + +import java.util.HashSet; +import java.util.List; + +/** + * Used for registering custom picture in picture renderers. + *

+ * Fired at the beginning of the loading screen when starting minecraft. + * Cannot be used to replace vanilla picture in picture renderers. + * This event is fired only on the {@linkplain LogicalSide#CLIENT logical client} + */ +public final class RegisterPictureInPictureRendererEvent extends MutableEvent implements SelfDestructing { + public static final EventBus BUS = EventBus.create(RegisterPictureInPictureRendererEvent.class); + + private final List> renderers; + private final MultiBufferSource.BufferSource bufferSource; + private final ImmutableMap.Builder, PictureInPictureRenderer> builder; + + @ApiStatus.Internal + public RegisterPictureInPictureRendererEvent(List> renderers, MultiBufferSource.BufferSource bufferSource, ImmutableMap.Builder, PictureInPictureRenderer> builder) { + this.renderers = renderers; + this.bufferSource = bufferSource; + this.builder = builder; + } + + public MultiBufferSource.BufferSource getBufferSource() { + return bufferSource; + } + + public void register(PictureInPictureRenderer renderer) { + var seen = HashSet.newHashSet(renderers.size()); + for (var r : renderers) + seen.add(r.getRenderStateClass()); + var key = renderer.getRenderStateClass(); + if (seen.add(key)) + this.builder.put(key, renderer); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index e600050b6e..058e52cddb 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -344,6 +344,8 @@ public net.minecraft.client.data.models.ModelProvider$ItemInfoCollector public net.minecraft.client.data.models.ModelProvider$ItemInfoCollector ()V # constructor public net.minecraft.client.data.models.ModelProvider$SimpleModelCollector public net.minecraft.client.data.models.ModelProvider$SimpleModelCollector ()V # constructor +public net.minecraft.client.gui.GuiGraphics$ScissorStack +public net.minecraft.client.gui.GuiGraphics$ScissorStack ()V # constructor protected net.minecraft.client.gui.components.AbstractButton f_290895_ # SPRITES protected net.minecraft.client.gui.components.AbstractSelectionList$Entry f_93521_ # list protected net.minecraft.client.gui.components.AbstractSliderButton m_293290_()Lnet/minecraft/resources/ResourceLocation; # getHandleSprite diff --git a/src/test/generated/pip_test/data/forge/test_instance/pip_test/was_registered.json b/src/test/generated/pip_test/data/forge/test_instance/pip_test/was_registered.json new file mode 100644 index 0000000000..7c51365443 --- /dev/null +++ b/src/test/generated/pip_test/data/forge/test_instance/pip_test/was_registered.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:pip_test/was_registered", + "max_ticks": 100, + "structure": "forge:empty3x3x3" +} \ No newline at end of file diff --git a/src/test/java/net/minecraftforge/debug/client/PictureInPictureTest.java b/src/test/java/net/minecraftforge/debug/client/PictureInPictureTest.java new file mode 100644 index 0000000000..f6d675a01f --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/client/PictureInPictureTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.navigation.ScreenPosition; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; +import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraftforge.client.event.RegisterPictureInPictureRendererEvent; + +import net.minecraftforge.client.event.ScreenEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.gametest.GameTest; +import net.minecraftforge.gametest.GameTestNamespace; +import net.minecraftforge.test.BaseTestMod; + +import java.awt.Color; +import java.util.Map; + +@GameTestNamespace("forge") +@Mod(PictureInPictureTest.MODID) +public class PictureInPictureTest extends BaseTestMod { + public static final String MODID = "pip_test"; + private static int RENDER_FRAMES = 0; + + public PictureInPictureTest(FMLJavaModLoadingContext context) { + super(context, false, false); + RegisterPictureInPictureRendererEvent.BUS.addListener(this::registerTestPip); + ScreenEvent.BackgroundRendered.BUS.addListener(this::onScreenBackground); + } + + @GameTest + public static void was_registered(GameTestHelper helper) throws IllegalAccessException, NoSuchFieldException { + // Get the GameRenderer instance + var gameRenderer = Minecraft.getInstance().gameRenderer; + + // Get the guiRenderer field from GameRenderer class + var guiRendererField = gameRenderer.getClass().getDeclaredField("guiRenderer"); + guiRendererField.setAccessible(true); + + // Get the guiRenderer instance + var guiRenderer = guiRendererField.get(gameRenderer); + + // Get the pictureInPictureRenderers field from GuiRenderer class + var pictureInPictureRenderersField = guiRenderer.getClass().getDeclaredField("pictureInPictureRenderers"); + pictureInPictureRenderersField.setAccessible(true); + + // Get the pictureInPictureRenderers map + Map pictureInPictureRenderers = (Map) pictureInPictureRenderersField.get(guiRenderer); + + helper.assertTrue(pictureInPictureRenderers != null, "PictureInPictureRenderer map was null"); + helper.assertTrue(pictureInPictureRenderers.containsKey(TestPipRendererState.class), "PictureInPictureRenderer was not registered"); + helper.succeed(); + // This is just a example feedback, doesn't matter too much so just show it when the test is run. + RENDER_FRAMES = 100; + } + + // this will draw a blue box outline in red in the top left corner of a screen. + private void onScreenBackground(ScreenEvent.BackgroundRendered event) { + if (RENDER_FRAMES <= 0) + return; + RENDER_FRAMES--; + var window = Minecraft.getInstance().getWindow(); + event.getGuiGraphics().getRenderState().submitPicturesInPictureState(new TestPipRendererState( + event.getGuiGraphics(), + 10, + 10, + 20, + 20, + new ScreenRectangle(new ScreenPosition(0, 0), window.getGuiScaledWidth(), window.getGuiScaledHeight()))); + } + + private void registerTestPip(RegisterPictureInPictureRendererEvent event) { + event.register(new TestPipRenderer(event.getBufferSource())); + } + + static class TestPipRenderer extends PictureInPictureRenderer { + protected TestPipRenderer(MultiBufferSource.BufferSource bufferSource) { + super(bufferSource); + } + + @Override + public Class getRenderStateClass() { + return TestPipRendererState.class; + } + + @Override + protected void renderToTexture(TestPipRendererState state, PoseStack poseStack) { + var graphics = state.graphics(); + int red = Color.red.getRGB(); + int x1 = state.x0 - 1; + int y1 = state.y0 - 1; + int x2 = x1 + state.x1() + 2; + int y2 = y1 + state.y1() + 2; + + graphics.fill(x1 + 2, y1+ 2, x2, y2, Color.BLUE.getRGB()); + graphics.fill(x1, y1, x2, y1 + 2, red); + graphics.fill(x1, y2, x2, y2 + 2, red); + graphics.fill(x1, y1, x1 + 2, y2, red); + graphics.fill(x2, y1, x2 + 2, y2 + 2, red); + } + + @Override + protected String getTextureLabel() { + return "test_pip_renderer"; + } + } + + record TestPipRendererState(GuiGraphics graphics, int x0, int y0, int x1, int y1, ScreenRectangle scissorArea, ScreenRectangle bounds) implements PictureInPictureRenderState { + public TestPipRendererState(GuiGraphics graphics, int x0, int y0, int x1, int y1, ScreenRectangle scissorArea) { + this(graphics, x0, y0, x1, y1, scissorArea, PictureInPictureRenderState.getBounds(x0, y0, x1, y1, scissorArea)); + } + + @Override + public float scale() { + return 32; + } + } +} diff --git a/src/test/resources/pip_test/META-INF/mods.toml b/src/test/resources/pip_test/META-INF/mods.toml new file mode 100644 index 0000000000..c361869e8e --- /dev/null +++ b/src/test/resources/pip_test/META-INF/mods.toml @@ -0,0 +1 @@ +clientSideOnly=true \ No newline at end of file