Painting of custom widget takes up a lot of CPU...



  • I have a custom widget pictured below which takes up a lot of CPU.
    The widget consists of a centerbutton and x wedgeshaped buttons in a circle around it.
    Each button has a text inside which updates dynamically and also a color, with the button being
    filled or not depending on state.

    Repaints of the widget takes place 5-10+ times per sec. depending of numbers of updates..
    Disabling antialiasing does lower the CPU usage, but it still uses up to 50% of the CPU.
    Looks like a repaint of one Selectorbutton triggers a repaint of the entire widget, and I have no clue on how to avoid that.
    Any feedback on how this can be improved is much appreciated...

    !http://img836.imageshack.us/img836/4623/selecsmall.png(selector)!

    Below I'm listing code snippets of the paint methods:

    Main widget:

    @void Selector::paintEvent(QPaintEvent*)
    {
    QPainter painter(this);

    m_radius = qMin(size().width()/2, size().height()/2);
    
    switch(m_alignment)
    {
        case Qt::AlignLeft:
            horZCenter = m_radius;
            break;
        case Qt::AlignHCenter:
            horZCenter = m_radius + (width()-m_radius*2)/2;
            break;
        case Qt::AlignCenter:
            horZCenter = m_radius + (width()-m_radius*2)/2;
            break;
        case Qt::AlignRight:
            horZCenter = width()-m_radius;
            break;
        default:
            break;
    }
    
    if(m_radius != m_radiusLast)
    {
        m_radiusLast = m_radius;
        calculateData();
    }
    
    
    for(int i=0; i<m_buttons.size(); i++)
    {
        SelectorButton* button = m_buttons.at(i);
    
        // Create button contour
        button->setPath(m_paths.value(i));
    
        // Place head buttons
        button->setGeometry(m_p1.value(i).x()-button->m_transform->dx(), m_p1.value(i).y()-button->m_transform->dy(), 2000, 2000);
    
        // Draw head numbers
        QString str;
        painter.setPen(QPen(Qt::white, 1));
        int txtXOffset = painter.fontMetrics().width(str.setNum(i))/2;
        int txtYOffset = painter.fontMetrics().ascent()/2;
        painter.drawText(m_p2.value(i).x()-txtXOffset, m_p2.value(i).y()+txtYOffset, str.setNum(i+1));
    }
    m_centerButton->setGeometry(horZCenter-m_centerButton->rect().center().x(), m_radius-m_centerButton->rect().center().y(),m_radius/1.7, m_radius/1.7);
    

    }

    void Selector::calculateData()
    {
    for(int i=0; i<m_buttons.size(); i++)
    {
    m_paths.insert(i,createPainterPath(i));

        int r1 = m_radius*radiusFactor;
        qreal v1 = (pi/m_numHeads) * 2 * (i+(m_numHeads-rotation));
        QPoint p1(round(cos(v1)*r1)+horZCenter, round(sin(v1)*r1)+m_radius);
        m_p1.insert(i,p1);
    
        QPoint p2(round(cos(v1)*r1/2.0)+horZCenter, round(sin(v1)*r1/2.0)+m_radius);
        m_p2.insert(i,p2);
    }
    

    }

    QPainterPath Selector::createPainterPath(int)
    {
    qreal pi = 3.1416;
    int r1 = m_radius/3;
    int r2 = m_radius-m_radius/5;

    m_offset = (r2-r1)/2;
    qreal v1 = pi/m_numHeads;
    qreal v2 = pi/m_numHeads;
    QPoint p1(round(cos(v1)*r1)-r1-m_offset, round(sin(v1)*r1));
    QPoint p2(round(cos(v1)*r1)-r1-m_offset, round(-sin(v1)*r1));
    QPoint p3(round(cos(v2)*r2)-r1-m_offset, round(sin(v2)*r2));
    QPoint p4(round(cos(v2)*r2)-r1-m_offset, round(-sin(v2)*r2));
    
    m_xOffset = p1.x();
    m_yOffset = 0;
    QPainterPath path;
    
    path.moveTo(p1);
    path.lineTo(p3);
    path.arcTo(0-r2-r1-m_offset,0-r2,r2*2,r2*2,-sin(v2)*180/pi,round((qreal)360/(qreal)m_numHeads-.5));
    path.lineTo(p2);
    path.lineTo(p1);
    
    return path;
    

    }@

    SelectorButton:

    @void SelectorButton::paintEvent(QPaintEvent* e)
    {
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    int margin = 20;
    int yMin=1000, yMax=-1000, xMin=1000, xMax=-1000;
    
    // Calculate transformation
    for(int i=0; i<m_path.elementCount(); i++)
    {
        m_path.elementAt(i).x > xMax ? xMax = m_path.elementAt(i).x : xMax=xMax;
        m_path.elementAt(i).x < xMin ? xMin = m_path.elementAt(i).x : xMax=xMax;
        m_path.elementAt(i).y > yMax ? yMax = m_path.elementAt(i).y : xMax=xMax;
        m_path.elementAt(i).y < yMin ? yMin = m_path.elementAt(i).y : xMax=xMax;
    }
    painter.translate(-xMin*1.5, yMax*2.5);
    painter.rotate(m_angle);
    
    m_transform->setMatrix(painter.matrix().m11(),painter.matrix().m12(),0,painter.matrix().m21(),painter.matrix().m22(),0,painter.matrix().dx(),painter.matrix().dy(),1);
    
    QPen pen(Qt::white, 4, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
    
    // Handle state colors
    switch(m_state)
    {
        case WHITE:
            pen.setColor(Qt::white);
            break;
        case YELLOW:
            pen.setColor(QColor(224,202,0,255));
            break;
        case GREEN:
            pen.setColor(QColor(41,180,115,255));
            break;
        case RED:
            pen.setColor(QColor(236,28,36,255));
            break;
    

    case ORANGE:
    pen.setColor(QColor(255,165,0,255));
    }

    painter.setPen(pen);
    
    
    // Draw button contour
    painter.drawPath(m_path);
    if(isChecked())
    {
        QColor col = pen.color();
    
        painter.fillPath(m_path, QBrush(col.darker()));
        //pen.setStyle(Qt::DashDotDotLine);
    }
    
    // Draw text
    painter.rotate(-m_angle);
    painter.setPen(QPen(Qt::white, 1));
    painter.setFont(QFont("Arial", 12));
    QString str = text();
    painter.drawText(m_path.boundingRect(),Qt::AlignCenter | Qt::AlignHCenter, text());
    

    }@



  • Please properly format your code - it is unreadable.



  • Yes, it was caused by the total length of my post being to big. Should be ok now.



  • You are not using the paint event's argument. A QPaintEvent object contains information on the area to repaint. You could use that to limit the amount of painting you do to only the region that actually needs updating.

    There are many other optimizations possible:

    Pre-render the base drawing to a pixmap: the circle and the wedges and I guess the numbers between the circle and the wedges, as these are (relatively) static, right?

    Pre-render a filled version of each of the wedges for each state, or better yet, only render them the first time you need them, and then store them.

    in your drawing operation, first simply draw the pre-rendered pixmap base pixmap and the needed filled versions of the wedges at the right locations instead of rendering from scratch.

    try to not interleave painting operations that cause state changes in the painter. For instance, it is better to draw all text in one go.



  • Avoid division operations if you can. They are notoriously slow on some platforms (read: ARM). I.e. if precision is not important, you could replace some of div by two operations with bit shift.

    Pre-calculate stuff outside of loops as much as you can. For example, in Selector::calculateData() you could calculate r1 and outside the for-loop. Second example, in Selector::paintEvent() you call painter.setPen(QPen(Qt::white, 1)); for each button; you could just set it once outside the loop.

    Check if you can optimize your code. I.e. qMin(size().width()/2, size().height()/2); should be the same as qMin(size().width(), size().height())/2; but you only do one division. Some compilers might do this automatically for you, but you just can't count on those.

    Out of curiosity, which platform you are on?



  • [quote author="Andre" date="1319198815"]You are not using the paint event's argument. A QPaintEvent object contains information on the area to repaint. You could use that to limit the amount of painting you do to only the region that actually needs updating.[/quote]

    One of my first ideas of improvement was also to only repaint the buttons that have been updated.
    I did look in the region argument of the event. However it's seems that, because of the wedge shape of the buttons, the bounding rectangle of one button will cause the neighbour to get repainted aswell, which cascades around the entire circle.

    I did try the prerendering of sorts. Just for testing I prerendered each button to a QImage the first time, and on next paintevent, I just painted the prerendered image.
    Maybe I didn't do it correctly, but this made the widget so laggy, you wouldn't believe it..

    I'm on a X86 platform running linux, but without X11 or hardware acceleration.
    It's a Via C7 with Unichrome Pro IGP. Was looking into acceleration, but haven't been able to find anything useful.


Log in to reply
 

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