Huge Tilemap with Flickable and GridLayout for Android deployment impossible?
-
Hi everyone,
I am trying to realize a large map application, which I want to deploy on Android 4.4 - 5.0. I have 233 png-files, which resulted from cutting a huge png (~18000 x 13000 px) file into 1024x1024 px tiles. As a QML Windows desktop application everything runs just fine, even when I am loading all tiles into a
Flickable
andGridLayout
. In case of Android, however, when loading more than ~90 tiles (i.e. 5*18), the app crashes at startup. The error message reads like:W/Adreno-EGL(17217): <qeglDrvAPI_eglSwapBuffers:3602>: EGL_BAD_CONTEXT
W/Qt (17217): (null):0 ((null)): QEGLPlatformContext::swapBuffers(): eglError: 12294, this: 0x9ee0efd0Here is the main code for reference:
import QtQuick 2.3 import QtQuick.Window 2.2 import QtQuick.Controls 1.2 import QtPositioning 5.4 import QtQuick.Layouts 1.1 Rectangle { id: backgroundRect color: "lightblue" Flickable { id: flickable1 boundsBehavior: Flickable.DragAndOvershootBounds anchors.fill: parent contentHeight: 12*1024+466 contentWidth: 17*1024+192 flickableDirection: Flickable.AutoFlickDirection GridLayout { id: bigGrid rowSpacing: 0 columnSpacing: 0 columns: 18 Repeater { model: tilesModel // This is created on c++ side as StringList containing the filenames Image { asynchronous: true cache: false source: modelData } } } } }
To me it seems to be an OpenGL-on-Android-related problem. I tried using an ImageProvider on the c++ side, but got stuck there as well.
Some specs: Qt5.4, QtCreator3.3.0, Android for armeabi-v7a (GCC 4.9, Qt 5.4.0), android-ndk-r10d, jdk1.8.0_25, Sony Experia Z1 (Lollipop)
Any help/ideas appreciated
-
It looks like you're loading all the images at start of your application. Your Android device probably ran out of memory.
You probably need to switch to an approach where you only load the pictures as needed
@t3685 Thanks for the answer. You are right, of course.
My problem is only that I have no clue how I could realize this "loading on demand" version. Of course, less than 3 tiles are visible at first and I can track the
Flickable
contentX and contentY properties from inside QML. I even managed to access these properties from the C++ side, but how do I stop loading all images at startup in the first place? Should I not set thesource
attribute of the Images in QML, perhaps? Then later I set the values from C++ side on demand?Actually, I hoped to be able to exploit the
requestImage()
function of my QImageProvider, but due to caching (and some undocumented "bug") this function is only called, when thesource
attribute changes to a non-cached string (even if the cache attriubte is set tofalse
for the image). In effect, I can only do the coordinate checking once at startup for all the pictures, if I dont add a random number to the source-string with each frame.Well, I'd be glad if anyone could help here.
Best,
wumpus -
@t3685 Thanks for the answer. You are right, of course.
My problem is only that I have no clue how I could realize this "loading on demand" version. Of course, less than 3 tiles are visible at first and I can track the
Flickable
contentX and contentY properties from inside QML. I even managed to access these properties from the C++ side, but how do I stop loading all images at startup in the first place? Should I not set thesource
attribute of the Images in QML, perhaps? Then later I set the values from C++ side on demand?Actually, I hoped to be able to exploit the
requestImage()
function of my QImageProvider, but due to caching (and some undocumented "bug") this function is only called, when thesource
attribute changes to a non-cached string (even if the cache attriubte is set tofalse
for the image). In effect, I can only do the coordinate checking once at startup for all the pictures, if I dont add a random number to the source-string with each frame.Well, I'd be glad if anyone could help here.
Best,
wumpusHi,
The repeater you are using creates all the images immediately.
Since you don't have a huge amount of tiles consider placing your images in loader elements. The loader element has a property called active which can be used to load or unload a qml component.
You would then need to add logic to switch this property on or off when they need to shown or not.
If the number of tiles increases you will have to think of other solutions.Ps, the default image provider is very efficient so I wouldn't mess too much if I were you :-)
Good luck!
-
Hi,
The repeater you are using creates all the images immediately.
Since you don't have a huge amount of tiles consider placing your images in loader elements. The loader element has a property called active which can be used to load or unload a qml component.
You would then need to add logic to switch this property on or off when they need to shown or not.
If the number of tiles increases you will have to think of other solutions.Ps, the default image provider is very efficient so I wouldn't mess too much if I were you :-)
Good luck!
Hi,
yes, indeed. This was the problem.I solved ot now by creating an item, which contains a grid of "only" 36 (i.e. 6x6) elements inside the Flickable.
Item { id: imageItem GridLayout { id: localGrid columns: 6 rows: 6 rowSpacing: 0 columnSpacing: 0 Repeater { model: tile1000model Image { transformOrigin: Item.Left asynchronous: true sourceSize.height: 1024 sourceSize.width: 1024 source: modelData } } } }
This grid is then dynamically moved over the Flickable-area, depending on the
contentX
andcontentY
changes:onContentXChanged: updateImages(contentX, contentY) onContentYChanged: updateImages(contentX, contentY) function updateImages(x, y) { var _x = Math.floor(x / 1024) var _y = Math.floor(y / 1024) if (_x < 0) { _x = 0 } if (_y < 0) { _y = 0 } var _x2 = _x var _y2 = _y if (_x >= 2) { // not at left border _x2 -= 2 } if (_y >= 2) { // not at top _y2 -= 2 } console.log("_x", _x, "prevX", prevX, "_y", _y, "prevY", prevY) if (Math.abs(prevX - _x) > 1 || Math.abs(prevY - _y) > 1) { imageItem.y = _y2 * 1024 imageItem.x = _x2 * 1024 for (var i = 0; i < localGrid.children.length; ++i) { var ix = i % 6 + _x2 var iy = Math.floor(i / 6) + _y2 if (localGrid.children[i].source) localGrid.children[i].source = "qrc:/slices/tile1000_" + iy + "_" + ix + ".png" } prevX = _x prevY = _y } }
This works fine, but when scrolling from right to left, or from bottom to top, some flickering happens, because the loading takes too much time.
Well, thank you for your help.