Search code examples
c++sfml

Cannot create a sprite using a RenderTexture


I'm making a game, and I have created a class to store the map. I have a function to create a single sprite on which is drawn all the visible map. I've used a RenderTexture and then I create and return a sprite created with it. However, the sprite is completely white.

Here is the code for the map generation and the sprite draw

void Map::procedural_generate()
{
    cout << "Starting the map generation" << endl;
    int generation_max = m_size.x * m_size.y, 
        current_generation = 0;
    vector<Decor> decor_vector;
    m_decor_vector.clear();

    const vector<Decor> const_vector
    {
        Decor(GRASS)
    };

    for (int i = 0; i < m_size.x; i++)
    {
        decor_vector.clear();

        for (int j = 0; j < m_size.y; j++)
        {
            decor_vector.push_back(const_vector[GRASS]);
            decor_vector[j].set_position(Vector2f(i * 32, j * 32));
            current_generation++;
            cout << "Generation : " << current_generation << '/' << generation_max << '\r';
        }

        m_decor_vector.push_back(decor_vector);
        decor_vector.clear();
    }
    cout << "Map generation has ended" << endl;
}

Sprite Map::sprite()
{
    RenderTexture map;
    if (!map.create(WINDOW_WIDTH, WINDOW_HEIGTH))
        cout << "Map : Unable to create the RenderTexture" << endl;

    map.clear(Color::Green);

    for (int i = 0; i < m_size.x; i++)
        for (int j = 0; j < m_size.y; j++)
            map.draw(m_decor_vector[i][j].sprite());

    map.display();

    Sprite sprite(map.getTexture());
    return sprite; 
}

The problem seems to come from the map.draw part, as, if I use map.clear(Color::Red) before the double loop, the sprite stays white, but if I use sprite.setColor(Color::Red), it works. The fact is that the decor sprites are not covering the whole texture (the texture is 1920x1080, and the sprites 320x320), so I can't understand what's happening.

This is not the decor sprite loading, if I use map.draw(Decor(GRASS)) the sprite is displayed correctly.

I've tried to use pointer for const_vector and for the returned sprite, whithout success.


Solution

  • Popular mistake when using SFML.

    Sprite Map::sprite()
    {
        RenderTexture map;
        // ...
        Sprite sprite(map.getTexture());
        return sprite; 
    }
    

    map is local. When function ends, map is destroyed. Sprite takes texture of map as shallow copy, it is only pointer to texture, according to official tutorial/documentation:

    The white square problem You successfully loaded a texture, constructed a sprite correctly, and... all you see on your screen now is a white square. What happened?

    This is a common mistake. When you set the texture of a sprite, all it does internally is store a pointer to the texture instance. Therefore, if the texture is destroyed or moves elsewhere in memory, the sprite ends up with an invalid texture pointer.

    So, sprite returned by copy stores dangling pointer. It is just undefined behaviour.


    Related (my) answer, posted 1 day ago


    Solution: you have to wrap sprites with textures in some kind of deep-copy-able class. You cannot rely on shallow defaulted generated copy operations.


    Such a class could look like:

    class TexturedSprite {
    public:
        sf::Sprite sprite;
        sf::Texture texture;
    
        TexturedSprite() {
            init(); // load texture
        }
    
        void init() {
            // load texture
            texture.loadFromFile("texture1.png");
            sprite.setTexture(texture);
            sprite.setPosition(0,0);
        }
    
        TexturedSprite(const TexturedSprite& theOther) {
            texture = theOther.texture; // deep copy
            sprite.setTexture(texture);
        }
    
        TexturedSprite& operator=(const TexturedSprite& theOther) {
            if (this == &theOther)
                return *this;
            texture = theOther.texture; // deep copy
            sprite.setTexture(texture);
            return *this;
        }
    };
    

    then the code:

    TexturedSprite fooMain;
    {
        TexturedSprite foo;     // local 
        fooMain = foo;    
    } // foo is destroyed, but = did deep copy of texture
    

    is safe.