gfx-rs/wgpu-rs

Shader Validation Error (wgpu 0.8.1): Required uniformity of control flow for IMPLICIT_LEVEL

mitchmindtree opened this issue · 2 comments

Hi folks! While updating conrod, I've run into a validation error on an old shader that seemingly worked OK on older versions (0.7 and prior).

Here's the error:

wgpu error: Validation Error

Caused by:
    In Device::create_shader_module
      note: label = `shaders/frag.spv`
    Function [1] 'main' is invalid
    Required uniformity of control flow for IMPLICIT_LEVEL in [20] is not fulfilled because of Expression([1])


thread 'main' panicked at 'Handling wgpu errors as fatal by default', /home/mindtree/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-0.8.1/src/backend/direct.rs:1955:5
stack backtrace:
   0: std::panicking::begin_panic
             at /rustc/2fd73fabe469357a12c2c974c140f67e7cdd76d0/library/std/src/panicking.rs:519:12
   1: wgpu::backend::direct::default_error_handler
             at /home/mindtree/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-0.8.1/src/backend/direct.rs:1955:5
   2: core::ops::function::Fn::call
             at /rustc/2fd73fabe469357a12c2c974c140f67e7cdd76d0/library/core/src/ops/function.rs:70:5
   3: <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call
             at /rustc/2fd73fabe469357a12c2c974c140f67e7cdd76d0/library/alloc/src/boxed.rs:1535:9
   4: wgpu::backend::direct::ErrorSinkRaw::handle_error
             at /home/mindtree/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-0.8.1/src/backend/direct.rs:1942:9
   5: wgpu::backend::direct::Context::handle_error
             at /home/mindtree/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-0.8.1/src/backend/direct.rs:93:9
   6: <wgpu::backend::direct::Context as wgpu::Context>::device_create_shader_module
             at /home/mindtree/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-0.8.1/src/backend/direct.rs:819:13
   7: wgpu::Device::create_shader_module
             at /home/mindtree/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-0.8.1/src/lib.rs:1537:17
   8: conrod_wgpu::Renderer::with_glyph_cache_dimensions
             at /home/mindtree/programming/rust/conrod/backends/conrod_wgpu/src/lib.rs:165:22
   9: conrod_wgpu::Renderer::new
             at /home/mindtree/programming/rust/conrod/backends/conrod_wgpu/src/lib.rs:140:9
  10: all_winit_wgpu::main
             at /home/mindtree/programming/rust/conrod/backends/conrod_wgpu/examples/all_winit_wgpu.rs:64:24
  11: core::ops::function::FnOnce::call_once
             at /rustc/2fd73fabe469357a12c2c974c140f67e7cdd76d0/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

And here's the shader:

#version 450

layout(set = 0, binding = 0) uniform texture2D text_texture;
layout(set = 0, binding = 1) uniform sampler image_sampler;
layout(set = 0, binding = 2) uniform texture2D image_texture;

layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec4 v_color;
layout(location = 2) flat in uint v_mode;

layout(location = 0) out vec4 Target0;

void main() {
    // Text
    if (v_mode == uint(0)) {
        float a = texture(sampler2D(text_texture, image_sampler), v_uv).r;
        Target0 = v_color * vec4(1.0, 1.0, 1.0, a);

    // Image
    } else if (v_mode == uint(1)) {
        Target0 = texture(sampler2D(image_texture, image_sampler), v_uv);

    // 2D Geometry
    } else if (v_mode == uint(2)) {
        Target0 = v_color;
    }
}

and the SPIR-V generated by glslangValidator:

; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 10
; Bound: 63
; Schema: 0
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %main "main" %v_mode %v_uv %Target0 %v_color
               OpExecutionMode %main OriginUpperLeft
               OpSource GLSL 450
               OpName %main "main"
               OpName %v_mode "v_mode"
               OpName %a "a"
               OpName %text_texture "text_texture"
               OpName %image_sampler "image_sampler"
               OpName %v_uv "v_uv"
               OpName %Target0 "Target0"
               OpName %v_color "v_color"
               OpName %image_texture "image_texture"
               OpDecorate %v_mode Flat
               OpDecorate %v_mode Location 2
               OpDecorate %text_texture DescriptorSet 0
               OpDecorate %text_texture Binding 0
               OpDecorate %image_sampler DescriptorSet 0
               OpDecorate %image_sampler Binding 1
               OpDecorate %v_uv Location 0
               OpDecorate %Target0 Location 0
               OpDecorate %v_color Location 1
               OpDecorate %image_texture DescriptorSet 0
               OpDecorate %image_texture Binding 2
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
       %uint = OpTypeInt 32 0
%_ptr_Input_uint = OpTypePointer Input %uint
     %v_mode = OpVariable %_ptr_Input_uint Input
     %uint_0 = OpConstant %uint 0
       %bool = OpTypeBool
      %float = OpTypeFloat 32
