我正在尝试改进Henry Thasler的GLSL实现的双-单算法(来自他的GLSL Mandelbrot演示),以在Linux上的NVIDIA图形上可靠地工作。我最近了解到,从OpenGL 4.0(第4.7节the spec中的精确限定符)或带有GL_ARB_gpu_shader5扩展名(spec)开始,我们可以使用precise限定符来使计算遵循GLSL源中指定的精确算术序列。

但是,以下尝试似乎并没有带来任何改善:

#version 330
#extension GL_ARB_gpu_shader5 : require

vec2 ds_add(vec2 dsa, vec2 dsb)
{
    precise float t1 = dsa.x + dsb.x;
    precise float e = t1 - dsa.x;
    precise float t2 = ((dsb.x - e) + (dsa.x - (t1 - e))) + dsa.y + dsb.y;

    precise vec2 dsc;
    dsc.x = t1 + t2;
    dsc.y = t2 - (dsc.x - t1);
    return dsc;
}

结果与没有添加precise的结果相同。我检查了算法本身是否正确:它可以在Intel Core i7-4765T内置图形上按原样运行(即使没有precise),并且如果我隐藏了一些禁止优化的变量,那么NVidia也会给出正确的结果。这是我禁止优化的方法:

#version 330

#define hide(x) ((x)*one)
uniform float one=1;

vec2 ds_add(vec2 dsa, vec2 dsb)
{
    float t1 = dsa.x + dsb.x;
    float e = hide(t1) - dsa.x;
    float t2 = ((dsb.x - e) + (dsa.x - (t1 - e))) + dsa.y + dsb.y;

    vec2 dsc;
    dsc.x = t1 + t2;
    dsc.y = t2 - (hide(dsc.x) - t1);
    return dsc;
}

因此,显然,我使用的precise限定词不正确。但是这里到底有什么问题呢?

作为引用,我将NVidia GeForce GTX 750Ti与二进制nvidia驱动程序390.116结合使用。这是完整的C++测试:

#include <cmath>
#include <vector>
#include <string>
#include <limits>
#include <iomanip>
#include <iostream>
// glad.h is generated by the following command:
// glad --out-path=. --generator=c --omit-khrplatform --api="gl=3.3" --profile=core --extensions=
#include "glad/glad.h"
#include <GL/freeglut.h>
#include <glm/glm.hpp>
using glm::vec4;

GLuint vao, vbo;
GLuint texFBO;
GLuint program;
GLuint fbo;
int width=1, height=2;

void printShaderOutput(int texW, int texH)
{
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texFBO);

    std::vector<vec4> data(texW*texH);
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, data.data());
    std::cout << "a,b,sum,relError(sum),note\n";
    for(int i=0;i<width;++i)
    {
        const auto a=double(data[i+width*0].x)+double(data[i+width*0].y);
        const auto b=double(data[i+width*0].z)+double(data[i+width*0].w);
        const auto sum=double(data[i+width*1].x)+double(data[i+width*1].y);
        const auto trueSum=a+b;
        const auto sumErr=(sum-trueSum)/trueSum;
        std::cout << std::setprecision(std::numeric_limits<double>::max_digits10)
                  << a << ',' << b << ','
                  << sum << ','
                  << std::setprecision(3)
                  << sumErr << ','
                  << (std::abs(sumErr)>1e-14 ? "WARN" : "OK")
                  << '\n';
    }
    std::cout.flush();
}

GLuint makeShader(GLenum type, std::string const& srcStr)
{
    const auto shader=glCreateShader(type);
    const GLint srcLen=srcStr.size();
    const GLchar*const src=srcStr.c_str();
    glShaderSource(shader, 1, &src, &srcLen);
    glCompileShader(shader);
    GLint status=-1;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    assert(glGetError()==GL_NO_ERROR);
    assert(status);
    return shader;
}

void loadShaders()
{
    program=glCreateProgram();

    const auto vertexShader=makeShader(GL_VERTEX_SHADER, 1+R"(
#version 330
in vec4 vertex;
void main() { gl_Position=vertex; }
)");
    glAttachShader(program, vertexShader);

    const auto fragmentShader=makeShader(GL_FRAGMENT_SHADER, 1+R"(
#version 330
#extension GL_ARB_gpu_shader5 : require

vec2 ds_add(vec2 dsa, vec2 dsb)
{
    precise float t1 = dsa.x + dsb.x;
    precise float e = t1 - dsa.x;
    precise float t2 = ((dsb.x - e) + (dsa.x - (t1 - e))) + dsa.y + dsb.y;

    precise vec2 dsc;
    dsc.x = t1 + t2;
    dsc.y = t2 - (dsc.x - t1);
    return dsc;
}

uniform vec2 a, b;
out vec4 color;

void main()
{
    if(gl_FragCoord.y<1)   // first row
        color=vec4(a,b);
    else if(gl_FragCoord.y<2)   // second row
        color=vec4(ds_add(a,b),0,0);
}

)");
    glAttachShader(program, fragmentShader);

    glLinkProgram(program);
    GLint status=0;
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    assert(glGetError()==GL_NO_ERROR);
    assert(status);

    glDetachShader(program, fragmentShader);
    glDeleteShader(fragmentShader);

    glDetachShader(program, vertexShader);
    glDeleteShader(vertexShader);
}

