Lucidic
Lucidic

Reputation: 173

What is the most efficient way display threaded graphics in Qt?

This isn't my project, but for simplicity's sake let's say I just need to print moving squares (which is what I actually did as an intermediate step). I have seen so many ways to make graphics in Qt but it's really unclear to me what is the "best" way.

I want my graphics running in a separate thread of course, and I have made this happen by simply painting to a widget or by moving the painting functionality into its own thread and also by painting to an image in a different thread and then displaying the image. I do this by making a QObject and then assigning it to a QThread, not by subclassing QThread and reimplementing run().

These ways all slow down after a couple thousand squares moving around the screen. I thought that this would be the simplest way to display graphics but I know there are other ways using openGL or QGraphicsView. I'm hoping stack overflow can save me research time and point me in the right direction.

Also, in case I am doing it the right way but implementing it wrong, here is some of my code:

In the render area:

RenderArea::RenderArea(QWidget *parent) : QWidget(parent)
{
    m_image = new QImage(size(), QImage::Format_ARGB32_Premultiplied);
    m_imageThread = new QThread();
    m_imageMaker = new ImageMaker(size());

    m_imageMaker->moveToThread(m_imageThread);

    setFocusPolicy(Qt::StrongFocus);

    QTimer* timer = new QTimer(this);

    //Frame rate approximations: 60FPS ~ 17 ms, 30FPS ~ 33 ms
    timer->start(33);

    connect(timer, SIGNAL(timeout()), this, SLOT(requestImageUpdate()));
    connect(this, SIGNAL(newImageParams(const QSize &)),
        m_imageMaker, SLOT(makeImage(const QSize &)));
    connect(m_imageMaker, SIGNAL(theImage(const QImage &)), this, SLOT(updateImage(const QImage &)));
    connect(m_imageThread, SIGNAL(finished()), m_imageMaker, SLOT(deleteLater()));
    connect(this, SIGNAL(doubleEntities()), m_imageMaker, SLOT(doubleEntities()));
    connect(this, SIGNAL(halveEntities()), m_imageMaker, SLOT(halveEntities()));

    m_imageThread->start();
}

RenderArea::~RenderArea()
{
    m_imageThread->quit();
    m_imageThread->deleteLater();
}

void RenderArea::paintEvent(QPaintEvent * /* event */)
{
    QPainter widgetPainter(this);
    widgetPainter.drawImage(0,0,*m_image);
}

void RenderArea::updateImage(const QImage& image) {
    *m_image = image;
    m_displayUpdatePending = false;
    update();
}

void RenderArea::requestImageUpdate() {
    if(!m_displayUpdatePending) {
        m_displayUpdatePending = true;
        emit newImageParams(size());
    }
}

void RenderArea::keyPressEvent(QKeyEvent *event) {
    switch (event->key()) {
    case Qt::Key_Plus:
        emit doubleEntities();
        break;
    case Qt::Key_Minus:
        emit halveEntities();
        break;
    default:
        QWidget::keyPressEvent(event);
    }
}

In the image maker:

ImageMaker::ImageMaker(QSize parentSize, QObject* parent) : QObject(parent)
{
    m_numEntities = 128;
    m_upperBound = 0;
    m_rightBound = parentSize.width();
    m_lowerBound = parentSize.height();
    m_leftBound = 0;

    //seed the random number generator
    QTime time = QTime::currentTime();
    qsrand((uint)time.msec());

    m_mutex.lock();
    randomizeEntities();
    m_mutex.unlock();
}

ImageMaker::~ImageMaker()
{

}

void ImageMaker::makeImage(const QSize & parentSize) {
    m_mutex.lock();

    advanceEntities();

    m_rightBound = parentSize.width();
    m_lowerBound = parentSize.height();

    QImage image(parentSize, QImage::Format_ARGB32_Premultiplied);


    QPainter imagePainter(&image);
    imagePainter.setRenderHint(QPainter::Antialiasing, true);

    imagePainter.setPen(QColor(0,0,0));

    for (int i = 0; i < m_numEntities; ++i) {
    imagePainter.drawRect(m_entityList.at(i).at(0),m_entityList.at(i).at(1),10,10);
    }

    imagePainter.setPen(QColor(255,0,0));
    imagePainter.drawText(10,10,QString::number(m_numEntities));

    imagePainter.end();

    m_mutex.unlock();

    emit theImage(image);
}

void ImageMaker::randomizeEntities() {
    m_entityList.clear();

    for(int i = 0; i < m_numEntities; ++i) {
        QList<int> thisRow;
        thisRow.push_back(qrand() % (m_rightBound + 1)); //random starting X coordinate
        thisRow.push_back(qrand() % (m_lowerBound + 1)); //random starting Y coordinate

        int tempRandX = (qrand() % 4) + 1;
        int tempRandY = (qrand() % 4) + 1;
        tempRandX *= (qrand() % 2) ? -1 : 1;
        tempRandY *= (qrand() % 2) ? -1 : 1;

        thisRow.push_back(tempRandX);                  //random starting X velocity
        thisRow.push_back(tempRandY);                  //random starting Y velocity

        m_entityList.push_back(thisRow);
    }
}

void ImageMaker::advanceEntities() {
    for (int i = 0; i < m_numEntities; ++i) {
        QList<int> thisRow = m_entityList.at(i);
        int xPos = thisRow.at(0);
        int yPos = thisRow.at(1);
        int xVel = thisRow.at(2);
        int yVel = thisRow.at(3);

        xPos += xVel;
        yPos += yVel;

        if ((xPos < 0 && xVel < 0) || (xPos > m_rightBound && xVel > 0)) xVel *= -1;
        if ((yPos < 0 && yVel < 0) || (yPos > m_lowerBound && yVel > 0)) yVel *= -1;

        thisRow.clear();
        thisRow << xPos << yPos << xVel << yVel;

        m_entityList.replace(i, thisRow);
    }
}

void ImageMaker::halveEntities() {
    m_mutex.lock();
    if (m_numEntities > 16) {
        m_numEntities /= 2;
    }
    randomizeEntities();
    m_mutex.unlock();
}

void ImageMaker::doubleEntities() {
    m_mutex.lock();
    m_numEntities *= 2;
    randomizeEntities();
    m_mutex.unlock();
}

Upvotes: 1

Views: 448

Answers (2)

HappyCactus
HappyCactus

Reputation: 2014

You could use QGraphicsScene and 1000 QGraphicsRectItem. This is the most portable and efficient way to do this (QGraphicsScene is quite optimized).

The most efficient way may be using OpenGL, but I am not sure it would worth the effort.

Upvotes: 0

MichaelCMS
MichaelCMS

Reputation: 4763

If you are trying to optimize your rendering speed by tackling multithreading optimizations you are doing it wrong.

What you should try to do is to batch the rendered primitives in only one draw call if possible (so, instead of drawing 1000 times 1 square, you should try drawing 1000 squares in one go).

I advise you to point your research in the OpenGl rendering optimizations direction (and learn how QT batches objects).

Upvotes: 2

Related Questions