Class IrisCompanionMesh
- All Implemented Interfaces:
IrisCompanionMeshResource
ExtendedShader returned by Iris/Oculus for ShaderKey.BLOCK_ENTITY.
The data is produced by a BufferBuilder starting from
DefaultVertexFormat.NEW_ENTITY; when Iris is loaded its mixin extends
the format on the fly to IrisVertexFormats.ENTITY so that fields like
midTexCoord, tangent, entityId, blockEntityId
and uv_mid_block are populated automatically.
The companion mesh is built once per part on first request from the render thread; if the build fails (e.g. Iris not loaded, build off-thread) the mesh is marked failed and the calling code is expected to fall back to vanilla paths.
-
Constructor Summary
ConstructorsConstructorDescriptionIrisCompanionMesh(List<net.minecraft.client.renderer.block.model.BakedQuad> quads) -
Method Summary
Modifier and TypeMethodDescriptionvoidSwitches this VAO's UV2 attribute to read fromlightmapVboId(per-vertex mode) instead of returning the per-draw constant set viaglVertexAttribI2i.longallocLightmapSlot(long key) Allocates or reuses a slot for the given lightmap key.voidbindLightmapForInstance(int slotIndex) Repoints the VAO's UV2 attribute at the given instance slot withinlightmapVboId.voidBinds this mesh's VAO if it is not already active.voiddestroy()Releases all GL resources (VAO/VBO/EBO + auxiliary lightmap buffers) and native CPU scratch.booleanAttempts to build the companion mesh.voidensureLightmapCapacity(int requiredInstances) Ensures the auxiliary lightmap VBO + CPU scratch can hold at leastrequiredInstancesconcurrent instance slots.voidConvenience for callers that wrote into a set of slots and need the GPU-visible contents before drawing.intintgetOrAllocateLightmapSlot(long key) Allocates or reuses a slot for the given lightmap key.intintgetVaoId()intgetVboId()GL handle of the VBO this companion holds - used by debug logging.intbooleanisBuilt()booleanisFailed()voidprepareForShader(int programId) Resolves the GLSL-linker-assigned locations of the Iris-extended attributes (iris_Entity,mc_midTexCoord,at_tangent) for the given program and binds our VBO data to those locations on this VAO.voidReverts the VAO back to the per-draw constant UV2 mode (disabled attribute array — callers supply the value viaglVertexAttribI2i).booleanbooleanvoidUploads all dirty slots to the GPU in one contiguous range, for the fallback (non-persistent) path.voiduploadLightmapRange(int firstSlot, int slotCount) Uploads the populated CPU-side lightmap data for instance slots[firstSlot, firstSlot + slotCount)to the GPU.voidwriteInstanceLightmap(int slotIndex, float[] corner16) Computes per-vertex UV2 (blockU, skyV) for the given instance slot by trilinearly combining the 8 corner lightmap samples with the bakedperVertexCornerWeights, and writes the result as USHORT pairs intolightmapCpuScratchat offsetslotIndex * vertexCount * 4.
-
Constructor Details
-
IrisCompanionMesh
-
-
Method Details
-
ensureBuilt
public boolean ensureBuilt()Attempts to build the companion mesh. Must be called on the render thread. Returnstrueon success,falseif a previous build failed or this build attempt failed.- Specified by:
ensureBuiltin interfaceIrisCompanionMeshResource
-
supportsPerVertexLightmap
public boolean supportsPerVertexLightmap()- Specified by:
supportsPerVertexLightmapin interfaceIrisCompanionMeshResource- Returns:
trueif per-vertex trilinear lighting is available for this mesh (weights precomputed + UV2 attribute location known).
-
supportsSlicedPerVertexLightmap
public boolean supportsSlicedPerVertexLightmap()- Returns:
trueifwriteInstanceLightmap(int, float[])can combine afloat[32]2×4×2 light probe set withperVertexSlicedWeights(Iris / extended path matches tall VBOs that useLightSampleCache#getOrSample16).
-
ensureLightmapCapacity
public void ensureLightmapCapacity(int requiredInstances) Ensures the auxiliary lightmap VBO + CPU scratch can hold at leastrequiredInstancesconcurrent instance slots. Grows (never shrinks) to the next power-of-two. No-op if already large enough.Each slot holds
vertexCountpairs of 16-bit lightmap values (blockU, skyV) →vertexCount * 4 bytes. For a 2000-vertex part with 64 slots that is512 KB— comfortably small even summed across every distinct multiblock part in the game.Must be called on the render thread; allocates GL resources on first use.
- Specified by:
ensureLightmapCapacityin interfaceIrisCompanionMeshResource
-
allocLightmapSlot
public long allocLightmapSlot(long key) Allocates or reuses a slot for the given lightmap key.- Returns:
- packed long: low 32 bits = slot, bit 32 = reused flag (1 means slot already contained key).
-
getOrAllocateLightmapSlot
public int getOrAllocateLightmapSlot(long key) Allocates or reuses a slot for the given lightmap key. The key must be a stable hash of the instance's quantized 8-corner UV2 (typically derived after round+clamp to 0..240).Slot reuse is LRU-ish via simple eviction cursor; bounded by
lightmapInstanceCapacity. -
uploadDirtySlotsIfNeeded
public void uploadDirtySlotsIfNeeded()Uploads all dirty slots to the GPU in one contiguous range, for the fallback (non-persistent) path. No-op when persistent mapping is active. -
finishLightmapWrites
public void finishLightmapWrites()Convenience for callers that wrote into a set of slots and need the GPU-visible contents before drawing. (Persistent mapped path: barrier; fallback: upload dirty slots.)- Specified by:
finishLightmapWritesin interfaceIrisCompanionMeshResource
-
writeInstanceLightmap
public void writeInstanceLightmap(int slotIndex, float[] corner16) Computes per-vertex UV2 (blockU, skyV) for the given instance slot by trilinearly combining the 8 corner lightmap samples with the bakedperVertexCornerWeights, and writes the result as USHORT pairs intolightmapCpuScratchat offsetslotIndex * vertexCount * 4.Does NOT upload to the GPU; batch the uploads via
uploadLightmapRange(int, int)after all instances for a draw pass have been written (oneglBufferSubDataper pass is far cheaper than one per instance).- Specified by:
writeInstanceLightmapin interfaceIrisCompanionMeshResource- Parameters:
slotIndex- 0-based slot, must be< lightmapInstanceCapacitycorner16- 16 floats laid out as[c0.blockU, c0.skyV, c1.blockU, c1.skyV, ..., c7.blockU, c7.skyV]; exactly the formatLightSampleCache.getOrSample8(net.minecraft.world.level.block.entity.BlockEntity, long, float[], net.minecraft.core.BlockPos, org.joml.Matrix4f, int, float[])produces, typically already on the 0..240 scale
-
uploadLightmapRange
public void uploadLightmapRange(int firstSlot, int slotCount) Uploads the populated CPU-side lightmap data for instance slots[firstSlot, firstSlot + slotCount)to the GPU.Typically called once per batch flush after all
writeInstanceLightmap(int, float[])calls for that batch are done — this collapses what would otherwise be oneglBufferSubDataper instance into a single driver roundtrip. -
activatePerVertexLightmap
public void activatePerVertexLightmap()Switches this VAO's UV2 attribute to read fromlightmapVboId(per-vertex mode) instead of returning the per-draw constant set viaglVertexAttribI2i. Idempotent — repeated calls after a no-op return immediately.The VAO state is persistent across frames, so we only need to do the heavy
glEnableVertexAttribArray + glVertexAttribIPointerdance on the first call (or after anensureLightmapCapacity(int)growth invalidated the binding).The caller MUST have this VAO bound (via
getVaoId()) before invoking this method. On return, the UV2 attribute points at slot 0; usebindLightmapForInstance(int)to switch slots per draw.- Specified by:
activatePerVertexLightmapin interfaceIrisCompanionMeshResource
-
bindLightmapForInstance
public void bindLightmapForInstance(int slotIndex) Repoints the VAO's UV2 attribute at the given instance slot withinlightmapVboId. Must be called betweenactivatePerVertexLightmap()and the per-instanceglDrawElements.Skips the GL call when the slot matches the previous one (rare — only happens if one part of the batch draws the same instance twice, e.g. for shadow + main in the same batch, which our pipeline currently splits into distinct passes).
- Specified by:
bindLightmapForInstancein interfaceIrisCompanionMeshResource
-
restoreConstantLightmap
public void restoreConstantLightmap()Reverts the VAO back to the per-draw constant UV2 mode (disabled attribute array — callers supply the value viaglVertexAttribI2i). Paired withactivatePerVertexLightmap(). No-op if we were never in per-vertex mode.Rarely needed in practice — any code path that does per-vertex lighting on one frame wants the same on every frame — but useful for debug toggles and shader-pack hot-reload scenarios where we might need to start clean.
- Specified by:
restoreConstantLightmapin interfaceIrisCompanionMeshResource
-
prepareForShader
public void prepareForShader(int programId) Resolves the GLSL-linker-assigned locations of the Iris-extended attributes (iris_Entity,mc_midTexCoord,at_tangent) for the given program and binds our VBO data to those locations on this VAO.Why this is needed. Iris's transformers (
EntityPatcher.patchEntityId,CommonTransformer.patchMultiTexCoord3) inject these attributes into the GLSL withoutlayout(location = N)qualifiers, andMixinShaderInstanceonly adds explicitglBindAttribLocationcalls for the six core Mojang attributes (Position/Color/UV0/UV1/UV2/Normal → 0..5). The linker is therefore free to putiris_Entityat, say, location 6 while Mojang'sVertexFormat.setupBufferStatewould have bound theIrisVertexFormats.ENTITYformat's iris_Entity element (which sits at list-index 7 in the format) to GL location 7. Whenever those disagree, the shader reads garbage from a slot nothing ever bound - which is exactly the broken-geometry symptom that appears intermittently near torches and gets dramatically worse when redstone particles or Embeddium chunk re-bakes pollute the global "current attribute value" bank.The fix. We query
GL20.glGetAttribLocation(int, java.nio.ByteBuffer)per attribute name to get the real linker-resolved location, then bind a VBO pointer to the correct byte offset within our VBO using the appropriate integer/float pipeline. Iris'sMixinBufferBuilder.iris$beforeNextalready wrote correct per-vertex values for these attributes into our VBO when we calledBufferBuilder.putBulkData(java.nio.ByteBuffer), so the shader now reads real, stable data - completely immune to any global state pollution from other rendering paths.The caller MUST have this VAO bound (via
getVaoId()) before invoking this method. The work is cached per program ID, so repeated calls with the same shader are nearly free.- Specified by:
prepareForShaderin interfaceIrisCompanionMeshResource
-
isBuilt
public boolean isBuilt() -
isFailed
public boolean isFailed() -
getVaoId
public int getVaoId()- Specified by:
getVaoIdin interfaceIrisCompanionMeshResource- Returns:
- GL handle of the companion VAO, or
-1when not built.Implemented by
getVaoId().
-
getVboId
public int getVboId()GL handle of the VBO this companion holds - used by debug logging. -
getIndexCount
public int getIndexCount()- Specified by:
getIndexCountin interfaceIrisCompanionMeshResource- Returns:
- number of indices in the EBO (
quadCount * 6), or0when not built.Implemented by
getIndexCount().
-
getUv2Location
public int getUv2Location()- Specified by:
getUv2Locationin interfaceIrisCompanionMeshResource- Returns:
- the GL attribute location for UV2 (lightmap) within this VAO, or
-1if the format does not contain such an element. Use withglVertexAttrib2f(getUv2Location(), blockU, skyV)to override the lightmap on a per-draw basis.
-
bindVaoIfNeeded
public void bindVaoIfNeeded()Binds this mesh's VAO if it is not already active. Safe to call beforeglDrawElementswhen Embeddium/Iris may have rebound VAO 0 between setup and draw (instanced flush loop). -
getVertexCount
public int getVertexCount() -
destroy
public void destroy()Description copied from interface:IrisCompanionMeshResourceReleases all GL resources (VAO/VBO/EBO + auxiliary lightmap buffers) and native CPU scratch. Safe to call off the render thread - deletion is deferred viaRenderSystem.recordRenderCall.Implemented by
destroy().- Specified by:
destroyin interfaceIrisCompanionMeshResource
-