shader-slang / slang

Making it easier to work with shaders
http://shader-slang.com
MIT License
2.19k stars 187 forks source link

Use ExplicitLod instructions for texture sampling in vertex shader #4356

Open jkwak-work opened 5 months ago

jkwak-work commented 5 months ago

Problem Description Because there is no way to implicitly calculate LOD for texture sampling in the vertex shader, it causes a validation error when "ImplicitLod" variants of the texture instructions are used in vertex shader.

Goal We should use "ExplicitLod" variants in vertex shader with LOD value zero.

Repro steps The following shader code is from CTS test, "dEQP-VK.glsl.texture_functions.textureproj.sampler2d_vec4_fixed_vertex".

//TEST:SIMPLE:-target spirv-asm -stage vertex -entry main -allow-glsl

#version 450 core
layout(location = 0) in highp vec4 a_position;
layout(location = 4) in highp vec4 a_in0;
layout(location = 0) out mediump vec4 v_color;
layout(set = 0, binding = 0) uniform highp sampler2D u_sampler;
layout(set = 0, binding = 1) uniform buf0 { highp vec4 u_scale; };
layout(set = 0, binding = 2) uniform buf1 { highp vec4 u_bias; };
out gl_PerVertex {
        vec4 gl_Position;
};

void main()
{
        gl_Position = a_position;
        v_color = vec4(textureProj(u_sampler, a_in0))*u_scale + u_bias;
}

When ran, the following validation error message is observed,

error: line 60: OpEntryPoint Entry Point <id> '2[%main]'s callgraph contains function <id> '2[%main]', which cannot be used with the current execution model:
ImplicitLod instructions require Fragment or GLCompute execution model: ImageSampleProjImplicitLod
csyonghe commented 5 months ago

Supporting this is not trivial and require infrastructural work.

For the intrinsic definition, we should be able to use __target_switch to switch to different implementation based on stage:

float4 texture(...)
{
      __target_switch
      {
      case spirv:
             __target_switch
             {
              case vertex:
                    // use explicit lod
              case fragment:
                   // use implicit lod
              ...

              }

       }
}

The tricky part is when handling modules that has both vertex and fragment shaders:

[shader("vertex")]
func vert() { texture(...); }

[shader("fragment")]
func frag() { texture(...); }

When compiling this file to spirv, we need to create two specializations of texture and then specialize them differently based on which stage they are for.

Right now our target switch specialization pass assumes there is no need to use the call graph to decide what capabilities are available, so a __target_switch on stage isn't supported. To make this work, we need to extend our target switch specilaization pass to do the following:

  1. Build the call graph.
  2. Identify all functions that includes or transitively includes a __target_switch where the switch cases contains stage-specific capability atoms.
  3. Use the call graph to create specializations of these functions for all stages they are called, and replace the callee to the correct specialization.
  4. Apply target-switch specilaization in the specialized functions, using the stage that the function is specialized for to resolve the target_swtich cases.
jkwak-work commented 5 months ago

I thought that a simple __target_switch would do the job and I didn't think about the case where a module has more than one stages.

I think I should still make the __target_switch change for now and put this issue to the backlog.

csyonghe commented 2 months ago

Instead of inventing the whole stage-specialization mechanism, we should just mark this function as fragment only.