You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 22:17:36