c++ - GPU 上的 HSL 图像调整

标签 c++ c image-processing glsl shader

我有一个应用程序,用户应该能够使用 slider 修改图像的色调、饱和度和亮度。所有图像处理均使用 GLSL 片段着色器在 GPU 上完成。

我的问题是,由于广泛的分支,RGB -> HSL -> RGB 转换在 gpu 上相当昂贵。

我的问题是我是否可以将用户的“颜色调整”转换为其他颜色空间,这样可以更有效地在 GPU 上计算调整后的图像。

最佳答案

假设在 GPU 中 分支和在代码中 分支是一回事是错误的。

对于简单的条件,根本没有任何分支。 GPU 具有条件移动指令,可直接转换为三元表达式和简单的 if-else 语句。

当您有嵌套条件或多个条件相关操作时,事情就会变得有问题。然后你必须考虑 GLSL 编译器是否足够聪明,可以将它全部翻译成 cmoves。只要有可能,编译器就会发出执行所有分支的代码,并将结果与​​条件移动重新组合,但它并不总是这样做。

你必须知道什么时候帮助它。永远不要猜测何时可以测量 - 使用 AMD 的 GPU Shader Analyzer 或 Nvidia 的 GCG 查看装配输出。 GPU 的指令集非常有限且简单,所以不要害怕“汇编”这个词。

这里有一对 RGB/HSL 转换函数,我已经对其进行了更改,因此它们可以很好地与 AMD 的 GLSL 编译器以及汇编输出一起使用。原始 C 转换代码归功于 Paul Bourke。

// HSL range 0:1
vec4 convertRGBtoHSL( vec4 col )
{
    float red   = col.r;
    float green = col.g;
    float blue  = col.b;

    float minc  = min3( col.r, col.g, col.b );
    float maxc  = max3( col.r, col.g, col.b );
    float delta = maxc - minc;

    float lum = (minc + maxc) * 0.5;
    float sat = 0.0;
    float hue = 0.0;

    if (lum > 0.0 && lum < 1.0) {
        float mul = (lum < 0.5)  ?  (lum)  :  (1.0-lum);
        sat = delta / (mul * 2.0);
    }

    vec3 masks = vec3(
        (maxc == red   && maxc != green) ? 1.0 : 0.0,
        (maxc == green && maxc != blue)  ? 1.0 : 0.0,
        (maxc == blue  && maxc != red)   ? 1.0 : 0.0
    );

    vec3 adds = vec3(
              ((green - blue ) / delta),
        2.0 + ((blue  - red  ) / delta),
        4.0 + ((red   - green) / delta)
    );

    float deltaGtz = (delta > 0.0) ? 1.0 : 0.0;

    hue += dot( adds, masks );
    hue *= deltaGtz;
    hue /= 6.0;

    if (hue < 0.0)
        hue += 1.0;

    return vec4( hue, sat, lum, col.a );
}

