Category Archives: Tutorial

The tutorial category contains all the Free Pascal and SDL tutorial posts. The tutorial may treat different SDL versions, e.g. SDL 1.2 or SDL 2.0.

Rendering Text with Fonts (SDL3_ttf)


Briefly: What’s a Font?

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 2 and 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.

Installing SDL3_ttf

  • Get your files here: SDL3_ttf library releases
  • Windows:
    • 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)
    • Copy these files, especially libSDL3_ttf.so, to your library folder; often /usr/local

The Road to Texts in SDL3

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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then Halt;

  if not SDL_CreateWindowAndRenderer('Use SDL3_Fonts', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;

  // initialization of TrueType font engine and loading of a font
  if not TTF_Init then Halt;
  ttfFont := TTF_OpenFont('font/stadium.ttf', 96);
  if ttfFont = nil then 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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then Halt;

  if not 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 font
  if not TTF_Init then Halt;
  ttfFont := TTF_OpenFont('font/stadium.ttf', 96);
  if ttfFont = nil then 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.

  1. TTF_STYLE_NORMAL
    • No application of a certain style
  2. TTF_STYLE_BOLD
    • Set a bold style
  3. TTF_STYLE_ITALIC
    • Set letters in italics
  4. TTF_STYLE_UNDERLINE
    • Have the text underlined
  5. 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.

  1. TTF_HINTING_NORMAL
    • Normal hinting applies standard grid-fitting.
  2. TTF_HINTING_LIGHT
    • Light hinting applies subtle adjustments to improve rendering.
  3. TTF_HINTING_MONO
    • Monochrome hinting adjusts the font for better rendering at lower resolutions.
  4. TTF_HINTING_NONE
    • No hinting, the font is rendered without any grid-fitting.
  5. 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.

TSDL_Color: Colouring the Text by RGB Triples

The next line of code is waiting for us.

  // define colors by RGB values
  sdlColor1.r := 255; sdlColor1.g := 0; sdlColor1.b := 0;

This line of code represents the color red. Why? – Let’s have a look at the definition of TSDL_Color:

TSDL_Color = record
    r: cuint8;
    g: cuint8;
    b: cuint8;
    a: cuint8;
  end;

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

TTF_RenderText_Blended_Wrapped(font, text, length, color, wrap width): PSDL_Surface

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 LineEnding character 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.

  1. Solid
    • transparency by colorkey (0 pixel)
    • very fast but low quality
    • 8-bit palettized RGB surface
    • Functions
      • TTF_RenderText_Solid(font: PTTF_Font; text: PAnsiChar; length: csize_t; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderText_Solid_Wrapped(font: PTTF_Font; text: PAnsiChar; length: csize_t; fg: TSDL_Color; wrapLength: cint): PSDL_Surface
      • TTF_RenderGlyph_Solid(font: PTTF_Font; ch: cuint32; fg: TSDL_Color): PSDL_Surface
  2. Shaded
    • antialiasing
    • slower than solid rendering, but high quality
    • 8-bit palettized RGB surface
    • Functions
      • TTF_RenderText_Shaded(font: PTTF_Font; text: PAnsiChar; length: csize_t; fg: TSDL_Color; bg: TSDL_Color): PSDL_Surface
      • TTF_RenderText_Shaded_Wrapped(font: PTTF_Font; text: PAnsiChar; length: csize_t; fg: TSDL_Color; bg: TSDL_Color; wrap_width: cint): PSDL_Surface
      • TTF_RenderGlyph_Shaded(font: PTTF_Font; ch: cuint32; fg: TSDL_Color; bg: TSDL_Color): PSDL_Surface
  3. Blended
    • transparency (alpha channel)
    • antialiasing
    • slow but very high quality
    • 32-bit unpalettized (RGBA) surface
    • Functions
      • TTF_RenderText_Blended(font: PTTF_Font; text: PAnsiChar; length: csize_t; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderText_Blended_Wrapped(font: PTTF_Font; text: PAnsiChar; length: csize_t; fg: TSDL_Color; wrap_width: cint): PSDL_Surface
      • TTF_RenderGlyph_Blended(font: PTTF_Font; ch: cuint32; fg: TSDL_Color): PSDL_Surface
  4. LCD
    • sub-pixel rendering
    • slow but very high quality
    • 32-bit unpalettized (RGBA) surface
    • Functions
      • TTF_RenderText_LCD(font: PTTF_Font; text: PAnsiChar; length: csize_t; fg: TSDL_Color; bg: TSDL_Color): PSDL_Surface
      • TTF_RenderText_LCD_Wrapped(font: PTTF_Font; text: PAnsiChar; length: csize_t; fg: TSDL_Color; bg: TSDL_Color; wrap_width: cint): PSDL_Surface
      • TTF_RenderGlyph_LCD(font: PTTF_Font; ch: cuint32; fg: TSDL_Color; bg: TSDL_Color): PSDL_Surface

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.

SDL_GetTextureSize(texture: PSDL_Texture; w: pcfloat; h: pcfloat): cbool

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).

We can proceed to the cleaning process.

  // 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.          

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:

sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, TTF_RenderText_Blended(ttfFont, 'Hello World!', 0, sdlColor1, 0);

Do NOT do this, because you have no handle (the surface pointer) to free the surface memory afterwards.

Have fun working with texts :-).

Previous Chapter | Next Chapter

Text Input in SDL3


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 subsystem
  if not 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 do
  begin
    while SDL_PollEvent(@sdlEvent) do
    begin
      case 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:
              begin
                Write('Text Input Mode is ' );
                if SDL_TextInputActive(sdlWindow1) then
                  begin
                    SDL_StopTextInput(sdlWindow1);
                    WriteLn('OFF');
                  end else
                  begin
                    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:
          begin
            Write( '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 subsystem
  if not 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 do
  begin
    while SDL_PollEvent(@sdlEvent) do
    begin
      case 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.

        // 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:
              begin
                Write('Text Input Mode is ' );
                if SDL_TextInputActive(sdlWindow1) then
                  begin
                    SDL_StopTextInput(sdlWindow1);
                    WriteLn('OFF');
                  end else
                  begin
                    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:
          begin
            Write( 'Text Input: "', sdlEvent.text.text, '"' );
            Text1 := Text1 + StrPas(sdlEvent.text.text);
            WriteLn( ' --> ' + Text1 );
          end;

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.

The resulting text is printed out.

      end;
    end;
    SDL_Delay(20);
  end;

  // clear memory
  SDL_DestroyWindow(sdlWindow1);

  // quit SDL3
  SDL_Quit;

end.                 

The application’s loop speed is reduced again by SDL_Delay().

If the application loop is left, the window and the SDL3 system is destroyed and shut down accordingly.

Great! You learned about the text input now.

Previous Chapter | Next Chapter

[Title image created with https://deepimg.ai; modified; public domain]

Event Handling in SDL3


What are Events in Programming?

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)
  • SDL_EVENT_MOUSE_BUTTON_DOWN / SDL_EVENT_MOUSE_BUTTON_UP
    • created by mouse button press / release
    • field name: button
    • provides the button which has been pressed / released
    • provides click rate (single click, double click, …)
    • provides x- and y-coordinates of mouse position
  • SDL_EVENT_KEY_DOWN, SDL_EVENT_KEY_UP
    • created by a keyboard button press / release
    • field name: key
    • provides key code, scan code and possible key modifiers for the pressed / released button
  • SDL_EVENT_WINDOW_RESIZED, SDL_EVENT_WINDOW_MOVED
    • created when the application window is resized / moved
    • field name: window
  • SDL_EVENT_QUIT
    • created when the application window is quit (e. g. click on “X”)
    • field name: quit

The full list according to the official SDL3 documentation is linked here.

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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then
    Halt;

  // create window and renderer
  if not 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 key
  while RunLoop do
  begin

    // run the event loop until all events in queue have been treated
    while SDL_PollEvent(@sdlEvent) do
    begin
      case sdlEvent.type_ of

        SDL_EVENT_KEY_DOWN:
          begin
            case 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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then
    Halt;

  // create window and renderer
  if not 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 key
  while RunLoop do
  begin

    // run the event loop until all events in queue have been treated
    while SDL_PollEvent(@sdlEvent) do
    begin                                        

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 function SDL_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:
          begin
            case 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.

  // clear memory
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  // quitting SDL3
  SDL_Quit;

end. 

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.

This modified quote from the SDL 1.2 to SDL 2.0 migration guide sums it up excellently:

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.

Previous Chapter | Next Chapter

[Title image created with https://deepimg.ai; public domain]

Rectangles: Texture Positioning and Scaling in SDL3


Screens, images, texture and sprites are rectangular, so this shape has a special importance to SDL3 and graphics programming in particular.

Rectangles: TSDL_FRect, PSDL_FRect and PPSDL_FRect

  PPSDL_FRect = ^PSDL_FRect;
  PSDL_FRect = ^TSDL_FRect;
  TSDL_FRect = record
    x: cfloat;
    y: cfloat;
    w: cfloat;
    h: cfloat;
  end;

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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then
    Halt;

  // create window and renderer
  if not SDL_CreateWindowAndRenderer('SDL3 Rectangles', 500, 500, 0, @sdlWindow1, @sdlRenderer) 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;

  // 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.

We will get this as a result.

Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
The result of the example program (500×500 px window). A part of the full image is stretched in the background while the full image is also squeezed into a small area.

For comparison, the original 200×200 px image (fpsdl.bmp) looks like this:

Original image (200×200 px) with a marked area which represents the used rectangle dimensions.

The dotted black part marks the rectangle we used in the code.

Let’s dissect the code.

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlSurface1: PSDL_Surface;
  sdlTexture1: PSDL_Texture;
  sdlRectangle: TSDL_FRect;

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

  1. SDL_SCALEMODE_NEAREST
    • nearest pixel sampling
  2. 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.

Previous Chapter | Next Chapter

[Title image created with https://flatai.org; public domain]

SDL3 Image Formats Feature Image

Rendering Image Files with any format (SDL3_image)


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:

  • Loading of many common image formats
  • Supported formats: AVIF, ICO, CUR, BMP, GIF, JPG, JXL, LBM, PCX, PNG, PNM, SVG, QOI, TIF, XCF, XPM, XV, WEBP
  • 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.

Installing SDL3_image

  • Get you files here: SDL3_image library releases
  • Windows:
    • 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)
    • Copy these files, especially libSDL3_image.so, to your library folder; often /usr/local

IMG_Load and IMG_LoadTexture

SDL2 Texture and Surface relation diagram
Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
An image file (left) can be loaded as a surface or a texture and then be rendered to the screen.

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.

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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then Halt;

  if not 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 = nil then Halt;

  // render texture
  if not 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.

Now let’s get into the code step by step.

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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then Halt;

  if not 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 = nil then Halt;

  // render texture
  if not 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.

Clean up the Memory in SDL3

  // clear memory
  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  // closing SDL2
  SDL_Quit;

end. 

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.

Previous Chapter | Next Chapter

[Title image created with https://flatai.org; public domain]

SDL3 Rendering Bitmap File Feature Image

Rendering a Bitmap File with SDL3


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).

Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
Flow diagram of the loading and rendering of a bitmap file in SDL3.

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).

Free Pascal meets SDL sample image bmp format
Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
The original image is 200×200 pixels wide.

And now let’s see how it is done in code.

program SDL_LoadingRenderingBMP;

uses SDL3;

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlSurface1: PSDL_Surface;
  sdlTexture1: PSDL_Texture;

begin

  // initilization of video subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then
    Halt;

  // create window and renderer
  if not SDL_CreateWindowAndRenderer('Rendering BMP File', 500, 500, 0, @sdlWindow1, @sdlRenderer) 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 not SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, nil) then
    Halt;

  // render to window for 2 seconds
  if not SDL_RenderPresent(sdlRenderer) then
    Halt;
  SDL_Delay(2000);

  // clear memory
  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroySurface(sdlSurface1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  // closing SDL2
  SDL_Quit;

end.                                                                   

The result is this:

Result screenshot for chapter 4
Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
This is the result of the code.

The var clause,

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, respectively.

SDL3 is initialized and the window and renderer are set up as known.

Step 1: Loading the BMP file to a SDL3 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 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 SDL2 Texture from the SDL2 Surface

The next step is to get a SDL3 texture. That’s achieved 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 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 SDL2 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 texture
  if not 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).

  // clear memory
  SDL_DestroyTexture(sdlTexture1);
  SDL_FreeSurface(sdlSurface1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

Now you know how to load and render a bmp file to a window. 🙂

Remark: DO NEVER combine SDL_CreateTextureFromSurface() and SDL_LoadIMG!

Do never combine step 1 and step 2 to avoid declaring and freeing a surface. DO NEVER do this:

sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, SDL_LoadBMP('fpsdl.bmp'));

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. 

Previous Chapter | Next Chapter

SDL3 Surfaces and Textures Feature Image

SDL3 Surfaces and SDL3 Textures


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: ball and paddle erased between each frame; right: ball and paddle not erased between frames
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):

  1. Draw the (new) frame
  2. Show the frame (in a window on screen)
  3. 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.

  1. All images (photo images, drawings, sprites for 2d games, textures for 3d games) are stored on your harddrive somewhere.
  2. 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.
  3. 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.

