【问题标题】:OpenGL compute shader - strange resultsOpenGL 计算着色器 - 奇怪的结果
【发布时间】:2017-03-11 13:05:03
【问题描述】:

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

这是我第一次在 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 处)有时也没有写入。 我是否正确设置了所有内容,还是缺少某些东西? 感觉这个贴图的方法真的很慢,有没有什么办法可以提高性能?

Edit1: 更改了内存屏障标志。

【问题讨论】:

    标签: c++ opencv opengl image-processing compute-shader


    【解决方案1】:
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
    

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

    【讨论】:

    • 哦,我错过了那部分,感谢您的澄清。还有什么方法可以提高上传/下载部分的速度吗?我的图像很少会通过程序改变大小,它们的速度约为 30 fps。
    • 好吧,好像flag改变什么都没做,第一个像素还在闪烁,有时候整张图会出现奇怪的伪影。
    【解决方案2】:

    我不能 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() 函数调用下检查我的批处理类时,它正在调用 ShaderManager 的函数来设置制服以使用纹理,然后调用 ShaderManager 的函数来设置纹理,然后再次设置制服,如果纹理包含一个 Alpha 通道。在 setTexture() 函数的 ShaderManger 类中,这是最终调用 glActiveTexture()glBindTexture() 的地方。

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

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

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

    【讨论】:

    • 感谢您抽出宝贵的时间来解决我的问题。不久前我有一个解决方案,答案实际上不是 gl 调用的顺序或纹理参数,而是 OpenCV 的 Mat 构造函数。我会尽快提供答案。
    • @GáborFekete 哦,好吧,是的,错误总是有可能来自外部来源或第 3 方库。
    • 其实调试opencv比调试opengl容易。 opengl 的状态很好,除了一些标志和你提到的glActiveTexture。我犯了一个错误,依赖其中一个库......永远不要相信他们! :D
    • @GáborFekete 我在 OpenGL 中使用的唯一主要的 3rd 方库是 GLM 数学库和用于加载 PNG 图像的库,对于音频,我使用 alut 和 ogg/vorbis。
    • 我需要 opencv,因为项目的其余部分使用它进行图像处理。这就是为什么它是必须的。 opengl 的这一特性利用 GPU 进行图像处理。
    【解决方案3】:

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

    问题在于 cv::Mat 的构造函数。 以下行仅为 cv::Mat:

    创建一个标题
    cv::Mat result(image.rows,image.cols,image.type());
    

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

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

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

    【讨论】:

    • 严格来说,您的初始调用cv::Mat result(image.rows,image.cols,image.type()); 应该分配图像缓冲区,与cv::Mat::create 相同,但是它不会初始化像素价值观。 cv::Mat::zeroscv::Mat::ones 都分配和初始化像素值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-23
    • 1970-01-01
    相关资源
    最近更新 更多