Tilemap Out of Video Memory

I’m drawing a 20 * 12 tile screen using 64 x 64 tiles for a project I’m working on, and after about 3 re-renders (when transitioning screens, effectively), it crashes as it has no video memory, and I want to find where the memory leak is in my tilemap drawing algorithm, as shown here:

void drawTilemap(int startX, int startY, int endX, int endY)
{
for(int dy = startY; dy < endY; dy++)
{
for(int dx = startX; dx < endX; dx++)
drawTile(tilemap[dy][dx], dx, dy, TILE_SIZE);
}
}

void drawTile(int id, int tileX, int tileY, int width)
{
char* source[1]; // this is 100% essential even though it goes unused. Without this line, it crashes for some weird reason when you draw the tilemap
SDL_Texture* tileTexture;
loadIMG(“SOUVUTU0.png”, &tileTexture);
const SDL_Rect* tileSelect = &((SDL_Rect){.x = (id / 8) * width, .y = (id % 8) * width, .w = width, .h = width});
const SDL_Rect* tileClip = &((SDL_Rect){.x = tileX * width, .y = tileY * width, .w = width, .h = width});
SDL_RenderCopy(mainRenderer, tileTexture, tileSelect, tileClip);
SDL_free(&tileClip);
SDL_free(&tileSelect);
SDL_RenderPresent(mainRenderer);
}

bool loadIMG(char* imgPath, SDL_Texture** dest)
{
SDL_Surface* surf = IMG_Load(imgPath);
*dest = SDL_CreateTextureFromSurface(mainRenderer, surf);
if (!*dest)
{
printf(“Unable to load image/create texture for %s! SDL_Error: %s\n”, imgPath, SDL_GetError());
return false;
}
SDL_FreeSurface(surf);
return true;
}

However, I don’t see any leaks, so I’d love to know what’s going on. I understand it isn’t quite helpful to just dump code and explain little, but I know this is where the video memory leak is, and I don’t get where it leaks. If someone way more knowledgeable on what uses video memory could help, that’d be great!

The leak is in the drawTile function. You create a SDL_Texture but never destroy it. And you do that for every tile.

You only have to create a texture once. The graphics driver will usually store it in video memory and you can reference it as many times as you want. I recommend you put the image loading and texture creating part into an initialization function at the beginning of your program or whenever it is appropriate to load level data.

There are a few other issues in your code:

  • The way you are creating the SDL_Rect data is problematic. Their lifetime will end immediately after assignment to the pointer and you now have undefined behavior. Even if you create the struct as a local variable, there’s no need to call SDL_free on it. I’m guessing it is placed on the stack and that’s why you need the char* source[1];
  • You only need to call SDL_RenderPresent after you’ve drawn all tiles to the screen. If you do it after every tile, only the last tile will show up in double buffered configurations (which is usually the default).

Here’s a very simple example on how to fix these issues:

texmanage.cpp
#include <SDL.h>
#include <SDL_image.h>

#define TILE_SIZE 32

static SDL_Texture* tileTexture;
static int tilemap[32][32];
static SDL_Renderer * mainRenderer;

bool loadIMG(const char* imgPath, SDL_Texture** dest)
{
	SDL_Surface* surf = IMG_Load(imgPath);
	*dest = SDL_CreateTextureFromSurface(mainRenderer, surf);
	if (!*dest)
	{
		printf("Unable to load image/create texture for %s! SDL_Error: %s\n", imgPath, SDL_GetError());
		return false;
	}
	SDL_FreeSurface(surf);
	return true;
}

void initTilemap()
{
	loadIMG("SOUVUTU0.png", &tileTexture);

	/* Just some random test data */
	for (int y = 0; y < 32; y++) {
		for (int x = 0; x < 32; x++) {
			tilemap[y][x] = ((x-7)*(y-7))%8 + (y*x*13)%8*8;
		}
	}
}

void drawTile(int id, int tileX, int tileY, int offsetX, int offsetY, int width)
{
	const SDL_Rect tileSelect = {.x = (id / 8) * width, .y = (id % 8) * width, .w = width, .h = width};
	const SDL_Rect tileClip = {.x = tileX * width + offsetX, .y = tileY * width + offsetY, .w = width, .h = width};
	SDL_RenderCopy(mainRenderer, tileTexture, &tileSelect, &tileClip);
}

void drawTilemap(int startX, int startY, int endX, int endY, int offsetX, int offsetY)
{
	SDL_RenderClear(mainRenderer);
	for(int dy = startY; dy < endY; dy++)
	{
		for(int dx = startX; dx < endX; dx++)
		{
			drawTile(tilemap[dy][dx], dx, dy, offsetX, offsetY, TILE_SIZE);
		}
	}
	SDL_RenderPresent(mainRenderer);
}

int main(int argc, char * argv[])
{
	SDL_Init(SDL_INIT_VIDEO);
	SDL_Window * window = SDL_CreateWindow("Loading...", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 400, 400, 0);
	mainRenderer = SDL_CreateRenderer(window, -1, 0);

	initTilemap();

	int done = 0;
	while (!done) {
		SDL_Event e;
		while (SDL_PollEvent(&e)) {
			if (e.type == SDL_QUIT) {
				done = 1;
			} else if (e.type == SDL_KEYUP) {
				Uint32 sym = e.key.keysym.sym;
				if (sym == SDLK_ESCAPE) {
					done = 1;
				}
			}
		}

		int ww, wh;
		int mx, my;
		SDL_GetWindowSize(window, &ww, &wh);
		SDL_GetMouseState(&mx, &my);

		drawTilemap(0, 0, 31, 31, mx - ww, my - wh);
	}

	SDL_DestroyTexture(tileTexture);
	SDL_DestroyRenderer(mainRenderer);
	SDL_DestroyWindow(window);
	SDL_Quit();

	return 0;
}

Note that this is a minimal example to get your code into a working state with no error checking. I have not tested it extensively. I also added some offset arguments to make to tilemap move around with the mouse. The code is C++11, I think. I forgot when they started allowing assigning struct member by name on initialization.

If you have questions about it, don’t hesitate to ask.

You should use
SDL_DestroyTexture(tileTexture)

designated initializers in structs are only in the upcoming C++2a standard, they’ve been a nonstandard c++ extension for a long time, however, they are in C99. See https://wg21.link/p0329

1 Like

I think you ought to put the

SDL_RenderPresent(mainRenderer);

after your nested ‘for’ loops in drawTilemap, rather than doing it every time a tile is rendered in drawTile!

Ah ok, thanks! What I used to do is used different PNGs for different tiles (named so I could access them by ID), but now instead I draw by drawing a piece of the whole tileset that I have, which means I don’t load a texture everytime I draw it.
About the other issues, I didn’t know that either! I thought that I had to free the SDL_Rects after creating them, but that’s helpful to know I don’t! And as for calling SDL_RenderPresent, I don’t have anything double-buffered, so it was just to see where the tilemap would error out while testing, I don’t use that anymore.
Thanks for your time! I’ll be sure to fix all that stuff.