在 nvidia opengl 上混合 glGetTexImage 和 imageStore 的问题

Posted

技术标签:

【中文标题】在 nvidia opengl 上混合 glGetTexImage 和 imageStore 的问题【英文标题】:issues with mixing glGetTexImage and imageStore on nvidia opengl 【发布时间】:2013-06-02 21:59:58 【问题描述】:

我写了一些代码,太长无法粘贴到这里,它通过使用无绑定 imageLoad 和 imageStore 的片段着色器渲染成 3D 1 组件浮动纹理。

该代码肯定有效。

然后我需要解决一些 GLSL 编译器错误,因此想通过 glGetTexImage 将上面的 3D 纹理读回主机。是的,我确实做了一个 glMemoryBarrierEXT(GL_ALL_BARRIER_BITS)。 我确实通过 glGetTexLevelparameteriv() 检查了纹理信息,并且我看到的所有内容都匹配。我确实检查了 OpenGL 错误,但没有。

遗憾的是,glGetTexImage 似乎从未读取片段着色器写入的内容。相反,它只返回我在调用 glTexImage3D() 来创建纹理时输入的假值。

这是预期的行为吗?文档另有说明。

如果 glGetTexImage 确实以这种方式工作,我如何读回该 3D 纹理中的数据(驻留在设备上?)显然,驱动程序可以像纹理非驻留时那样执行此操作。当然有一个简单的方法来做这个简单的事情......


我在问 glGetTexImage 是否应该以这种方式工作。代码如下:

void Bindless3DArray::dump_array(Array3D<float> &out)
  
bool was_mapped = m_image_mapped;
if (was_mapped)
    unmap_array();          // unmap array so it's accessible to opengl

out.resize(m_depth, m_height, m_width);

glBindTexture(GL_TEXTURE_3D, m_textureid);  // from glGenTextures()

#if 0
int w,h,d;
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_WIDTH, &w);
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_HEIGHT, &h);
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_DEPTH, &d);
int internal_format;
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format);
int data_type_r, data_type_g;
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_RED_TYPE, &data_type_r);
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_GREEN_TYPE, &data_type_g);
int size_r, size_g;
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_RED_SIZE, &size_r);
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_GREEN_SIZE, &size_g);
#endif

glGetTexImage(GL_TEXTURE_3D, 0, GL_RED, GL_FLOAT, &out(0,0,0));
glBindTexture(GL_TEXTURE_3D, 0);
CHECK_GLERROR();

if (was_mapped)
    map_array_to_cuda();    // restore state

以下是创建无绑定数组的代码:

void Bindless3DArray::allocate(int w, int h, int d, ElementType t)

if (!m_textureid)
    glGenTextures(1, &m_textureid);
m_type = t;
m_width = w;
m_height = h;
m_depth = d;

glBindTexture(GL_TEXTURE_3D, m_textureid);
CHECK_GLERROR();
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 0);    // ensure only 1 miplevel is allocated
CHECK_GLERROR();

Array3D<float> foo(d, h, w);
// DEBUG -- glGetTexImage returns THIS data, not what's on device
for (int z=0; z<m_depth; ++z)
for (int y=0; y<m_height; ++y)
for (int x=0; x<m_width; ++x)
    foo(z,y,x) = 3.14159;

//-- Texture creation
if (t == ElementInteger)
    glTexImage3D(GL_TEXTURE_3D, 0, GL_R32UI, w, h, d, 0, GL_RED_INTEGER, GL_INT, 0);
else if (t == ElementFloat)
    glTexImage3D(GL_TEXTURE_3D, 0, GL_R32F,  w, h, d, 0, GL_RED, GL_FLOAT, &foo(0,0,0));
else
    throw "Invalid type for Bindless3DArray";
CHECK_GLERROR();

m_handle = glGetImageHandleNV(m_textureid, 0, true, 0, (t == ElementInteger) ? GL_R32UI : GL_R32F);
glMakeImageHandleResidentNV(m_handle, GL_READ_WRITE);
CHECK_GLERROR();

#ifdef USE_CUDA
checkCuda(cudaGraphicsGLRegisterImage(&m_image_resource, m_textureid, GL_TEXTURE_3D, cudaGraphicsRegisterFlagsSurfaceLoadStore));
#endif

