Added a new great project by Jarosław Baran, Fairtris! – It is purely done in the Lazarus IDE with Free Pascal and uses the SDL2-For-Pascal units to make use of graphic hardware acceleration. The full source code is available and poses a great opportunity to learn how to craft a SDL2 game with Free Pascal!
Updated the widgets (replaced legacy widgets) and added new links. Some tags got updated to reflect my new “naming policy” to use SDL2 instead of SDL 2.0.
The work on SDL3 has begun on the official development branch of the SDL project. I’m curious to see what comes from this.
Edit: Fairtris’ author’s name got corrected. (03/06/2023)
Error handling in Pascal means raising exceptions and catching them. SDL2 has also a way to treat errors. Both worlds can be combined and this chapter shows you how.
Let’s look at the whole program code:
program SDL_ErrorHandling;
uses SDL2, SysUtils;
type
ESDLNilPointer = class(Exception);
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
sdlSurface1: PSDL_Surface;
const
filename = 'nofile.bmp'; // this file does not exist
begin
// Initialize SDL2
if SDL_Init(SDL_INIT_VIDEO) < 0 then Exit;
// Create a window, renderer and a surface
try
SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN,
@sdlWindow1, @sdlRenderer);
if not Assigned(sdlWindow1) then
raise ESDLNilPointer.CreateFmt(
'sdlWindow1 is nil. SDL_GetError: %s', [SDL_GetError]);
if not Assigned(sdlRenderer) then
raise ESDLNilPointer.CreateFmt(
'sdlRenderer is nil. SDL_GetError: %s', [SDL_GetError]);
sdlSurface1 := SDL_LoadBMP(filename); // sdlSurface1 will be nil!
if not Assigned(sdlSurface1) then
raise ESDLNilPointer.CreateFmt(
'sdlSurface1 is nil on loading %s. ' + sLineBreak + 'SDL_GetError: %s',
[filename, SDL_GetError]);
except
on E: ESDLNilPointer do
begin
WriteLn('Exception ESDLNilPointer: ' + sLineBreak + E.Message);
Readln;
end;
end;
// Clean-up and quit SDL2
if Assigned(sdlSurface1) then SDL_FreeSurface(sdlSurface1);
if Assigned(sdlRenderer) then SDL_DestroyRenderer(sdlRenderer);
if Assigned(sdlWindow1) then SDL_DestroyWindow(sdlWindow1);
SDL_Quit;
end.
If you run this program, it will stop execution because it catches an exception. It will return several helpful information to make identification of the cause of the exception easier. It allows you to proceed running after the exception has been caught to free resources and prevent memory leaks. It will report the following exception messages:
sdlSurface1 is nil on loading nofile.bmp.
SDL_GetError: Parameter ‘src’ is invalid
Let’s start with the first part of the code:
program SDL_ErrorHandling;
uses SDL2, SysUtils;
type
ESDLNilPointer = class(Exception);
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
sdlSurface1: PSDL_Surface;
const
filename = 'nofile.bmp'; // this file does not exist
The program “SDL_ErrorHandling” has three variables representing a SDL2 window, a SDL2 renderer and a SDL2 surface. It also has a constant “filename” which defines a file name of an imaginary file called “nofile.bmp”. As this file doesn’t exists, it will cause an exception on trying to load it (later in the code).
Right above the var block is an exception class declared whose names start by convention with a capital E instead of a capital T. It is called “ESDLNilPointer” and will be raised whenever a SDL2 function returns a nil pointer as error code. Nearly all function in SDL2 return an error code where 0 usually means sucess and -1 usually means failure. SDL2 function which create objects usually simply indicate failure by nil-pointers.
The main program’s begin .. end. code block starts with the initilization of the SDL2 system and ends with the clean up part and the quitting of the SDL2 system. I will not cover this in detail here again. Let’s instead have a detailed look into the core part of the begin .. end. block:
// Create a window, renderer and a surface
try
SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN,
@sdlWindow1, @sdlRenderer);
if not Assigned(sdlWindow1) then
raise ESDLNilPointer.CreateFmt(
'sdlWindow1 is nil. SDL_GetError: %s', [SDL_GetError]);
if not Assigned(sdlRenderer) then
raise ESDLNilPointer.CreateFmt(
'sdlRenderer is nil. SDL_GetError: %s', [SDL_GetError]);
sdlSurface1 := SDL_LoadBMP(filename); // sdlSurface1 will be nil!
if not Assigned(sdlSurface1) then
raise ESDLNilPointer.CreateFmt(
'sdlSurface1 is nil on loading %s. ' + sLineBreak + 'SDL_GetError: %s',
[filename, SDL_GetError]);
Exception handling in Pascal
In short: Pascal’s exceptions are raised by the raise keyword and stop the program flow at this point. As this would terminate the program immediately, usually raised exception are caught and handled by try .. except or try .. finally blocks. After proper exception handling the program can run without termination although an exception occured.
As you can see, here the whole whole code is encapsuled by a try .. except statement.
Error handling in SDL2
SDL2 knows three error related functions: SDL_GetError, SDL_SetError(Format string, Arguments) and SDL_ClearError. They are completely independent of Pascal’s error handling but are very useful to gather the necessary information to fix issues.
Most SDL2 functions return an error code which indicated success or failure. SDL_GetError returns a string (as PAnsiChar) which gives you information about the error of the last function that returned an error code which indicated failure. Attention here, only fails will result in a message in SDL_GetError and they won’t be cleared autmatically if other functions run successully afterwards, hence you should use SDL_ClearError to clear the last error message from older functon calls. Hint: Sometimes SDL_GetError can be populated with a message even though functions were run successfully.
In the try-block of our code there are actually just two things tried:
Create a renderer and a window
Create a SDL2 surface from a bitmap file
After creating the renderer and window as known to you, we check for the error code (in these cases we check if one or both of them are nil). If the error code indicated a failure, an exception should be raised. Actually this code should not raise an exception and just run smoothly to the second step.
Creating a SDL2 surface will fail intentionally because there is no bitmap file to load. SDL_LoadBMP will return a nil pointer, hence “sdlSurface1” is nil. This will raise the previously declared “ESDLNilPointer” exception with a message as format string (see below if you don’t know about format strings).
The error message string is:
'sdlSurface1 is nil on loading %s. ' + sLineBreak + 'SDL_GetError: %s'
The arguments are represented by an array of constants:
[filename, SDL_GetError]
The error message has three parts. The first part states the failing variable and the associated file name. The second part (sLineBreak) is a cross-platform line break. The third part returns the message of the SDL_GetError function.
What is a Format string?
Coming from C, format strings are strings which contain data placeholders of defined type (string, integer, float, …) which are then combined to the final string. The data placeholders are preceded by a percent symbol (%) and followed by single symbol indicating its type (s for string, d for decimal integer, …)
Ex.: ‘This %s has more than %d words.’ [chapter, 20] will result in: “This chapter has more than 20 words.”
Back to the code:
except
on E: ESDLNilPointer do
begin
WriteLn('Exception ESDLNilPointer: ' + sLineBreak + E.Message);
Readln;
end;
end;
The except-block handles the raised exception. In the example code it just prints the error message we composed on creation of the exception.
// Clean-up and quit SDL2
if Assigned(sdlSurface1) then SDL_FreeSurface(sdlSurface1);
if Assigned(sdlRenderer) then SDL_DestroyRenderer(sdlRenderer);
if Assigned(sdlWindow1) then SDL_DestroyWindow(sdlWindow1);
SDL_Quit;
end.
Finally, the resources are free’d.
Combining Pascal exception and SDL2 error handling
The SDL2 error functions are a convenient way to have a good insight in errors related to the SDL2 functions. It builds a powerful symbiosis with Pascal’s exception handling capabilities.
By the way, SDL_SetError(Format string, Arguments) allows to create your own error messages. In Pascal this is usually not necessary though because the message is set in the exception class.
The description of this project found on it’s Wiki page says it all, nothing to add other than that this project is really capable of spawning some good, nostalgic feelings (!):
“Fairtris is a video game, a clone of the 32-year-old Tetris® game produced by Nintendo for the Famicom and NES consoles, designed for modern Windows and Linux systems. Fairtris is not an emulator — it is a full-fledged game, created from scratch in Free Pascal language (using the Lazarus IDE) and with Direct3D and OpenGL support via the SDL library. Thanks to this combination, it is super-fast and ultra-light.”
Project name: Fairtris
Author: Jarosław Baran a. k. a. furious programming
Latest version: 3.0
First release date: 22 Aug. 2021 (Ver. 1.0)
Compiler and SDL version: Free Pascal / Lazarus / SDL2
Thanks to suve a new documentation resource is available for the PGD SDL2 units at https://pascalgamedevelopment.github.io/SDL2-for-Pascal. The docs are generated directly from the source if new pull requests are merged into the master branch of the repository. Talking of the SDL2 units: They made good progress in 2022 and are still making good progress and soon the first official release will be available.
FFPlay4Laz2 has been added to the projects. It is a Free Pascal/Lazarus Media Player powered by the FFmpeg frame work and SDL2.
In the future I will not refer to SDL version 2 as “SDL 2.0” anymore as SDL’s version numbering scheme has changed with version 2.0.23 (now actually being 2.23.1). That means the minor version is not zero anymore and SDL 2.0 is misleading in that regard. I will update any reference by “SDL2”.
FFPlay4Laz2 is the SDL2 version of the older SDL 1.2 based (Win32 only) FFPlay4Laz FFmpeg Video and Media Player. FFPlay4Laz2 is extended to have more features than FFPlay4Laz and provide better performance. It is designed to be cross-platform by replacing Win-API calls by SDL2 calls. The project is open source and can be found on the official Lazarus forums.
Console menue of FFPlay4Laz2. (Image source: Captured from FFPlay4Laz2_Images.pdf, see link below; 28/12/2022)
This project has no official website and the author provides updates via the official Lazarus forums (see link below).
Project name: FFPlay4Laz2
Author: metis
Latest version: v2.5.2
First release date: Thread startet in Dec. 2014
Compiler and SDL version: Free Pascal / Lazarus / SDL2
After an PHP update the syntax plugin doesn’t work anymore. I added a better plugin and updated all chapters to display code in a reader friendly way. I also removed the description how to copy source code using the old plugin in the introductory chapter. I adapted code snippet length in some chapters (Chapter 4, Chapter 15). Updated Chapter 18 about music and sound.
I added a new SDL2 powered Free Pascal project: SuperSakura. It’s a nice visual novel engine to run retro-style Japanese games by Kirinn.
I updated some chapters (Introduction, Windows Installation, Linux Installation), mainly because of the repository change to the PGD Community SDL2 units. The units got a lot of updates lately, and help is needed to improve them even further. While this news post was waiting to be published, it appears Tim is back. Now, we have the unfortunate situation that there are two active repositories for the SDL2 headers and the situation has not been resolved, yet.
Menue screen of the engine. (Image: With permission of the author.)
Well, the introduction for this amazing project over at the Kirinn’s (developer) website says it all!
SuperSakura is a free, open-source visual novel engine that can run quite a few old games, mostly published by JAST, one of the first developers in the field. In addition to well-known localised titles, Japanese companies produced lots of fairly good games in the 90’s that were never translated.
And this project is fully written in Free Pascal and uses SDL2. Amazing work! I was happy to hear, the author got started with Free Pascal and SDL2 right here, with these tutorials :-)!
IMPORTANT: The repository to get the newest, up to date, SDL2 units has changed. It is found here: https://github.com/PascalGameDevelopment/SDL2-for-Pascal. It is now hosted by our Partner site Pascal Game Development, where you can find many resources if you are interested in game development with Delphi or Free Pascal.
Background: The old repository by Tim Blume has been an up to date source for these units for many years since its establishment (Thanks Tim!), but lately no more pull requests were integrated and so a new repository maintainer had to be found. Long time contributor to the units, Super Vegeta (suve), came up with the great idea to integrate it to the PGD repository, which is now reality (Thanks AthenaOfDelphi!).
In the next chapter we will see how so called events are processed to handle input of any kind (keyboard, mouse, joystick,…) Before that I’d like to introduce a simple, yet powerful way to handle keyboard input via the keyboard state.
Lets have a look at the most important function here. The keyboard state represents the state (pressed = 1 or unpressed = 0) of all the keyboard keys, hence the key states. By
function SDL_GetKeyboardState(numkeys: PInt): PUInt8
we have easy access to this array. This is a highly efficient and fast way of gathering keyboard information without the need to queue any events.
Advantages of using the keyboard state
It is very fast and efficient compared to processing keyboard events. It can (and should) be combined freely with the classical event processing of key-events or other events. You have no hassle with case-statements to care for all the keys you like to consider but rather instant access to all keys on the keyboard. The keyboard state can easily propagated through your program as it is a simple and plain pointer. – Running speed is a crucial property for many games so you should consider using keyboard states if your game relies heavily on keyboard input.
Disadvantages of using the keyboard state
The keyboard states does not consider key modifiers like the shift-key. So you know a key has been pressed but you cannot distinguish between a letter and a capital letter for example. There are no equivalent input states for other devices like mouse, joystick, joypad, and so on. Last but not least, you still need the event processing in the program loop because otherwise the state does not get updated (see below for more information on that). This also means, if the key state of a certain key changes from unpressed to pressed and back to unpressed before the event loop has been processed, the pressed key remains unnoticed.
The red rectangle can be moved by the WASD keys by reading out their key states.
The following code example will draw a red rectangle which can be moved by the WASD keys. Therefore we read out their key states on every cycle of the program loop.
program SDL_KeyboardState;
uses SDL2;
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
sdlRectangle: TSDL_Rect;
sdlKeyboardState: PUInt8;
Running: Boolean = True;
begin
//initilization of video subsystem
if SDL_Init(SDL_INIT_VIDEO) < 0 then Halt;
if SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN, @sdlWindow1, @sdlRenderer) <> 0
then Halt;
// prepare rectangle
sdlRectangle.x := 250;
sdlRectangle.y := 250;
sdlRectangle.w := 10;
sdlRectangle.h := 10;
// get keyboard state pointer
sdlKeyboardState := SDL_GetKeyboardState(nil);
// program loop
while Running = True do
begin
SDL_PumpEvents;
// ESC pressed
if sdlKeyboardState[SDL_SCANCODE_ESCAPE] = 1 then
Running := False;
// WASD keys pressed
if sdlKeyboardState[SDL_SCANCODE_W] = 1 then
sdlRectangle.y := sdlRectangle.y-1;
if sdlKeyboardState[SDL_SCANCODE_A] = 1 then
sdlRectangle.x := sdlRectangle.x-1;
if sdlKeyboardState[SDL_SCANCODE_S] = 1 then
sdlRectangle.y := sdlRectangle.y+1;
if sdlKeyboardState[SDL_SCANCODE_D] = 1 then
sdlRectangle.x := sdlRectangle.x+1;
// black background
SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(sdlRenderer);
// draw red rectangle
SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderDrawRect(sdlRenderer, @sdlRectangle);
SDL_RenderPresent(sdlRenderer);
SDL_Delay(20);
end;
// clear memory
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow (sdlWindow1);
//closing SDL2
SDL_Quit;
end.
To get the keyboard state, we define a unsigned 8 bit pointer variable sdlKeyboardState in the var clause. It points to the array of key states.
After setting up a SDL2 window and and preparing a SDL2 rectangle, we jump right into allocating the keyboard state pointer to the sdlKeyboardState variable by the function SDL_GetKeyboardState. The argument should be nil. It is sufficient to this once in your program code as you see in the second line:
// get keyboard state pointer
sdlKeyboardState := SDL_GetKeyboardState(nil);
// program loop
while Running = True do
begin
SDL_PumpEvents;
In the program event loop we need to update the event queue by procedure SDL_PumpEvents (or SDL_PollEvent or SDL_WaitEvent; both call SDL_PumpEvent implicitly). Only calling one of these procedures in the program loop updates the keyboard state.
// ESC pressed
if sdlKeyboardState[SDL_SCANCODE_ESCAPE] = 1 then
Running := False;
// WASD keys pressed
if sdlKeyboardState[SDL_SCANCODE_W] = 1 then
sdlRectangle.y := sdlRectangle.y-1;
if sdlKeyboardState[SDL_SCANCODE_A] = 1 then
sdlRectangle.x := sdlRectangle.x-1;
if sdlKeyboardState[SDL_SCANCODE_S] = 1 then
sdlRectangle.y := sdlRectangle.y+1;
if sdlKeyboardState[SDL_SCANCODE_D] = 1 then
sdlRectangle.x := sdlRectangle.x+1;
We now can check for the of any key in the array by sdlKeyboardState[SDL_SCANCODE_…] using its scancode as a handle (e.g. SDL_SCANCODE_ESCAPE for the escape key) and react as desired, e.g. exit the program loop or change the x/y coordinates of the rectangle. The scancode represents the position of the related key state in the array. A detailed description of scancodes in the next chapter. A list of all the scancodes shows all possible scancodes.
Could you please give a short description of Savage Vessels for those who have never heard of it?
Within fields of asteroids and fragmented freighters you scavenge and combat against robot vessels. At the same time you have to keep away from the surrounding void. With your carrier you got into this threatening area. In order to escape you have to determine your location repeatedly: leave the carrier, visit some navpoints, land again and move on. But the robots won’t let you. So you have to arm against them by gathering and crafting.
The visuals are based on top down pixel-art and a dynamic field of view. Modern physics provide inertia and collision. Sound fx creates an eerie atmosphere. It’s a spiritual successor to Teleglitch.
Why did you decide to choose Pascal as a programing language and SDL/SDL2 as a library for this project?
Pascal is my mother tongue. It’s capable of everything I need and I’m feeling comfortable with it.
SDL is versatile, platform-agnostic and plain.
What do you think is the most interesting Pascal/SDL/SDL2 project out there (besides of your own, of course :-D)?
This project is a program to load GoldSrc BSP files. The GoldSrc BSP file format has been derived from the id’s Quake 2 file format by Valve Software for their Half-Life game series.
It has been realized with
Lazarus, Free Pascal
SDL2, OpenGL.
The BSP Loader powered by Lazarus.Loading a WAD file and displaying a selected texture from it.Textured rendering of a scene (estate). The blue colorkey is not interpreted to be transparent yet.Scene: Oilrig.Scene: Assault.
02/08/2018, v0.1 alpha
Capabilities
Load BSP files and show contents of data lumps (exception: VIS Lump)
Load WAD files and render contained textures
Load BSP file and all WAD files which are necessary to render the fully textured scene
Navigate by simple camera through scene
To-Do’s
lightmapping from lightmap data
VIS Lump: treat it at all
collision detection
face culling
have spaces between textures in atlas texture to prevent bleeding-effect (esp. in tiled textures recognizable)
make blue colorkey transparent
sky cube
release the source code (if beta stadium reached)
Important Sources
BSP and WAD File Formats
I cannot state how important these documents were in understanding the structure of the BSP and WAD file formats. Without them, this project wouldn’t have been possible.
Simply, this record describes a rectangle, hence the name. The variables x and y correspond to the x/y coordinates of the left upper corner of the rectangle, related to the origin 0/0 which is the left upper corner of, e.g. a texture, window,… The variable w is the width and h the height of the rectangle. That’s it. The next step is to define the rectangle by assign some values for x, y, w and h.
If you use PSDL_Rect, you free the required memory by Pascal’s new procedure as you would for any simple record pointer.
Using Rectangles for Movement and Scaling
The following code demonstrates the basic principle how to achieve the impression of movement of images (sprites) and how scaling works. You will be impressed how simple it actually is.
program SDL_RectanglesScaling;
uses SDL2;
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
sdlSurface1: PSDL_Surface;
sdlTexture1: PSDL_Texture;
sdlRectangle: TSDL_Rect;
begin
//initilization of video subsystem
if SDL_Init(SDL_INIT_VIDEO) < 0 then Halt;
if SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN, @sdlWindow1, @sdlRenderer) <> 0
then Halt;
// set scaling quality
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 'nearest');
// create surface from file
sdlSurface1 := SDL_LoadBMP('fpsdl.bmp');
if sdlSurface1 = nil then
Halt;
// load image file
sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1);
if sdlTexture1 = nil then
Halt;
// prepare rectangle
sdlRectangle.x := 12;
sdlRectangle.y := 25;
sdlRectangle.w := 178;
sdlRectangle.h := 116;
// render texture
SDL_RenderCopy(sdlRenderer, sdlTexture1, @sdlRectangle, nil);
SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, @sdlRectangle);
// render to window for 2 seconds
SDL_RenderPresent(sdlRenderer);
SDL_Delay(2000);
// clear memory
SDL_DestroyTexture(sdlTexture1);
SDL_FreeSurface(sdlSurface1);
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow (sdlWindow1);
//closing SDL2
SDL_Quit;
end.
After initializing SDL2 and setting up the window and renderer as known, the rectangle is getting some values. It just encloses the words “Free Pascal meets SDL” in the original image (see above).
Scaling in SDL2
Scaling Quality
Right before creating the surface and texture, there is this line in code.
// set scaling quality
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 'nearest');
It sets the render quality. It has to be set before creating the texture. The SDL_SetHint(hint name, hint value) function is no specific function for setting scaling quality, but here we use it for exactly that. Possible values are
nearest or 0
nearest pixel sampling
linear or 1
linear filtering
support by OpenGL and Direct3D
best or 2
anisotropic filtering
support by Direct3D.
All of the values have to be set as string values, so ‘nearest’ or ‘0’. Here is a comparision of the nearest- and the linear filter.
Nearest pixel sampling filter for scaling.
Linear filter for scaling.
The anisotropic filter doesn’t do anything for me, even if I used Direct3D.
At this point happens the magic that leads to the resulting image. By the way, since the SDL_RenderCopy() function requires the rectangle arguments to be of PSDL_Rect, we use the @-operator (pointer operator) here.
This means, copy the area described by “sdlRectangle” from the source (“sdlTexture1” here) to the whole area (because of nil value) of the destination, hence the window.
Since the window has a width and height of 500 px each, the source rectangle just a width of 178 px and a height of 116 px, SDL2 automatically scales the image to fit into the larger (or smaller) dimensions of the destination.
This means, copy the whole source (because of nil value) to the area described by “sdlRectangle”. The source is the 200×200 px image, which has to squeezed to the 178×116 px rectangle at position (12/25). This is just what you see in the resulting image (above) where the whole image is squeezed into this area.
Movement of Images (Sprites)
Although not covered directly by this code example, you get the picture how movement works. Every frame you adjust the (x/y) coordinates of the rectangle for the destination to bring the sprite about to move.
After cleaning up the memory the program finishes.
Let’s start on the left in the diagram. The easiest way to get a bitmap (BMP) image file for a game or application ready for usage is to create one in a drawing application. Or use the example bitmap “fpsdl.bmp” we used in the code.
The bmp image file is stored on your hard drive and can be loaded by SDL_LoadBMP function to a SDL2 surface. This SDL2 surface is then transformed into a SDL2 texture by SDL_CreateTextureFromSurface function (whose name is just explaining what is does). And finally this texture is rendered by SDL_RenderPresent, this function we know already.
And now let’s see how it is done in code.
program SDL_LoadingRenderingBMP;
uses SDL2;
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
sdlSurface1: PSDL_Surface;
sdlTexture1: PSDL_Texture;
begin
//initilization of video subsystem
if SDL_Init(SDL_INIT_VIDEO) < 0 then Halt;
if SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN, @sdlWindow1, @sdlRenderer) <> 0
then Halt;
// create surface from file
sdlSurface1 := SDL_LoadBMP('fpsdl.bmp');
if sdlSurface1 = nil then
Halt;
// create texture from surface
sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1);
if sdlTexture1 = nil then
Halt;
// render texture
if SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil) <> 0 then
Halt;
// render to window for 2 seconds
SDL_RenderPresent(sdlRenderer);
SDL_Delay(2000);
// clear memory
SDL_DestroyTexture(sdlTexture1);
SDL_FreeSurface(sdlSurface1);
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow (sdlWindow1);
//closing SDL2
SDL_Quit;
end.
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
sdlSurface1: PSDL_Surface;
sdlTexture1: PSDL_Texture;
contains two new variables, namely “sdlSurface1” and “sdlTexture1” of the pointer types PSDL_Surface and PSDL_Texture, respecitvely.
After setting up SDL2, a window and a renderer as known, we find this.
Step 1: Loading the BMP file to a SDL2 Surface
// create surface from file
sdlSurface1 := SDL_LoadBMP('fpsdl.bmp');
if sdlSurface1 = nil then
Halt;
SDL_LoadBMP(name of bmp image file) does what you expect, it loads the image file and generates a SDL2 surface from it. Attention though, if you just give a file name, it is assumed that the file is found in the same folder as the executing application. Optionally you can also give a full file path, e.g. in Windows something like ‘C:\MyImages\fpsdl.bmp’. The function is declared as
SDL_LoadBMP(_file: PAnsiChar): PSDL_Surface
and return nil on error, e.g. if the file is not found.
Step 2: Creating a SDL2 Texture from the SDL2 Surface
The next step is to get a SDL2 texture. That’s achieve as follows.
// create texture from surface
sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1);
if sdlTexture1 = nil then
Halt;
The function to use is SDL_CreateTextureFromSurface(renderer, surface).
It just does what you expect and transforms the SDL2 surface into a SDL2 texture with the help of the given renderer.
Step 3: Prepare the SDL2 Texture to be Rendered
Before actually rendering the texture, we need to copy it to the rendering target (our window) by SDL_RenderCopy(renderer, texture, source rectangle (texture), destination rectangle (rendering target)).
// render texture
if SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil) <> 0 then
Halt;
So the texture is copied to the rendering target (which is the window). The first nil argument means that we want to copy the whole rectangle. The second nil means that we want to copy to the whole dimensions of the rendering target. Let’s have a closer look at the function.
This will run without any problem, though SDL_CreateTextureFromSurface() will not free the surface created by SDL_LoadBMP(). And you have no handle to free this surface. This creates a memory leak.
This chapter treats some basics you should know to understand the way SDL2 works.
Briefly: The Basics of Graphics Programming
Loading and the movement of images in a game (or other applications) is a major concept in (game) programming. These images are then refered to as sprites, usually. Let’s have a look at a simple example:
Left: Window is cleared between each new drawn frame. Right: Window is not cleared.
Here are two screenshots from a simple game. The player has to move the yellow-green paddle up- and downwards to prevent the blue ball from getting through to the right side. The game uses two sprites, the blue ball sprite and the yellow-green paddle sprite (see left screenshot). The background color is set to black. The left screenshot is how the game usually appears to the player, here between each frame that got drawn, the former frame has been cleared. The right screenshot demonstrates what happens if the former frame hasn’t been erased before the next one is drawn. – Now it is clearly visible that the sprites are redrawn again and again with sligthly different coordinates, and that is how (game) graphics work (even for the most sophisticated 3d games):
Draw the frame
Show the frame (in a window on screen)
Clear the frame (and go back to step 1)
Briefly: The Relation between Graphic Objects (e.g. Sprites) and Hardware
Actually there are just three locations where these images are stored in your computer system. All images (photo images, drawings, sprites for 2d games, textures for 3d games) are stored on your harddrive somewhere. If you start a photo viewer, a paint program, a 2d game or a 3d game, in all cases the corresponding images need to be loaded from your harddrive to RAM (Random-Access Memory) since displaying and manipulation (e.g. rotation of a photo image by 90°) of images loaded to RAM is much, much faster. Especially for games a fast access to the image data is highly important! And finally there isn’t just one RAM but two, a CPU controlled one located on the motherboard used by every program/application that needs some RAM. The second RAM is located right at your graphic board and controlled by the so-called GPU (graphics processing unit). This is what we want to use if we develop games since it is dedicated, optimized and just hungry for tasks related to fast image processing.
Many games and applications do not only target at common computer systems, but for mobile devices, e.g. smart phones. The principles described are also true for these devices even though there may be differences in detail.
The SDL2 Surface
The SDL2 surface allows you to represent graphic objects like sprites. Every SDL2 surface has a width and height, a pixel format and other properties. Nevertheless, it is a concept which originates from the outdated SDL 1.2 and therefore should not be used anymore. Still, there are reasons why we need to introduce it here. This will be clear soon.
The SDL2 Texture
The SDL2 texture allows you to represent graphic objects just like the SDL2 surface does, although there is a major difference: It is hardware accalerated. So the graphic object is stored in the graphic board’s RAM and any manipulation is done by the graphic board’s GPU.
So as a rule,
always use SDL2 Textures to store your graphic objects (sprites) for SDL 2.0,
then you go for high performance!
Three ways to SDL_Texture
So, how to get a SDL_Texture? In principle there are three ways to create SDL2 textures. For way 2 and 3 the flow diagram may illustrate how it works.
Way 1: From Scratch
You create a SDL_Texture from scratch, so you set a pixel format and texture access format and have to fill in your texture data manually. This is the most sophisticated way and is usually not necessary, unless you work with raw pixel data.
Way 2: The path from the file to the surface, to the texture and to the screen. Way 3: The path rom the file to the texture and to the screen.
Way 2: From SDL2 Surface
2) You have or create a SDL_Surface from an image file first and then you create the SDL_Texture from the SDL_Surface. This way is shown in the diagram but it means two steps.
Way 3: Directly from Image File
3) You create a SDL_Texture from and image file directly. This is shown in the diagram, too. This is the simplest way to create a SDL_Texture.
Every SDL2 program that shall show some graphic output has to have at least one SDL2 window and a SDL2 renderer. The window is the entity that is showing the graphic output and the renderer is the “machine” that is generating the output to be shown in the window. The code to set up a window and a renderer is as follows.
program SDL_WindowAndRenderer;
uses SDL2;
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
begin
//initilization of video subsystem
if SDL_Init(SDL_INIT_VIDEO) < 0 then Halt;
// full set up
sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
if sdlWindow1 = nil then Halt;
sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
if sdlRenderer = nil then Halt;
// quick set up
{
if SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN, @sdlWindow1, @sdlRenderer) <> 0
then Halt;
}
// render to window for 2 seconds
SDL_RenderPresent(sdlRenderer);
SDL_Delay(2000);
// clear memory
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow (sdlWindow1);
//closing SDL2
SDL_Quit;
end.
Let’s have closer look at the var clause.
var
sdlWindow1: PSDL_Window;
sdlRenderer: PSDL_Renderer;
The SDL2 Window
In SDL 2.0 you can create as many windows as you like, and each window is adressed by its PSDL_Window variable. We just need one window for now, let’s call it “sdlWindow1”. It defines the window’s properties, e.g. size, appearance, border, title name and so on. And it holds the content it shows.
Creation of a Window
// full set up
sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
if sdlWindow1 = nil then Halt;
The creation of a SDL2 window is simple as using the function SDL_CreateWindow(title, x, y, width, height, flags) or more specific:
In our example the window is titled “Window1”, it is located at position x = 50 and y = 50 pixels (relative to your screen). It has a width and height of 500 pixels respecitvly. And we have used the flag SDL_WINDOW_SHOWN. More about these flags later. First let’s get an understanding of the coordinate system in SDL2.
Creation of a Window on MacOS
If you use Linux or Windows, you may skip this paragraph.
It is important that the code shown above will not work on MacOS. For technical reasons you need to add an event loop to your code. Event loops are covered in a later chapter.
In short to make your code work:
add a variable “ExitLoop” of type “Boolean” to your var clause and preset it to False
add a variable “Event” of type “TSDL_Event” to your var clause
replace the call “SDL_Delay(2000);” with the following code snippet
while not ExitLoop do
begin
while (SDL_PollEvent(@Event) <> 0) do
begin
if (Event.type_ = SDL_QUITEV) then
ExitLoop := True;
end;
end;
Instead of a 2 second delay you need to quit your window (click on “X”) and the program will terminate.
The Coordinate System in SDL 2.0
This rule applies:
The origin from where to count to place a window is always the left upper corner of your screen.
So if you choose (0/0) as coordinates the window’s left upper corner will be placed right at the left upper corner of your screen. The diagram below may help to understand this. You may try SDL_WINDOWPOS_CENTERED for each or both coordinates which will lead to a centered window with respect of the screen. If you choose SDL_WINDOWPOS_UNDEFINED you don’t care for the window’s position.
Now let’s talk about the flags. They decide for the properties of the window. Look at the following table (source) of possible flags and you may get an idea what they do.
Flag
Description
SDL_WINDOW_FULLSCREEN
fullscreen window
SDL_WINDOW_FULLSCREEN_DESKTOP
fullscreen window at the current desktop resolution
SDL_WINDOW_OPENGL
window usable with OpenGL context
SDL_WINDOW_SHOWN
window is visible
SDL_WINDOW_HIDDEN
window is not visible
SDL_WINDOW_BORDERLESS
no window decoration
SDL_WINDOW_RESIZABLE
window can be resized
SDL_WINDOW_MINIMIZED
window is minimized
SDL_WINDOW_MAXIMIZED
window is maximized
SDL_WINDOW_INPUT_GRABBED
window has grabbed input focus
SDL_WINDOW_INPUT_FOCUS
window has input focus
SDL_WINDOW_MOUSE_FOCUS
window has mouse focus
SDL_WINDOW_FOREIGN
window not created by SDL
SDL_WINDOW_ALLOW_HIGHDPI
window should be created in high-DPI mode if supported (available since SDL 2.0.1)
As you can see, these flags determine different properties of the window. E.g. SDL_WINDOW_FULLSCREEN will create a fullscreen window and SDL_WINDOW_BORDERLESS will create a borderless window. You may combine several flags by OR (if appropriate). For our purpose SDL_WINDOW_SHOWN is a good choice because we just create a shown window without any further restrictions.
The SDL2 Renderer
In computer graphics rendering means the process of synthesizing the final image on your screen from the individual basic data structures. To draw some content to the window, we need therefore a renderer. The PSDL_Renderer (which we declared in the var clause) is responsible for synthesizing all the content in a window, be it some lines, a flat background, a texture, a 3d object, or whatever. We call our PSDL_Renderer “sdlRenderer”.
Creation of a Renderer
sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
if sdlRenderer = nil then Halt;
The creation of the renderer is as simple as one function call of SDL_CreateRenderer(window, index, flags) or
First we need the renderer to know where to render the finished/rendered output. That will be “Window1” in our case. Next the shown function asks for a cryptic “index”. Well, each driver which is capable of rendering (e.g. OpenGL, Direct3d, Software,…) is indexed in SDL 2.0. In principle you could choose one specific driver here by choosing the corresponding index. Since we don’t know too much about the drivers at the moment the best choice is -1. -1 means that the first driver which is supporting the chosen flag(s) is chosen. Talking about flags, there are four flags you may choose:
SDL_RENDERER_SOFTWARE
SDL_RENDERER_ACCELERATED
SDL_RENDERER_PRESENTVSYNC
SDL_RENDERER_TARGETTEXTURE
You should always prefer SDL_RENDERER_ACCELERATED because this means the graphics board is responsible for rendering, SDL_RENDERER_SOFTWARE in contrast means, the CPU has to do the rendering. As discussed before for best performance the graphic board is the best choice for rendering/graphic related tasks. SDL_RENDERER_PRESENTVSYNC allows for so called vertical synchronization which means that the display of the rendered image is synchronized with the refresh rate of the monitor. SDL_RENDERER_TARGETTEXTURE allows for rendering to a texture. You may have noticed that none of these flags but 0 was used in the example code. This automatically gives priority to hardware accelerated renderers.
Quick Creation of a Window and a Renderer
// quick set up
{
if SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN, @sdlWindow1, @sdlRenderer) <> 0
then Halt;
}
Instead of creating the window and the renderer separately as demonstrated, you may use SDL_CreateWindowAndRenderer(width, height, window flags, window pointer pointer, renderer pointer pointer). This has the advantage that you just need one line to set up a window and a renderer, though setting a window title, a window position or specific renderer flags have to be done afterwards if necessary.
Just remove the curly brackets and enclose the “full set up” -part to try it.
This function returns 0 on success and -1 on failure.
Rendering a SDL2 Scene
The actual rendering is achieved by SDL_RenderPresent(renderer). As a sidenote for people coming from SDL 1.2, this is what formerly has been achieved by SDL_Flip().
SDL_RenderPresent(renderer: PSDL_Renderer)
Freezing (delaying) a running program in SDL 2.0
SDL_Delay(time in milliseconds) is a simple, yet powerful and important procedure to stop the program running for a certain time in milliseconds. 2000 milliseconds are two seconds. This is kind of a twin of Pascal’s Delay procedure.
Clean up the memory in SDL 2.0
Now the final lines of code are discussed. One of the most important rules for sophisticated programming is followed here:
Always clean up the memory on program finish.
For nearly any pointer type generated by SDL 2.0, there is a destroy procedure to remove it from memory. These procedures are comparable to Pascal’s dispose procedure to remove pointer types from memory. Make sure to destroy the objects in the opposite sequence of their generation. We first created a window, then a renderer. So now we go the opposite way, first destroy the renderer and then the window by the procedures SDL_DestroyRenderer(renderer) and SDL_DestroyWindow(window) respectively.