2013-02-21 247 views
2

我正在编写一个视频内容分析应用程序,它分析录制的和实况视频。OpenGL纹理映射内存泄漏

我使用opengl在qt界面上显示视频(使用qglwidgets)。如果显卡支持图像缓冲区对象,我使用纹理映射(参考:http://www.songho.ca/opengl/gl_pbo.html)来显示视频(从OpenCV的IPLImage加载)。

问题是,应用程序的内存不断增加。约。每秒4-8KB。我正在使用任务管理器来验证这一点。
我已经缩小了视频渲染的问题,因为我看到很多关于纹理的帖子没有被释放,这会导致内存使用,但是我一直无法为我的问题找到解决方案。

我只在initializeGL()中使用glGenTextures,所以纹理只生成一次并重用。

下面的代码,其中的问题在于:

void paintGL(){ 
static int index = 0; 
int nextIndex = 0;     // pbo index used for next frame 
if(paintFlag){ 
    if(pboMode > 0) { 
// "index" is used to copy pixels from a PBO to a texture object "nextIndex" is used to update  pixels in a PBO  

if(pboMode == 1){ 
// In single PBO mode, the index and nextIndex are set to 0 

     index = nextIndex = 0; 
     } 
     else if(pboMode == 2) 
     { 
      // In dual PBO mode, increment current index first then get the next index 
      index = (index + 1) % 2; 
      nextIndex = (index + 1) % 2; 
     } 

     // start to copy from PBO to texture object /////// 

     // bind the texture and PBO 
     glBindTexture(GL_TEXTURE_2D, texture); 
     glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[index]); 

     // copy pixels from PBO to texture object 
     // Use offset instead of ponter. 
     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, GL_BGR, GL_UNSIGNED_BYTE, 0); 

     // measure the time copying data from PBO to texture object 
     //t1.stop(); 
     //copyTime = t1.getElapsedTimeInMilliSec(); 
     /////////////////////////////////////////////////// 


     // start to modify pixel values /////////////////// 
     //  t1.start(); 

     // bind PBO to update pixel values 
     glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[nextIndex]); 

     // map the buffer object into client's memory 
     // Note that glMapBufferARB() causes sync issue. 
     // If GPU is working with this buffer, glMapBufferARB() will wait(stall) 
     // for GPU to finish its job. To avoid waiting (stall), you can call 
     // first glBufferDataARB() with NULL pointer before glMapBufferARB(). 
     // If you do that, the previous data in PBO will be discarded and 
     // glMapBufferARB() returns a new allocated pointer immediately 
     // even if GPU is still working with the previous data. 
     glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_DRAW_ARB); 
     GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB); 
     if(ptr) 
     { 
      // update data directly on the mapped buffer 
      //updatePixels(ptr, DATA_SIZE); 
      memcpy(ptr,original->imageData,DATA_SIZE); 
      glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB); // release pointer to mapping buffer 
     } 

     // measure the time modifying the mapped buffer 
     //t1.stop(); 
     //updateTime = t1.getElapsedTimeInMilliSec(); 
     /////////////////////////////////////////////////// 

     // it is good idea to release PBOs with ID 0 after use. 
     // Once bound with 0, all pixel operations behave normal ways. 
     glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0); 
    } 
    else 
    { 
     /////////////////////////////////////////////////// 
     // start to copy pixels from system memory to textrure object 
     //t1.start(); 

     glBindTexture(GL_TEXTURE_2D, texture); 
     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, GL_BGR, GL_UNSIGNED_BYTE, (GLvoid*)original->imageData); 

     //t1.stop(); 
     //copyTime = t1.getElapsedTimeInMilliSec(); 


    } 
    paintFlag=false; 
} 


// clear buffer 
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 
glBegin(GL_QUADS); 
glTexCoord2i(0,1); glVertex2i(0,HEIGHT); 
glTexCoord2i(0,0); glVertex2i(0,0); 
glTexCoord2i(1,0); glVertex2i(WIDTH,0); 
glTexCoord2i(1,1); glVertex2i(WIDTH,HEIGHT); 
glEnd(); 
glFlush(); 
glBindTexture(GL_TEXTURE_2D, 0); 
swapBuffers(); 
glDeleteBuffers(1,&texture); 
updateGL(); 
} 

