Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: [SOLVED] Windows XP incompatibility: sf::Texture silently failing to load image?  (Read 4435 times)

0 Members and 1 Guest are viewing this topic.

Mutoh

  • Newbie
  • *
  • Posts: 31
    • View Profile
So far, it has happened to all Windows XP (and Windows 7 Starter) users to whom I have shared releases of my game: the .png files are opened, but SFML either isn't able to load it, or it is technically loaded (in that SFML doesn't complain of errors) but the Texture is left a blank.

Unfortunately, I haven't been able to trace the error further from "SFML is acting weird" because I don't have Windows XP myself, I use Windows 8. Running the .exe with Compatibility Mode here works just as fine for me as without, so I cannot even "pretend" that I have Windows XP. I compile my game with gcc 4.7 on CodeBlocks and from there I just share the .exe and data files with whomever it is needed through .rars.

I at first suspected that it's a problem of incompatibility between OS's, but there are times in which the Textures work as expected in their computers. I wish I could provide more information, but as I said, I don't have exactly the environment for debugging this. :-\ I just want to know, for now, if there have been developers with similar problems here. But maybe, later, I can provide excerpts of code demonstrating the problem. It's just that I can't just press F9 and check it it simulates the conditions, I have to send the program to a friend to truly check it!

And before anyone mentions that Windows XP has been discontinued... I'm afraid that these issues might manifest themselves when porting my games to other platforms, even if I put Windows XP aside.
« Last Edit: June 27, 2014, 01:45:18 am by Mutoh »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11016
    • View Profile
    • development blog
    • Email
Been willing to install a VM with XP for a while, so your post just motivated me to do so.

As such I can't confirm what you said. Sprites are being drawn just fine on my end. The only thing I get some hickup with, is when using a shader. So far I assume that's an issue with the VirtualBox.

I've written the following very simple example that works fine:

#include <SFML/Graphics.hpp>

int main()
{
        sf::RenderWindow window(sf::VideoMode(1024, 768), "Test");
        window.setFramerateLimit(60);
       
        sf::Texture texture;
        texture.loadFromFile("image.png");
        sf::Sprite sprite(texture);
       
        while(window.isOpen())
        {
                sf::Event event;
                while(window.pollEvent(event))
                {
                        if(event.type == sf::Event::Closed)
                                window.close();
                }
               
                window.clear();
                window.draw(sprite);
                window.display();
        }
}

However the console outputs the following three lines:

OpenGL Warning: DrvShareLists: unsupported
OpenGL Warning: DrvShareLists: unsupported
OpenGL Warning: DrvShareLists: unsupported

Yet another VirtualBox issue?

I haven't installed any graphics driver on XP, so this might well be the issue. I've now installed an ATI driver and shaders still arent' supported. Oh well, but sprites definitely draw when not using a shader.
Ah and I'm using the latest version from GitHub. ;)

Can you provide the code you used or test my simple example above?
« Last Edit: June 25, 2014, 01:02:19 am by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Mutoh

  • Newbie
  • *
  • Posts: 31
    • View Profile
Well, I wouldn't doubt that this code would work.

[...], but there are times in which the Textures work as expected in their computers.

I actually verified that, if I don't wrap the resource loading with my own methods, they are loaded without a problem. It happened accidentally because, in one of the versions I sent to my friend, all textures but one were loaded through my wrapper loading method - the exception texture was the one that worked. Now, you could say that I was being careless with the loading in my wrapper methods, but they are actually as simple as:

#pragma once

// <abx/Tileset.h>
// ---

#include <vector>
#include <cmath>
#include <stdexcept>

#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/RenderTarget.hpp>

// ---

namespace abx {
       
        class Tileset {
                public:
                        Tileset (float tileWidth, float tileHeight)
                                : m_tileSize(tileWidth, tileHeight)
                        {
                                m_drawer.setTexture(m_texture);
                        }
                       
                        // Here, it doesn't work. But if I use a stand-alone Texture, it will.
                        bool loadFromFile (const std::string& path) {
                                if (m_texture.loadFromFile(path)) {
                                        // It enters the clause and is able to prepare the tile rectangles.
                                        prepareTiles();
                                        return true;
                                }
                               
                                return false;
                        }

                        void drawTile (int index, const sf::Vector2f& posId, sf::RenderTarget&) const {
                                auto where = m_tileSize;
                                where.x *= posId.x;
                                where.y *= posId.y;
                               
                                m_drawer.setTextureRect(getTile(index));
                                m_drawer.setPosition(where);

                                tar.draw(m_drawer);
                        }

