Почему эквивалентные шейдеры выдают разные результаты? - PullRequest
4 голосов
/ 21 сентября 2019

Давайте рассмотрим этот 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()

После запуска вы увидите, что сгенерированы эти 2 фрагментных шейдера (вы можете переключаться между ними, нажимая клавишу 'w'):

------------------------------------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);
}

Можно ожидать, что оба шейдера выдают одинаковый вывод.К сожалению, это не тот случай, если вы запустите этот код, вы увидите, как оба выглядят действительно по-разному:

enter image description here enter image description here

ВОПРОС :

  • Если вы можете воспроизвести это там, как вы думаете, что может быть причиной для получения этих различий?Это может быть ошибка драйвера?
  • Но самое главное ... даже если это была ошибка драйвера, как я мог предотвратить это неправильное поведение с текущими драйверами?

Основная причина в том, что я хотел бы, чтобы мои однородные переменные были жестко закодированы в шейдере без каких-либо других результатов после настройки моих шейдеров.

Спецификации, где вы можетеВоспроизведите ошибку

  • GeForce GTX 970M / PCIe / SSE2 и 4.4.0 NVIDIA 344.42 и win7 ultimate
  • GeForce GTX 1060 6 ГБ / PCIe / SSE2 & NVIDIA 435.21 и Debian9.11
  • GeForce RTX 2070 & драйвер 26.21.14.3160 ​​16.07.2009 & windows

Спецификации, где ошибка не будет отображаться

  • Radeon Pro 560 и яблоко, запеченное в драйверах и macOS Mojave (10.14.4)
...