OpenGL 计算着色器 - 奇怪的结果

OpenGL compute shader - strange results

我正在尝试实现用于图像处理的多通道计算着色器。 每遍都有一个输入图像和一个输出图像。 下一关的输入图像是前一关的输出。

这是我第一次在 OpenGL 中使用计算着色器,所以我的设置可能存在一些问题。 我使用 OpenCV 的 Mat 作为 read/copy 操作的容器。

代码中有一些与问题无关的部分,所以我没有包括在内。其中一些部分包括加载图像或初始化上下文。

初始化:

//texture init
glGenTextures(1, &feedbackTexture_);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, feedbackTexture_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);

glGenTextures(1, &resultTexture_);
glActiveTexture(GL_TEXTURE0+1);
glBindTexture(GL_TEXTURE_2D, resultTexture_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);

// shader init
computeShaderID = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(computeShaderID, 1, &computeShaderSourcePtr, &computeShaderLength);
glCompileShader(computeShaderID);
programID = glCreateProgram();
glAttachShader(programID, computeShaderID);
glLinkProgram(programID);
glDeleteShader(computeShaderID);

着色器代码:

//shader code (simple invert)
#version 430
layout (local_size_x = 1, local_size_y = 1) in;

layout (location = 0, binding = 0, /*format*/ rgba32f) uniform readonly image2D inImage;
layout (location = 1, binding = 1, /*format*/ rgba32f) uniform writeonly image2D resultImage;

uniform writeonly image2D image;

void main()
{
    // Acquire the coordinates to the texel we are to process.
    ivec2 texelCoords = ivec2(gl_GlobalInvocationID.xy);

    // Read the pixel from the first texture.
    vec4 pixel = imageLoad(inImage, texelCoords);

    pixel.rgb = 1. - pixel.rgb;

    imageStore(resultImage, texelCoords, pixel);
}

用法:

cv::Mat image = loadImage().clone();
cv::Mat result(image.rows,image.cols,image.type());
// These get the appropriate enums used by glTexImage2D
GLenum internalformat = GLUtils::getMatOpenGLImageFormat(image);
GLenum format = GLUtils::getMatOpenGLFormat(image);
GLenum type = GLUtils::getMatOpenGLType(image);

int dispatchX = 1;
int dispatchY = 1;

for ( int i = 0; i < shaderPasses_.size(); ++i)
{
    // Update textures
    glBindTexture(GL_TEXTURE_2D, feedbackTexture_);
    glTexImage2D(GL_TEXTURE_2D, 0, internalformat, result.cols, result.rows, 0, format, type, result.data);
    glBindTexture(GL_TEXTURE_2D, resultTexture_);
    glTexImage2D(GL_TEXTURE_2D, 0, internalformat, image.cols, image.rows, 0, format, type, 0);
    glBindTexture(GL_TEXTURE_2D, 0);

    glClear(GL_COLOR_BUFFER_BIT);
    std::shared_ptr<Shader> shaderPtr = shaderPasses_[i];
    // Enable shader
    shaderPtr->enable();
    {
        // Bind textures
        // location = 0, binding = 0
        glUniform1i(0,0);
        // binding = 0
        glBindImageTexture(0, feedbackTexture_, 0, GL_FALSE, 0, GL_READ_ONLY, internalformat);
        // location = 1, binding = 1
        glUniform1i(1,1);
        // binding = 1
        glBindImageTexture(1, resultTexture_, 0, GL_FALSE, 0, GL_WRITE_ONLY, internalformat);

        // Dispatch rendering
        glDispatchCompute((GLuint)image.cols/dispatchX,(GLuint)image.rows/dispatchY,1);
        // Barrier will synchronize
        glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT);
    }
    // disable shader
    shaderPtr->disable();

    // Here result is now the result of the last pass.
}

有时我会得到奇怪的结果(有问题的纹理、部分渲染的纹理),而且有时不会写入第一个像素(在 0,0)。 我是否正确设置了所有内容,还是缺少某些内容? 看来这种带纹理的方法真的很慢,有没有其他方法可以提高性能?

Edit1: 更改了 memorybarrier 标志。

glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

这是错误的障碍。障碍指定 how you intend to access the data after the incoherent accesses。如果您尝试使用 glGetTexImage 读取纹理,则必须使用 GL_TEXTURE_UPDATE_BARRIER_BIT.