                        size_t getTileCount() const {
                                return m_tiles.size();
                        }
                       
                        const sf::IntRect& getTile (int index) const {
                                return m_tiles.at(index);
                        }

                private:
                        void prepareTiles() {
                                m_texture.setSmooth(true);
                               
                                // If the texture really wasn't loaded, then the return value of this would
                                // pretty much be equal to sf::Vector2u(0, 0), right?
                                sf::Vector2u textureSize = m_texture.getSize();

                                sf::Vector2u nTiles = sf::Vector2u (
                                                std::ceil(static_cast<float>(textureSize.x) / m_tileSize.x),
                                                std::ceil(static_cast<float>(textureSize.y) / m_tileSize.y)
                                        );

                                m_tiles.clear();
                               
                                // This means that there wouldn't be enough tiles "prepared" for ::drawTile.
                                for (int row = 0; row < nTiles.y; row++) {
                                        for (int col = 0; col < nTiles.x; col++) {
                                                m_tiles.emplace_back (
                                                                col * m_tileSize.x,
                                                                row * m_tileSize.y,
                                                                m_tileSize.x,
                                                                m_tileSize.y
                                                        );
                                        }
                                }
                                // An index beyond the limits of the rectangle vector would be provided by my class
                                // that parses Tiled levels, and std::vector::at would throw an exception at
                                // ::drawTile, but this doesn't happen. The size of the texture is loaded, but the pixels
                                // aren't.
                        }
                       
                        sf::Vector2f m_tileSize;
                       
                        mutable sf::Sprite m_drawer;
                        sf::Texture m_texture;
                        std::vector<sf::IntRect> m_tiles;
        };
       
}

(The class is actually spread between .h and .cpp)

It is also not a memory management issue, as my Tileset's remain alive until the application finishes execution. No copying even is done. It also happens within my ResourceManager classes, which operate almost the same way, but with templates for storing the resources.

I will try to provide, later, a program that isolates better the problem. In sum, it's almost as if the started being bratty only when deep enough in the call stack. I could be doing something wrong, but, c'mon, how much potential for bugs does forwarding a string actually have?

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11016
    • View Profile
    • development blog
    • Email
Ah I guess, I didn't see that remark.
Well if a simple example works, but a more complex one doesn't chances are way higher that it's in the user code.

However I looked at what you provided and can't spot any issues.
If you provide a complete and minimal example, I could run and debug it. ;)
« Last Edit: June 25, 2014, 01:33:11 pm by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

wintertime

  • Sr. Member
  • ****
  • Posts: 255
    • View Profile
You are probably copying that tileset class somewhere accidentally, because the compiler generates copy constructor and operator= if you forget to provide them.
Just declare a private copy constructor and operator= without providing a definition of it (add =delete if using C++11) to prevent that and recompile to see if/where it generates an error!

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Or simply inherit sf::NonCopyable.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Mutoh

  • Newbie
  • *
  • Posts: 31
    • View Profile
Nope, no copying. :) I had tested beforehand deleting the copy operators, just as you recommend, wintertime, but no compiler errors.

Here, now we have a code that anyone can test!

#include <vector>
#include <cmath>
#include <stdexcept>
#include <iostream>

#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/RenderTarget.hpp>

// ---

class Tileset {
        public:
                Tileset (unsigned int tileWidth, unsigned int tileHeight)
                        : m_tileSize(tileWidth, tileHeight)
                {
                        m_drawer.setTexture(m_texture);
                }
               
                Tileset (const Tileset&) =delete;
                Tileset& operator=(const Tileset&) =delete;
               
                ~Tileset() {
                        std::cout << "I will die horribly.\n\n";
                        std::terminate();
                }
               
                bool loadFromFile (const std::string& path) {
                        if (m_texture.loadFromFile(path)) {
                                prepareTiles();
                                return true;
                        }
                       
                        return false;
                }
               
                bool loadFromMemory (const void* data, size_t size) {
                        if (m_texture.loadFromMemory(data, size)) {
                                prepareTiles();
                                return true;
                        }
                       
                        return false;
                }

                void drawTile (int index, const sf::Vector2u& posId, sf::RenderTarget& tar) const {
                        auto where = m_tileSize;
                        where.x *= posId.x;
                        where.y *= posId.y;
                       
                        m_drawer.setTextureRect(getTile(index));
                        m_drawer.setPosition(where.x, where.y);

                        tar.draw(m_drawer);
                }

