OpenGL Texture Mapping memory leak - qt
I am writing a video content analysis application which analyses recorded and live videos.
I use opengl to display the videos on a qt interface (using qglwidgets). I am using texture mapping with picture buffer objects if the graphics card supports it(here's the reference: http://www.songho.ca/opengl/gl_pbo.html )to display the video(loaded from OpenCV's IPLImage).
The problem is that, the memory for the application keeps on increasing over time. Approx. 4-8KB per second. I am using the task manager to verify this.
I have narrowed down the issue with the rendering of the video because I saw a lot of posts about textures not being freed which leads to memory usage but I haven't been able to find a solution for my problem.
I am only using glGenTextures in initializeGL() so the texture is being generated only once and reused.
Here's the code wherein the problem lies:
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();
}
The code is pretty much the same as in the tutorial. However, my texture data comes from an IplImage structure which is continuously updated by a separate thread. I am also using boost's lock_guard for synchronization purposes.
Is there anything wrong that I am doing here?
EDIT: I am adding the remaining code:
//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=ℑ
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
}
You're calling glDeleteBuffers() on a texture object (should be buffer object), or rather, should not be here at all I think. Like other GL objects, only glDelete() once for every glGen() call.
You're calling glFlush() and swapBuffers(), I believe Qt takes care of that for you.
The OpenGL driver could have a memory leak. Try it without PBO.
Try glGetError() after each GL call to see if you've made a mistake elsewhere.
Related
How to properly use QOpenGLBuffer.PixelPackBuffer with PyQt5
I am trying to read the color buffer content of the default framebuffer in PyQt5 using pixel buffer object given by the Qt OpenGL framework. It looks like the reading is unsuccessful because the end image always contains all zeros. There's very little examples with pixel buffers and PyQt5, so I was mostly relying on this c++ tutorial explaining pixel buffers, specifically section Example: Asynchronous Read-back. My code goes something like this: class GLCanvas(QtWidgets.QOpenGLWidget): # ... def screenDump(self): """ Takes a screenshot and returns a pixmap. :returns: A pixmap with the rendered content. :rtype: QPixmap """ self.makeCurrent() w = self.size().width() h = self.size().height() ppo = QtGui.QOpenGLBuffer(QtGui.QOpenGLBuffer.PixelPackBuffer) ppo.setUsagePattern(QOpenGLBuffer.StaticRead) ppo.create() success = ppo.bind() if success: ppo.allocate(w * h * 4) # Render the stuff # ... # Read the color buffer. glReadBuffer(GL_FRONT) glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, 0) # TRY1: Create an image with pixel buffer data - Doesn't work, image contains all zeros. pixel_buffer_mapped = ppo.map(QOpenGLBuffer.ReadOnly) image = QtGui.QImage(sip.voidptr(pixel_buffer_mapped), w, h, QtGui.QImage.Format_ARGB32) ppo.unmap() # TRY2: Create an image with pixel buffer data - Doesn't work, image contains all zeros. # image = QtGui.QImage(w, h, QtGui.QImage.Format_ARGB32) # bits = image.constBits() # ppo.read(0, bits, w * h * 4) ppo.release() pixmap = QtGui.QPixmap.fromImage(image) return pixmap Any help would be greatly appreciated.
I didn't have any success after a couple of days, so I decided to implement color buffer fetching with pixel buffer object in C++, and then use SWIG to pass the data to Python. I'm posting relevant code, maybe it will help somebody. CPP side // renderer.cpp class Renderer{ // ... void resize(int width, int height) { // Set the viewport glViewport(0, 0, width, height); // Store width and height width_ = width; height_ = height; // ... } // -------------------------------------------------------------------------- // // Returns the color buffer data in GL_RGBA format. GLubyte* screenDumpCpp(){ // Check if pixel buffer objects are available. if (!GLInfo::pixelBufferSupported()){ return 0; } // Get the color buffer size in bytes. int channels = 4; int data_size = width_ * height_ * channels; GLuint pbo_id; // Generate pixel buffer for reading. glGenBuffers(1, &pbo_id); glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_id); glBufferData(GL_PIXEL_PACK_BUFFER, data_size, 0, GL_STREAM_READ); // Set the framebuffer to read from. glReadBuffer(GL_FRONT); // Read the framebuffer and store data in the pixel buffer. glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, 0); // Map the pixel buffer. GLubyte* pixel_buffer = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); // Cleanup. glUnmapBuffer(GL_PIXEL_PACK_BUFFER); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); glDeleteBuffers(1, &pbo_id); return pixel_buffer; } // Returns the color buffer data in RGBA format as a numpy array. PyObject* screenDump(){ // Get screen dump. GLubyte* cpp_image = screenDumpCpp(); int channels = 4; int image_size = width_* height_ * channels; // Setup dimensions for numpy vector. PyObject * python_image = NULL; int ndim = 1; npy_intp dims[1] = {image_size}; // Set up numpy vector. python_image = PyArray_SimpleNew(ndim, dims, NPY_UINT8); GLubyte * data = static_cast<GLubyte *>(PyArray_DATA(toPyArrayObject(python_image))); // Copy screen dump to python space. memcpy(data, cpp_image, image_size); // return screen dump to python. return python_image; } }; // glinfo.cpp const GLInt GLInfo::glVersionInt(){ ... } GLV GLInt::GLV(int major, int minor){ ... } bool GLInfo::pixelBufferSupported(){ const GLint version = GLInfo::glVersionInt(); bool supported = false; if (version >= GLInfo::GLV(1, 5) && version < GLInfo::GLV(3, 0)){ supported = true; } else if (version >= GLInfo::GLV(3, 0)){ GLint extensions_number; glGetIntegerv(GL_NUM_EXTENSIONS, &extensions_number); std::string pixel_buffer_extension("GL_ARB_pixel_buffer_object"); while (extensions_number--) { const auto extension_name = reinterpret_cast<const char *>(glGetStringi(GL_EXTENSIONS, extensions_number)); std::string extension_name_str(extension_name); if (pixel_buffer_extension == extension_name) { supported = true; break; } } } return supported; } Python side # ... class MyCanvas(QOpenGLWidget): def __init__(self): # Get renderer from c++ self._renderer = Renderer() def resizeGL(self, width, height): self._renderer.resize(width, height) # ... if __name__ == '__main__': # ... canvas = MyCanvas() canvas.show() width = canvas.width() height = canvas.height() data = canvas._renderer().screenDump() image = QtGui.QImage(data.data, width, height, QtGui.QImage.Format_RGBA8888) new_image = image.mirrored() pixmap = QtGui.QPixmap.fromImage(new_image) pixmap.save(path) sys.exit(app.exec_())
How to make animation in *.gif run only once in Qt? [duplicate]
I'm using QMovie and gif to create explosion after collision. The problem is that my gif is looping over and over, I've checked loopcount status and it returns -1 (infinite). How to display my gif just one time? #include "Bullet.h" #include <QTimer> #include <QGraphicsScene> #include <QList> #include "Enemy.h" #include "Game.h" #include <typeinfo> #include "levels.h" extern Game * game; // there is an external global object called game int Bullet::killed = 0; int Bullet::missed = 0; double Bullet::accurancy = 0; Bullet::Bullet(QGraphicsItem *parent): QGraphicsPixmapItem(parent){ // draw graphics setPixmap(QPixmap(":/images/res/images/bullets/bullet.png")); missed++; // increse missed when bullet is created movie = new QMovie(":/images/res/images/effects/explosion/64x48.gif"); processLabel = new QLabel; processLabel->setMovie(movie); // make/connect a timer to move() the bullet every so often QTimer * timer = new QTimer(this); connect(timer,SIGNAL(timeout()),this,SLOT(move())); // start the timer timer->start(2); } void Bullet::move(){ // get a list of all the items currently colliding with this bullet QList<QGraphicsItem *> colliding_items = collidingItems(); // if one of the colliding items is an Enemy, destroy both the bullet and the enemy for (int i = 0, n = colliding_items.size(); i < n; ++i){ if (typeid(*(colliding_items[i])) == typeid(Enemy)){ // increase the score game->score->increase(); //play explosion animation movie->start(); movie->setSpeed(180); processLabel->setAttribute(Qt::WA_NoSystemBackground); processLabel->setGeometry(QRect(x()-15,y()-15,64,48)); scene()->addWidget(processLabel); qDebug() << movie->loopCount(); //connect(movie,SIGNAL(finished()),movie,SLOT(stop())); // remove them from the scene (still on the heap) scene()->removeItem(colliding_items[i]); scene()->removeItem(this); // delete them from the heap to save memory delete colliding_items[i]; delete this; killed++; missed--; // decrese missed if bullet colide with enemy if((killed+1) % 9 == 0) { game->level->Levels::incrementLevels(); game->score->Score::addToSum(); /// TODO } //qDebug() << "Already killed: " << killed; //qDebug() << "Already missed: " << missed; // return (all code below refers to a non existint bullet) return; } } // if there was no collision with an Enemy, move the bullet forward setPos(x(),y()-1); // if the bullet is off the screen, destroy it if (pos().y() < 0){ scene()->removeItem(this); delete this; } }
I had the same question and didn't find anything, so here's my solution: connect the signal "frameChanged(int)" to: void Bullet::frameChanged_Handler(int frameNumber) { if(frameNumber == (movie->frameCount()-1)) { movie->stop(); } } If you want to run X times the loop you just have to add a static counter to know how many times you've passed the last frame: void Bullet::frameChanged_Handler(int frameNumber) { static int loopCount = 0; if(frameNumber == (movie->frameCount()-1)) { loopCount++; if(loopCount >= MAX_LOOP_TIMES) movie->stop(); } } and of course, connect with this: connect(movie, SIGNAL(frameChanged(int)), this, SLOT(frameChanged_Handler(int))); That's it... Hope it can help you.
Qt signal slot cv::Mat unable to read memory access violation
I have a Microsoft visual studio application that is grabbing frames from cameras and I am trying to display those frames in a Qt application. I am doing some processing with the frames using OpenCV, so the frames are Mat objects. I use QThreads to parallelize the application. I am getting a Access Violation reading location when I try to emit a Mat signal from my CameraThread class. main.cpp int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow window; window.show(); return app.exec(); } mainwindow.cpp #include "main_window.h" MainWindow::MainWindow() { // create a horizontal widget main_layout = new QVBoxLayout; QHBoxLayout* row1 = new QHBoxLayout; QHBoxLayout* row2 = new QHBoxLayout; for (int i = 0; i < 1; i++) { camera_array[i] = new CameraWidget(i); if (i < 4) row1->addWidget(camera_array[i]); else row2->addWidget(camera_array[i]); } main_layout->addLayout(row1); main_layout->addLayout(row2); // make the central widget the main layout window central = new QWidget(); central->setLayout(main_layout); setCentralWidget(central); } camerawidget.cpp #include "stdafx.h" #include "camera_widget.h" CameraWidget::CameraWidget(int id) { camera_id = id; qRegisterMetaType<cv::Mat>("cv::Mat"); current_frame = cv::imread("camera_1.png"); thread = new CameraThread(camera_id); QObject::connect(thread, SIGNAL(renderFrame(cv::Mat)), this, SLOT(updateFrame(cv::Mat))); thread->start(); } CameraWidget::~CameraWidget() { qDebug("camera widget destructor"); thread->wait(5000); } // initializeGL() function is called just once, before paintGL() is called. void CameraWidget::initializeGL() { qglClearColor(Qt::black); glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, 480.0f, 640.0f, 0.0f, 0.0f, 1.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glEnable(GL_TEXTURE_2D); glGenTextures(3, &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); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 480.0f, 640.0f, GL_BGR, GL_UNSIGNED_BYTE, NULL); glDisable(GL_TEXTURE_2D); } void CameraWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, 480.0f, 640.0f, 0.0f, 0.0f, 1.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glEnable(GL_TEXTURE_2D); current_frame_i = QImage(current_frame.data, current_frame.cols, current_frame.rows, current_frame.cols * 3, QImage::Format_RGB888); glBindTexture(GL_TEXTURE_2D, texture); // ****************************** // getting access violation here // ****************************** glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 480.0f, 640.0f, 0.0f, GL_BGR, GL_UNSIGNED_BYTE, current_frame.ptr()); glBegin(GL_QUADS); glTexCoord2i(0, 1); glVertex2i(0, 640.0f); glTexCoord2i(0, 0); glVertex2i(0, 0); glTexCoord2i(1, 0); glVertex2i(480.0f, 0); glTexCoord2i(1, 1); glVertex2i(480.0f, 640.0f); glEnd(); glFlush(); } void CameraWidget::resizeGL(int w, int h) { // setup viewport, projection etc. glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, 480.0f, 640.0f, 0.0f, 0.0f, 1.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void CameraWidget::updateFrame(cv::Mat image) { current_frame = image; update(); } camerathread.cpp CameraThread::CameraThread(int id) { camera_q = new bounded_frame_queue(50); } void CameraThread::run() { cv::Mat image; while (true) { if (!camera_q->empty()) { image = camera_q->pop(); if (!image.empty()) emit renderFrame(image); } else { msleep(1); } } } When I emit renderFrame from the camerathread.cpp, I get an access violation reading location. I cannot read the current_frame.ptr() value in camerawidget.cpp. Can someone direct me on how I can fix this issue?
What I see is happenning: You get an image from queue. As per OpenCV docs: Mat& cv::Mat::operator= ( const Mat & m ) Assigned, right-hand-side matrix. Matrix assignment is an O(1) operation. This means that no data is copied but the data is shared and the reference counter, if any, is incremented. Before assigning new data, the old data is de-referenced via Mat::release . Then you pass it as cv::Mat image (by value) when emitting signal. The copy constructor again doesn't copy any data: Array that (as a whole or partly) is assigned to the constructed matrix. No data is copied by these constructors. Instead, the header pointing to m data or its sub-array is constructed and associated with it. The reference counter, if any, is incremented. So, when you modify the matrix formed using such a constructor, you also modify the corresponding elements of m . If you want to have an independent copy of the sub-array, use Mat::clone() . Your data pointers are queued on UI thread You get/try-get new frame triggering release from p.1 Your queued slot is executed and crashes... Suggestion: I haven't worked much with it, but it seems something like cv::Mat::clone to make a deep copy is what you need, to prevent release of memory before it would be used by UI thread. Or possibly it would be enough to define image right when you pop it from queue: cv::Mat image = camera_q->pop();
QGraphicsScene & OpenGL Fragment Shader Not Working
I have a very large QGraphicsScene that can contain a very large number of graphics. I'm using a QGLWidget as the viewport so that I can leverage OpenGL to try to improve how some things get rendered. I have created a custom QGraphicsItem that I can use to draw several quads with the same texture in one render call rather than having hundreds or thousands of different QGraphicsItems in the scene that really all get drawn the same way, just in different locations. In my custom QGraphicsItem's paint() method, I called beginNativePainting() and endNativePainting() and do all of my OpenGL calls between them. I want to use shader programs so that I can manipulate the vertices somewhat within the vertex shader, so I copied Qt's OpenGL Textures Example which uses a shader program to draw 6 textured quads. That example works just fine as is, but when I try to use the same approach within a QGraphicsItem's paint() method, all of my quads just get drawn white. My best guess is that my fragment shader just isn't getting used. I've even tried hardcoding the color within the fragment shader and nothing changes. Here's the source code of my custom QGraphicsItem class. class BatchGraphics : public QGraphicsPixmapItem { enum {PROGRAM_VERTEX_ATTRIBUTE = 0, PROGRAM_TEXCOORD_ATTRIBUTE = 1}; public: BatchGraphics() : _program(0), _texture(0), _dirty(false) { } // Returns the custom bounding rect for this item which encompasses all quads QRectF boundingRect() const { return _boundingRect; } // Add a quad to the batch. Only the center point is necessary void addQuad(int id, float x, float y) { _quads.insert(id, QPointF(x, y)); updateBoundingRect(); _dirty = true; } // Remove a quad from the batch. void removeQuad(int id) { if (_quads.contains(id)) { _quads.remove(id); updateBoundingRect(); _dirty = true; } } // Return the number of quads in the batch int count() {return _quads.count();} // Paint the batch using a custom implementation. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // If the item is dirty (has been modified, update the geometry) if (_dirty) { updateGeometry(); } if (_program) { painter->beginNativePainting(); // Enable GL states //glEnable(GL_TEXTURE_2D); // Set the MVP matrix _program->setUniformValue("matrix", painter->transform()); // Enable and set the vertex and texture attributes _program->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); _program->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); _program->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, GL_FLOAT, _vertices.constData(), 3, 5*sizeof(GLfloat)); _program->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, _vertices.constData()+2, 2, 5*sizeof(GLfloat)); // Bind the texture _texture->bind(); // Draw the arrays glDrawArrays(GL_TRIANGLES, 0, _quads.count()*6); // 6 vertices per quad painter->endNativePainting(); } } private: // Initialize the shader and texture void initialize() { // Create the OpenGL texture _texture = new QOpenGLTexture(pixmap().toImage()); // Vertex Shader QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex); const char *vsrc = "attribute highp vec4 vertex;\n" "attribute mediump vec4 texCoord;\n" "varying mediump vec4 texc;\n" "uniform mediump mat4 matrix;\n" "void main(void)\n" "{\n" " gl_Position = matrix * vertex;\n" " texc = texCoord;\n" "}\n"; vshader->compileSourceCode(vsrc); // Fragment Shader QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment); const char *fsrc = "uniform sampler2D texture;\n" "varying mediump vec4 texc;\n" "void main(void)\n" "{\n" " gl_FragColor = texture2D(texture, texc.st);\n" "}\n"; fshader->compileSourceCode(fsrc); // Program _program = new QOpenGLShaderProgram; _program->addShader(vshader); _program->addShader(fshader); _program->bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); _program->bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE); _program->link(); _program->bind(); _program->setUniformValue("texture", 0); } // Update the vertex array. Calls initialize the first time. void updateGeometry() { if (_program == 0) { initialize(); } _vertices.clear(); // Half pixmap size QPointF s = QPointF(pixmap().width()/2, pixmap().height()/2); // Build vertex data for each quad foreach (const QPointF& point, _quads) { // Top Left _vertices << point.x()-s.x(); // x _vertices << point.y()-s.y(); // y _vertices << 1; // z _vertices << 0; // tu _vertices << 1; // tv // Top Right _vertices << point.x()+s.x(); // x _vertices << point.y()-s.y(); // y _vertices << 1; // z _vertices << 1; // tu _vertices << 1; // tv // Bottom Left _vertices << point.x()-s.x(); // x _vertices << point.y()+s.y(); // y _vertices << 1; // z _vertices << 0; // tu _vertices << 0; // tv // Top Right _vertices << point.x()+s.x(); // x _vertices << point.y()-s.y(); // y _vertices << 1; // z _vertices << 1; // tu _vertices << 1; // tv // Bottom Left _vertices << point.x()-s.x(); // x _vertices << point.y()+s.y(); // y _vertices << 1; // z _vertices << 0; // tu _vertices << 0; // tv // Bottom Right _vertices << point.x()+s.x(); // x _vertices << point.y()+s.y(); // y _vertices << 1; // z _vertices << 1; // tu _vertices << 0; // tv } _dirty = false; } private: // Updates the bounding rect based on the quads in the batch. void updateBoundingRect() { prepareGeometryChange(); double left = 9999; double right = -9999; double top = 9999; double bottom = -9999; double w = pixmap().width()/2; double h = pixmap().width()/2; foreach (const QPointF& p, _quads) { left = qMin(left, p.x()-w); right = qMax(right, p.x()+w); top = qMin(top, p.y()-h); bottom = qMax(bottom, p.y()+h); } _boundingRect = QRectF(left, top, (right-left), (bottom-top)); } private: QOpenGLShaderProgram* _program; QOpenGLTexture* _texture; QRectF _boundingRect; QMap<int, QPointF> _quads; QVector<GLfloat> _vertices; bool _dirty; }; I understand the basics of the render pipeline and how to use shaders, but as far as dependencies between things and other OpenGL methods that must be called when using certain features I'm pretty clueless on. I can get the quads to be rendered with the texture using a fixed function pipeline approach, but that's old school and like I said, I want to be able to manipulate the vertices in the vertex shader once I get this working. I'm not doing anything special when creating the QGLWidget, and its QGLFormat ends up being 2.0. I'v also tried calling glEnable(GL_TEXTURE_2D), but that just makes the quads get rendered black instead of white. I've also tried binding the program each time paint() is called, thinking perhaps Qt is binding a different shader somewhere else behind the scenes, but that just causes NOTHING to appear. Can anyone provide any help please? I can't figure out why this approach works fine in Qt's Textures example but not when I try to do it inside of a QGraphicsItem.
I finally figured it out after looking at Qt's source code and what happens when beginNativePainting(). First, I DID have to bind my shader each time paint() was called, and second I had to get the correct MVP matrix. I was trying to just pass the QPainter's transform to my shader to act as the modelview projection matrix, but the transform was only the modelview matrix. I needed to get the projection matrix as well, which Qt sets when beginNativePainting() is called. I got the project and modelview matrices from OpenGL directly and combined them to pass to my shader after binding my texture and presto! It worked! Here are the relevant changes I had to make: painter->beginNativePainting(); // Enable GL states //glEnable(GL_TEXTURE_2D); // === Begin New Code ====== // Bind my program _program->bind(); QMatrix4x4 proj; glGetFloatv(GL_PROJECTION_MATRIX, proj.data()); QMatrix4x4 model; glGetFloatv(GL_MODELVIEW_MATRIX, model.data()); // Set the MVP matrix _program->setUniformValue("matrix", proj * model); // === End New Code ====== // Enable and set the vertex and texture attributes _program->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); _program->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); _program->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, GL_FLOAT, _vertices.constData(), 3, 5*sizeof(GLfloat)); _program->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, _vertices.constData()+2, 2, 5*sizeof(GLfloat));
Qt OpenGL framebuffer to image
I'm drawing the framebuffer to an image, it used to work fine, however something broke and I have no idea what.. Any help would be great. I get the error "QGLFramebufferObject: Framebuffer incomplete, missing attachment." It seems to work intermittently. VoxelEditor::VoxelEditor(QWidget *parent) : QGLWidget(QGLFormat(QGL::SampleBuffers), parent) { makeCurrent(); catchFbo = new QGLFramebufferObject(PICTURE_SIZE, PICTURE_SIZE); void VoxelEditor::renderToImage() { saveGLState(); const int nrPics = 360 / DEGREES_BETWEEN_PICTURES; for (int i = 0; i < nrPics; i++) { catchFbo->bind(); glColorMask(true, true, true, true); glClearColor(0,0,0,0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_MULTISAMPLE); glLoadIdentity(); GLfloat x = GLfloat(PICTURE_SIZE) / PICTURE_SIZE; glFrustum(-x, +x, -1.0, +1.0, 1.0, 1000.0); glViewport(0, 0, PICTURE_SIZE, PICTURE_SIZE); drawScreenshot(i); catchFbo->release(); QImage catchImage = catchFbo->toImage(); catchImage.save("object/test" + QString::number(i) + ".png"); } glDisable(GL_MULTISAMPLE); restoreGLState(); }
I solved this by putting the creation of the fbo in the rendertoimage call. It seemed at creation it was valid and had the appropriate attachment, but at execution it failed.. Perhaps creating the fbo in the initializeGL call would work as well.
Have you check you buffer with isValid() method? Try to release buffer after call toImage() method.