此函数的汇编输出:

 1  x: MIN         ____,    R0.y,   R0.z      
    y: ADD         R127.y, -R0.x,   R0.z      
    z: MAX         ____,    R0.y,   R0.z      
    w: ADD         R127.w,  R0.x,  -R0.y      
    t: ADD         R127.x,  R0.y,  -R0.z      
 2  y: MAX         R126.y,  R0.x,   PV1.z      
    w: MIN         R126.w,  R0.x,   PV1.x      
    t: MOV         R1.w,    R0.w      
 3  x: ADD         R125.x, -PV2.w,  PV2.y      
    y: SETE_DX10   ____,    R0.x,   PV2.y      
    z: SETNE_DX10  ____,    R0.y,   PV2.y      
    w: SETE_DX10   ____,    R0.y,   PV2.y      
    t: SETNE_DX10  ____,    R0.z,   PV2.y      
 4  x: CNDE_INT    R123.x,  PV3.y,  0.0f,   PV3.z      
    y: CNDE_INT    R125.y,  PV3.w,  0.0f,   PS3      
    z: SETNE_DX10  ____,    R0.x,   R126.y      
    w: SETE_DX10   ____,    R0.z,   R126.y      
    t: RCP_e       R125.w,  PV3.x      
 5  x: MUL_e       ____,    PS4,     R127.y      
    y: CNDE_INT    R123.y,  PV4.w,   0.0f,  PV4.z      
    z: ADD/2       R127.z,  R126.w,  R126.y      VEC_021 
    w: MUL_e       ____,    PS4,     R127.w      
    t: CNDE_INT    R126.x,  PV4.x,   0.0f,  1065353216      
 6  x: MUL_e       ____,    R127.x,  R125.w      
    y: CNDE_INT    R123.y,  R125.y,  0.0f,  1065353216      
    z: CNDE_INT    R123.z,  PV5.y,   0.0f,  1065353216      
    w: ADD         ____,    PV5.x,   (0x40000000, 2.0f).y      
    t: ADD         ____,    PV5.w,   (0x40800000, 4.0f).z      
 7  x: DOT4        ____,    R126.x,  PV6.x      
    y: DOT4        ____,    PV6.y,   PV6.w      
    z: DOT4        ____,    PV6.z,   PS6      
    w: DOT4        ____,    (0x80000000, -0.0f).x,  0.0f      
    t: SETGT_DX10  R125.w,  0.5,     R127.z      
 8  x: ADD         R126.x,  PV7.x,   0.0f      
    y: SETGT_DX10  ____,    R127.z,  0.0f      
    z: ADD         ____,   -R127.z,  1.0f      
    w: SETGT_DX10  ____,    R125.x,  0.0f      
    t: SETGT_DX10  ____,    1.0f,    R127.z      
 9  x: CNDE_INT    R127.x,  PV8.y,   0.0f,   PS8      
    y: CNDE_INT    R123.y,  R125.w,  PV8.z,  R127.z      
    z: CNDE_INT    R123.z,  PV8.w,   0.0f,   1065353216      
    t: MOV         R1.z,    R127.z      
10  x: MOV*2       ____,    PV9.y      
    w: MUL         ____,    PV9.z,   R126.x      
11  z: MUL_e       R127.z,  PV10.w,  (0x3E2AAAAB, 0.1666666716f).x      
    t: RCP_e       ____,    PV10.x      
12  x: ADD         ____,    PV11.z,  1.0f      
    y: SETGT_DX10  ____,    0.0f,    PV11.z      
    z: MUL_e       ____,    R125.x,  PS11      
13  x: CNDE_INT    R1.x,    PV12.y,  R127.z,  PV12.x      
    y: CNDE_INT    R1.y,    R127.x,  0.0f,    PV12.z  

请注意,这里没有分支指令。一路上都是有条件的移动,几乎和我写的一模一样。

条件移动所需的硬件只是一个二进制比较器(每位 5 个门)和一堆跟踪。非常快。

另一个值得注意的有趣的事情是没有分界线。相反,编译器使用了近似倒数和乘法指令。很多时候它也会为 sqrt 操作执行此操作。您可以使用(例如)SSE rcpps 和 rsqrtps 指令在 CPU 上使用相同的技巧。

现在逆向操作:

// HSL [0:1] to RGB [0:1]
vec4 convertHSLtoRGB( vec4 col )
{
    const float onethird = 1.0 / 3.0;
    const float twothird = 2.0 / 3.0;
    const float rcpsixth = 6.0;

    float hue = col.x;
    float sat = col.y;
    float lum = col.z;

    vec3 xt = vec3(
        rcpsixth * (hue - twothird),
        0.0,
        rcpsixth * (1.0 - hue)
    );

    if (hue < twothird) {
        xt.r = 0.0;
        xt.g = rcpsixth * (twothird - hue);
        xt.b = rcpsixth * (hue      - onethird);
    } 

    if (hue < onethird) {
        xt.r = rcpsixth * (onethird - hue);
        xt.g = rcpsixth * hue;
        xt.b = 0.0;
    }

    xt = min( xt, 1.0 );

    float sat2   =  2.0 * sat;
    float satinv =  1.0 - sat;
    float luminv =  1.0 - lum;
    float lum2m1 = (2.0 * lum) - 1.0;
    vec3  ct     = (sat2 * xt) + satinv;

    vec3 rgb;
    if (lum >= 0.5)
         rgb = (luminv * ct) + lum2m1;
    else rgb =  lum    * ct;

    return vec4( rgb, col.a );
}

(2013 年 7 月 5 日编辑:原始翻译此函数时我犯了一个错误。程序集也已更新)。

汇编输出:

