Skip to content

Cached BlockEntity Rendering 26.1 only ​

The package dev.anvilcraft.lib.v2.rendering.cachedber provides a cached block entity rendering pipeline. It compiles block entity render states into per-chunk cached data, avoiding repeated uploads each frame and dramatically reducing draw call overhead.

Core Components ​

CachedBlockEntityRenderingPipeline ​

Per-level singleton and the central scheduler of the cached BER system. Access the current dimension's instance via CachedBlockEntityRenderingPipeline.getInstance(). It maintains a ChunkPos to CachedRenderingChunk map and manages compile and upload task queues.

Key methods:

MethodDescription
update(be, true)Marks a block entity dirty (second param is client interaction flag) and queues it
blockRemoved(be)Cleans up cached data when a block is removed
runTasks()Executes compile/upload task queues (should be called from LevelRenderer)
render(frustum)Renders all cached chunks within the frustum
forcedUpdate(pos)Forces an immediate update of the cache at the given position

CachedRenderingChunk ​

One instance per chunk, holding the compiled render state for all block entities within that chunk. Lifecycle is managed by CachedBlockEntityRenderingPipeline.

CachedBlockEntityRenderer<T, S> ​

Block entity renderer interface, similar to Vanilla's BlockEntityRenderer, but the render output is cached rather than resubmitted every frame. The generic S must extend CachedBlockEntityRenderState.

java
public interface CachedBlockEntityRenderer<T extends BlockEntity, S extends CachedBlockEntityRenderState> {
    S createRenderState();
    void extractRenderState(T be, S state, float partialTicks, Camera camera);
    void submit(S state, PoseStack pose, VertexConsumer collector, Camera camera);
}

CachedBlockEntityRenderDispatcher ​

Render dispatcher singleton that manages all registered CachedBlockEntityRenderer instances. During the compile phase it calls each renderer's extractRenderState and submit methods to produce cached data.

RebuildTask ​

A rebuild task representing one pending dirty block entity. CachedBlockEntityRenderingPipeline consumes these tasks in runTasks(), delegating to CachedBlockEntityRenderDispatcher to recompile and upload render data.

CachedBlockEntityRenderState ​

Base render state class, extending BlockEntityRenderState, created by CachedBlockEntityRenderer.createRenderState(), carrying the render data extracted from the block entity.

Usage Guide ​

Registering a Renderer ​

Register custom renderers in FMLClientSetupEvent via CachedBlockEntityRenderDispatcher.INSTANCE:

java
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent event) {
    event.enqueueWork(() -> {
        CachedBlockEntityRenderDispatcher.INSTANCE.registerRenderer(
            MyBlockEntityTypes.MY_BLOCK_ENTITY.get(),
            new MyCachedRenderer()
        );
    });
}

Block Entity Pattern (Tile) ​

Override setRemoved() in your block entity to notify the pipeline to clean up cached data:

java
@Override
public void setRemoved() {
    super.setRemoved();
    if (level != null && level.isClientSide()) {
        CachedBlockEntityRenderingPipeline.getInstance().blockRemoved(this);
    }
}

Block Pattern ​

When a client-side change requires re-rendering the block entity, call update to mark it dirty:

java
@Override
public InteractionResult use(BlockState state, Level level, BlockPos pos,
                              Player player, InteractionHand hand, BlockHitResult hit) {
    if (level.isClientSide()) {
        BlockEntity be = level.getBlockEntity(pos);
        if (be != null) {
            CachedBlockEntityRenderingPipeline.getInstance().update(be, true);
        }
    }
    return InteractionResult.sidedSuccess(level.isClientSide());
}

Render State Class ​

Custom render states must extend CachedBlockEntityRenderState (which itself extends BlockEntityRenderState):

java
public class MyRenderState extends CachedBlockEntityRenderState {
    // Holds render data extracted from the block entity
    public float someValue;
    public int someColor;
}

Custom Renderer ​

Implement the three methods of the CachedBlockEntityRenderer interface:

java
public class MyCachedRenderer implements CachedBlockEntityRenderer<MyBlockEntity, MyRenderState> {

    @Override
    public MyRenderState createRenderState() {
        return new MyRenderState();
    }

    @Override
    public void extractRenderState(MyBlockEntity be, MyRenderState state,
                                    float partialTicks, Camera camera) {
        // Extract data from block entity into render state
        state.someValue = be.getSomeValue();
        state.someColor = be.getSomeColor();
    }

    @Override
    public void submit(MyRenderState state, PoseStack poseStack,
                       VertexConsumer collector, Camera camera) {
        // Compile render data into collector
        // ...
    }
}

Bloom Integration ​

To output bloom/glow effects in submit, obtain a Compound storage from the bloom instance:

java
@Override
public void submit(MyRenderState state, PoseStack poseStack,
                   SubmitNodeCollector collector, CameraRenderState camera) {
    CompoundSubmitNodeStorage compoundSubmit =
        ALRPostEffects.getBloomPostEffect().createCompoundSubmitStorage(collector);

    // Use compoundSubmit for glow portions
    state.blockModelRenderState.submit(poseStack, compoundSubmit, light, overlay, 0);
    // ...
}

Released under the CC-BY-NC-SA 4.0 License.