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
};