Skip to content

turnip: implement VK_EXT_shader_demote_to_helper_invocation and VK_KHR_shader_terminate_invocation

VK_EXT_shader_demote_to_helper_invocation:

The "demote" intrinsic has the semantics of D3D discard, which means it doesn't change the control flow, allowing derivatives to work.

On A6xx there is no known way to check whether invocation was demoted, thus we use nir_lower_is_helper_invocation which is added in the first commit.

Add "logical" OPC_DEMOTE which is later translated to "kill". Such separation is necessary to run "kill" specific optimizations which are invalid for "demote".

VK_KHR_shader_terminate_invocation is trivially implemented since it matches how we already handle discard.

@cwabbott0 We discussed in irc that kill may not match demote judging from the tests you ran some time ago. However, I don't find it to be the case, from what I see - kill does not terminate invocation.

All dEQP-VK.glsl.demote.* tests are passing and they include tests which check that dFdx and dFdy are correct when 3 out 4 invocations in quad are demoted.

Here is shader from dEQP-VK.glsl.demote.static_loop_deriv

o_color = v_color;
for (int i = 0; i < 2; i++)
{
    if (i > 0) {
        ivec2 f = ivec2(gl_FragCoord.xy);
        int lsb = (f.x | f.y) & 1;
        if (lsb != 0) demote;
        bool isHelper = helperInvocationEXT();
        highp vec2 dx = dFdx(a_one.xy + gl_FragCoord.xy);
        highp vec2 dy = dFdy(a_one.xy + gl_FragCoord.xy);
        highp float dh = dFdx(float(isHelper));
        bool valid = abs(dx.x-1.0) < 0.01 && dx.y == 0.0 && dy.x == 0.0 && abs(dy.y-1.0) < 0.01 && abs(dh-1.0) < 0.1 && !isHelper;
        if (valid) demote;
        o_color = vec4(1,0,0,1);
    }
}

And an explanation from test source code:

First demote pixels where fragCoord.xy LSBs are not both zero, leaving only one
non-helper pixel per quad. Then compute derivatives of "one+fragCoord" and check they
are 0 or 1 as appropriate. Also check that helperInvocationEXT varies in the quad and
is false on non-helper pixels. Demote the pixel if it gets the right values, so the final
image should be entirely the clear color. If we don't get the right values, output red.
This test case would not work for discard, because derivatives become undefined.

Also I did a few more tests by changing the shader in RenderDoc, and all I saw was consistent with expected demote behavior.

dEQP-VK.rasterization.frag_side_effects.color_at_*.demote are passing too.

Merge request reports