1   x: ADD         ____,   -R2.x,  1.0f      
    y: ADD         ____,    R2.x,  (0xBF2AAAAB, -0.6666666865f).x      
    z: ADD         R0.z,   -R2.x,  (0x3F2AAAAB, 0.6666666865f).y      
    w: ADD         R0.w,    R2.x,  (0xBEAAAAAB, -0.3333333433f).z      
2   x: SETGT_DX10  R0.x,    (0x3F2AAAAB, 0.6666666865f).x,  R2.x      
    y: MUL         R0.y,    PV2.x,  (0x40C00000, 6.0f).y      
    z: MOV         R1.z,    0.0f      
    w: MUL         R1.w,    PV2.y,  (0x40C00000, 6.0f).y      
3   x: MUL         ____,    R0.w,  (0x40C00000, 6.0f).x      
    y: MUL         ____,    R0.z,  (0x40C00000, 6.0f).x      
    z: ADD         R0.z,   -R2.x,  (0x3EAAAAAB, 0.3333333433f).y      
    w: MOV         ____,    0.0f      
4   x: CNDE_INT    R0.x,    R0.x,   R0.y,  PV4.x      
    y: CNDE_INT    R0.y,    R0.x,   R1.z,  PV4.y      
    z: CNDE_INT    R1.z,    R0.x,   R1.w,  PV4.w      
    w: SETGT_DX10  R1.w,    (0x3EAAAAAB, 0.3333333433f).x,  R2.x      
5   x: MUL         ____,    R2.x,   (0x40C00000, 6.0f).x      
    y: MUL         ____,    R0.z,   (0x40C00000, 6.0f).x      
    z: ADD         R0.z,   -R2.y,   1.0f      
    w: MOV         ____,    0.0f      
6   x: CNDE_INT    R127.x,  R1.w,   R0.x,  PV6.w      
    y: CNDE_INT    R127.y,  R1.w,   R0.y,  PV6.x      
    z: CNDE_INT    R127.z,  R1.w,   R1.z,  PV6.y      
    w: ADD         R1.w,   -R2.z,   1.0f      
7   x: MULADD      R0.x,    R2.z,   (0x40000000, 2.0f).x, -1.0f      
    y: MIN*2       ____,    PV7.x,  1.0f      
    z: MIN*2       ____,    PV7.y,  1.0f      
    w: MIN*2       ____,    PV7.z,  1.0f      
8   x: MULADD      R1.x,    PV8.z,  R2.y,    R0.z      
    y: MULADD      R127.y,  PV8.w,  R2.y,    R0.z      
    z: SETGE_DX10  R1.z,    R2.z,            0.5      
    w: MULADD      R0.w,    PV8.y,  R2.y,    R0.z      
9   x: MULADD      R0.x,    R1.w,   PV9.x,   R0.x      
    y: MULADD      R0.y,    R1.w,   PV9.y,   R0.x      
    z: MUL         R0.z,    R2.z,   PV9.y      
    w: MULADD      R1.w,    R1.w,   PV9.w,   R0.x      
10  x: MUL         ____,    R2.z,   R0.w      
    y: MUL         ____,    R2.z,   R1.x      
    w: MOV         R2.w,    R2.w       
11  x: CNDE_INT    R2.x,    R1.z,   R0.z,    R0.y      
    y: CNDE_INT    R2.y,    R1.z,   PV11.y,  R0.x      
    z: CNDE_INT    R2.z,    R1.z,   PV11.x,  R1.w  

同样没有分支。好吃!

关于c++ - GPU 上的 HSL 图像调整,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4728581/

相关文章:

c++ - 定义清晰但角度变化的图像的图像识别

android - 除了比较像素和字节之外,还有其他方法可以在 Android 中比较位图吗?

c++ - 如何为这个函数 c++ 提供正确的返回类型?

c++ - Qt 滚动条不会出现在自定义小部件上

c++ - 具有 FFT 卷积的低通 FIR 滤波器 - 重叠添加,原因和方式

c - 简单的 30 个字符缓冲区 PIPE 打印不可打印的字符,不知道为什么

在没有返回语句的情况下检查函数的返回值

python - cv2.IMREAD_GRAYSCALE 如何将 16 位图像转换为 8 位图像(OpenCV python)?

c++ - 在模板类中使用标准容器迭代器

c - 在我的代码中打印出额外的 2 行