Constant black artifacting on terrain

Autumn :3

New Member
Mar 16, 2026
12
6

Game Version

v2026.7.4.4860, Linux native build

Operating System

Linux, CachyOS, Kernel 7.1.3-2-cachyos, Mesa 26.1.4-1

What Happened​

Crazy flickering patchy artifacts all over some parts of terrain.

What Was Expected​

Terrain rendering realistically with no weird patches.

Reproduction Steps​

  1. Open KSA
  2. Switch to freecam
  3. Fly around until you find the bugged terrain.

Reproduction Rate​

Always.

Additional Info​

  • Video/Screenshots:
5013d21f91badd6d53ed6e0d2c4958c7bad28f5a.pnj
1af6543ff0526c5f4785331d72a4704dc33bff16.pnj

  • Other Notes:
    My system has a Ryzen 5 5600X and a Radeon RX 6750 XT
 
  • Like
Reactions: leha44691
Upvote 2
I have the same problem with AMD on Linux and I investigated a bit.

Operating System: EndeavourOS
KDE Plasma Version: 6.7.2
KDE Frameworks Version: 6.27.0
Qt Version: 6.11.1
Kernel Version: 7.0.14-arch1-1 (64-bit)
Graphics Platform: Wayland
Processors: 16 × AMD Ryzen 7 7800X3D 8-Core Processor
Memory: 32 GiB of RAM (30,5 GiB usable)
Graphics Processor 1: AMD Radeon RX 6800
Graphics Processor 2: AMD Ryzen 7 7800X3D 8-Core Processor
Manufacturer: Micro-Star International Co., Ltd.
Product Name: MS-7D75
System Version: 1.0