的代码几乎是一样的教程。但是,我的纹理数据来自IplImage结构,它由一个单独的线程不断更新。我也使用boost的lock_guard进行同步。

我在这里有什么不对吗?

编辑:我加入剩余的代码:

//Constructor, this is where all the allocation happens 
const int DATA_SIZE = WIDTH * HEIGHT * 3; 
QGLCanvas::QGLCanvas(QWidget* parent,QString caption) 
    : QGLWidget(parent) 
{ 
    imageFormat=QImage::Format_RGB888; 
this->name=caption; 
original=cvCreateImage(cvSize(WIDTH,HEIGHT),IPL_DEPTH_8U,3); 
if(this->name=="Background") 
    bgFrameBackup=cvCreateImage(cvSize(WIDTH,HEIGHT),IPL_DEPTH_8U,3); 
cvZero(original); 
//cvShowImage("w",original); 
//cvWaitKey(0); 

switch(original->nChannels) { 
case 1: 
    format = GL_LUMINANCE; 
    break; 
case 2: 
    format = GL_LUMINANCE_ALPHA; 
    break; 
case 3: 
    format = GL_BGR; 
    break; 
default: 
    return; 
} 


drawing=false; 
setMouseTracking(true); 
mouseX=0;mouseY=0; 
startX=0; endX=0; 
startY=0; endY=0; 
dialog=new EntryExitRuleDialog(); 
makeCurrent(); 
GLenum result=glewInit(); 
if(result){ 
    qDebug()<<(const char*)(glewGetErrorString(result)); 
} 
//qDebug()<<"Open GL Version: "<<(const char*)glGetString(GL_VERSION); 
bgColor=QColor::fromRgb(100,100,100); 
initializeGL(); 
qglClearColor(bgColor); 
glInfo glInfo; 
glInfo.getInfo(); 

#ifdef _WIN32 
// check PBO is supported by your video card 

if(glInfo.isExtensionSupported("GL_ARB_pixel_buffer_object")) 
{ 
    // get pointers to GL functions 
    glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffersARB"); 
    glBindBufferARB = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB"); 
    glBufferDataARB = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB"); 
    glBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)wglGetProcAddress("glBufferSubDataARB"); 
    glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB"); 
    glGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)wglGetProcAddress("glGetBufferParameterivARB"); 
    glMapBufferARB = (PFNGLMAPBUFFERARBPROC)wglGetProcAddress("glMapBufferARB"); 
    glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)wglGetProcAddress("glUnmapBufferARB"); 

    // check once again PBO extension 
    if(glGenBuffersARB && glBindBufferARB && glBufferDataARB && glBufferSubDataARB && 
     glMapBufferARB && glUnmapBufferARB && glDeleteBuffersARB && glGetBufferParameterivARB) 
    { 
     pboSupported = true; 
     cout << "Video card supports GL_ARB_pixel_buffer_object." << endl; 
    } 
    else 
    { 
     pboSupported = false; 
     cout << "Video card does NOT support GL_ARB_pixel_buffer_object." << endl; 
    } 
} 

#else // for linux, do not need to get function pointers, it is up-to-date 
if(glInfo.isExtensionSupported("GL_ARB_pixel_buffer_object")) 
{ 
    pboSupported = pboUsed = true; 
    cout << "Video card supports GL_ARB_pixel_buffer_object." << endl; 
} 
else 
{ 
    pboSupported = pboUsed = false; 
    cout << "Video card does NOT support GL_ARB_pixel_buffer_object." << endl; 
} 
#endif 

if(pboSupported){ 
    glGenBuffersARB(2, pboIds); 
    glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[0]); 
    glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_DRAW_ARB); 
    glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[1]); 
    glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_DRAW_ARB); 
    glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0); 
    //Note: pboMode=2 somehow does not work while calibration. Fix this later. 
    pboMode=1; 
} 
else{ 
    pboMode=0; 
} 

paintFlag=false; 
} 

