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
我正在尝试实现用于图像处理的多通道计算着色器。 每遍都有一个输入图像和一个输出图像。 下一关的输入图像是前一关的输出。
这是我第一次在 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