From c26373137e06bd0bc0298622eb2712381b86c3d1 Mon Sep 17 00:00:00 2001 From: grondag <grondag@users.noreply.github.com> Date: Wed, 30 Dec 2020 08:44:23 -0800 Subject: [PATCH] Add WorldRenderEvents (#1182) * Add WorldRenderEvents * Fix typos * Incorporate PR feedback * Simplify context and block outline events * Update implementation * Ensure the BLOCK_OUTLINE mixin does nothing if BEFORE_BLOCK_OUTLINE mixin is disabled * Document event order in class header * Update fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com> * Add environment tag to nested type * More envionment tags Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com> --- fabric-rendering-v1/build.gradle | 2 +- .../rendering/v1/WorldRenderContext.java | 138 ++++++++ .../rendering/v1/WorldRenderEvents.java | 327 ++++++++++++++++++ .../rendering/WorldRenderContextImpl.java | 221 ++++++++++++ .../client/rendering/MixinWorldRenderer.java | 133 +++++++ 5 files changed, 820 insertions(+), 1 deletion(-) create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderContext.java create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/WorldRenderContextImpl.java diff --git a/fabric-rendering-v1/build.gradle b/fabric-rendering-v1/build.gradle index 9c7afb287..0f6f68051 100644 --- a/fabric-rendering-v1/build.gradle +++ b/fabric-rendering-v1/build.gradle @@ -1,5 +1,5 @@ archivesBaseName = "fabric-rendering-v1" -version = getSubprojectVersion(project, "1.4.0") +version = getSubprojectVersion(project, "1.5.0") moduleDependencies(project, [ 'fabric-api-base' diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderContext.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderContext.java new file mode 100644 index 000000000..4eebdd015 --- /dev/null +++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderContext.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.rendering.v1; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.Frustum; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Matrix4f; +import net.minecraft.util.profiler.Profiler; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +/** + * Except as noted below, the properties exposed here match the parameters passed to + * {@link WorldRenderer#render(MatrixStack, float, long, boolean, Camera, GameRenderer, LightmapTextureManager, Matrix4f)}. + */ +@Environment(EnvType.CLIENT) +public interface WorldRenderContext { + /** + * The world renderer instance doing the rendering and invoking the event. + * + * @return WorldRenderer instance invoking the event + */ + WorldRenderer worldRenderer(); + + MatrixStack matrixStack(); + + float tickDelta(); + + long limitTime(); + + boolean blockOutlines(); + + Camera camera(); + + GameRenderer gameRenderer(); + + LightmapTextureManager lightmapTextureManager(); + + Matrix4f projectionMatrix(); + + /** + * Convenient access to {WorldRenderer.world}. + * + * @return world renderer's client world instance + */ + ClientWorld world(); + + /** + * Convenient access to game performance profiler. + * + * @return the active profiler + */ + Profiler profiler(); + + /** + * Test to know if "fabulous" graphics mode is enabled. + * + * <p>Use this for renders that need to render on top of all translucency to activate or deactivate different + * event handlers to get optimal depth testing results. When fabulous is off, it may be better to render + * during {@code WorldRenderLastCallback} after clouds and weather are drawn. Conversely, when fabulous mode is on, + * it may be better to draw during {@code WorldRenderPostTranslucentCallback}, before the fabulous mode composite + * shader runs, depending on which translucent buffer is being targeted. + * + * @return {@code true} when "fabulous" graphics mode is enabled. + */ + boolean advancedTranslucency(); + + /** + * The {@code VertexConsumerProvider} instance being used by the world renderer for most non-terrain renders. + * Generally this will be better for most use cases because quads for the same layer can be buffered + * incrementally and then drawn all at once by the world renderer. + * + * <p>IMPORTANT - all vertex coordinates sent to consumers should be relative to the camera to + * be consistent with other quads emitted by the world renderer and other mods. If this isn't + * possible, caller should use a separate "immediate" instance. + * + * <p>This property is {@code null} before {@link WorldRenderEvents#BEFORE_ENTITIES} and after + * {@link WorldRenderEvents#BEFORE_DEBUG_RENDER} because the consumer buffers are not available before or + * drawn after that in vanilla world rendering. Renders that cannot draw in one of the supported events + * must be drawn directly to the frame buffer, preferably in {@link WorldRenderEvents#LAST} to avoid being + * overdrawn or cleared. + */ + @Nullable VertexConsumerProvider consumers(); + + /** + * View frustum, after it is initialized. Will be {@code null} during + * {@link WorldRenderEvents#START}. + */ + @Nullable Frustum frustum(); + + /** + * Used in {@code BLOCK_OUTLINE} to convey the parameters normally sent to + * {@code WorldRenderer.drawBlockOutline}. + */ + @Environment(EnvType.CLIENT) + public interface BlockOutlineContext { + VertexConsumer vertexConsumer(); + + Entity entity(); + + double cameraX(); + + double cameraY(); + + double cameraZ(); + + BlockPos blockPos(); + + BlockState blockState(); + } +} diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java new file mode 100644 index 000000000..7d45d9d99 --- /dev/null +++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/WorldRenderEvents.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.rendering.v1; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.util.hit.HitResult; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext.BlockOutlineContext; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Mods should use these events to introduce custom rendering during {@link WorldRenderer#render(net.minecraft.client.util.math.MatrixStack, float, long, boolean, net.minecraft.client.render.Camera, net.minecraft.client.render.GameRenderer, net.minecraft.client.render.LightmapTextureManager, net.minecraft.util.math.Matrix4f)} + * without adding complicated and conflict-prone injections there. Using these events also enables 3rd-party renderers + * that make large-scale changes to rendering maintain compatibility by calling any broken event invokers directly. + * + * <p>The order of events each frame is as follows: + * <ul><li>START + * <li>AFTER_SETUP + * <li>BEFORE_ENTITIES + * <li>AFTER_ENTITIES + * <li>BEFORE_BLOCK_OUTLINE + * <li>BLOCK_OUTLINE (If not cancelled in BEFORE_BLOCK_OUTLINE) + * <li>BEFORE_DEBUG_RENDER + * <li>AFTER_TRANSLUCENT + * <li>LAST + * <li>END</ul> + * + * <p>These events are not dependent on the Fabric rendering API or Indigo but work when those are present. + */ +@Environment(EnvType.CLIENT) +public final class WorldRenderEvents { + private WorldRenderEvents() { } + + /** + * Called before world rendering executes. Input parameters are available but frustum is not. + * Use this event instead of injecting to the HEAD of {@link WorldRenderer#render} to avoid + * compatibility problems with 3rd-party renderer implementations. + * + * <p>Use for setup of state that is needed during the world render call that + * does not depend on the view frustum. + */ + public static final Event<Start> START = EventFactory.createArrayBacked(Start.class, context -> { }, callbacks -> context -> { + for (final Start callback : callbacks) { + callback.onStart(context); + } + }); + + /** + * Called after view Frustum is computed and all render chunks to be rendered are + * identified and rebuilt but before chunks are uploaded to GPU. + * + * <p>Use for setup of state that depends on view frustum. + */ + public static final Event<AfterSetup> AFTER_SETUP = EventFactory.createArrayBacked(AfterSetup.class, context -> { }, callbacks -> context -> { + for (final AfterSetup callback : callbacks) { + callback.afterSetup(context); + } + }); + + /** + * Called after the Solid, Cutout and Cutout Mipped terrain layers have been output to the framebuffer. + * + * <p>Use to render non-translucent terrain to the framebuffer. + * + * <p>Note that 3rd-party renderers may combine these passes or otherwise alter the + * rendering pipeline for sake of performance or features. This can break direct writes to the + * framebuffer. Use this event for cases that cannot be satisfied by FabricBakedModel, + * BlockEntityRenderer or other existing abstraction. If at all possible, use an existing terrain + * RenderLayer instead of outputting to the framebuffer directly with GL calls. + * + * <p>The consumer is responsible for setup and tear down of GL state appropriate for the intended output. + * + * <p>Because solid and cutout quads are depth-tested, order of output does not matter except to improve + * culling performance, which should not be significant after primary terrain rendering. This means + * mods that currently hook calls to individual render layers can simply execute them all at once when + * the event is called. + * + * <p>This event fires before entities and block entities are rendered and may be useful to prepare them. + */ + public static final Event<BeforeEntities> BEFORE_ENTITIES = EventFactory.createArrayBacked(BeforeEntities.class, context -> { }, callbacks -> context -> { + for (final BeforeEntities callback : callbacks) { + callback.beforeEntities(context); + } + }); + + /** + * Called after entities are rendered and solid entity layers + * have been drawn to the main frame buffer target, before + * block entity rendering begins. + * + * <p>Use for global block entity render setup, or + * to append block-related quads to the entity consumers using the + * {@VertexConsumerProvider} from the provided context. This + * will generally give better (if not perfect) results + * for non-terrain translucency vs. drawing directly later on. + */ + public static final Event<AfterEntities> AFTER_ENTITIES = EventFactory.createArrayBacked(AfterEntities.class, context -> { }, callbacks -> context -> { + for (final AfterEntities callback : callbacks) { + callback.afterEntities(context); + } + }); + + /** + * Called before default block outline rendering and before checks are + * done to determine if it should happen. Can optionally cancel the default + * rendering but all event handlers will always be called. + * + * <p>Use this to decorate or replace the default block outline rendering + * for specific modded blocks or when the need for a block outline render + * would not be detected. Normally, outline rendering will not happen for + * entities, fluids, or other game objects that do not register a block-type hit. + * + * <p>Returning false from any event subscriber will cancel the default block + * outline render and suppress the {@code BLOCK_RENDER} event. This has no + * effect on other subscribers to this event - all subscribers will always be called. + * Canceling here is appropriate when there is still a valid block hit (with a fluid, + * for example) and you don't want the block outline render to appear. + * + * <p>This event should NOT be used for general-purpose replacement of + * the default block outline rendering because it will interfere with mod-specific + * renders. Mods that replace the default block outline for specific blocks + * should instead subscribe to {@link #BLOCK_OUTLINE}. + */ + public static final Event<BeforeBlockOutline> BEFORE_BLOCK_OUTLINE = EventFactory.createArrayBacked(BeforeBlockOutline.class, (context, hit) -> true, callbacks -> (context, hit) -> { + boolean shouldRender = true; + + for (final BeforeBlockOutline callback : callbacks) { + if (!callback.beforeBlockOutline(context, hit)) { + shouldRender = false; + } + } + + return shouldRender; + }); + + /** + * Called after block outline render checks are made and before the + * default block outline render runs. Will NOT be called if the default outline + * render was cancelled in {@link #BEFORE_BLOCK_OUTLINE}. + * + * <p>Use this to replace the default block outline rendering for specific blocks that + * need special outline rendering or to add information that doesn't replace the block outline. + * Subscribers cannot affect each other or detect if another subscriber is also + * handling a specific block. If two subscribers render for the same block, both + * renders will appear. + * + * <p>Returning false from any event subscriber will cancel the default block + * outline render. This has no effect on other subscribers to this event - + * all subscribers will always be called. Canceling is appropriate when the + * subscriber replacing the default block outline render for a specific block. + * + * <p>This event is not appropriate for mods that replace the default block + * outline render for <em>all</em> blocks because all event subscribers will + * always render - only the default outline render can be cancelled. That should + * be accomplished by mixin to the block outline render routine itself, typically + * by targeting {@link WorldRenderer#drawShapeOutline}. + */ + public static final Event<BlockOutline> BLOCK_OUTLINE = EventFactory.createArrayBacked(BlockOutline.class, (worldRenderContext, blockOutlieContext) -> true, callbacks -> (worldRenderContext, blockOutlieContext) -> { + boolean shouldRender = true; + + for (final BlockOutline callback : callbacks) { + if (!callback.onBlockOutline(worldRenderContext, blockOutlieContext)) { + shouldRender = false; + } + } + + return shouldRender; + }); + + /** + * Called before vanilla debug renderers are output to the framebuffer. + * This happens very soon after entities, block breaking and most other + * non-translucent renders but before translucency is drawn. + * + * <p>Unlike most other events, renders in this event are expected to be drawn + * directly and immediately to the framebuffer. The OpenGL render state view + * matrix will be transformed to match the camera view before the event is called. + * + * <p>Use to drawn lines, overlays and other content similar to vanilla + * debug renders. + */ + public static final Event<DebugRender> BEFORE_DEBUG_RENDER = EventFactory.createArrayBacked(DebugRender.class, context -> { }, callbacks -> context -> { + for (final DebugRender callback : callbacks) { + callback.beforeDebugRender(context); + } + }); + + /** + * Called after entity, terrain, and particle translucent layers have been + * drawn to the framebuffer but before translucency combine has happened + * in fabulous mode. + * + * <p>Use for drawing overlays or other effects on top of those targets + * (or the main target when fabulous isn't active) before clouds and weather + * are drawn. However, note that {@code WorldRenderPostEntityCallback} will + * offer better results in most use cases. + * + * <p>Vertex consumers are not available in this event because all buffered quads + * are drawn before this event is called. Any rendering here must be drawn + * directly to the frame buffer. The render state matrix will not include + * camera transformation, so {@link #LAST} may be preferable if that is wanted. + */ + public static final Event<AfterTranslucent> AFTER_TRANSLUCENT = EventFactory.createArrayBacked(AfterTranslucent.class, context -> { }, callbacks -> context -> { + for (final AfterTranslucent callback : callbacks) { + callback.afterTranslucent(context); + } + }); + + /** + * Called after all framebuffer writes are complete but before all world + * rendering is torn down. + * + * <p>Unlike most other events, renders in this event are expected to be drawn + * directly and immediately to the framebuffer. The OpenGL render state view + * matrix will be transformed to match the camera view before the event is called. + * + * <p>Use to draw content that should appear on top of the world before hand and GUI rendering occur. + */ + public static final Event<Last> LAST = EventFactory.createArrayBacked(Last.class, context -> { }, callbacks -> context -> { + for (final Last callback : callbacks) { + callback.onLast(context); + } + }); + + /** + * Called after all world rendering is complete and changes to GL state are unwound. + * + * <p>Use to draw overlays that handle GL state management independently or to tear + * down transient state in event handlers or as a hook that precedes hand/held item + * and GUI rendering. + */ + public static final Event<End> END = EventFactory.createArrayBacked(End.class, context -> { }, callbacks -> context -> { + for (final End callback : callbacks) { + callback.onEnd(context); + } + }); + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface Start { + void onStart(WorldRenderContext context); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface AfterSetup { + void afterSetup(WorldRenderContext context); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface BeforeEntities { + void beforeEntities(WorldRenderContext context); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface AfterEntities { + void afterEntities(WorldRenderContext context); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface BeforeBlockOutline { + /** + * Event signature for {@link WorldRenderEvents#BEFORE_BLOCK_OUTLINE}. + * + * @param context Access to state and parameters available during world rendering. + * @param hitResult The game object currently under the crosshair target. + * Normally equivalent to {@link MinecraftClient#crosshairTarget}. Provided for convenience. + * @return true if vanilla block outline rendering should happen. + * Returning false prevents {@link WorldRenderEvents#BLOCK_OUTLINE} from invoking + * and also skips the vanilla block outline render, but has no effect on other subscribers to this event. + */ + boolean beforeBlockOutline(WorldRenderContext context, @Nullable HitResult hitResult); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface BlockOutline { + boolean onBlockOutline(WorldRenderContext worldRenderContext, BlockOutlineContext blockOutlieContext); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface DebugRender { + void beforeDebugRender(WorldRenderContext context); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface AfterTranslucent { + void afterTranslucent(WorldRenderContext context); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface Last { + void onLast(WorldRenderContext context); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface End { + void onEnd(WorldRenderContext context); + } +} diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/WorldRenderContextImpl.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/WorldRenderContextImpl.java new file mode 100644 index 000000000..4d47cedda --- /dev/null +++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/WorldRenderContextImpl.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.rendering; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.Frustum; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Matrix4f; +import net.minecraft.util.profiler.Profiler; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; + +@Environment(EnvType.CLIENT) +public final class WorldRenderContextImpl implements WorldRenderContext.BlockOutlineContext, WorldRenderContext { + private WorldRenderer worldRenderer; + private MatrixStack matrixStack; + private float tickDelta; + private long limitTime; + private boolean blockOutlines; + private Camera camera; + private Frustum frustum; + private GameRenderer gameRenderer; + private LightmapTextureManager lightmapTextureManager; + private Matrix4f projectionMatrix; + private VertexConsumerProvider consumers; + private Profiler profiler; + private boolean advancedTranslucency; + private ClientWorld world; + + private VertexConsumer vertexConsumer; + private Entity entity; + private double cameraX; + private double cameraY; + private double cameraZ; + private BlockPos blockPos; + private BlockState blockState; + + public boolean renderBlockOutline = true; + + public void prepare( + WorldRenderer worldRenderer, + MatrixStack matrixStack, + float tickDelta, + long limitTime, + boolean blockOutlines, + Camera camera, + GameRenderer gameRenderer, + LightmapTextureManager lightmapTextureManager, + Matrix4f projectionMatrix, + VertexConsumerProvider consumers, + Profiler profiler, + boolean advancedTranslucency, + ClientWorld world + ) { + this.worldRenderer = worldRenderer; + this.matrixStack = matrixStack; + this.tickDelta = tickDelta; + this.limitTime = limitTime; + this.blockOutlines = blockOutlines; + this.camera = camera; + this.gameRenderer = gameRenderer; + this.lightmapTextureManager = lightmapTextureManager; + this.projectionMatrix = projectionMatrix; + this.consumers = consumers; + this.profiler = profiler; + this.advancedTranslucency = advancedTranslucency; + this.world = world; + } + + public void setFrustum(Frustum frustum) { + this.frustum = frustum; + } + + public void prepareBlockOutline( + VertexConsumer vertexConsumer, + Entity entity, + double cameraX, + double cameraY, + double cameraZ, + BlockPos blockPos, + BlockState blockState + ) { + this.vertexConsumer = vertexConsumer; + this.entity = entity; + this.cameraX = cameraX; + this.cameraY = cameraY; + this.cameraZ = cameraZ; + this.blockPos = blockPos; + this.blockState = blockState; + } + + @Override + public WorldRenderer worldRenderer() { + return worldRenderer; + } + + @Override + public MatrixStack matrixStack() { + return matrixStack; + } + + @Override + public float tickDelta() { + return tickDelta; + } + + @Override + public long limitTime() { + return limitTime; + } + + @Override + public boolean blockOutlines() { + return blockOutlines; + } + + @Override + public Camera camera() { + return camera; + } + + @Override + public Matrix4f projectionMatrix() { + return projectionMatrix; + } + + @Override + public ClientWorld world() { + return world; + } + + @Override + public Frustum frustum() { + return frustum; + } + + @Override + public VertexConsumerProvider consumers() { + return consumers; + } + + @Override + public GameRenderer gameRenderer() { + return gameRenderer; + } + + @Override + public LightmapTextureManager lightmapTextureManager() { + return lightmapTextureManager; + } + + @Override + public Profiler profiler() { + return profiler; + } + + @Override + public boolean advancedTranslucency() { + return advancedTranslucency; + } + + @Override + public VertexConsumer vertexConsumer() { + return vertexConsumer; + } + + @Override + public Entity entity() { + return entity; + } + + @Override + public double cameraX() { + return cameraX; + } + + @Override + public double cameraY() { + return cameraY; + } + + @Override + public double cameraZ() { + return cameraZ; + } + + @Override + public BlockPos blockPos() { + return blockPos; + } + + @Override + public BlockState blockState() { + return blockState; + } +} diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinWorldRenderer.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinWorldRenderer.java index 07ee7d968..dacc11957 100644 --- a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinWorldRenderer.java +++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinWorldRenderer.java @@ -17,16 +17,149 @@ package net.fabricmc.fabric.mixin.client.rendering; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.ShaderEffect; +import net.minecraft.client.render.BufferBuilderStorage; +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.Frustum; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Matrix4f; import net.fabricmc.fabric.api.client.rendering.v1.InvalidateRenderStateCallback; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.fabricmc.fabric.impl.client.rendering.WorldRenderContextImpl; @Mixin(WorldRenderer.class) public abstract class MixinWorldRenderer { + @Shadow private BufferBuilderStorage bufferBuilders; + @Shadow private ClientWorld world; + @Shadow private ShaderEffect transparencyShader; + @Shadow private MinecraftClient client; + @Unique private final WorldRenderContextImpl context = new WorldRenderContextImpl(); + @Unique private boolean didRenderParticles; + + @Inject(method = "render", at = @At("HEAD")) + private void beforeRender(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, CallbackInfo ci) { + context.prepare((WorldRenderer) (Object) this, matrices, tickDelta, limitTime, renderBlockOutline, camera, gameRenderer, lightmapTextureManager, matrix4f, bufferBuilders.getEntityVertexConsumers(), world.getProfiler(), transparencyShader != null, world); + WorldRenderEvents.START.invoker().onStart(context); + didRenderParticles = false; + } + + @Inject(method = "setupTerrain", at = @At("RETURN")) + private void afterTerrainSetup(Camera camera, Frustum frustum, boolean hasForcedFrustum, int frame, boolean spectator, CallbackInfo ci) { + context.setFrustum(frustum); + WorldRenderEvents.AFTER_SETUP.invoker().afterSetup(context); + } + + @Inject( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/WorldRenderer;renderLayer(Lnet/minecraft/client/render/RenderLayer;Lnet/minecraft/client/util/math/MatrixStack;DDD)V", + ordinal = 2, + shift = Shift.AFTER + ) + ) + private void afterTerrainSolid(CallbackInfo ci) { + WorldRenderEvents.BEFORE_ENTITIES.invoker().beforeEntities(context); + } + + @Inject(method = "render", at = @At(value = "CONSTANT", args = "stringValue=blockentities", ordinal = 0)) + private void afterEntities(CallbackInfo ci) { + WorldRenderEvents.AFTER_ENTITIES.invoker().afterEntities(context); + } + + @Inject( + method = "render", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/MinecraftClient;crosshairTarget:Lnet/minecraft/util/hit/HitResult;", + shift = At.Shift.AFTER, + ordinal = 1 + ) + ) + private void beforeRenderOutline(CallbackInfo ci) { + context.renderBlockOutline = WorldRenderEvents.BEFORE_BLOCK_OUTLINE.invoker().beforeBlockOutline(context, client.crosshairTarget); + } + + @Inject(method = "drawBlockOutline", at = @At("HEAD"), cancellable = true) + private void onDrawBlockOutline(MatrixStack matrixStack, VertexConsumer vertexConsumer, Entity entity, double cameraX, double cameraY, double cameraZ, BlockPos blockPos, BlockState blockState, CallbackInfo ci) { + if (!context.renderBlockOutline) { + // Was cancelled before we got here, so do not + // fire the BLOCK_OUTLINE event per contract of the API. + ci.cancel(); + } else { + context.prepareBlockOutline(vertexConsumer, entity, cameraX, cameraY, cameraZ, blockPos, blockState); + + if (!WorldRenderEvents.BLOCK_OUTLINE.invoker().onBlockOutline(context, context)) { + ci.cancel(); + } + } + } + + @Inject( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/debug/DebugRenderer;render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;DDD)V", + ordinal = 0 + ) + ) + private void beforeDebugRender(CallbackInfo ci) { + WorldRenderEvents.BEFORE_DEBUG_RENDER.invoker().beforeDebugRender(context); + } + + @Inject( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/particle/ParticleManager;renderParticles(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;Lnet/minecraft/client/render/LightmapTextureManager;Lnet/minecraft/client/render/Camera;F)V" + ) + ) + private void onRenderParticles(CallbackInfo ci) { + // set a flag so we know the next pushMatrix call is after particles + didRenderParticles = true; + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;pushMatrix()V")) + private void beforeClouds(CallbackInfo ci) { + if (didRenderParticles) { + didRenderParticles = false; + WorldRenderEvents.AFTER_TRANSLUCENT.invoker().afterTranslucent(context); + } + } + + @Inject( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/WorldRenderer;renderChunkDebugInfo(Lnet/minecraft/client/render/Camera;)V" + ) + ) + private void onChunkDebugRender(CallbackInfo ci) { + WorldRenderEvents.LAST.invoker().onLast(context); + } + + @Inject(method = "render", at = @At("RETURN")) + private void afterRender(CallbackInfo ci) { + WorldRenderEvents.END.invoker().onEnd(context); + } + @Inject(method = "reload", at = @At("HEAD")) private void onReload(CallbackInfo ci) { InvalidateRenderStateCallback.EVENT.invoker().onInvalidate();