SDL Doesn't Display Image C++ Ubuntu Linux

I’m on Ubuntu Linux and I’m making a game in C++ that uses SDL for graphics and event handling, but for some reason it doesn’t display the background image when it’s called! I’m using two files for this, a header file for the game engine and a CPP file for actually coding the game. LQGE stands for Leon’s Questionable Game Engine.

The Engine
Main Game File

Your code is working on my Ubuntu machine, perhaps the path to the image png itself has a different capital letter in it, or a change in spelling?

Excellent progress by the way.
Something that will save you a bit of time: IMG_LoadTexture(renderer, imagePath)
– but being able to transfer a surface to a texture is still going to be a necessary skill for SDL_TTF: the text rendering library.

Some questions for you since I don’t know your C++ experience:

  1. Have you used std::vector?
  2. Are you comfortable with creating C++ classes and objects? (Sometimes referred to as OOP)

Neither are necessary for creating a game/engine, but they are very useful as the complexity builds.
Otherwise we can just stick to a C based approach and just use tools from C++ when available and convenient.

1 Like

I sometimes use classes but normally I have no need to use them, and I haven’t heard of std::vector.

Ok, do you have experience in other laguages like Java/Javascript/Python/etc? This would help me if there are perhaps some comparisons I can use to help explain things.

Like this:
std::vector is like an array in Javascript, it uses dynamic memory which means it can be added to at any time without having to allocate new memory.
– A normal array in C/C++ on the other hand is static, you need to know the size of the data that you want to store in an array before you create said array.

Sadly, there aren’t any other programming languages that I am proficient enough in for you to compare std::vector to.

No problem, everyone has to start somewhere. It’s crazy impressive that you’re getting into SDL at this level.
Right on.

Ok, that’s good.

Based off of that, here’s how I might set up your loadImage() function from your earlier project:

SDL_Texture * loadImage(std::string path)
{
        SDL_Texture * image = IMG_LoadTexture(renderer, path.c_str());
        if(image)
        {
                return image;
        }
        SDL_Log("Error loading image %s", path.c_str());
        return NULL;
}

This way the client (the game) can call this function for any image.
They can check if the image loaded correctly by checking if the returned pointer is NULL or not. If it’s not NULL then it loaded correctly and is ready for use.
There will also be an automatic debugging message if the image fails, and the path that failed will also be printed.

I had no idea you could return textures and I never knew was c_str() was they seem very useful! Thanks for helping!

You can return the pointer to the texture because the image is created as dynamic memory on the heap.

Beware: this also means that if you don’t destroy it using SDL_DestroyTexture(image), it still exists as orphaned data. If you overwrite the texture pointer with new data without destroying the old data, that old texture data exists as “used memory” inside your program but your pointer no longer points to it.
That is what we call a memory leak.

This in part is why it’s a good idea to load images before the game loop (or at the very beginning of a level), and reuse that pointer as needed during the game/level. That way you aren’t creating these memory leaks inside of a long-running loop. Then you can safely destroy/free all of the data at the end of the game/level sure in the knowledge that you don’t have a memory leak.

Just remember: For every “Create” or “Load” method in SDL, there is an equivalent “Destroy” or “Free” method that should be used when you are done with it.

There are exceptions to that rule, Someone is likely to say that this is not true for the first renderer/window, but honestly it does not hurt to clean those up at the end of the program either.

[edit: sorry for rambling. The reason that all this was important was that your LQGE_ChangeBackground() function has a couple of memory leaks that are not being addressed. If the program runs for too long it will crash the program if it doesn’t crash the computer first.]

There are several ways to test a program for memory leaks. I tend to just run the program and watch my system monitor for a minute looking for memory usage to be stable. That’s not the recommended method. I think most people use a program called valgrind.

I forgot to offer a fix. Here’s your game code without a recurring memory leak, we’re loading the backgroundTexture before the game loop:

// Compile With g++ LQGE.h main.cpp -lSDL2 -lSDL_image -o "Butter Game"

#include "./engine.h"

#define bedroomBackground "./Assets/Backgrounds/bedroomBackground.png"

