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

OpenGL compute shader - strange results

本文关键字:结果 计算 OpenGL      更新时间:2023-10-16

我正在尝试实现一个用于图像处理的多路径计算着色器。在每个过程中都有一个输入图像和一个输出图像。下一次的输入图像是前一次的输出图像。

这是我第一次在OpenGL中使用计算着色器,所以我的设置可能有一些问题。我使用OpenCV的Mat作为容器来读取/复制操作。

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

初始化:

//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(有时也没有被写入。我是正确设置了所有内容,还是缺少了什么?看起来这种纹理的方法真的很慢,有没有其他方法可以提高性能?

第1版:更改了memorybarrier标志

glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

这是错误的障碍。屏障指定在非相干访问之后,您打算如何访问数据。如果要使用glGetTexImage读取纹理,则必须使用GL_TEXTURE_UPDATE_BARRIER_BIT

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

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

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

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

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

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

我不能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

我不使用计算着色器,但在我的引擎中,我有几个类可以管理不同的东西。我有一个资产存储,它将把所有资产保存到一个内存数据库中,包括图像的纹理,我还有一个ShaderManager类来管理当前仅使用顶点和片段着色器的不同着色器。它将读入和编译着色器文件,创建着色器程序,设置属性和统一,链接程序并运行着色器。我使用的是批处理过程,其中我有一个批处理类和一个批管理器类来呈现不同类型的基元。因此,当我研究我的解决方案并遵循逻辑的路径或流程时,这就是我在代码中看到的。

AssetStorage类正在为纹理设置属性,并在其add()函数中按此顺序调用这些API调用,以便将纹理添加到内存中。

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

然后AssetStorage也调用了

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

将纹理添加到AssetStorage的函数最终将返回TextureInfo对象的自定义结构。

当我在render()函数调用下检查Batch Class时,它就是在这里调用ShaderManager的函数来设置统一使用纹理,然后调用ShaderManagement的函数来设定纹理,如果纹理包含alpha通道,则再次设定统一。在setTexture()函数的ShaderManger类中,这是glActiveTexture()glBindTexture()最终被调用的地方。

因此,简而言之,尝试将glActiveTexture()调用移动到两个纹理的最后一个glTexParameter()和最后一个glBindTexture()调用之间。我认为它也应该在这两次通话之后出现glPixelStorei()&glTexImage2D(),因为您希望在即将渲染纹理时使其处于活动状态。

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

需要注意的是,纹理设置的唯一标志是在包裹/重复部分。您可以尝试在前两个glTexParameteri()调用中使用GL_REPEAT,而不是使用GL_CLAMP_TO_EDGE,并让我知道您的想法,您不必担心最后两个glTexParameteri()调用的mipmap设置,因为您使用的设置中似乎没有使用mipmap。