FabricMC / fabric

Essential hooks for modding with Fabric.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[1.21] Using custom render layers in BLOCK_OUTLINE event causes crash

SuperMartijn642 opened this issue · comments

When a render type other than RenderLayer#LINES (and that does not have a fixed buffer) is used to render something in the WorldRenderEvents#BLOCK_OUTLINE event, this causes a crash.
To replicate the crash, simply add the following subsciber to the event:

WorldRenderEvents.BLOCK_OUTLINE.register((worldRenderContext, blockOutlineContext) -> {
    worldRenderContext.consumers().getBuffer(RenderLayer.getEndPortal());
    return true;
});

Here is a log of such a crash (although with mojmaps): https://gist.github.com/SuperMartijn642/2a6ba4977d6c172b01f2137d4f8dec30


The current flow for the WorldRenderEvents#BLOCK_OUTLINE is as follows:

  1. WorldRenderer#render requests a buffer for RenderLayer#LINES.
  2. WorldRenderer#render calls WorldRenderer#drawBlockOutline with the requested buffer.
  3. Fabric's WorldRendererMixin#onDrawBlockOutline mixin fires the WorldRenderEvents#BLOCK_OUTLINE.
  4. Fabric's WorldRendererMixin#onDrawBlockOutline mixin requests a buffer for RenderLayer#LINES.
  5. WorldRenderer#drawBlockOutline renders the block's outline with the buffer passed to it.

If a mod requests a buffer for a render type other than RenderLayer#LINES during the WorldRenderEvents#BLOCK_OUTLINE event, that causes the original buffer for RenderLayer#LINES to be ended. If simply left as is, this would cause a crash in step 5 when vertices are submitted to the original buffer.
To combat this, step 4 requests a buffer for RenderLayer#LINES again.

In 1.20.6, VertexConsumerProvider#Immediate simply held one buffer object which gets reset whenever the requested render type is different from the last. Hence, the buffer requested in step 1 would be reset by step 4.
In 1.21, VertexConsumerProvider#Immediate now creates a new BufferBuilder instance every time the requested render type is different from the last. Hence, the buffer requested in step 1 is never reset, rather step 4 causes a new BufferBuilder to be created. As step 5 still uses the buffer object requested in step 1, this leads to a crash.

The WorldRendererMixin#onDrawBlockOutline mixin for context:

@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(entity, cameraX, cameraY, cameraZ, blockPos, blockState);
if (!WorldRenderEvents.BLOCK_OUTLINE.invoker().onBlockOutline(context, context)) {
ci.cancel();
}
// The immediate mode VertexConsumers use a shared buffer, so we have to make sure that the immediate mode VCP
// can accept block outline lines rendered to the existing vertexConsumer by the vanilla block overlay.
context.consumers().getBuffer(RenderLayer.getLines());
}
}