This fragment shader renders incorrectly when created via glShaderBinary:
#version 460 core
layout(location = 0) in vec2 vUv;
layout(location = 0) out vec4 fragColor;
void main()
{
fragColor = vec4(0.0);
bool outside =
vUv.x > 0.75 || vUv.x < 0.25;
if ( outside )
fragColor = vec4(0.0, 1.0, 0.0, 1.0);
if ( !outside )
fragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
Swapping vUv.x comparison order moves green part to the right. Rewriting the same condition to use && chain (vUv.x > 0.25 && vUv.x < 0.75)gets really weird and produces black area.
Compiling the same shader via glShaderSource produces expected output regardless of used chain or comparison ordering:
Both glsl/spv paths render correctly on AMD hardware and under Mesa3D drivers on the same NVIDIA hardware.
That’s how I build spv binaries:
glslang -G -S vert shaders/shader.vert -o shaders/shader.vert.spv
glslang -G -S frag shaders/shader.frag -o shaders/shader.frag.spv
spirv-opt --ssa-rewrite shaders/shader.frag.spv -o shaders/shader.frag.spv
Using other spirv-opt optimization flags (or not using spirv-opt at all) renders correctly. Using --print-all for attached fragment shader snippet gives the following difference.
Before --ssa-rewrite:
%32 = OpPhi %bool %24 %5 %31 %26
OpStore %outside %32
%33 = OpLoad %bool %outside // <-- gets optimized out
OpSelectionMerge %35 None
OpBranchConditional %33 %34 %35
%34 = OpLabel
OpStore %fragColor %37
OpBranch %35
%35 = OpLabel
%38 = OpLoad %bool %outside // <-- gets optimized out
%39 = OpLogicalNot %bool %38
OpSelectionMerge %41 None
OpBranchConditional %39 %40 %41
After --ssa-rewrite:
%32 = OpPhi %bool %24 %5 %31 %26
OpStore %outside %32
OpSelectionMerge %35 None
OpBranchConditional %32 %34 %35
%34 = OpLabel
OpStore %fragColor %37
OpBranch %35
%35 = OpLabel
%39 = OpLogicalNot %bool %32
OpSelectionMerge %41 None
OpBranchConditional %39 %40 %41
Even though I’m more familiar with x86 asm, reading through SPIR-V spec I don’t see the optimization spirv-opt did here is suspicious or invalid. Given other drivers render the same SPV correctly I don’t think it’s spirv-opt fault.
In my production code this bug produces artifacts in innocent snippets like if ( uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 ) because it mostly seems to evaluate only the last expression in the chain. The awkward shader I attached as a sample is the simplest reproducible form with the least amount of used spirv-opt flags.
glslang version: 11:16.3.0
spirv-opt version: v2026.2.rc2-1-g2ec8457a (initially I had old v2018 SPIR-V tools, there bug was triggered by -O flag)
I encountered this bug using relatively old NVIDIA driver somewhere from February 2026. Updating the driver didn’t fix the issue.
MRE sources & binaries:
nvidia-driver-bug-src.zip (47.0 KB)
nvidia-driver-bug-binaries.zip (416.2 KB)
Might be related:

