Now an awesome feature of SDL 2.0 will be presented which is fixing a big major problem for many programmers. It concerns the resolution of the application or game.
The Logical Resolution in SDL 2.0
If you create a game, often there are a lot of questions you just can solve partly by planning. The main question is: What exact resolution are the users going to use to play the game? Do their monitors support the resolution you decide for? Do the user/gamer use a wide screen monitor? And so on. Depending on the answers to these questions you may choose for sprite/image file resolutions. If you are going for a target resolution of 640 x 480 (VGA), you will choose less detailed source images as if you go for 1024 x 768 (XGA), or even widescreen 1680 x 1050 (WSXGA+) or any other high detail resolution. If every monitors/graphic boards would be capable of displaying all possible resolutions, this wouldn’t be much of a problem. The system would just switch to the necessary resolution and voila the game is running and looking like expected. Unfortunately though, you cannot expect a system whose maximum resolution allows for XGA to display WSXGA+ resolution. Also, even if the system is capable of displaying very high resolutions, there is no guarantee that it will be able to switch down to VGA resolution.
Furthermore, if you optimize a game for VGA resolution, you will choose the sprites/images according to fit into a 640 x 480 pixels screen. Lets say you are doing a racing game. You choose a car image of 64 pixels width and 32 pixels height. This fills a good amount of the screen in VGA resolution. In WSXGA+ resolution the car’s width and height will appear less than half, it will appear to be tiny. SDL 2.0 provides a solution to circumvent many of the troubles related to resolution settings: the logical resolution.
Let’s have a look at the code.
program SDL_LogicalResolution;
uses SDL2;
const
Border: TSDL_Rect = (x: 0; y: 0; w: 640; h: 480);
Quarter: TSDL_Rect = (x: 320; y: 240; w: 320; h: 240);
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
begin
// initilization of video subsystem
if SDL_Init(SDL_INIT_VIDEO) < 0 then
Halt;
if SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP, @sdlWindow1, @sdlRenderer) <> 0 then
Halt;
// set scaling filter
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 'linear');
// set logical resolution
if SDL_RenderSetLogicalSize(sdlRenderer, 640, 480) <> 0 then
Halt;
// draw red border
SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderDrawRect(sdlRenderer, @Border);
// draw green quarter
SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, SDL_ALPHA_OPAQUE);
SDL_RenderDrawRect(sdlRenderer, @Quarter);
// render to window for 3 seconds
SDL_RenderPresent(sdlRenderer);
SDL_Delay(3000);
// clear memory
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow (sdlWindow1);
// closing SDL2
SDL_Quit;
end.
This program basically draws two rectangles, a red one and a green one. They are defined by these constants.
const
Border: TSDL_Rect = (x: 0; y: 0; w: 640; h: 480);
Quarter: TSDL_Rect = (x: 320; y: 240; w: 320; h: 240);
The const “Border” is located at (0/0) and has a width of 640 px and a height of 480 px. “Quarter” is located at (320/240) and has a width of 320 px and a height of 240. Let’s assume you have a modern monitor and render these two rectangles fullscreen at a typical resolution of 1680×1050 or even higher. You would expect a result to be something like this:
But since we introduced a logical resolution in the program, we really get something like this:
How does SDL2 achieve a Logical Resolution?
Although your monitor resolution may be much larger (e.g. 1680×1050 px) than what your program is expecting (640×480 px), SDL2 will scale everything up or down just to fit your real resolution the best!
The function SDL_RenderSetLogicalSize() is used to achieve a logical resolution and returns 0 on success and the negative error code on failure.
SDL_RenderSetLogicalSize(renderer: PSDL_Renderer; w: SInt32; h: SInt32): SInt32.
It sets a device independent resolution for rendering. It will make use of scaling functions internally to fit the target resolution to the actual screen/device resolution. If you want to display a game in VGA (640 x 480) resolution on a XGA (1024 x 768) resolved screen, SDL_RenderSetLogicalSize() will scale up everything as necessary to fill the screen. Even if the ration of the device is different, for example you are trying to have a 4:3 ratio resolution on a wide screen 16:9 monitor, this function will just put some bars at appropriate places (see screenshot above) and fit the targeted resolution to the device resolution as good as possible. This is demonstrated in the example code. A target resolution of 640 pixels width x 480 pixels height (4:3 ratio) should be achieved in a device (window) of width x height of 1680 x 1050 pixels (16:10 ratio).
Back to the code. Fullscreen rendering at desktop resolution is achieved by the SDL_WINDOW_FULLSCREEN_DESKTOP flag.
if SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP, @sdlWindow1, @sdlRenderer) <> 0 then
Halt;
And then the logical size is set as discussed by SDL_RenderSetLogicalSize(). By the way, it is strongly recommended to set the scaling filter (SDL_SetHint()) to a better quality filter, because heavy scaling is done behind the scenes by SDL_RenderSetLogicalSize().
// set scaling filter
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 'linear');
// set logical resolution
if SDL_RenderSetLogicalSize(sdlRenderer, 640, 480) <> 0 then
Halt;
Actually that’s it :-), everything else, drawing and rendering rectangles and cleaning up memory is known to you.
Ukrainian translation avaliable here: https://lazarus-games.blogspot.com/p/12.html
i took a look at sdlrenderer.inc: SDL_RenderSetScale allows “resolution independent drawing with a single coordinate system.”. Maybe this function is for another purpose..
Hi there,
thanks for the hint and sorry for the delayed response.
In principle I totally agree with you. The scaling factors SHOULD work as you describe. Unfortunately in reality or at least to my experience, they don’t work this way. Regardless of the factors you try (0.75, 1.25, 2.0, 3.0) for any direction, the image will not be scaled at all. The only way I made this function to do something, is the way described in the tutorial chapter. – It is still not satisfactory though.
I think RenderSetScale has something to do with the “logical screen size” and isn’t intended to be used to change the size of a texture. I am guessing you are supposed to use rectangles for what you are demonstrating. I haven’t had time to experiment with it myself yet though.
Additionally, I couldn’t get the code to compile until I changed sdlFlip’s variable type to integer. I guess there must have been another change to the ev1313 library you are using (my copy is from a couple weeks ago). The PSDL_RendererFlip type has been removed.
Hi MattCash,
I agree that the destination rectangle can be used to achieve the texture scaling. For RenderSetScale it seems to be dependent upon the target which may be a texture or not. I will examine this again and update the chapter accordingly.
you’re right about SDL_Flip. The code has recently changed (Jul 17, 2017) and now the integer value can be used directly which makes it more convenient.
Thanks for your helpful hints!
Matthias
Hi !
i’m just working me through the tutorials..
About the scaling problem: scale is of type Float.
SDL_RenderSetScale( sdlRenderer, 1.0, 1.0 ); -> the original scale (1:1)
SDL_RenderSetScale( sdlRenderer, 500.0, 500.0 ); -> (500:500)
if the scale is lower than 1.0, the texture will be resized smaller than the original.
the tutorials are a great effort. thank you !!