移动GPU上的扩展浮点精度

时间:2015-02-28 12:00:23

标签: opengl-es opengl-es-2.0 gpu floating-point-precision post-processing

我试图使用opengl-es 2.0计算gpu上图像的梯度矢量场。我找到了一个cpu实现,我用它来比较我的gpu实现。这里的挑战是cpu实现依赖于java类型的float(32位),而我的gpu实现使用lowp float(8位)。我知道我可以使用mediump或highp来获得更好的结果,但我仍然希望继续使用lowp float来确保我的代码能够在最差的硬件上运行。

计算梯度矢量场的前几个步骤非常简单:

  1. 计算标准化灰度(红色+绿色+蓝色)/3.0
  2. 计算边缘图(右像素左像素)/2.0和(向上像素向下像素)/2.0
  3. 计算laplacian(稍微复杂但现在没有必要详细说明)
  4. 目前,在没有做任何花哨的事情的情况下,我能够完全模仿第1步,这样cpu实现的图像结果与gpu中的结果相同。

    不幸的是,我已经停留在第2步,因为我的边缘地图计算在gpu上不够准确。

    所以我试图实现一个扩展的精度浮点,灵感来自http://andrewthall.org/papers/df64_qf128.pdf

    我对opengl-es相当新,所以我甚至不确定我是否在这里做了正确的事情,但下面是我打算编码的操作,以便计算出这种精确度损失。 ;目前正在遭受痛苦。

        vec2 split(float a)
    {
        float   t   =   a * (2e-8+1.0);
        float   aHi =   t - (t -a);
        float   aLo =   a - aHi;
    
        return vec2(aHi,aLo);
    }
    
    vec2 twoProd(float a, float b)
    {
        float   p   = a * b;
        vec2    aS  = split(a);
        vec2    bS  = split(b);
        float   err = ( ( (aS.x * bS.x) - p) + (aS.x * bS.y) + (aS.y * bS.x) ) + (aS.y * bS.y);
    
        return vec2(p,err);
    }
    
    vec2 FMAtwoProd(float a,float b)
    {
        float   x   =   a * b;
        float   y   =   a * b - x;
    
        return vec2(x,y);
    }
    
    vec2 div(vec2 a, vec2 b)
    {
        float   q   = a.x / b.x;
        vec2    res = twoProd( q , b.x );
        float   r   = ( a.x - res.x ) - res.y ;
    
        return vec2(q,r);
    }
    
    vec2 div(vec2 a, float b)
    {
        return div(a,split(b));
    }
    
    vec2 quickTwoSum(float a,float b)
    {
        float   s   =   a + b;
        float   e   =   b - (s-a);
    
        return vec2(s,e);
    }
    
    vec2 twoSum(float a,float b)
    {
        float   s   =   a + b;
        float   v   =   s - a;
        float   e   =   ( a - (s - v)) + ( b - v );
    
        return vec2(s,e);
    }
    
    vec2 add(vec2 a, vec2 b)
    {
        vec2    s   =   twoSum(a.x , b.x);
        vec2    t   =   twoSum(a.y , b.y);
    
        s.y     +=  t.x;
        s       =   quickTwoSum(s.x,s.y);
        s.y     +=  t.y;
        s       =   quickTwoSum(s.x,s.y);
    
        return s;
    }
    
    vec2 add(vec2 a,float b)
    {
        return add(a,split(b));
    }
    
    vec2 mult2(vec2 a,vec2 b)
    {
        vec2    p   =   twoProd(a.x,b.x);
        p.y     +=  a.x * b.y;
        p.y     +=  a.y * b.x;
        p       =   quickTwoSum(p.x,p.y);
    
        return p;
    }
    
    vec2 mult(vec2 a,float b)
    {
        return mult2(a, split(b));
    }
    

    显然,我必须在这里做错事或错过一些非常基本的概念,因为我使用简单操作或扩展浮点运算得到相同的结果...

1 个答案:

答案 0 :(得分:0)

  

这里的挑战是cpu实现依赖于java类型的float(32位),而我的gpu实现使用的是lowp float(8位)。

lowp实际上并不意味着用于浮点运算的位数。它更多地与必须可表达的值范围和最小可区分值(精度)有关 - 您可以使用它来计算最小位数,但GLSL从未如此讨论它

  

目前,在没有做任何花哨的事情的情况下,我能够完全模仿第1步,这样cpu实现的图像结果与gpu中的结果相同。

这很幸运,因为您的描述中的一个直接问题来自lowp仅保证代表[ -2.0 2.0 ]。如果您尝试将低精度浮点值除以 3 (如步骤1 中所示)来标准化,则可能有效,也可能无效。在最坏的情况下,这将不起作用,因为浮点值永远不会达到 3.0 。但是,在某些GPU上可能会有效,因为lowpmediump之间可能没有区别,或者GPU的lowp可能超出了GLSL ES 4.5.2 Precision Qualifiers中列出的最低要求1.00规范。

  

...我仍然希望继续使用lowp float来确保我的代码能够在最差的硬件上运行。

如果您要定位最低端硬件,请记住ES 2.0在所有着色器阶段都需要mediump支持。 lowp唯一可能得到的是一些GPU的性能提升,但任何可以托管ES 2.0的GPU都是支持中等精度浮点的GPU,而你的算法需要的范围大于{{1}保证。