Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QImage in a loop causing mipmap issues w/ OpenGL. Image draws black after first iteration.
Forum Updated to NodeBB v4.3 + New Features

QImage in a loop causing mipmap issues w/ OpenGL. Image draws black after first iteration.

Scheduled Pinned Locked Moved Unsolved General and Desktop
8 Posts 2 Posters 1.1k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    Domiran
    wrote on last edited by Domiran
    #1

    I'm drawing a blank on this. Ha!

    I have a level editor for a game engine. This part draws a preview of what you're going to paste when pasting terrain. The function is passed a valid QPainter. For each tile in its list, it gets the raw tile image, copies it to a QImage and paints it.

    void LevelClipboard::PreviewTerrain(Chain::Map::Grid& grid, const Chain::Map::DungeonBuilder::GridNode& topLeftNode, QPainter& painter)
    {
    	auto& lvl = Chain::game.Level();
    	auto& atlas = *lvl.Atlas();
    	auto offset = topLeftNode.gridPosition - glm::ivec2(copiedTerrainWidth / grid.get_blocksize().x / 2, copiedTerrainHeight / grid.get_blocksize().y / 2);
    	auto blockSize = grid.get_blocksize();
    	auto sz = atlas.tile_size();
    	QImage tileImg(blockSize.x, blockSize.y, QImage::Format::Format_RGBA8888);
    	tileImg.fill(QColor::fromRgb(255, 255, 255, 255));
    	for (auto& copiedNode : copiedTerrain)
    	{
    		for (auto& layer : copiedNode.layers)
    		{
    			if ((layer.brush != Chain::TileAtlas::INVALID_BRUSH) && (layer.tileIndex != Chain::TileAtlas::INVALID_TILE))
    			{
    				auto tileid = Chain::TileAtlas::tile_id(layer.tileIndex, layer.brush);
    				auto rawTile = atlas.get_tile(tileid);
    				// QImage tileImg(blockSize.x, blockSize.y, QImage::Format::Format_RGBA8888); // having this here instead of the top does not help
    				auto bits = tileImg.bits();
    				memcpy(bits, rawTile.data(), sz.x * sz.y * 4);
    				auto targetGridPos = copiedNode.gridPosition + offset;
    				painter.drawImage((targetGridPos.x * blockSize.x) - (blockSize.x / 2.0f), (targetGridPos.y * blockSize.y) - (blockSize.y / 2.0f), tileImg);
    			}
    		}
    	}
    }
    

    This works great! For the first tile, and then the rest are drawing completely black.

    81feecbb-5ce1-4d2b-8638-ae84391f5a13-image.png

    I even tried making a global cache of all the raw tile data, since the tile atlas passes back a copy of the original (vector<u8vec4>), which will go away once the loop iteration ends. This doesn't work either. "fill()" after each drawImage doesn't help. The only way to make it work right is to make an array of how many images I need and use one per loop iteration but to do that seems remarkably stupid. Not even having a "QImage t(...)" in the inner loop seems to work. Same result as above.

    I did also try using a single, larger image which would comprise the entire preview image and copying the raw data onto the appropriate location and then painting that single image. The problem here is there are layers of transparency and I would need 4, one for each tile layer. Again, it sounds dumb to have to need more than one.

    The culprit seems to be merely calling "t.bits()". Remove that line (and the subsequent memcpy) and I get the original fill color over and over. Remove only the memcpy (leaving and the first tile is the fill color and the rest blank.

    It's worth mentioning that the rest of the image above is drawn by the engine itself in Open GL. Qt has no part in anything seen here except the grid and the black mass in the bottom right.

    I've been at this for hours. I know Qt uses some data sharing under the hood but I can't understand what is going on to cause this. Send help.

    [Edited the code a couple times for clarity, including alternate versions that also did not work.]

    [Edit]
    RenderDoc is showing Qt is drawing them in the expected order. Tile 1 at 0,0, Tile 2 at 1,0, etc. But, while the first texture is ok, it's telling me subsequent textures are incomplete and missing LOD data.

    f34a42e0-46e2-454d-a821-516884bdeb66-image.png

    Why is this ok on the first pass but not subsequent ones? I'd rather not bring the game engine into drawing this. Optionally I guess I could render to target using Qt and then QPainter the final image but this was supposed to be simple!

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      @Domiran said in Behavior of QImage in a drawing loop. Image draws black after first iteration.:

      it->second.data()

      Where does that one come from ?

      On an unrelated note, I would suggest a bit less of auto and single letter variables, it makes your code hard to follow.

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      D 1 Reply Last reply
      0
      • SGaistS SGaist

        Hi,

        @Domiran said in Behavior of QImage in a drawing loop. Image draws black after first iteration.:

        it->second.data()

        Where does that one come from ?

        On an unrelated note, I would suggest a bit less of auto and single letter variables, it makes your code hard to follow.

        D Offline
        D Offline
        Domiran
        wrote on last edited by
        #3

        @SGaist said in Behavior of QImage in a drawing loop. Image draws black after first iteration.:

        On an unrelated note, I would suggest a bit less of auto and single letter variables, it makes your code hard to follow.

        Apologies! There were so many versions of this code that the version you're seeing here is a hand-edited version I never tried to compile as I removed all the comments with different variations. It'll get cleaned up with better names after it actually works. The "it->second.data()" was from another version and I missed it in the edit. Changed the variable names a bit.

        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          Did you double check the rawTile ? Is its content valid ?

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          D 1 Reply Last reply
          0
          • SGaistS SGaist

            Did you double check the rawTile ? Is its content valid ?

            D Offline
            D Offline
            Domiran
            wrote on last edited by Domiran
            #5

            @SGaist said in Behavior of QImage in a drawing loop. Image draws black after first iteration.:

            Did you double check the rawTile ? Is its content valid ?

            It is. Once I made it use that single QImage and not draw until the end, it works fine (except as I said the multiple layers don't draw correctly due to transparency because I'm merely copying raw pixel data).

            void LevelClipboard::PreviewTerrain(Chain::Map::Grid& grid, const Chain::Map::DungeonBuilder::GridNode& topLeftNode, QPainter& painter)
            {
            	auto& lvl = Chain::game.Level();
            	auto& atlas = *lvl.Atlas();
            	auto offset = topLeftNode.gridPosition - glm::ivec2(copiedTerrainWidth / grid.get_blocksize().x / 2, copiedTerrainHeight / grid.get_blocksize().y / 2);
            	auto blockSize = grid.get_blocksize();
            	auto sz = atlas.tile_size();
            	QImage wholeImage(copiedTerrainWidth, copiedTerrainHeight, QImage::Format::Format_RGBA8888);
            	auto dest = reinterpret_cast<glm::u8vec4*>(wholeImage.bits());
            	wholeImage.fill(QColor::fromRgb(1, 2, 3, 4));
            	auto targetPoint = [this](int x, int y) { return (y * copiedTerrainWidth) + x; };
            	for (auto& copiedNode : copiedTerrain)
            	{
            		for (auto& layer : copiedNode.layers)
            		{
            			if ((layer.brush != Chain::TileAtlas::INVALID_BRUSH) && (layer.tileIndex != Chain::TileAtlas::INVALID_TILE))
            			{
            				auto tileid = Chain::TileAtlas::tile_id(layer.tileIndex, layer.brush);
            				auto iter = rawTileCache.find(tileid);
            				if (iter == rawTileCache.end())
            				{
            					auto rawTile = atlas.get_tile(tileid);
            					rawTileCache[tileid] = rawTile;
            					iter = rawTileCache.find(tileid);
            				}
            				auto src = iter->second.data();
            				for (auto y = 0; y < blockSize.y; y++)
            				{
            					auto targetPos = targetPoint(copiedNode.gridPosition.x * blockSize.x, (copiedNode.gridPosition.y * blockSize.y) +y);
            					memcpy(dest + targetPos, src + (y * blockSize.x), blockSize.x * 4);
            				}
            				auto targetGridPos = copiedNode.gridPosition + offset;
            				break; // draw first layer only
            			}
            		}
            	}
            	painter.drawImage((offset.x * blockSize.x) - (blockSize.x / 2.0f), (offset.y * blockSize.y) - (blockSize.y / 2.0f), wholeImage);
            
            1 Reply Last reply
            0
            • SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #6

              Then are you sure you are copying the right memory region ?

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              1 Reply Last reply
              0
              • D Offline
                D Offline
                Domiran
                wrote on last edited by Domiran
                #7

                After some more experimentation, I came up with this:

                void LevelClipboard::PreviewTerrain(Chain::Map::Grid& grid, const Chain::Map::DungeonBuilder::GridNode& topLeftNode, QPainter& painter)
                {
                	auto& lvl = Chain::game.Level();
                	auto& atlas = *lvl.Atlas();
                	auto offset = topLeftNode.gridPosition - glm::ivec2(copiedTerrainWidth / grid.get_blocksize().x / 2, copiedTerrainHeight / grid.get_blocksize().y / 2);
                	auto blockSize = grid.get_blocksize();
                	auto sz = atlas.tile_size();
                	QVector<QImage> tileLayers2;
                	tileLayers2.resize(4);
                	for (auto& img : tileLayers2)
                	{
                		img = QImage(copiedTerrainWidth, copiedTerrainHeight, QImage::Format::Format_RGBA8888);
                		img.fill(QColor::fromRgb(1, 2, 3, 4));
                	}
                	auto targetPoint = [this](int x, int y) { return (y * copiedTerrainWidth) + x; };
                	int i;
                	for (auto& copiedNode : copiedTerrain)
                	{
                		i = 0;
                		for (auto& layer : copiedNode.layers)
                		{
                			if ((layer.brush != Chain::TileAtlas::INVALID_BRUSH) && (layer.tileIndex != Chain::TileAtlas::INVALID_TILE))
                			{
                				auto tileid = Chain::TileAtlas::tile_id(layer.tileIndex, layer.brush);
                				auto it = raws.find(tileid);
                				if (it == raws.end())
                				{
                					auto r = atlas.get_tile(tileid);
                					raws[tileid] = r;
                					it = raws.find(tileid);
                				}
                				auto dest = reinterpret_cast<glm::u8vec4*>(tileLayers2[i].bits());
                				auto src = it->second.data();
                				for (auto y = 0; y < blockSize.y; y++)
                				{
                					auto target = targetPoint(copiedNode.gridPosition.x * blockSize.x, (copiedNode.gridPosition.y * blockSize.y) +y);
                					memcpy(dest + target, src + (y * blockSize.x), blockSize.x * 4);
                				}
                				auto targetGridPos = copiedNode.gridPosition + offset;
                				i++;
                
                				// down there, or up here
                			}
                		}
                	}
                
                	// moving this block of code to the "up here" comment produces a completely black image
                	for (auto& img : tileLayers2)
                	{
                		auto& img2 = tileLayers2[0]; // purposefully only drawing the same image over and over
                		painter.drawImage((offset.x * blockSize.x) - (blockSize.x / 2.0f), (offset.y * blockSize.y) - (blockSize.y / 2.0f), img2);
                	}
                }
                

                Alternatively, move that block of code up but change it to:

                				// down there, or up here
                				for (auto& img : tileLayers2)
                				{
                					//auto& img2 = tileLayers2[0];
                					painter.drawImage((offset.x * blockSize.x) - (blockSize.x / 2.0f), (offset.y * blockSize.y) - (blockSize.y / 2.0f), img);
                				}
                

                And you also get a correct image. Even more alternatively:

                				// down there, or up here
                				//for (auto& img : tileLayers2)
                				{
                					auto& img2 = tileLayers2[0];
                					painter.drawImage((offset.x * blockSize.x) - (blockSize.x / 2.0f), (offset.y * blockSize.y) - (blockSize.y / 2.0f), img2);
                					return;
                				}
                

                It will draw the first tile and the rest of the image is transparent. This proves it has nothing to do with the tile atlas data or copying into the right memory regions, as simply moving a block of code up and down and changing what layer it draws changes the output.

                I looked to see if for some reason it's overdrawing somehow and giving me black but there's zero evidence of that. Painting over an image multiple times should give me black but the very original version is moving the paint location on the original QPainter. I can see that if I step through and move the current line of code to the parts that actually perform the draw and skip the data copy. I can get the same tile to draw multiple times at its expected location, so the original QPainter logic using a single QImage as a data store (as in the original post) works right.

                I'm attaching RenderDoc to this to see what Qt is doing. I'll update if I find out it's my code or Qt.

                [Edit]
                RenderDoc is showing Qt is drawing them in the expected order. Tile 1 at 0,0, Tile 2 at 1,0, etc. But, while the first texture is ok, it's telling me subsequent textures are incomplete and missing LOD data.

                f34a42e0-46e2-454d-a821-516884bdeb66-image.png

                Why is this ok on the first pass but not subsequent ones?

                1 Reply Last reply
                0
                • SGaistS Offline
                  SGaistS Offline
                  SGaist
                  Lifetime Qt Champion
                  wrote on last edited by
                  #8

                  If data is incomplete it could be that either you have wrong or incomplete data stored there or that your offset might be off when accessing it.

                  Interested in AI ? www.idiap.ch
                  Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                  1 Reply Last reply
                  0

                  • Login

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • Users
                  • Groups
                  • Search
                  • Get Qt Extensions
                  • Unsolved