Why are equivalent shaders outputting different results?

BPL*_*BPL 5 python opengl shader glsl fragment-shader

Let's consider this mcve:

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import textwrap
from string import Template


def compile(shader_type, source):
    identifier = glCreateShader(shader_type)
    glShaderSource(identifier, source)
    glCompileShader(identifier)

    if not glGetShaderiv(identifier, GL_COMPILE_STATUS):
        for i, l in enumerate(source.splitlines()):
            print(f"{i+1}: {l}")
        raise Exception(glGetShaderInfoLog(identifier).decode("utf-8"))

    return identifier


def create_program(vs, fs):
    vs_identifier = compile(GL_VERTEX_SHADER, vs)
    fs_identifier = compile(GL_FRAGMENT_SHADER, fs)

    program = glCreateProgram()
    glAttachShader(program, vs_identifier)
    glAttachShader(program, fs_identifier)
    glLinkProgram(program)
    if not glGetProgramiv(program, GL_LINK_STATUS):
        raise RuntimeError(glGetProgramInfoLog(program))

    return program


def set_uniform1f(prog, name, v0):
    # print("set_uniform1f", name, glGetUniformLocation(prog, name))
    glUniform1f(glGetUniformLocation(prog, name), v0)


def set_uniform1i(prog, name, v0):
    # print("set_uniform1i", name, glGetUniformLocation(prog, name))
    glUniform1i(glGetUniformLocation(prog, name), v0)


def set_uniform2f(prog, name, v0, v1):
    # print("set_uniform2f", name, glGetUniformLocation(prog, name))
    glUniform2f(glGetUniformLocation(prog, name), v0, v1)


