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.
This should be a better source of knowledge - https://themaister.net/blog/2019/09/12/the-weird-world-of-shader-divergence-and-lod/