簡體   English   中英

為什么`exact`限定符沒有生效?

[英]Why does `precise` qualifier not take effect?

我正在努力改進Henry Thasler的GLSL雙精度算術實現(來自他的GLSL Mandelbrot演示),以便可靠地在Linux上的NVIDIA圖形上工作。 我最近了解到,自OpenGL的4.0(§4.7的精確預選賽 規范 )或GL_ARB_gpu_shader5擴展名( 規格 ),我們可以使用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程序集:

  • 沒有hide且沒有precise天真案例:
!!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版本是3.30,與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上內聯(它們有更好的優化,至少在歷史上),所以我想編譯器可以執行內聯,然后如果將結果存儲到非精確變量中,那么所有現有的精確限定符都是忽略。

您的着色器沒有問題。 ds_add()代碼沒有任何可以在編譯時合並的操作。 通常添加和乘/除合並。 但是您的代碼只能添加操作。

更新:

  1. 在計算過程中,所有變量都存儲在GPU寄存器中。 寄存器的操作順序不依賴於代碼或編譯器。 它甚至不依賴於硬件。 它取決於當前在GPU中運行的操作。

  2. 寄存器之間的浮點運算精度不是嚴格的32位。 它通常高於。 GPU的實際精度是商業秘密。 盡管變量存儲在32位存儲器中,但x86 FPU的實際精度為80位或128位。

  3. 但是,GPU並非設計用於非常精確的計算。 該算法的作者知道它並實現了32位浮點數的雙思想對。 如果你需要提高精度,那么你必須使用長雙精度和32位浮點數。 簡單的“精確”無濟於事。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM