使用OpenGL计算着色器的朴素框模糊非常慢

时间:2017-04-09 10:48:35

标签: c++ opengl glsl compute-shader

我以前使用片段着色器进行图像处理,但是我想通过使用计算着色器来避免现在设置全屏四边形渲染所需的额外代码。我用以下方式编写了一个简单的框模糊实现:

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>

#include <iostream>
#include <vector>

int main() {
    glfwInit();

    GLFWwindow* window = glfwCreateWindow(512, 512, "Dummy", nullptr, nullptr);
    glfwMakeContextCurrent(window);

    gladLoadGL();

    int width, height, channels;
    unsigned char* data = stbi_load("input.png", &width, &height, &channels, 4);

    GLuint inTexture;
    glGenTextures(1, &inTexture);
    glBindTexture(GL_TEXTURE_2D, inTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

    stbi_image_free(data);

    GLuint outTexture;
    glGenTextures(1, &outTexture);
    glBindTexture(GL_TEXTURE_2D, outTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    const char* shaderSource = R"glsl(
            #version 440 core

            layout(local_size_x = 1, local_size_y = 1) in;

            layout(rgba8, binding = 0) readonly restrict uniform image2D imageInput;
            layout(rgba8, binding = 1) writeonly restrict uniform image2D imageOutput;

            void main() {
                ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy);

                const int windowSize = 33;

                vec4 colorSum = vec4(0.0);
                float weightSum = 0.0;

                for (int x = pixelCoord.x - windowSize / 2; x <= pixelCoord.x + windowSize / 2; x++) {
                    for (int y = pixelCoord.y - windowSize / 2; y <= pixelCoord.y + windowSize / 2; y++) {
                        colorSum += imageLoad(imageInput, ivec2(x, y));
                        weightSum += 1.0;
                    }
                }

                imageStore(imageOutput, pixelCoord, colorSum / weightSum);
            }
        )glsl";

    GLuint computeShader = glCreateShader(GL_COMPUTE_SHADER);
    glShaderSource(computeShader, 1, &shaderSource, nullptr);
    glCompileShader(computeShader);

    GLuint computeProgram = glCreateProgram();
    glAttachShader(computeProgram, computeShader);
    glLinkProgram(computeProgram);

    glUseProgram(computeProgram);
    glBindImageTexture(0, inTexture, 0, false, 0, GL_READ_ONLY, GL_RGBA8);
    glBindImageTexture(1, outTexture, 0, false, 0, GL_WRITE_ONLY, GL_RGBA8);

    double start = glfwGetTime();

    std::vector<unsigned char> buffer(width * height * 4);

    for (int i = 0; i < 20; i++) {
        glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
        glDispatchCompute(512, 512, 1);
        glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

        glBindTexture(GL_TEXTURE_2D, outTexture);
        glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer.data());
    }

    double end = glfwGetTime();

    std::cerr << "compute time: " << (end - start) << std::endl;

    stbi_write_png("output.png", width, height, 3, buffer.data(), 0);

    glfwTerminate();

    return 0;
}

我知道这个盒子模糊实现不是最佳的,但我还是会在此之后实现其他过滤器,如双边过滤器。

当我将其实现为片段着色器并将输出渲染到渲染缓冲区时,我会得到大约800 FPS,几乎完全相同的着色器代码。我期望计算着色器同样快,但以这种方式处理512x512图像需要半秒钟!我确保在驱动程序通过运行计算操作20次来推迟某些操作时没有初始减速,但这会导致等待20秒的挂钟时间。

我承认我并不十分熟悉确定全局和本地工作组大小的最佳方法,但这似乎是大多数教程采用的方法。为每个像素和一些少量的本地工作组(如2x2,4x4或8x8)设置工作组。但是,我发现使用任何高于1x1的本地工作组会导致性能更差。

我还认为内存访问可能是瓶颈,所以我尝试用vec4(1.0, 0.0, 0.0, 1.0)作为测试替换imageLoad,但这只会将运行时间缩短到150毫秒左右,这仍然是不可接受的。

什么可能导致我的计算着色器如此慢?

2 个答案:

答案 0 :(得分:3)

我可以提出一些建议。

  1. 使用sampler2DtexelFetch()而不是image2DimageLoad()一起阅读。这样您就可以从纹理缓存中受益。

  2. 本地工作组大小应为硬件的warp / wavefront大小的倍数。对于NVidia来说它是32,对于AMD来说它是64,所以8x8本地工作组是一个不错的选择。我知道你已经尝试了它并且它使事情变得更糟,但结合其他建议应该有所帮助。

  3. 考虑将大小为 wogkroup_dims + window_dims 的矩形像素区域提取到共享数组中,然后在进行卷积时从该数组中读取。这样,您可以最大限度地减少昂贵的纹理提取次数,并以更便宜的共享内存访问权限替换它们。使用这种方法时,使用更大的本地工作组大小(可能是16x16)是有意义的。此方法需要使用GLSL barrier()memoryBarrierShared()函数。

答案 1 :(得分:0)

试试......

glDispatchCompute(32,32,1);

布局(local_size_x = 16,local_size_y = 16)in;

我只是想弄清楚这些东西,这些设置对我来说最快 在512x512图像上。

-CdS