class Window:

    def __init__(self, w, h):
        glutInit()
        # glutInitContextVersion(3,2) # at least 3.2 is required, you can use a higer version when needed
        # glutInitContextProfile(GLUT_CORE_PROFILE)
        # glutInitContextFlags(GLUT_FORWARD_COMPATIBLE)
        glutSetOption(GLUT_MULTISAMPLE, 16)
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE)
        glutInitWindowSize(w, h)
        glutCreateWindow('Mcve uniforms')
        glutReshapeFunc(self.reshape)
        glutKeyboardFunc(self.keyboard_func)
        glutKeyboardUpFunc(self.keyboard_up_func)
        glutDisplayFunc(self.display)
        glutIdleFunc(self.idle_func)
        self.keys = {chr(i): False for i in range(256)}

        # -------- Conflicting shader exposing bug --------
        self.MAGIC_CONSTANT = 635
        vs_code = textwrap.dedent("""\
             void main()
             {
                 gl_Position = ftransform();
             }
        """)
        text = """\
            #pragma optimize (off)

            uniform vec2 resolution;
            $block_declaration

            vec3 f(vec2 x) {
                x = sin(abs(x * 0.5));
                float cs = cos(float($var_name) * 10037.5);
                float ss = sin(float($var_name) * 12.5) * 0.09;
                float t = sin(float($var_name)) * 0.5 + 0.5;
                float d = sin(10. * length(x - vec2(cs, ss)) + mix(8., 10., t));
                vec3 color = mix(vec3(0.8,0.86,0.85), vec3(0.52,0.72,0.79), sin(t) * 0.5);
                return color*pow(d, 2.) / 5.;
            }

            void main() {
                vec2 uv = (gl_FragCoord.xy / resolution.xy) * 2.0 - 1.0;
                uv.x *= (resolution.x / resolution.y);
                vec3 col = f(uv);
                gl_FragColor = vec4(col*5.0, 1.0);
            }
        """
        VAR_NAME = "my_time"
        fs_code0 = textwrap.dedent(Template(text).substitute(
            block_declaration=f"int {VAR_NAME} = {self.MAGIC_CONSTANT};",
            var_name=VAR_NAME
        ))
        fs_code1 = textwrap.dedent(Template(text).substitute(
            block_declaration=f"uniform int {VAR_NAME};",
            var_name=VAR_NAME
        ))
        print("SHADER0".center(80, '-'))
        print(fs_code0)
        print("SHADER1".center(80, '-'))
        print(fs_code1)

        # -------- Shader using time uniform --------
        self.program0 = create_program(vs_code, fs_code0)
        self.program1 = create_program(vs_code, fs_code1)

        # -------- Setup --------
        s = 1.0
        glClearColor(1, 1, 1, 1)
        glEnable(GL_DEPTH_TEST)
        glMatrixMode(GL_PROJECTION)
        glOrtho(-s, s, -s, s, -s, s)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

    def keyboard_func(self, *args):
        self.keys[args[0].decode("utf8")] = True

    def keyboard_up_func(self, *args):
        self.keys[args[0].decode("utf8")] = False

    def display(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        if self.keys['w']:
            # Case a) Using uniform
            glUseProgram(self.program0)
            set_uniform2f(self.program0, "resolution", self.width, self.height)
        else:
            # Case b) Using constant with same value than uniform
            glUseProgram(self.program1)
            set_uniform1i(self.program1, "my_time", self.MAGIC_CONSTANT)
            set_uniform2f(self.program1, "resolution", self.width, self.height)

        s = 0.5
        glBegin(GL_QUADS)
        glVertex3f(-s, -s, 0)
        glVertex3f(s, -s, 0)
        glVertex3f(s, s, 0)
        glVertex3f(-s, s, 0)
        glEnd()

        glutSwapBuffers()

    def run(self):
        glutMainLoop()

    def idle_func(self):
        glutPostRedisplay()

    def reshape(self, w, h):
        glViewport(0, 0, w, h)
        self.width = w
        self.height = h


if __name__ == '__main__':
    Window(800, 600).run()
Run Code Online (Sandbox Code Playgroud)

After running you'll see these 2 fragment shaders are generated (you can switch between them by pressing 'w' key):

------------------------------------SHADER0-------------------------------------
#pragma optimize (off)

uniform vec2 resolution;
int my_time = 635;

vec3 f(vec2 x) {

    x = sin(abs(x * 0.5));
    float cs = cos(float(my_time) * 10037.5);
    float ss = sin(float(my_time) * 12.5) * 0.09;
    float t = sin(float(my_time)) * 0.5 + 0.5;
    float d = sin(10. * length(x - vec2(cs, ss)) + mix(8., 10., t));
    vec3 color = mix(vec3(0.8,0.86,0.85), vec3(0.52,0.72,0.79), sin(t) * 0.5);
    return color*pow(d, 2.) / 5.;
}

void main() {
    vec2 uv = (gl_FragCoord.xy / resolution.xy) * 2.0 - 1.0;
    uv.x *= (resolution.x / resolution.y);
    vec3 col = f(uv);
    gl_FragColor = vec4(col*5.0, 1.0);
}

------------------------------------SHADER1-------------------------------------
#pragma optimize (off)

uniform vec2 resolution;
uniform int my_time;

vec3 f(vec2 x) {

    x = sin(abs(x * 0.5));
    float cs = cos(float(my_time) * 10037.5);
    float ss = sin(float(my_time) * 12.5) * 0.09;
    float t = sin(float(my_time)) * 0.5 + 0.5;
    float d = sin(10. * length(x - vec2(cs, ss)) + mix(8., 10., t));
    vec3 color = mix(vec3(0.8,0.86,0.85), vec3(0.52,0.72,0.79), sin(t) * 0.5);
    return color*pow(d, 2.) / 5.;
}

void main() {
    vec2 uv = (gl_FragCoord.xy / resolution.xy) * 2.0 - 1.0;
    uv.x *= (resolution.x / resolution.y);
    vec3 col = f(uv);
    gl_FragColor = vec4(col*5.0, 1.0);
}
Run Code Online (Sandbox Code Playgroud)

One would expect both shaders to give exactly the same output. Unfortunately that's not the case, if you run this code you'll see how both look really different:

在此处输入图片说明 在此处输入图片说明

QUESTION:

  • If you can reproduce it over there, what do you think that could be the reason to get these differences? Is this maybe a driver bug?
  • But the most important... even if this was a driver bug, how could I prevent this misbehaviour with the current drivers?

Main reason being I'd like to have my uniform variables as hardcoded values in the shader without getting any different results after tweaking my shaders.

Specs where you can reproduce the bug

  • GeForce GTX 970M/PCIe/SSE2 & 4.4.0 NVIDIA 344.42 & win7 ultimate
  • GeForce GTX 1060 6GB/PCIe/SSE2 & NVIDIA 435.21 & Debian 9.11
  • GeForce RTX 2070 & driver 26.21.14.3160 7/16/2019 & windows

Specs where the bug won't show up

  • Radeon Pro 560 & apple's baked in drivers & macOS Mojave (10.14.4)