2

I'm trying to create a Tile rendering program. Heres some basic code.

Header

class Tile: public QGraphicsItem
{
public:
Tile(void);
~Tile(void);
QGraphicsPixmapItem *tileItem;
void update(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget);
 protected:
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget);
};

CPP:

.Constructor etc
.
.

void Tile::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
    if(tileItem==NULL)
    {
        qDebug()<<"Loading Pixmap";
        QPixmap p("c:\\qt\\tile\\tile0-0.png");
        tileItem=new QGraphicsPixmapItem;
        tileItem->setPixmap(p); 
    }
    tileItem->paint(painter,option,widget);
}

I'm trying to make a application that will paste tiles of a big image onto a QGraphicsScene. But loading all the tiles at once is time consuming and takes up a lot of memory. So I'm subclassing QGraphicsItem and overriding paint. The paint method in the QGraphicsItem class is only called when a it comes into view inside the QGraphicsView. So by loading up my tile inside paint, I can basically create an application that loads tiles only when they come into view. This much is working so far.

To make the user experience better I'm use QtConcurrent to try an load the tiles up in a seperate thread. SO here's the changes I've made.

CPP

connect(&watcher,SIGNAL(finished()),this,SLOT(updateSceneSlot()));

void Tile::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
    if(tileItem==NULL)
    {   
        TilePainter=painter;
        TileOption=option;
        TileWidget=widget;
        qDebug()<<"Paint Thread id "<< QThread::currentThread();

        future=QtConcurrent::run(LoadTilePixmap,this);
        watcher.setFuture(future);
    }
    else
        tileItem->paint(painter, option, widget);

}    

LoadTilePixmap function:

void LoadTilePixmap(Tile *temp,QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
qDebug()<<"Loading Pixmap";
QPixmap p("c:\\qt\\tile\\tile0-0.png");
temp->tileItem=new QGraphicsPixmapItem;
temp->tileItem->setPixmap(p);
qDebug()<<"Loaded Pixmap";
}


void Tile::updateSceneSlot()
{
    qDebug()<<"updateSceneSlot Thread id "<< QThread::currentThread();
    tileItem->paint(TilePainter, TileOption, TileWidget);
}

This code should work but it keeps crashing at runtime as soon as paint gets called. After adding breakpoints I narrowed the problem down to temp->tileItem->paint(painter,option,widget); which causes the crash.

The output that I get is

Loading Pixmap 
Almost Loaded Pixmap 
First-chance exception at 0x6526174a (QtGuid4.dll) in Visualizer.exe: 0xC0000005: Access violation reading location 0xc88bffe1.
Unhandled exception at 0x6526174a (QtGuid4.dll) in Visualizer.exe: 0xC0000005: Access violation reading location 0xc88bffe1.

Could anyone help me and let me know why the lastline/paint method crashes. How can I fix it?

EDITED CODE TO UPDATE CHANGES

sleeping.ninja
  • 607
  • 1
  • 10
  • 21
  • 1
    I think you are running into general concurrency issues. You don't have any synchronization for the variables you are trying to access in multiple threads. You have to consider that after launching the future, the call returns immediately. Paint could be called many times while the other thread is working. How do you know that 1) you haven't already scheduled the work and 2) the work is actually done? Paint() just tests to see if the pointer is null, but how does it really know that the other thread is done? The GUI thread is probably getting switched in during the pixmap creation. – Dusty Campbell May 12 '11 at 05:23
  • This is the first time I'm trying multithreading so I really don't know if I'm doing it right. According to Qt concurrent documentation, Qt's supposed to take care of synchronization of variables. Secondly I even tried a signal slot mechanism where a queued slot is being used. So basically after the thread finishes execution it emits a signal which is put into a queue. The slot (which calls paint) is then called turn by turn. I think that should have resolved concurrency issues, don't know though. I've solved it now by calling update instead of paint. Update schedules a paint call. It works. – sleeping.ninja May 12 '11 at 13:49

3 Answers3

1

Only the main(also called GUI) thread can draw on the screen. The following line from the LoadTilePixmap() function, which you run in a separate thread, I believe, tries to paint the content of your pixmap item on the screen.

temp->tileItem->paint(painter,option,widget);

In the thread you should just load and prepare the image and when the thread is done, signal to the main thread that the image is ready and do the drawing from the main thread.

zkunov
  • 3,362
  • 1
  • 20
  • 17
  • Thats not the issue. I setup a signal slot mechanism. But the program still crashes. I did a quick get current thread to find which thread calls paint. The main thread calls paint. I'm still getting the same errors. `Unhandled exception at 0x65256977 (QtGuid4.dll) in Visualizer.exe: 0xC0000005: Access violation reading location 0x00000080.` I get a popup box that gives me two options break or continue. On clicking break it takes me to line 7130 of `qpainter.cpp` or line 2194 of `qglobal.h`. Any idea to whats going on? – sleeping.ninja May 10 '11 at 19:46
  • 1
    That was the main issue, but not the only one. Steffen saw one possible, and here is another one. You can't create the pointer to the QGraphicsPixmapItem like that. You must pass reference to the pointer, then your LoadTilePixmap() function will be getting pointer to a pointer. Actually I think it will be much more clear if LoadTilePixmap() function does not have any params, just create a QGraphicsPixmapItem in it and return a pointer to that object, this pointer you will then assign to your tileItem member. – zkunov May 10 '11 at 21:57
  • I tried that too. Still facing the same issue. Steffen's suggestion isn't an issue. This same code works perfectly if I don't use multithreading(the first two code snippets). – sleeping.ninja May 11 '11 at 07:59
1

It's not clear from the code you posted, but are you initializing tileItem to be NULL in the constructor of Tile? If not, that would be a possible explanation for the crash you are seeing.

Steffen
  • 2,888
  • 19
  • 19
  • tileItem is set to NULL in the constructor. But Qt is smart enough not to do anything with it since it is not visible. As soon as it becomes visible I load up a pixmap that can be seen. This works perfectly. In fact the first 2 snippets of code is exactly that. I have tested it out a lot and it works perfectly. The only issue comes in when I add multi-threading and try to paint it. If I comment out the last paint line my code works just fine. – sleeping.ninja May 11 '11 at 03:11
0

I solved the issue by avoiding paint and instead using the update function. From whatever documentation I've read update indirectly schedules a paint call. So in the end my code looks like this

void LoadTilePixmap(Tile *temp)
{
        QPixmap p("c:\\qt\\tile\\tile0-0.png");
        temp->tileItem=new QGraphicsPixmapItem;
        temp->tileItem->setPixmap(p);
        temp->update(0,0,511,511);
}

This will cause my overloaded paint function to get called for a second time but this time the if condition is false and it goes into else which paints. Not exactly an optimum solution but it works for now.

sleeping.ninja
  • 607
  • 1
  • 10
  • 21