Skip to content

Fix sub-level shader block IDs and single-block model data on NeoForge - #1417

Open
BlackCube4 wants to merge 1 commit into
ryanhcode:mainfrom
BlackCube4:fix/sublevel-shader-block-ids
Open

Fix sub-level shader block IDs and single-block model data on NeoForge#1417
BlackCube4 wants to merge 1 commit into
ryanhcode:mainfrom
BlackCube4:fix/sublevel-shader-block-ids

Conversation

@BlackCube4

@BlackCube4 BlackCube4 commented Jul 31, 2026

Copy link
Copy Markdown

Fix two sub-level rendering issues on NeoForge, both caused by the vanilla render path
being fed something different from what actually meshes the geometry.

1. Shader packs get no block ID for anything on a sub-level

ReachAroundSubLevelRenderDispatcher meshes sub-levels with a vanilla SectionRenderDispatcher
rather than Sodium. Iris instruments the two paths separately, and only the Sodium one has a
caller:

  • Sodium - MixinChunkMeshBuildTask calls VertexEncoderInterface#beginBlock per block.
  • Vanilla - MixinBufferBuilder implements BlockSensitiveBufferBuilder and does write the
    ID into IrisVertexFormats.ENTITY_ELEMENT, but nothing in Iris ever calls beginBlock on it.
    In iris-neoforge-1.8.14-beta.1+mc1.21.1.jar the only classes referencing that interface are the
    interface itself and the mixin implementing it.

So currentBlock keeps its initial -1 for every block meshed for a sub-level, and shaders see
mc_Entity.x == -1. Under Complementary that skips the entirety of terrainIPBR.glsl, which is
wrapped in a single if (mat >= 10000): gold stops being metallic, glowstone stops being
emissive, and every block loses its integrated-PBR material properties the moment it is assembled
into a physics object. Disassembling restores them.

This adds compatibility/iris/ModelBlockRendererMixin, which brackets
ModelBlockRenderer#tesselateWithAO and #tesselateWithoutAO with beginBlock/endBlock when the
consumer is a BlockSensitiveBufferBuilder. It sits under mixin.compatibility.iris, so
AbstractSableMixinPlugin already gates it on Iris being loaded.

Both sub-level data paths converge on those two methods and only those two.
VanillaChunkedSubLevelRenderData reaches them through SectionCompiler and renderBatched,
while VanillaSingleSubLevelRenderData, used when a physics object is exactly one block, calls
tesselateWithoutAO directly from SableSubLevelRenderPlatformImpl, bypassing renderBatched
entirely. Bracketing the dispatcher instead covers the first but silently misses the second.

Note the vanilla path writes currentBlock raw as a short, unlike the Sodium encoder's
(id + 1) << 1 | renderType packing - so the value from getBlockStateIds() is passed through
unchanged.

2. Models whose render layers depend on model data are invisible on single-block sub-levels

SableSubLevelRenderPlatformImpl asks two different sources for the same block's ModelData:

// geometry - correct
tesselateBlock(...)  -> blockAndTintGetter.getLevel().getModelData(pos)

// render layers - always ModelData.EMPTY
getRenderLayers(...) -> blockAndTintGetter.getModelData(pos)

SingleBlockSubLevelWrapper is in the common source set and so cannot override NeoForge's
BlockAndTintGetter#getModelData, leaving it on the default of ModelData.EMPTY. Any model whose
getRenderTypes depends on its model data therefore reports no layers, and
VanillaSingleSubLevelRenderData#renderSingleBlock returns before it ever reaches the tesselate
call that would have worked.

The symptom is oddly specific and was how I found it: a physics object made of exactly one such
block is invisible, and adding any second block makes both appear, because that switches to
VanillaChunkedSubLevelRenderData, which uses the level's manager throughout.

Fixed by having getRenderLayers query the level, matching tesselateBlock directly above it.

Testing

Sodium + Iris + Complementary Reimagined with integrated PBR (no resource pack):

  1. Place a gold block and a glowstone block; note the metallic / emissive shading.
  2. Assemble them into a Create Aeronautics physics object - before this change they shade as
    untyped blocks; after it they keep their material properties.
  3. For (2), assemble a single block whose render layers come from its model data (any dynamic
    block-entity model) - invisible before, drawn after.

@CLAassistant

CLAassistant commented Jul 31, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants