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

标签 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 毫秒左右,这仍然是 Not Acceptable 。

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

最佳答案

我可以建议一些可以尝试的事情。

  1. 使用 texelFetch()sampler2D 读取,而不是使用 imageLoad()image2D 读取。这样您就可以从纹理缓存中受益。

  2. 本地工作组大小应该是硬件的扭曲/波前大小的倍数。 NVidia 为 32,AMD 为 64,因此 8x8 本地工作组是一个不错的选择。我知道您已经尝试过了,但它让事情变得更糟,但结合其他建议应该会有所帮助。

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

关于c++ - 使用 OpenGL 计算着色器的朴素框模糊非常慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43305828/

相关文章:

c++ - 如何从 C++ 类中的构造函数为 Arduino 分配字符串字段?

c++ - 在 Qt 中编译一个快速项目?

c++ - 如何使用点图元将纹理映射到由参数方程渲染的球体

opengl - 着色器中没有名称为 'u_proj' 的制服

c++ - 当给定相同的字符串输入时, `std::atof` 是否保证产生相同的输出?

c++ - 我在数组函数中的 Min 和 Max 数字显示除 Max 和 Min 之外的任何内容

opengl glRasterPos*() 更改参数

opengl - 在 OpenGL 中,有没有办法获取着色器程序使用的所有制服和属性的列表?

opengl - 修复 Nvidia 和 AMD 的 GLSL 着色器

c++ - OpenGL 批处理渲染器中的纹理出血/损坏