void QGLCanvas::setImage(IplImage image){ 

if(QString(this->name)=="Background"){ 
    cvCopyImage(&image,bgFrameBackup); 
} 
//cvShowImage(name,&image); 
// Display a rectangle between startX ,startY and endX,endY if we are in calibration mode 
//and drawing flag is set.(typically, by a mouse click) 
if(QString(this->name)=="Calibrate" && calibrating){ 
    if(drawing) 
     cvRectangle(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee)); 
    if(select_object) //During calibration 
     cvRectangle(&image,cvPoint(selection.x,selection.y),cvPoint(selection.x+selection.width,selection.y+selection.height),cvScalarAll(0xee)); 
    //Draw existing calibration rectangles 

    for (list<CvRect>::iterator it=calibration_rect_list->begin(); it!=calibration_rect_list->end(); ++it) 
    { 
     cvRectangle(&image, cvPoint((*it).x, (*it).y), cvPoint((*it).x + (*it).width, (*it).y + (*it).height), CV_RGB(100,255,0), 2, 8, 0); 
    } 


} 
//Only draw on the video widget with the name "Final" 
if(QString(this->name)=="Final") 
{ 

    if(calibrating && drawing) 
     cvRectangle(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee)); 
    //If we are adding a rule, the corresponding rule shape must be drawn on the widget. 
    if(addingRule && drawing){ 
     if(currentShape==RULE_SHAPE_RECT){ 
      cvRectangle(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee)); 
     } 
     else if(currentShape==RULE_SHAPE_POLY){ 
      int linecolor=0xee; 
      if(points.count()>0){ 
       //Draw polygon... 
       for(int i=1;i<points.count();i++){ 
        cvLine(&image,cvPoint(points[i-1]->x(),points[i-1]->y()),cvPoint(points[i]->x(),points[i]->y()),cvScalarAll(linecolor)); 
       } 

       cvLine(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee)); 
       cvLine(&image,cvPoint(endX,endY),cvPoint(points[0]->x(),points[0]->y()),cvScalarAll(linecolor)); 
      } 
     } 
     else if(currentShape==RULE_SHAPE_TRIPLINE){ 
      for(int i=1;i<points.count();i++){ 
       cvLine(&image,cvPoint(points[i-1]->x(),points[i-1]->y()),cvPoint(points[i]->x(),points[i]->y()),cvScalarAll(0xee)); 
      } 
      cvLine(&image,cvPoint(startX,startY),cvPoint(endX,endY),cvScalarAll(0xee)); 
     } 

    } 
    if(entryExitRuleCreated && currentZoneType==RULE_ZONE_TYPE_ENTRY_EXIT){ 
     //Highlight appropriate sides of the currentRule to mark them as Entry/Exit Zone 
     for(int i=0;i<currentRule->points.count();i++){ 
      QPoint* P1=currentRule->points[i]; 
      QPoint* P2; 
      //Implement cyclic nature of polygon 
      if(i<currentRule->points.count()-1) 
       P2=currentRule->points[i+1]; 
      else P2=currentRule->points[0]; 
      int deltax=mouseX-P1->x(); 
      int deltax1=P2->x()-P1->x(); 
      float m,m1; 
      if(deltax!=0) 
       m= (float)(mouseY-P1->y())/deltax; 
      if(deltax1!=0 && deltax!=0){ 
       m1=(float)(P2->y()-P1->y())/deltax1; 
       if(round(m,1)==round(m1,1))//Mouse pointer lies on the line whose slope is same as the polygon edge 
       { 
        //Mouse pointer is on the edge of a polygon, highlight the edge 
        if(abs(P1->y()-P2->y()) >= abs(mouseY-P2->y()) && abs(P1->y()-P2->y()) >= abs(mouseY-P1->y()) 
         && abs(P1->x()-P2->x()) >= abs(mouseX-P2->x()) && abs(P1->x()-P2->x()) >= abs(mouseX-P1->x()) 
         ){ 
          edgeHighlighted=true; 
          highlightedEdge[0]=P1; 
          highlightedEdge[1]=P2; 
          currentEdgeNumber=i; 
          break; 
        } 
       } 
       else{ 
        edgeHighlighted=false; 
       } 
      } 
      else{ 
       //Vertical edge of a polygon. 
       if(abs(mouseX-P1->x())<4) { //Same vertical line 
        if(abs(P1->y()-P2->y()) > abs(mouseY-P2->y()) && abs(P1->y()-P2->y()) > abs(mouseY-P1->y())){ 
         //Current y lies between the two vertices of an edge 
         //Mouse pointer is on the edge of polygon,highlight the edge 
         //qDebug()<<"P1="<<P1->x()<<","<<P1->y()<<", P2="<<P2->x()<<","<<P2->y(); 
         edgeHighlighted=true; 
         highlightedEdge[0]=P1; 
         highlightedEdge[1]=P2; 
         currentEdgeNumber=i; 
         break; 
        } 
        else 
         edgeHighlighted=false; 
       } 
      } 

     } 
     if(edgeHighlighted || edgeHighlightedFromButton){ 
      cvLine(&image,cvPoint(highlightedEdge[0]->x(),highlightedEdge[0]->y()),cvPoint(highlightedEdge[1]->x(),highlightedEdge[1]->y()),cvScalar(0xff,0x00,0x00),3); 
     } 
    } 
} 

