I am developing a game that uses SDL2, SDL2 Image & SDL2 TTF, while using clang compiler under Linux. When running it in release mode(compiler flags: -Wall -O3) there is a constant usage of about 52MB, I've checked with Valgrind for memory leaks and there are none.
My Game class:
#include "Game.h"
Game::Game(Configurations configurations) {
_graphics = std::make_unique<Graphics>(configurations.windowWidth,
configurations.windowHeight,
configurations.flags);
_gameState = std::make_unique<GameState>(configurations.windowWidth,
configurations.windowHeight);
_mouseInput = std::make_unique<MouseInput>(MouseInput{-1, -1, 0});
_fpsCounter = std::make_unique<FPSCounter>();
_worldWidth = configurations.worldWidth;
_worldHeight = configurations.worldHeight;
_fpsCap = configurations.fpsCap;
}
auto Game::start() -> void {
_fpsCounter->fpsInit();
gameLoop(_fpsCap);
}
auto Game::convertStateToGraphicsMap()
-> std::vector<std::pair<TileType, SDL_Point>> {
std::vector<std::pair<TileType, SDL_Point>> convertedVector;
for (auto o : _gameState->getGameObjects()) {
convertedVector.push_back(
std::make_pair(o.getTileType(), SDL_Point{(int)o.getPosition().x,
(int)o.getPosition().y}));
}
std::remove_if(convertedVector.begin(), convertedVector.end(),
[this](std::pair<TileType, SDL_Point> o) {
return o.second.x - (int)_gameState->getCamera().x < 0 ||
o.second.y - (int)_gameState->getCamera().y < 0 ||
o.second.x - (int)_gameState->getCamera().x >
_graphics->getWindowWidth() ||
o.second.y - (int)_gameState->getCamera().y >
_graphics->getWindowHeight();
});
for (auto &o : convertedVector) {
o.second.x -= _gameState->getCamera().x;
o.second.y -= _gameState->getCamera().y;
}
return convertedVector;
}
auto Game::checkForSDLQuitEvents() -> bool {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
std::cout << "closeRequested! quiting\n";
return true;
}
}
return false;
}
auto Game::validatePlayerPosition() -> void {
Vector2d<float> &playerPosition = _gameState->getPlayer()->getPosition();
if (playerPosition.x < 0) {
playerPosition.x = 0;
}
if (playerPosition.y < 0) {
playerPosition.y = 0;
}
if (playerPosition.x > _worldWidth) {
playerPosition.x = (float)_worldWidth;
}
if (playerPosition.y > _worldHeight) {
playerPosition.y = (float)_worldHeight;
}
}
auto Game::handleMouseState(float fps) -> void {
_mouseInput->mouseState =
SDL_GetMouseState(&_mouseInput->mouseX, &_mouseInput->mouseY);
if (_mouseInput->mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) {
// printf("SDL_MOUSEBUTTONDOWN\n");
auto halfWindowWidth = _graphics->getWindowWidth() / 2;
auto halfWindowHeight = _graphics->getWindowHeight() / 2;
_gameState->setCamera(
{_gameState->getPlayer()->getPosition().x - halfWindowWidth,
_gameState->getPlayer()->getPosition().y - halfWindowHeight});
_gameState->getPlayer()->onDestinationSelected(
{(float)_mouseInput->mouseX + _gameState->getCamera().x,
(float)_mouseInput->mouseY + _gameState->getCamera().y},
fps);
validatePlayerPosition();
}
}
auto Game::gameLoop(float fpsCap) -> void {
float minFrameRateDelay = MILLISECOND_IN_SECOND / fpsCap;
while (true) {
// process events
if (checkForSDLQuitEvents())
return;
_graphics->clearRender();
float averageFPS = _fpsCounter->getAverageFramesPerSecond();
handleMouseState(averageFPS);
_graphics->renderGrid(convertStateToGraphicsMap());
_graphics->renderText("FPS: " + std::to_string(averageFPS),
{255, 255, 0, 255}, 0, 0);
_graphics->presentRender();
if (fpsCap < averageFPS) {
SDL_Delay(minFrameRateDelay);
}
_fpsCounter->fpsThink();
}
}
auto Game::loadMap(const char *filename) -> void {
int current, mx, my, mw, mh;
std::ifstream in(filename);
if (!in.is_open()) {
std::cout << "Failed to open map file." << std::endl;
return;
}
in >> mw;
in >> mh;
in >> mx;
in >> my;
for (int i = 0; i < mh; i++) {
for (int j = 0; j < mw; j++) {
if (in.eof()) {
std::cout << "Reached end of map file too soon." << std::endl;
return;
}
in >> current;
if (current != 0) {
GameObject tmp(DEFAULT_OBJECT_SIZE, 0, {0, 0}, HUMAN_FEMALE);
if (current == 2 || current == 4) {
}
_map.push_back(tmp);
}
}
}
in.close();
}
Game::~Game() { std::cout << "Game destructor\n"; }
My Graphics class:
#include "Graphics.h"
Graphics::Graphics(Uint32 windowWidth, Uint32 windowHeight, Uint32 flags)
: _windowWidth(windowWidth), _windowHeight(windowHeight), _flags(flags) {
if (initializeSdl()) {
_window = std::unique_ptr<SDL_Window, std::function<void(SDL_Window *)>>(
createWindow(), SDL_DestroyWindow);
_renderer =
std::unique_ptr<SDL_Renderer, std::function<void(SDL_Renderer *)>>(
createRenderer(), SDL_DestroyRenderer);
_textures = std::unique_ptr<
std::map<TileType, SDL_Texture *>,
std::function<void(std::map<TileType, SDL_Texture *> *)>>(
loadAllTextures(), Graphics::destroyAllTextures);
_baseTile =
std::unique_ptr<RectAndTexture, std::function<void(RectAndTexture *)>>(
createBaseRect(), destroyRectAndTexture);
_globalFont = std::unique_ptr<TTF_Font, std::function<void(TTF_Font *)>>(
createRegularFont(), destroyFont);
std::cout << "Graphics created\n";
}
}
auto Graphics::initializeSdl() -> bool {
// attempt to initialize graphics and timer system
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) {
std::cout << "error initializing SDL: \n" << SDL_GetError();
return false;
}
if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
std::cout << "could not initialize sdl2_image: \n" << IMG_GetError();
return false;
}
if (TTF_Init() == -1) {
std::cout << "SDL_ttf could not initialize! SDL_ttf Error: \n"
<< TTF_GetError();
return false;
}
return true;
}
auto Graphics::createWindow() -> SDL_Window * {
SDL_Window *window = SDL_CreateWindow(
"InvasiveSpecies", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
_windowWidth, _windowHeight, _flags);
if (!window) {
std::cout << "error creating window: \n" << SDL_GetError();
SDL_Quit();
return nullptr;
}
return window;
}
auto Graphics::createRenderer() -> SDL_Renderer * {
// create a renderer, which sets up the graphics hardware
Uint32 render_flags = SDL_RENDERER_ACCELERATED;
SDL_Renderer *renderer = SDL_CreateRenderer(_window.get(), -1, render_flags);
if (!renderer) {
std::cout << "error creating renderer: \n" << SDL_GetError();
quitSdl();
return nullptr;
}
return renderer;
}
auto Graphics::loadTexture(const char *imagePath) -> SDL_Texture * {
SDL_Texture *texture = IMG_LoadTexture(_renderer.get(), imagePath);
if (!texture) {
std::cout << "error creating texture\n";
quitSdl();
return nullptr;
}
return texture;
}
auto Graphics::createRectFromTexture(SDL_Texture *texture) -> RectAndTexture * {
auto *dest = new SDL_Rect();
SDL_QueryTexture(texture, nullptr, nullptr, &dest->w, &dest->h);
auto *rectAndTexture = new RectAndTexture{dest, texture};
return rectAndTexture;
}
auto Graphics::createBaseRect() -> RectAndTexture * {
RectAndTexture *baseTile = createRectFromTexture((*_textures)[SOIL]);
return baseTile;
}
auto Graphics::clearRender() -> void { SDL_RenderClear(_renderer.get()); }
auto Graphics::presentRender() -> void { SDL_RenderPresent(_renderer.get()); }
auto Graphics::renderGrid(
const std::vector<std::pair<TileType, SDL_Point>> &gameObjectsPositionsMap)
-> void {
renderGridBackground();
renderGameObjects(gameObjectsPositionsMap);
}
auto Graphics::renderGameObjects(
const std::vector<std::pair<TileType, SDL_Point>> &gameObjectsPositionsMap)
-> void {
for (const auto ¤t : gameObjectsPositionsMap) {
_baseTile->texture = (*_textures)[current.first];
_baseTile->rect->x = current.second.x;
_baseTile->rect->y = current.second.y;
renderTexture(_baseTile.get());
}
}
auto Graphics::renderGridBackground() -> void {
// printf("renderGridBackground\n");
int tileWidth = _baseTile->rect->w;
int tileHeight = _baseTile->rect->h;
_baseTile->texture = (*_textures)[SOIL];
for (int i = 0; i < _windowWidth; i += tileWidth) {
for (int j = 0; j < _windowHeight; j += tileHeight) {
_baseTile->rect->x = i;
_baseTile->rect->y = j;
renderTexture(_baseTile.get());
}
}
}
auto Graphics::renderTexture(RectAndTexture *rectAndTexture) -> void {
SDL_RenderCopy(_renderer.get(), rectAndTexture->texture, nullptr,
rectAndTexture->rect);
}
auto Graphics::getImagePathStringByTileType(TileType tileType) -> const char * {
switch (tileType) {
case TileType::SOIL:
return SOIL_IMAGE_PATH;
case TileType::GRASS:
return GRASS_IMAGE_PATH;
case TileType::STONES:
return STONES_IMAGE_PATH;
case TileType::HUMAN_MALE:
return HUMAN_MALE_IMAGE_PATH;
case TileType::HUMAN_FEMALE:
return HUMAN_FEMALE_IMAGE_PATH;
default:
break;
}
return nullptr;
}
auto Graphics::loadAllTextures() -> std::map<TileType, SDL_Texture *> * {
auto texturesMap = new std::map<TileType, SDL_Texture *>();
for (auto currentTileType : tileTypeVector) {
(*texturesMap)[currentTileType] =
loadTexture(getImagePathStringByTileType(currentTileType));
}
return texturesMap;
}
auto Graphics::destroyAllTextures(
std::map<TileType, SDL_Texture *> *texturesMap) -> void {
for (auto const &p : *texturesMap) {
SDL_DestroyTexture(p.second);
}
texturesMap->clear();
delete texturesMap;
std::cout << "destroyAllTextures done\n";
}
auto Graphics::destroyFont(TTF_Font *font) -> void {
TTF_CloseFont(font);
std::cout << "destroyFont done\n";
}
auto Graphics::destroyRectAndTexture(RectAndTexture *rectAndTexture) -> void {
delete rectAndTexture->rect;
// SDL_DestroyTexture(rectAndTexture->texture); //already freed at
// destroyAllTextures()
delete rectAndTexture;
}
auto Graphics::renderText(const std::string &textureText, SDL_Color textColor,
int x, int y) -> void {
SDL_Surface *textSurface =
TTF_RenderText_Solid(_globalFont.get(), textureText.c_str(), textColor);
SDL_Texture *textTexture = nullptr;
if (textSurface == nullptr) {
std::cout << "Unable to render text surface! SDL_ttf Error: \n",
TTF_GetError();
} else {
// Create texture from surface pixels
textTexture = SDL_CreateTextureFromSurface(_renderer.get(), textSurface);
if (textTexture == nullptr) {
std::cout << "Unable to create texture from rendered text! SDL Error: \n"
<< SDL_GetError();
} else {
std::unique_ptr<RectAndTexture, std::function<void(RectAndTexture *)>>
baseTile = std::unique_ptr<RectAndTexture,
std::function<void(RectAndTexture *)>>(
createRectFromTexture(textTexture), destroyRectAndTexture);
baseTile->rect->x = x;
baseTile->rect->y = y;
renderTexture(baseTile.get());
baseTile.reset();
SDL_DestroyTexture(textTexture);
}
// Get rid of old surface
SDL_FreeSurface(textSurface);
}
}
auto Graphics::getFontFromFile(const char *file, int ptsize) -> TTF_Font * {
TTF_Font *gFont = TTF_OpenFont(file, ptsize);
if (gFont == nullptr) {
std::cout << "Failed to load lazy font! SDL_ttf Error: \n"
<< TTF_GetError();
}
return gFont;
}
auto Graphics::createRegularFont() -> TTF_Font * {
return getFontFromFile(FONT_PATH, FONT_SIZE);
}
auto Graphics::quitSdl() -> void {
// these must be here or else they will get called after SDL_Quit()
_globalFont.reset();
_textures.reset();
_baseTile.reset();
_renderer.reset();
_window.reset();
TTF_Quit();
IMG_Quit();
SDL_Quit();
}
Graphics::~Graphics() {
std::cout << "Graphics destructor\n";
quitSdl();
}
The assets for the game contains .png and .tff files with a total size of about 34KiB.
The rest of the code is in My GitHub repo
Is the usage of about 50MB RAM at runtime is normal under these conditions?
SDL
or another library uses that much memory. \$\endgroup\$ – TomTsagk Apr 7 '20 at 14:52SDL2
is made that way. Not much you can do about that. If you think that's an issue with your project, the only option is to experiment with other libraries. \$\endgroup\$ – TomTsagk Apr 7 '20 at 15:46