Image files to use in games and applications often are available in other formats, e.g. JPG or PNG. In contrast to simple bitmap files, which need a significant higher amount of disk space memory, are they memory-compressed and need significant less disk space.
Also it would be desirable to create the SDL3 texture directly by loading from an image file, instead of creating a SDL2 surface in-between as seen in the previous chapter.
The SDL3_image Unit
Here comes the SDL3_image unit into play. It allows:
Creation of SDL3 texture from image files directly
SDL3_image is an official extension of SDL3, which is developed and maintained by the same developers. Therefore, before right jumping into the code we need the corresponding library files.
Download the most recent version of the Runtime Binaries (DLL files) of the SDL3_image library for your system (32 bit or 64 bit)
Copy all these files, especially SDL3_image.dll, to your system folder (system or system32)
Linux:
Download the most recent version of the source code and compile it (until the repos are providing a pre-compiled version to install via the paket manager)
Copy these files, especially libSDL3_image.so, to your library folder; often /usr/local
Have a look at the flow diagram which is an extended version of the diagram seen in the chapter about loading of bitmap files. You see it is extended by two function with the prefix IMG instead of SDL, namely IMG_LoadTexture() and IMG_Load(). Both of these functions allow to load image files of all the supported file formats mentioned above. Also you see that IMG_LoadTexture() creates a texture directly from the image file, so we can skip creating a SDL3 surface.
Let’s try the following image files (dimensions: 200 x 200 pixels, formats: bmp, jpg and png) but feel free to use any other image file you like.
BMP FilePNG FileJPG File
Code Example using SDL3_image
program SDL_LoadingDifferentFormats;uses SDL3, SDL3_image;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlTexture1: PSDL_Texture;begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;ifnot SDL_CreateWindowAndRenderer('Use SDL3_image', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;// load image file directly to texture sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'data/fpsdl.png');if sdlTexture1 = nilthen Halt;// render textureifnot SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, nil) then Halt;// render to window for 2 seconds SDL_RenderPresent(sdlRenderer); SDL_Delay(2000);// clear memory SDL_DestroyTexture(sdlTexture1); SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);// closing SDL2 SDL_Quit;end.
This code will create a window of dimensions 500 x 500 pixels which is showing the “Freepascal meets SDL” image for two seconds. After that the window closes automatically. The following screenshot shows the result of the example program.
program SDL_LoadingDifferentFormats;uses SDL3, SDL3_image;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlTexture1: PSDL_Texture;
The first eight line starts a Pascal program as usual. In contrast to the previous chapter, the uses clause is extended by SDL3_image. Again, native SDL3 has no support for loading different image formats, except for BMP image files. And native SDL3 doesn’t allow for loading of BMP image files directly into SDL3 textures, but SDL3 surfaces only.
We need a texture variable to which we can load the image information of an image file. This will be “sdlTexture1” of PSDL_Texture type.
begin// initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;ifnot SDL_CreateWindowAndRenderer('Use SDL3_image', 500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;
The initialization of SDL3, the window and renderer creation are done as known from previous chapters.
Creation and Rendering of a SDL3 Texture from an Image File
// load image file directly to texture sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'data/fpsdl.png');if sdlTexture1 = nilthen Halt;// render textureifnot SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, nil) then Halt;// render to window for 2 seconds SDL_RenderPresent(sdlRenderer); SDL_Delay(2000);
Now we load the image file to the SDL3 texture we called “sdlTexture1”. The function to do this is IMG_LoadTexture(renderer, path to image file).
This function is provided by SDL3_image. Its prefix is IMG instead of SDL for native SDL3 functions. That function is why we needed to insert SDL2_image in the uses clause. The parameters of this function are a renderer, that is “sdlRenderer” for us, and as a second the absolute or relative path to an image file, for us it is “data/fpsdl.png”, assuming that the “fpsdl.png” image file is in a sub-folder called “data”.
Of course you may use any other directory to store/load the image or even use a different image. The function will recognize the image file’s format automatically, so feel free to load any of the allowed formats. If the loading fails, for instance you gave a wrong path as argument, this function will return nil. Try changing “fpsdl.png” by “fpsdl.jpg” or “fpsdl.bmp” (if these files exist in the data sub-folder).
The loaded SDL3 texture in “sdlTexture1” we can now render as known from previous chapter by SDL_RenderTexture(), followed by SDL_RenderPresent(). SDL_Delay() delays the application by 2 seconds.
As known from previous chapters, we free the memory by destroying the objects in reversed order. After removing the objects from memory, SDL3 has to be quit.
Wow, we finally made it. Congratulations, this chapter is finished :-). The next chapter is waiting though.
Loading of bitmap image files (BMP files) is natively supported by SDL3. The way to go is as follows (from the flow diagram).
The bitmap image file (.bmp) is stored on your hard drive and can simply be loaded by the SDL_LoadBMP(bmp file) function to a SDL3 surface. This SDL3 surface is then transformed into a SDL3 texture by SDL_CreateTextureFromSurface(renderer, surface) function. And finally this texture is rendered by SDL_RenderPresent(renderer).
Let’s start on the left in the diagram. The easiest way to get a bitmap image file (.bmp) for a game or application ready for usage is to create one in a drawing application. Or use the example bitmap “fpsdl.bmp” we used in the code (right click, save copy).
SDL_LoadBMP(name of bmp image file) does what you expect, it loads the image file and generates a SDL3 surface from it. Attention though, if you just give a file name, it is assumed that the file is found in the same folder as the executing application. Optionally you can also give a full file path, e.g. in Windows something like ‘C:\MyImages\fpsdl.bmp’.
Step 2: Creating a SDL2 Texture from the SDL2 Surface
The next step is to get a SDL3 texture. That’s achieved as follows.
The function to use is SDL_CreateTextureFromSurface(renderer, surface).
It just does what you expect and transforms the SDL3 surface into a SDL3 texture with the help of the given renderer. But be careful, the SDL3 surface is still there and its memory is not released automatically by this function.
Step 3: Prepare the 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 textureifnot SDL_RenderTexture(sdlRenderer, sdlTexture1, nil, nil) then Halt;
So the texture is copied to the rendering target (which is the window). The first nil argument means that we want to copy the whole rectangle. The second nil means that we want to copy to the whole dimensions of the rendering target, here the whole window. Let’s have a closer look at the function.
You see here, that you could use arguments of type PSDL_FRect instead of nil, which basically describes rectangles. This way you could copy parts of the texture to certain parts of the window.
Step 4: The Rendering
And finally the rendering is done by the known SDL_RenderPresent().
Step 5: Destroying Surfaces and Textures
It is important to free the memory occupied by the surface and texture by SDL_DestroySurface(surface) and SDL_DestroyTexture(texture).
This will run without any problem, though SDL_CreateTextureFromSurface() will not free the surface created by SDL_LoadBMP() automatically. And now you have no handle to free this surface. This creates a memory leak.
This chapter treats two very important SDL3 concepts, namely SDL3 surfaces and SDL3 textures.
Briefly: The Basics of Graphics Programming
Loading and the movement of objects in a game (or other applications) is a major concept in (game) programming. These images are then refered to as sprites, usually. Let’s have a look at a simple example:
Left: Window is cleared between each new drawn frame. Right: Window is not cleared.
Here are two screenshots from a simple game. The player has to move the green paddle up- and downwards to prevent the bouncing blue ball from getting through to the right side. The game uses two sprites, the blue ball sprite and the green paddle sprite (see left screenshot). The background color is set to black. The left screenshot is how the game appears to the player. The right screenshot demonstrates what happens if each frame is drawn onto each other without clearing it in between. – Now it is clearly visible that the sprites are redrawn again and again with slightly different coordinates, and that is how (game) graphics work (even for the most sophisticated 3d games):
Draw the (new) frame
Show the frame (in a window on screen)
Clear the frame (and go back to step 1)
Briefly: The Relation between Graphic Objects (e.g. Sprites) and Hardware
Actually there are just three locations where these images are stored in your computer system.
All images (photo images, drawings, sprites for 2d games, textures for 3d games) are stored on your harddrive somewhere.
If you start a photo viewer, a paint program, a 2d game or a 3d game, in all cases the corresponding images need to be loaded from your harddrive to RAM (Random-Access Memory) since displaying and manipulation (e.g. rotation of a photo image by 90°) of images loaded to RAM is much, much faster.
Especially for games a fast access to the image data is highly important! And finally there isn’t just one RAM but two, a CPU controlled one located on the motherboard used by every program/application that needs some RAM. The second RAM is located right at your graphic board and controlled by the so-called GPU (graphics processing unit). This is what we want to use if we develop games since it is dedicated, optimized and just hungry for tasks related to fast image processing.
Many games and applications do not only target at common computer systems, but for mobile devices, e.g. smart phones. The principles described are also true for these devices even though there may be differences in detail.
The SDL3 Surface
The SDL3 surface allows you to represent graphic objects like sprites. Every SDL2 surface has a width and height, a pixel format and other properties. The surfaces have the advantage that they are very easy to use and understand conceptually.
SDL3 surfaces are usually represented by a PSDL_Surface handle.
The SDL2 Texture
The SDL3 texture allows you to represent graphic objects just like the SDL3 surface does, although there is a major difference: It is hardware accalerated. So the graphic object is stored in the graphic board’s RAM and any manipulation is done by the graphic board’s GPU.
So as a rule,
always use SDL3 Textures to render your graphic objects in a game
then you go for high performance! You can barely manipulate them directly. SDL3 textures are usually represented by a PSDL_Texture handle.
Oftentimes the workflow is to prepare some graphics using SDL3 surfaces, but then it is transformed into a SDL3 texture for rendering in a game.
Three Ways to the PSDL_Texture
So, how to get a PSDL_Texture? In principle there are three ways to create SDL3 textures. For way 2 and 3 the flow diagram may illustrate how it works.
Way 1: From Scratch
You create a SDL3 texture from scratch, so you set a pixel format and texture access format and have to fill in your texture data manually. This is the most sophisticated way and will not be covered at this point.
Way 2: From SDL3 Surface
Way 2: The path from the file to the surface, to the texture and to the screen. Way 3: The path rom the file to the texture and to the screen.
2) You create a SDL3 surface from an image file first and then you create the SDL3 texture from the SDL3 surface. This way is shown in the diagram but it means two steps: From left to bottom and from bottom to top.
Way 3: Directly from Image File
3) You create a SDL3 texture from an image file directly. This is shown in the diagram, too. This is the simplest way to create a SDL3 texture.
program SDL_DrawingPrimitives;uses SDL3;var i: Integer; sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlRect1: TSDL_FRect; sdlPoints: array[0..499] of TSDL_FPoint;begin//initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt; sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);if sdlWindow1 = nilthen Halt; sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);if sdlRenderer = nilthen Halt;//render and show cleared window with grey background color SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//render and show a red line SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderLine(sdlRenderer, 10, 10, 490, 490); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//render and draw yellow points diagonally with distance between each other SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);for i := 0to47do SDL_RenderPoint(sdlRenderer, 490-i*10, 10+i*10); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//prepare, render and draw a series of random connected black lines Randomize;for i := 0to499dobegin sdlPoints[i].x := Random(500); sdlPoints[i].y := Random(500);end; SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderLines(sdlRenderer, @sdlPoints, 500); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//prepare, render and draw a green rectangle outline at upper-right corner sdlRect1.x := 260; sdlRect1.y := 10; sdlRect1.w := 230; sdlRect1.h := 230; SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, SDL_ALPHA_OPAQUE); SDL_RenderRect(sdlRenderer, @sdlRect1);//render and draw the rectangle with 50% opacity at lower-left corner sdlRect1.x := 10; sdlRect1.y := 260; SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, 128); SDL_RenderFillRect(sdlRenderer, @sdlRect1); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);//clean memory SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);//shut down SDL3 SDL_Quit;end.
Wow, that looks like a big load of new functions, but I promise, drawing in SDL3 is very simple. Running the program should result in something like this (it’s alright, nothing went wrong here 😉
Now, let’s have a closer look to the first lines of code.
program SDL_DrawingPrimitives;uses SDL3;var i: Integer; sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer; sdlRect1: TSDL_FRect; sdlPoints: array[0..499] of TSDL_FPoint;begin//initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt; sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);if sdlWindow1 = nilthen Halt; sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);if sdlRenderer = nilthen Halt;
The program is called “SDL_DrawingPrimitives” and uses the SDL3 unit. In the var clause we have a counter variable “i” of native Pascal type integer we need later. We need a window and a renderer and call them “sdlWindow1” and “sdlRenderer” as known from the previous chapters. And in the bottom part of the code snippet they are created as known from last chapter and SDL3 is initialized.
The interesting and new part about this code snippet is in the var clause.
About Rectangles and Points in SDL3
Next we declare a variable “sdlRect1” which is of type TSDL_FRect. The same is true for “sdlPoints” which is an array of 500 elements of type TSDL_FPoint.
TSDL_FRect is a record which is used to describe rectangles in SDL3 by four values:
x: x-coordinate of the rectangle
y: y-coordinate of the rectangle
w: width of the rectangle
h: height of the rectangle.
The F indicates that the values are of float point type (Pascal: Single). This allows for sub-pixel precision for coordinates and dimensions.
TSDL_FRect has an older brother, TSDL_Rect, which only allows integer values. Other than that they are identical.
Unsurprisingly, TSDL_FPoint describes a point in SDL3 by just two values:
x: x-coordinate of the point
y: y-coordinate of the point.
By definition, points have no dimension, so that’s it. Again, the F indicates their values being of float point type. And also TSDL_FPoint has an older counter-part TSDL_Point with integer values instead.
Colours and RGB(A) Notation in SDL3
//render and show cleared window with grey background color SDL_SetRenderDrawColor(sdlRenderer, 50, 50, 50, SDL_ALPHA_OPAQUE); SDL_RenderClear(sdlRenderer); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
Now we have something new here: SDL_SetRenderDrawColor(renderer, red value, green value, blue value, alpha value) sets a colour for drawing operations, just as if you choose for a pencil colour to draw something. This function doesn’t draw anything though.
First you need to set the renderer for which this drawing colour is meant. After that you have to set the colours red, green, blue and the alpha value. They can range from 0 to 255 (integer values only). Think in terms of 255 being 100% of that colour and 0 being 0%. As a start let’s neglect the alpha value.
If you like to have a red colour, you need to set the value for the red colour high, e.g. 100%, the maximum value is 255, and since you don’t want to mix in green and blue, they get the minimum value of 0 (hence 0%). Setting up red therefore corresponds to 255/0/0 in terms of r/g/b. For comparison, 0/255/0 leads to green, 0/0/255 to blue, 255/255/255 is white and 0/0/0 is black, and so on. In the example code we used 50/50/50 which leads to a dark grey (mixing up red, green and blue of the same ratio additively). With these three values you can generate every colour possible. For more information on colours in computer graphics, go here.
The Alpha Value
The alpha value determines the transparency. 255 means fully opaque and 0 means full transparency. This is very important for blend effects in computer graphics and will be demonstrated later on in the example. By the way, instead of 255 you could use SDL_ALPHA_OPAQUE.
Setting up a Background in SDL3
The function SDL_RenderClear(renderer) is for clearing the screen with the drawing colour. As argument you just need to tell the renderer. It is simple as that :-). Since we set the drawing colour to dark grey before, the screen will be cleared with that colour.
The cleared screen will be shown for one second by SDL_RenderPresent() and SDL_Delay(). These two procedures are known from the previous chapter.
Drawing Lines and Points in SDL3
//render and show a red line SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderLine(sdlRenderer, 10, 10, 490, 490); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
Now we change the drawing colour by SDL_SetRenderDrawColor() to red and use SDL_RenderDrawLine(renderer, x1, y1, x2, y2) to draw a simple line, where x1, y1, x2 and y2 refers to coordinate pairs, hence (x1/y1) and (x2/y2). The first pair is (10/10), the second pair is (490/490). The red line should be drawn from coordinate (10/10) to coordinate (490/490). But where are these coordinates exactly in the window?
The Coordinate System in SDL3
This rule applies:
The origin from where to place an object is always the left upper corner of your screen or window.
In a SDL3 window (or screen if fullscreen) the cooodinate (0/0) refers to the left upper corner. The diagram below may help to understand this.
The coordinate (10/10) means to start at the point ten pixels to the right and ten pixel to the bottom relative to the origin (0/0). Thus, the coordinate (490/490) for the second point is nearly at the right bottom edge of the window and will lead to a diagonal line across. This diagonal line will be 10 pixels short with respect to the window edges.
After that again we ask to render this line to the screen by SDL_RenderPresent() and wait one second by SDL_Delay().
//render and draw yellow points diagonally with distance between each other SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 0, SDL_ALPHA_OPAQUE);for i := 0to47do SDL_RenderPoint(sdlRenderer, 490-i*10, 10+i*10); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
Now we change the colour to yellow and draw some points by the function SDL_RenderDrawPoint(renderer, x, y). It is nearly identical to SDL_RenderDrawLine() but instead of four coordinates you need just two coordinates where the point should be drawn.
I thought it would be nice to have more than just one point to be drawn, so the function is used in a for-loop to draw altogether 48 points. Here we need the counter variable “i”. Maybe you can guess from the code where the points are and how they are arranged, if not, just run the code ;-). Finally the result is rendered to the screen by SDL_RenderPresent() and the program waits one second by SDL_Delay().
Spread Points Randomly using Arrays
//prepare, render and draw a series of random connected black lines Randomize;for i := 0to499dobegin sdlPoints[i].x := Random(500); sdlPoints[i].y := Random(500);end; SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderLines(sdlRenderer, @sdlPoints, 500); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
Randomize is a Free Pascal procedure (from system unit) to initilize the random number generator. Imagine this as shaking the dice.
Free Pascal’s Random(max. number – 1) function generates random numbers between 0 and the number which is used as argument substracted by one. So we generate 500 times a random x and y value between 0 and 499 and save them into the 500 SDL_Point records of the “sdlPoints” array.
Colour black is set by SDL_SetRenderDrawColor().
To draw the lines which connects the points in the array “sdlPoints” we use SDL_RenderDrawLines(renderer, pointer to point array, number of array elements). First you need to set the renderer, then just a pointer to an array of TSDL_FPoint and finally the number of points. The latter should be consistent with the array. So, if your array has 500 elements, the count should be 500 at maximum. Notice how we are not using a loop here to draw all 500 lines by calling a certain draw function 500 times, which is a slow solution. Instead we just pass an array of points once. This way we save a lot of time at runtime, especially if you think of a real application where even more lines have to be drawn. There are similar functions for points, rectangles and filled rectangles. They are not used in the example but it may be interesting to know, so here they are:
SDL_RenderDrawPoints(renderer, pointer to points array, number of array elements)
SDL_RenderDrawRects(renderer, pointer to rectangles array, number of array elements)
SDL_RenderFillRects(renderer, pointer to rectangles array, number of array elements)
As a hint, try replacing SDL_RenderDrawLines by SDL_RenderDrawPoints.
Often, you will have functions in SDL3, where you can use batches of something
Drawing Rectangles in SDL3
//prepare, render and draw a green rectangle outline at upper-right corner sdlRect1.x := 260; sdlRect1.y := 10; sdlRect1.w := 230; sdlRect1.h := 230; SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, SDL_ALPHA_OPAQUE); SDL_RenderRect(sdlRenderer, @sdlRect1);
The drawing colour is set to green by SDL_SetRenderDrawColor() and a rectangle’s outline is drawn by SDL_RenderDrawRect(renderer, pointer of rectangle). Prior to using a rectangle we define it at position (260/10) with 230 pixels width and 230 pixels height.
It requires the renderer, which is “sdlRenderer” for us and a PSDL_Rect , thus we use the @-operator for the declared rectangle of TSDL_FRect type to get its pointer value. Notice, we neither render the result to the screen now nor do we delay here. We want a second rectangle immediately! 🙂
//render and draw the rectangle with 50% opacity at lower-left corner sdlRect1.x := 10; sdlRect1.y := 260; SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, 128); SDL_RenderFillRect(sdlRenderer, @sdlRect1); SDL_RenderPresent(sdlRenderer); SDL_Delay(1000);
We change the rectangles (x/y) coordinate for the second rectangle to (10/260), but keep its width and height the same. What we are looking for is a filled rectangle that has some transparency. Until now we always used SDL_ALPHA_OPAQUE (opaque) as alpha value. We keep the colour to draw the second rectangle to green by SDL_SetRenderDrawColor(). Notice that the fourth value is 128 (half-transparent) instead of the opaque constant. So everything behind the green rectangle should therefore shine through. To generate a filled rectangle SDL_RenderFillRect(renderer, pointer to rectangle) is used.
The Blend Mode in SDL3
But to be honest, even if you change the alpha value it will be opaque, unless you change the blend mode, which we did by SDL_SetRenderDrawBlendMode(renderer, blend mode). The default settings don’t allow for blending. We need to change this to be able to use the alpha value as desired.
First the renderer for which the blend mode has to be set is chosen. In our case it is “sdlRenderer” again. Then there are seven blend modes available in SDL3 and they determine how the new colours are combined with the already present colours at a certain pixel coordinate. Here is an overview of the four most important ones:
SDL_BLENDMODE_NONE
no blending
dstRGBA = srcRGBA
the final pixel colour is just the new pixel colour
SDL_BLENDMODE_BLEND
alpha blending
dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))
dstA = srcA + (dstA * (1-srcA))
the final pixel colour is an addition of the previous pixel colour the new pixel colour; the ratio is determined by the alpha value
SDL_BLENDMODE_ADD
additive blending
dstRGB = (srcRGB * srcA) + dstRGB
dstA = dstA
the final pixel colour is an addition of the previous pixel colour and the new pixel colour; only the previous pixel colour is affected by the alpha value though
SDL_BLENDMODE_MOD
color modulate
dstRGB = srcRGB * dstRGB
dstA = dstA
the final pixel colour is a multiplication of the previous and the new pixel colour
We are looking for (common) alpha blending, so we use SDL_BLENDMODE_BLEND as argument for the blend mode.
After doing so the result is rendered to the screen by SDL_RenderPresent() and shown for one second by SDL_Delay(). Both rectangles appear at the same time.
//clean memory SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);//shut down SDL3 SDL_Quit;
Finally all the memory reserved for points, the rectangle, the renderer and the window is free’d and SDL3 shut down by SDL_Quit.
Congratulations, you just finished this chapter :-).
Every SDL3 application with graphic output has to have at least one SDL3 window and a SDL3 renderer. The window is the entity that is showing the graphic output and the renderer is the “machine” that is generating the output. The code to set up a window and a renderer is as follows.
program SDL_WindowAndRenderer;uses SDL3;var sdlWindow1: PSDL_Window; sdlRenderer: PSDL_Renderer;begin//initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) then Halt;// full set up sdlWindow1 := SDL_CreateWindow('Window1', 500, 500, 0);if sdlWindow1 = nilthen Halt; sdlRenderer := SDL_CreateRenderer(sdlWindow1, nil);if sdlRenderer = nilthen Halt;// quick set up{ if not SDL_CreateWindowAndRenderer(500, 500, 0, @sdlWindow1, @sdlRenderer) then Halt;}// show window with rendered content for 2 seconds SDL_RenderPresent(sdlRenderer); SDL_Delay(2000);// clear memory SDL_DestroyRenderer(sdlRenderer); SDL_DestroyWindow (sdlWindow1);//closing SDL3 SDL_Quit;end.
Getting a Window with SDL3
With SDL3 you can create as many windows as you like, and each window is adressed by a variable of pointer type PSDL_Window. We just need one window we define in the var clause, let’s call it “sdlWindow1”.
It defines the window’s properties, e.g. size, appearance, border, title name and so on. And it holds the content it shows.
The actual creation of a window in SDL3 is as simple as using the function SDL_CreateWindow(title, width, height, flags). If it fails, sdlWindow1 will be nil afterwards. You should always check this.
In our example the window is titled “Window1”, it has a width and height of 500 pixels respectively. And we don’t want to set a specific flag. This will generate a common window as typical for your operating system at the screen’s center.
Hint for MacOS users: Window creation doesn’t work without an event loop in MacOS in SDL2. Please let me know, if this also applies for SDL3.
SDL3 Window Flags
The SDL3 window flags decide for the properties of the window. Look at the following list of possible flags and you may get an idea what they do. E.g. SDL_WINDOW_FULLSCREEN will create a fullscreen window and SDL_WINDOW_BORDERLESS will create a borderless window. You may combine several flags by the logical OR (if appropriate).
SDL_WINDOW_FULLSCREEN: fullscreen window at desktop resolution
SDL_WINDOW_OPENGL: window usable with an OpenGL context
SDL_WINDOW_OCCLUDED: window partially or completely obscured by another window
SDL_WINDOW_HIDDEN: window is not visible
SDL_WINDOW_BORDERLESS: no window decoration
SDL_WINDOW_RESIZABLE: window can be resized
SDL_WINDOW_MINIMIZED: window is minimized
SDL_WINDOW_MAXIMIZED: window is maximized
SDL_WINDOW_MOUSE_GRABBED: window has grabbed mouse focus
SDL_WINDOW_INPUT_FOCUS: window has input focus
SDL_WINDOW_MOUSE_FOCUS: window has mouse focus
SDL_WINDOW_EXTERNAL: window not created by SDL
SDL_WINDOW_MODAL: window is modal
SDL_WINDOW_HIGH_PIXEL_DENSITY: window uses high pixel density back buffer if possible
SDL_WINDOW_MOUSE_CAPTURE: window has mouse captured (unrelated to MOUSE_GRABBED)
SDL_WINDOW_ALWAYS_ON_TOP: window should always be above others
SDL_WINDOW_UTILITY: window should be treated as a utility window, not showing in the task bar and window list
SDL_WINDOW_TOOLTIP: window should be treated as a tooltip and does not get mouse or keyboard focus, requires a parent window
SDL_WINDOW_POPUP_MENU: window should be treated as a popup menu, requires a parent window
SDL_WINDOW_KEYBOARD_GRABBED: window has grabbed keyboard input
SDL_WINDOW_VULKAN: window usable with a Vulkan instance
SDL_WINDOW_METAL: window usable with a Metal instance
SDL_WINDOW_TRANSPARENT: window with transparent buffer
SDL_WINDOW_NOT_FOCUSABLE: window should not be focusable
The Renderer
In computer graphics rendering means the process of synthesizing the final image on your screen from the individual basic data structures, be it some lines, a flat background, a texture, a 3d object, or whatever. Hence we need a renderer to draw some content to the window.
The PSDL_Renderer pointer (which we declared in the var clause) is responsible for this and we call our PSDL_Renderer handle “sdlRenderer”. You could have many different renderers in an application, but oftentimes you use just one.
The creation of a renderer is as simple as one function call of SDL_CreateRenderer(window, name). If it fails, sdlRenderer is nil afterwards. The first argument is the window where the rendered output should be shown. That will be sdlWindow1 in our case.
The second argument should be nil which means SDL3 is choosing the best renderer for you. Otherwise you could specify a renderer by name, but that is not necessary for now.
Instead of creating the window and the renderer separately as demonstrated, you may use SDL_CreateWindowAndRenderer(width, height, window flags, window pointer pointer, renderer pointer pointer). This has the advantage that you just need one line to set up a window and a renderer. Since the window and renderer parameters are double pointers, you need to use the @ operator to make the window and renderer pointers available to the function.
Just remove the curly brackets and enclose the “full set up” -part in the example code to try it.
The function returns a logical false on failure.
Rendering a SDL3 Scene
The actual rendering is achieved by SDL_RenderPresent(renderer).
Freezing (delaying) a running application in SDL3
SDL_Delay(time in milliseconds) is a simple, yet powerful and important procedure to stop the program running for a certain time in milliseconds. 2000 milliseconds are two seconds. This is kind of a twin of Pascal’s Delay procedure.
// show window with rendered content for 2 seconds SDL_RenderPresent(sdlRenderer); SDL_Delay(2000);
Clean up the Memory in SDL3
Now the final lines of code are discussed. One of the most important rules for sophisticated programming is followed here:
Always clean up the memory on program finish.
For nearly any pointer type generated by SDL3, there is a destroy procedure to remove it from memory. These procedures are comparable to Pascal’s dispose procedure to remove pointer types from memory. Make sure to destroy the objects in the opposite sequence of their generation. We first created a window, then a renderer. So now we go the opposite way, first destroy the renderer and then the window by the procedures SDL_DestroyRenderer(renderer) and SDL_DestroyWindow(window) respectively.
Let’s jump right into our first SDL3 application. Every SDL3 application needs to include the SDL3 unit (obviously), because it is that unit which provides all the SDL3 functions and types we use for development.
program FirstSteps;uses SDL3;begin//initilization of video subsystemifnot SDL_Init(SDL_INIT_VIDEO) thenExit;{your SDL2 application/game} SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, 'First Steps', 'It works!', nil);//shutting down video subsystem SDL_Quit;end.
Initialization and Quitting of SDL3
The first function we use is called SDL_Init(subsystem flags). It is used to initialize subsystems of SDL3 and must be called always before using any SDL3 features. These subsystems are:
SDL_INIT_AUDIO
SDL_INIT_VIDEO
SDL_INIT_JOYSTICK
SDL_INIT_HAPTIC
SDL_INIT_GAMEPAD
SDL_INIT_EVENTS
SDL_INIT_SENSOR
SDL_INIT_CAMERA
The names are self-explanatory, so if you need video functionality you set SDL_INIT_VIDEO. If you need audio functionality, you set SDL_INIT_AUDIO. You can combine subsystems by using the logical or. For video and audio functionality you use SDL_INIT_VIDEO or SDL_INIT_AUDIO.
By the way, with the exception of the haptic flag, all flags do imply the event system, so you don’t need to load it.
You may have wondered why the SDL_Init function is buried in the if-statement. Most functions in SDL3 return a boolean return value which indicates if its execution was successful. This allows for efficient error checking as seen here. If the SDL_Init is returning true the applications proceeds, if not it would exit immediately.
When quitting your application, use the SDL_Quit function.
A simple message box, because we can!
If you see a message box like this…
… then everything works alright. Otherwise, something is is wrong. Most probably the SDL3 library hasn’t been found.
And here you see one great benefit of SDL3. The message box is generated by exactly one simple call to the SDL_ShowSimpleMessageBox(message box flags, title, message, window) function. To realize the same, not to mention on different platforms in a platform-independent way, is whole other question and takes much greater effort.
SDL3 simplifies your (game) development endeavor by a giant margin in many respects.