void LQGE_UpdateScreen()
{
}

int main()
{
        // Sets LQGE_CreateWindowReturnNumber To The Return Value Of LQGE_CreateWindow() Then Checks What It Is Equal To.

        int LQGE_CreateWindowReturnNumber;

        LQGE_CreateWindowReturnNumber = LQGE_CreateWindow(WINDOW_CENTER_X, WINDOW_CENTER_Y, 1920, 1080, false);

        if(LQGE_CreateWindowReturnNumber == LQGE_INITIALIZATION_ERROR)
        {
                std::cout << "LQGE_CreateWindow Returned Negative For SDL_Init()." << std::endl;
                quit();
        }
        if(LQGE_CreateWindowReturnNumber == LQGE_WINDOW_CREATION_ERROR)
        {
                std::cout << "LQGE_CreateWindow Returned NULL For SDL_CreateWindow()." << std::endl;
                quit();
        }
        if(LQGE_CreateWindowReturnNumber == LQGE_INITIALIZATION_ERROR)
        {
                std::cout << "LQGE_CreateWindow Returned NULL For SDL_CreateRenderer()." << std::endl;
                quit();
        }

        LQGE_ChangeBackground(bedroomBackground);
        while(running)
        {
                LQGE_GetInput();
                LQGE_UpdateScreen();
                LQGE_RenderScreen();
        }
    // LQGE_Quit();
}

Now if you create a quit() function in your game engine, you will give yourself a place where all dynamic objects can be safely destroyed/freed/deleted.

And here’s the engine with some tweaks to remove the leak caused by the SDL surface creation, and a new quit function:

// Compile With: g++ LQGE.h main.cpp -lSDL2 -lSDL_image -o "Butter Game"
 
#include <iostream>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
 
// Errors
#define LQGE_SUCCESS 0
#define LQGE_INITIALIZATION_ERROR 1
#define LQGE_IMAGE_INITIALIZATION_ERROR 2
#define LQGE_WINDOW_CREATION_ERROR 3
#define LQGE_RENDERER_CREATION_ERROR 4
#define LQGE_IMAGE_LOADING_ERROR 5
 
// Constants Needed
#define WINDOW_CENTER_X 1920/2
#define WINDOW_CENTER_Y 1080
 
// The Window And Renderer And Other Important Stuff
SDL_Window* window;
SDL_Renderer* renderer;
 
SDL_Texture* backgroundTexture = NULL;
 
bool running = true;
bool fullscreen;
 
int LQGE_CreateWindow(int posX, int posY, int sizeX, int sizeY, bool fullscreen)
{
	if(SDL_Init(SDL_INIT_EVERYTHING) != 0){return LQGE_INITIALIZATION_ERROR;}
	if(IMG_Init(IMG_INIT_PNG) == 0){return LQGE_IMAGE_INITIALIZATION_ERROR;}
	if(fullscreen){window = SDL_CreateWindow("Untitled Butter Game", posX, posY, sizeX, sizeY, SDL_WINDOW_FULLSCREEN); fullscreen = true;}
	if(!fullscreen){window = SDL_CreateWindow("Untitled Butter Game", posX, posY, sizeX, sizeY, SDL_WINDOW_SHOWN); fullscreen = false;}
	if(window == NULL){return LQGE_WINDOW_CREATION_ERROR;}
 
	renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);
	if(renderer == NULL){return LQGE_RENDERER_CREATION_ERROR;}
 
	return LQGE_SUCCESS;
}
 
void quit()
{
	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window); 
	IMG_Quit();
	SDL_Quit();
 
	running = false;
}
 
int LQGE_ChangeBackground(const char* path)
{
	// if the background texture is already holding data, then let's free the pointer
	if(backgroundTexture)
	{
		SDL_DestroyTexture(backgroundTexture);
		backgroundTexture = NULL;
	}
	SDL_Surface* backgroundSurface;
	backgroundSurface = IMG_Load(path);
	if(backgroundSurface == NULL){return LQGE_IMAGE_LOADING_ERROR;}
	backgroundTexture = SDL_CreateTextureFromSurface(renderer, backgroundSurface);
	if(backgroundTexture == NULL){return LQGE_IMAGE_LOADING_ERROR;}

	SDL_FreeSurface(backgroundSurface);
 
	return LQGE_SUCCESS;
}
 