The terrain shader is under "linux-x64/Content/Core/Shaders/Planet/Planet.frag", so I modified it an shaved it down to first value that has artifacts.
That happens to be the displacements.
I discussed a lot with AI and I think (DON'T TAKE THIS AS FACT THOUGH) the problem is the following:
The displacements are the first point where textures chosen based on a non uniform id are sampled. In that case `globalTextures[material.displacementId]`.
I don't know much about shaders and GPUs but as far as I understand, the GPUs samples multiple pixels at once in a wavefront and assumes that the control flow is the same for all pixels in the wavefront.
However when that assumption breaks, artifacts appear.
In this case, one pixel must sample texture A at a certain point and another pixel in the same wavefront should sample texture B.

I attached a zip with the modified "Planet.frag" I experimented with.

There are two sections:

C-like:
    // BEGIN SECTION
    // This section has artifacts
    displacement = SampleBiplanarTextureGrad(
            globalTextures[material.displacementId],
            // This also does not work.
            // (When enabling, you have to put "#extension GL_EXT_nonuniform_qualifier : require" at the top below the version.)
            // nonuniformEXT(material.displacementId),
            linearRepeatSampler,
            biplanarInfo,
            sampleBiplanarMajor,
            sampleBiplanarMedian,
            texturePos.xyz,
            uvOffset1,
            uvOffset2,
            material.textureTiling * tilingFactor.x,
            material.textureTiling * tilingFactor.y,
            texBlend
    ).r;
    // END SECTION

The first is akin to the current code that produces artifacts.


C-like:
    // BEGIN SECTION
    // This section has no artifacts
    // 110 to 130 is the approximate range of the used displacement textures for me.
    // But that probably varies from start to start, with location and with different settings.
    int textureIdx = material.displacementId;

    #pragma unroll
    for (int i = 110; i < 130; i++)
    {
        if (i == textureIdx)
        {
            displacement = SampleBiplanarTextureGrad(
                    globalTextures[textureIdx],
                    linearRepeatSampler,
                    biplanarInfo,
                    sampleBiplanarMajor,
                    sampleBiplanarMedian,
                    texturePos.xyz,
                    uvOffset1,
                    uvOffset2,
                    material.textureTiling * tilingFactor.x,
                    material.textureTiling * tilingFactor.y,
                    texBlend
            ).r;
        }
    }
    // END SECTION

The second has a loop over some texture ids that happen to contain the ones needed. But it only samples if the index in the loop is the needed one.
Even though I came up with this loop "fix" by myself (and not the AI), I don't quite understand why it works.
My guess is that this ensures that for different pixels in the same wavefront, the same texture is always sampled at a given point in the control flow. If different textures need to be sampled, it happens at different points.
Obviously, this "fix" does not scale, since we would have to loop over all texture ids to make it always work.

The AI also suggested to me probably a thousand times to use `nonuniformEXT(material.displacementId)` instead of just `material.displacementId` and to add `#extension GL_EXT_nonuniform_qualifier : require` at the top.
That doesn't work though. Not sure why. Maybe this needs to be enabled somewhere in the CPU code too?

The same texture id access also causes artifacts with the other textures, like diffuse, normal and aoRoughMetal I think.

I have attached screenshots of the displacement without the fix, with the fix and the vanilla view.

Does this help @Linx ? If you want to send shader files with attempted fixes, I can paste them in and give'em a spin.
 

Attachments

  • Planet.frag.zip
    Planet.frag.zip
    3.2 KB · Views: 0
  • Planet.frag.zip
    Planet.frag.zip
    3.2 KB · Views: 0
  • displacement_artifacts.jpg
    displacement_artifacts.jpg
    357.8 KB · Views: 0
  • displacement_no_artifacts.jpg
    displacement_no_artifacts.jpg
    292.5 KB · Views: 0
  • vanilla.jpg
    vanilla.jpg
    704.8 KB · Views: 0
I actually did manage to make the for loop hack work semi-cleanly for the original shader now.

I added
C-like:
    // Compute possible material IDs.
    uint materialIds[MAX_CANDIDATES];

    #pragma unroll
    for (int biomeIndex = 0; biomeIndex < BIOMES_PER_INVOCATION; biomeIndex++)
    {
        int biomeId = int(biomeIDs[biomeIndex] * float(uboPlanet.biomeCount) + 0.4);
        BiomeRenderData biome = biomeDataBuffer.biomeData[biomeId];
        materialIds[biomeIndex * 2] = biome.groundMaterialId;
        materialIds[biomeIndex * 2 + 1] = biome.slopeMaterialId;
    }

at the top and wrapped the two sites that use the material to get texture ids in this:

C-like:
for (uint j = 0; j < MAX_CANDIDATES; j++) {
   if (materialId != materialIds[j]) continue;
   ...
}

That way we only iterate over the 8 materials that can actually occur and not a whole range of ids.
It's still not great and probably less performant, but I think fine until a proper fix lands.

I attached the fixed shader and a screenshot.
So anyone who has that problem can replace the shader under "linux-x64/Content/Core/Shaders/Planet/Planet.frag" with the one in the zip.
This shader is based on version v2026.7.5.4892 though, so if anything changes in a newer update, this may no longer be compatible or lack features. Then you may need to manually apply the two mentioned changes.
 

Attachments

  • Like
Reactions: Kiwi Shark
I have found that my workaround from the previous post still had some problems. Firstly, some materials, on mars for example, suddenly appeared much brighter. Secondly, the artifacts still weren't fixed on biome boundaries, proabably because the materialIds calculated in the first added snippet weren't stable within the same wavefront when different biomes are involved. And thirdly, ground clutter still had the same artifacts.

To fix this, I just deleted the first snippet and replaced the loops from the second with

C-like:
// Workaround for AMD artifacts
for (uint j = 0; j < MAX_BIOME_MATERIALS; j++) {
   if (materialId != j) continue;
   ...
}

That simply loops over all possible 128 materials. Still seems to be performant though. AI also tells me "AMD drivers optimize flat loop branches remarkably well.", so it's probably fine as a workaround.
The same has to be done in "linux-x64/Content/Core/Shaders/Planet/GroundClutter/Solid.frag" for the ground clutter.
That seems to fix all the remaining artifacts that I found.

To apply the fix, you can run
Bash:
(cd "linux-x64/Content/Core/Shaders/Planet" && { grep -q "// Workaround for AMD artifacts" Planet.frag GroundClutter/Solid.frag || sed -i -z -E 's/(uint materialId = topMaterialIds\[i\];)([^}]*)(\n[[:space:]]*\})/\1\n\/\/ Workaround for AMD artifacts\nfor (uint j = 0; j < MAX_BIOME_MATERIALS; j++) {\nif (materialId != j) continue;\2\n}\3/g' Planet.frag GroundClutter/Solid.frag; })
beside your linux build. That wraps the relevant parts with the for loops, if they aren't already there. I guess you can put this in front of your startup script if you want. Should also be a bit more stable across version than replacing the entire file.
 
  • Like
Reactions: Kiwi Shark