Memory leak in QGraphcisPixmapItem.setPixmap()?



  • hello
    I met strange issue.
    I have application which uses QGraphcsiView, QGraphcisScene and QGraphicsPixmapItem on scene.
    My GraphcsiVew has assigned OpenGL as

    gl = new QGLWidget();
    setViewport(gl);
    

    Periodically Pixmap item gets updated with new image.

    Only on some computers with some OSes I get memory leak on call to setPixmap().
    For example IN windows 10 my app is OK.
    On windows 8.1 my app generates huge memory leaks and crashes.

    This somehow is dependant to OpenGL. If I remove setViewPort(gl); then all is OK in both OSes.
    My app is big and complex so of cause reason can be anywhere.

    I made a simple test. I just little modified QT example "moveblocks"

    /****************************************************************************
    **
    ** Copyright (C) 2015 The Qt Company Ltd.
    ** Contact: http://www.qt.io/licensing/
    **
    ** This file is part of the QtCore module of the Qt Toolkit.
    **
    ** $QT_BEGIN_LICENSE:BSD$
    ** You may use this file under the terms of the BSD license as follows:
    **
    ** "Redistribution and use in source and binary forms, with or without
    ** modification, are permitted provided that the following conditions are
    ** met:
    **   * Redistributions of source code must retain the above copyright
    **     notice, this list of conditions and the following disclaimer.
    **   * Redistributions in binary form must reproduce the above copyright
    **     notice, this list of conditions and the following disclaimer in
    **     the documentation and/or other materials provided with the
    **     distribution.
    **   * Neither the name of The Qt Company Ltd nor the names of its
    **     contributors may be used to endorse or promote products derived
    **     from this software without specific prior written permission.
    **
    **
    ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
    **
    ** $QT_END_LICENSE$
    **
    ****************************************************************************/
    
    #include <QtCore>
    #include <QtWidgets>
    
    QImage* im;
    QPixmap* pm;
    QGraphicsPixmapItem* gi;
    
    //![15]
    class StateSwitchEvent: public QEvent
    {
    public:
        StateSwitchEvent()
            : QEvent(Type(StateSwitchType))
        {
        }
    
        explicit StateSwitchEvent(int rand)
            : QEvent(Type(StateSwitchType)),
              m_rand(rand)
        {
        }
    
        enum { StateSwitchType = QEvent::User + 256 };
    
        int rand() const { return m_rand; }
    
    private:
        int m_rand;
    };
    //![15]
    
    //![16]
    class QGraphicsRectWidget : public QGraphicsWidget
    {
    public:
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
                   QWidget *) Q_DECL_OVERRIDE
        {
            painter->fillRect(rect(), Qt::blue);
        }
    };
    //![16]
    
    class StateSwitchTransition: public QAbstractTransition
    {
    public:
        StateSwitchTransition(int rand)
            : QAbstractTransition(),
              m_rand(rand)
        {
        }
    
    protected:
    //![14]
        virtual bool eventTest(QEvent *event) Q_DECL_OVERRIDE
        {
            return (event->type() == QEvent::Type(StateSwitchEvent::StateSwitchType))
                && (static_cast<StateSwitchEvent *>(event)->rand() == m_rand);
        }
    //![14]
    
        virtual void onTransition(QEvent *) Q_DECL_OVERRIDE {}
    
    private:
        int m_rand;
    };
    
    //![10]
    class StateSwitcher : public QState
    {
        Q_OBJECT
    public:
        StateSwitcher(QStateMachine *machine)
            : QState(machine), m_stateCount(0), m_lastIndex(0)
        { }
    //![10]
    
    //![11]
        virtual void onEntry(QEvent *) Q_DECL_OVERRIDE
        {
            int n;
            while ((n = (qrand() % m_stateCount + 1)) == m_lastIndex)
            { }
            m_lastIndex = n;
            machine()->postEvent(new StateSwitchEvent(n));
        }
        virtual void onExit(QEvent *) Q_DECL_OVERRIDE {}
    //![11]
    
    //![12]
        void addState(QState *state, QAbstractAnimation *animation) {
            StateSwitchTransition *trans = new StateSwitchTransition(++m_stateCount);
            trans->setTargetState(state);
            addTransition(trans);
            trans->addAnimation(animation);
        }
    //![12]
    
    private:
        int m_stateCount;
        int m_lastIndex;
    };
    
    //![13]
    QState *createGeometryState(QObject *w1, const QRect &rect1,
                                QObject *w2, const QRect &rect2,
                                QObject *w3, const QRect &rect3,
                                QObject *w4, const QRect &rect4,
                                QState *parent)
    {
        QState *result = new QState(parent);
        result->assignProperty(w1, "geometry", rect1);
        result->assignProperty(w2, "geometry", rect2);
        result->assignProperty(w3, "geometry", rect3);
        result->assignProperty(w4, "geometry", rect4);
    
        return result;
    }
    //![13]
    
    class GraphicsView : public QGraphicsView
    {
        Q_OBJECT
    public:
        GraphicsView(QGraphicsScene *scene, QWidget *parent = NULL) : QGraphicsView(scene, parent)
        {
        }
    
    
    protected:
       virtual void mouseMoveEvent(QMouseEvent * event) Q_DECL_OVERRIDE
       {
           static uchar c = 0;
           c=c+0x20;
           uchar* pbits = im->bits();
           for(int i=0; i<1024*1024; i++)
           {
               pbits[i*4+0] =0xFF;
               pbits[i*4+1] =c;
               pbits[i*4+2] =0xFF;
               pbits[i*4+3] =0xFF;
           }
           //pm = new QPixmap( );
           //gi=new QGraphicsPixmapItem();
           gi->setPixmap(QPixmap::fromImage(*im, Qt::NoFormatConversion));
    
       }
        virtual void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE
        {
            fitInView(scene()->sceneRect());
            QGraphicsView::resizeEvent(event);
        }
    };
    
    #include <QtOpenGL/QGLWidget>
    
    int main(int argc, char **argv)
    {
        QApplication app(argc, argv);
    
    //![1]
        QGraphicsRectWidget *button1 = new QGraphicsRectWidget;
        QGraphicsRectWidget *button2 = new QGraphicsRectWidget;
        QGraphicsRectWidget *button3 = new QGraphicsRectWidget;
        QGraphicsRectWidget *button4 = new QGraphicsRectWidget;
        im = new QImage(1024,1024,QImage::Format_RGBA8888);
        uchar* pbits = im->bits();
        for(int i=0; i<1024*1024; i++)
        {
            pbits[i*4+0] =0xFF;
            pbits[i*4+1] =0x00;
            pbits[i*4+2] =0xFF;
            pbits[i*4+3] =0xFF;
        }
        //pm = new QPixmap( );
        gi=new QGraphicsPixmapItem();
        gi->setPixmap(QPixmap::fromImage(*im, Qt::NoFormatConversion));
        gi->setPos(0,0);
    
        button2->setZValue(1);
        button3->setZValue(2);
        button4->setZValue(3);
        QGraphicsScene scene(0, 0, 300, 300);
        scene.setBackgroundBrush(Qt::black);
    
        scene.addItem(gi);
        scene.addItem(button1);
        scene.addItem(button2);
        scene.addItem(button3);
        scene.addItem(button4);
    //![1]
        GraphicsView window(&scene);
        QGLWidget* gl = new QGLWidget();
        window.setViewport(gl);
    
        window.setFrameStyle(0);
        window.setAlignment(Qt::AlignLeft | Qt::AlignTop);
        window.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        window.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    //![2]
        QStateMachine machine;
    
        QState *group = new QState();
        group->setObjectName("group");
        QTimer timer;
        timer.setInterval(1250);
        timer.setSingleShot(true);
        QObject::connect(group, SIGNAL(entered()), &timer, SLOT(start()));
    //![2]
    
    //![3]
        QState *state1;
        QState *state2;
        QState *state3;
        QState *state4;
        QState *state5;
        QState *state6;
        QState *state7;
    
        state1 = createGeometryState(button1, QRect(100, 0, 50, 50),
                                     button2, QRect(150, 0, 50, 50),
                                     button3, QRect(200, 0, 50, 50),
                                     button4, QRect(250, 0, 50, 50),
                                     group);
    //![3]
        state2 = createGeometryState(button1, QRect(250, 100, 50, 50),
                                     button2, QRect(250, 150, 50, 50),
                                     button3, QRect(250, 200, 50, 50),
                                     button4, QRect(250, 250, 50, 50),
                                     group);
        state3 = createGeometryState(button1, QRect(150, 250, 50, 50),
                                     button2, QRect(100, 250, 50, 50),
                                     button3, QRect(50, 250, 50, 50),
                                     button4, QRect(0, 250, 50, 50),
                                     group);
        state4 = createGeometryState(button1, QRect(0, 150, 50, 50),
                                     button2, QRect(0, 100, 50, 50),
                                     button3, QRect(0, 50, 50, 50),
                                     button4, QRect(0, 0, 50, 50),
                                     group);
        state5 = createGeometryState(button1, QRect(100, 100, 50, 50),
                                     button2, QRect(150, 100, 50, 50),
                                     button3, QRect(100, 150, 50, 50),
                                     button4, QRect(150, 150, 50, 50),
                                     group);
        state6 = createGeometryState(button1, QRect(50, 50, 50, 50),
                                     button2, QRect(200, 50, 50, 50),
                                     button3, QRect(50, 200, 50, 50),
                                     button4, QRect(200, 200, 50, 50),
                                     group);
    //![4]
        state7 = createGeometryState(button1, QRect(0, 0, 50, 50),
                                     button2, QRect(250, 0, 50, 50),
                                     button3, QRect(0, 250, 50, 50),
                                     button4, QRect(250, 250, 50, 50),
                                     group);
        group->setInitialState(state1);
    //![4]
    
    //![5]
        QParallelAnimationGroup animationGroup;
        QSequentialAnimationGroup *subGroup;
    
        QPropertyAnimation *anim = new QPropertyAnimation(button4, "geometry");
        anim->setDuration(1000);
        anim->setEasingCurve(QEasingCurve::OutElastic);
        animationGroup.addAnimation(anim);
    //![5]
    
    //![6]
        subGroup = new QSequentialAnimationGroup(&animationGroup);
        subGroup->addPause(100);
        anim = new QPropertyAnimation(button3, "geometry");
        anim->setDuration(1000);
        anim->setEasingCurve(QEasingCurve::OutElastic);
        subGroup->addAnimation(anim);
    //![6]
    
        subGroup = new QSequentialAnimationGroup(&animationGroup);
        subGroup->addPause(150);
        anim = new QPropertyAnimation(button2, "geometry");
        anim->setDuration(1000);
        anim->setEasingCurve(QEasingCurve::OutElastic);
        subGroup->addAnimation(anim);
    
        subGroup = new QSequentialAnimationGroup(&animationGroup);
        subGroup->addPause(200);
        anim = new QPropertyAnimation(button1, "geometry");
        anim->setDuration(1000);
        anim->setEasingCurve(QEasingCurve::OutElastic);
        subGroup->addAnimation(anim);
    
    //![7]
        StateSwitcher *stateSwitcher = new StateSwitcher(&machine);
        stateSwitcher->setObjectName("stateSwitcher");
        group->addTransition(&timer, SIGNAL(timeout()), stateSwitcher);
        stateSwitcher->addState(state1, &animationGroup);
        stateSwitcher->addState(state2, &animationGroup);
    //![7]
        stateSwitcher->addState(state3, &animationGroup);
        stateSwitcher->addState(state4, &animationGroup);
        stateSwitcher->addState(state5, &animationGroup);
        stateSwitcher->addState(state6, &animationGroup);
    //![8]
        stateSwitcher->addState(state7, &animationGroup);
    //![8]
    
    //![9]
        machine.addState(group);
        machine.setInitialState(group);
        machine.start();
    //![9]
    
        window.resize(300, 300);
        window.show();
    
        qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
    
        return app.exec();
    }
    
    #include "main.moc"
    
    

    Here I only added on scene QGrapchsiPixmapItem.
    Every time when mouse is moved on view (please press any mouse button) then Pixmap is updated with new image: gi->setPixmap(QPixmap::fromImage(*im, Qt::NoFormatConversion));

    Also note here

        GraphicsView window(&scene);
        QGLWidget* gl = new QGLWidget();
        window.setViewport(gl);
    

    This app has a memory leak in Windows 8.1 (I use it in VMWARE virtual machine ) but has no leak in Window 10.
    Endusers also complain on crash on WIndows 8.1 - I guess same memory leak.

    Besides that if OpenGL is not used then it works without issues.

    What I am doing wrong?
    Thanks in advance.


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Why are you creating your im on the heap ? There's no need for that.



  • @SGaist
    yes, i know, but for this particular issue it does not matter.
    Can You please give me advice how properly setPixmap() to QGraphicsPixmapItem()?
    I belive this code

    gi->setPixmap(QPixmap::fromImage(*im, Qt::NoFormatConversion));
    

    should be correct, right?


  • Lifetime Qt Champion

    On a second look, one thing is that calling bits like that triggers a deep copy of the pixel data which is a bad idea.

    If you want to be sure that there's a leak at some point you should really create a minimal compilable example that contains the minimum code to trigger the problem.



  • @SGaist thank You for Your reply, I really do not know whom can I ask.
    Besdies that I do not think this is a deep copy issue.
    I have just commented out all bits() related code

    protected:
       virtual void mouseMoveEvent(QMouseEvent * event) Q_DECL_OVERRIDE
       {
            /*
           static uchar c = 0;
           c=c+0x20;
           uchar* pbits = im->bits();
           for(int i=0; i<1024*1024; i++)
           {
               pbits[i*4+0] =0xFF;
               pbits[i*4+1] =c;
               pbits[i*4+2] =0xFF;
               pbits[i*4+3] =0xFF;
           }
           */
           gi->setPixmap(QPixmap::fromImage(*im, Qt::NoFormatConversion));
       }
    

    But I see issue is still there.
    Still huge memory leaks during mouse press and move.

    Second thing. Issue is very simple to avoid - just comment out OpenGL viewport:

        GraphicsView window(&scene);
        //QGLWidget* gl = new QGLWidget();
        //window.setViewport(gl);
    

    And that is all - no more memory leaks. This cannot be related to QImage.bits(), right?

    3rd thing. Issue happens only on some machines, not on every.
    I have one machine with windows 8 where this happens.
    On other Windows 8 machine this does not happen. On 3rd machine with windows10 this does not happen.

    I begin to think that this is either particular machine OpenGL drivers issue or missing some Windows updates related to graphcis. Can this be true?


  • Lifetime Qt Champion

    From your description, the driver installed could indeed be at fault. Application using OpenGL depends heavily on the drivers installed and this is nothing Qt specific. Thus if there's a bug in the driver, then application will appear as being buggy though they likely be innocent.

    Note that doing heavy processing and setting setting a pixmap in a function like mouseMoveEvent isn't a nice thing to do, this event will be triggered many times and quickly so you might be swamping your application yourself doing that.



  • @SGaist well,
    I have installed into VMWARE virtual machine fresh Windows 8.1 N - Updated from MSDN.
    VMWARE tools are installed.
    Trying same example with enabled OpenGL

    QGLWidget* gl = new QGLWidget();
    window.setViewport(gl);
    

    Any setPixmap() to QGraphicsPixmapItem on scene causes memory leak.
    Not setPixmap() exactly but maybe events triggered by setPixmap().
    Disabling OpenGL helps.

    I do not see any possibility to modify QGraphcisPixmapItem on scene on the fly.

    On windows 10 I do not see such issue, neither on real HW computer nor in VMWARE machine with win10.
    Feel myself stupid - cannot resolve this issue.

    Bad thing is that application already distributed between users and some of them complain on crash (out of memory).


  • Lifetime Qt Champion

    Can you get a stack trace from that ?



  • A leak in the OpenGL driver is a real possibility. I had one of those many years ago, and was lucky to get a fix by the graphics card vendor. Observe whether the leak occurs always on cards from the same chipset vendor. Consider that even VMWare has to rely on the underlying hardware drivers at some point, otherwise drawing would be rather slow.

    Have you tried upgrading your VM (on which the leak occurs) to Win10? Does the leak occur then?

    Back then, I decided to run my software without any hardware acceleration. I was lucky the performance was sufficient, so I could rely on the application to behave the same way on all target systems.



  • @SGaist
    I hope I found solution for bug in my app.
    If I use

    //QGLWidget* gl = new QGLWidget();
    QOpenGLWidget* gl = new QOpenGLWidget();
    window.setViewport(gl);
    

    then I do not see memory leaks in my app on WIndows 8.1.
    One line of code changes everything..
    I will do more tests on side effects..


  • Lifetime Qt Champion

    Seeing QGLWidget I didn't realise that you were using Qt 5... QGLWidget has been deprecated in favour of QOpenGLWidget.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.