Using SDL_RenderSetLogicalSize for Resolution‑Independent Rendering in SDL2
SDL_RenderSetLogicalSize creates a fixed virtual resolution that SDL2 scales to the window, letting game logic stay resolution‑independent while requiring careful handling of input and pixel‑perfect UI.
20 Jun 2026, 10:55 UTC

Useful answer
SDL_RenderSetLogicalSize lets you define a fixed virtual resolution (for example, 640×480) that SDL2 scales to match the actual window size. Game logic can then work in those logical pixels, and the rendered output will appear at the same relative position whether the window is 800×600, 1920×1080, or any other size.
How it works
After you create an SDL_Renderer, calling SDL_RenderSetLogicalSize(renderer, logicalW, logicalH) tells SDL to build an internal scaling matrix. All subsequent draw commands are interpreted in the logical coordinate system; SDL multiplies them by the matrix to map to the actual backbuffer pixels. The function also preserves the aspect ratio by default and can optionally enforce integer scaling to avoid sub‑pixel blur.
Worked example
The following minimal C program shows the typical setup. Compile with gcc example.c -lSDL2 -o example (adjust flags for your platform).
#include
#include
int main(int argc, char *argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
return 1;
}
/* Create a window that the user can resize */
SDL_Window *win = SDL_CreateWindow(
"Logical size demo",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
800, 600,
SDL_WINDOW_RESIZABLE
);
if (!win) {
SDL_Log("Failed to create window: %s", SDL_GetError());
SDL_Quit();
return 1;
}
/* Create a hardware‑accelerated renderer with vsync */
SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!ren) {
SDL_Log("Failed to create renderer: %s", SDL_GetError());
SDL_DestroyWindow(win);
SDL_Quit();
return 1;
}
/* Set a fixed virtual resolution of 640×480 */
if (SDL_RenderSetLogicalSize(ren, 640, 480) != 0) {
SDL_Log("Failed to set logical size: %s", SDL_GetError());
/* Continue – drawing will still work, but not resolution‑independent */
}
bool quit = false;
SDL_Event e;
while (!quit) {
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) quit = true;
}
/* Clear the backbuffer */
SDL_SetRenderDrawColor(ren, 30, 30, 30, 255);
SDL_RenderClear(ren);
/* Draw a 50×50 rectangle at logical position (100,100) */
SDL_SetRenderDrawColor(ren, 200, 80, 80, 255);
SDL_Rect r = {100, 100, 50, 50};
SDL_RenderFillRect(ren, &r);
/* Present the frame */
SDL_RenderPresent(ren);
}
SDL_DestroyRenderer(ren);
SDL_DestroyWindow(win);
SDL_Quit();
return 0;
}
When you run this program and resize the window, the red rectangle stays visually anchored to the same fraction of the screen (roughly one‑third across and down). The logical coordinates (100,100) never change; SDL handles the scaling behind the scenes.
Limits and considerations
- Rendering only: The logical size affects how drawing commands are interpreted. It does not change the size of the backbuffer or the actual pixel dimensions of textures you create. If you need textures that match the logical resolution, create them after setting the logical size, or recreate them when the logical size changes.
- Input coordinates: Mouse, touch, or game‑controller positions are reported in actual window pixels. To use them with your logical game world you must convert them back, e.g.:
int mx, my;
SDL_GetMouseState(&mx, &my);
float scaleX, scaleY;
SDL_RenderGetScale(ren, &scaleX, &scaleY);
int logicalX = (int)(mx / scaleX);
int logicalY = (int)(my / scaleY);
- UI that should stay pixel‑perfect: Elements like crisp fonts or HUD icons often look better when rendered in actual pixels. Temporarily disable logical size for those draws by calling
SDL_RenderSetLogicalSize(ren, 0, 0)(or by setting the renderer’s viewport to the full window and resetting the scale). - Order of operations: Setting the logical size after you have already created textures can cause those textures to be sampled at an unintended resolution. Either set the logical size before loading assets, or reload/recreate textures after the change.
- Aspect‑ratio handling: By default SDL preserves the aspect ratio, adding black bars (letterboxing) when the window’s aspect differs from the logical size. If you need stretching instead, you can set the logical size and then adjust the renderer’s viewport manually, but be aware that this will distort non‑uniformly scaled assets.
Common mistakes
- Assuming that
SDL_GetMouseState returns logical coordinates directly, leading to misaligned clicks or touches. - Leaving logical size enabled while drawing UI textures that were created at the window’s native resolution, resulting in blurry or scaled‑up fonts.
- Calling
SDL_RenderSetLogicalSizeinside the render loop every frame, which is unnecessary overhead and can interfere with VSYNC timing if not done carefully. - Forgetting to check the return value of
SDL_RenderSetLogicalSize; a failure (e.g., out of memory) leaves the renderer in an undefined state.
Practical verification
- Run the example above on any platform with SDL2 installed.
- Resize the window and confirm the rectangle keeps the same relative position.
- Query the logical size and scale after setting it:
int w, h; SDL_RenderGetLogicalSize(ren, &w, &h); float sx, sy; SDL_RenderGetScale(ren, &sx, &sy); /* w and h should be 640 and 480; sx and sy should reflect the window’s scaling factor */ - Test input handling: convert mouse coordinates as shown earlier and verify that clicking on the rectangle yields logical coordinates near (100,100)±25.
These steps let you confirm that logical size is active and that your coordinate transformations are correct without needing any specialized profiling tools.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.