我不是 100% 确定这是否能解决您的问题;但我没有看到您初始化纹理设置的标志有任何明显错误。当我将您的代码与我的项目进行比较时,API 调用的顺序引起了我的注意。在您的来源中,您有以下顺序:

glGenTextures(...);    // Generate
glActiveTexture(...);  // Set Active
glBindTexture(...);    // Bind Texture
glTexParameteri(...);  // Wrap Setting
glTexParameteri(...);  // Wrap Setting
glTexParameteri(...);  // Mipmap Setting
glTexParameteri(...);  // Mipmap Setting
glBindTexture(...);    // Bind / Unbind

然后你对每个纹理重复这个,除了纹理变量的传递和 id 值的增加。

我不知道这是否会有所不同,但在我的引擎中并遵循我设置的逻辑路径;试试按照这个顺序做,看看是否有什么不同

glGenTextures(...);    // Generate
glBindTexture(...);    // Bind Texture
glTexParameteri(...);  // Wrap Setting
glTexParameteri(...);  // Wrap Setting
glTexParameteri(...);  // Mipmap Setting
glTexParameteri(...);  // Mipmap Setting

glActiveTexture(...);  // Set Active
glBindTexture(...);    // Bind / Unbind

我没有使用计算着色器,但在我的引擎中我有几个 classes 来管理不同的东西。我有一个资产存储,它将所有资产保存到内存数据库中,包括图像纹理,我有一个 ShaderManager class 来管理目前仅使用顶点和片段着色器的不同着色器。它将读入并编译着色器文件、创建着色器程序、设置属性和制服、link 程序和 运行 着色器。我正在使用一个批处理过程,其中我有一个批处理 class 和一个批处理管理器 class 来渲染不同类型的图元。因此,当我完成我的解决方案并遵循逻辑路径或流程时,这就是我在代码中看到的内容。

是 AssetStorage class 设置了纹理的属性,它在其 add() 函数中按此顺序调用这些 API 调用以将纹理添加到内存中.

 glGenTextures(...);
 glBindTextures(...);
 glTexParameteri(...);
 glTexParameteri(...);
 glTexParameteri(...);
 glTexParameteri(...);

然后 AssetStorage 也在调用这些

glPixelStorei(...);
glTexImage2D(...)

并且将纹理添加到 AssetStorage 的函数最终会 return TextureInfo 对象的自定义结构。

当我在其 render() 函数调用下检查我的 Batch Class 时,这是它调用 ShaderManager 的函数来设置制服以使用纹理的地方,然后调用 ShaderManager 的函数来设置纹理如果纹理包含 alpha 通道,则再次设置制服。在 setTexture() 函数的 ShaderManger class 中,这是最终调用 glActiveTexture()glBindTexture() 的地方。

所以简而言之,尝试将您的 glActiveTexture() 调用移动到最后一个 glTexParameter() 和最后一个 glBindTexture() 调用之间。我认为它也应该在这两个调用之后出现 glPixelStorei() & glTexImage2D() 因为你想在渲染纹理时激活它。

正如我之前提到的,我不是 100% 确定这是否是您问题的根本原因,但我相信值得一试,看看它是否对您有帮助。如果您尝试此操作,请告诉我会发生什么。我想亲自了解这些 API 调用的顺序是否对其有任何影响。我会在我自己的解决方案中尝试它,但我不想破坏我的 classes 或项目,因为它目前正在正常工作。

请注意,唯一带有纹理设置标志的是 wrap/repeat 部分。您可以尝试在前两个 glTexParameteri() 调用中使用 GL_REPEAT 而不是使用 GL_CLAMP_TO_EDGE 并告诉我您的想法,您不必担心 mipmap 设置最后两个 glTexParameteri() 调用,因为您似乎没有使用您正在使用的设置中的 mipmap。

我终于可以解决这个问题了!

问题出在 cv::Mat 的构造函数上。 以下行仅为 cv::Mat 创建一个 header:

cv::Mat result(image.rows,image.cols,image.type());

确实分配数据,但它 初始化数据,这就是为什么我得到这些奇怪的结果。这是内存中的垃圾。

使用分配 AND 的任何函数初始化此数据可解决问题:

cv::Mat::zeros
cv::Mat::ones
cv::Mat::create