diff --git a/patches/net/minecraft/client/renderer/LevelRenderer.java.patch b/patches/net/minecraft/client/renderer/LevelRenderer.java.patch index 31507412779..a6a9066ab76 100644 --- a/patches/net/minecraft/client/renderer/LevelRenderer.java.patch +++ b/patches/net/minecraft/client/renderer/LevelRenderer.java.patch @@ -230,7 +230,7 @@ if (j < k) { j = k; } -@@ -1449,5 +_,22 @@ +@@ -1449,5 +_,27 @@ public CloudRenderer getCloudRenderer() { return this.cloudRenderer; @@ -251,5 +251,10 @@ + synchronized (this.globalBlockEntities) { + this.globalBlockEntities.forEach(blockEntityConsumer); + } ++ } ++ ++ @org.jetbrains.annotations.ApiStatus.Internal ++ public Iterable getRenderableSections() { ++ return this.visibleSections; } } diff --git a/patches/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java.patch b/patches/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java.patch index 5b7ff9be4fb..e64a447cdc9 100644 --- a/patches/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java.patch +++ b/patches/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java +++ b/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java +@@ -254,7 +_,7 @@ + } + + @OnlyIn(Dist.CLIENT) +- public class RenderSection { ++ public class RenderSection implements net.neoforged.neoforge.client.IRenderableSection { + public static final int SIZE = 16; + public final int index; + public final AtomicReference compiled = new AtomicReference<>( @@ -399,9 +_,10 @@ public SectionRenderDispatcher.RenderSection.CompileTask createCompileTask(RenderRegionCache p_295324_) { @@ -13,6 +22,22 @@ return this.lastRebuildTask; } +@@ -441,6 +_,15 @@ + ); + } + ++ // Neo: start ++ ++ @Override ++ public boolean isEmpty() { ++ return !getCompiled().hasRenderableLayers(); ++ } ++ ++ // Neo: end ++ + @OnlyIn(Dist.CLIENT) + public abstract class CompileTask { + protected final AtomicBoolean isCancelled = new AtomicBoolean(false); @@ -470,10 +_,17 @@ class RebuildTask extends SectionRenderDispatcher.RenderSection.CompileTask { @Nullable diff --git a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java index 50750c45cb1..a57a17ad719 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java @@ -274,7 +274,7 @@ public static void dispatchRenderStage(RenderLevelStageEvent.Stage stage, LevelR var mc = Minecraft.getInstance(); var profiler = Profiler.get(); profiler.push(stage.toString()); - NeoForge.EVENT_BUS.post(new RenderLevelStageEvent(stage, levelRenderer, poseStack, modelViewMatrix, projectionMatrix, renderTick, mc.getDeltaTracker(), camera, frustum)); + NeoForge.EVENT_BUS.post(new RenderLevelStageEvent(stage, levelRenderer, poseStack, modelViewMatrix, projectionMatrix, renderTick, mc.getDeltaTracker(), camera, frustum, levelRenderer.getRenderableSections())); profiler.pop(); } diff --git a/src/main/java/net/neoforged/neoforge/client/IRenderableSection.java b/src/main/java/net/neoforged/neoforge/client/IRenderableSection.java new file mode 100644 index 00000000000..32bd3b6637b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/IRenderableSection.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.phys.AABB; + +/** + * Describes a chunk section that may be rendered on the GPU. + * + * The renderer may choose to reuse a common backing object + * for this interface under the hood (for performance reasons), + * so the {@link IRenderableSection} and any objects its methods + * return are not guaranteed to be immutable or valid after + * exiting the scope in which its provided. + */ +public interface IRenderableSection { + /** + * {@return the block position at the origin of the section} + */ + BlockPos getOrigin(); + + /** + * {@return the bounding box of the section} + */ + AABB getBoundingBox(); + + /** + * {@return true if the compiled section contains no chunk render layers} + */ + boolean isEmpty(); +} diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderLevelStageEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderLevelStageEvent.java index 5a018d69444..5cd53e31f7a 100644 --- a/src/main/java/net/neoforged/neoforge/client/event/RenderLevelStageEvent.java +++ b/src/main/java/net/neoforged/neoforge/client/event/RenderLevelStageEvent.java @@ -8,6 +8,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; import net.minecraft.client.Camera; import net.minecraft.client.DeltaTracker; import net.minecraft.client.renderer.GameRenderer; @@ -20,6 +21,7 @@ import net.neoforged.bus.api.ICancellableEvent; import net.neoforged.fml.LogicalSide; import net.neoforged.fml.event.IModBusEvent; +import net.neoforged.neoforge.client.IRenderableSection; import net.neoforged.neoforge.client.NeoForgeRenderTypes; import net.neoforged.neoforge.common.NeoForge; import org.jetbrains.annotations.Nullable; @@ -44,8 +46,9 @@ public class RenderLevelStageEvent extends Event { private final DeltaTracker partialTick; private final Camera camera; private final Frustum frustum; + private final Iterable renderableSections; - public RenderLevelStageEvent(Stage stage, LevelRenderer levelRenderer, @Nullable PoseStack poseStack, Matrix4f modelViewMatrix, Matrix4f projectionMatrix, int renderTick, DeltaTracker partialTick, Camera camera, Frustum frustum) { + public RenderLevelStageEvent(Stage stage, LevelRenderer levelRenderer, @Nullable PoseStack poseStack, Matrix4f modelViewMatrix, Matrix4f projectionMatrix, int renderTick, DeltaTracker partialTick, Camera camera, Frustum frustum, Iterable renderableSections) { this.stage = stage; this.levelRenderer = levelRenderer; this.poseStack = poseStack != null ? poseStack : new PoseStack(); @@ -55,6 +58,7 @@ public RenderLevelStageEvent(Stage stage, LevelRenderer levelRenderer, @Nullable this.partialTick = partialTick; this.camera = camera; this.frustum = frustum; + this.renderableSections = renderableSections; } /** @@ -121,6 +125,16 @@ public Frustum getFrustum() { return frustum; } + /** + * Returns an iterable of all visible sections. + * + * Calling {@link Iterable#forEach(Consumer)} on the returned iterable allows the underlying renderer + * to optimize how it fetches the visible sections, and is recommended. + */ + public Iterable getRenderableSections() { + return renderableSections; + } + /** * Use to create a custom {@linkplain RenderLevelStageEvent.Stage stages}. * Fired after the LevelRenderer has been created. diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/client/ClientEventTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/client/ClientEventTests.java index 3eed63a3e8d..f239ba7a76a 100644 --- a/tests/src/main/java/net/neoforged/neoforge/debug/client/ClientEventTests.java +++ b/tests/src/main/java/net/neoforged/neoforge/debug/client/ClientEventTests.java @@ -11,6 +11,7 @@ import java.util.Map; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.entity.AbstractHoglinRenderer; import net.minecraft.client.renderer.entity.LivingEntityRenderer; import net.minecraft.client.renderer.entity.MobRenderer; @@ -185,4 +186,48 @@ class TestBrokenHoglinRendererTypeToken extends Type }); }); } + + @TestHolder(description = { "Tests if rendering custom geometry on visible chunks works", "When the message \"gold block\" is sent in chat, gold blocks should render at the origin of every visible section with blocks" }) + static void renderLevelStageWithSectionData(final DynamicTest test) { + test.whenEnabled(listeners -> { + listeners.forge().addListener((final ClientChatEvent chatEvent) -> { + if (chatEvent.getMessage().equalsIgnoreCase("gold block")) { + var player = Minecraft.getInstance().player; + NeoForge.EVENT_BUS.addListener((final RenderLevelStageEvent event) -> { + if (event.getStage() == RenderLevelStageEvent.Stage.AFTER_SOLID_BLOCKS) { + var buffer = Minecraft.getInstance().renderBuffers().bufferSource().getBuffer(Sheets.solidBlockSheet()); + var randomSource = new SingleThreadedRandomSource(0); + var state = Blocks.GOLD_BLOCK.defaultBlockState(); + var stack = event.getPoseStack(); + var camera = event.getCamera().getPosition(); + event.getRenderableSections().forEach(section -> { + if (section.isEmpty()) { + return; + } + + stack.pushPose(); + stack.translate( + section.getOrigin().getX() - camera.x, + section.getOrigin().getY() - camera.y, + section.getOrigin().getZ() - camera.z); + Minecraft.getInstance().getBlockRenderer().renderBatched( + state, + section.getOrigin(), + Minecraft.getInstance().level, + stack, + buffer, + false, + randomSource, + ModelData.EMPTY, + RenderType.solid()); + stack.popPose(); + + test.pass(); + }); + } + }); + } + }); + }); + } }