How to load file font into RAM using C/C++ and SDL2?

Accordingly to the ‘‘best practices’’ I have learned, we should load the resources we need to our programs into RAM, avoiding unnecessary requests to user’s hard drive. Using SDL2, I always free image files after loading them into RAM. (File -> Surface -> Texture -> Free File/Surface). So, if I other application changes the file, my program ignores it, as the file is not in use by it anymore.

Now in lesson 16 I am learning to use the Add-on SDL_ttf .

However, using SDL_ttf addon I could not find a way to free the font.ttf file, loading it into RAM too. I can only see it through a pointer. It seems to me that the file keeps being read each time I render a text.

How can I load it into RAM, so the rendering calls a RAM position, instead of the file in HD?

Full code

#define SDL_MAIN_HANDLED
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

int G = 255;

int main (void) {SDL_SetMainReady();

    int SCREEN_WIDTH   = 800;
    int SCREEN_HEIGHT  = 600;
    bool QUIT_APPLICATION = false;
    SDL_Event union_Event_manager;

    SDL_Color      str_White_colour = {255,255,255,255};    
    SDL_Window   * ptr_Window       = nullptr;
    SDL_Surface  * ptr_Text_Surface = nullptr;
    SDL_Surface  * ptr_Main_surface = nullptr;
    SDL_RWops    * ptr_str_rwops    = nullptr;
    TTF_Font     * ptr_Font         = nullptr;


    SDL_Init(SDL_INIT_VIDEO);
    TTF_Init();


    ptr_Window = SDL_CreateWindow("Lesson 16 - TrueTypeFonts", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
    ptr_Main_surface = SDL_GetWindowSurface(ptr_Window);

    ptr_str_rwops = SDL_RWFromFile("FreeMono.ttf", "r");

    ptr_Font = TTF_OpenFontIndexRW(ptr_str_rwops, 1, 72, 0);

    ptr_Text_Surface = TTF_RenderText_Solid(ptr_Font, "Hello World", str_White_colour);

    while(!QUIT_APPLICATION){

        while(SDL_PollEvent(&union_Event_manager) != 0 ){
            if (union_Event_manager.type == SDL_QUIT) {QUIT_APPLICATION = true;}
        /*END WHILE*/}

    SDL_BlitSurface(ptr_Text_Surface, NULL, ptr_Main_surface, NULL);
    SDL_UpdateWindowSurface(ptr_Window);
    /*END WHILE*/}

    TTF_CloseFont(ptr_Font); 
// if called before any rendering, the app crashes, as supposed to.
// So, how free the **file** and keep using its content from RAM?
    SDL_RWclose(ptr_str_rwops);
    SDL_FreeSurface(ptr_Text_Surface);
    SDL_FreeSurface(ptr_Main_surface);
    SDL_DestroyWindow(ptr_Window);

    ptr_Font         = nullptr;
    ptr_str_rwops    = nullptr;
    ptr_Text_Surface = nullptr;
    ptr_Main_surface = nullptr;
    ptr_Window       = nullptr;

    TTF_Quit();
    SDL_Quit();

return (0);}

Failure 1:

Create a structure to hold information from file.

TTF_Font str_Font; // Error in compilation ''incomplete type''
str_Font = *ptr_Font;
TTF_CloseFont(ptr_Font);
ptr_Font = nullptr;
ptr_Font = &str_Font;   

Reason to failure: I misunderstood how the file works. The structure only holds information about the file, not the media itself. This approach is useless, and crash the program just after freeing the pointer (the rendering tries to dereference a nullptr ).

Failure 2:

Use built in function to free resource.

ptr_Font = TTF_OpenFontIndexRW(SDL_RWFromFile("FreeMono.ttf", "r"), 1, 72, 0);

Reason to failure: I do not understand why, as the second argument (non-zero) specifies it should free the resource after usage. It also happens in the completed source code above, where I merely separated the functions in two lines.

Failure 3:

Create structure to hold information about pointer.

ptr_str_rwops = SDL_RWFromFile("FreeMono.ttf", "r");
str_rwops = *ptr_str_rwops;
SDL_RWclose(ptr_str_rwops); // crashes  the program
ptr_str_rwops = nullptr;
ptr_str_rwops = &str_rwops; // useless: file still in use.

Reason to failure: The structure RWops seems to not hold the file, only information about it. So it is the sum of failure 1 and 2.

Failure 4:

Tried to load file as object.

ptr_LoadObject = (TTF_Font*)SDL_LoadObject("FreeMono.ttf");
ptr_str_rwops = SDL_RWFromFile((const char *)ptr_LoadObject, "r");

Reason to failure: This function works with shared operational system files. Wrong usage of function.

Isn’t that advice largely irrelevant now most (all?) modern filesystems automatically cache files in RAM ‘behind the scenes’? I usually advise the opposite: if it’s easier to read from the filesystem multiple times do so, because the OS will ensure that the physical storage device is typically only accessed once.

I wouldn’t trust the filesystem to take care of it, but I would suggest that your usage pattern is more important than whatever SDL_ttf does behind the scenes. Rasterize the glyphs you need and that’s usually good enough.

As has been mentioned, you probably do not need to worry about disk I/O since the OS is likely to cache file contents in memory since you have the file handle open (and may also cache things you don’t even have file handles open for yet, if it expects them to be needed due to heuristics or something). With that said, thanks to the flexibility of the RWops API, you can load the contents of the file into an in-memory buffer, and use SDL_RWFromMem to produce an SDL_RWops* that you then give to SDL_ttf’s font-opening functions. Here is some example code:

SDL_RWops* SDL_RWMemFromFile (char* path, char* mode)
{
	// Open file.
	SDL_RWops* diskfile = SDL_RWFromFile(path,mode);
	if (!diskfile) 
		goto FAIL;
	
	// Jump to the end.
	if (SDL_RWseek(diskfile,0,RW_SEEK_END) < 0) 
		goto CLOSE_FAIL;
	
	// Current position is file length.
	Sint64 length = SDL_RWtell(diskfile);
	if (length < 0) 
		goto CLOSE_FAIL;
	
	// Jump back to the beginning.
	if (SDL_RWseek(diskfile,0,RW_SEEK_SET) < 0) 
		goto CLOSE_FAIL;
	
	// Allocate memory.
	void* buffer = malloc(length);
	if (!buffer) 
		goto CLOSE_FAIL;
	
	// Read the file contents into memory.
	if (SDL_RWread(diskfile,buffer,1,length) != length) 
		goto FREE_CLOSE_FAIL;
	
	// We are done with this now.
	SDL_RWclose(diskfile);
	
	// Construct the output.
	SDL_RWops* memfile = SDL_RWFromMem(buffer,length);
	if (!memfile) free(buffer);
	return memfile;
	
	// Locations to jump to upon failure.
	FREE_CLOSE_FAIL:
	free(buffer);
	CLOSE_FAIL:
	SDL_RWclose(diskfile);
	FAIL:
	return NULL;
};

It also may be worth mentioning that, in addition to the fact you can probably rely on the OS to cache file contents in memory, allocating memory with malloc does not actually guarantee that the memory will stay in RAM: the OS may swap out pages of memory (perhaps the page(s) with the file contents which you’d be going to extra effort to put into an in-memory buffer) onto the disk.

At the end of the day, my opinion is it’s much more productive to focus first on making the experience you are using SDL to create in the first place. When something is slow, then you can begin to concern yourself with performance enhancements. Not to say that it’s bad to keep efficiency in mind, but your time is going to go to waste if you are too busy worrying about these things.

1 Like

TTF_OpenFont does likely load the font file into memory itself and put it into internal structs for easier handling.

I took a look at the implementation (SDL_ttf.c, around line 1650) and

TTF_Font* TTF_OpenFontIndex(const char *file, int ptsize, long index)
{
    SDL_RWops *rw = SDL_RWFromFile(file, "rb");
    if (rw == NULL) {
        return NULL;
    }
    return TTF_OpenFontIndexRW(rw, 1, ptsize, index);
}

TTF_Font* TTF_OpenFont(const char *file, int ptsize)
{
    return TTF_OpenFontIndex(file, ptsize, 0);
}

You should be completly fine with just using TTF_OpenFont :slight_smile:

~mkalte