mojira.dev
MC-170012

Lightmaps are missing for initial skylight

Upon initial lighting of a chunk, skylight (and blocklight which doesn't matter here) is directly propagated to all neighbors. However, those neighbors might not have their lightmaps set up correctly, yet.
Non-empty subchunks are only registered during initial lighting

net.minecraft.world.ServerLightingProvider.java

public CompletableFuture<Chunk> light(Chunk chunk, boolean bl) {
    ChunkPos chunkPos = chunk.getPos();
    chunk.setLightOn(false);
    this.enqueue(chunkPos.x, chunkPos.z, ServerLightingProvider.Stage.PRE_UPDATE, Util.debugRunnable(() -> {
        ChunkSection[] chunkSections = chunk.getSectionArray();

        for(int i = 0; i < 16; ++i) {
            ChunkSection chunkSection = chunkSections[i];
            if (!ChunkSection.isEmpty(chunkSection)) {
                super.updateSectionStatus(ChunkSectionPos.from(chunkPos, i), false);
            }
        }
        ...

This means that during initial lighting, neighbor chunks might have not yet registered their non-empty subchunks and hence lightmaps might not have been setup yet correctly.

This can cause skylight updates to get stuck at missing lightmaps. When later on the missing lightmaps get created during initial lighting of the neighbor chunks, those lightmaps will be initialized by copying down the skylight values from the lightmap above it. Hence, if such a subchunk contains any blocks but did not have a lightmap associated to it, the lightmap will now be initialized to wrong values, making the subchunk erroneously bright.

As a concrete example, I will use "erase cached data" in order to force a relighting and hence simulate initial skylight on a well controlled geometry.

  • Create a new redstone-ready world

  • /tp 500 56 0

  • /fill 496 80 0 511 95 15 minecraft:stone hollow

  • /setblock 487 119 7 minecraft:stone

  • /tp @s 0 56 0 270 0

  • Optimize world -> erase cached data

  • Fly 500 blocks in +x direction and fly inside the stone cube using spectator mode

    [media]

 

Fortunately, this issue is quite easy to solve. Simply move the registration of non-empty subchunks outside of the initial lighting to some earlier stage. Concretely, add some pre_light stage to worldgen that registers all non-empty subchunks and require all neighbors to have passed this stage before running initial lighting for a chunk.

 

Best,
PhiPro

Linked issues

Attachments

Comments 14

There is also a related issue for blocklight. As mentioned in the discussion of MC-170010 ProtoChunk.setBlockState(...) schedules light checks for chunks that have passed the features generation stage, suggesting that such block updates are indeed possible. Looking at the worldgen code also suggests that this is possible since the features generation stage has access to an 8 chunk radius.

The problem is now that such a block change could place a light source and the scheduled light check would then propagate the light too early. In contrast to the original report about skylight, lightmaps do exist for the neighbors, since they will be created due to the placed blocklight source (well, not quite due to the second issue below). However, the lighting engine treats chunks before the features stage as opaque, so the light propagation can get stuck at the chunk boundary nevertheless. So the end-result is similar to the original report, namely that light propagation can get stuck if carried out too early, i.e., before the neighbors are ready, altough the underlying cause is slightly different in this case.
Note that this version for blocklight cannot be as easily visualised by relighting the world using erase cached data as it relies on blocks being placed between the features and light generation stage.

The fix for this is again quite easy. Just let the lighting engine check if a chunk already has its initial lighting done, and otherwise ignore the luminance of a block for light calculations. This way, blocks don't emit light by themselves before the initial lighting and are properly turned on during initial lighting, similar to my proposed solution for initial skylight in MC-170010.
Note that you cannot simply remove the light checks that cause the early propagation. Initial lighting spreads light too all neighbors, which are only guaranteed to be in pre_light stage at that point. Any block changes thereafter need to schedule light checks as otherwise you can have missing or ghost-contributions to those neighbors. When later on those neighbors are Initially lighted they will not be completely relighted but only add their own blocklight-sources. So, you need any lighting information to stay consistent after the pre_light stage and hence you need these light checks.
Also note that you should not fix this bug by not treating chunks before the features stage as opaque. Leaving in the early propagation of blocklight would then cause light propagations into chunks before the features stage and hence the above discussion would also apply to those, so you would need to schedule light checks before the features stage as well. However, for performance reasons this is not desired.

 

There is another issue related to block changes after the features stage. ProtoChunk only schedules light checks upon block changes (for chunks after the features stage). but it does not handle lightmap creation and removal. Concretely, it misses the calls to LightingView.updateSectionStatus(BlockPos pos, boolean status) that are present in WorldChunk and World. In view of MC-169913 lightmap creation schould be scheduled before the block change and lightmap removal should be scheduled after the light checks.

 

Let me know if I should post these as separate bug reports.

In 1.16.2

There is another related issue regarding chunks before the light stage. Upon loading from disk, lightmaps are only loaded for chunks that were already lighted and discarded otherwise, although they are always saved to disk.

net.minecraft.world.ChunkSerializer.java

public static ProtoChunk deserialize(...) {
    ...
    boolean bl = compoundTag.getBoolean("isLightOn");
    ...
    if (bl) {
        if (compoundTag2.contains("BlockLight", 7)) {
           lightingProvider.enqueueSectionData(LightType.BLOCK, ChunkSectionPos.from(pos, k), new ChunkNibbleArray(compoundTag2.getByteArray("BlockLight")), true);
        }

        if (bl2 && compoundTag2.contains("SkyLight", 7)) {
           lightingProvider.enqueueSectionData(LightType.SKY, ChunkSectionPos.from(pos, k), new ChunkNibbleArray(compoundTag2.getByteArray("SkyLight")), true);
        }
    }
    ...
}

 

This basically erases any light propagations to chunks in pre_light stage when unloading them before the light stage and hence causes lighting glitches.

 

The Vanilla code currently uses this mechanism to erase cached data, which only removes the isLightOn field but not the lightmaps themselves.

Please create a new ticket

Actually both comments should have separate tickets

4 more comments

Can confirm in 1.17.1 Release Candidate 2.

Can confirm in 1.17.1.

Can confirm in 21w37a.

Can confirm in 21w40a.

The relevant code is in lightChunk(...) in net.minecraft.server.level.ThreadedLevelLightEngine.java using Mojang mappings.

PhiPro

(Unassigned)

Confirmed

Important

Lighting

mojang_internal_1

1.15.1, 1.15.2 Pre-release 2, 1.15.2, 20w06a, 20w20b, ..., 1.17.1, 21w37a, 21w40a, 1.18, 1.19 Pre-release 3

23w16a

Retrieved