PDA

View Full Version : QGraphicsView and many QGraphicsPixmapItem items - memory managment



goofacz
17th November 2011, 15:51
Hello!

I'm working on project focused on map similar to Google Maps. The goal is to display whole map in QGraphicsView widget. To do this I created a QGraphicsScene built of many QGraphicsPixmapItem items. Each item stands for one map tile (A small PNG graphic). Because map is quite large (it's square of about 512x512 tiles and more) I'm trying to load tiles on demand (when they're visible in QGraphicsView widget's viewport area).

Some parts of code:

I create all tiles (QGraphicsPixmapItem items) at the beginning and set them with "empty" QPixmap:


MapTile::MapTile(int zoomLevel, ...)
{
bool returnStatus (false);

returnStatus = QFile::exists(this->tilePath);
if(!returnStatus)
{
qWarning("Faield to locate tile PNG!");
return;
}

this->setPixmap(QPixmap (256, 256));
this->setPos (this->pixelsPosition.getX(), this->pixelsPosition.getY());
}


Then I add each tile to scene.

I created class based on QGraphicsView named MapView and overloaded resizeEvent() and scrollContentsBy() methods. Code sample below shows only scrollContentsBy(), resizeEvent() is analogous.

First thing, I map top-left and bottom-right corners of QGraphicsView object into scene coordinates (pointa, pointb). Later in first loop I unload (load "empty" pixmap) all items assigned to scene (I know, it's stupid to unload all tiles, but I will make this part of code smarter later). In next two nested loops I pick only items currently visible in QGraphicsView object and load pixmaps into them.


void MapView::scrollContentsBy ( int dx, int dy )
{
this->QGraphicsView::scrollContentsBy(dx,dy);

MapScene* currentScene = this->zoomLevelScenes [this->currentZoomLevel];
QPointF pointa (this->mapToScene(0, 0)); // top-left
QPointF pointb (this->mapToScene(this->height(), this->width())); //bottom-right
QList<QGraphicsItem*> list = currentScene->items();

foreach(QGraphicsItem* item, list)
{
((MapTile*)item)->loadPixmap(false);
}

for (qreal x = pointa.x() - 300; x < pointb.x() + 300; x += 255)
{
for (qreal y = pointa.y() - 300; y < pointb.y() + 300; y += 255)
{
MapTile* tile ((MapTile*) currentScene->itemAt(x,y));
if (tile!=0)
tile->loadPixmap(true);
}
}
}


My MapTile::loadPixmap() method:


void MapTile::loadPixmap(bool status)
{
QPixmap pixmap (256, 256);
bool returnStatus (false);

if (status)
{
returnStatus = pixmap.load(this->tilePath);
if(!returnStatus)
{
qWarning("Faield to load tile PNG!");
return;
}
}

this->pixmapLoaded = status;
this->setPixmap(pixmap);
}


Code seems to work fine, because folder with tiles has about 500 MB and my program after start use only about ~25 MB. I also see correct map in window (there are no blank tiles displayed). The problems is that when I scroll map or resize window program is slowly, but constantly allocating more and more memory (on single resize event it allocates another 0.2 - 0.4 MB of RAM). After some time of working with program it becomes serious.

Any idea what's wrong? I assume, that I'm doing something wrong, right? Or maybe I'm not aware of some data chaching method used in Qt? I worked a lot with GTK+ and i'm pretty new to Wt...

d_stranz
17th November 2011, 22:15
Your loadPixmap( false ) isn't really clearing the pixmap, it is simply setting it to a blank pixmap of the same size as the tiles. So, even if you are scrolling tiles out of the view, you really aren't getting rid of the memory they use.

Change the logic in loadPixmap so that if "status" is false, you call setPixmap( QPixmap() ) so it really is empty.

By the way, it is never necessary to call member functions with "this->" when you are inside the class. It is also odd that you use constructor syntax when you define a local variable:


bool returnStatus( false );

is very unusual for native types, and I had to stop and think about that. Of course it is correct, just unusual. For native types, you usually see the more common form of declaration and assignment:


bool returnStatus = false;

Is there some reason why you use the first method?

goofacz
17th November 2011, 23:52
Thank you for response, I'll check if it works for me as soon as it's possible :)

About variables initialization and using "this->". It's part of coding style, which we worked out with friends at university. It gives us common syntax for variables initialization (so it does not matter if we initialize class object or primitive type variable) and unambiguous statements meaning (for example when we have in class field named "foo" and use "foo" as an argument's name in one of methods in the same class). We don't like to have special name pattern for class fields (e.g. add prefix "m_"). We prefer to write:

Foo* bar (0);
instead of:

Foo *bar (0);
because in our opinion "Foo*" stand for valid name type in C++ etc. We have pertty much other "rules" :)

Code you see above is a "working draft" ;) Final version is always much more elegant, it has intuitive variables naming, better intending etc. :)

goofacz
18th November 2011, 12:46
@d_stranz, your idea does'n work for me. When I set item to QPixmap() in result I see... nothing. i does not matter if I try to resize it or not, it's always white. My scene does to not work properly at all, because I don't see scroll bars. I seems that, when I set item with QPixmap() it's being removed from scene at all (my scene should be really huge). Note that I set item's position manually in MapTile constructor.

Any other idea? Maybe there is any way to get rid of pixmap caching...?

d_stranz
18th November 2011, 16:14
Later in first loop I unload (load "empty" pixmap) all items assigned to scene (I know, it's stupid to unload all tiles, but I will make this part of code smarter later). In next two nested loops I pick only items currently visible in QGraphicsView object and load pixmaps into them.


Maybe there is any way to get rid of pixmap caching...?

I don't think this has anything to do with pixmap caching, if that even occurs. The problem with memory is that as long as you keep adding more tiles, the memory use will continue to grow. Do you ever -remove- a QGraphicsItem from the scene? From what I can see of your code, you just keep adding them. As the user moves around the map, more and more tiles are added, and memory continues to grow. Your first statement about "unloading" pixmaps simply isn't true - you are -emptying- the pixmap and setting all the pixels to whatever the default value is, but you aren't "unloading" anything from memory. A 256 x 256 pixmap still occupies the same memory, no matter what color the pixels are. Setting it to QPixmap( 256, 256 ) just changes the color but doesn't reduce the size at all.

Unless you get rid of the QGraphicsItems (or their pixmaps) for tiles that are no longer visible on the screen, these will still use memory. Setting their contents to an empty pixmap (QPixmap()) -will- reduce their memory use, but apparently you are using the actual size of the pixmap when you calculate the bounding rectangle for each item. A pixmap with zero dimensions will screw up the calculations, as you see.

So, I think that if your tile size is fixed at 256 x 256, then that's what you return for the bounds, whether a pixmap is loaded or not. Then the computation for overall scene size will be correct even if no pixmaps are loaded. Next, you need to get rid of the storage used for pixmaps that are not visible. Either keep the tile item around but set it to an empty pixmap (like I suggested), or actually remove it completely. If you remove it, you need to make sure the scene still returns the correct bounding rect so the scrollbars work properly.

goofacz
19th November 2011, 22:05
Hello!

Thanks, when I set fixed scene size and set items with QPixmap() it solved problem :)

There was a problem however, like you saw I mapped viewport coordinates to scene coordinates:

QPointF pointa (this->mapToScene(0, 0)); // top-left
and then I used QGraphicsScene::itemAt() to find "truly" visible items. The problem is that after setting item's pixmap empty, it's dimensions is 0x0 pixels :) I added new field to MapTile class, which holds top-left coordinates of tile so I able to calculate it given tile is in visible area or not quite fast.

Than you for help!