Previous Chapter | Next Chapter

SDL3 Primitives Feature Image

Drawing Primitives


For many applications and games drawing operations are essential. The most famous case is, if you create your own graphical user interface (GUI). This means there are buttons (and other GUI elements) of dynamic dimensions and coloring in your application. Explanation of another important case, especially in game development, can be read about here.

Supported Primitives: Points, Lines, Rectangles

SDL3 supports natively the drawing of

  • Points
  • Lines
  • Rectangles (outlines)
  • Filled Rectangles.

Drawing in SDL3

Now lets jump right into the 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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then Halt;

  sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);
  if sdlWindow1 = nil then Halt;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);
  if sdlRenderer = nil then 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 := 0 to 47 do
    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 := 0 to 499 do
  begin
    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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then Halt;

  sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);
  if sdlWindow1 = nil then Halt;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);
  if sdlRenderer = nil then 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.

Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
The window is placed with respect to the left upper corner of the screen.

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 := 0 to 47 do
    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 := 0 to 499 do
  begin
    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:

  1. SDL_BLENDMODE_NONE
    • no blending
    • dstRGBA = srcRGBA
    • the final pixel colour is just the new pixel colour
  2. 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
  3. 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
  4. 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 :-).

Previous Chapter | Next Chapter

Title Window SDL3

