基于Qt OpenGLWidget,如何获取不同资源占用下的纹理尺寸上限?
我有一个基于Qt的程序,在QOpenGLWidget中使用多个VBO和两个帧缓冲对象(FBO),类定义如下:
class PatternWidget: public QOpenGLWidget,protected QOpenGLFunctions_4_3_Core {...}
由于Points、Tangents、tbo_depth、tbo_norm这些资源会在Widget类外部重置,因此我选择在paintGL()函数中分配或重新分配这些资源,而非initializeGL()中初始化。
initializeGL()函数实现如下:
void PatternWidget::initializeGL(){ ... glGenBuffers(1, &Points); glBindBuffer(GL_ARRAY_BUFFER,Points); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(0); glGenBuffers(1, &Tangents); glBindBuffer(GL_ARRAY_BUFFER,Tangents); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(1); glActiveTexture(GL_TEXTURE0); glGenTextures(1, &tbo_radiance); glBindTexture(GL_TEXTURE_2D_ARRAY, tbo_radiance); glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R32F, 100, 100, 32); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glActiveTexture(GL_TEXTURE1); glGenTextures(1, &tbo_density); glBindTexture(GL_TEXTURE_2D_ARRAY, tbo_density); glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R32F, 100, 100, 32); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glActiveTexture(GL_TEXTURE2); glGenTextures(1, &tbo_depth); glBindTexture(GL_TEXTURE_2D, tbo_depth); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 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_COMPARE_MODE, GL_NONE); glActiveTexture(GL_TEXTURE3); glGenTextures(1, &tbo_norm); glBindTexture(GL_TEXTURE_2D, tbo_norm); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glGenFramebuffers(1, &depthFrame); glBindFramebuffer(GL_FRAMEBUFFER, depthFrame); glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, tbo_depth, 0); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tbo_norm, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); glGenRenderbuffers(1, &rb_colorPattern); glBindRenderbuffer(GL_RENDERBUFFER, rb_colorPattern); glGenRenderbuffers(1, &rb_depthPattern); glBindRenderbuffer(GL_RENDERBUFFER, rb_depthPattern); glGenFramebuffers(1, &patternFrame); glBindFramebuffer(GL_FRAMEBUFFER, patternFrame); glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER, rb_colorPattern); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rb_depthPattern); glDrawBuffer(GL_COLOR_ATTACHMENT0); }
在paintGL()中,我使用多个布尔值确保未在initializeGL()中分配的资源仅初始化一次,外部重置时也仅执行一次重置:
void PatternWidget::paintGL(){ if(!frameObjectUpdated){//frameObjectUpdated bool value,to make sure reset framebuffer only once resetFBO(); } if(!bufferObjectUpdated){//bufferObjectUpdated same as frameObjectUpdated resetBO(); } if(!crossTextureUpdated){//crossTextureUpdatedsame as frameObjectUpdated resetCrossData(); } //render depth texture for shadow map glUseProgram(shadowProgram); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, depthFrame); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); ...//some basic matrix set glDrawArrays(GL_POINTS, 0, renderSize); //render pattern in framebuffer glUseProgram(renderProgram); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, patternFrame); ...//some basic matrix set glBindVertexArray(vao_render); glDrawArrays(GL_POINTS, 0, renderSize); //copy framebuffer to default framebuffer glBindFramebuffer(GL_READ_FRAMEBUFFER, patternFrame); glReadBuffer(GL_COLOR_ATTACHMENT0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, defaultFramebufferObject()); glBlitFramebuffer(0,0,winWid, winHei,0,0, winWid, winHei,GL_COLOR_BUFFER_BIT,GL_NEAREST); }
以下是我的重置函数,setFrameSize()接收外部传入的窗口宽高后调用resetFBO()重置帧缓冲尺寸,resetRenderData()则调用resetBO()重置VBO:
void PatternWidget::setFrameSize(int w,int h){ winWid = w; winHei = h; resetFBO(); } void PatternWidget::resetFBO(){ if(openGLInitialized){ glBindTexture(GL_TEXTURE_2D, tbo_depth); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); glBindTexture(GL_TEXTURE_2D, tbo_norm); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1024, 1024, 0, GL_RGBA, GL_FLOAT, NULL); glBindRenderbuffer(GL_RENDERBUFFER, rb_colorPattern); glRenderbufferStorage(GL_RENDERBUFFER,GL_RGBA,winWid, winHei); glBindRenderbuffer(GL_RENDERBUFFER, rb_depthPattern); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32F, winWid, winHei); frameObjectUpdated = true; } } void PatternWidget::resetRenderData(QVector<vec4> *pnts, QVector<vec3> *t){ points = pnts; tangents = t; resetBO(); } void PatternWidget::resetBO(){ if(openGLInitialized){ renderSize = points->size(); int nBytes = renderSize * sizeof(vec4); glBindBuffer(GL_ARRAY_BUFFER,bo_renderPoints); glBufferData(GL_ARRAY_BUFFER,nBytes,&(*points)[0].x,GL_DYNAMIC_DRAW); nBytes = renderSize * sizeof(vec3); glBindBuffer(GL_ARRAY_BUFFER,bo_renderTangents); glBufferData(GL_ARRAY_BUFFER,nBytes,&(*tangents)[0].x,GL_DYNAMIC_DRAW); nBytes = renderSize * sizeof(float); glBindBuffer(GL_ARRAY_BUFFER,bo_renderRotations); glBufferData(GL_ARRAY_BUFFER,nBytes,&(*rotations)[0],GL_DYNAMIC_DRAW); bufferObjectUpdated = true; } }
我发现当Points和Tangents占用75M内存时,tbo_depth和tbo_norm的尺寸必须小于10241024,否则阴影贴图无法正常工作,且通过glBlitFramebuffer将tbo_norm复制到默认帧缓冲时无输出;当Points和Tangents的尺寸减半时,tbo_depth和tbo_norm的尺寸可以达到20482048。请问有没有办法获取不同资源占用情况下的实际纹理最大尺寸?
1. 查询硬件绝对纹理尺寸上限
首先获取GPU硬件支持的理论最大纹理尺寸,这是不可突破的硬件上限:
GLint maxHardwareTextureSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxHardwareTextureSize); // 对于2D纹理,该值代表单维度的最大边长(如2048、4096等)
2. 跟踪自身显存占用,估算剩余可用空间
OpenGL没有跨平台的API直接查询当前已用显存,你可以手动统计自己分配的所有GPU资源大小:
- VBO:按
元素数量 × 单个元素字节数计算,比如vec4占16字节/元素,vec3占12字节/元素,累加所有VBO的容量。 - 纹理:按
宽度 × 高度 × 层数(数组纹理) × 像素格式字节数计算,比如GL_DEPTH_COMPONENT32是4字节/像素,GL_RGBA32F是16字节/像素。 - Renderbuffer:按
宽度 × 高度 × 像素格式字节数计算。
用GPU总显存(可通过glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &totalMem);获取,需NVX_gpu_memory_info扩展;或用Qt的QOpenGLContext::format().totalMemory()获取近似值)减去已占用显存,得到剩余可用显存,再反推纹理的最大可行尺寸。
3. 动态尝试创建纹理并验证(最可靠方式)
手动计算可能存在误差,最稳妥的方式是从硬件上限开始向下尝试创建纹理,直到成功:
int findMaxUsableTextureSize(GLenum internalFormat) { GLint maxHardwareSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxHardwareSize); int currentSize = maxHardwareSize; GLuint tempTex; glGenTextures(1, &tempTex); glBindTexture(GL_TEXTURE_2D, tempTex); while (currentSize > 0) { // 根据目标纹理格式调整glTexImage2D的参数 if (internalFormat == GL_DEPTH_COMPONENT32) { glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, currentSize, currentSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); } else if (internalFormat == GL_RGBA32F) { glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, currentSize, currentSize, 0, GL_RGBA, GL_FLOAT, nullptr); } if (glGetError() == GL_NO_ERROR) { // 绑定到临时FBO验证完整性 GLuint tempFBO; glGenFramebuffers(1, &tempFBO); glBindFramebuffer(GL_FRAMEBUFFER, tempFBO); glFramebufferTexture(GL_FRAMEBUFFER, internalFormat == GL_DEPTH_COMPONENT32 ? GL_DEPTH_ATTACHMENT : GL_COLOR_ATTACHMENT0, tempTex, 0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { glDeleteFramebuffers(1, &tempFBO); glDeleteTextures(1, &tempTex); return currentSize; } glDeleteFramebuffers(1, &tempFBO); } currentSize /= 2; // 每次减半快速缩小范围 } glDeleteTextures(1, &tempTex); return 0; // 极端情况无可用显存 }
4. 启用错误检查与FBO完整性验证
在resetFBO中添加错误检查,及时捕获显存不足的情况:
void PatternWidget::resetFBO(){ if(openGLInitialized){ glBindTexture(GL_TEXTURE_2D, tbo_depth); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, targetSize, targetSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); if (glGetError() != GL_NO_ERROR) { // 显存不足,调用findMaxUsableTextureSize获取可用尺寸并重新创建 targetSize = findMaxUsableTextureSize(GL_DEPTH_COMPONENT32); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, targetSize, targetSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); } // 对tbo_norm执行同样的检查逻辑... glBindFramebuffer(GL_FRAMEBUFFER, depthFrame); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { // FBO不完整,处理逻辑(如缩小纹理尺寸) } frameObjectUpdated = true; } }
额外建议
- 给VBO设置
GL_DYNAMIC_DRAW或GL_STREAM_DRAW使用提示,帮助GPU优化显存分配。 - 及时调用
glDeleteBuffers/glDeleteTextures释放不再使用的资源。 - 使用Qt的
QOpenGLDebugLogger捕获OpenGL警告和错误,快速定位显存不足问题。
内容的提问来源于stack exchange,提问作者Chris