                size_t getTileCount() const {
                        return m_tiles.size();
                }
               
                const sf::IntRect& getTile (int index) const {
                        return m_tiles.at(index);
                }

        private:
                void prepareTiles() {
                        m_texture.setSmooth(true);
                       
                        sf::Vector2u textureSize = m_texture.getSize();

                        sf::Vector2u nTiles = sf::Vector2u (
                                        std::ceil(static_cast<float>(textureSize.x) / m_tileSize.x),
                                        std::ceil(static_cast<float>(textureSize.y) / m_tileSize.y)
                                );

                        m_tiles.clear();
                       
                        for (int row = 0; row < nTiles.y; row++) {
                                for (int col = 0; col < nTiles.x; col++) {
                                        m_tiles.emplace_back (
                                                        col * m_tileSize.x,
                                                        row * m_tileSize.y,
                                                        m_tileSize.x,
                                                        m_tileSize.y
                                                );
                                }
                        }
                }
               
                sf::Vector2u m_tileSize;
               
                mutable sf::Sprite m_drawer;
                sf::Texture m_texture;
                std::vector<sf::IntRect> m_tiles;
};

// ---

#include <map>
#include <string>
#include <memory>
#include <fstream>
#include <iterator>

// ---

class LevelData {
        public:
                static std::shared_ptr<const Tileset> getTileset (const std::string& tilesetName, const sf::Vector2u& tileSize) {
                        auto found = tilesets.find(tilesetName);
                       
                        if (found != tilesets.end()) {
                                return found->second;
                        }
                        else {
                                auto tileset = std::make_shared<Tileset>(tileSize.x, tileSize.y);
                               
                                #define TAKE_IT_EASY
                                // #define CPP_FILES
                               
                                #ifdef TAKE_IT_EASY
                                if (!tileset->loadFromFile(tilesetName)) {
                                        std::cerr << "Tileset couldn't load!\n\n";
                                        std::terminate();
                                }
                                #elif defined(CPP_FILES)
                                std::ifstream stream(tilesetName, std::ios::binary);
                                       
                                if (!stream) {
                                        std::cerr << "Couldn't open the file.\n\n";
                                        std::terminate();
                                }
                               
                                std::vector<char> buffer(
                                                (std::istream_iterator<char>(stream)),
                                                (std::istream_iterator<char>())
                                        );
                               
                                if (!tileset->loadFromMemory(buffer.data(), buffer.size())) {
                                        std::cerr << "Tileset couldn't load!\n\n";
                                        std::terminate();
                                }
                                #else
                                std::FILE *file;
                               
                                if (!(file = std::fopen(tilesetName.c_str(), "rb"))) {
                                        std::cerr << "Couldn't open the file.\n\n";
                                        std::fclose(file);
                                        std::terminate();
                                }
                               
                                auto beg = std::ftell(file);
                                std::fseek(file, 0, SEEK_END);
                                auto size = std::ftell(file);
                                std::fseek(file, beg, SEEK_SET);
                               
                                char buffer[size];
                                std::fread(buffer, 1, size, file);
                               
                                if (!tileset->loadFromMemory(buffer, size)) {
                                        std::cerr << "Tileset couldn't load!\n\n";
                                        std::fclose(file);
                                        std::terminate();
                                }
                               
                                std::fclose(file);
                                #endif
                               
                                tilesets.insert({ tilesetName, tileset });
                                return tileset;
                        }
                }
       
        private:
                typedef std::map<std::string, std::shared_ptr<Tileset>> TilesetMap;
                static TilesetMap tilesets;
};

LevelData::TilesetMap LevelData::tilesets;

// ---

#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/RenderTexture.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Window/Event.hpp>

int main() {
        sf::RenderWindow window(sf::VideoMode(640, 480), "test");
       
        sf::RenderTexture tex;
        tex.create(640, 480);
       
        sf::Sprite sprite;
        sprite.setTexture(tex.getTexture());
       
        if (auto tileset = LevelData::getTileset("tile.png", sf::Vector2u(45, 45))) {
                for (sf::Vector2u pos(0, 0); pos.x < tileset->getTileCount(); ++pos.x) {
                        tileset->drawTile(0, pos, tex);
                }
        }
       
        tex.display();
       
        while (window.isOpen()) {
                sf::Event event;
               
                while (window.pollEvent(event)) {
                        switch (event.type) {
                                case sf::Event::Closed: return 0;
                        }
                }
               
                window.clear();
                window.draw(sprite);
                window.display();
        }
}