Window and Renderer in SDL3


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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then Halt;

  // full set up
  sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);
  if sdlWindow1 = nil then Halt;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);
  if sdlRenderer = nil then 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.

Creation of a Window in SDL3

  sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);
  if sdlWindow1 = nil then Halt;

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.

Creation of a Renderer in SDL3

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);
  if sdlRenderer = nil then Halt;

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.

Quick Creation of a Window and a Renderer

  if not SDL_CreateWindowAndRenderer(500, 500, 0, @sdlWindow1, @sdlRenderer)
    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. 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.

  // clear memory
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);
  
  //closing SDL3
  SDL_Quit;

Finally quit SDL3.

That’s it. And now things are going to get really interesting :-).

Previous Chapter | Next Chapter

First Steps Feature Image SDL3

First Steps with SDL3


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 subsystem
  if not SDL_Init(SDL_INIT_VIDEO) then Exit;

    {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.

Previous Chapter | Next Chapter

SDL3 Install Chapter Feature Image

Installation of SDL3 (Linux and Windows)


Step 1: Get the SDL3 Library to your system

Step 2: Install SDL3

Windows

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 distributions yet. This means, you need to compile and install it manually. If SDL3 is shipped with the distribution’s package managers you install it automatically this much more convenient way.

For the compilation you simply go in the extracted folder in the Terminal and run these three commands one after another:

cmake -S . -B build
cmake --build build
sudo cmake --install build --prefix /usr/local

The first try you may be informed you need to install some dependencies to run cmake. You should install them.

If you are successful, you end up with a “build” folder in which you find libSDL3.so and further files. Also a copy is found in your system’s /usr/local folder. So your system is ready to use SDL3.

Step 3: Get the SDL3 unit for Pascal

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 may vary depending on you distribution. Assuming you have an Ubuntu/Mint system, you may try:

ldconfig -n /usr/local/libSDL3.so
sudo ldconfig

That’s it, you should be able to run SDL3 applications done in Pascal now. 🙂

Next Chapter

Error Handling

Last updated on March 5th, 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:

  1. Create a renderer and a window
  2. 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.

← previous Chapter | next Chapter →

Keyboard State and Key States

Last updated on February 17th, 2024

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.

Keyboard state demo.
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.

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:

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.

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.

previous Chapter | next Chapter


Rectangles, Image Movement and Scaling

Last updated on November 20th, 2021

Screens and Images are rectangular, so this shape has a special importance to SDL2 and graphics programming in particular.

Rectangles: TSDL_Rect and PSDL_Rect in SDL 2.0

Often functions require an argument of PSDL_Rect type. This is the pointer counterpart to TSDL_Rect. It is declared as follows.

PSDL_Rect = ^TSDL_Rect;
TSDL_Rect = record
  x,y: SInt32;
  w,h: SInt32;
end;

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.

We will get this as a result.

Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
The result of the example program (500×500 px window). A part of the full image is stretched in the background while the full image is also squeezed into an area above the word “Free”.

For comparison, here is the original 200×200 px image.

Free Pascal meets SDL sample image bmp format
Original image (200×200 px).

Let’s disect the code.

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlTexture1: PSDL_Texture;
  sdlRectangle: TSDL_Rect;

In the var clause we declare the known variables for window, renderer and a texture. Also we have a new variable of TSDL_Rect type.

  // prepare rectangle
  sdlRectangle.x := 12;
  sdlRectangle.y := 25;
  sdlRectangle.w := 178;
  sdlRectangle.h := 116;

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

  1. nearest or 0
    • nearest pixel sampling
  2. linear or 1
    •  linear filtering
    • support by OpenGL and Direct3D
  3. 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.

The anisotropic filter doesn’t do anything for me, even if I used Direct3D.

Scaling by using Rectangles

  // render texture
  SDL_RenderCopy(sdlRenderer, sdlTexture1, @sdlRectangle, nil);
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, @sdlRectangle);

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.

  SDL_RenderCopy(sdlRenderer, sdlTexture1, @sdlRectangle, nil);

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.

  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, @sdlRectangle);

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.

