How to render a QGraphicsScene in a separate thread properly?

Viewed 357

I have to render a list of lines (>400000) from a model in a QGraphicsScene. Since this operation is long, I try to do the render operation in another thread.

Note : all my classes are template because I render different types of models with different view transformation for each type

First, I create the QGraphicsView from the main thread :

template<>
GCode3DView<Core::PrintingModel>::GCode3DView(int width, int height, QWidget* parent)
    : QGraphicsView(parent),
      m_scene(new GCode3DScene<Core::PrintingModel>(this)),
      m_result(),
      m_handler(),
      m_yRatio(2.0/3)
{
    int w = width;
    int h = height;
    setScene(m_scene);

    QTransform rotation;
    rotation.rotate(30, Qt::XAxis);
    setTransform(rotation);

    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setTransformationAnchor(QGraphicsView::NoAnchor);
    setCacheMode(QGraphicsView::CacheNone);
    setInteractive(false);

    m_scene->setSceneSize(w,h);
    m_scene->setSceneRect(*sceneRect*);
}

When the model (the list of lines) is ready, I call this function from the main thread :

template<class T>
void GCode3DView<T>::setSceneModel(const T *model)
{
    m_result = QPixmap();
    m_scene->setModel(model); //Creates QGraphicsItems if not enough, disables the one that will be unused (see the function below if needed)
    QtConcurrent::run(this,&GCode3DView<T>::saveImage, model);
}

Before the call to a concurrent run, I believe that every item of the scene belong to the main thread. The saveImage is then called from a concurrent thread, doing this :

template<class T>
void GCode3DView<T>::saveImage(){
    QRect rect = viewport()->rect();

    //Create pixmap, fill it, and create QPainter;
    QPixmap pixmap = QPixmap(size()*4);
    pixmap.fill(Utils::GENERAL_BACKGROUND);
    QPainter painter(&pixmap);

    //Render scene in image
    render(&painter, pixmap.rect(), rect); //---> The error occurs here
    //Copy result
    m_result=pixmap.copy();

    m_handler.emitImageReadySignal();
}

My scene is rendered in an image, that I display in the GUI Main thread. Most of the time this works, but sometimes I can see an error :

QObject::killTimer: Timers cannot be stopped from another thread

Which seems to come at the beginning of the render function, before painting the QGraphicsItems. What happens next is that even thought the rendering is finish the CPU usage is still 8%, which means that I still have a thread running and doing nothing. I don't understand why this is happening. I followed this thread and it seems that all my objects belongs to the same thread. How should I do to get a proper render in a separated concurrent thread ?

Thanks in advance.

EDIT Trying to be more precise, it seems that the render function tries to stop a timer running in another thread, which fails and throws the error. I guess that that timer not being killed causes a part of the rendering to continue even thought the render is finished. The GUI is still usable and does not froze, I guess that the thread still running could be the concurrent one used to render.

To be more precise about my QGraphicScene, here's its code : template

class GCode3DScene : public QGraphicsScene
{
    friend class LayerItem<T>;
public:
    GCode3DScene(GCode3DView<T>* parentView = nullptr, QObject* parent = nullptr);
    ~GCode3DScene();

    ///Sets the model, creates LayerItems if needed and hides the unused one
    void setModel(const T* model);
    ///Adds a layer to m_items and to the scene
    void addLayer();
    ///Removes every layer item and the grid
    void clearScene();
    ///Sets the scene size
    void setSceneSize(int width, int height);

protected:
    ///Computes the total progression on the rendering of the scene
    void updateNumberOfRenderedPoints(int nOfRendered);

private:
    int                     m_sceneWidth;   ///Scene's width
    int                     m_sceneHeight; ///Scene's Height
    QList<LayerItem<T>*>    m_items; ///Items to display inside the scene
    LayerItem<T>*           m_grid; ///Grid to use as background
    const T*                m_model; ///Model to display
    double                  m_renderingProgression; ///Progression of the rendering
    GCode3DView<T>*         m_parentView; ///Pointer to parent
};

and the setModel function :

template <class T>
void GCode3DScene<T>::setModel(const T *model)
{
    m_model = model;
    m_renderingProgression = 0;

    //Create grid if not existant
    if(!m_grid){
        m_grid = new LayerItem<T>(this,-1,m_sceneWidth,m_sceneHeight);
        addItem(m_grid);
    }
    m_grid->setModel(model);

    //Get number of layers
    int numberOfLayer = model ? model->getNumberOfLayers() : 0;
    if(numberOfLayer>=1000)
        numberOfLayer=1000;
    //Add Layers if there is not enough
    while(m_items.size()<numberOfLayer)
        addLayer();
    //Hide unused layers if there are too much
    for(int i = 0; i < m_items.size(); ++i){
        m_items.at(i)->setVisible(i<numberOfLayer);
        m_items.at(i)->setModel(i<numberOfLayer?model:nullptr);
    }
}

LayerItem::paint is just some geometric computation and calls to painter->drawLines

If needed, here is the code for GCode3DView :

template<class T>
class GCode3DView : public QGraphicsView
{
    friend GCode3DScene<T>;
public:
    GCode3DView(int width, int height, QWidget *parent = nullptr);
    ~GCode3DView();

    ///Sets the model to display to the scene and ask for render
    void setSceneModel(const T* model);
    ///Renders scene into an image
    void saveImage();

    ...

private:
    GCode3DScene<T>*    m_scene;    ///Scene to display the model
    QPixmap             m_result;   ///Render result

};
0 Answers
Related