{ 
    //qDebug()<<name<<":Saving original image"; 
    ExclusiveLock xlock(globalXMutex); 
    this->original=&image; 
    paintFlag=true; 
} 

updateGL(); 


/*if(this->name=="Final"){ 
cvShowImage("Final",original); 
cvWaitKey(1); 
}*/ 
} 

//Texture is generated here 
void QGLCanvas::initializeGL(){ 

glDisable(GL_LIGHTING); 
glEnable(GL_TEXTURE_2D); 
glClearColor(0, 0, 0, 0);     // background color 
glClearStencil(0);       // clear stencil buffer 
glClearDepth(1.0f);       // 0 is near, 1 is far 
glDepthFunc(GL_LEQUAL); 

glEnable(GL_TEXTURE_2D); 
glGenTextures(1,&texture); 
glBindTexture(GL_TEXTURE_2D,texture); 
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); 
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 
glBindTexture(GL_TEXTURE_2D,texture); 
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,WIDTH,HEIGHT,0,GL_BGR,GL_UNSIGNED_BYTE,NULL); 
glBindTexture(GL_TEXTURE_2D, 0); 

glClearStencil(0);       // clear stencil buffer 
glClearDepth(1.0f);       // 0 is near, 1 is far 
glDepthFunc(GL_LEQUAL); 
setAutoBufferSwap(false); 
} 


void QGLCanvas::resizeGL(int width,int height){ 

if (height==0)          // Prevent A Divide By Zero By 
{ 
    height=1;          // Making Height Equal One 
} 

glViewport(0,0,WIDTH,HEIGHT);      // Reset The Current Viewport 
glMatrixMode(GL_PROJECTION);      // Select The Projection Matrix 
glLoadIdentity();         // Reset The Projection Matrix 
glOrtho(0.0f,WIDTH,HEIGHT,0.0f,0.0f,1.0f); 
glEnable(GL_TEXTURE_2D); 


glMatrixMode(GL_MODELVIEW);       // Select The Modelview Matrix 
glLoadIdentity();         // Reset The Modelview Matrix 

} 
+0

也许你正在处理iplimages的内存泄漏?你从哪里得到?你是否正在做任何转换/复制,你没有发布? – berak 2013-02-21 21:09:42

+1

您发布的代码没有内存分配。这里不可能有内存泄漏。 – 2013-02-21 23:07:11

+0

只有在“显示”qglwidget上的iplimages时才会发生内存泄漏。我只通过评论paintGL代码来检查它,并且内存泄漏消失了。这就是为什么我说我已经将问题本地化到了这个代码中。但我仍然添加了其余的代码,以防万一。 – 2013-02-23 10:54:50

回答

2

你打电话glDeleteBuffers()纹理对象(应该是缓冲对象),或者说,不应该在这里所有我的想法。像其他GL对象一样,每次glGen()调用只需glDelete()一次。

您正在调用glFlush()和swapBuffers(),我相信Qt会帮您解决这个问题。

OpenGL驱动程序可能有内存泄漏。尝试没有PBO。

在每次GL调用之后尝试glGetError()以查看其他地方是否犯了错误。

+0

我禁用了Qt的自动切换缓冲区标志,这就是我使用swapBuffers的原因,因为自动缓冲区切换不适用于我。我也检查了没有使用PBO。问题依然存在(仅适用于Windows 7,这可能是驱动程序问题)。 – 2013-02-25 10:05:21