我分配数组,通过 OpenGL 片段程序渲染给它,然后我调用 dump_array() 来读回数据。可悲的是,我只得到了我在 allocate 调用中加载的内容。

渲染程序的样子

void App::clear_deepz()

deepz_clear_program.bind();

deepz_clear_program.setUniformValue("sentinel", SENTINEL);
deepz_clear_program.setUniformValue("deepz", deepz_array.handle());
deepz_clear_program.setUniformValue("sem", semaphore_array.handle());

run_program();

glMemoryBarrierEXT(GL_ALL_BARRIER_BITS);
//  glMemoryBarrierEXT(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
//  glMemoryBarrierEXT(GL_SHADER_GLOBAL_ACCESS_BARRIER_BIT_NV);

deepz_clear_program.release();

片段程序是:

#version 420\n

in vec4 gl_FragCoord;
uniform float sentinel;
coherent uniform layout(size1x32) image3D deepz;
coherent uniform layout(size1x32) uimage3D sem;

void main(void)

ivec3 coords = ivec3(gl_FragCoord.x, gl_FragCoord.y, 0);
imageStore(deepz, coords, vec4(sentinel));
imageStore(sem, coords, ivec4(0));
discard;    // don't write to FBO at all

【问题讨论】:

当您不显示任何实际代码时,很难说问题出在哪里。 根据要求,我添加了代码。 glMemoryBarrierEXT(GL_ALL_BARRIER_BITS); 这是来自 EXT_shader_image_load_store。然而,其他一切都在使用核心 OpenGL 4.2/ARB_shader_image_load_store 功能。你不应该在那里有 EXT。 好的,感谢您发现这一点。 我也遇到了这个问题(只是我没有使用 bindless)。你找到解决办法了吗? 【参考方案1】:
discard;    // don't write to FBO at all

discard 不是这个意思。哦,确实是这个意思。但它意味着所有图像加载/存储写入也将被丢弃。事实上,编译器很可能会看到该语句,而对整个片段着色器什么都不做。

如果您只想执行片段着色器,您可以使用empty framebuffer object 的 GL 4.3 功能(在您的 NVIDIA 硬件上可用)。或者您可以使用compute shader。如果您还不能使用 GL 4.3,请使用 write mask 关闭所有颜色写入。

【讨论】:

你这么说很有趣,因为我确信 imageStore 实际上存储了东西。无论如何,如果需要,我会尝试您的建议并在 nvidia 上提交错误。 @WaltDonovan:这不是错误。 OpenGL 规范要求这样做。如果您发出discard,则片段着色器所做的每个可见操作必须被丢弃。 可爱——我在 GL_EXT_shader_image_load_store 规范的深处发现了这个单一的参考: (20) 如果着色器为杀死/丢弃的像素指定图像存储或原子操作会发生什么?已解决:发生这种情况时不会发生任何存储。所以它似乎工作的事实实际上是驱动程序中的另一个错误。 废话。使用空的 FBO 并没有解决问题(实际上给出了相同的结果。)我注释掉了所有的丢弃。现在有什么想法吗? @WaltDonovan:如果不使用 NVIDIA 的无绑定东西会怎样?【参考方案2】:

正如 Nicol 上面提到的,如果您只想要图像加载和存储的副作用,正确的方法是使用空的帧缓冲区对象。

混合 glGetTexImage() 和无绑定纹理的错误实际上是驱动程序错误,并且已在驱动程序版本 335.23 中修复。我提交了错误并确认我的代码现在可以正常工作。

注意我在代码中使用的是空的帧缓冲对象,不要再使用“丢弃”了。

【讨论】:

以上是关于在 nvidia opengl 上混合 glGetTexImage 和 imageStore 的问题的主要内容,如果未能解决你的问题,请参考以下文章

Nvidia Nsight 4.0 无法在 OpenGL 4.3 中分析代码

我应该在 nvidia 显卡上使用啥 opengl 库

OpenGL - 在 NVIDIA 卡上渲染到纹理时出现 FBO 黑屏

NVIDIA 卡上不同的 OpenGL 帧缓冲区 RGBA 值

OpenGL:glVertexAttribPointer() 在新的 NVIDIA 驱动程序上因步幅大于 2048 而出现“无效值”失败

求教关于opengl函数glGetFloatv(GL_MODELVIEW_MATRIX, m)效率问题