The bad news is that in many popular Linux distributions there are no pre-compiled SDL3 library files as of now (28/10/2025) available to be easily installed on the system.
The good news is, this description will show you how to compile them easily and quick yourself.
Step 1: Download and Extract the Libraries
Obviously we need the source code of the respective library. Usually these are:
SDL3 for the core system
SDL3_image for convenient image functions
SDL3_ttf for easy usage of fonts
SDL3_mixer for convenient sound and music functions
cmake is a software building tool usually already installed on your Linux system or easily available via your system’s package manager (see bottom of page to see how). What these lines do:
Line 1: Configure the source directory (-S) to be the current directory (.) and the destination directory (-B) for the results of the build procedure (build).
Line 2: Do the actual building (–build) and put the result in the build directory. This step may take some time.
Line 3: Install the built results in directory build (–install build) to the /usr/local directory. This will create a sub-folder “lib” in which the successfully compiled dynamic libraries will reside (.so files). Consequently the full path to them is /usr/local/lib.
The folder /usr/local is used typically to store programs that are not managed by Linux’ package manager. As we did not install the libraries via the package manager, we consequently choose this folder to store the results. If in the future SDL3 will be available in the package manager, we can simply delete the folder’s content related to SDL3.
Step 3: Make SDL3 known to your System
In Windows it is sufficient to copy the libraries to specific folders to make them known system-wide. Specifically in Linux, you have to make sure your system adds them to an internal library cache additionally.
Step 1: Verifying Library folder
First let’s verify that the folder /usr/local/lib is known as library folder. Type this in the terminal.
cd/etc/ld.so.conf.dcatlibc.conf
The first line changes into the ld.so.conf.d system folder. The second line prints the content of the file libc.conf to the terminal. If this shows you a list containing /usr/local/lib then the folder is known and everything is alright.
Otherwise, but if a libc.conf file still exists, just add the folder /usr/local/lib to it and save. If not even the libc.conf file exists, please check where the library folders are found for your Linux distribution.
Step 2: Updating Cache and check if Library is known
sudoldconfigldconfig-p
The first line updates the cache. Not much appears on the terminal.
The second line prints out a long list of libraries. Scroll up to libSDL3. There you should see all libraries you just added to the cache like this:
Now you are finished and ready to use SDL3 in Linux.
Have fun. 🙂
CMake is not installed on your System
If you are unsure, try the following lines. The first lines returns the version of cmake, if it is installed on your system. Skip the other lines in this case. If the first line returns an error message, install cmake by the other two commands.
cmake--versionsudoaptupdatesudoaptinstallcmake
Now you should be able to install SDL3 with the steps shown above.
A font represents the style how the letters of a word or sentence appear (e.g. Arial, New Times Roman, and so on). These style informations are saved in so-called font files. There are different file standards how to save these information into a file, but the most important font file standard is the TrueType Font standard, thus the name SDL3_ttf. SDL3_ttf is capable to work with TrueType Fonts. The typical file extension of TrueType font files is “.ttf”.
FreeType 2and HarfBuzz are open source and free software libraries to render text with respect to a TrueType font in high quality.
Texts and Fonts in Applications
In some previous chapters you have read about the creation of textures from image files and how to draw primitives to the renderer. But there is nearly no application out there where you go out without some text messages. Of course, you could print some text to an image and load this image to your application to introduce text to it. That would be very static, what if you like to implement a chat feature to your application where text messages are created dynamically? Or you like the user to insert his name for a highscore list? Or you need to change all texts of the whole application according to a different language. And there are thousands of other circumstances where you need dynamically generated texts.
Not only that they need to be dynamically generated, they need to be different in style, color, size, and so on according to the situation. A chat text appears differently than the caption of a button.
The Solution: SDL3_ttf
Here comes the SDL3_ttf Unit into play. It provides an official extension to SDL3 to render texts by TrueType fonts.
Download the most recent version of the Runtime Binaries (DLL files) of the SDL3_image library for your system (32 bit or 64 bit) as a zip file
extract the zip file
Copy all these files, especially SDL3_ttf.dll and the other library files, to your system folder (system or system32)
Linux:
Download the most recent version of the source code and compile it (until the repos are providing a pre-compiled version to install via the paket manager)
In SDL3_ttf there are several functions to load a text as a surface (details later). Hence we need to convert the surface into a texture as known. Unfortunately there is no native way to create a texture directly by a function provided by SDL3_ttf. Look at the following diagram to understand the way we need to go:
Create a surface with a given text and create a texture from it. This can be rendered as known to the screen.
So according to the diagram the way to go is as follows, if you have a text in mind (left square in diagram), first create a SDL3 Surface by using one of the functions SDL3_ttf provides (lower path to square at the bottom in the diagram). Next you convert this SDL_Surface into a SDL_Texture by the native SDL3 function SDL_CreateTextureFromSurface (go straight from the buttom square to the upper square in the diagram). Finally render the text as you like to the screen.
Let’s jump into the code now .
program SDL_Fonts;uses SDL3, SDL3_ttf;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlSurface1: PSDL_Surface; sdlTexture1: PSDL_Texture; ttfFont: PTTF_Font; sdlColor1: TSDL_Color; sdlRect1: TSDL_FRect;begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;ifnot SDL_CreateWindowAndRenderer('Use SDL3_Fonts', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;// initialization of TrueType font engine and loading of a fontifnot TTF_Init then Halt; ttfFont := TTF_OpenFont('font/stadium.ttf', 96);if ttfFont = nilthen Halt;// font settings TTF_SetFontStyle(ttfFont, TTF_STYLE_ITALIC); TTF_SetFontOutline(ttfFont, 1); TTF_SetFontHinting(ttfFont, TTF_HINTING_NORMAL);// define colors by RGB values sdlColor1.r := 255; sdlColor1.g := 0; sdlColor1.b := 0;// rendering a text to a SDL_Surface sdlSurface1 := TTF_RenderText_Blended_Wrapped(ttfFont, 'Hello'+LineEnding+' World!', 0, sdlColor1, 0);// convert SDL_Surface to SDL_Texture and get dimensions sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1); sdlRect1.x := 30; sdlRect1.y := 100; SDL_GetTextureSize(sdlTexture1, @sdlRect1.w, @sdlRect1.h);// rendering of grey background and the text texture SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer); SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, @sdlRect1); SDL_RenderPresent(sdlRenderer); SDL_Delay(5000);// quit TTF engine and clear memory TTF_CloseFont(ttfFont); TTF_Quit; SDL_DestroySurface(sdlSurface1); SDL_DestroyTexture(sdlTexture1); SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow(sdlWindow1);// quit SDL3 SDL_Quit;end.
The final result should behave like this: The text “Hello World!” appears for five seconds in italics and underlined. The text is red and the background is grey. The following screenshot gives an impression what is to be expected.
Let’s begin with the initial lines of code.
program SDL_Fonts;uses SDL3, SDL3_ttf;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlSurface1: PSDL_Surface; sdlTexture1: PSDL_Texture; ttfFont: PTTF_Font; sdlColor1: TSDL_Color; sdlRect1: TSDL_FRect;begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;ifnot SDL_CreateWindowAndRenderer('Use SDL3_Fonts', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;
The program is called “SDL_Fonts” and we will need unit SDL3_ttf additional to SDL3 to have access to the TrueType font engine.
Then we are preparing a window, renderer, surface and texture variable as known.
Next we find a PTTF_Font variable called “ttfFont” which points at TTF_Font that holds the font’s data. The font’s data itself is usually coming from a font file.
Finally there is a color variable “sdlColor1” of the new type TSDL_Color (more on this later) and a rectangle variable “sdlRect1” of the known type TSDL_Rect.
// initialization of TrueType font engine and loading of a fontifnot TTF_Init then Halt; ttfFont := TTF_OpenFont('font/stadium.ttf', 96);if ttfFont = nilthen Halt;// font settings TTF_SetFontStyle(ttfFont, TTF_STYLE_ITALIC); TTF_SetFontOutline(ttfFont, 1); TTF_SetFontHinting(ttfFont, TTF_HINTING_NORMAL);
To inialize the TrueType font engine the function TTF_Init without any arguments is called. If it returns true, the initialization was successful.
Function TTF_OpenFont(file path and file name, desired point size) returns a pointer to the font data gathered from the font file. If the result is nil, the loading of the font data failed. Most probably something is wrong with the path or file name.
The second argument here is the point size, which asks for an integer value which determines the size of the font. The larger the value, the larger the letters will appear finally (just as known from text editors). Anyway, if you choose too large size the largeste size will be chosen.
Styling the Text in SDL3
By the procedures
TTF_SetFontStyle(font, style),
TTF_SetFontOutline(font, outline)
and
TTF_SetFontHinting(font, hinting)
further shaping of the appearence of the text can be performed. All the procedures need to know as first argument to which font they should be applied, so “ttfFont” in our case. Then the style can be set by the constants shown in the following table, which are kind of self-explanatory. By OR’ing the style constants you can even combine them, e. g. if you need the text to be in italics and underlined.
TTF_STYLE_NORMAL
No application of a certain style
TTF_STYLE_BOLD
Set a bold style
TTF_STYLE_ITALIC
Set letters in italics
TTF_STYLE_UNDERLINE
Have the text underlined
TTF_STYLE_STRIKETHROUGH
Have the text stroken through
The outline is simply set by an integer number. The larger the number, the larger the outline of the letters will appear. If you don’t need an outline, the argument has to be 0.
The hinting is set similar to the style by pre-defined constants shown in the following table. The hinting setting influences the appearance of the letters and text. In general it should lead to sharper letters, anyway, which setting is best for a certain situation and display device may differ. So if you are unsure what setting to use you should choose TTF_HINTING_NORMAL. If you don’t call this procedure this setting is the default anyway.
TTF_HINTING_NORMAL
Normal hinting applies standard grid-fitting.
TTF_HINTING_LIGHT
Light hinting applies subtle adjustments to improve rendering.
TTF_HINTING_MONO
Monochrome hinting adjusts the font for better rendering at lower resolutions.
TTF_HINTING_NONE
No hinting, the font is rendered without any grid-fitting.
TTF_HINTING_LIGHT_SUBPIXEL
Light hinting with subpixel rendering for more precise font edges.
All of these three procedures have counter-functions which return the set style, outline and hinting as integer number (the style and hinting constants above are actually just integer numbers), defined as
TTF_GetFontStyle(font): Style constant as Integer,
TTF_GetFontOutline(font): Integer,
TTF_GetFontHinting(font): Hinting constant as Integer.
Obviously the only parameter to be set is the font whose style, outline or hinting you like to know. These functions aren’t demonstrated in the sample though.
The TSDL_Color record has four fields. For additive color mixing you often have a red share, a green share and a blue share (RGB triple). These three fundamental colors are capable of generating any other color by mixing in the respective proportions. E.g. 100% red and 0% green and 0% blue will lead to red (that is what is done in the code!). But 100% red, 100% green and 0% blue will lead to yellow because of additive mixing. If all three colours are 100% you will get white. If all of them are 0% you will get black. You may notice the variable type cuint8 which means 8bit unsigned integer. From this follows that the values can range between 0 and 255 where 0 equals 0% and 255 equals 100% of the corresponding color. So 2563 = 16,777,216 individual colors can be created.
The a field is for the alpha value, which determines the share of transparency. It is set to opaque (value 255) by default.
Text (Surface) Creation
// rendering a text to a SDL_Surface sdlSurface1 := TTF_RenderText_Blended_Wrapped(ttfFont, 'Hello'+LineEnding+' World!', 0, sdlColor1, 0);
Finally a text, e.g. the famous “Hello World!” with the chosen colors has to be created. There are several functions to do so and just one of them is chosen for demonstration how the creation generally works. The chosen function is
It requires five arguments and returns a SDL3 Surface (no SDL3 Texture!). The first argument is the font you would like to use (it sets the style, outline, size, …). That is “ttfFont” here.
Next is the actual text, obviously, “Hello World”. The text can be directly written as shown in the example but also could come from a variable of type PAnsiChar. Also we placed a LineEndingcharacter in between “Hello” and “World”. This will split the text into two lines at this point. This is possibly only because we used the Wrapped version of the function. The related function TTF_RenderText_Blended() will just ignore the LineEnding character.
The LineEnding character is a native platform-independent control character provided by Free Pascal. Delphi users should use the sLineBreak character instead.
The last argument, the wrap width, is related to this. It is the width in pixels for the text being wrapped (broken into the new line) automatically. This argument and the line ending characters can be combined.
The third argument is the length. Here you can set a length for the text being rendered. Usually you set it to 0, if the text is null-terminated, which it is if you use a PAnsiChar here.
The fourth color argument is setting the color, obviously. We use the “sdlColor1” variable here that we defined before to create a red color.
Text Quality and Rendering Speed
As mentioned before there are more functions to create a SDL3 Surface with text. In general there are four groups of functions according to their properties. The general naming scheme is as follows:
TTF_Render[Text or Glyph]_[suffix]
Those which have the suffix “Solid” are very fast created but their quality is low. Use them when rendering fast changing text (e.g. the score of a pinball simulation). The second group has the suffix “Shaded”. They are slower rendered and have a background color but have much better quality. The third group of functions have the suffix “Blended”. They are of high quality but slow to be rendered. Use them for more static text which doesn’t change a lot. The last group has the suffix “LCD”, if you want sub-pixel rendered text, which may improve quality for LCD screens.
For rendering a single character by its Unicode code(point), use the function which contains “Glyph” as suffix to “TTF_Render”.
As seen in the example code, the TTF_RenderText functions also have a further version where “_Wrapped” is added. Additional to the usual parameters there is a parameter wrapLength of type cint (integer number). Here you can have an integer value which determines the amount of pixels until the text will be text-wrapped. So in our case with a width of the window of 500 pixels the setting of wrapLength to 250 for example would result in a word-wrap when a text would exceed 250 pixels.
Overview: Rendering Modes
The following list summarizes all the functions and most important properties for the three differen rendering modes.
All the functions will return nil on failure to create a SDL3 Surface.
Converting into SDL3 Texture and getting its size
Okay, now let’s proceed to the next lines of code.
// convert SDL_Surface to SDL_Texture and get dimensions sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1); sdlRect1.x := 30; sdlRect1.y := 100; SDL_GetTextureSize(sdlTexture1, @sdlRect1.w, @sdlRect1.h);
In the previous list you saw all the functions to generate SDL3 Surfaces with a certain text. Now we need to transform it into a SDL3 Texture by SDL_CreateTextureFromSurface as known.
Then we prepare a rectangle and set the x and y coordinate to 30 and 100. At this point the new text should be rendered in the window.
The dimensions of the new text texture “sdlTexture1” are different from the windows dimensions. If we would render it directly to the window, it would be stretched to fit the window. Hence we need a way to get the dimensions (the width and height) of the new text texture.
helps us. This function returns the width and height of a texture into the second and third pointer argument. We want the width and height of “sdlTexture1” and want the result be returned into the width and height field of our rectangle variable “sdlRect1”. So we need to use the @-operator here to get the pointer address of the width and height field.
// rendering of grey background and the text texture SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer); SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, @sdlRect1); SDL_RenderPresent(sdlRenderer); SDL_Delay(5000);
Next, we prepare a grey background to be rendered.
Then the text texture “sdlTexture1” is prepared to be rendered to the rectangle “sdlRect1”.
Then the result will be rendered to the window. The rendered result is shown for 5000 ms (5 seconds).
All the allocated memory has to be free’d now. The font is free’d by procedure
TTF_CloseFont(font: PTTF_Font)
and the TrueType engine is quit by procedure
TTF_Quit.
“sdlSurface1” is free’d by procedure
SDL_FreeSurface(surface: PSDL_Surface)
and the surface, texture, renderer and window as known. After that SDL3 is shut down.
Remark: DO NOT create Text Surfaces on the fly!
As for the loading of bitmap files, while it is possible to directly load the generated text surface to the texture without declaring a surface by combining the surface creation with the texture creation as follows:
For text input there is a special way and a special event in place in SDL3.
The SDL_EVENT_TEXT_INPUT Event and PAnsiChar?
The SDL_EVENT_TEXT_INPUT event has the follwing structure:
TSDL_TextInputEvent = record type_: TSDL_EventType; {*< SDL_EVENT_TEXT_INPUT } reserved: cuint32; timestamp: cuint64; {*< In nanoseconds, populated using SDL_GetTicksNS() } windowID: TSDL_WindowID; {*< The window with keyboard focus, if any } text: PAnsiChar; {*< The input text, UTF-8 encoded }end;
You can see that apart from the first four fields, which all events have in common, it has just one further field, called text. It is of the PAnsiChar type. If you are familar with Pascal and its String type for text handling, you may wonder, why in this record the text field uses this unusual type.
SDL3 is written in C/C++, and in these languages pointers to null-terminated character “strings” are used to store and handle text. For better compatibility and easier translation of SDL3 (and other C/C++ projects) the Pascal compiler provides a similar type, PAnsiChar.
In principle it is no big deal to use PAnsiChar instead of String, even though Pascal programmers prefer the much more convenient String variables. In short: Strings in Pascal are always safe to use, whereas C’s strings may cause memory leaks if handled wrongly.
The best practice is to convert a C string as soon as possible into a Pascal string, especially if string manipulations follow.
The Text Field does not contain what is considered a Text!
It may surprise you to read, that this text field does not contain a whole text but just a single (or a bunch of) character(s), which represent a word.
In Western languages (especially English) with Latin characters you always only submit one character at a time by each key stroke and compose words and sentences this way. But even in some Western languages you sometimes must combine a Latin character with an accent (which means two key strokes, but just one character) to get special characters. In many non-Western languages (think of e. g. Asian languages) words and sentences are constructed by highly individual signs. To construct these signs you need many key strokes before they are submitted to the text field which then generates the correct sign or sometimes several signs. (I would be glad to give a more detailed explanation here, please feel free to contact me to improve this paragraph.)
How is Text Input done in SDL3?
This is the code:
program SDL_TextInput;uses SDL3;var sdlWindow1: PSDL_Window; sdlEvent: TSDL_Event; RunLoop: Boolean = True; Text1: AnsiString = '';begin// initilization of event subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// create a window (necessary to receive events) sdlWindow1 := SDL_CreateWindow('SDL3 Text Input', 500, 500, 0);while RunLoop dobeginwhile SDL_PollEvent(@sdlEvent) dobegincase sdlEvent.type_ of// quit program SDL_EVENT_QUIT: RunLoop := False; SDL_EVENT_KEY_DOWN:case sdlEvent.key.key of// switch text input mode on/off by ENTER/RETURN key SDLK_KP_ENTER, SDLK_RETURN:beginWrite('Text Input Mode is ' );if SDL_TextInputActive(sdlWindow1) thenbegin SDL_StopTextInput(sdlWindow1); WriteLn('OFF');endelsebegin SDL_StartTextInput(sdlWindow1); WriteLn('ON');end;end;// remove last character SDLK_BACKSPACE:begin Delete(Text1, Length(Text1), 1); WriteLn( ' --> ' + Text1 );end;end; SDL_EVENT_TEXT_INPUT:beginWrite( 'Text Input: "', sdlEvent.text.text, '"' ); Text1 := Text1 + StrPas(sdlEvent.text.text); WriteLn( ' --> ' + Text1 );end;end;end; SDL_Delay(20);end;// clear memory SDL_DestroyWindow(sdlWindow1);// quit SDL3 SDL_Quit;end.
The result looks like this:
As you see, in this example we have a blank window and just use the text output to your command line. In the Lazarus IDE you can get the raw output window by Ctrl + Alt + O.
program SDL_TextInput;uses SDL3;var sdlWindow1: PSDL_Window; sdlEvent: TSDL_Event; RunLoop: Boolean = True; Text1: AnsiString = '';begin// initilization of event subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// create a window (necessary to receive events) sdlWindow1 := SDL_CreateWindow('SDL3 Text Input', 500, 500, 0);while RunLoop dobeginwhile SDL_PollEvent(@sdlEvent) dobegincase sdlEvent.type_ of
Most of this first code bit is known to you from previous chapters. We have no renderer variable because we do not need to render something.
Instead we have “Text1” variable of the Pascal AnsiString type. In contrast to the classical ShortString, it has no length limit. If you just use String, it depends on your compiler settings if they are treated as AnsiString or ShortString. We want to be clear about this here!
We create a window “sdlWindow1” without a renderer. You may ask yourself, why do we need a window if we do not render something to it. You are right, in principle we wouldn’t need one. We still need it, because the events are connected to a window, without a window we have no event detection.
As known from previous chapter about event handling, we enter two while loops. The first one for the application to run and the second one to gather all the events each cycle.
We in the inner “event loop”, we have three events, two of whom are new, SDL_EVENT_QUIT and SDL_EVENT_TEXT_INPUT.
The SDL_EVENT_QUIT
SDL_EVENT_QUIT is triggered when the application gets the signal to quit, e. g. by clicking on the “X” of a typical application window. We didn’t catch this event in the last chapter, you may have noticed that clicking the “X” didn’t do anything.
SDL_EVENT_TEXT_INPUT Functions
Before getting to the SDL_EVENT_TEXT_INPUT, let’s look at the SDL_EVENT_KEY_DOWN part. If you press the enter or return key, the function SDL_TextInputActive(window) checks, if the text input mechanism is started or not. Obviously, its return value is boolean.
If the text input mechanism is started already, it is stopped by the function SDL_StopTextInput(window). If it is not started, it is started by the function SDL_StartTextInput(window). Only if t he mechanism is started, SDL_EVENT_TEXT_INPUT events are created and can be caught.
This is accompanied by a text which is generated reading “Text Input Mode is ON” or “… OFF” accordingly.
Let proceed to the SDL_EVENT_TEXT_INPUT part: If the the text input mechanism has been started by SDL_StartTextInput(window), we get SDL_EVENT_TEXT_INPUT events as soon as keys are pressed. The new character is stored in:
sdlEvent.text.text
The Pascal function Write() prints out PAnsiChar strings (or rather characters in this case) natively and will show us directly what character has been created.
In line
Text1 := Text1 + StrPas(sdlEvent.text.text);
we concatenate the new character to the “Text1” string. Note how the PAnsiChar character is converted into a native Pascal ShortString by Pascal function StrPas(PAnsiChar string) beforehand. The concatenation will convert it to an AnsiString result in “Text1”.
The resulting text in “Text1” is printed out by the Pascal WriteLn statement.
It is remarkable how easy it is to get even complex characters added to the text without any hassle. Try doing this with just SDL_EVENT_KEY_DOWN events and to make accents possible alone would take a lot of effort.
Removing a Character from the Text
The text input mechanism does not provide a way to remove a character natively. Also other features for convenience, like moving a cursor into the text and inserting a character in between are not supported natively.
As “Text1” is a convenient Pascal string now, we can use Pascal’s string routines to implement all the features we need with little effort. You see, if you press the backspace key with key code SDLK_BACKSPACE, we use Pascal’s Delete(string, index, number of characters) and Length(string) functions to pick the last character of the string an delete it.
Event handling is a major concept in game and application programming. It allows for the user to interact dynamically with your program. Whenever you move your mouse cursor, press or release a key on the keyboard, use a touch screen or maximize/minimize the application window, all these interactions (and many, many more) are recognized as so-called events.
Events in SDL3
Whenever an event occurs in SDL3, for instance a key gets pressed on the keyboard, all the information related to that specific event is stored in a TSDL_Event record. Depending on the event type (e.g. mouse motion, key pressed on a keyboard, maximizing the application window) there is a different event field available in the record.
For example for a mouse motion you can read out the x and y position of the cursor (in the motion field). In contrast there is no sense in having x and y values for a pressed key on the keyboard, but to know which specific key has been pressed on the keyboard (in the key field).
Event Types, Event Fields and Event Structures
There are many more types of events than a mouse motion and a key being pressed on a keyboard. Think of using a joystick, using a touchscreen, minimizing/maximizing the application window, and so forth. There are plenty of different events that could occur.
All these event types have certain names, e.g. the event type which indicates a mouse motion is called SDL_EVENT_MOUSE_MOTION. All names of event types start with SDL_EVENT_, then often follows the device it is related to, e.g. MOUSE, KEYBOARD, GAMEPAD, JOYSTICK, PEN, CAMERA, and finally the actual action, e. g. MOTION, BUTTON_DOWN, ADDED, … .
A few selected examples are described below:
SDL_EVENT_MOUSE_MOTION
created by a mouse motion
field name: motion
provides x- and y-coordinates of mouse position
provides relative distance to last x- and y-coordinate
provides state of the pressed buttons (during motion)
This list is overwhelmingly long but don’t worry, as soon as you get the concept behind events you will easily understand which of these event types will play a role for the applications you like to develop.
Relationship between Event Type, Event Field and Event Structure
The event type determines the available event field, which determines the event structure.
Let’s assume you press a key. This will create an event of type SDL_EVENT_KEY_DOWN. This event provides an event field key which allows access to an event (record) structure.
TSDL_KeyboardEvent = record type_: TSDL_EventType; {*< SDL_EVENT_KEY_DOWN or SDL_EVENT_KEY_UP } reserved: cuint32; timestamp: cuint64; {*< In nanoseconds, populated using SDL_GetTicksNS() } windowID: TSDL_WindowID; {*< The window with keyboard focus, if any } ...end;
The first four fields of the event record structure is the same for all events: their event type, a reserved field, a timestamp when the event was triggered and the window id, hence, in which application window has the event been triggered.
Then follow event specific fields, which are dependent of the specific event as described in the list above. For a keyboard event these may be key codes, for a mouse motion event this may be the mouse position.
Some event types can share the same event field and structure. For example, the event types SDL_EVENT_KEY_DOWN and SDL_EVENT_KEY_UP which are generated by pressing or releasing a key share the same event structure since the information are the same.
Keyboard Event and Mouse Event in Action
Let’s dive into the example code.
program SDL3_EventHandling;uses SDL3;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlEvent: TSDL_Event; sdlPoint: TSDL_FPoint; RunLoop: Boolean = True;begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// create window and rendererifnot SDL_CreateWindowAndRenderer('SDL3 Events', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;// render and show cleared window with grey background color SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer);// set point coordinates at center of window sdlPoint.x := 255; sdlPoint.y := 255;// run the (game) loop until it should stop (= false); triggered by escape or q keywhile RunLoop dobegin// run the event loop until all events in queue have been treatedwhile SDL_PollEvent(@sdlEvent) dobegincase sdlEvent.type_ of SDL_EVENT_KEY_DOWN:begincase sdlEvent.key.key of SDLK_W: sdlPoint.y := sdlPoint.y-1; SDLK_A: sdlPoint.x := sdlPoint.x-1; SDLK_S: sdlPoint.y := sdlPoint.y+1; SDLK_D: sdlPoint.x := sdlPoint.x+1; SDLK_SPACE:begin SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer);end; SDLK_ESCAPE, SDLK_Q: RunLoop := False;end;// set yellow drawing color SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);end; SDL_EVENT_MOUSE_MOTION:begin sdlPoint.x := sdlEvent.motion.x; sdlPoint.y := sdlEvent.motion.y;// set red drawing color SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);end;end;end;// render the point SDL_RenderPoint(sdlRenderer, sdlPoint.x, sdlPoint.y); SDL_RenderPresent(sdlRenderer); SDL_Delay(20);end;// clear memory SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);// quitting SDL3 SDL_Quit;end.
The result depends on your mouse movement in the application window (red dots) and/or the pressed W-, A-, S- or D- keys on the keyboard (yellow dots).
The initial lines of code are:
program SDL3_EventHandling;uses SDL3;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlEvent: TSDL_Event; sdlPoint: TSDL_FPoint; RunLoop: Boolean = True;begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// create window and rendererifnot SDL_CreateWindowAndRenderer('SDL3 Events', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;
We initialize a SDL3 application with a 500 by 500 pixels window as known.
The TSDL_Event variable “sdlEvent” stores the events generated by the user of the application. We use it later to read out the event information of each individual event that gets created.
We also need a variable “sdlPoint” of known type TSDL_FPoint to describe a point with float point coordinates.
Finally there is a simple Pascal boolean variable “RunLoop”, which is initilized to true.
// render and show cleared window with grey background color SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer);// set point coordinates at center of window sdlPoint.x := 255; sdlPoint.y := 255;
Also known from previous chapters, we set up a grey drawing color (RGB: 50, 50, 50) for the background and render it.
Also we set the initial coordinates of the point which is at the center of the window.
// run the (game) loop until it should stop (= false); triggered by escape or q keywhile RunLoop dobegin// run the event loop until all events in queue have been treatedwhile SDL_PollEvent(@sdlEvent) dobegin
We now enter two while loops. The first, outer while loop keeps the application running as long as the “RunLoop” variable is true. The loop is exited as soon as “RunLoop” equals false.
For every application cycle (outer while loop), we enter a second, inner while loop. It runs as long as the SDL3 functionSDL_PollEvent(pointer of TSDL_Event) equals to true. SDL_PollEvent() equals to true as long as there are still events in the event queue.
Every time the SDL_PollEvent() is called, it feeds the event type and the specific event information to the event of the argument and deletes the event from the event queue. In our case, the information is fed to “sdlEvent”. To be precise, the argument does not need the event itself but rather its pointer, so we use the @ operator to get its pointer. That is why we call SDL_PollEvent() by
SDL_PollEvent(@sdlEvent)
as the while loop condition.
The type_ field
Every event provides the type_ field. The event type can be read out from the field type_, hence we check for the type in sdlEvent.type_ by a case-statement.
case sdlEvent.type_ of SDL_EVENT_KEY_DOWN:begincase sdlEvent.key.key of SDLK_W: sdlPoint.y := sdlPoint.y-1; SDLK_A: sdlPoint.x := sdlPoint.x-1; SDLK_S: sdlPoint.y := sdlPoint.y+1; SDLK_D: sdlPoint.x := sdlPoint.x+1; SDLK_SPACE:begin SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer);end; SDLK_ESCAPE, SDLK_Q: RunLoop := False;end;// set yellow drawing color SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);end;
In our example program we distinguish and treat only two event types by a case statement, namely SDL_EVENT_KEY_DOWN and SDL_EVENT_MOUSE_MOTION. Every other possible event type is ignored by our program.
The SDL_EVENT_KEY_DOWN Event
If _type is a SDL_EVENT_KEY_DOWN event, a begin-end block is entered.
We know from this event type, that the event field which has all the event’s information is called key. The event structure you have there is as follows:
SDL_KeyboardEvent = record type_: TSDL_EventType; {*< SDL_EVENT_KEY_DOWN or SDL_EVENT_KEY_UP } reserved: cuint32; timestamp: cuint64; {*< In nanoseconds, populated using SDL_GetTicksNS() } windowID: TSDL_WindowID; {*< The window with keyboard focus, if any } which: TSDL_KeyboardID; {*< The keyboard instance id, or 0 if unknown or virtual } scancode: TSDL_Scancode; {*< SDL physical key code } key: TSDL_Keycode; {*< SDL virtual key code } mod_: TSDL_Keymod; {*< current key modifiers } raw: cuint16; {*< The platform dependent scancode for this event } down: cbool; {*< true if the key is pressed } repeat_: cbool; {*< true if this is a key repeat }end;
Additionally to the already seen first four fields, we have
the keyboard id,
the scan code,
the key code,
pressed key modifiers (shift, ctrl, …),
a raw scan code,
if the key is pressed (if not, it means it got released),
and if the event is triggered by the repeat mechanism, which kicks in if you keep a key pressed for a longer time.
We are interested in the key code of the pressed key, so we read out the information from the event structure’s key field, hence the information is found in
sdlEvent.key.key
Basically a key code is a constant which corresponds to a certain, unique hexadecimal number. For example the constant for the W-key is SDLK_W, its hexadecimal number is 77 (decimal number 119). All key codes’ names start with SDLK_.
With the case statement we look for specific key codes that may have been pressed. The key code names are self-explanatory. We check for the W-, A-, S-, D-. Space-, Escape- and Q-key by the key codes SDLK_W, SDLK_A, SDLK_S, SDLK_D, SDLK_SPACE, SDLK_ESCAPE and SDLK_Q. A list of all SDL3 key codes and their hexadecimal numbers is found in this link.
Depending on the pressed key, we change the position of the point (W: up, A: left, S: down, D: right), clear the window with the background color (space) or set the “RunLoop” variable to false, which effectively stops the program.
At the end of this event’s block the drawing color is changed to yellow.
The SDL_EVENT_MOUSE_MOTION Event
The second case of the case statement is the mouse motion event.
SDL_EVENT_MOUSE_MOTION:begin sdlPoint.x := sdlEvent.motion.x; sdlPoint.y := sdlEvent.motion.y;// set red drawing color SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);end;end;
The event field for a mouse motion is called motion. It’s record structure is shown below:
SDL_MouseMotionEvent = record type_: TSDL_EventType; {*< SDL_EVENT_MOUSE_MOTION } reserved: cuint32; timestamp: cuint64; {*< In nanoseconds, populated using SDL_GetTicksNS() } windowID: TSDL_WindowID; {*< The window with mouse focus, if any } which: TSDL_MouseID; {*< The mouse instance id or SDL_TOUCH_MOUSEID } state: TSDL_MouseButtonFlags; {*< The current button state } x: cfloat; {*< X coordinate, relative to window } y: cfloat; {*< Y coordinate, relative to window } xrel: cfloat; {*< The relative motion in the X direction } yrel: cfloat; {*< The relative motion in the Y direction }end;
If an SDL_EVENT_MOUSE_MOTION event is found, we change the x- and y-values according to the x- and y-values (mouse coordinates) provided by the event.
Finally we change the drawing color to red.
end;end;// render the point SDL_RenderPoint(sdlRenderer, sdlPoint.x, sdlPoint.y); SDL_RenderPresent(sdlRenderer); SDL_Delay(20);end;
The first “end;” closes the case statement for the event types, the second “end;” closes the inner while loop which checks for pending events in the event queue. After that we are back in the outer application-“RunLoop”. Every cycle we render the point once, update the rendered window and delay for 20 milliseconds.
The delay is necessary to prevent the CPU from running as fast as it can, which is kind of short-circuiting the program. You should never do this for your CPU’s sake.
Nothing new here, the renderer and window get destroyed and SDL3 is quit.
Congratulations! Event handling in SDL3 is now known to you.
I will will elaborate on some specifics here, which may or may not be interesting to you. Please skip them if you are not interested right now.
The Repeat-Delay Problem (Keyboard)
The _repeat field let’s you know if the corresponding key on a keyboard is in repeat mode. For most operation systems the repeat mode kicks in after a short delay when you keep a key pressed.
You may try out to open up a simple text editor. If you press any key that has a letter (e.g. “a”-key), in the text editor you will see an “a”. And if you keep the key pressed after a short delay the repeat mode kicks in and rapidly several further a’s are coming up. If you are in repeat mode for a certain key, repeat_ has true boolean value, otherwise false.
Especially for games, you may want to turn off the initial delay if you keep a key pressed and let the constant repeat mode kick in without delay. Let’s assume you have a spaceship which should move left on pressing the “a”-key. – You do not want to have this happening with a delay.
A simple solution to the “repeat-delay”-problem: Instead of looking for the actual event being repeatedly triggered by an key event, use a switch which gets turned on if the key down event is occuring and which is turned off if the key up event is occuring.
Example: Let’s assume the spaceship moving left on pressing the “a”-key again. Instead of using the repeat_ field at all, you make a switch (e.g. MoveSpaceshipLeft := True) if the “a”-key is pressed. As soon as the SDL_EVENT_KEY_UP event is triggered for the “a”-key, the switch is turned off (e.g. MoveSpaceshipLeft := False).
The Difference between Key code and Scan code
You may have noticed that we used key codes in the example code, but there is another field in the keyboard event structure, which reads scan code. Scan codes also represent keys on the keyboard. So, why wouldn’t we use these instead?
The scan code refers to a specific physical location of a key on the keyboard. The scan code is referenced to the typical US keyboard layout (QWERTY layout). The term “QWERTY” just refers to the the first six letters from left to right in the first row with letters on a typical US keyboard. For example: The German keyboard layout (QWERTZ layout) is similar to the US one (for most of the letter keys), though the “Y”-key and the “Z”-key have exactly opposite positions (hence QWERTZ for the German layout in contrast to QWERTY for the US layout). If you press the “Z”-key on a German keyboard, the returned scan code will represent the letter “Y”, since the position of the key (independent of the layout) is equal to the position of the “Y” key on an US keyboard. Scan codes are layout-independent.
The key code refers to the virtual representation of the key according to the keyboard layout. Here you consider the layout, hence key codes are layout-dependent. As discussed before the scan code for the “Z”-key on a German keyboard will return a letter “Y”, since the key has the location of the “Y”-key of an US keyboard. In contrast the key code will correctly return that the “Z”-key has been pressed.
Think of the famous WASD key arrangement (arrangement of the four keys “W”, “A”, “S” and “D”) in the US layout, even if you have a keyboard without any latin letters, you may want to use the four keys which are physically at the WASD location to move a game character forward (“W”), left (“A”), backward (“S”) or right (“D”). The labeling of the keys doesn’t matter in that case and the keys are not used to input some text. That is when you use scan codes.
Text Input is done in SDL3 in another way!
The shown way of handling keyboard events could be used to create kind of a text input mechanism. DO NOT do it this way! SDL3 provides a special and much more convenient event for that.
Use SDL_EVENT_KEY_DOWN to treat the keyboard like a 101-button joystick now. Text input comes from somewhere else.
Key Modifiers: Shift, Ctrl, Alt, …
The _mod field is a 16 bit unsigned integer (corresponds to Pascal’s Word) and represents key modifiers (ctrl, alt, shift, num lock, caps lock, …), which may be pressed while pressing another key. Their names always start with SDL_KMOD_. For example, the left shift key has the name SDL_KMOD_LSHIFT. The full list of possible key modifiers is not very long and found in the link.
If a SDL_EVENT_KEY_DOWN event is caught, you can check the _mod field too see if a key modifiers was pressed at the same time.
Let’s assume you want to have your application to be quit by the user pressing the left or the right ctrl key and “Q”. So you read out the key code for “Q” and check if _mod equals to SDL_KMOD_CTRL.
TSDL_FRect describes a rectangle by four float point values (indicated by the F prefix). 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.
Often functions require an argument of PSDL_FRect type. This is the pointer counterpart to TSDL_FRect. And for convenience there is a double pointer PPSDL_FRect available, which in some cases may be needed.
Using Rectangles for Positioning and Scaling
The following code demonstrates the basic principle how to position and scale a texture simply by using rectangles in SDL3. It assumes you have the fpsdl.bmp image file available from the previous chapter about rendering a bitmap file. (If not, copy it from there.)
program SDL3_Rectangles;uses SDL3;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlSurface1: PSDL_Surface; sdlTexture1: PSDL_Texture; sdlRectangle: TSDL_FRect;begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// create window and rendererifnot SDL_CreateWindowAndRenderer('SDL3 Rectangles', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;// create surface from file sdlSurface1 := SDL_LoadBMP('fpsdl.bmp');if sdlSurface1 = nilthen Halt;// create texture from surface sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1);if sdlTexture1 = nilthen Halt;// set scaling mode SDL_SetTextureScaleMode(sdlTexture1, SDL_SCALEMODE_NEAREST);// prepare rectangle enclosing word "SDL" in fpsdl.bmp sdlRectangle.x := 17; sdlRectangle.y := 104; sdlRectangle.w := 73; sdlRectangle.h := 36;// render "SDL"-part from the texture to the whole window SDL_RenderTexture(sdlRenderer, sdlTexture1, @sdlRectangle, nil);// render the whole texture into the rectangle dimensions SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, @sdlRectangle);// render to window for 2 seconds SDL_RenderPresent(sdlRenderer); SDL_Delay(2000);// clear memory SDL_DestroyTexture(sdlTexture1); SDL_DestroySurface(sdlSurface1); SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);// quitting SDL3 SDL_Quit;end.
In the var clause we declare the known variables for window, renderer, surface and a texture. Also we have a new variable of TSDL_FRect type.
// prepare rectangle enclosing word "SDL" in fpsdl.bmp sdlRectangle.x := 17; sdlRectangle.y := 104; sdlRectangle.w := 73; sdlRectangle.h := 36;
After initializing SDL3 and setting up the window, renderer and texture as known, the rectangle is getting some values. It just encloses the word “SDL” in the original image (see above, black dots).
Scaling in SDL3
Scaling Mode
Right before creating the surface and texture, there is this line in code.
// set scaling mode SDL_SetTextureScaleMode(sdlTexture1, SDL_SCALEMODE_NEAREST);
The SDL_SetTextureScaleMode(texture, scale mode) function sets the render mode. Possible values are
SDL_SCALEMODE_NEAREST
nearest pixel sampling
SDL_SCALEMODE_LINEAR
linear filtering
The difference can be seen in the following image.
Positioning and Scaling SDL3 Textures by using Rectangles
// render "SDL"-part from the texture to the whole window SDL_RenderTexture(sdlRenderer, sdlTexture1, @sdlRectangle, nil);// render the whole texture into the rectangle dimensions SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, @sdlRectangle);
At this point happens the magic that leads to the resulting image. By the way, since the SDL_RenderTexture function requires the rectangle arguments to be of PSDL_Rect, we use the @-operator (pointer operator) here.
// render "SDL"-part from the texture to the whole window SDL_RenderTexture(sdlRenderer, sdlTexture1, @sdlRectangle, nil);
This means, copy the area described by “sdlRectangle” from the source (“sdlTexture1” here) to the whole area (because of the nil value) of the destination, hence the whole window.
Since the window has a width and height of 500 px each, whereas the source rectangle has just a width of 73 px and a height of 36 px, SDL3 automatically scales the image to fit into the larger dimensions of the window.
A Texture Atlas (Spritesheet)
Instead of creating individual textures for every sprite in a game, often one texture atlas is used. A texture atlas is a large texture which contains all textures for the game. When composing a game scene, you just render all the needed textures from the texture atlas by using corresponding rectangles as just seen above. This method is fast and convenient.
Let’s get back to the code.
// render the whole texture into the rectangle dimensions SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, @sdlRectangle);
This means, copy the whole source texture (because of nil value) to the area described by “sdlRectangle”. The source is the 200×200 px “Free Pascal meets SDL” texture, which is squeezed to the 73×36 px rectangle at (x/y)-position (17/104). This is just what you see in the resulting image (above) where the whole image is squeezed into this tiny area.
Movement of Textures (Sprites)
Although not covered directly by this code example, you get the picture how to achieve the impression of movement of textures (sprites). Every game loop cycle you adjust the (x/y) coordinates of the rectangle for the destination to bring the texture about to move.
Clean up
After rendering the two textures as seen above, the program is delayed for 2 seconds, then cleans all objects from memory and quits SDL3. Nothing new here, though.
Image files to use in games and applications often are available in other formats, e.g. JPG or PNG. In contrast to simple bitmap files, which need a significant higher amount of disk space memory, are they memory-compressed and need significant less disk space.
Also it would be desirable to create the SDL3 texture directly by loading from an image file, instead of creating a SDL2 surface in-between as seen in the previous chapter.
The SDL3_image Unit
Here comes the SDL3_image unit into play. It allows:
Creation of SDL3 texture from image files directly
SDL3_image is an official extension of SDL3, which is developed and maintained by the same developers. Therefore, before right jumping into the code we need the corresponding library files.
Download the most recent version of the Runtime Binaries (DLL files) of the SDL3_image library for your system (32 bit or 64 bit)
Copy all these files, especially SDL3_image.dll, to your system folder (system or system32)
Linux:
Download the most recent version of the source code and compile it (until the repos are providing a pre-compiled version to install via the paket manager)
Have a look at the flow diagram which is an extended version of the diagram seen in the chapter about loading of bitmap files. You see it is extended by two function with the prefix IMG instead of SDL, namely IMG_LoadTexture() and IMG_Load(). Both of these functions allow to load image files of all the supported file formats mentioned above. Also you see that IMG_LoadTexture() creates a texture directly from the image file, so we can skip creating a SDL3 surface.
Let’s try the following image files (dimensions: 200 x 200 pixels, formats: bmp, jpg and png) but feel free to use any other image file you like.
BMP FilePNG FileJPG File
Code Example using SDL3_image
program SDL_LoadingDifferentFormats;uses SDL3, SDL3_image;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlTexture1: PSDL_Texture;begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;ifnot SDL_CreateWindowAndRenderer('Use SDL3_image', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;// load image file directly to texture sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'data/fpsdl.png');if sdlTexture1 = nilthen Halt;// render textureifnot SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, nil) then Halt;// render to window for 2 seconds SDL_RenderPresent(sdlRenderer); SDL_Delay(2000);// clear memory SDL_DestroyTexture(sdlTexture1); SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);// closing SDL2 SDL_Quit;end.
This code will create a window of dimensions 500 x 500 pixels which is showing the “Freepascal meets SDL” image for two seconds. After that the window closes automatically. The following screenshot shows the result of the example program.
program SDL_LoadingDifferentFormats;uses SDL3, SDL3_image;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlTexture1: PSDL_Texture;
The first eight line starts a Pascal program as usual. In contrast to the previous chapter, the uses clause is extended by SDL3_image. Again, native SDL3 has no support for loading different image formats, except for BMP image files. And native SDL3 doesn’t allow for loading of BMP image files directly into SDL3 textures, but SDL3 surfaces only.
We need a texture variable to which we can load the image information of an image file. This will be “sdlTexture1” of PSDL_Texture type.
begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;ifnot SDL_CreateWindowAndRenderer('Use SDL3_image', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;
The initialization of SDL3, the window and renderer creation are done as known from previous chapters.
Creation and Rendering of a SDL3 Texture from an Image File
// load image file directly to texture sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'data/fpsdl.png');if sdlTexture1 = nilthen Halt;// render textureifnot SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, nil) then Halt;// render to window for 2 seconds SDL_RenderPresent(sdlRenderer); SDL_Delay(2000);
Now we load the image file to the SDL3 texture we called “sdlTexture1”. The function to do this is IMG_LoadTexture(renderer, path to image file).
This function is provided by SDL3_image. Its prefix is IMG instead of SDL for native SDL3 functions. That function is why we needed to insert SDL2_image in the uses clause. The parameters of this function are a renderer, that is “sdlRenderer” for us, and as a second the absolute or relative path to an image file, for us it is “data/fpsdl.png”, assuming that the “fpsdl.png” image file is in a sub-folder called “data”.
Of course you may use any other directory to store/load the image or even use a different image. The function will recognize the image file’s format automatically, so feel free to load any of the allowed formats. If the loading fails, for instance you gave a wrong path as argument, this function will return nil. Try changing “fpsdl.png” by “fpsdl.jpg” or “fpsdl.bmp” (if these files exist in the data sub-folder).
The loaded SDL3 texture in “sdlTexture1” we can now render as known from previous chapter by SDL_RenderTexture(), followed by SDL_RenderPresent(). SDL_Delay() delays the application by 2 seconds.
As known from previous chapters, we free the memory by destroying the objects in reversed order. After removing the objects from memory, SDL3 has to be quit.
Wow, we finally made it. Congratulations, this chapter is finished :-). The next chapter is waiting though.
Loading of bitmap image files (BMP files) is natively supported by SDL3. The way to go is as follows (from the flow diagram).
The bitmap image file (.bmp) is stored on your hard drive and can simply be loaded by the SDL_LoadBMP(bmp file) function to a SDL3 surface. This SDL3 surface is then transformed into a SDL3 texture by SDL_CreateTextureFromSurface(renderer, surface) function. And finally this texture is rendered by SDL_RenderPresent(renderer).
Let’s start on the left in the diagram. The easiest way to get a bitmap image file (.bmp) 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 (right click, save copy).
SDL_LoadBMP(name of bmp image file) does what you expect, it loads the image file and generates a SDL3 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’.
Step 2: Creating a SDL3 Texture from the SDL3 Surface
The next step is to get a SDL3 texture. That’s achieved as follows.
The function to use is SDL_CreateTextureFromSurface(renderer, surface).
It just does what you expect and transforms the SDL3 surface into a SDL3 texture with the help of the given renderer. But be careful, the SDL3 surface is still there and its memory is not released automatically by this function.
Step 3: Prepare the SDL3 Texture to be Rendered
Before actually rendering the texture, we need to copy it to the rendering target (our window) by SDL_RenderTexture(renderer, texture, source rectangle (texture), destination rectangle (rendering target)).
// render textureifnot SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, nil) 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, here the whole window. Let’s have a closer look at the function.
You see here, that you could use arguments of type PSDL_FRect instead of nil, which basically describes rectangles. This way you could copy parts of the texture to certain parts of the window.
Step 4: The Rendering
And finally the rendering is done by the known SDL_RenderPresent().
Step 5: Destroying Surfaces and Textures
It is important to free the memory occupied by the surface and texture by SDL_DestroySurface(surface) and SDL_DestroyTexture(texture).
This will run without any problem, though SDL_CreateTextureFromSurface() will not free the surface created by SDL_LoadBMP() automatically. And now you have no handle to free this surface. This creates a memory leak.
This chapter treats two very important SDL3 concepts, namely SDL3 surfaces and SDL3 textures.
Briefly: The Basics of Graphics Programming
Loading and the movement of objects 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 green paddle up- and downwards to prevent the bouncing blue ball from getting through to the right side. The game uses two sprites, the blue ball sprite and the green paddle sprite (see left screenshot). The background color is set to black. The left screenshot is how the game appears to the player. The right screenshot demonstrates what happens if each frame is drawn onto each other without clearing it in between. – Now it is clearly visible that the sprites are redrawn again and again with slightly different coordinates, and that is how (game) graphics work (even for the most sophisticated 3d games):
Draw the (new) 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 SDL3 Surface
The SDL3 surface allows you to represent graphic objects like sprites. Every SDL2 surface has a width and height, a pixel format and other properties. The surfaces have the advantage that they are very easy to use and understand conceptually.
SDL3 surfaces are usually represented by a PSDL_Surface handle.
The SDL2 Texture
The SDL3 texture allows you to represent graphic objects just like the SDL3 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 SDL3 Textures to render your graphic objects in a game
then you go for high performance! You can barely manipulate them directly. SDL3 textures are usually represented by a PSDL_Texture handle.
Oftentimes the workflow is to prepare some graphics using SDL3 surfaces, but then it is transformed into a SDL3 texture for rendering in a game.
Three Ways to the PSDL_Texture
So, how to get a PSDL_Texture? In principle there are three ways to create SDL3 textures. For way 2 and 3 the flow diagram may illustrate how it works.
Way 1: From Scratch
You create a SDL3 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 will not be covered at this point.
Way 2: From SDL3 Surface
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.
2) You create a SDL3 surface from an image file first and then you create the SDL3 texture from the SDL3 surface. This way is shown in the diagram but it means two steps: From left to bottom and from bottom to top.
Way 3: Directly from Image File
3) You create a SDL3 texture from an image file directly. This is shown in the diagram, too. This is the simplest way to create a SDL3 texture.
program SDL_DrawingPrimitives;uses SDL3;var i: Integer; sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlRect1: TSDL_FRect; sdlPoints: array[0..499] of TSDL_FPoint;begin//initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt; sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);if sdlWindow1 = nilthen Halt; sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);if sdlRenderer = nilthen Halt;//render and show cleared window with grey background color SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//render and show a red line SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderLine(sdlRenderer, 10, 10, 490, 490); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//render and draw yellow points diagonally with distance between each other SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);for i := 0to47do SDL_RenderPoint(sdlRenderer, 490-i*10, 10+i*10); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//prepare, render and draw a series of random connected black lines Randomize;for i := 0to499dobegin sdlPoints[i].x := Random(500); sdlPoints[i].y := Random(500);end; SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderLines(sdlRenderer, @sdlPoints, 500); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//prepare, render and draw a green rectangle outline at upper-right corner sdlRect1.x := 260; sdlRect1.y := 10; sdlRect1.w := 230; sdlRect1.h := 230; SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, SDL_ALPHA_OPAQUE); SDL_RenderRect(sdlRenderer, @sdlRect1);//render and draw the rectangle with 50% opacity at lower-left corner sdlRect1.x := 10; sdlRect1.y := 260; SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, 128); SDL_RenderFillRect(sdlRenderer, @sdlRect1); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//clean memory SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);//shut down SDL3 SDL_Quit;end.
Wow, that looks like a big load of new functions, but I promise, drawing in SDL3 is very simple. Running the program should result in something like this (it’s alright, nothing went wrong here 😉
Now, let’s have a closer look to the first lines of code.
program SDL_DrawingPrimitives;uses SDL3;var i: Integer; sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlRect1: TSDL_FRect; sdlPoints: array[0..499] of TSDL_FPoint;begin//initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt; sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);if sdlWindow1 = nilthen Halt; sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);if sdlRenderer = nilthen Halt;
The program is called “SDL_DrawingPrimitives” and uses the SDL3 unit. In the var clause we have a counter variable “i” of native Pascal type integer we need later. We need a window and a renderer and call them “sdlWindow1” and “sdlRenderer” as known from the previous chapters. And in the bottom part of the code snippet they are created as known from last chapter and SDL3 is initialized.
The interesting and new part about this code snippet is in the var clause.
About Rectangles and Points in SDL3
Next we declare a variable “sdlRect1” which is of type TSDL_FRect. The same is true for “sdlPoints” which is an array of 500 elements of type TSDL_FPoint.
TSDL_FRect is a record which is used to describe rectangles in SDL3 by four values:
x: x-coordinate of the rectangle
y: y-coordinate of the rectangle
w: width of the rectangle
h: height of the rectangle.
The F indicates that the values are of float point type (Pascal: Single). This allows for sub-pixel precision for coordinates and dimensions.
TSDL_FRect has an older brother, TSDL_Rect, which only allows integer values. Other than that they are identical.
Unsurprisingly, TSDL_FPoint describes a point in SDL3 by just two values:
x: x-coordinate of the point
y: y-coordinate of the point.
By definition, points have no dimension, so that’s it. Again, the F indicates their values being of float point type. And also TSDL_FPoint has an older counter-part TSDL_Point with integer values instead.
Colours and RGB(A) Notation in SDL3
//render and show cleared window with grey background color SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
Now we have something new here: SDL_SetRenderDrawColor(renderer, red value, green value, blue value, alpha value) sets a colour for drawing operations, just as if you choose for a pencil colour to draw something. This function doesn’t draw anything though.
First you need to set the renderer for which this drawing colour is meant. After that you have to set the colours red, green, blue and the alpha value. They can range from 0 to 255 (integer values only). Think in terms of 255 being 100% of that colour and 0 being 0%. As a start let’s neglect the alpha value.
If you like to have a red colour, you need to set the value for the red colour high, e.g. 100%, the maximum value is 255, and since you don’t want to mix in green and blue, they get the minimum value of 0 (hence 0%). Setting up red therefore corresponds to 255/0/0 in terms of r/g/b. For comparison, 0/255/0 leads to green, 0/0/255 to blue, 255/255/255 is white and 0/0/0 is black, and so on. In the example code we used 50/50/50 which leads to a dark grey (mixing up red, green and blue of the same ratio additively). With these three values you can generate every colour possible. For more information on colours in computer graphics, go here.
The Alpha Value
The alpha value determines the transparency. 255 means fully opaque and 0 means full transparency. This is very important for blend effects in computer graphics and will be demonstrated later on in the example. By the way, instead of 255 you could use SDL_ALPHA_OPAQUE.
Setting up a Background in SDL3
The function SDL_RenderClear(renderer) is for clearing the screen with the drawing colour. As argument you just need to tell the renderer. It is simple as that :-). Since we set the drawing colour to dark grey before, the screen will be cleared with that colour.
The cleared screen will be shown for one second by SDL_RenderPresent() and SDL_Delay(). These two procedures are known from the previous chapter.
Drawing Lines and Points in SDL3
//render and show a red line SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderLine(sdlRenderer, 10, 10, 490, 490); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
Now we change the drawing colour by SDL_SetRenderDrawColor() to red and use SDL_RenderDrawLine(renderer, x1, y1, x2, y2) to draw a simple line, where x1, y1, x2 and y2 refers to coordinate pairs, hence (x1/y1) and (x2/y2). The first pair is (10/10), the second pair is (490/490). The red line should be drawn from coordinate (10/10) to coordinate (490/490). But where are these coordinates exactly in the window?
The Coordinate System in SDL3
This rule applies:
The origin from where to place an object is always the left upper corner of your screen or window.
In a SDL3 window (or screen if fullscreen) the cooodinate (0/0) refers to the left upper corner. The diagram below may help to understand this.
The coordinate (10/10) means to start at the point ten pixels to the right and ten pixel to the bottom relative to the origin (0/0). Thus, the coordinate (490/490) for the second point is nearly at the right bottom edge of the window and will lead to a diagonal line across. This diagonal line will be 10 pixels short with respect to the window edges.
After that again we ask to render this line to the screen by SDL_RenderPresent() and wait one second by SDL_Delay().
//render and draw yellow points diagonally with distance between each other SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);for i := 0to47do SDL_RenderPoint(sdlRenderer, 490-i*10, 10+i*10); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
Now we change the colour to yellow and draw some points by the function SDL_RenderDrawPoint(renderer, x, y). It is nearly identical to SDL_RenderDrawLine() but instead of four coordinates you need just two coordinates where the point should be drawn.
I thought it would be nice to have more than just one point to be drawn, so the function is used in a for-loop to draw altogether 48 points. Here we need the counter variable “i”. Maybe you can guess from the code where the points are and how they are arranged, if not, just run the code ;-). Finally the result is rendered to the screen by SDL_RenderPresent() and the program waits one second by SDL_Delay().
Spread Points Randomly using Arrays
//prepare, render and draw a series of random connected black lines Randomize;for i := 0to499dobegin sdlPoints[i].x := Random(500); sdlPoints[i].y := Random(500);end; SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderLines(sdlRenderer, @sdlPoints, 500); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
Randomize is a Free Pascal procedure (from system unit) to initilize the random number generator. Imagine this as shaking the dice.
Free Pascal’s Random(max. number – 1) function generates random numbers between 0 and the number which is used as argument substracted by one. So we generate 500 times a random x and y value between 0 and 499 and save them into the 500 SDL_Point records of the “sdlPoints” array.
Colour black is set by SDL_SetRenderDrawColor().
To draw the lines which connects the points in the array “sdlPoints” we use SDL_RenderDrawLines(renderer, pointer to point array, number of array elements). First you need to set the renderer, then just a pointer to an array of TSDL_FPoint and finally the number of points. The latter should be consistent with the array. So, if your array has 500 elements, the count should be 500 at maximum. Notice how we are not using a loop here to draw all 500 lines by calling a certain draw function 500 times, which is a slow solution. Instead we just pass an array of points once. This way we save a lot of time at runtime, especially if you think of a real application where even more lines have to be drawn. There are similar functions for points, rectangles and filled rectangles. They are not used in the example but it may be interesting to know, so here they are:
SDL_RenderDrawPoints(renderer, pointer to points array, number of array elements)
SDL_RenderDrawRects(renderer, pointer to rectangles array, number of array elements)
SDL_RenderFillRects(renderer, pointer to rectangles array, number of array elements)
As a hint, try replacing SDL_RenderDrawLines by SDL_RenderDrawPoints.
Often, you will have functions in SDL3, where you can use batches of something
Drawing Rectangles in SDL3
//prepare, render and draw a green rectangle outline at upper-right corner sdlRect1.x := 260; sdlRect1.y := 10; sdlRect1.w := 230; sdlRect1.h := 230; SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, SDL_ALPHA_OPAQUE); SDL_RenderRect(sdlRenderer, @sdlRect1);
The drawing colour is set to green by SDL_SetRenderDrawColor() and a rectangle’s outline is drawn by SDL_RenderDrawRect(renderer, pointer of rectangle). Prior to using a rectangle we define it at position (260/10) with 230 pixels width and 230 pixels height.
It requires the renderer, which is “sdlRenderer” for us and a PSDL_Rect , thus we use the @-operator for the declared rectangle of TSDL_FRect type to get its pointer value. Notice, we neither render the result to the screen now nor do we delay here. We want a second rectangle immediately! 🙂
//render and draw the rectangle with 50% opacity at lower-left corner sdlRect1.x := 10; sdlRect1.y := 260; SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, 128); SDL_RenderFillRect(sdlRenderer, @sdlRect1); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
We change the rectangles (x/y) coordinate for the second rectangle to (10/260), but keep its width and height the same. What we are looking for is a filled rectangle that has some transparency. Until now we always used SDL_ALPHA_OPAQUE (opaque) as alpha value. We keep the colour to draw the second rectangle to green by SDL_SetRenderDrawColor(). Notice that the fourth value is 128 (half-transparent) instead of the opaque constant. So everything behind the green rectangle should therefore shine through. To generate a filled rectangle SDL_RenderFillRect(renderer, pointer to rectangle) is used.
The Blend Mode in SDL3
But to be honest, even if you change the alpha value it will be opaque, unless you change the blend mode, which we did by SDL_SetRenderDrawBlendMode(renderer, blend mode). The default settings don’t allow for blending. We need to change this to be able to use the alpha value as desired.
First the renderer for which the blend mode has to be set is chosen. In our case it is “sdlRenderer” again. Then there are seven blend modes available in SDL3 and they determine how the new colours are combined with the already present colours at a certain pixel coordinate. Here is an overview of the four most important ones:
SDL_BLENDMODE_NONE
no blending
dstRGBA = srcRGBA
the final pixel colour is just the new pixel colour
SDL_BLENDMODE_BLEND
alpha blending
dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))
dstA = srcA + (dstA * (1-srcA))
the final pixel colour is an addition of the previous pixel colour the new pixel colour; the ratio is determined by the alpha value
SDL_BLENDMODE_ADD
additive blending
dstRGB = (srcRGB * srcA) + dstRGB
dstA = dstA
the final pixel colour is an addition of the previous pixel colour and the new pixel colour; only the previous pixel colour is affected by the alpha value though
SDL_BLENDMODE_MOD
color modulate
dstRGB = srcRGB * dstRGB
dstA = dstA
the final pixel colour is a multiplication of the previous and the new pixel colour
We are looking for (common) alpha blending, so we use SDL_BLENDMODE_BLEND as argument for the blend mode.
After doing so the result is rendered to the screen by SDL_RenderPresent() and shown for one second by SDL_Delay(). Both rectangles appear at the same time.
//clean memory SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);//shut down SDL3 SDL_Quit;
Finally all the memory reserved for points, the rectangle, the renderer and the window is free’d and SDL3 shut down by SDL_Quit.
Congratulations, you just finished this chapter :-).
Every SDL3 application with graphic output has to have at least one SDL3 window and a SDL3 renderer. The window is the entity that is showing the graphic output and the renderer is the “machine” that is generating the output. The code to set up a window and a renderer is as follows.
program SDL_WindowAndRenderer;uses SDL3;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer;begin//initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// full set up sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);if sdlWindow1 = nilthen Halt; sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);if sdlRenderer = nilthen Halt;// quick set up{ if not SDL_CreateWindowAndRenderer(500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;}// show window with rendered content for 2 seconds SDL_RenderPresent(sdlRenderer); SDL_Delay(2000);// clear memory SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);//closing SDL3 SDL_Quit;end.
Getting a Window with SDL3
With SDL3 you can create as many windows as you like, and each window is adressed by a variable of pointer type PSDL_Window. We just need one window we define in the var clause, 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.
The actual creation of a window in SDL3 is as simple as using the function SDL_CreateWindow(title, width, height, flags). If it fails, sdlWindow1 will be nil afterwards. You should always check this.
In our example the window is titled “Window1”, it has a width and height of 500 pixels respectively. And we don’t want to set a specific flag. This will generate a common window as typical for your operating system at the screen’s center.
Hint for MacOS users: Window creation doesn’t work without an event loop in MacOS in SDL2. Please let me know, if this also applies for SDL3.
SDL3 Window Flags
The SDL3 window flags decide for the properties of the window. Look at the following list of possible flags and you may get an idea what they do. 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 the logical OR (if appropriate).
SDL_WINDOW_FULLSCREEN: fullscreen window at desktop resolution
SDL_WINDOW_OPENGL: window usable with an OpenGL context
SDL_WINDOW_OCCLUDED: window partially or completely obscured by another window
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_MOUSE_GRABBED: window has grabbed mouse focus
SDL_WINDOW_INPUT_FOCUS: window has input focus
SDL_WINDOW_MOUSE_FOCUS: window has mouse focus
SDL_WINDOW_EXTERNAL: window not created by SDL
SDL_WINDOW_MODAL: window is modal
SDL_WINDOW_HIGH_PIXEL_DENSITY: window uses high pixel density back buffer if possible
SDL_WINDOW_MOUSE_CAPTURE: window has mouse captured (unrelated to MOUSE_GRABBED)
SDL_WINDOW_ALWAYS_ON_TOP: window should always be above others
SDL_WINDOW_UTILITY: window should be treated as a utility window, not showing in the task bar and window list
SDL_WINDOW_TOOLTIP: window should be treated as a tooltip and does not get mouse or keyboard focus, requires a parent window
SDL_WINDOW_POPUP_MENU: window should be treated as a popup menu, requires a parent window
SDL_WINDOW_KEYBOARD_GRABBED: window has grabbed keyboard input
SDL_WINDOW_VULKAN: window usable with a Vulkan instance
SDL_WINDOW_METAL: window usable with a Metal instance
SDL_WINDOW_TRANSPARENT: window with transparent buffer
SDL_WINDOW_NOT_FOCUSABLE: window should not be focusable
The Renderer
In computer graphics rendering means the process of synthesizing the final image on your screen from the individual basic data structures, be it some lines, a flat background, a texture, a 3d object, or whatever. Hence we need a renderer to draw some content to the window.
The PSDL_Renderer pointer (which we declared in the var clause) is responsible for this and we call our PSDL_Renderer handle “sdlRenderer”. You could have many different renderers in an application, but oftentimes you use just one.
The creation of a renderer is as simple as one function call of SDL_CreateRenderer(window, name). If it fails, sdlRenderer is nil afterwards. The first argument is the window where the rendered output should be shown. That will be sdlWindow1 in our case.
The second argument should be nil which means SDL3 is choosing the best renderer for you. Otherwise you could specify a renderer by name, but that is not necessary for now.
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. Since the window and renderer parameters are double pointers, you need to use the @ operator to make the window and renderer pointers available to the function.
Just remove the curly brackets and enclose the “full set up” -part in the example code to try it.
The function returns a logical false on failure.
Rendering a SDL3 Scene
The actual rendering is achieved by SDL_RenderPresent(renderer).
Freezing (delaying) a running application in SDL3
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.
// show window with rendered content for 2 seconds SDL_RenderPresent(sdlRenderer); SDL_Delay(2000);
Clean up the Memory in SDL3
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 SDL3, 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.
Let’s jump right into our first SDL3 application. Every SDL3 application needs to include the SDL3 unit (obviously), because it is that unit which provides all the SDL3 functions and types we use for development.
program FirstSteps;uses SDL3;begin//initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) thenExit;{your SDL2 application/game} SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, 'First Steps', 'It works!', nil);//shutting down video subsystem SDL_Quit;end.
Initialization and Quitting of SDL3
The first function we use is called SDL_Init(subsystem flags). It is used to initialize subsystems of SDL3 and must be called always before using any SDL3 features. These subsystems are:
SDL_INIT_AUDIO
SDL_INIT_VIDEO
SDL_INIT_JOYSTICK
SDL_INIT_HAPTIC
SDL_INIT_GAMEPAD
SDL_INIT_EVENTS
SDL_INIT_SENSOR
SDL_INIT_CAMERA
The names are self-explanatory, so if you need video functionality you set SDL_INIT_VIDEO. If you need audio functionality, you set SDL_INIT_AUDIO. You can combine subsystems by using the logical or. For video and audio functionality you use SDL_INIT_VIDEO or SDL_INIT_AUDIO.
By the way, with the exception of the haptic flag, all flags do imply the event system, so you don’t need to load it.
You may have wondered why the SDL_Init function is buried in the if-statement. Most functions in SDL3 return a boolean return value which indicates if its execution was successful. This allows for efficient error checking as seen here. If the SDL_Init is returning true the applications proceeds, if not it would exit immediately.
When quitting your application, use the SDL_Quit function.
A simple message box, because we can!
If you see a message box like this…
… then everything works alright. Otherwise, something is is wrong. Most probably the SDL3 library hasn’t been found.
And here you see one great benefit of SDL3. The message box is generated by exactly one simple call to the SDL_ShowSimpleMessageBox(message box flags, title, message, window) function. To realize the same, not to mention on different platforms in a platform-independent way, is whole other question and takes much greater effort.
SDL3 simplifies your (game) development endeavor by a giant margin in many respects.
For Windows it is as easy as copying the SDL3.dll to your system or system32 folder (try it out). Alternatively you can also just copy the SDL3.dll in the same folder as your SDL3 application resides.
Linux
As SDL3 is very new, it has not been entered the package managers of the Linux distributions yet. This means, you need to compile and install it manually.
Click on “Download ZIP” to download the SDL3 unit.
Extract the files and now they are ready to use.
Step 4: Setup Lazarus Project
If you use the Lazarus IDE for development, do not forget to set the following paths:
In the “Paths” sub-options menu of the “Compiler Options”, you need to set the path in “Other unit files (-Fu)” to the SDL3 units. The setting (“units”) as displayed in the screenshot means that the SDL3 unit files are in a “units” subfolder of your application folder.
For other editors you need to set them accordingly. If you compile from command line, use the compiler flags.
If your SDL3 shared library files are not recognized system wide you may have copied the DLL files to the wrong system folder on Windows. In Linux the way to achieve this is described in the mentioned article.
That’s it, you should be able to run SDL3 applications done in Pascal now. 🙂
The major news is the release of SDL3! Many improvements and new features got implemented! We are working on creating an easy to use binding called SDL3 for Pascal in a community effort. It is Free Pascal and Delphi compatible. sechshelme is working on another set of bindings as well.
Fairtris 2: The Ultimate Challenge got released and has got many impressive improvements! You should definitively look into this game. It is open source and you can learn a lot how to use SDL2 in Pascal.
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)
SDL is the abbreviation of Simple DirectMedia Layer. It is a software library written in C which provides a free, easy and platform-independent access to features needed for developing high performance games and applications. This includes easy access to graphic, sound and input device handling (keyboard, gamepad, mouse, joystick, touch screen, …)
The idea is, you create a program against SDL and it should compile on any supported platform (Linux, Windows, Mac, Android, iOS, Playstation, …) without or with minimal adaptions necessary.
Originally when referring to SDL, SDL version 1.2 was meant. It is the predecessor of SDL2 and modern SDL3. Nowadays, when refering to SDL, it depends on context if you really mean the old SDL 1.2, the successor SDL2 or the modern SDL3.
For the obsolete SDL versions and the modern SDL3 are sets of units available for Free Pascal and other Pascal dialects.