void setupBuffers()
{
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    const GLfloat vertices[]=
    {
        -1, -1,
         1, -1,
        -1,  1,
         1,  1,
    };
    glBufferData(GL_ARRAY_BUFFER, sizeof vertices, vertices, GL_STATIC_DRAW);
    constexpr GLuint attribIndex=0;
    constexpr int coordsPerVertex=2;
    glVertexAttribPointer(attribIndex, coordsPerVertex, GL_FLOAT, false, 0, 0);
    glEnableVertexAttribArray(attribIndex);
    glBindVertexArray(0);
}

bool init()
{
    if(!gladLoadGL())
    {
        std::cerr << "Failed to initialize GLAD\n";
        return false;
    }
    if(!GLAD_GL_VERSION_3_3)
    {
        std::cerr << "OpenGL 3.3 not supported\n";
        return false;
    }

    glGenTextures(1, &texFBO);
    glGenFramebuffers(1,&fbo);

    loadShaders();
    setupBuffers();

    glViewport(0,0,width,height);

    glBindTexture(GL_TEXTURE_2D,texFBO);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA32F,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,nullptr);
    glBindTexture(GL_TEXTURE_2D,0);
    glBindFramebuffer(GL_FRAMEBUFFER,fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texFBO,0);
    const auto status=glCheckFramebufferStatus(GL_FRAMEBUFFER);
    assert(status==GL_FRAMEBUFFER_COMPLETE);
    glBindFramebuffer(GL_FRAMEBUFFER,0);

    return true;
}

void display()
{
    const static bool inited=init();
    if(!inited) std::exit(1);

    glBindFramebuffer(GL_FRAMEBUFFER,fbo);

    glUseProgram(program);
#define SPLIT_DOUBLE_TO_FLOATS(x) GLfloat(x),GLfloat(x-GLfloat(x))
    glUniform2f(glGetUniformLocation(program,"a"),SPLIT_DOUBLE_TO_FLOATS(3.1415926535897932));
    glUniform2f(glGetUniformLocation(program,"b"),SPLIT_DOUBLE_TO_FLOATS(2.7182818284590452));
    glUniform1f(glGetUniformLocation(program,"rtWidth"),width);

    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindVertexArray(0);

    printShaderOutput(width, height);
    std::exit(0);

    glFinish();
}

int main(int argc, char** argv)
{
    glutInitContextVersion(3,3);
    glutInitContextProfile(GLUT_CORE_PROFILE);
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_RGB);

    glutInitWindowSize(width, height);
    glutCreateWindow("Test");
    glutDisplayFunc(display);

    glutMainLoop();
}

