libretro / libretro/RetroArch

textureLod() and textureGrad() do not perform as expected across all graphics drivers.

Open
#16,567 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
14.1k
Forks
2.2k
Avg merge
7h 35m
Merged PRs (30d)
51

Description

Description

When using textureLod or textureGrad() texture sampling with custom screen-space derivatives, the result appears to use nearest-neighbor (NN) mip-map sampling. This results in harsh transitions between mip-map levels.

Here's a screenshot showing the actual behavior for textureGrad().
This screenshot uses the vulkan graphics driver.
image

This was tested on different graphics drivers with Slang shaders. The behavior is as follows:

  • vulkan: NN mip-map sampling as seen above.
  • glcore: Same as vulkan.
  • d3d11: Mip-map sampling is wholly ignored both for textureGrad() and textureLod().
  • d3d12 Same as d3d11.

Here's a screenshot that uses the d3d12 driver:
image

Expected behavior

I would expect that textureGrad() uses proper tri-linear interpolation for smooth anti-aliasing / anisotropy behavior.

Here's a simulation of the expected behavior using two texture taps and manual interpolation in the shader code:
image

Actual behavior

See description.

Steps to reproduce the bug

Here's a minimal fragment shader that shows the issue with textureLod():

void main() {
    vec3 res =
        textureLod(Source, vTexCoord, vTexCoord.x * 8.0).rgb;
    FragColor = vec4(res, 1.0);
}

Here's a minimal fragment shader that shows the issue with textureGrad():

void main() {
    vec3 res =
        textureGrad(Source, vTexCoord,
                    vec2(log2(1.0 - vTexCoord.x) / 32.0, 0.0), dFdy(vTexCoord))
            .rgb;
    FragColor = vec4(res, 1.0);
}

Make sure to use these minimal examples with a preset that computes mip-map levels properly.
Currently, this necessitates a primary "stock" pass to produce the mip-mapping in the first place.
Example:

shaders = 2

shader0 = stock.slang
filter_linear0 = false
scale_type0 = source

shader1 = tri_linear_bug.slang
filter_linear1 = true
scale_type1 = viewport
mipmap_input1 = true

The code for the manual interpolation using 2 taps is as follows:

void main() {
    float lod = vTexCoord.x * 8.0;
    vec3 res_1 = textureLod(Source, vTexCoord, floor(lod)).rgb;
    vec3 res_2 = textureLod(Source, vTexCoord, floor(lod) + 1.0).rgb;
    FragColor = vec4(mix(res_1, res_2, fract(lod)), 1.0);
}
Bisect Results

Did not do a bisect.

Version/Commit

RA version 1.18.0
Commit hash 72a10eda9a

Environment information
  • OS: Win 11
  • Compiler: Downloaded from public release

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the minimal textureLod() and textureGrad() shaders with the stock pass and mipmap_input1=true on the Vulkan, glcore, d3d11, and d3d12 drivers. Compare each result with the expected trilinear interpolation and the manual two-tap interpolation; done means the built-in sampling behaves consistently across the listed drivers.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
computer-graphics
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.