At LevelData::getTileset, you can toggle those defines at will. In the actual code of my project I am using the "C files" configuration because I just can't slurp data out of C++ files without getting it corrupted - mainly, it eats away any blank characters. If the file has "a b c" as its contents, only "abc" will be extracted. This bug manifested itself even when I executed this stand-alone code in a new "SFML configured" CodeBlocks project. And yes, I have attested that the TAKE_IT_EASY version has problems in my friends' Windows XP computers.

Also, in the actual code, "terminates" are exceptions that I throw, and Tileset's destructor only terminates the program as proof that no Tilesets are destroyed in inappropriate times. You can also remove that once the point comes across and it has become annoying.

getTileset is actually called after a lot of XML parsing by LevelData, but it's nothing that should actually interfere with the functionalities of getTileset and the Tilesets themselves.

Attached is the tile.png I used.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11016
    • View Profile
    • development blog
    • Email
Not really minimal, but at least complete... ;)

Well if you had sent your friend a version with the console visible, you'd have gotten the probable source of the issue at the first try. Like I did when running a debug build:

OpenGL Warning: DrvShareLists: unsupported
OpenGL Warning: DrvShareLists: unsupported
OpenGL Warning: DrvShareLists: unsupported
An internal OpenGL call failed in RenderTextureImplFBO.cpp (117) : GL_INVALID_OPERATION, the specified operation is not allowed in the current state
Impossible to create render texture (failed to link the target texture to the frame buffer)
OpenGL Warning: DrvShareLists: unsupported

Which refers to this line in the source.
Since I run XP in a VM, I can't exclude that it's an VM issue, but if it's not, then it seems that the RenderTexture implementation doesn't work on XP for some reason.

You said, it worked for some of the textures that you loaded directly, did you also draw them directly?
Also why the heck would you call std::terminate();? Just for the fun of crashing the application? :o

Here's again a truly complete and minimal example. You could send it to your friend to test, whether something gets drawn or not.

#include <SFML/Graphics.hpp>

int main() {
    sf::RenderWindow window(sf::VideoMode(640, 480), "test");
   
    sf::RenderTexture tex;
    tex.create(640, 480);
   
    sf::Sprite sprite;
    sprite.setTexture(tex.getTexture());
       
        sf::Texture entity_tex;
        entity_tex.loadFromFile("tile.png");
       
        sf::Sprite entity_sprite(entity_tex);
   
    tex.clear();
        tex.draw(entity_sprite);
    tex.display();
   
    while (window.isOpen()) {
        sf::Event event;
       
        while (window.pollEvent(event)) {
            switch (event.type) {
                case sf::Event::Closed: return 0;
            }
        }
       
        window.clear();
        window.draw(sprite);
        window.display();
    }
}
« Last Edit: June 26, 2014, 10:27:33 pm by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Mutoh

  • Newbie
  • *
  • Posts: 31
    • View Profile
Actually, I won't need to have my friend test this. Knowing that this is a RenderTexture problem actually solves all problems! :D The exact cases that worked were Sprites that were drawn directly into the screen and had Textures that were loaded directly from a file, and the ones that didn't were because I was drawing Sprites whose Textures were actually from RenderTextures that pre-rendered elements of my level (tiles). Thanks very much, expl0it3r!

I'm sorry for not providing a minimal enough code, but I was afraid that if I "minimized the problem too much" I would accidentally remove the actual problem. For example, I almost considered rendering the tile directly into the RenderWindow in the sample to minimize the code. If I did, we wouldn't have found out that it was a problem with RenderTexture.

And in fact, I did provide my friend versions with the console visible, mainly because then we would know through SFML itself what was happening (it prints into the console whether the file couldn't be opened, if the image is corrupted or if it's empty, etc.) But, surprisingly enough, this OpenGL message never showed up for any of us. It indeed would have saved plenty of time.

Oh, I don't know why I decided to scatter terminates through the sample. :) But, as I said, they are actually substitutes for exceptions that I would throw. Well, I just thought that the sample program wouldn't have any reason to continue if it came across any of those errors.

Again, thanks for the help. I'm sorry if I wasn't helpful enough at any time. :D

EDIT: Even then, I don't think I'll tweak enough my game so that it works on Windows XP, if it's a case as "obscure" as this. It's good to know that it's beyond my reach, nevertheless, so I don't really have to worry about it. |)
« Last Edit: June 27, 2014, 01:47:09 am by Mutoh »