%_ptr_Function_float = OpTypePointer Function %float
         %18 = OpTypeImage %float 2D 0 0 0 1 Unknown
%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
%text_texture = OpVariable %_ptr_UniformConstant_18 UniformConstant
         %22 = OpTypeSampler
%_ptr_UniformConstant_22 = OpTypePointer UniformConstant %22
%image_sampler = OpVariable %_ptr_UniformConstant_22 UniformConstant
         %26 = OpTypeSampledImage %18
    %v2float = OpTypeVector %float 2
%_ptr_Input_v2float = OpTypePointer Input %v2float
       %v_uv = OpVariable %_ptr_Input_v2float Input
    %v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
    %Target0 = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
    %v_color = OpVariable %_ptr_Input_v4float Input
    %float_1 = OpConstant %float 1
     %uint_1 = OpConstant %uint 1
%image_texture = OpVariable %_ptr_UniformConstant_18 UniformConstant
     %uint_2 = OpConstant %uint 2
       %main = OpFunction %void None %3
          %5 = OpLabel
          %a = OpVariable %_ptr_Function_float Function
          %9 = OpLoad %uint %v_mode
         %12 = OpIEqual %bool %9 %uint_0
               OpSelectionMerge %14 None
               OpBranchConditional %12 %13 %44
         %13 = OpLabel
         %21 = OpLoad %18 %text_texture
         %25 = OpLoad %22 %image_sampler
         %27 = OpSampledImage %26 %21 %25
         %31 = OpLoad %v2float %v_uv
         %33 = OpImageSampleImplicitLod %v4float %27 %31
         %34 = OpCompositeExtract %float %33 0
               OpStore %a %34
         %39 = OpLoad %v4float %v_color
         %41 = OpLoad %float %a
         %42 = OpCompositeConstruct %v4float %float_1 %float_1 %float_1 %41
         %43 = OpFMul %v4float %39 %42
               OpStore %Target0 %43
               OpBranch %14
         %44 = OpLabel
         %45 = OpLoad %uint %v_mode
         %47 = OpIEqual %bool %45 %uint_1
               OpSelectionMerge %49 None
               OpBranchConditional %47 %48 %56
         %48 = OpLabel
         %51 = OpLoad %18 %image_texture
         %52 = OpLoad %22 %image_sampler
         %53 = OpSampledImage %26 %51 %52
         %54 = OpLoad %v2float %v_uv
         %55 = OpImageSampleImplicitLod %v4float %53 %54
               OpStore %Target0 %55
               OpBranch %49
         %56 = OpLabel
         %57 = OpLoad %uint %v_mode
         %59 = OpIEqual %bool %57 %uint_2
               OpSelectionMerge %61 None
               OpBranchConditional %59 %60 %61
         %60 = OpLabel
         %62 = OpLoad %v4float %v_color
               OpStore %Target0 %62
               OpBranch %61
         %61 = OpLabel
               OpBranch %49
         %49 = OpLabel
               OpBranch %14
         %14 = OpLabel
               OpReturn
               OpFunctionEnd

Curiously, if I remove the conditions and just leave the first block like so:

void main() {
    float a = texture(sampler2D(text_texture, image_sampler), v_uv).r;
    Target0 = v_color * vec4(1.0, 1.0, 1.0, a);
}

It passes validation without issue. Similarly, if I leave the conditions but remove the texture lookup like so:

void main() {
    // Text
    if (v_mode == uint(0)) {
        float a = 1.0;
        Target0 = v_color * vec4(1.0, 1.0, 1.0, a);

    // Image
    } else if (v_mode == uint(1)) {
        Target0 = vec4(1.0);

    // 2D Geometry
    } else if (v_mode == uint(2)) {
        Target0 = v_color;
    }
}

This also seems to pass validation without issues.

Perhaps I'm running into some odd edge-case w.r.t doing texture lookup inside of conditional branches?

I'll add the explanation from @kvark in the wgpu room here too, in case someone hits this problem. The root cause is because we're sampling a color without a specified mip level inside of an if block.
It can be fixed by:

  • sampling outside the if blocks
  • sampling at a predefined mip level

More detailed explanation:

Let me try to explain. In order to compute an implicit LOD (mipmap level), the HW needs to know how much your texture coordinates change per pixel. This is computed from derivatives. Derivatives are computed for 2x2 blocks of rasterized pixels. So for the implicit LOD to make sense, all of the pixel shaders in that 2x2 block (a quad group) need to run this code.
Now, if you have a branch on a non-uniform condition (roughly speaking), then some of these pixel shaders may not be running this code, so the derivatives may be broken. In practice, they aren't, because the inactive invocations just compute the same code anyway, but this is implementation-dependent.
Imagine, for example, that one of the threads in this 2x2 block has run into a discard(). The HW doesn't have to keep running it, technically, so derivatives stop being defined.