← previous Chapter | next Chapter →

Loading and Rendering a Bitmap File

Last updated on November 20th, 2021

Loading of bitmap image files (BMP files) is natively supported by SDL2. The way to go is as follows (from the flow diagram).

 

Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
Flow diagram of the loading and rendering of a bitmap file in SDL2.

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.

Free Pascal meets SDL sample image bmp format
Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
The original image is 200×200 pixels wide.

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.                                                                   

The result is this:

Result screenshot for chapter 4
Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
This is the result of the code.

The var clause,

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)

SDL_CreateTextureFromSurface(renderer: PSDL_Renderer; surface: PSDL_Surface): PSDL_Texture

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.

SDL_RenderCopy(renderer: PSDL_Renderer; texture: PSDL_Texture; srcrect: PSDL_Rect; dstrect: PSDL_Rect): SInt32

You see here, that you could use arguments of type PSDL_Rect, which basically describes rectangles.

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_FreeSurface(surface) and SDL_DestroyTexture(texture) right after .

  // clear memory
  SDL_DestroyTexture(sdlTexture1);
  SDL_FreeSurface(sdlSurface1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

Now you know how to load and render a bmp file to a window. 🙂

Remark: DO NEVER combine SDL_CreateTextureFromSurface() and SDL_LoadIMG!

Do never combine step 1 and step 2 to avoid declaring and freeing a surface. DO NEVER do this:

sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, SDL_LoadBMP('fpsdl.bmp'));

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. 

← previous Chapter | next Chapter →