在不同情况下,我已经能够从GLSL程序二进制文件中提取NVfp5.0程序集:
  • 不含hideprecise的朴素案例:
  • !!NVfp5.0
    OPTION NV_internal;
    OPTION NV_bindless_texture;
    PARAM c[2] = { program.local[0..1] };
    TEMP R0;
    TEMP T;
    TEMP RC, HC;
    OUTPUT result_color0 = result.color;
    SLT.F R0.x, fragment.position.y, {1, 0, 0, 0};
    TRUNC.U.CC HC.x, R0;
    IF NE.x;
    MOV.F result_color0.xy, c[0];
    MOV.F result_color0.zw, c[1].xyxy;
    ELSE;
    SLT.F R0.x, fragment.position.y, {2, 0, 0, 0};
    TRUNC.U.CC HC.x, R0;
    IF NE.x;
    ADD.F R0.y, -c[0].x, c[0].x;
    ADD.F R0.x, -c[1], c[1];
    ADD.F R0.x, R0, R0.y;
    ADD.F R0.x, R0, c[0].y;
    ADD.F R0.y, R0.x, c[1];
    ADD.F R0.x, c[0], c[1];
    ADD.F result_color0.x, R0, R0.y;
    ADD.F result_color0.y, R0, -R0;
    MOV.F result_color0.zw, {0, 0, 0, 0}.x;
    ENDIF;
    ENDIF;
    END
    
  • 带有precise的情况(注意,除了“指令”中的.PREC后缀之外,没有任何变化):
  • !!NVfp5.0
    OPTION NV_internal;
    OPTION NV_bindless_texture;
    PARAM c[2] = { program.local[0..1] };
    TEMP R0;
    TEMP T;
    TEMP RC, HC;
    OUTPUT result_color0 = result.color;
    SLT.F R0.x, fragment.position.y, {1, 0, 0, 0};
    TRUNC.U.CC HC.x, R0;
    IF NE.x;
    MOV.F result_color0.xy, c[0];
    MOV.F result_color0.zw, c[1].xyxy;
    ELSE;
    SLT.F R0.x, fragment.position.y, {2, 0, 0, 0};
    TRUNC.U.CC HC.x, R0;
    IF NE.x;
    ADD.F.PREC R0.y, -c[0].x, c[0].x;
    ADD.F.PREC R0.x, -c[1], c[1];
    ADD.F.PREC R0.x, R0, R0.y;
    ADD.F.PREC R0.x, R0, c[0].y;
    ADD.F.PREC R0.y, R0.x, c[1];
    ADD.F.PREC R0.x, c[0], c[1];
    ADD.F.PREC result_color0.x, R0, R0.y;
    ADD.F.PREC result_color0.y, R0, -R0;
    MOV.F result_color0.zw, {0, 0, 0, 0}.x;
    ENDIF;
    ENDIF;
    END
    
  • 带有hide的情况有效,并且显然具有不同的算术运算序列:
  • !!NVfp5.0
    OPTION NV_internal;
    OPTION NV_bindless_texture;
    PARAM c[3] = { program.local[0..2] };
    TEMP R0, R1;
    TEMP T;
    TEMP RC, HC;
    OUTPUT result_color0 = result.color;
    SLT.F R0.x, fragment.position.y, {1, 0, 0, 0};
    TRUNC.U.CC HC.x, R0;
    IF NE.x;
    MOV.F result_color0.xy, c[1];
    MOV.F result_color0.zw, c[2].xyxy;
    ELSE;
    SLT.F R0.x, fragment.position.y, {2, 0, 0, 0};
    TRUNC.U.CC HC.x, R0;
    IF NE.x;
    ADD.F R0.x, c[1], c[2];
    MAD.F R0.y, R0.x, c[0].x, -c[1].x;
    ADD.F R0.z, R0.x, -R0.y;
    ADD.F R0.z, -R0, c[1].x;
    ADD.F R0.y, -R0, c[2].x;
    ADD.F R0.y, R0, R0.z;
    ADD.F R0.y, R0, c[1];
    ADD.F R0.y, R0, c[2];
    ADD.F R1.x, R0, R0.y;
    MAD.F R0.x, R1, c[0], -R0;
    MOV.F R1.zw, {0, 0, 0, 0}.x;
    ADD.F R1.y, R0, -R0.x;
    MOV.F result_color0, R1;
    ENDIF;
    ENDIF;
    END
    

    最佳答案

    尽管您可能会从这里学习OpenCL或CUDA受益,但我从未使用过精确的自己。

    无论如何,您的GLSL version is 3.30, which is tied with OpenGL 3.3。扩展程序可以提供精确的限定词,但是如果可以的话,我将始终尝试使用OpenGL的内置功能。

    扩展的实现方式可能不同,建议您至少使用GLSL 4.0版,最好是最新的OpenGL/GLSL版本。

    有时,如果没有人使用,这些旧扩展可以在较新的GPU上进行回归。

    GPU编译器在优化方面往往更加自由。您可能会从查看已编译的着色器的输出中受益,也许可以通过某种方式查看带有GLSL的Nvidia编译器的PTX程序集输出。使用CUDA,您绝对可以预览程序集输出,以确保编译器不会对操作进行重新排序。

    规范中提到MAD是限定符的主要原因-它将迫使编译器不使用MAD指令。也许对精确限定词进行加/减运算很少。

    如果hide为您解决了问题,最好将其命名为一天,我怀疑确切的限定词是否已在GLSL方面进行了彻底检查。我强烈建议为此使用CUDA或OpenCL,如果您也想快速显示纹理,则可以使用CL-GL互操作,这不会很痛苦。

    精确的限定词可确保不会对操作进行重新排序,但不会提及不会影响排序的优化。看来AMD只是在使用它时关闭了优化功能。 Nvidia仍可能会应用影响您的结果的优化,这些优化与操作顺序无关,而是与执行添加操作的特定优化无关。

    precise float t1 = dsa.x + dsb.x;
    precise float e = t1 - dsa.x;
    

    这可能会将e计算为dsb.x。编译器可能仍会添加不影响操作顺序的优化,因为这是规范所保证的全部。除了重新排序的操作会影响此结果之外,我没有想到其他任何方法,但是我在这里不是专家。

    要注意的另一件事是,根据我对规范的粗略阅读,可能还需要将ds_add的结果存储到精确变量中,以使计算精确。该函数可能仅在Nvidia上内联(至少在历史上它们具有更好的优化),所以我想像编译器可以执行内联,然后如果将结果存储到非精确变量中,则所有现有的精确限定词都是忽略了。

    关于c++ - 为什么 `precise`限定词不生效?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56308534/

    10-11 00:50