If you're making a game that you want to be used on a wide variety of systems, your game has to be scalable, especially if it is in a very small resolution. This is usually pretty straightforward. Most graphics libraries and the like have things built in for doing the scaling for you. SDL2 does, but only to a certain extent. If you want your game to be scaled without letter-boxing, you have to write this yourself. It took me a while to figure out, so I though I would spread the knowledge.
Note: Using this form of scaling can create some serious distortion as well as introduce scaling artifacts to the game with different aspect ratios.
The theory here is this: You don't want letterboxing. You want the game to be taken from its original size and stretched to fit the window no matter what. So you just render everything to a back buffer by setting the target to it, instead of to the window's buffer, then at the end of your rendering cycle, switch the target back to the screen and RenderCopy the back buffer onto the screen, rescaling if neccessary, then set the target back to your back buffer and repeat. It's slightly trickier than it sounds.
Enter The Code
So in order to accomplish scaling without letter-boxing, we will need some SDL rectangles variables.
One is for storing the original size of the game, nativeSize, and one is for storing the size of the window when it changes size, newWindowSize.
We also will need an SDL_Window, an SDL_Renderer for that window, and last but not least a backBuffer for rendering everything to before copying it, properly scaled, to the window's buffer. Also there is ballImage which will be used to demonstrate the actual scaling. The two floats, scaleRatioW and scaleRatioH are optional. They are used if you need to scale the values of certain things that rely on the coordinates of the mouse, e.g. a button. You would multiply the x coord and width of its bounding-box by scaleRatioW and the y coord and height by scaleRatioH. resize is used to tell the render function to call the resize function. It is neccessary to do it this way because there is a stall in the program after resizing the window that can sometimes cause problems if you call the resize functions immediately upon receiving a resize event.
So I have set up a fairly simple main function to run the program.
InitValues does what you might expect it to do, gives all the variables their starting values. InitSDL does most of the importnat work, setting up the window and renderer as well as set up the back buffer and load the ball image. HandleEvents returns false if the user clicks the x button on the window, however it also captures the window resize event and resizes the screen using a function called Resize. Render handles a very important part of the process, changing from the back buffer to the window buffer. I'll explain each of the functions in turn.
I set the native size to that of the SNES, 256 by 224.
if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
cout << "Failed to initialize SDL" << endl;
//Set the scaling quality to nearest-pixel
if(SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0") < 0)
cout << "Failed to set Render Scale Quality" << endl;
//Window needs to be resizable
window = SDL_CreateWindow("Rescaling Windows!",
//You must use the SDL_RENDERER_TARGETTEXTURE flag in order to target the backbuffer
renderer = SDL_CreateRenderer(window,
//Set to blue so it's noticeable if it doesn't do right.
SDL_SetRenderDrawColor(renderer, 0, 0, 200, 255);
//Similarly, you must use SDL_TEXTUREACCESS_TARGET when you create the texture
backBuffer = SDL_CreateTexture(renderer,
//IMPORTANT Set the back buffer as the target
//Load an image yay
SDL_Surface * image = SDL_LoadBMP("Ball.bmp");
ballImage = SDL_CreateTextureFromSurface(renderer, image);
This is a fairly large chunk of code. First SDL_Init is called to set up all the SDL subsystems. Next, something that one might find important, SDL_SetHint is called to set the render scale quality to nearest-pixel. Linear quality makes scaling very small images to very large images hazy, and this is unwanted behavior.
Next the window is created with the SDL_WINDOW_RESIZABLE flag to allow it to be resized. Then, very important, the renderer is created with the SDL_RENDERER_TARGETTEXTURE flag which allows a texture to be targeted, and backBuffer must be created using the SDL_TEXTUREACCESS_TARGET flag for it to be used as a back buffer. The render target is then set to backBuffer so that all drawing will happen on it, not on the window.
The HandleEvents function for clarification. Resize is not called all the time, just when the window's size changes.
int w, h;
SDL_GetWindowSize(window, &w, &h);
scaleRatioW = w / nativeSize.w;
scaleRatioH = h / nativeSize.h; //The ratio from the native size to the new size
newWindowSize.w = w;
newWindowSize.h = h;
//In order to do a resize, you must destroy the back buffer. Try without it, it doesn't work
backBuffer = SDL_CreateTexture(renderer,
SDL_TEXTUREACCESS_TARGET, //Again, must be created using this
if(viewPort.w != newWindowSize.w || viewPort.h != newWindowSize.h)
//VERY IMPORTANT - Change the viewport over to the new size. It doesn't do this for you.
So here is the most important part of scaling and where it became tricky. There are quite a few things that happen here. First, get the new window size (using GetWindowSize for clarity). Then calculate the new ratio based on the new size and the native size. Then, Very Important you must destory and recreate your back buffer. I honestly don't know why, but if you try without doing this it will not work. Then, also Very Important, you must set the view port to the new window size because it will not do it for you, or do it right anyways.
SDL_RenderCopy(renderer, ballImage, NULL, NULL); //Render the entire ballImage to the backBuffer at (0, 0)
SDL_SetRenderTarget(renderer, NULL); //Set the target back to the window
if(resize) //If a resize is neccessary, do so.
resize = false;
SDL_RenderCopy(renderer, backBuffer, &nativeSize, &newWindowSize); //Render the backBuffer onto the
//screen at (0,0)
SDL_RenderClear(renderer); //Clear the window buffer
SDL_SetRenderTarget(renderer, backBuffer); //Set the target back to the back buffer
SDL_RenderClear(renderer); //Clear the back buffer
And last but not least the Render function. The comments explain this one pretty well.
Note: For some reason, whenever you first resize the window, there is about a three-second delay and after that it resizes without a hitch. Not sure why on that one.
SDL2 is a pretty straight-forward API for doing just about anything. However, getting around letter-boxing was slightly tricky. Some of the things that occur in the background of SDL aren't obvious which led to a sort of trial and error process to figure out the way around. I hope this helps if anyone has the same problem with letter-boxing that I did.