Skip to content

Commit

Permalink
Fix block outline rendering (#4286)
Browse files Browse the repository at this point in the history
* Fix block outline rendering

- Move outline event invoker to before either normal or high contrast outlines are rendered
- Remove outline layer reset, fixing high contrast outline being rendered with wrong layer
- Pass translucent parameter to BEFORE_BLOCK_OUTLINE event
- Remove deprecated BlockOutlineContext#vertexConsumer

* Re-add deprecated BlockOutlineContext#vertexConsumer

* Add translucent block outline to context
  • Loading branch information
PepperCode1 authored Dec 12, 2024
1 parent 5f43f98 commit b874120
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@ public interface WorldRenderContext {
*/
WorldRenderer worldRenderer();

/**
* The matrix stack is only not null in {@link WorldRenderEvents#AFTER_ENTITIES} or later events.
*/
@Nullable
MatrixStack matrixStack();

RenderTickCounter tickCounter();

boolean blockOutlines();
Expand All @@ -58,10 +52,10 @@ public interface WorldRenderContext {

GameRenderer gameRenderer();

Matrix4f projectionMatrix();

Matrix4f positionMatrix();

Matrix4f projectionMatrix();

/**
* Convenient access to {WorldRenderer.world}.
*
Expand Down Expand Up @@ -92,30 +86,39 @@ public interface WorldRenderContext {
* 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
* {@link WorldRenderEvents#BLOCK_OUTLINE} (translucent) 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();
@Nullable
VertexConsumerProvider consumers();

/**
* View frustum, after it is initialized. Will be {@code null} during
* {@link WorldRenderEvents#START}.
*/
@Nullable Frustum frustum();
@Nullable
Frustum frustum();

/**
* Used in {@code BLOCK_OUTLINE} to convey the parameters normally sent to
* The matrix stack is only not null in {@link WorldRenderEvents#AFTER_ENTITIES} or later events.
*/
@Nullable
MatrixStack matrixStack();

/**
* Meant to be used in {@link WorldRenderEvents#BEFORE_BLOCK_OUTLINE} and {@link WorldRenderEvents#BLOCK_OUTLINE}.
* @return {@code true} if the current block outline is being rendered after translucent terrain; {@code false} if
* it is being rendered after solid terrain
*/
boolean translucentBlockOutline();

/**
* Used in {@link WorldRenderEvents#BLOCK_OUTLINE} to convey the parameters normally sent to
* {@code WorldRenderer.drawBlockOutline}.
*/
interface BlockOutlineContext {
/**
* @deprecated Use {@link #consumers()} directly.
*/
@Deprecated
VertexConsumer vertexConsumer();

Entity entity();

double cameraX();
Expand All @@ -127,5 +130,11 @@ interface BlockOutlineContext {
BlockPos blockPos();

BlockState blockState();

/**
* @deprecated Use {@link #consumers()} directly.
*/
@Deprecated
VertexConsumer vertexConsumer();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
* <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_BLOCK_OUTLINE (non-translucent)
* <li>BLOCK_OUTLINE (if not cancelled in non-translucent BEFORE_BLOCK_OUTLINE and vanilla checks pass)
* <li>BEFORE_DEBUG_RENDER
* <li>BEFORE_BLOCK_OUTLINE (translucent)
* <li>BLOCK_OUTLINE (if not cancelled in translucent BEFORE_BLOCK_OUTLINE and vanilla checks pass)
* <li>AFTER_TRANSLUCENT
* <li>LAST
* <li>END</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package net.fabricmc.fabric.impl.client.rendering;

import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;

import net.minecraft.block.BlockState;
Expand All @@ -37,17 +38,21 @@
public final class WorldRenderContextImpl implements WorldRenderContext.BlockOutlineContext, WorldRenderContext {
private WorldRenderer worldRenderer;
private RenderTickCounter tickCounter;
private MatrixStack matrixStack;
private boolean blockOutlines;
private Camera camera;
private Frustum frustum;
private GameRenderer gameRenderer;
private Matrix4f projectionMatrix;
private Matrix4f positionMatrix;
private Matrix4f projectionMatrix;
private VertexConsumerProvider consumers;
private boolean advancedTranslucency;
private ClientWorld world;

@Nullable
private Frustum frustum;
@Nullable
private MatrixStack matrixStack;
private boolean translucentBlockOutline;

private Entity entity;
private double cameraX;
private double cameraY;
Expand All @@ -59,27 +64,29 @@ public final class WorldRenderContextImpl implements WorldRenderContext.BlockOut

public void prepare(
WorldRenderer worldRenderer,
RenderTickCounter delta,
RenderTickCounter tickCounter,
boolean blockOutlines,
Camera camera,
GameRenderer gameRenderer,
Matrix4f projectionMatrix,
Matrix4f positionMatrix,
Matrix4f projectionMatrix,
VertexConsumerProvider consumers,
boolean advancedTranslucency,
ClientWorld world
) {
this.worldRenderer = worldRenderer;
this.tickCounter = delta;
this.matrixStack = null;
this.tickCounter = tickCounter;
this.blockOutlines = blockOutlines;
this.camera = camera;
this.gameRenderer = gameRenderer;
this.projectionMatrix = projectionMatrix;
this.positionMatrix = positionMatrix;
this.projectionMatrix = projectionMatrix;
this.consumers = consumers;
this.advancedTranslucency = advancedTranslucency;
this.world = world;

frustum = null;
matrixStack = null;
}

public void setFrustum(Frustum frustum) {
Expand All @@ -90,6 +97,10 @@ public void setMatrixStack(MatrixStack matrixStack) {
this.matrixStack = matrixStack;
}

public void setTranslucentBlockOutline(boolean translucentBlockOutline) {
this.translucentBlockOutline = translucentBlockOutline;
}

public void prepareBlockOutline(
Entity entity,
double cameraX,
Expand All @@ -111,11 +122,6 @@ public WorldRenderer worldRenderer() {
return worldRenderer;
}

@Override
public MatrixStack matrixStack() {
return matrixStack;
}

@Override
public RenderTickCounter tickCounter() {
return this.tickCounter;
Expand All @@ -132,23 +138,28 @@ public Camera camera() {
}

@Override
public Matrix4f projectionMatrix() {
return projectionMatrix;
public GameRenderer gameRenderer() {
return gameRenderer;
}

@Override
public Matrix4f positionMatrix() {
return positionMatrix;
}

@Override
public Matrix4f projectionMatrix() {
return projectionMatrix;
}

@Override
public ClientWorld world() {
return world;
}

@Override
public Frustum frustum() {
return frustum;
public boolean advancedTranslucency() {
return advancedTranslucency;
}

@Override
Expand All @@ -157,18 +168,20 @@ public VertexConsumerProvider consumers() {
}

@Override
public GameRenderer gameRenderer() {
return gameRenderer;
@Nullable
public Frustum frustum() {
return frustum;
}

@Override
public boolean advancedTranslucency() {
return advancedTranslucency;
@Nullable
public MatrixStack matrixStack() {
return matrixStack;
}

@Override
public VertexConsumer vertexConsumer() {
return consumers.getBuffer(RenderLayer.getLines());
public boolean translucentBlockOutline() {
return translucentBlockOutline;
}

@Override
Expand Down Expand Up @@ -200,4 +213,10 @@ public BlockPos blockPos() {
public BlockState blockState() {
return blockState;
}

@Deprecated
@Override
public VertexConsumer vertexConsumer() {
return consumers.getBuffer(RenderLayer.getLines());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import net.minecraft.block.BlockState;
Expand All @@ -39,15 +38,13 @@
import net.minecraft.client.render.FrameGraphBuilder;
import net.minecraft.client.render.Frustum;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.RenderPass;
import net.minecraft.client.render.RenderTickCounter;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.util.ObjectAllocator;
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.Vec3d;

Expand All @@ -72,7 +69,7 @@ public abstract class WorldRendererMixin {

@Inject(method = "render", at = @At("HEAD"))
private void beforeRender(ObjectAllocator objectAllocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
context.prepare((WorldRenderer) (Object) this, tickCounter, renderBlockOutline, camera, gameRenderer, projectionMatrix, positionMatrix, bufferBuilders.getEntityVertexConsumers(), MinecraftClient.isFabulousGraphicsOrBetter(), world);
context.prepare((WorldRenderer) (Object) this, tickCounter, renderBlockOutline, camera, gameRenderer, positionMatrix, projectionMatrix, bufferBuilders.getEntityVertexConsumers(), MinecraftClient.isFabulousGraphicsOrBetter(), world);
WorldRenderEvents.START.invoker().onStart(context);
}

Expand Down Expand Up @@ -118,41 +115,28 @@ private void afterEntities(CallbackInfo ci) {
WorldRenderEvents.AFTER_ENTITIES.invoker().afterEntities(context);
}

@Inject(
method = "renderTargetBlockOutline",
at = @At(
value = "FIELD",
target = "Lnet/minecraft/client/MinecraftClient;crosshairTarget:Lnet/minecraft/util/hit/HitResult;",
shift = At.Shift.AFTER,
ordinal = 0
)
)
private void beforeRenderOutline(CallbackInfo ci) {
@Inject(method = "renderTargetBlockOutline", at = @At("HEAD"))
private void beforeRenderOutline(Camera camera, VertexConsumerProvider.Immediate vertexConsumers, MatrixStack matrices, boolean translucent, CallbackInfo ci) {
context.setTranslucentBlockOutline(translucent);
context.renderBlockOutline = WorldRenderEvents.BEFORE_BLOCK_OUTLINE.invoker().beforeBlockOutline(context, client.crosshairTarget);
}

@SuppressWarnings("ConstantConditions")
@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, int color, CallbackInfo ci) {
@Inject(method = "renderTargetBlockOutline", at = @At(value = "INVOKE", target = "net/minecraft/client/option/GameOptions.getHighContrastBlockOutline()Lnet/minecraft/client/option/SimpleOption;"), cancellable = true)
private void onDrawBlockOutline(Camera camera, VertexConsumerProvider.Immediate vertexConsumers, MatrixStack matrices, boolean translucent, CallbackInfo ci, @Local BlockPos blockPos, @Local BlockState blockState, @Local Vec3d cameraPos) {
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(entity, cameraX, cameraY, cameraZ, blockPos, blockState);

if (!WorldRenderEvents.BLOCK_OUTLINE.invoker().onBlockOutline(context, context)) {
ci.cancel();
}
return;
}
}

@SuppressWarnings("ConstantConditions")
@ModifyVariable(method = "drawBlockOutline", at = @At("HEAD"))
private VertexConsumer resetBlockOutlineBuffer(VertexConsumer vertexConsumer) {
// The original VertexConsumer may have been ended during the block outlines event, so we
// have to re-request it to prevent a crash when the vanilla block overlay is submitted.
return context.consumers().getBuffer(RenderLayer.getLines());
context.prepareBlockOutline(camera.getFocusedEntity(), cameraPos.x, cameraPos.y, cameraPos.z, blockPos, blockState);

if (!WorldRenderEvents.BLOCK_OUTLINE.invoker().onBlockOutline(context, context)) {
vertexConsumers.drawCurrentLayer();
ci.cancel();
}
}

@Inject(
Expand Down

0 comments on commit b874120

Please sign in to comment.