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 and GridLayout. 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: 0x9ee0efd0

    Here 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 the source 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 the source attribute changes to a non-cached string (even if the cache attriubte is set to false 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.


  • @wumpus


    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!

  • @t3685

    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 and contentY 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.