void LQGE_RenderScreen()
{
	SDL_RenderClear(renderer);
 
	SDL_RenderCopy(renderer, backgroundTexture, NULL, NULL);
 
	SDL_RenderPresent(renderer);
}
 
void LQGE_GetInput()
{
	SDL_Event event;
 
	while(SDL_PollEvent(&event) != 0)
	{
		switch(event.type)
		{
			case SDL_QUIT:
				quit();
				break;	
			case SDL_KEYDOWN:
				switch(event.key.keysym.sym)
				{
					case SDLK_F4:
						if(fullscreen)
						{
							SDL_SetWindowFullscreen(window, 0);
							fullscreen = false;
						}
						else if(!fullscreen)
						{
							SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
							fullscreen = true;
						}
						break;
					default:
						break;
				}
				break;
			default:
				break;
		}
	}
}

void LQGE_Quit()
{
	if(backgroundTexture)
	{
		SDL_DestroyTexture(backgroundTexture);
		backgroundTexture = NULL;
	}
}

I know in your previous project you used the SDL_FreeSurface function correctly, but that’s the reason that it was required.

I’ve checked hundreds of time(No exaggeration) and it is the right path I have no idea what it could be :frowning:

Do you get an open window with no content, or is the window closing right away too?

  • If the window is closing then it’s likely a result of the memory leak - the changes above should fix that.
  • If the window remains open but is just blank: Is it an image that you created, or is it something downloaded?
  • Have you tried it with a different image from a different source (using a different editor, or downloaded from a different website, etc.)?

I’ve checked hundreds of time(No exaggeration) and it is the right path

Have you verified that IMG_Load or IMG_LoadTexture (whichever you use) does not return null? If it returns null you can print the error message returned by SDL_GetError() to get more information.

backgroundSurface = IMG_Load(path);
if (backgroundSurface == nullptr)
{
	std::cout << "IMG_Load error: " << SDL_GetError() << "\n";
}

Note that relative paths are looked up from the current working directory of the process which is not necessarily the directory where the executable file is located. If you start the program by double clicking, the working directory is going to be the same as where the executable file is. If you run it from the command line it will be the directory that you are currently at (that you have navigated to using the cd command). If you’re using an IDE it depends…

The image is from an artist, because this game started in august and unity erased it three times so I switched to using godot then I disliked that so I finally started using SDL which I’m using now and it opens the window but it is just a blank screen .

Your code works for us so there is no way we will be able to tell you why the background doesn’t show for you. All we can do is come with helpful suggestions of what might be wrong and things that you can do to find out.

Please tell us more about what you have actually tried and what the outcome was.

Have you checked if IMG_Load or SDL_CreateTextureFromSurface return null? If either of them returned null what was the error message?

You could also try using the absolute path for the background image just to rule out that there isn’t anything wrong with the path (I still think this is the most likely problem). Copy & paste the path so you’re sure you get it correct, because it’s too easy to make a mistake if you type it manually. If it works when you use an absolute path you know that the path is the problem.

Ok, It does return NULL! That’s why but I don’t know how to fix it.

That’s good. Now, what does the error message returned by SDL_GetError() say?

(What we are doing now is called “debugging”. We are on our way to finding the problem but where not there yet.)

I just added SDL_GetError and it says Unsupported file type but it is a png and it is IMG_INIT_PNG maybe the artist accidentally exported it as another format and renamed it to PNG?

Actually, no I turned it to TXT and it said png at the start so that can’t be the case.

Did it say “Unsupported image format”? I don’t think that should happen if it has a valid PNG file signature.

Does it work if you load another PNG file in your program?
Does the file show correctly if you open it in other programs?

If you don’t mind sharing it you could upload it here* or somewhere else and we could have a look. Maybe it’s corrupted.

* Not sure if discourse will try to convert/compress image files that are uploaded so to prevent that you might want to zip it.