Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to improve performance on a board without a GPU



  • Hello,

    I'm trying to decrease the frame rate of the rendering in a QGraphicsScene (qt version 4.8.6). but I can't seem to find which bit of code to patch (if I have to) to achieve what I want. I have found the define for the animation frame rate but I can't find how to control the frame rate for the actual rendering.

    I know that I can control the frame rate by setting viewport mode to NoViewportUpdate and using a timer to render the scene at a fixed rate but I'd like to avoid this because there is no graphics card on the board and the CPU usage goes quite high at 60% and sometimes up to 100% when I redraw repeatedly big sections of the screen (for example when I move a fader that controls a graph).

    I think I have already optimised the rendering code (so its points and paths etc are cached and the paint methods are pretty much just setBrush, setPen, drawRect/Arc/Path etc). And I already managed to improve performance a bit by using scene->setItemIndexMethod(QGraphicsScene::NoIndex) as any other option seems too slow (makes sense as the items in the scene are often moved, set to visible/hidden etc cause of animations). So it seems to me one of the few things left is to try reducing the frame rate.

    I have also tried to use directfb where I render changed items into pixmaps first, so I can use the accelerated directfb version of drawRect and drawPixmap (all other functions seem to be falling back to software rendering) but this seems even slower.

    So is it possible to reduce/control the rendering frame rate? I suspect that Qt targets 60fps?



  • Hello,

    Yes I would prefer to update the graph when the mouse moves rather than just when it stops. I have actually figured out a solution.

    I noticed that everything behind the graph is also rendered when I update the graph, as expected I guess since there's no depth test. So I basically just implemented a custom depth test.

    I added a "isTransparent" variable in my base class which inherits from QGraphicsItem. Then everytime QGraphicsView::paintEvent runs I create a path from the exposed region, iterate from front to back through the items that are about to be redrawn and test if the item still intersects the exposed region. If the item is not transparent I subtract it from the region (at which point it becomes a polygon) and continue to test and subtract (if its opaque) the next item.

    This hacked depth test seems to be getting rid of a lot of the items that are about to be redrawn. Something like half of them because there are quite a few opaque backgrounds and whatnot in the app. Its pretty fast too for a depth test.

    Now I wonder, is there actually anything in qt that already does this that I could use instead? I noticed some members called "exposed" and "opaque" before, but when I tried to use them they seemed really expensive. In any case my solution seems to be working pretty well.

    Here is the code btw:

    void GraphicsView::paintEvent(QPaintEvent* event)
    {
        const QRectF exposedRegion = mapToScene(event->region().boundingRect()).boundingRect();
    
        QPainterPath path;
        path.moveTo(QPointF(exposedRegion.x(), exposedRegion.y()));
        path.lineTo(QPointF(exposedRegion.x() + exposedRegion.width(), exposedRegion.y()));
        path.lineTo(QPointF(exposedRegion.x() + exposedRegion.width(), exposedRegion.y() + exposedRegion.height()));
        path.lineTo(QPointF(exposedRegion.x(), exposedRegion.y() + exposedRegion.height()));
    
        bool allItems = false;
        QList<QGraphicsItem *> itemList = itemsInRegion(exposedRegion, &allItems, viewportTransform());
        auto item = itemList.constEnd();
    
        while (item != itemList.constBegin())
        {
            --item;
            auto view = dynamic_cast<View*>(*item);
            if (view)
            {
                auto x = std::max(exposedRegion.x(), view->scenePos().x());
                auto y = std::max(exposedRegion.y(), view->scenePos().y());
                auto z = std::min(exposedRegion.x() + exposedRegion.width(), view->scenePos().x() + view->size().width());
                auto w = std::min(exposedRegion.y() + exposedRegion.height(), view->scenePos().y() + view->size().height());
    
                QPainterPath subviewPath;
                subviewPath.moveTo(QPointF(x, y));
                subviewPath.lineTo(QPointF(z, y));
                subviewPath.lineTo(QPointF(z, w));
                subviewPath.lineTo(QPointF(x, w));
    
                auto intersection = path.toFillPolygon().intersected(subviewPath.toFillPolygon());
                if (intersection.isEmpty())
                {
                    view->setBlockRender(true);
                }
                else
                {
                    view->setBlockRender(false);
                }
    
                // extract the subpath of opaque items from the path
                if (!view->isTransparent())
                {
                    path = path.subtracted(subviewPath);
                }
            }
        }
    
        QGraphicsView::paintEvent(event);
    }
    

    setBlockRender sets a boolean that I check in the paint method. If it is set I unset it and exit the function without rendering. Do you reckon this code can be made even faster? I wonder if I can actually get away without creating a Path (or without converting them to fillPolygons) from the bounding rect of the item and do the intersection test by just using the rect. Or is it going to be the same thing under the hood?

    Thank you for the replies!


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Qt doesn't have any frame rate target. It already tries to minimise the number of update it does.

    Can you explain what happens in your application ?

    Did you already tried 4.8.7 ?



  • Hello and thank you for the quick reply,

    I basically have a scene of items, mostly items that draw rectangles, pixmaps and text. And a couple of custom items that draw things like audio filter graphs that use drawPath for the fill and drawPolyline for the stroke. So when I move a fader that affects the graph the CPU usage goes up to 98% (Its a singlecore 500MHz Arm CPU driving a 800x480 16bit RGB screen using linuxfb driver). I've made sure that updating the path and the points for drawPath and drawPolyline is as cheap as possible and I'm just using one loop to update both of them. Commenting out the paint methods (for the fader and the graph) drops the usage pretty much down to 0-10% or so.

    Using a newer version of qt is a bit tricky as we are using buildroot and from what I see I cannot just change the qt version anywhere there. But I can try to build qt 4.8.7 manually and install it on the board. I think I'll do this next. Are there any major changes in the render engine in 4.8.7?


  • Qt Champions 2017

    Without seeing code we cannot comment much. But few questions.

    1. How are you doing painting ?
    2. How refresh is forced ?
    3. Is there something partof view can drawn and kept in memory only once ?

    I felt this is typical fast update issue.



  • Hello dheerendra,

    Here is some code:

    void EQFilter::updateResponse(vector<float> inputData)
    {
        m_points = calculatePointsForStroke();
        m_path = calculatePathForFill();
        update();
    }
    
    void EQFilter::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        Q_UNUSED(option);
        Q_UNUSED(widget);
    
        painter->setRenderHints(QPainter::Antialiasing
            | QPainter::SmoothPixmapTransform
            | QPainter::HighQualityAntialiasing, true);
    
        painter->setBrush(QBrush(bandColorsAlpha[m_band]));
        painter->drawPath(m_path);
    
        painter->setPen(QPen(QBrush(bandColors[m_band]), 1));
        painter->drawPolyline(m_points.data(), m_points.size());
    }
    

    So refreshing is done by calling update() function. The function updateResponse is called on a mouse move when I move a fader. There are about 4 instances of EQFilter and when a fader moves I only call updateResponse on one of them, although I noticed that the paint function runs for all the filters (I suspect cause their bounding rects are the same). I have also tried rendering each filter to a pixmap so that when I call update() I only need to call drawPixmap on all 4 of them (plus recreate the pixmap for the dirty filter) and not drawPath and drawPolyline but this is even slower. I suspect when you say save something into memory you mean a qpixmap?

    Also I have tried using removing all those hints on the painter (antialiasing etc) and that makes almost no difference either. Also as I've mentioned the code inside calculatePointsForStroke() and calculatePathForFill() is negligible as the performance goes down to around 10% when I just comment out the paint functions and I just leave the rest of the code.


  • Lifetime Qt Champion

    Are you doing updates for each values when moving ? Otherwise you could compress that e.g. only animate on stop.



  • Hello,

    Yes I would prefer to update the graph when the mouse moves rather than just when it stops. I have actually figured out a solution.

    I noticed that everything behind the graph is also rendered when I update the graph, as expected I guess since there's no depth test. So I basically just implemented a custom depth test.

    I added a "isTransparent" variable in my base class which inherits from QGraphicsItem. Then everytime QGraphicsView::paintEvent runs I create a path from the exposed region, iterate from front to back through the items that are about to be redrawn and test if the item still intersects the exposed region. If the item is not transparent I subtract it from the region (at which point it becomes a polygon) and continue to test and subtract (if its opaque) the next item.

    This hacked depth test seems to be getting rid of a lot of the items that are about to be redrawn. Something like half of them because there are quite a few opaque backgrounds and whatnot in the app. Its pretty fast too for a depth test.

    Now I wonder, is there actually anything in qt that already does this that I could use instead? I noticed some members called "exposed" and "opaque" before, but when I tried to use them they seemed really expensive. In any case my solution seems to be working pretty well.

    Here is the code btw:

    void GraphicsView::paintEvent(QPaintEvent* event)
    {
        const QRectF exposedRegion = mapToScene(event->region().boundingRect()).boundingRect();
    
        QPainterPath path;
        path.moveTo(QPointF(exposedRegion.x(), exposedRegion.y()));
        path.lineTo(QPointF(exposedRegion.x() + exposedRegion.width(), exposedRegion.y()));
        path.lineTo(QPointF(exposedRegion.x() + exposedRegion.width(), exposedRegion.y() + exposedRegion.height()));
        path.lineTo(QPointF(exposedRegion.x(), exposedRegion.y() + exposedRegion.height()));
    
        bool allItems = false;
        QList<QGraphicsItem *> itemList = itemsInRegion(exposedRegion, &allItems, viewportTransform());
        auto item = itemList.constEnd();
    
        while (item != itemList.constBegin())
        {
            --item;
            auto view = dynamic_cast<View*>(*item);
            if (view)
            {
                auto x = std::max(exposedRegion.x(), view->scenePos().x());
                auto y = std::max(exposedRegion.y(), view->scenePos().y());
                auto z = std::min(exposedRegion.x() + exposedRegion.width(), view->scenePos().x() + view->size().width());
                auto w = std::min(exposedRegion.y() + exposedRegion.height(), view->scenePos().y() + view->size().height());
    
                QPainterPath subviewPath;
                subviewPath.moveTo(QPointF(x, y));
                subviewPath.lineTo(QPointF(z, y));
                subviewPath.lineTo(QPointF(z, w));
                subviewPath.lineTo(QPointF(x, w));
    
                auto intersection = path.toFillPolygon().intersected(subviewPath.toFillPolygon());
                if (intersection.isEmpty())
                {
                    view->setBlockRender(true);
                }
                else
                {
                    view->setBlockRender(false);
                }
    
                // extract the subpath of opaque items from the path
                if (!view->isTransparent())
                {
                    path = path.subtracted(subviewPath);
                }
            }
        }
    
        QGraphicsView::paintEvent(event);
    }
    

    setBlockRender sets a boolean that I check in the paint method. If it is set I unset it and exit the function without rendering. Do you reckon this code can be made even faster? I wonder if I can actually get away without creating a Path (or without converting them to fillPolygons) from the bounding rect of the item and do the intersection test by just using the rect. Or is it going to be the same thing under the hood?

    Thank you for the replies!



  • By the way it seems the title is not very related to the thread anymore. Should I change it to something like "how to improve performance on a board without a GPU" or something like that?


  • Lifetime Qt Champion

    That's a good idea, it gives a better clue about what the question was about in the end.



  • Done. Thanks for all the help.


Log in to reply