PDA

View Full Version : Implementing cached rendering of a widget.



emmanuel
10th February 2010, 21:12
Hello,
Here is the problem. A Widget takes time to render (this is the short explanation) and upon a paintEvent we want to render it in two stages:
1/ If not already done, render the Widget into a cache (Pixmap)
2/ Do the actual painting of the Widget using the cache.

I tried the naive solution below:


template <class WIDGET>
class CachedWidget : public WIDGET
{
public:
CachedWidget(QWidget *parent = 0) : WIDGET(parent), m_paintState(Idle) { }

virtual void paintEvent ( QPaintEvent * event ) {
switch(m_paintState) {
case Idle: // paint event occurred when widget was 'idle'
m_paintState = Painting;
if(m_cache.size() != QWidget::size()) {
m_cache = QPixmap(QWidget::size());
}

// this will somehow trigger a secondary paint event
// this is out of our control
QWidget::render(&m_cache);

{ QPainter painter(this);
painter.drawPixmap(event->rect(), m_cache, event->rect()); }
m_paintState = Idle;
break;

case Painting: // paint event occurred when widget was already painting
WIDGET::paintEvent(event);
break;
}
}

private:
enum PaintState { Idle, Painting };
PaintState m_paintState;
QPixmap m_cache;
};

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CachedWidget<QWidget> w;
w.show();
return a.exec();
}


This 'solution' does not work because to render the Widget into a cache we have to resort to the QWidget::render() function, and this function happens to somehow trigger another paintEvent, and Qt forbids reentrancy in the paintEvent() method.

The question is the following. When inside the paintEvent() function, is there a way to perform the painting into a QPaintDevice that is different than the Widget that received the QPaintEvent?
Or is it possible to otherwise implement a caching system similar to the the intention above?

Another question: since the render() method manages to render a QWidget in a different QPaintDevice, why does it have to send a QPaintEvent to the QWidget?

Thanks!

Lykurg
10th February 2010, 21:44
Is it possible that you do the caching direct in WIDGET? if so, your code should work well. Inside the paint event check if your pixmap is still valid. If not, update it. otherwise just paint it. Like you do in your cached variant.

Why render is sending an update is probably that it want ensure that the right/updated view is rendered.


And I see right now: Shouldn't it be: WIDGET::render(&m_cache)?

emmanuel
14th February 2010, 19:29
Thanks for your reply.


Is it possible that you do the caching direct in WIDGET? if so, your code should work well. Inside the paint event check if your pixmap is still valid. If not, update it. otherwise just paint it. Like you do in your cached variant.


Actually, for in the application I have in mind, WIDGET would be QGraphicsView.
I cannot modify this widget to introduce some caching, short of making a full copy of its code into a custom widget.


Why render is sending an update is probably that it want ensure that the right/updated view is rendered.


That makes sense.


And I see right now: Shouldn't it be: WIDGET::render(&m_cache)?

Actually no. When I try to do that with WIDGET=QGraphicsView, it does not compile because a method named render()
is also defined in QGraphicsView but with a different signature.

The original problem was to have a QGraphicsView with more than one QGraphicsScene arranged in layers, so that some
cache could be placed between some of these layers. There no doubt are solutions to this problem. The challenge is to find
the smartest one.
One solution (or hack) that works not too bad is to have an invisible QGraphicsView draw itself into a QPixmap that is used as a
background brush by the visible QGraphicsView.
The QGraphicsScene that needs to be cached goes into the invisible QGraphicsView.
The one that is being manipulated and not cached goes into the visible QGraphicsView.
But there are some imperfections with this solution.

Lykurg
14th February 2010, 19:50
The original problem was to have a QGraphicsView with more than one QGraphicsScene arranged in layers, so that some
cache could be placed between some of these layers. There no doubt are solutions to this problem. The challenge is to find
the smartest one.
One solution (or hack) that works not too bad is to have an invisible QGraphicsView draw itself into a QPixmap that is used as a
background brush by the visible QGraphicsView.
As I understand you right you want to design something like a paint program with layer. For now you want to realize that by using different scenes as layers. But how about using QGraphicsItemGroup as layers. than you only need one scene and Qt will handle all the caching stuff for you.

emmanuel
16th February 2010, 20:37
As I understand you right you want to design something like a paint program with layer. For now you want to realize that by using different scenes as layers. But how about using QGraphicsItemGroup as layers. than you only need one scene and Qt will handle all the caching stuff for you.

Thanks for your advice. That is sure a smart solution that I had not thought about before.
Unfortunately, as soon as I put transparent items in the layer (which is the reason I wanted some cache in the first place)
the ItemGroup does not seem to be able to cache them as a whole. Maybe it is not meant to, or I am doing something wrong.
Perhaps there is a way by reimplementing some functions of ItemGroup as regards opacity and such or some other hack
with flags or anything. I'll check about it but I'm not too hopeful.