Class LightSampleCache
Why this exists. InstancedStaticPartRenderer.sampleSmoothLightUV
is the most expensive call on the per-machine path under shaders - the
profiler attributed roughly 17% of frame time to it on a small Advanced
Assembler farm. The work itself (6 × Level.getBlockState + 6 ×
LevelRenderer.getLightColor) is fundamentally chunky, but it was
being repeated per part renderer for the same BlockEntity:
- Advanced Assembler has 11 parts (Base, Frame, 3× Ring, Arm[Bone1..6]).
For each visible machine the BlockEntityRenderer dispatches to 11 different
InstancedStaticPartRenderer.addInstance(...)calls, each of which used to sample the lightmap independently for the SAME world-space bbox. - Both the main pass and the shadow pass repeat the dispatch - so the 11× redundancy doubles to 22× per machine per frame.
- For 6 visible machines that adds up to ~792 lightmap lookups per frame for a single multiblock type, plus the bbox call (which itself can be non-trivial for dynamic-bbox BEs).
Invalidation strategy. The cache uses a render-frame counter that
is bumped from ClientModEvents.onRenderLevelStage(AFTER_BLOCK_ENTITIES)
- i.e. ONCE per fully-rendered frame, after both the shadow pass and the
main pass have drained their block-entity dispatches. So a sample taken
during the shadow pass is reused during the main pass of the same frame
(correct - the world hasn't changed between them) and a fresh sample is
taken on the next frame. There is no need for a stronger TTL because the
lightmap only changes over multiple ticks anyway.
Periodic pruning every 600 frames (~10 seconds) drops entries belonging to BlockEntities that haven't been rendered recently - typical "I walked past a chunk and now it's behind me" case - keeping the cache bounded in long sessions.
Single-threaded: lives entirely on the render thread.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final ThreadLocal<org.joml.Matrix4f>static final ThreadLocal<Boolean> -
Method Summary
Modifier and TypeMethodDescriptionstatic voidgetOrSample(net.minecraft.world.level.block.entity.BlockEntity be, int packedLightFallback, float[] outUV, int outBase) Returns the smoothed lightmap UV pair for the supplied BlockEntity, sampling lazily on the first call within a frame and reusing the cached value for all subsequent calls within the same frame.static voidgetOrSample16(net.minecraft.world.level.block.entity.BlockEntity be, long partIdentityHash, float[] objBbox, net.minecraft.core.BlockPos blockPos, org.joml.Matrix4f localPose, int packedLightFallback, float[] out32) Samples lightmap UV pairs for a 2x4x2 lattice (X/Z corners across 4 Y slices).static voidgetOrSample8(net.minecraft.world.level.block.entity.BlockEntity be, long partIdentityHash, float[] objBbox, net.minecraft.core.BlockPos blockPos, org.joml.Matrix4f localPose, int packedLightFallback, float[] out16) Samples lightmap UV pairs at the 8 world-space corners of the given object-space bbox.static voidgetOrSample8Lod(net.minecraft.world.level.block.entity.BlockEntity be, long partIdentityHash, float[] objBbox, net.minecraft.core.BlockPos blockPos, org.joml.Matrix4f localPose, int packedLightFallback, float[] out16, double distSqToCamera) LikegetOrSample8(net.minecraft.world.level.block.entity.BlockEntity, long, float[], net.minecraft.core.BlockPos, org.joml.Matrix4f, int, float[])but skips expensive 8-block spatial sampling whendistSqToCameraexceedsRenderDistanceHelper.getLightCornerDetailDistanceSq().static voidDrops every cached entry.static voidBumps the frame counter; subsequentgetOrSample(net.minecraft.world.level.block.entity.BlockEntity, int, float[], int)calls will resample once per BlockEntity rather than reusing the previous frame's value.
-
Field Details
-
BASE_POSE
-
BASE_POSE_SET
-
-
Method Details
-
onFrameStart
public static void onFrameStart()Bumps the frame counter; subsequentgetOrSample(net.minecraft.world.level.block.entity.BlockEntity, int, float[], int)calls will resample once per BlockEntity rather than reusing the previous frame's value. Periodically prunes stale entries. -
getOrSample
public static void getOrSample(@Nullable net.minecraft.world.level.block.entity.BlockEntity be, int packedLightFallback, float[] outUV, int outBase) Returns the smoothed lightmap UV pair for the supplied BlockEntity, sampling lazily on the first call within a frame and reusing the cached value for all subsequent calls within the same frame.Falls back to
packedLightFallbackwhen:beis null or detached from a level;- the bounding box is non-finite (some BEs return AABB.INFINITE);
- all 6 face positions are themselves opaque (machine fully buried).
- Parameters:
be- the BlockEntity providing world + bboxpackedLightFallback- vanilla packed light to use if sampling failsoutUV- destination array, written asoutUV[outBase] = blockU, outUV[outBase+1] = skyVon the same 0..240 scale thatBufferBuilder.uv2writesoutBase- offset intooutUV
-
invalidateAll
public static void invalidateAll()Drops every cached entry. Call when the level changes (dimension switch, world unload) so we don't return a stale UV taken from a now-invalid Level reference. Cheap - the map is typically a few dozen entries. -
getOrSample8
public static void getOrSample8(@Nullable net.minecraft.world.level.block.entity.BlockEntity be, long partIdentityHash, float[] objBbox, net.minecraft.core.BlockPos blockPos, org.joml.Matrix4f localPose, int packedLightFallback, float[] out16) Samples lightmap UV pairs at the 8 world-space corners of the given object-space bbox. Writes intoout16as[c0.blockU, c0.skyV, c1.blockU, c1.skyV, ...].Corner index encoding: bit 0 = x, bit 1 = y, bit 2 = z; bit set means "take max side on that axis, else min side". This order must match the shader's
w.x / w.y / w.zweight indexing intrilinearBright.Coordinate system. The 8 world block positions are resolved as
blockPos + floor(localPose * objCorner), where:blockPosis the BE's integer world position (exact);localPoseis the per-BE local transform (rotation, scale, and any small sub-block translation applied inside the BER) without the camera's view rotation and without theblockPos - cameraPosoffset baked in by LevelRenderer.
Matrix4f, which would lose precision at large camera offsets (≥ ~10k blocks from origin) and cause the floored block index to flicker between adjacent blocks as the camera moves sub-block distances - producing the "models shimmer between bright and dark when you move near a torch" symptom. Float32 has ~7 decimal digits of precision, so(float)cameraPos + (float)(blockPos - cameraPos)atcameraPos ~ 10^6rounds inconsistently across frames.Cache key combines
BE.getBlockPos().asLong()with apartIdentityHashso separate parts of one BE don't collide.- Parameters:
be- owning BlockEntity (for world lookup + cache key)partIdentityHash- stable hash identifying which part of the BE this sample set belongs to (e.g. renderer identity)objBbox- {minX, minY, minZ, maxX, maxY, maxZ}blockPos- BE world position (int, exact)localPose- object-space -> block-relative transform (per-BE rot/scale/local trans; no view rot; no camera-relative offset)packedLightFallback- vanilla packed light when sampling isn't possibleout16- destination, 16 floats (must be non-null)
-
getOrSample8Lod
public static void getOrSample8Lod(@Nullable net.minecraft.world.level.block.entity.BlockEntity be, long partIdentityHash, float[] objBbox, net.minecraft.core.BlockPos blockPos, org.joml.Matrix4f localPose, int packedLightFallback, float[] out16, double distSqToCamera) LikegetOrSample8(net.minecraft.world.level.block.entity.BlockEntity, long, float[], net.minecraft.core.BlockPos, org.joml.Matrix4f, int, float[])but skips expensive 8-block spatial sampling whendistSqToCameraexceedsRenderDistanceHelper.getLightCornerDetailDistanceSq(). -
getOrSample16
public static void getOrSample16(@Nullable net.minecraft.world.level.block.entity.BlockEntity be, long partIdentityHash, float[] objBbox, net.minecraft.core.BlockPos blockPos, org.joml.Matrix4f localPose, int packedLightFallback, float[] out32) Samples lightmap UV pairs for a 2x4x2 lattice (X/Z corners across 4 Y slices). Output layout: 16 probes * 2 floats = 32 floats: slice0: (x0z0, x1z0, x0z1, x1z1), then slice1, slice2, slice3; each probe contributes (blockU, skyV).Intended for very tall machines where a single 2x2x2 corner set fails to capture mid-height localized block light (e.g. a torch attached halfway up the side of a tower).
-