Tag Archives: free pascal

This tag indicates that the content is about the Free Pascal compiler, it may be a news article, a tutorial chapter, a project entry or something else. Most Free Pascal content is also related to SDL2 here.

Event handling Pt. 2, Mouse handling

Last updated on February 17th, 2024

This is part 2 of the chapter about event handling. Mouse and window handling is treated here.

Let’s start with the full example code:

program Chapter8_SDL2;

uses SDL2;

var
sdlWindow1: PSDL_Window;
sdlEvent: PSDL_Event;
exitloop: boolean = false;
text1: string = '';

begin

  //initilization of video subsystem
  if SDL_Init( SDL_INIT_VIDEO ) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow( 'Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN );
  if sdlWindow1 = nil then HALT;

  new( sdlEvent );

  while exitloop = false do
  begin
    while SDL_PollEvent( sdlEvent ) = 1 do
    begin
      write( 'Event detected: ' );
      case sdlEvent^.type_ of

        //keyboard events
        SDL_KEYDOWN: begin
                       writeln( 'Key pressed:');
                       writeln( '  Key code: ', sdlEvent^.key.keysym.sym );
                       writeln( '  Key name: "', SDL_GetKeyName( sdlEvent^.key.keysym.sym ), '"' );
                       writeln( '  Scancode: ', sdlEvent^.key.keysym.scancode );
                       writeln( '  Scancode name: "', SDL_GetScancodeName( sdlEvent^.key.keysym.scancode ), '"' );
                       writeln( '  Key modifiers: ', sdlEvent^.key.keysym._mod );
                       case sdlEvent^.key.keysym.sym of
                         27: exitloop := true;  // exit on pressing ESC key

                         //switching text input mode on/off
                         SDLK_F1: begin
                                    if SDL_IsTextInputActive = SDL_True then SDL_StopTextInput
                                    else SDL_StartTextInput;
                                    writeln(' Text Input Mode switched' );
                                  end;
                       end;
                     end;
        SDL_KEYUP: writeln( 'Key released ' );
        SDL_TEXTINPUT: begin
                         writeln( 'Text input: "', sdlEvent^.text.text, '"' );
                         text1 := text1 + sdlEvent^.text.text;
                         writeln( 'Full string: ' + text1 );
                       end;

        //mouse events
        SDL_MOUSEMOTION: begin
                           writeln( 'X: ', sdlEvent^.motion.x, '   Y: ', sdlEvent^.motion.y,
                                    '   dX: ', sdlEvent^.motion.xrel, '   dY: ', sdlEvent^.motion.yrel );
                         end;
        SDL_MOUSEBUTTONDOWN: writeln( 'Mouse button pressed: Button index: ', sdlEvent^.button.button );
        SDL_MOUSEBUTTONUP: writeln( 'Mouse button released' );
        SDL_MOUSEWHEEL: begin
                          write( 'Mouse wheel scrolled: ' );
                          if sdlEvent^.wheel.y > 0 then writeln( 'Scroll forward, Y: ', sdlEvent^.wheel.y )
                          else writeln( 'Scroll backward, Y: ', sdlEvent^.wheel.y );
                        end;

        //window events
        SDL_WINDOWEVENT: begin
                           write( 'Window event: ' );
                           case sdlEvent^.window.event of
                             SDL_WINDOWEVENT_SHOWN: writeln( 'Window shown' );
                             SDL_WINDOWEVENT_MOVED: writeln( 'Window moved' );
                             SDL_WINDOWEVENT_MINIMIZED: writeln( 'Window minimized' );
                             SDL_WINDOWEVENT_MAXIMIZED: writeln( 'Window maximized' );
                             SDL_WINDOWEVENT_ENTER: writeln( 'Window got mouse focus' );
                             SDL_WINDOWEVENT_LEAVE: writeln( 'Window lost mouse focus' );
                           end;
                         end;
      end;
    end;
    SDL_Delay( 20 );
  end;

  dispose( sdlEvent );
  SDL_DestroyWindow ( sdlWindow1 );

  //shutting down video subsystem
  SDL_Quit;

end.

Mouse handling in SDL 2.0

If you got the basic concept of event handling, you will find that mouse handling and keyboard handling have a lot in common.

        //mouse events
        SDL_MOUSEMOTION: begin
                           writeln( 'X: ', sdlEvent^.motion.x, '   Y: ', sdlEvent^.motion.y,
                                    '   dX: ', sdlEvent^.motion.xrel, '   dY: ', sdlEvent^.motion.yrel );
                         end;

For mouse motions, mouse buttons and the mouse wheel there are three different mouse event structures: SDL_MouseMotionEvent, SDL_MouseButtonEvent and SDL_MouseWheelEvent.

Mouse motion handling in SDL 2.0

If you are moving the mouse, the SDL_MOUSEMOTION event is triggered. The record structure of the SDL_MouseMotionEvent is shown below:

TSDL_MouseMotionEvent = record
    type_: UInt32;       // SDL_MOUSEMOTION
    timestamp: UInt32;
    windowID: UInt32;    // The window with mouse focus, if any
    which: UInt32;       // The mouse instance id, or SDL_TOUCH_MOUSEID
    state: UInt8;        // The current button state 
    padding1: UInt8;
    padding2: UInt8;
    padding3: UInt8;
    x: SInt32;           // X coordinate, relative to window
    y: SInt32;           // Y coordinate, relative to window
    xrel: SInt32;        // The relative motion in the X direction 
    yrel: SInt32;        // The relative motion in the Y direction
  end;

Again there are the type_, the timestamp and the windowID fields. Nothing new here. The field which contains the mouse id. This is important if you have more than one mouse device attached to your computer. Think for example of a laptop with a touchpad area to move the mouse cursor and at the same time there is an usb mouse attached to the laptop. To distinguish between the two, you may retrieve their id’s.

The state field is known from the SDL_KeyBoardEvent structure. It may be a difference if you have a mouse button pressed and move the mouse or if you don’t have a button pressed. The most famous example is if you want to select a bunch of files on your desktop or in a folder. By the way, the state field encodes a number which is different depending on which buttons you pressed actually. This works similar to the key modifiers, if you keep two mousebuttons pressed while moving, the state is the sum of each individual mouse button state value. As an example for my mouse: No mouse button 0, left mouse button 1, right mouse button 4, middle mouse button 2, thumb button 8. If I keep pressed left and right mouse button 5 (sum 1 + 4).

The x and y fields contain the coordinate of the mouse cursor in pixels. These coordinates are relative to the window of the SDL 2.0 application. Keep in mind, the coordinates (0/0) correspond to the left upper corner. Positive x values are counted from left to right and positive y values are counted from top to bottom.

The fields xrel and yrel are used to determine how fast the mouse has been moved from one point to another. Let’s assume you move the mouse surcor from left to right in your application’s window. The first time you do it slowly, xrel might be 1, means, you just moved pixel from left to right between two mouse motion events. If you move fast, xrel might be 50, meaning that this time you moved by 50 pixels between two mouse motion events. Especially for game programming this can be a extremely important information. E.g., think of first person shooter, if the movement speed of the first person view would be independent of the actual movement of the mouse, this game wouldn’t make much sense.

To access these fields the event’s motion field has to be read out. In the example code the (x/y) coordinates and the relative positions xrel and yrel are read out by

sdlEvent^.motion.x

sdlEvent^.motion.y

sdlEvent^.motion.xrel

sdlEvent^.motion.yrel

and simply printed out to the screen. Let’s go for the next chunk of code.

        SDL_MOUSEBUTTONDOWN: writeln( 'Mouse button pressed: Button index: ', sdlEvent^.button.button );
        SDL_MOUSEBUTTONUP: writeln( 'Mouse button released' );
        SDL_MOUSEWHEEL: begin
                          write( 'Mouse wheel scrolled: ' );
                          if sdlEvent^.wheel.y > 0 then writeln( 'Scroll forward, Y: ', sdlEvent^.wheel.y )
                          else writeln( 'Scroll backward, Y: ', sdlEvent^.wheel.y );
                        end;

Pressing a mouse button in SDL 2.0

As for the SDL_KeyBoardEvent, you would want to know if a mouse button and which one is pressed or released. If a mouse button is pressed, a SDL_MOUSEBUTTONDOWN event is triggered. On releasing a mouse button a SDL_MOUSEBUTTONUP event is triggered. It has SDL_MouseButtonEvent structure. Let’s have a look into the structure:

TSDL_MouseButtonEvent = record
    type_: UInt32;       // SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP 
    timestamp: UInt32;
    windowID: UInt32;    // The window with mouse focus, if any
    which: UInt32;       // The mouse instance id, or SDL_TOUCH_MOUSEID 
    button: UInt8;       // The mouse button index
    state: UInt8;        // SDL_PRESSED or SDL_RELEASED
    padding1: UInt8;
    padding2: UInt8;
    x: SInt32;           // X coordinate, relative to window
    y: SInt32;           // Y coordinate, relative to window 
  end;

If you compare this structure to the structure of the SDL_MouseMotionEvent, you will find, only the two field xrel and yrel are gone and a new field, a crucial one to be clear, is new, which is button of 8 bit unsigned integer type.

I won’t discuss all the fields again we discussed for the SDL_MouseMotionEvent structure. Attention, if you compare the state field of the SDL_MouseButtonEvent, it works another way. It allows just for two values, SDL_PRESSED or SDL_RELEASED, as known from SDL_KeyBoardEvent. As a reminder: For the SDL_MouseMotionEvent, it represented that full state of all the buttons being pressed while mouse motion.

The button field allows to recognize which button has triggered the SDL_MouseButtonEvent. Each button of the mouse has its own index. As an example for my mouse they are as follows: Left mouse button 1, right mouse button 3, middle mouse button 2, thumb mouse button 4. Do not confuse these index numbers with the mouse button state values of the SDL_MouseMotionEvent structure. There can only be one button which triggered this event! Combinations as for the motion event are not possible. In the example code this value is just printed to the screen using

sdlEvent^.button.button.

You see, to access the fields of this record you need to address the event’s button field. This mustn’t be confused with SDL_MouseButtonEvent’s button field discussed above.

The x and y fields contain the (x/y) coordinates when the mouse button (whose index is stored in field button) has been pressed (or released). This is crucial to know. What would an application be worth if you could recognized that a certain button has been pressed but you don’t know where exactly? Not shown in the code but you could access these fields by

sdlEvent^.button.x

sdlEvent^.button.y.

The mouse wheel in SDL 2.0

If the mouse wheel is used a SDL_MOUSEWHEEL event is triggered. Let’s look into the corresponding SDL_MouseWheelEvent structure.

TSDL_MouseWheelEvent = record
    type_: UInt32;        // SDL_MOUSEWHEEL
    timestamp: UInt32;
    windowID: UInt32;    // The window with mouse focus, if any
    which: UInt32;       // The mouse instance id, or SDL_TOUCH_MOUSEID
    x: SInt32;           // The amount scrolled horizontally 
    y: SInt32;           // The amount scrolled vertically
  end;

New fields here are the x and y field which do not correspond to the mouse cursor position this time. Instead of that they refer to the direction of the mouse wheel being scrolled. If you scroll the mouse wheel upwards or forward it will return 1, and if you scroll it backwards it will return -1. If you have a mouse wheel which can be scrolled horizontally, it will be similar. By the way scrolling a mouse wheel can be considered as pressing a button very quickly for that direction.

In the code, if a SDL_MOUSEWHEEL event is triggered, the y value is checked to be positive or negative. This decides if an upward or downward scrolling has happened and the corresponding value (1 or -1) will be returned and printed out. Anyway, could you guess what happens if anyone would use a mouse wheel that is scrolling horizontally? – The same block will be executed since a SDL_MOUSEWHEEL event is triggered. Instead of y being 1 or -1, it will be 0 but the x value will be 1 or -1. Nevertheless, the else-block will be executed since y is not greater than 0. So for the example program it will print out wrongly that an backward scroll has happened. Anyway, you get the idea.

Window handling in SDL 2.0

Modern applications are always run in windows. The famous operation system “Windows” by Microsoft even derived it’s name from this. The first task for most of SDL 2.0 applications is the creation of a window. The example code creates a window of width 500 pixels and height 500 pixels. It may be important to know if the user interacts with the application window. Whenever this happens a SDL_WINDOWEVENT is triggered.

        //window events
        SDL_WINDOWEVENT: begin
                           write( 'Window event: ' );
                           case sdlEvent^.window.event of
                             SDL_WINDOWEVENT_SHOWN: writeln( 'Window shown' );
                             SDL_WINDOWEVENT_MOVED: writeln( 'Window moved' );
                             SDL_WINDOWEVENT_MINIMIZED: writeln( 'Window minimized' );
                             SDL_WINDOWEVENT_MAXIMIZED: writeln( 'Window maximized' );
                             SDL_WINDOWEVENT_ENTER: writeln( 'Window got mouse focus' );
                             SDL_WINDOWEVENT_LEAVE: writeln( 'Window lost mouse focus' );
                           end;

If the an event of type SDL_WINDOWEVENT is triggered, the text message “Window event: ” is printed out.

To access the window event fields, you need to access the event’s window field. The field event contains the window event’s type information, hence what window event has been triggered.

sdlEvent^.window.event

In contrast to the keyboard and the mouse event we discussed before, the different event types are not distinguished by the type_ field but by an additional field event.

In the example code six different window event types are checked: SDL_WINDOWEVENT_SHOWN, SDL_WINDOWEVENT_MOVED, SDL_WINDOWEVENT_MINIMIZED, SDL_WINDOWEVENT_MAXIMIZED, SDL_WINDOWEVENT_ENTER and SDL_WINDOWEVENT_LEAVE. From the texts printed out you can guess when they get triggered. I think no further explanation is needed here.

By the way, there are more window event types which are shown a little bit later. Sometimes if one of these is triggered, only the text that a window event has been triggered is shown but without any further details since the example code doesn’t covers further treatment. Feel free to extent the code yourself.

Let’s have a look at the event structure of SDL_WindowEvent.

TSDL_WindowEvent = record
    type_: UInt32;       // SDL_WINDOWEVENT
    timestamp: UInt32;
    windowID: UInt32;    // The associated window
    event: UInt8;        // SDL_WindowEventID
    padding1: UInt8;
    padding2: UInt8;
    padding3: UInt8;
    data1: SInt32;       // event dependent data
    data2: SInt32;       // event dependent data 
  end;

The fields type_, timestamp and windowID are known and have the same meaning as discussed before.

The field event stores an identifier (SDL_WindowEventID) to distinguish between different window events. Here they are listed and in brackets you find the window related action which has triggered them:

  1. SDL_WINDOWEVENT_SHOWN (window has been shown)
  2. SDL_WINDOWEVENT_HIDDEN (window has been hidden)
  3. SDL_WINDOWEVENT_EXPOSED (window has been exposed and should be redrawn)
  4. SDL_WINDOWEVENT_MOVED (window has been moved to data1, data2)
  5. SDL_WINDOWEVENT_RESIZED (window has been resized to data1xdata2; this is event is always preceded by SDL_WINDOWEVENT_SIZE_CHANGED)
  6. SDL_WINDOWEVENT_SIZE_CHANGED (window size has changed, either as a result of an API call or through the system or user changing the window size; this event is followed by SDL_WINDOWEVENT_RESIZED if the size was changed by an external event, i.e. the user or the window manager)
  7. SDL_WINDOWEVENT_MINIMIZED (window has been minimized)
  8. SDL_WINDOWEVENT_MAXIMIZED (window has been maximized)
  9. SDL_WINDOWEVENT_RESTORED (window has been restored to normal size and position)
  10. SDL_WINDOWEVENT_ENTER (window has gained mouse focus)
  11. SDL_WINDOWEVENT_LEAVE (window has lost mouse focus)
  12. SDL_WINDOWEVENT_FOCUS_GAINED (window has gained keyboard focus)
  13. SDL_WINDOWEVENT_FOCUS_LOST (window has lost keyboard focus)
  14. SDL_WINDOWEVENT_CLOSE (the window manager requests that the window be closed)

This list is based upon information found at the SDL 2.0 wiki.

If you read through the list carefully you will notice the mention of data1 and data2 which rather explains their occurance in the event structure :-)! They need to be read out for SDL_WINDOWEVENT_MOVED and SDL_WINDOWEVENT_RESIZED to get the new window position or dimensions.

At the moment I’m not sure why for window events the distinction between the individual window events (e.g. SDL_WINDOWEVENT_MOVED, SDL_WINDOWEVENT_RESIZED, and so on) is not done by the type_ field as for keyboard, mouse and other events, but rather by the additional event field.

                         end;
      end;
    end;
    SDL_Delay( 20 );
  end;

  dispose( sdlEvent );
  SDL_DestroyWindow ( sdlWindow1 );

  //shutting down video subsystem
  SDL_Quit;

end.

Not much to learn in the final part. The loop is delayed by 20 milliseconds for better recognizability of the text output.

If the loop is left, the event pointer gets free’d, the SDL 2.0 window gets destroyed and SDL 2.0 quit. That’s it :-)!

Touchscreen events, Joystick events and many more!

This chapter covered keyboard, mouse and window events in some detail. Keep in mind, SDL 2.0 has much more to show! – There are many more events you can use for application development. They basically cover any modern type of interaction you could wish for. This includes touchscreen events (important for smartphone development), joystick events (game console development), and even a dropfile event (drag and drop files) and more.

previous Chapter | next Chapter

Event handling Pt. 1, Keyboard handling

Last updated on February 17th, 2024

What’s an event and event handling in programming?

Event handling is a major concept in game and application programming. It allows for the user to interact with your program. Whenever you move your mouse cursor, press or release a key on the keyboard or use a touch screen, all these interactions are recognized as so-called events.

Events in SDL 2.0, SDL_Event

In SDL 2.0 whenever an event occurs, for instance a key gets pressed on the keyboard, all the information related to that specific event is stored in a SDL_Event record. Depending on the event type (e.g. mouse motion, key pressed on a keyboard, maximizing the application window) there are very different fields which can be accessed. For a mouse motion you can read out the x and y position of the cursor whereas 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.

Event types available in SDL 2.0

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_MOUSEMOTION. The full list according to the official SDL 2.0 documentation is:

Event types, Event structure and SDL_Event field
Event type Event structure SDL_Event field
SDL_AUDIODEVICEADDED
SDL_AUDIODEVICEREMOVED
SDL_AudioDeviceEvent adevice
SDL_CONTROLLERAXISMOTION SDL_ControllerAxisEvent caxis
SDL_CONTROLLERBUTTONDOWN
SDL_CONTROLLERBUTTONUP
SDL_ControllerButtonEvent cbutton
SDL_CONTROLLERDEVICEADDED
SDL_CONTROLLERDEVICEREMOVED
SDL_CONTROLLERDEVICEREMAPPED
SDL_ControllerDeviceEvent cdevice
SDL_DOLLARGESTURE
SDL_DOLLARRECORD
SDL_DollarGestureEvent dgesture
SDL_DROPFILE SDL_DropEvent drop
SDL_FINGERMOTION
SDL_FINGERDOWN
SDL_FINGERUP
SDL_TouchFingerEvent tfinger
SDL_KEYDOWN
SDL_KEYUP
SDL_KeyboardEvent key
SDL_JOYAXISMOTION SDL_JoyAxisEvent jaxis
SDL_JOYBALLMOTION SDL_JoyBallEvent jball
SDL_JOYHATMOTION SDL_JoyHatEvent jhat
SDL_JOYBUTTONDOWN
SDL_JOYBUTTONUP
SDL_JoyButtonEvent jbutton
SDL_JOYDEVICEADDED
SDL_JOYDEVICEREMOVED
SDL_JoyDeviceEvent jdevice
SDL_MOUSEMOTION SDL_MouseMotionEvent motion
SDL_MOUSEBUTTONDOWN
SDL_MOUSEBUTTONUP
SDL_MouseButtonEvent button
SDL_MOUSEWHEEL SDL_MouseWheelEvent wheel
SDL_MULTIGESTURE SDL_MultiGestureEvent mgesture
SDL_QUIT SDL_QuitEvent quit
SDL_SYSWMEVENT SDL_SysWMEvent syswm
SDL_TEXTEDITING SDL_TextEditingEvent edit
SDL_TEXTINPUT SDL_TextInputEvent text
SDL_USEREVENT SDL_UserEvent user
SDL_WINDOWEVENT SDL_WindowEvent window
Other events SDL_CommonEvent none use .type
Source: SDL 2.0 Documentation: SDL_Event

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. The most important events are covered by this tutorial in detail anyway. You will be able to work with the remaining events once you got the concept.

In contrast to SDL 1.2 there are some event types gone and many new types available in SDL 2.0 which are useful to use new forms of interaction between the user and the application (e.g. touch screen technology).

What is the difference between event type, event structure and the event field?

In the table above you’ll notice the first column covers the event type. It determines which type of event occured, e.g. a key is pressed down on the keyboard (SDL_KEYDOWN).

The event structure in the second column is the record structure which is dependend upon the event type. As discussed before, a pressed key will need a record structure which stores the key identifier rather than x,y-coordinated (which would in turn be necessary for a mouse motion). Each event type has a certain apropriate (event) record structure to hold the event information.

Many event types can share the same event structure. The event types SDL_KEYDOWN and SDL_KEYUP which are generated by pressing or releasing a key share the same event structure SDL_KeyboardEvent since the information are the same, e.g. the key identifier.

The third column shows the SDL_Event field name to access the event specific fields. In case of an event type SDL_KEYDOWN the event structure is SDL_KeyboardEvent. The specific information, e.g. the key identifier, is accessible via the field key in the SDL_Event record.

This may sound confusing. Later on the relation is discussed in more detail. Let’s right proceed into the code.

program Chapter8_SDL2;

uses SDL2;

var
sdlWindow1: PSDL_Window;
sdlEvent: PSDL_Event;
exitloop: boolean = false;
text1: string = '';

begin

  //initilization of video subsystem
  if SDL_Init( SDL_INIT_VIDEO ) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow( 'Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN );
  if sdlWindow1 = nil then HALT;

  new( sdlEvent );

  while exitloop = false do
  begin
    while SDL_PollEvent( sdlEvent ) = 1 do
    begin
      write( 'Event detected: ' );
      case sdlEvent^.type_ of

        //keyboard events
        SDL_KEYDOWN: begin
                       writeln( 'Key pressed:');
                       writeln( '  Key code: ', sdlEvent^.key.keysym.sym );
                       writeln( '  Key name: "', SDL_GetKeyName( sdlEvent^.key.keysym.sym ), '"' );
                       writeln( '  Scancode: ', sdlEvent^.key.keysym.scancode );
                       writeln( '  Scancode name: "', SDL_GetScancodeName( sdlEvent^.key.keysym.scancode ), '"' );
                       writeln( '  Key modifiers: ', sdlEvent^.key.keysym._mod );
                       case sdlEvent^.key.keysym.sym of
                         SDLK_ESCAPE: exitloop := true;  // exit on pressing ESC key

                         //switching text input mode on/off
                         SDLK_F1: begin
                                    if SDL_IsTextInputActive = SDL_True then SDL_StopTextInput
                                    else SDL_StartTextInput;
                                    writeln(' Text Input Mode switched' );
                                  end;
                       end;
                     end;
        SDL_KEYUP: writeln( 'Key released ' );
        SDL_TEXTINPUT: begin
                         writeln( 'Text input: "', sdlEvent^.text.text, '"' );
                         text1 := text1 + sdlEvent^.text.text;
                         writeln( 'Full string: ' + text1 );
                       end;

        //mouse events
        SDL_MOUSEMOTION: begin
                           writeln( 'X: ', sdlEvent^.motion.x, '   Y: ', sdlEvent^.motion.y,
                                    '   dX: ', sdlEvent^.motion.xrel, '   dY: ', sdlEvent^.motion.yrel );
                         end;
        SDL_MOUSEBUTTONDOWN: writeln( 'Mouse button pressed: Button index: ', sdlEvent^.button.button );
        SDL_MOUSEBUTTONUP: writeln( 'Mouse button released' );
        SDL_MOUSEWHEEL: begin
                          write( 'Mouse wheel scrolled: ' );
                          if sdlEvent^.wheel.y > 0 then writeln( 'Scroll forward, Y: ', sdlEvent^.wheel.y )
                          else writeln( 'Scroll backward, Y: ', sdlEvent^.wheel.y );
                        end;

        //window events
        SDL_WINDOWEVENT: begin
                           write( 'Window event: ' );
                           case sdlEvent^.window.event of
                             SDL_WINDOWEVENT_SHOWN: writeln( 'Window shown' );
                             SDL_WINDOWEVENT_MOVED: writeln( 'Window moved' );
                             SDL_WINDOWEVENT_MINIMIZED: writeln( 'Window minimized' );
                             SDL_WINDOWEVENT_MAXIMIZED: writeln( 'Window maximized' );
                             SDL_WINDOWEVENT_ENTER: writeln( 'Window got mouse focus' );
                             SDL_WINDOWEVENT_LEAVE: writeln( 'Window lost mouse focus' );
                           end;
                         end;
      end;
    end;
    SDL_Delay( 20 );
  end;
  
  dispose( sdlEvent );
  SDL_DestroyWindow ( sdlWindow1 );

  //shutting down video subsystem
  SDL_Quit;

end.

The result will not be seen in the actually SDL 2.0 window but in the command line window (which usually is showing up along with the SDL 2.0 window on Windows environments).

Chapter 8 - result

The initial lines of code are:

program Chapter8_SDL2;

uses SDL2;

var
sdlWindow1: PSDL_Window;
sdlEvent: PSDL_Event;
exitloop: boolean = false;
text1: string = '';

begin

  //initilization of video subsystem
  if SDL_Init( SDL_INIT_VIDEO ) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow( 'Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN );
  if sdlWindow1 = nil then HALT;

  new( sdlEvent );

The program is called “Chapter8_SDL2”. Since event handling is a basic feature of SDL 2.0, no further units except for SDL2 itself necessary.

We need three variables. “sdlWindow1” is necessary to create a window as known from previous chapters. This time we won’t draw anything to it but use it to recognize events (e.g. mouse clicks into the window).

The SDL_Event variable “sdlEvent” stores the events generated by the user of the application. It is of pointer type PSDL_Event. Then there is a simple Pascal boolean variable “exitloop” which is set to false since we don’t want to leave the program loop initially. Also we have a “text1” string variable we will need to demonstrate text input later.

Nothing new for the following lines, SDL 2.0 is initilised and the SDL 2.0 window is created.

Since “sdlEvent” is a pointer type variable we need to allocate some memory. This is done by the new command, as known.

  while exitloop = false do
  begin
    while SDL_PollEvent( sdlEvent ) = 1 do
    begin
      write( 'Event detected: ' );

First a while-do loop is started which will run as long as the variable “exitloop” is false. If it is changed to true the loop will be exited. (This is triggered by pressing ESC, later this is discussed in detail.)

Within the outer loop the first thing is to poll for an event, which is done by function SDL_PollEvent.

SDL_PollEvent(event: PSDL_Event): SInt32

This function returns integer value 1 if one or more events are in the queue. The event data is allocated to the SDL_Event variable and deleted from the queue. If there are no events waiting, it returns 0 and the event variable is filled with nil instead of specific event data.

If there is an event waiting, its information are fed to “sdlEvent” and 1 is returned. The inner while loop is running until all events in the queue are treated. This will print out a text saying “Event detected” and, more important, check for the event type!

Don’t use an if-clause (instead of the inner while loop) to check for the events because that means we only check once a event each every cycle of the outer loop. By combining two while loops and check for all the events in the inner loop we can treat every event occured for this program loop cycle.

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

        //keyboard events
        SDL_KEYDOWN: begin
                       writeln( 'Key pressed:');
                       writeln( '  Key code: ', sdlEvent^.key.keysym.sym );
                       writeln( '  Key name: "', SDL_GetKeyName( sdlEvent^.key.keysym.sym ), '"' );
                       writeln( '  Scancode: ', sdlEvent^.key.keysym.scancode );
                       writeln( '  Scancode name: "', SDL_GetScancodeName( sdlEvent^.key.keysym.scancode ), '"' );
                       writeln( '  Key modifiers: ', sdlEvent^.key.keysym._mod );

If _type is a SDL_KEYDOWN event a begin-end block is entered. There are several writeln output lines. Let’s discuss them one after another.

In the first line the SDL (virtual) key code is returned, which is represented by an integer value. For special keys (e.g. F-keys, Insert, …) these values often range beyond the scope of SmallInt (-32768 to 32767) or Word (0 to 65535) variables, which you should keep in mind if you intend to return these values to variables. The SDL virtual key code can be accessed by

sdlEvent^.key.keysym.sym.

Let’s try to break this down a little bit. The event is stored in sdlEvent which is of pointer type so to access the content we need sdlEvent^. In the keyboard event there is a field keysym which itself is a record. The SDL virtual key code is stored in the field sym of the keysym record. Don’t worry if this sounds kind of confusing, in the next part we treat these two records (SDL_KeyboardEvent record and keysym record) in detail.

Most of the key codes have a name, e.g. “Escape” for the escape key. In the next line of the code, the function SDL_GetKeyName is used to get the name of the key whose key code we found:

function SDL_GetKeyName(key: TSDL_ScanCode): PAnsiChar

The other way round it is also possible by this function:

function SDL_GetKeyFromName(const name: PAnsiChar): TSDL_KeyCode

This is not shown in the code though.

The scancode of a key is another representation. The details about the difference between key codes and scancodes are discussed a little bit later. Anyway, the scancode is stored in the scancode field of the keysym record. So it can be accessed by:

sdlEvent^.key.keysym.scancode

and its name can be read out by

function SDL_GetScancodeName(scancode: TSDL_ScanCode): PAnsiChar.

Also here you can do it the other way round by

function SDL_GetScancodeFromName(const name: PAnsiChar): TSDL_ScanCode.

For completion and your information, it is possible to get the key code from the scancode and vice versa. The functions to use are:

function SDL_GetKeyFromScancode(scancode: TSDL_ScanCode): TSDL_KeyCode

and

function SDL_GetScancodeFromKey(key: TSDL_KeyCode): TSDL_ScanCode.

You see, there is a strong relation between the two but they are not the same. Lets have a short example of what happens if you press a key:

Keycodes and Scancodes
Keycodes and Scancodes

The tutorial code returns these key- and scancodes for the “Q”-key, the escape key and the return key. As you can see the codes do not only differ between different keys but also for the same key, e.g. the key code for the escape key is 27 but the scanscode is 41. Anyway, the key names seems to be consistent. Later we will see an example where even the names differ.

The last line returns the code value of key modifiers. Key modifiers are keys which literally modify them. E.g., if you press a letter key while holding the shift key, usually you modify the letter to be the capital letter. Typical key modifiers are shift, ctrl and alt. The field which stores the key modifier value is called _mod. Later we will discuss this field more in detail.

                       case sdlEvent^.key.keysym.sym of
                         SDLK_ESCAPE: exitloop := true;  // exit on pressing ESC key

                         //switching text input mode on/off
                         SDLK_F1: begin
                                    if SDL_IsTextInputActive = SDL_True then SDL_StopTextInput
                                    else SDL_StartTextInput;
                                    writeln(' Text Input Mode switched' );
                                  end;
                       end;
                     end;

We check for the value of the key code by a case-statement with sdlEvent^.key.keysym.sym. If we know the key code of a certain key, we may check for this key and react accordingly. First, we check if the event’s key code is SDLK_ESCAPE since this is the key code of the escape key (ESC). If so, the variable exitloop is set to true to stop the outer while loop and exiting the program.

Every key code is represented by the key code constant (e.g. SDLK_ESCAPE), a decimal value (27 here) and a hexadecimal value ($001B here).  You may check the key codes in the following (official) SDL 2.0 key code lookup table:

https://wiki.libsdl.org/SDLKeycodeLookup or SDL 2.0 Key code lookup table (Backup hosted here)

In the fifth row the escape key key code is found.

In this table you find additionally to the decimal value, the hexadecimal value. You may try to use $001B instead of 27. This will do the trick either :-)! Also there is a character representation shown if possible.

If the the F1-key is pressed we would like to turn on or off the text input mode. This time we do not use a decimal key code to recognize the key but rather a constant representation (SDLK_F1). You could guess you have the choice and could use either the decimal value, the constant representation (what about SDLK_ESCAPE in the case before?) or the hexadecimal representation. – SDLK_ESCAPE even exists(!) but guess what, that doesn’t work. For some special keys this works like shown for the F1-key, for most of the other keys it doesn’t work (you will find that most keys are stored as string constants so the compiler will complain that they are of wrong type). So for most keys you have to look up the decimal representation and do as shown.

In the same way we check if the F1 key got pressed by checking for SDLK_F1. If F1 got pressed it is checked if the so called Text input mode is active by

function SDL_IsTextInputActive: TSDL_Bool.

If it is active, it gets deactivated by

procedure SDL_StopTextInput

and if it is not active, it gets activated by

procedure SDL_StartTextInput.

Finally a short text is returned which states that the Text input mode has been changed. More about the input mode later.

Last but not least, if _type is a SDL_KEYUP event, we know a key has been released, hence it physically moves up on the keyboard. We just print out “Key released”, we don’t care what exactly key is released here.

The keyboard events SDL_KEYDOWN and SDL_KEYUP

Let’s have a look at the structure of the keyboard event record and discuss the fields from top to bottom:

TSDL_KeyboardEvent = record
    type_: UInt32;        // SDL_KEYDOWN or SDL_KEYUP
    timestamp: UInt32;
    windowID: UInt32;     // The window with keyboard focus, if any
    state: UInt8;         // SDL_PRESSED or SDL_RELEASED 
    _repeat: UInt8;       // Non-zero if this is a key repeat
    padding2: UInt8;
    padding3: UInt8;
    keysym: TSDL_KeySym;  // The key that was pressed or released
  end;

type_ is an unsigned 32 bit integer (hence UInt32) value which determines what exact type of event you have. As usual the integer values are represented by constants, here SDL_KEYDOWN (if pressed down) and SDL_KEYUP (if key has been released). They share the same overall event record structure (SDL_KeyboardEvent). As a sidenote, all the SDL 2.0 events have a type_ field for obvious reason.

The next field timestamp obviously contains a timestamp of integer type which is used internally by SDL 2.0 to resolute the sequence in which all the events were triggered. All SDL 2.0 events have the timestamp field.

The windowID field is necessary to distinguish between events raised from different SDL 2.0 windows if there is more than one. Let’s assume your program has two SDL 2.0 windows, window1 and window2. These two windows have certain constant id’s allocated by SDL 2.0. Now, if you have the focus on window1 (meaning the active window is window1) and press a key, the event’s windowID contains the specific id for window1. If the active SDL 2.0 window is window2 and you press a key, the specific id of window2 will be present in windowID. This way you can easily distinguish for which window the program should react to the keyboard event, e.g. a pressed key. Anyway, the capability to handle more than one window has been introduced by SDL 2.0, so you can imagine that for many programs and if you do not have more than one window in your program, you may ignore this field. The windowID field isn’t present (and necessary) for all the events SDL 2.0 provides.

The 8 bit integer state field may be read out to get the state of the key (pressed or release) which are encoded by SDL_PRESSED and SDL_RELEASED. You may be a little bit confused what the difference between the pair SDL_PRESSED and SDL_RELEASED and the pair SDL_KEYDOWN and SDL_KEYUP is. Essentially they have the same meaning for a key of a keyboard. Formally, the difference is that SDL_KEYDOWN and SDL_KEYUP are two different event types whereas SDL_PRESSED and SDL_RELEASED are two different key states. In the case of a pressed key on a keyboard the state is kind of redundant because if you get a SDL_KEYDOWN event you already know that a key was pressed and to read out the state (which will be SDL_PRESSED) is unnecessary. Anyway, the state field seems to be introduced for completeness, since for other event types (e.g. mouse events) there is a huge difference if you move the mouse and have mouse buttons pressed or released.

_repeat let’s you know if the corresponding key 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”. 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 a value different from 0 (most likely 1) and otherwise it will be 0. 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. In SDL 1.2 I described here how to do it simply by using function called SDL_EnableKeyRepeat, this function is obsolete and does not exist in SDL 2.0 anymore!

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 you have a spaceship which should move left on pressing the “a”-key. Instead of changing it’s coordinates only once when the key down event is triggered, you start a switch (e.g. MoveSpaceshipLeft := true). The trigger is treated independently of the events treatment somewhere in the main game loop. As soon as the key up event is triggered for the “a”-key, the switch is turned off (e.g. MoveSpaceshipLeft := false).

I don’t know about meaning of the fields padding2 and padding3. Maybe they are kind of place holders for future developments or used internally.

Last but not least a very important field. The field keysym of SDL_KeySym type contains several information about the identity of the pressed key. In most cases we need to know which exact key has been pressed. Let’s have look into the SDL_KeySym record:

TSDL_Keysym = record
    scancode: TSDL_ScanCode;      // SDL physical key code 
    sym: TSDL_KeyCode;            // SDL virtual key code
    _mod: UInt16;                 // current key modifiers
    unicode: UInt32;              // (deprecated) 
  end;

As you can see the first two fields contain keycode representations for identitifcation of the key pressed or released. Even though both fields seem to have again special records, namely SDL_ScanCode and SDL_KeyCode, they actually consist of only one field each, DWord (integer type) for SDL_ScanCode and SInt32 (integer type) for SDL_KeyCode.

You may have a look into the SDL 2.0 scancode lookup table: https://wiki.libsdl.org/SDLScancodeLookup or SDL 2.0 Scancode lookup table (Backup)

The difference between scancode and key code

The difference is that the scancode refers to a specific physical location of a key on the keyboard. The scancode 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 I press the “Z” key on the German keyboard, the returned scancode will represent the “Y” key since the position of the key (independent of the layout) is equal to the position of the “Y” key on an US keyboard. Scancodes 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 scancode for the “Z”-key on a German keyboard will return that the “Y”-key has been pressed since the key has the location of the “Y”-key of an US keyboard. But the key code will not return it is the “Y”-key but it will correctly return that the “Z”-key has been pressed. The red marked output in the following image illustrates the result if the”Z” key is pressed on a German keyboard.

Difference Key code and Scancode

You may say, then I should always use keycodes to read out text input, right? – Wrong :-). In fact since SDL 2.0 it depends strongly on what you actually want to do. If you want to get a text or a single character from the user, you should neither use scancodes nor use key codes (anymore). (In former SDL 1.2 key codes or the unicode representation were indeed the preferable choice.) Treatment of real text input is not done this way anymore. We will discuss this case later. Anyway, this quote from the SDL 1.2 to SDL 2.0 migration guide sums it up excellently:

Use SDL_KEYDOWN to treat the keyboard like a 101-button joystick now. Text input comes from somewhere else.

Think of the famous T-shaped WASD key arrangement (arrangement of the four keys “W”, “A”, “S” and “D”) in the US layout, even if you keyboard without any latin letters, you may want to use these four keys 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.

Again, keep in mind:

Never use key codes or scancodes to read out text input in SDL 2.0 (anymore)

The remaining _mod field is a 16 bit unsigned integer (corresponds to Pascal’s Word) and represents key modifiers (ctrl, alt, shift, num lock, caps lock, …). If one or more key modifiers are pressed the _mod value has a unique number for each key or the key combination. For example, the left shift key has the decimal value 1, the right shift key has the value 2, the left control (ctrl) key has the value 64, the right ctrl key has the value 128. If the left shift and ctrl key are pressed at the same time the _mod vlue will be 65 (1 + 64). Let’s assume you want to have your application to be quit by the user pressing the ctrl key and “Q”. So you read out the key code for “Q” and check if _mod is 64 or 128. Since there doesn’t seem to exist a table for key modifiers, here the most important ones:

Modifier key decimal values
Modifier key UInt16 value
Left shift 1
Right shift 2
Left ctrl 64
Right ctrl 128
Left alt 256
Right alt 576 (64 + 512?)
Caps lock 8192
Num lock 4096

The unicode field is deprecated and will not be discussed here. By the way, also procedure SDL_EnableUnicode is gone, which turned on the unicode mode.

Text input in SDL 2.0

Let’s have a look in the next part of the code and learn about the correct text input in SDL 2.0.

        SDL_TEXTINPUT: begin
                         writeln( 'Text input: "', sdlEvent^.text.text, '"' );
                         text1 := text1 + sdlEvent^.text.text;
                         writeln( 'Full string: ' + text1 );
                       end;

With SDL 2.0 there is a new event type named SDL_TEXTINPUT. This one has been explicitly introduce to SDL 2.0 to make text input more flexible and easy.

Let’s have a look into the record structure of SDL_TextInputEvent.

TSDL_TextInputEvent = record
    type_: UInt32;                                          // SDL_TEXTINPUT 
    timestamp: UInt32;
    windowID: UInt32;                                       // The window with keyboard focus, if any
    text: array[0..SDL_TEXTINPUTEVENT_TEXT_SIZE] of Char;   // The input text 
  end;

In contrast to the event structure SDL_KeyBoardEvent where two event types (SDL_KEYDOWN and KEYUP) were available, at the moment for the event structure SDL_TextInputEvent only one event type, SDL_TEXTINPUT, is possible.

The timestamp and windowID field have been discussed earlier in great detail. They contain some general information about when this event was triggered and which application window had the focus when the event was triggered. You may scroll up to get more information about this.

Unique about this event structure is the field text which is an array of char elements. This array contains from 0 to SDL_TEXTINPUTEVENT_TEXT_SIZE char elements. SDL_TEXTINPUTEVENT_TEXT_SIZE has a size of 32 by default. Attention here, this doesn’t mean you can only have 32 characters in texts or something like this! It means that in the moment this event is triggered not more than 32 characters are submittable to the event. Keep in mind though, if you use a Western language with Latin characters you always only submit one chracter at a time by each key stroke.

So why there are 32 possible characters anyway then? – To understand this, it would be necessary to go deeper into the construction of non-Western languages and how words and sentences are constructed. The massive amount and complexity of the way words and sentences are constructed makes it so that these are constructed beforehand before they are submitted to the text field of SDL_TextInputEvent. These constructs are not simply created by just a single key stroke which could be read out. For these constructs there are 32 chars reserved. (I would be glad to give a more detailed explanation here, please feel free to contact me to improve this paragraph.)

Let’s go back to the code. If a SDL_TEXTINPUT event has been found, its record (SDL_TextInputEvent) can be accessed by text. Its text input information is stored in the text field which we have seen recently. That is why we can use

sdlEvent^.text.text

to access the text input information. In the first line of the code we simply print out the content of the text field. The string variable “text1” is used to store the found character and add to the characters which have been found before. This string is also printed out.

Note how special characters (e.g. currency symbols, French accent symbols, the German “ß” symbol, …) and capital letters are recognized correctly. Try this with key codes or scancodes (it is a pain).

Note also that function keys ( F1, Backspace, …) are not recognized as characters. It is your responsibility for them to work the desired way :-).

previous Chapter | next Chapter

SDL2_ttf: Rendering Fonts and Texts

Last updated on November 20th, 2021

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 standards how to save the font into a file, but the most important font file standard is the TrueType Font standard, thus the name SDL2_ttf. SDL2_ttf is capable to work with TrueType fonts. The typical file extension of TrueType font files is “.ttf”. FreeType 2.0 is no font file standard but a software library to render TrueType font files.

Fonts in Applications

In the last chapters you have heard a lot about the creation of textures from image files and how to manipulate and draw to them. 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. But 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? There are thousands of other circumstances where you need dynamically generated texts.

The SDL2_ttf Unit

Here comes the SDL2_ttf unit into play. The ability to do this is not implemented in the native SDL 2.0 library itself but the SDL project provides an official extension called SDL2_ttf to render TrueType fonts based on the FreeType project and their FreeType 2.0 release.

Installing SDL2_ttf

As for the work with SDL2_image you will need to add some library files to your system to use fonts since SDL2_ttf isn’t part of native SDL 2.0.

  • download the most recent version of the Runtime Binaries of the SDL2_ttf library for your system
  • install the library according to your system (Win32/64, Linux, Mac OS X)

SDL2_ttf Installing Instructions for Windows

Download the corresponding SDL2_ttf package depending on your system (32 bit or 64 bit) and extract the zip file. You will end up with a SDL2_ttf.dll and two more dlls (zlib1.dll, libfreetype-6.dll) which are necessary for support of compression and FreeType routines. Copy all these files to your system folder, e.g. for Windows XP or Windows 8.1 32 bit C:\WINDOWS\system32\. If you are not sure about your system folder, you should copy all these files into the same folder where the source code file (.pas or .pp) of your SDL 2.0 program is.

If you are running on a different platform (e.g. Mac OS X, Linux, …), please check the link given above for further information on SDL2_ttf installation.

The Road to Texts in SDL 2.0

Before jumping right into the code, let’s discuss the two concepts of SDL 2.0 of saving images in memory again. We prefer to work with textures because they have all the advantages we discussed in prior chapters, especially in Chapter – Surfaces and Textures. Anyway, in SDL2_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. Look at the following diagram to understand the way we need to go:

SDL2 text creation diagram
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 quadrat in diagram), first create a SDL_Surface by using one of the functions SDL2_ttf provides (lower path to quad at the bottom in the diagram). Next you convert this SDL_Surface into a SDL_Texture by the function SDL_CreateTextureFromSurface (go straight from the buttom quad to the upper quad in the diagram). Finally render the text as you like to the screen.

Sidenote, “blitting” and SDL_Flip are not possible in SDL2 anymore for good (performance) reasons.

Let’s jump into the code now .

program SDL_Fonts;

uses SDL2, SDL2_ttf;

var
  sdlSurface1 : PSDL_Surface;
  ttfFont : PTTF_Font;
  sdlColor1, sdlColor2 : TSDL_Color;
  sdlWindow1 : PSDL_Window;
  sdlRenderer : PSDL_Renderer;
  sdlTexture1 : PSDL_Texture;

begin

  //initilization of video subsystem
  if SDL_Init(SDL_INIT_VIDEO) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
  if sdlWindow1 = nil then HALT;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;


  //initialization of TrueType font engine and loading of a font
  if TTF_Init = -1 then HALT;
  ttfFont := TTF_OpenFont('C:\WINDOWS\fonts\Arial.ttf', 40);
  TTF_SetFontStyle(ttfFont, TTF_STYLE_UNDERLINE or 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;
  sdlColor2.r := 0; sdlColor2.g := 255; sdlColor2.b := 255;

  //rendering a text to a SDL_Surface
  sdlSurface1 := TTF_RenderText_Shaded(ttfFont, 'Hello World!', sdlColor1, sdlColor2);

  //convert SDL_Surface to SDL_Texture
  sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1);

  //rendering of the texture
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(5000);

  //cleaning procedure
  TTF_CloseFont(ttfFont);
  TTF_Quit;

  SDL_FreeSurface(sdlSurface1);
  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow(sdlWindow1);

  //shutting down video subsystem
  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 cyan. The following screenshot gives an impression what is to be expected.

Result screenshot for chapter 7

Let’s begin with the initial lines of code.

program SDL_Fonts;

uses SDL2, SDL2_ttf;

The program is called “SDL_Fonts” and we will need unit SDL2_ttf additional to SDL2 to have access to the TrueType font engine.

var
  sdlSurface1 : PSDL_Surface;
  ttfFont : PTTF_Font;
  sdlColor1, sdlColor2 : TSDL_Color;
  sdlWindow1 : PSDL_Window;
  sdlRenderer : PSDL_Renderer;
  sdlTexture1 : PSDL_Texture;

begin

  //initilization of video subsystem
  if SDL_Init(SDL_INIT_VIDEO) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
  if sdlWindow1 = nil then HALT;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;

Then we are preparing for some new types of variables. First of all we have a pointer variable called “sdlSurface1” which is of type PSDL_Surface and points at a SDL_Surface. The text message we provide will be rendered into this SDL_Surface first.

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 stored in a font file.

Finally there are two color variables “sdlColor1” and “sdlColor” of pointer type PSDL_Color. These pointers point at records which contain color data as composition of a r, b and g value. The exact definition will be shown when they are used in the code.

Well, the next variables are known from previous chapters. Also the initialization of SDL 2.0 and the creation of the window and the renderer are known already. So we should proceed to the next code chunk now.

//initialization of TrueType font engine and loading of a font
  if TTF_Init = -1 then HALT;
  ttfFont := TTF_OpenFont('C:\WINDOWS\fonts\Arial.ttf', 40);
  TTF_SetFontStyle(ttfFont, TTF_STYLE_UNDERLINE or TTF_STYLE_ITALIC);
  TTF_SetFontOutline(ttfFont, 1);
  TTF_SetFontHinting(ttfFont, TTF_HINTING_NORMAL);

To inialize the TrueType font engine

TTF_Init(): Integer

without any arguments is called which returns 0 on success and -1 on error. There is no specific error code returned.

TTF_OpenFont(_file: PAnsiChar; ptsize: Integer): PTTF_Font

prepares a font. The first parameter “_file” asks for the absolute file path of the font. The parameter “ptsize” 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 SDL 2.0

By procedures

TTF_SetFontStyle(font: PTTF_Font; style: Integer),

TTF_SetFontOutline(font: PTTF_Font; outline: Integer)

and

TTF_SetFontHinting(font: PTTF_Font; hinting: Integer)

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 as shown in the example code. The text shall 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 a 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 is applied
  2. TTF_HINTING_LIGHT
    • Light hinting is applied
  3. TTF_HINTING_MONO
    • I guess monospaced characters, so all the characters have the same space between each other
  4. TTF_HINTING_NONE
    • Have the text underlined

All of these three procedure have a counter-function which returns the set style, outline and hinting as an integer number, defined as

TTF_GetFontStyle(font: PTTF_Font): Integer,

TTF_GetFontOutline(font: PTTF_Font): Integer,

TTF_GetFontHinting(font: PTTF_Font): 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.

Colouring the Text using TSDL_Color/PSDL_Color

The next lines of code are waiting for us.

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

Here we are starting to consider some coloring of the text. The first color set up in “sdlColor1” of kind TSDL_Color will be the color of the letters. The second color set up in “sdlColor2” will be the background color of the letters. Let’s have a look at the definition of TSDL_Color:

TSDL_Color and RGB triples

  TSDL_Color = record
    r: UInt8;
    g: UInt8;
    b: UInt8;
    a: UInt8;
  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. But 100% red, 100% green and 0% blue will lead to yellow. 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 UInt8 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 creation

//rendering a text to a SDL_Surface
  sdlSurface1 := TTF_RenderText_Shaded(ttfFont, 'Hello World!', sdlColor1, sdlColor2);

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_Shaded(font: PTTF_Font; text: PAnsiChar; fg, bg: TSDL_Color): PSDL_Surface

It requires four arguments and returns a PSDL_Surface (no PSDL_Texture!), a pointer to a SDL_Surface. The first argument is the font you would like to use. Next is the actual text. The text can be directly written as shown in the example but also could be stored in a variable of type PAnsiChar.

PAnsiChar and Pascal Strings

Usually String variables are used in Pascal programming language, so why here we have a PAnsiChar? Well, since SDL is written in C/C++, and there typically null-terminated strings of characters are used to store and handle text, this is kept for better compatibility and easier translation of SDL 2.0 for Pascal compilers. In principle it is no big deal to use PAnsiChar instead of String, even though Pascal programmers prefer String variables.

Last but not least there are the two arguments “fg” and “bg” which translates to foreground and background. Here we insert the colors we defined before. That’s it.

Text Quality and Rendering Speed

Okay, as mentioned before there are more functions to create a SDL_Surface with text. In general there are three groups of functions according to their properties. The general naming scheme is as follows:

TTF_Render[encoding]_[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 last 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.

For each group of quality and functions you find “Text”, “UTF8”, “UNICODE” and “Glyph” in the encoding part of the functions name right after “TTF_Render”. “Text”, “UTF8” and “UNICODE” are three different types of encoding of the text. “Text” corresponds to Latin1 encoding, “UTF8” to UTF-8 encoding and “UNICODE” to Unicode encoding. Which type to choose depends on what type of characters (e.g. Cyrillic letters, Latin characters, Chinese characters) you are going to use. If you are unsure which of these functions to use, go with the “UNICODE” version.

For rendering a single character by its Unicode code, use the function which contains “Glyph” as suffix to “TTF_Render”.

Finally I’d like to mention a special function which is just available in high quality “Blended” mode. It has the suffix “_Blended_Wrapped”. Additional to the usual parameters there is a parameter wrapLength of type UInt32 (32 bit unsigned integer). Here you can have an integer value which determines the amount of pixels until the text will be word-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 word 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; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderUTF8_Solid(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderUNICODE_Solid(font: PTTF_Font; text: PUInt16; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderGlyph_Solid(font: PTTF_Font; ch: UInt16; 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; fg, bg: TSDL_Color): PSDL_Surface
      • TTF_RenderUTF8_Shaded(font: PTTF_Font; text: PAnsiChar; fg, bg: TSDL_Color): PSDL_Surface
      • TTF_RenderUNICODE_Shaded(font: PTTF_Font; text: PUInt16; fg, bg: TSDL_Color): PSDL_Surface
      • TTF_RenderGlyph_Shaded(font: PTTF_Font; ch: UInt16; fg, 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; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderUTF8_Blended(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderUNICODE_Blended(font: PTTF_Font; text: UInt16; fg: TSDL_Color): PSDL_Surface
      • TTF_RenderText_Blended_Wrapped(font: PTTF_Font; text: PAnsiChar; fg: TSDL_Color; wrapLength: UInt32): PSDL_Surface
      • TTF_RenderGlyph_Blended(font: PTTF_Font; ch: UInt16; fg: TSDL_Color): PSDL_Surface

All the functions will return nil on failure to create a SDL_Surface.

Converting a SDL_Surface into a SDL_Texture

Okay, now let’s proceed to the next lines of code.

//convert SDL_Surface to SDL_Texture
  sdlTexture1 := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface1);

//rendering of the texture
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(5000);

In the previous table you saw all the functions to generate SDL_Surfaces with a certain text. Now we need to transform it into a SDL_Texture. The function to do so by SDL_CreateTextureFromSurface as known.

Then the result will be rendered to the window. For simplicity the third and fourth argument for SDL_RenderCopy() are nil which means that the SDL_Texture with the text will be stretched to the window. The rendered result is shown for 5000 ms (5 seconds).

We can proceed to the cleaning process.

//cleaning procedure
  TTF_CloseFont(ttfFont);
  TTF_Quit;

  SDL_FreeSurface(sdlSurface1);
  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow(sdlWindow1);

//shutting down video subsystem
  SDL_Quit;

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 texture, renderer and window as known. After that SDL 2.0 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 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_Shaded(ttfFont, 'Hello World!', sdlColor1, sdlColor2));

Don’t 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 →

Logical Resolution

Last updated on November 20th, 2021

Now an awesome feature of SDL 2.0 will be presented which is fixing a big major problem for many programmers. It concerns the resolution of the application or game.

The Logical Resolution in SDL 2.0

If you create a game, often there are a lot of questions you just can solve partly by planning. The main question is: What exact resolution are the users going to use to play the game? Do their monitors support the resolution you decide for? Do the user/gamer use a wide screen monitor? And so on. Depending on the answers to these questions you may choose for sprite/image file resolutions. If you are going for a target resolution of 640 x 480 (VGA), you will choose less detailed source images as if you go for 1024 x 768 (XGA), or even widescreen 1680 x 1050 (WSXGA+) or any other high detail resolution. If every monitors/graphic boards would be capable of displaying all possible resolutions, this wouldn’t be much of a problem. The system would just switch to the necessary resolution and voila the game is running and looking like expected. Unfortunately though, you cannot expect a system whose maximum resolution allows for XGA to display WSXGA+ resolution. Also, even if the system is capable of displaying very high resolutions, there is no guarantee that it will be able to switch down to VGA resolution.

Furthermore, if you optimize a game for VGA resolution, you will choose the sprites/images according to fit into a 640 x 480 pixels screen. Lets say you are doing a racing game. You choose a car image of 64 pixels width and 32 pixels height. This fills a good amount of the screen in VGA resolution. In WSXGA+ resolution the car’s width and height will appear less than half, it will appear to be tiny. SDL 2.0 provides a solution to circumvent many of the troubles related to resolution settings: the logical resolution.

Let’s have a look at the code.

program SDL_LogicalResolution;

uses SDL2;

const
  Border:  TSDL_Rect = (x: 0;   y:   0; w: 640; h: 480);
  Quarter: TSDL_Rect = (x: 320; y: 240; w: 320; h: 240);

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;

begin

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

  if SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP, @sdlWindow1, @sdlRenderer) <> 0 then
    Halt;

  // set scaling filter
  SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 'linear');  

  // set logical resolution
  if SDL_RenderSetLogicalSize(sdlRenderer, 640, 480) <> 0 then
    Halt;

  // draw red border
  SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderDrawRect(sdlRenderer, @Border);

  // draw green quarter
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, SDL_ALPHA_OPAQUE);
  SDL_RenderDrawRect(sdlRenderer, @Quarter);

  // render to window for 3 seconds
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(3000);

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

  // closing SDL2
  SDL_Quit;

end.

This program basically draws two rectangles, a red one and a green one. They are defined by these constants.

const
  Border:  TSDL_Rect = (x: 0;   y:   0; w: 640; h: 480);
  Quarter: TSDL_Rect = (x: 320; y: 240; w: 320; h: 240);

The const “Border” is located at (0/0) and has a width of 640 px and a height of 480 px. “Quarter” is located at (320/240) and has a width of 320 px and a height of 240. Let’s assume you have a modern monitor and render these two rectangles fullscreen at a typical resolution of 1680×1050 or even higher. You would expect a result to be something like this:

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 expected result if your monitor has a resolution of 1680×1050 px, and you render a red rectangle at position (0/0) width dimension 640×480 px, and a green rectangle at position (320/240) with dimension 320×240 px.

But since we introduced a logical resolution in the program, we really get something like this:

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 on a 1680×1050 px monitor resolution, if a logical size of 640×480 px is used. Notice the black border (letter boxing) to the left and right of the red rectangle, because of the different aspect ratios, 16:10 (monitor) and 4:3 (640×480 target resolution).

How does SDL2 achieve a Logical Resolution?

Although your monitor resolution may be much larger (e.g. 1680×1050 px) than what your program is expecting (640×480 px), SDL2 will scale everything up or down just to fit your real resolution the best!

The function SDL_RenderSetLogicalSize() is used to achieve a logical resolution and returns 0 on success and the negative error code on failure.

SDL_RenderSetLogicalSize(renderer: PSDL_Renderer; w: SInt32; h: SInt32): SInt32.

It sets a device independent resolution for rendering. It will make use of scaling functions internally to fit the target resolution to the actual screen/device resolution. If you want to display a game in VGA (640 x 480) resolution on a XGA (1024 x 768) resolved screen, SDL_RenderSetLogicalSize() will scale up everything as necessary to fill the screen. Even if the ration of the device is different, for example you are trying to have a 4:3 ratio resolution on a wide screen 16:9 monitor, this function will just put some bars at appropriate places (see screenshot above) and fit the targeted resolution to the device resolution as good as possible. This is demonstrated in the example code. A target resolution of 640 pixels width x 480 pixels height (4:3 ratio) should be achieved in a device (window) of width x height of 1680 x 1050 pixels (16:10 ratio).

Back to the code. Fullscreen rendering at desktop resolution is achieved by the SDL_WINDOW_FULLSCREEN_DESKTOP flag.

  if SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP, @sdlWindow1, @sdlRenderer) <> 0 then
    Halt;

And then the logical size is set as discussed by SDL_RenderSetLogicalSize(). By the way, it is strongly recommended to set the scaling filter (SDL_SetHint()) to a better quality filter, because heavy scaling is done behind the scenes by SDL_RenderSetLogicalSize().

  // set scaling filter
  SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 'linear');
  
  // set logical resolution
  if SDL_RenderSetLogicalSize(sdlRenderer, 640, 480) <> 0 then
    Halt;

Actually that’s it :-), everything else, drawing and rendering rectangles and cleaning up memory is known to you.

← previous Chapter | next Chapter →

SDL2_image: Loading Image Files with Different Formats

Last updated on November 20th, 2021

Image files to use in games and applications often are available in other formats than simple bitmap files (e.g. jpg, png, tif and so forth), because often bitmap files need a significant higher amount of disk space memory.

Also it would be desirable to load an image file directly and create a SDL2 texture from it instead of firstly creating a SDL2 surface and then creating a SDL2 texture from the surface (as seen in the chapter about bitmap loading).

The SDL2_image Unit

Here comes the SDL2_image unit into play. It allows:

  • Loading of many common image formats
  • Supported formats: ICO, CUR, BMP, GIF, JPG, LBM, PCX, PNG, PNM, TIF, XCF, XPM, XV, WEBP
  • Creation of SDL2 texture from image files directly

These features are obviously not part of native SDL 2.0. SDL2_image is an official extension of SDL 2.0, which is developed and maintained by the same developers. Therefore, before right jumping into the code we need the corresponding library files.

Installing SDL2_image

  • download the most recent version of the Runtime Binaries of the SDL2_image library for your system
  • install the library according to your system (Win32/64, Linux, Mac OS X)

SDL2_image Installing Instructions for Windows

Download the corresponding SDL2_image package depending on your system (32 bit or 64 bit) and extract the file. You will end up with a SDL2_image.dll and some further dlls which are necessary for support of some image formats. Copy all these files to your system folder, e.g. for Windows XP 32 bit C:\WINDOWS\system32\. If you are not sure about your system folder, you should copy all these files to the same folder where the source code file (.pas or .pp) of your SDL 2.0 program is.

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 below 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 skip the step of first creating a SDL2 surface from the image file.

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.

Coding example with SDL2_image

And now let’s start with some code. I will show the code as a whole first and after that I will discuss it in smaller pieces from top to bottom until the “end.” 🙂

program Chapter4_SDL2;

uses SDL2, SDL2_image;

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

begin

  //initilization of video subsystem
  if SDL_Init(SDL_INIT_VIDEO) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
  if sdlWindow1 = nil then HALT;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;

  sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'C:\fpsdl.bmp');
  if sdlTexture1 = nil then HALT;
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent (sdlRenderer);
  SDL_Delay(2000);

  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //shutting down video subsystem
  SDL_Quit;

end.

Well, here it is. 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 Chapter4_SDL2;

uses SDL2, SDL2_image;

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

Now the first eight lines are discussed. Well, the first line starts a Pascal program as usual. In contrast to the previous chapter, the uses clause is extended by SDL2_image. To be clear again, native SDL 2.0 has no support for different image formats, except for BMP image files. Although native SDL 2.0 allows for loading of BMP image files, it just allows for creation of SDL_Surfaces, but we would like to create SDL_Textures.

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. All these variable types are pointer types which is indicated by the captial “P” at the beginning of the variable types’ names.

begin

  //initilization of video subsystem
  if SDL_Init(SDL_INIT_VIDEO) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
  if sdlWindow1 = nil then HALT;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;

As any Pascal program the main program’s begin-end block is initiated by “begin”. The initilization of SDL 2.0 is started as discussed in detail in the last chapter by SDL_Init().

After successful initialization of SDL 2.0 a window with title “Window1”  and a renderer is created as known from a previous chapter.

Creation of SDL_Texture and Rendering in SDL 2.0

  sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'C:\fpsdl.bmp');
  if sdlTexture1 = nil then HALT;
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent (sdlRenderer);
  SDL_Delay(2000);

Now we load the image file to the SDL_Texture we called “sdlTexture1”. The function to do this is

IMG_LoadTexture(renderer: PSDL_Renderer; _file: PAnsiChar): PSDL_Texture

This function is provided by SDL2_image. Its prefix is IMG instead of SDL for native SDL 2.0 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 “C:\fpsdl.bmp”. 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.

Next we would like the successfully loaded image in “sdlTexture1” to be rendererd for which reason we pass it to the renderer by function

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

At first this function asks for a renderer (and indirectly for the related window) to which we would like to copy the texture. In our case this will be “sdlRenderer” again. Next the texture to be copied to the renderer/window is required, this is “sdlTexture1” here. The last two parameters are named “srcrect” and “dstrect” and of type PSDL_Rect. PSDL_Rect is a SDL 2.0 predefined record to define rectangles, hence the name. I will not go into details about this here, but in the next chapter (Chapter 5) we will learn more about PSDL_Rect, although it will be in another context. For simplicity we just use nil as argument here. This makes the function to pass the full texture to the renderer/window and stretching it to the dimensions of the window. So the 200 x 200 pixel image is strechted to 500 x 500 pixels, the latter being the width and height of the window. This function returns 0 on success and the negative error code on failure.

Finally everything gets rendered to the window by the SDL_RenderPresent(Renderer) procedure as known from the previous chapter. To be able to see it, we wait with a 2 seconds delay.

Clean up the memory in SDL 2.0

  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //shutting down video subsystem
  SDL_Quit;

end.

We first created a window, then a renderer and finally a texture. So now we go the opposite way, first destroy the texture, then the renderer and finally the window. The procedures  are:

SDL_DestroyTexture(texture: PSDL_Texture)

SDL_DestroyRenderer(renderer: PSDL_Renderer)

and

SDL_DestroyWindow(window: PSDL_Window).

After removing the objects from memory, SDL 2.0 has to be quit as seen in the previous chapter.

Wow, we finally made it. Congratulations, this chapter is finished :-). The next chapter is waiting though.

← previous Chapter | next Chapter →

Chapter 8a: Converting SDL image to OpenGL texture (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

It is highly recommended that you read the previous Chapter 8. The code from last chapter was used and modified to show how the conversion works. However, I won’t explain twice everything already introduced in Chapter 8. Also I’d like to express here that NeHe Productions’ OpenGL tutorial 06 “Texture Mapping” and the translated (by Dominique Louis) Jedi-SDL file was inspiring me a lot for this chapter.

You need this software:

Software Version Source Description
OpenGL driver Usually your graphic card provides the corresponding OpenGL driver and you don’t have to do anything. And if so it is very likely that version 1.1 is fully supported. However if you are one of the few poor people whose graphic card doesn’t support OpenGL, check the graphic card’s manufacturer’s homepage for OpenGL drivers.

Now following the whole code at once as usual. As you will notice many lines are exactly the same as in Chapter 8.

PROGRAM chap8a;
USES CRT, SDL, GL, GLU;

VAR
userkey:CHAR;
screen, picture:pSDL_SURFACE;
h,hh,th,thh:REAL;
ogl_texture:pGLUINT;

BEGIN

//some calculations needed for a regular tetrahedron with side length of 1
h:=SQRT(0.75); //height of equilateral triangle
hh:=h/2;       //half height of equilateral triangle
th:=0.75;      //height of tetrahedron
thh:=th/2;     //half height of tetrahedron

SDL_INIT(SDL_INIT_VIDEO);

SDL_GL_SETATTRIBUTE(SDL_GL_RED_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_GREEN_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_DEPTH_SIZE, 16);
SDL_GL_SETATTRIBUTE(SDL_GL_DOUBLEBUFFER, 1);

screen:=SDL_SETVIDEOMODE(640, 480, 0, SDL_OPENGL);
IF screen=NIL THEN HALT;

//preparing SDL image
picture:=SDL_LOADBMP('C:\fpsdl256.bmp');
IF picture=NIL THEN HALT;

//preparing OpenGL texture
NEW(ogl_texture);
glGENTEXTURES(1, ogl_texture);
glBINDTEXTURE(GL_TEXTURE_2D, ogl_texture^);
glTEXIMAGE2D(GL_TEXTURE_2D, 0, 3, picture^.w, picture^.h, 0,
             GL_RGB, GL_UNSIGNED_BYTE, picture^.pixels);
glTEXPARAMETERi(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTEXPARAMETERi(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
SDL_FREESURFACE(picture);


glCLEARCOLOR(0.0, 0.0, 1.0, 0.0);
glVIEWPORT(0,0,640,480);
glMATRIXMODE(GL_PROJECTION);
glLOADIDENTITY;
gluPERSPECTIVE(45.0, 640.0/480.0, 1.0, 3.0);
glMATRIXMODE(GL_MODELVIEW);
glLOADIDENTITY;
glCLEAR(GL_COLOR_BUFFER_BIT);
glENABLE(GL_CULL_FACE);
glTRANSLATEf(0.0, 0.0, -2.0);

REPEAT
SDL_DELAY(50);

glROTATEf(5, 0.0, 1.0, 0.0);
glCLEAR(GL_COLOR_BUFFER_BIT);

//drawing textured face of tetrahedron
glENABLE(GL_TEXTURE_2D);
glBEGIN(GL_TRIANGLES);
    glTEXCOORD2f(2,2);
    glVERTEX3f(thh, 0.0, 0.0);
    glTEXCOORD2f(0,0);
    glVERTEX3f(-thh, hh, 0.0);
    glTEXCOORD2f(0,2);
    glVERTEX3f(-thh, -hh, 0.5);
glEND;
glDISABLE(GL_TEXTURE_2D);

//drawing remaining three untextured faces
glBEGIN(GL_TRIANGLES);
    glCOLOR3f(0.0, 1.0, 1.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, -hh, -0.5);
    glVERTEX3f(-thh, hh, 0.0);

    glCOLOR3f(1.0, 0.0, 1.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, -hh, 0.5);
    glVERTEX3f(-thh, -hh, -0.5);

    glCOLOR3f(1.0, 1.0, 1.0);
    glVERTEX3f(-thh, -hh, 0.5);
    glVERTEX3f(-thh, hh, 0.0);
    glVERTEX3f(-thh, -hh, -0.5);
glEND;

SDL_GL_SWAPBUFFERS;
UNTIL keypressed;

glDELETETEXTURES(1, ogl_texture);
DISPOSE(ogl_texture);
SDL_QUIT;
END.

This code will again draw a tetrahedron which is spinning, as known from Chapter 8. However, this time one face is textured with the “Free Pascal meets SDL” image known from Chapter 3. Now lets go through the code step by step.

PROGRAM chap8a;
USES CRT, SDL, GL, GLU;

VAR
userkey:CHAR;
screen, picture:pSDL_SURFACE;
h,hh,th,thh:REAL;
ogl_texture:pGLUINT;

The program is called “chap8a”. Additionally to the variables defined in the previous chapter there are two new variables. The SDL surface “picture” which will store the SDL image before converting it to an OpenGL texture. ogl_texture is an integer pointer variable (provided by the OpenGL Uitility Library (GLU), so pGLUINT) which is needed to reference to the OpenGL texture we will create from the SDL image.

BEGIN

//some calculations needed for a regular tetrahedron with side length of 1
h:=SQRT(0.75); //height of equilateral triangle
hh:=h/2;       //half height of equilateral triangle
th:=0.75;      //height of tetrahedron
thh:=th/2;     //half height of tetrahedron

SDL_INIT(SDL_INIT_VIDEO);

SDL_GL_SETATTRIBUTE(SDL_GL_RED_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_GREEN_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_DEPTH_SIZE, 16);
SDL_GL_SETATTRIBUTE(SDL_GL_DOUBLEBUFFER, 1);

screen:=SDL_SETVIDEOMODE(640, 480, 0, SDL_OPENGL);
IF screen=NIL THEN HALT;

The code shown here is discussed in detail in Chapter 8. In short the tetrahedron parameters are calculated, some important OpenGL scene settings are applied and finally the SDL video subsystem is intilized.

//preparing SDL image
picture:=SDL_LOADBMP('C:\fpsdl256.bmp');
IF picture=NIL THEN HALT;

First we should load a simple BMP image to a SDL surface as known from Chapter 3. There are some limitations about the height and length of images if used as OpenGL textures. Their pixel height and pixel length has to be power of 2. So whatever image you use, its height and lengths should fulfill the following equation: f(n) = 2n. So appropriate values are: 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,… The height and length don’t have to be of the same size, so an image with height 64 px and width 32 px is perfectly acceptable. This means the image of Chapter 3 with height and length of 200 x 200 px is not acceptable. A new image with 256 x 256 dimensions is therefore provided now:

Free Pascal meets SDL images with 256x256 dimensions
Free Pascal meets SDL images with 256×256 dimensions

//preparing OpenGL texture
NEW(ogl_texture);
glGENTEXTURES(1, ogl_texture);
glBINDTEXTURE(GL_TEXTURE_2D, ogl_texture^);
glTEXIMAGE2D(GL_TEXTURE_2D, 0, 3, picture^.w, picture^.h, 0,
             GL_RGB, GL_UNSIGNED_BYTE, picture^.pixels);
glTEXPARAMETERi(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTEXPARAMETERi(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
SDL_FREESURFACE(picture);

First the pointer “ogl_texture” gets some space. glGENTEXTURES(number of IDs, array of integer pointer) generates one or more OGL integer identifiers for textures. Anyway, we just have one texture, so we just need one texture identifier, therefore we request “1” and ogl_texture should point at it. If we need to identify the texture we just need to call ogl_texture from now on.

glBINDTEXTURE(target, texture) essentially creates a texture object of type: GL_TEXTURE_1D or GL_TEXTURE_2D. Usually textures in 2d and 3d games are two-dimensional, so GL_TEXTURE_2D is a good choice. Now it is clear, ogl_texture will be a 2d texture.

Briefly, glTEXIMAGE2D(target, mipmap level, internal image format, width, height, border, pixel format, pixel type, actual pixel data) creates the actual 2d texture. The target is GL_TEXTURE_2D again since we are looking for creating a 2d texture. The mipmap level should be set to 0 because we wouldn’t want to have a mipmap effect here. A higher number corresponds to the number’s mipmap level, anyway in the example for a number different from 0 there is no image at all finally. The internal image format is RGB because the image is a RGB image, anyway there is a large list of possibilities for this parameter, you should look it up in the internet if you’re interested. The width and height of the image in pixels is received from the SDL image. The border is off (values 0 and 1 are acceptable). The pixel format is RGB, too, so again SDL_RGB is the right choice here. The pixel explains how the pixel data is stored. The pixel data from the SDL image is stored as unsigned byte (GL_UNSIGNED_BYTE). Finally the pixel data pointer of the SDL image is needed. Essentially the SDL image is now transformed to an OGL texture!

Briefly, glTEXPARAMETERi(target, texture parameter, parameter value) allocates a certain value to a specific texture parameter. The possible parameters and values are OGL specific and won’t be treated here in more detail. Anyway, again we are concerned about our 2d texture, so the target is GL_TEXTURE_2D. The parameters to be set are GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER. They are used to define how to treat textures that have to be drawn to a smaller or larger scale. The routine used for this is specified by GL_LINEAR.

Since the SDL image isn’t needed anymore it can be omitted as known by SDL_FREESURFACE.

glCLEARCOLOR(0.0, 0.0, 1.0, 0.0);
glVIEWPORT(0,0,640,480);
glMATRIXMODE(GL_PROJECTION);
glLOADIDENTITY;
gluPERSPECTIVE(45.0, 640.0/480.0, 1.0, 3.0);
glMATRIXMODE(GL_MODELVIEW);
glLOADIDENTITY;
glCLEAR(GL_COLOR_BUFFER_BIT);
glENABLE(GL_CULL_FACE);
glTRANSLATEf(0.0, 0.0, -2.0);

This part is completely described in Chapter 8. Nothing has changed for this part. In short, the viewport is set up so that the tetrahedron finally can be seen.

REPEAT
SDL_DELAY(50);

glROTATEf(5, 0.0, 1.0, 0.0);
glCLEAR(GL_COLOR_BUFFER_BIT);

//drawing textured face of tetrahedron
glENABLE(GL_TEXTURE_2D);
glBEGIN(GL_TRIANGLES);
    glTEXCOORD2f(2,2);
    glVERTEX3f(thh, 0.0, 0.0);
    glTEXCOORD2f(0,0);
    glVERTEX3f(-thh, hh, 0.0);
    glTEXCOORD2f(0,2);
    glVERTEX3f(-thh, -hh, 0.5);
glEND;
glDISABLE(GL_TEXTURE_2D);

Now the REPEAT..UNTIL loop is entered which is delayed by 50 milliseconds by known SDL_DELAY. Each cycle the the scene gets rotated by 5 degrees around the y-axis by function glROTATEf. More details about this in Chapter 8.

The actual texturing of one of the four triangles of the tetrahedron is now described. Therefore 2d texturing has to be enabled by glENABLE(OGL capability). The capability we would like to enable is defined by GL_TEXTURE_2D.

Just as known from Chapter 8 the triangle mode is started by glBEGIN(geometric primitive type) with GL_TRIANGLES. Instead of a color we now define specific texture coordinates which should be allocated to specific vertices. glTEXCOORD2f(s coordinate, t coordinate) is used to define the coordinate of the texture we then allocate to a specific vertex. By the way, even though the official names of the texture coordinates are s and t, they can be considered as x and y values, which is more common for two-dimensional coordinate systems. The values for s and t are relative, so a value of 1 (= 100%) means the full width or height, independent of the actual width or height (32 x 32, 64 x 64, 128 x 256, …), a value of 2 (= 200%) then corresponds to two times the texture’s width or height. The coordinate (s, t) = (2, 2) is allocated to the vertex with the vertex coordinates (x, y, z) = (thh, 0.0, 0.0). Texture coordinate (0, 0) is allocated to vertex (thh, hh, 0.0). Texture coordinate (0, 2) is allocated to vertex (thh, hh, 0.5). Often this texturing process is compared to papering a wall, and indeed there are similarities. The vertex coordinates are exactly the same as for the first triangle in Chapter 8.

Finally the geometry definition and the texturing mode is finished by glEND and glDISABLE(OGL capability).

//drawing remaining three untextured faces
glBEGIN(GL_TRIANGLES);
    glCOLOR3f(0.0, 1.0, 1.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, -hh, -0.5);
    glVERTEX3f(-thh, hh, 0.0);

    glCOLOR3f(1.0, 0.0, 1.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, -hh, 0.5);
    glVERTEX3f(-thh, -hh, -0.5);

    glCOLOR3f(1.0, 1.0, 1.0);
    glVERTEX3f(-thh, -hh, 0.5);
    glVERTEX3f(-thh, hh, 0.0);
    glVERTEX3f(-thh, -hh, -0.5);
glEND;

SDL_GL_SWAPBUFFERS;
UNTIL keypressed;

The remaining three areas of the triangle are kept as in Chapter 8. Finally the display buffer if swapped after each cycle and the REPEAT..UNTIL loop stopped if a key is pressed in the console.

glDELETETEXTURES(1, ogl_texture);
DISPOSE(ogl_texture);
SDL_QUIT;
END.

Last but not least everything has to be free’s and closed as known. Anyway, the texture has to free’d by glDELETETEXTURES(number of textures, texture pointer). Then the pointer can be disposed as known and SDL can be quit.

Again, if you want to learn OpenGL and its capabilities to a more advaced extend you need to read more professional tutorials related to pure OpenGL programming. As a starting point I’d like to mention NeHe Productions’ OpenGL tutorials again, because they are professional and provide example code for JEDI-SDL for several lessons. 🙂

This file contains the source code: chap8a.pas (right click and “save as”)
This file is the executable: chap8a.exe (right click and “save as”)

The final result should look and behave like this: A tetrahedron consisting of three different coloured areas (cyan, magenta and white) and one textured area is spinning slowly around itself. When pressing a key in the console the show quits.

Result of JEDI-SDL Chapter 8a

Chapter 8: SDL and OpenGL – Entering the third dimension (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

This chapter will introduce you on how to combine the SDL library with the famous Open Graphics Library (OpenGL). OpenGL is the first choice when it comes to platform independent 2d and 3d graphic programming. As long as you just want to do 2d programming you can stay with SDL and there is no need to use OpenGL (even though OpenGL is also capable of doing 2d graphics). However, if your project needs 3d graphics you can set up your system for this quite easy using OpenGL in SDL. The main advantages of SDL here are that any other task except the graphics is further done by SDL (e.g. keyboard handling, sound,…) to keep the platform independence and ease. Further the setup of your SDL environment for the usage of OpenGL is very easy (compared to the setup without SDL) AND also is platform independent. Without SDL you would’ve to write different code to set up OpenGL for each operating system.

You need this software:

Software Version Source Description
OpenGL driver Usually your graphic card provides the corresponding OpenGL driver and you don’t have to do anything. And if so it is very likely that version 1.1 is fully supported. However if you are one of the few poor people whose graphic card doesn’t support OpenGL, check the graphic card’s manufacturer’s homepage for OpenGL drivers.

There is support for some others OpenGL related units, like GLUT (OpenGL Utility Toolkit) which provides a simple windowing application programming interface, GLX (OpenGL Extension to the X Window system) which provides a binding to use OpenGL in X Window windows (Linux), GLEXT (OpenGl Extensions) which provide additional functions since version 1.1 of OpenGL from 1996. I didn’t try out any of the latter mentioned units but if you progress in learning and using OpenGL especially GLEXT might get interesting to you since it provides the up-to-date functionality.

Now following the whole code at once as usual.

PROGRAM chap8;
USES CRT, SDL, GL, GLU;

VAR
userkey:CHAR;
screen:pSDL_SURFACE;
h,hh,th,thh:REAL;

BEGIN
//some calculations needed for a regular tetrahedron with side length of 1
h:=SQRT(0.75); //height of equilateral triangle
hh:=h/2;       //half height of equilateral triangle
th:=0.75;      //height of tetrahedron
thh:=th/2;     //half height of tetrahedron

SDL_INIT(SDL_INIT_VIDEO);

SDL_GL_SETATTRIBUTE(SDL_GL_RED_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_GREEN_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_DEPTH_SIZE, 16);
SDL_GL_SETATTRIBUTE(SDL_GL_DOUBLEBUFFER, 1);

screen:=SDL_SETVIDEOMODE(640, 480, 0, SDL_OPENGL);
IF screen=NIL THEN HALT;

glCLEARCOLOR(0.0, 0.0, 1.0, 0.0);
glVIEWPORT(0,0,640,480);
glMATRIXMODE(GL_PROJECTION);
glLOADIDENTITY;
gluPERSPECTIVE(45.0, 640.0/480.0, 1.0, 3.0);
glMATRIXMODE(GL_MODELVIEW);
glLOADIDENTITY;
glCLEAR(GL_COLOR_BUFFER_BIT);
glENABLE(GL_CULL_FACE);
glTRANSLATEf(0.0, 0.0, -2.0);

REPEAT
SDL_DELAY(50);

glROTATEf(5, 0.0, 1.0, 0.0);
glCLEAR(GL_COLOR_BUFFER_BIT );

glBEGIN(GL_TRIANGLES);
    glCOLOR3f(1.0, 1.0, 0.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, hh, 0.0);
    glVERTEX3f(-thh, -hh, 0.5);

    glCOLOR3f(0.0, 1.0, 1.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, -hh, -0.5);
    glVERTEX3f(-thh, hh, 0.0);

    glCOLOR3f(1.0, 0.0, 1.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, -hh, 0.5);
    glVERTEX3f(-thh, -hh, -0.5);

    glCOLOR3f(1.0, 1.0, 1.0);
    glVERTEX3f(-thh, -hh, 0.5);
    glVERTEX3f(-thh, hh, 0.0);
    glVERTEX3f(-thh, -hh, -0.5);
glEND;

SDL_GL_SWAPBUFFERS;
UNTIL keypressed;

SDL_QUIT;
END.

What this code will do is to switch to the OpenGL mode of SDL and draw a spinning tetrahedron. When you press a key the program aborts. However, some identifiers mentioned in the code are not related to SDL but OpenGL. Furthermore there are some functions which are related to OpenGL but are provided by SDL. Pure OpenGL identifiers begin with GL_ (e.g. GL_TRIANGLES) or gl (e.g. glCLEARCOLOR) whereas SDL provided OpenGL functions begin with SDL_GL_ (e.g. SDL_GL_SETATTRIBUTE).

PROGRAM chap8;
USES CRT, SDL, GL, GLU;

VAR
userkey:CHAR;
screen:pSDL_SURFACE;
h,hh,th,thh:REAL;

BEGIN
//some calculations needed for a regular tetrahedron with side length of 1
h:=SQRT(0.75); //height of equilateral triangle
hh:=h/2;       //half height of equilateral triangle
th:=0.75;      //height of tetrahedron
thh:=th/2;     //half height of tetrahedron

The program is called “chap8”. We use CRT as known from many former chapters to provide an easy way to recognize if a key got pressed. We need SDL for SDL support. New are the OpenGL units GL and GLU which are both included uncompiled in the JEDI unit package as well as pre-compiled along with the FPC compiler.

We need a variable “userkey” to recognize the user pressing a button. This is unrelated to SDL and OpenGL. The screen variable will display the scene later and is known from every former chapter. Four REAL variables are needed for some calculations regarding a tetrahedron.

The first four lines of code after BEGIN are needed to have some pre-calculated values at hand when it comes to constructing the tetrahedron later. h is the height of a equilateral triangle when each side has the length of one. hh corresponds to the half height meaning the value of h devided by two. th is the height of the tetrahedron (from any of the four possible bases to the corresponding peak) constructed of equilateral triangles. thh means the half value of th. These expressions result just from some geometry (Pythagorean theorem) and aren’t related directly to SDL or OpenGL.

SDL_INIT(SDL_INIT_VIDEO);

SDL_GL_SETATTRIBUTE(SDL_GL_RED_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_GREEN_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SETATTRIBUTE(SDL_GL_DEPTH_SIZE, 16);
SDL_GL_SETATTRIBUTE(SDL_GL_DOUBLEBUFFER, 1);

screen:=SDL_SETVIDEOMODE(640, 480, 0, SDL_OPENGL);
IF screen=NIL THEN HALT;

First of all we initialize SDL as known. Attention now: Before setting up the video mode for OpenGL we have to set all needed attributes of the OpenGL environment. If you set them afterwards they won’t be recognized and default values are used by OpenGL. The function to do so is SDL_GL_SETATTRIBUTE(attribute, value). The function returns the integer 0 if setting was successful, -1 otherwise. The corresponding function to read out the set value of an attribute is SDL_GETATTRIBUTE(attribute, value) with the same error checking values. The attribute’s value is written to value. This last function is not used in the code.

The following table (Source: JEDI-SDL documentation) shows all possible attributes which can be set:

Attribute Description
SDL_GL_RED_SIZE Size of the framebuffer red component,
in bits
SDL_GL_GREEN_SIZE Size of the framebuffer green component,
in bits
SDL_GL_BLUE_SIZE Size of the framebuffer blue component,
in bits
SDL_GL_ALPHA_SIZE Size of the framebuffer alpha component,
in bits
SDL_GL_DOUBLEBUFFER 0 or 1, enable or disable double buffering
SDL_GL_BUFFER_SIZE Size of the framebuffer, in bits
SDL_GL_DEPTH_SIZE Size of the depth buffer, in bits
SDL_GL_STENCIL_SIZE Size of the stencil buffer, in bits
SDL_GL_ACCUM_RED_SIZE Size of the accumulation buffer red
component, in bits
SDL_GL_ACCUM_GREEN_SIZE Size of the accumulation buffer green
component, in bits
SDL_GL_ACCUM_BLUE_SIZE Size of the accumulation buffer blue
component, in bits
SDL_GL_ACCUM_ALPHA_SIZE Size of the accumulation buffer alpha
component, in bits
Source of table and content: JEDI-SDL documentation.

Each pixel of the screen contains three different colour components (red, green, blue). We want each pixel’s colour component’s size to be five bits. This is a default value for the colour components. The depth buffer (also called Z-buffer) will get 16 bits of size, also a default value. Finally we allow double buffering. Be careful here. The double buffering for an OpenGL scene is not enabled by the SDL flag SDL_DOUBLEBUF in the video set function but is set by SDL_GL_SETATTRIBUTE(SDL_GL_DOUBLEBUFFER, 1). For any other attributes, its meanings and its values refer to OpenGL tutorials which you find anywhere in the Internet.

screen:=SDL_SETVIDEOMODE(640, 480, 0, SDL_OPENGL);
IF screen=NIL THEN HALT;

The goal is to draw an OpenGL scene within a SDL environment to have access to all the advantages each library provides. So after setting all necessary attributes of the OpenGL scene you have to create a SDL window which is able to display an OpenGL scene. To set up such a window you use the common SDL_SETVIDEOMODE(parameters) function. The first two parameters determine the window’s size as known. The values for the width and the height should be consistent with the values for the OpenGL viewport which will be set later.

The third parameter determining the colour depth of a SDL scene should be ignored and set to 0 if you want to set up an OpenGL scene. The colour depth of an OpenGL scene is set up as shown before with SDL_GL_SETATTRIBUTES(parameters). However, any value different from 0 will not affect the OpenGL scene in any way.

To set up an OpenGL scene successfully you must add the SDL_OPENGL flag as shown. However, you can combine it with any other window appearance flag (e.g. fullscreen, without border, …) by using the logic operator OR as known.

glCLEARCOLOR(0.0, 0.0, 1.0, 0.0);
glVIEWPORT(0,0,640,480);
glMATRIXMODE(GL_PROJECTION);
glLOADIDENTITY;
gluPERSPECTIVE(45.0, 640.0/480.0, 1.0, 3.0);
glMATRIXMODE(GL_MODELVIEW);
glLOADIDENTITY;
glCLEAR(GL_COLOR_BUFFER_BIT);
glENABLE(GL_CULL_FACE);
glTRANSLATEf(0.0, 0.0, -2.0);

The following descriptions regard to pure OpenGL programming. Detailed descriptions you’ll find in any OpenGL tutorial. However, I will give a brief overview over these functions.

glCLEARCOLOR(parameters) sets the background colour used if you call glCLEAR to reset the colour buffer. glCLEARCOLOR(parameters) expects four float point values corresponding the red, blue, green colour and the alpha channel (for transparency). Any value is a number between 0.0 and 1.0 where 0.0 means no addition of the corresponding colour component or transparency and 1.0 full addition of the corresponding colour component or complete transparency. In the example code we set up a completly blue background without transparency.

glVIEWPORT(parameters) sets the viewport of the OpenGL scene. The first two parameters are integer x- and y-values for the actual position in space, 0/0 is the default setting. The lower-left corner of the viewport is therefore at position 0/0 by default. The next two parameters define the width and height of the scene’s projection to the physical screen. Remeber please, these two values should be the same as has been used for the window width and height when using SDL_SETVIDEOMODE(parameters). However, it isn’t forbidden to use different values. We use a width of 640 and height of 480 pixels.

glMATRIXMODE(parameter) sets the matrix which you’d like to manipulate. It allows different parameters. Very common values are GL_PROJECTION and GL_MODELVIEW, further parameters are GL_TEXTURE and GL_COLOR. The following matrix operations (glTRANSLATEf, glROTATEf,…) are applied to the set matrix’ stack. Often this function is followed by glLOADIDENTITY. glLOADIDENTITY replaces the current matrix by the identity matrix. All elements of the identity matrix equal 0 except for the diagonal elements which are 1.

First we need to specify the projection transformation. Therefore we set the matrix mode by parameter GL_PROJECTION. Either we choose a perspective projection that reflects a 3d world, so objects with a great z value appear smaller than objects with a lower z value, or we choose an orthographic projection that reflects a 2d world, so objects will be drawn independently of their z value. The latter mode is usually used for text, menus, buttons, even complete side scrolling 2d games. Since we’d like to render a tetrahedron, we set up a persepctive projection matrix by gluPERSPECTIVE(parameters). The prefix “glu” indicates it’s a function from the OpenGL utilities unit. Instead we also could have used the glFRUSTUM(parameters) function which is part of the core OpenGL unit to generate such a matrix but gluPERSPECTIVE(parameters) makes it easy to get a matrix for distortionless display of the resulting projection. The first parameter is the field of view angle (fovy) in degrees. The second parameter is the aspect, usually the aspect ratio of the window dimensions width/height, here 640/480. The last two parameters are the (positive) z values for the near and far clipping plane of the generated viewing frustum. The generated perspective projection matrix corresponds to a viewing frustum.

Next we have to set up the viewing and modeling transformation. We switch to this matrix mode by GL_MODELVIEW as argument for GL_MATRIXMODE(parameter). We replace the matrix stack with the identity matrix. Now any subsequent matrix operations (glTRANSLATEf, glROTATEf,…) are applied to this matrix. It is replaced by the identity matrix and ready for further manipulations. This mode lets you manipulate the actual objects or models, e.g. a tetrahedron.

The color buffer gets cleared by glCLEAR(GL_COLOR_BUFFER_BIT). Then back-face culling is enabled by GL_ENABLE(GL_CULL_FACE). If you draw e.g. a triangle it has two faces. A front face and a back face. In a tetrahedron consisting of four triangles the inner faces are never seen. To avoid drawing them the back-face culling gets enabled so OpenGL isn’t drawing them. Finally the scene gets translated along the z-axis about -2.0 units by glTRANSLATEf(parameters) with x, y and z as parameters.

REPEAT
SDL_DELAY(50);

glROTATEf(5, 0.0, 1.0, 0.0);
glCLEAR(GL_COLOR_BUFFER_BIT );

glBEGIN(GL_TRIANGLES);
    glCOLOR3f(1.0, 1.0, 0.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, hh, 0.0);
    glVERTEX3f(-thh, -hh, 0.5);

    glCOLOR3f(0.0, 1.0, 1.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, -hh, -0.5);
    glVERTEX3f(-thh, hh, 0.0);

    glCOLOR3f(1.0, 0.0, 1.0);
    glVERTEX3f(thh, 0.0, 0.0);
    glVERTEX3f(-thh, -hh, 0.5);
    glVERTEX3f(-thh, -hh, -0.5);

    glCOLOR3f(1.0, 1.0, 1.0);
    glVERTEX3f(-thh, -hh, 0.5);
    glVERTEX3f(-thh, hh, 0.0);
    glVERTEX3f(-thh, -hh, -0.5);
glEND;

SDL_GL_SWAPBUFFERS;
UNTIL keypressed;

SDL_QUIT;
END.

Don’t be worried. This piece of code looks big but this is just because of the definition of the actual tetrahedron. First the REPEAT loop is entered. Every cycle is delayed by 50 milliseconds by the known SDL_DELAY(time in ms) function. Next the scene gets rotated. This can be achieved by the OpenGL function glROTATEf(parameters). You’ve to pass four arguments. The degree of rotation first, then three values which define the orientation of the axis around which is rotated. A triple (1.0 / 0.0 / 0.0) or (0.0 / 1.0 / 0.0) or (0.0 / 0.0 / 1.0) corresponds to a rotation around a x- or y- or z-axis respectivly. However, by linear combination of all the three components you can orient the rotation axis completly free in 3d space. For the tutorial with each cycle of the loop the tetrahedron is rotated by 5 degrees around the y axis. The color buffer gets cleared so the tetrahedron from the previous cycle disappears before drawing a new one (rotated by 5 degrees).

Now we need to construct the tetrahedron. Such constructions of objects is always done between a glBEGIN and glEND clause. Yes, even C/C++ programmers have to use Pascal’s own best BEGIN/END clause here :). Well, the glBEGIN (geometric primitive type) command needs an argument which defines what type of primitive is drawn. In our case we want to draw triangles, so we use GL_TRIANGLES here. Further common arguments are GL_POINTS, GL_LINES, GL_QUADS and GL_POLYGON, for points, lines, quads and polygons. Depending of this setting the vertices given within the glBEGIN/glEND block are interpreted. If you chose GL_TRIANGLES, three vertices are interpreted to form one triangles. However, for GL_LINES, only two vertices are needed. The first and the second vertex from our example would form one line. The third vertex would be interpreted to be the starting point for a new line, the first vertex of the next triangle block would be used as second point to form the second line and so on. Note, for 3d games usually triangles are used since most graphic cards seem to be optimized to draw them.

A tetrahedron consists of four triangular areas. Each trianglular area has three corners, these corners are called vertices in OpenGL. Within the glBEGIN/glEND clause the code has four blocks, each containing three glVERTEX3f(coordinates) and one glCOLOR3f(RGB components) commands. Each block creates one triangular area of the tetrahedron consisting of 3 vertices. Every triangular area also gets its own colour. For glCOLOR3f(RGB components) you use the three normalized colour codes for the red, green and blue component. 1.0 corresponds to 255 and means full addition of this colour component, 0.0 corresponds to 0 and means no addition of this colour component. As you may have noticed in the example always two colour components are used, e.g. red and green leads to yellow. You may prefer other combinations.

For the actual creation of a geometric primitive, like a triangle, just place the corresponding number of vertices in the 3d space and OpenGL will do the rest. It is important though to consider the order of placing the vertices. The order of placing the vertices determines what will be the front and back of the triangles. If you don’t consider this you may end up with curious results after doing back-face culling as described. Remeber: Back-face culling means, that the back of each primitive isn’t drawn. The way the triangles are created in the examples all areas have their front showing while their back is inside the tetrahedron. So without hestiation you can leave them undrawn. Imagine though what would be the result if one of the triangles would have been flipped because of placing the vertices in the wrong order. The undrawn back would show up. The impression of a rotating tetrahedron would be messed up. To get the front showing up instead of the back you have to place the vertices anticlockwise! The arguments for glVERTEX3f(x, y, z coordinate) are just the cartesian coordinates of the vertex you want to place.

Since I decided to give a rather complicated example for this tutorial (usually OpenGL tutorials explain this by cubes and six quads) I will examine the code here in more detail for the first triangle. If you’ve understood it, you’ll do cubes just by the way :). The coordinates for the first vertex are (thh / 0 / 0), the next vertex are (-thh / hh / 0) and the last vertex are (-thh / -hh / 0.5). The following image will show you how the vertices are placed in the cartesian space.

Coordinates in OpenGLAs you can check quickly, the vertices are indeed placed anticlockwise. The origin of the coordinate system (0 / 0 / 0) is approximately in the center of the triangle, actually it is in the center of the tetrahedron, when it is finished. The first vertex is placed onto the x axis at thh, the half height of the tetrahedron. The second vertex is placed on the opposite x position of the origin at -thh and has a y value of hh, the half height of an equilateral triangle. This point is still in the x/y-plane formed by the x and y axis. The last vertex has a x value of -tth and and y value of -hh, because of the z value of 0.5 this vertex doesn’t belong to the x/y-plane the other two vertices belong to. It is in front of this plane. You’ll notice a slight distorted appearance of the triangle for the third vertex, just as if it would bend towards you, that is because it actually is.

Now try to check if you understand the other vertices coordinates and if you understand why their front and not their back is showing. The loop’s last command is SDL_GL_SWAPBUFFERS which corresponds to SDL_FLIP for a pure SDL scene. It refreshes the scene. In case you have an OpenGL scene you never use SDL_FLIP or SDL_UPDATERECT to refresh the scene!

The loop quits if a key gets pressed. Then SDL and the Pascal program quit as known. If you want to learn OpenGL and its capabilities to a more advaced extend you need to read more professional tutorials related to pure OpenGL programming. As a starting point I’d like to mention NeHe Productions’ OpenGL tutorials, because they are professional and provide example code for JEDI-SDL for several lessons.

This file contains the source code: chap8.pas (right click and “save as”)
This file is the executable: chap8.exe (right click and “save as”)

The final result should look and behave like this: A tetrahedron consisting of four different coloured areas (yellow, cyan, magenta and white) is spinning slowly around itself. When pressing a key in the console the show quits.

Result of JEDI-SDL Chapter 8

Chapter 5: Text and font handling (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

This chapter will introduce you on how to load fonts and write to the screen. The ability for this is not implemented in the original SDL library itself but Sam Lantinga provides an add-on to SDL called SDL_TTF to work with texts based on the FreeType project and their FreeType 2.0 release. Fortunately the JEDI-SDL project also provides this unit for Free Pascal called SDL_TTF as well. So for preparation we have to do three things (sorry the following instructions are for Windows only but should be similar for Linux system as well. The examples after the three steps of installing should work for Linux, too.):

You need this .dll file:

Software Version Source Description
SDL_ttf-2.0.10-win32.zip 2.0.10 http://www.libsdl.org/projects/SDL_ttf/ This is the corresponding dynamic link library file.

You should extract the zip-file and get two files. A text file and the important SDL_ttf.dll. Analogous to SDL.dll in chapter 1 you have to copy them to the system32-folder. If you forget this and run the examples below you will get an error with exitcode = 309.

First of all we want to discuss the principle behind adding text to the screen within the SDL environment. Any text which is written to the screen is a simple surface itself which gets blit to the screen surface as done with graphics seen in previous chapters. In practice this means we need the screen surface again and a surface onto which can be written (here: “fontface”). The text content in the fontface surface will then be blitted to the screen surface. A simple diagram may illustrate this.

The concept of using fontsYou probably wondered already how to get the text to the “fontface” though. Exactly therefore we need the new SDL_TTF unit which has to be loaded by adding SDL_TTF to the uses clause. It provides the command TTF_RENDERTEXT_…(font, text, colour) which creates a surface with a given font, text and colour. This procedure is illustrated by the first arrow in the diagram. In fact there is not just one but many different TTF_RENDERTEXT_… modes which differ (hinted at by the three dots) in arguments list, quality, render speed and other properties. The following table will give you an overview. For dynamic ingame text or chats the solid mode is the best choice since it is the fastest rendering mode and also provides simple transparency. The following table will give you a brief overview of the modes and their properties.

Function Transparency Antialiasing Colour depth and format Quality Speed
TTF_RENDERTEXT_… in general creates surfaces with the given text (of type pChar) in ISO 8859-1 (Latin1) format; analogous you can use TTF_RENDERUTF8_… to get the the text in the corresponding UNICODE Transformation Format 8.
TTF_RENDERTEXT_SOLID(font, text, colour); yes (colorkey, 0 pixel) no 8-bit palettized (RGB) low very fast
TTF_RENDERTEXT_SHADED(font, text, colour1, colour2); no (0 pixel is background colour) yes 8-bit palettized (RGB) high fast
TTF_RENDERTEXT_BLENDED(font, text, colour); yes (alpha channel) yes 32-bit unpalettized (RGBA) very high slow
TTF_RENDERGLYPH_… in general creates surfaces with the corresponding UNICODE glyph letter (of type WORD); analogous you can use TTF_RENDERUNICODE_… to get the corresponding UNICODE letter but attention: you have to give the letter’s number as pointer, so number is of type ^WORD.
TTF_RENDERGLYPH_SOLID(font, number, colour); yes (colorkey) no 8-bit palettized (RGB) low very fast
TTF_RENDERGLYPH_SHADED(font, number, colour1, colour2); no (0 pixel is background colour) yes 8-bit palettized (RGB) high fast
TTF_RENDERGLYPH_BLENDED(font, number, colour); yes (alpha channel) yes 32-bit unpalettized (RGBA) very high slow

All of the given commands are functions and will return the NIL pointer (instead of the new surface) if the text or letter rendering failed. Let’s have a look at the code.

PROGRAM chap5;

USES SDL, SDL_TTF;

VAR
screen, fontface:pSDL_SURFACE;
loaded_font:pointer;
colour_font, colour_font2:pSDL_COLOR;
i:BYTE;

BEGIN
SDL_INIT(SDL_INIT_VIDEO);
screen:=SDL_SETVIDEOMODE(400,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;


IF TTF_INIT=-1 THEN HALT;
loaded_font:=TTF_OPENFONT('C:\WINDOWS\fonts\arial.ttf',40);

NEW(colour_font);
NEW(colour_font2);
colour_font^.r:=255; colour_font^.g:=0;    colour_font^.b:=0;
colour_font2^.r:=0;  colour_font2^.g:=255; colour_font2^.b:=255;

fontface:=TTF_RENDERTEXT_SHADED(loaded_font,'HELLO WORLD!',colour_font^,colour_font2^);


SDL_BLITSURFACE(fontface,NIL,screen,NIL);
SDL_FLIP(screen);
READLN;

DISPOSE(colour_font);
DISPOSE(colour_font2);
SDL_FREESURFACE(screen);
SDL_FREESURFACE(fontface);
TTF_CLOSEFONT(loaded_font);
TTF_QUIT;
SDL_QUIT;
END.

As always now we look at the code step by step.

PROGRAM chap5;

USES SDL, SDL_TTF;

VAR
screen, fontface:pSDL_SURFACE;
loaded_font:pointer;
colour_font, colour_font2:pSDL_COLOR;
i:BYTE;

The program is initilized with the name “chap5”. The unit SDL as well SDL_TTF has to be given in the uses clause! Remember please that the True Type Font system is an individual separate project and therefore you have to add this unit separately.

There are two pSDL_SURFACE variables. screen is for displaying at the physical screen as known, fontface will store the generated text by the True Type Font system before it is blitted to the screen finally. loaded_font will store font and is of pointer type. colour_font and colour_font2 are storing the text and background colour of the generated text later, they are of a specific SDL colour type which will be discussed in more detail later. i is a counting variable as known.

BEGIN
SDL_INIT(SDL_INIT_VIDEO);
screen:=SDL_SETVIDEOMODE(400,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;


IF TTF_INIT=-1 THEN HALT;
loaded_font:=TTF_OPENFONT('C:\WINDOWS\fonts\arial.ttf',40);

First the SDL system has to initilized as known from all the previous chapters. To initialize the TTF (True Type Font) system you have to use TTF_INIT which returns -1 if something failed. Notice again that the whole true type support is an own additional project (FreeType project) to the SDL library, so this cannot to be initilized by the SDL_INIT command.

To load a certain font you use TTF_OPENFONT(font,point size), or more specific TTF_OPENFONT(const filename:PCHAR; ptsize:INTEGER):pTTF_FONT. This command is a function that returns a usual pointer (which pTTF_FONT actually is)! The parameters are the absolute(!) path to the font (e.g. C:\WINDOWS\fonts\arial.ttf) and the point size which detemines the size of the letters. These font information are accessed by the loaded_font pointer initially defined.

NEW(colour_font);
NEW(colour_font2);
colour_font^.r:=255; colour_font^.g:=0;    colour_font^.b:=0;
colour_font2^.r:=0;  colour_font2^.g:=255; colour_font2^.b:=255;

So next we have to determine the colour of the letters and the message itself. The colour is determined by a pSDL_COLOR record, which is of course kind of pointer. In pSDL_COLOR record there are three elements (actually four, but the forth is unused) which can be accessed by .r,.g and .b and determine as common the shares of red, green and blue colour. You will agree that writing a function allocation of RGB triples can be senseful, especially if you have to handle many different colours but for our example we won’t do this though since we just have to define two colours. The fact that pSDL_COLOR is of pointer type needs us to set colour_font and colour_font2 up by NEW command and free it finally by DISPOSE. These commands you should be familiar with because they are usual Pascal commands.

fontface:=TTF_RENDERTEXT_SHADED(loaded_font,'HELLO WORLD!',colour_font^,colour_font2^);

Now we use TTF_RENDERTEXT_SHADED(font, text, colour1, colour2), or more specific TTF_RENDERTEXT_SHADED(font:pTTF_FONT; const text:PCHAR; fg:tSDL_COLOR; bg:tSDL_COLOR):pSDL_SURFACE, which we introduced recently (check the table). It returns a pSDL_SURFACE. We return it to fontface. The parameters are the used font as pointer (loaded_font), the message string and the colours (colour_font, colour_font2). colour_font will provide the foreground colour which should be red since we defined R=255, G=0, B=0. The background should get cyan since we defined R=0, G=255, B=255. If the rendering process works fine we have the text in the specified colours in fontface.

SDL_BLITSURFACE(fontface,NIL,screen,NIL);
SDL_FLIP(screen);
READLN;

DISPOSE(colour_font);
DISPOSE(colour_font2);
SDL_FREESURFACE(screen);
SDL_FREESURFACE(fontface);
TTF_CLOSEFONT(loaded_font);
TTF_QUIT;
SDL_QUIT;
END.

The fontface surface is a usual SDL surface so you can now do every manipulation you want or at least blit it to the screen surface and display the text after updating/flipping.

Finally all the variables and surfaces have to disposed as known. Like every SDL surface has to be free’d and SDL system has to be quit so has the TTF system. The procedures TTF_CLOSEFONT(font:pTTF_FONT) and TTF_QUIT have to be used to do this.

Now you are able to write ;).

This file contains the source code: chap5.pas (right click and “save as”)
This file is the executable: chap5.exe (right click and “save as”)

The final result should look and behave like this: The text “Hello World!” is displayed having red letters on cyan background.

Result of JEDI-SDL Chapter 5

Chapter 3a: Displaying different picture formats (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

In the previous chapter it was shown how to load bitmap images in the bitmap format. You may have wondered if there is a possibility to load other formats than bitmap images. The JEDI-SDL package provides a unit called SDL_IMAGE which exactly is created for this purpose. The following formats you can load according to the unit’s C/C++ documentation: TGA, BMP, PNM, XPM, XCF, PCX, GIF, JPG, TIF, LBM, PNG. I had troubles loading XCF images which is probably due to the fact this format is not standardized.

You need this .dll file to work successfully with the new unit:

Software Version Source Description
SDL_image-1.2.7-win32.zip 1.2.7 http://www.libsdl.org/projects/SDL_image/ This is the corresponding dynamic link library file for unit and image formats.

You should extract the zip-file and get six files. A text file, the important SDL_image.dll and several image format DLLs. Analogous to SDL.dll in chapter 1 you have to copy them to the system32-folder. If you forget this and run the examples below you will get an error with exitcode = 309.

After installation of the unit we now can proceed to the source code.

PROGRAM chap3;
USES SDL, SDL_IMAGE, STRINGS;

CONST
//add the path to your files here
picturepath:PCHAR = 'C:\FPC\2.2.4\my_images\fpsdl.';

VAR
screen:pSDL_SURFACE;
picture: ARRAY[0..2] OF pSDL_SURFACE;
fileextension: ARRAY[0..2] OF PCHAR;
filepath: ARRAY[0..2] OF PCHAR;
i:BYTE;

BEGIN
SDL_INIT(SDL_INIT_VIDEO);
screen:=SDL_SETVIDEOMODE(200,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;


fileextension[0]:='png';
fileextension[1]:='jpg';
fileextension[2]:='tif';

FOR i:=0 TO 2 DO
BEGIN
  filepath[i]:=STRNEW(picturepath);
  filepath[i]:=STRCAT(filepath[i],fileextension[i]);

  picture[i] := IMG_LOAD(filepath[i]);
  IF picture[i]=NIL THEN HALT;

  SDL_BLITSURFACE(picture[i],NIL,screen,NIL);
  SDL_FLIP(screen);
  READLN;
END;

FOR i:=0 TO 2 DO
BEGIN
  SDL_FREESURFACE(picture[i]);
  STRDISPOSE(filepath[i]);
END;
SDL_FREESURFACE(screen);
SDL_QUIT;
END.

The code shows the same picture known from chapter 3 in three different formats: PNG, JPG and TIF. You should download them (right-click onto each image and save) and put them at a desired location of your hard drive. Don’t worry if the tif image isn’t shown properly in your web browser. Since it isn’t a native web image format most web browsers don’t support it. However you can still download it.

tif image - usually not shown
TIF image – usually not supported natively by browsers to be displayed

Free Pascal example image in different formats
JPEG images

fpsdl
PNG image

PROGRAM chap3;
USES SDL, SDL_IMAGE, STRINGS;

First we need to include the SDL unit, of course we need also the SDL_IMAGE unit. The STRINGS unit is needed to handle file pathes a simple and short way. The latter unit is not related to the SDL library.

CONST
//add the path to your files here
picturepath:PCHAR = 'C:\FPC\2.2.4\my_images\fpsdl.';

VAR
screen:pSDL_SURFACE;
picture: ARRAY[0..2] OF pSDL_SURFACE;
fileextension: ARRAY[0..2] OF PCHAR;
filepath: ARRAY[0..2] OF PCHAR;
i:BYTE;

Now we define a constant, which contains the absolute path to the image files. The path is C:\FPC\2.2.4\my_images\. Of course you are free to choose any other location. Each file is named “fpsdl.” and the corresponding extension, like “bmp” for bitmap image, “png” for portable network graphics image, and so on. They all lie in the folder my_images.

In the variables part we define the screen surface again and an array of picture surfaces, all of kind pSDL_SURFACE. The screen surface decides what is shown at the display and the picture surfaces will store the images from image files of different formats. Next two arrays for the file extensions of the different image formats (bmp, png,…) and the image file paths are defined and of kind PCHAR. Finally we define the counter variable i.

BEGIN
SDL_INIT(SDL_INIT_VIDEO);
screen:=SDL_SETVIDEOMODE(200,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;


fileextension[0]:='png';
fileextension[1]:='jpg';
fileextension[2]:='tif';

As usual we start by initating the SDL library, defining a 200 x 200 pixels window and check if it was successful. The next part defines the different file extensions we want to use. In this example we will show the ability of SDL_IMAGE to load png, jpg and tif images. Feel free to try out all the other common formats by extending this demo program. Again I’d like to mention that I experienced troubles loading GIMP’s images in XCF format.

FOR i:=0 TO 2 DO
BEGIN
  filepath[i]:=STRNEW(picturepath);
  filepath[i]:=STRCAT(filepath[i],fileextension[i]);

  picture[i] := IMG_LOAD(filepath[i]);
  IF picture[i]=NIL THEN HALT;

  SDL_BLITSURFACE(picture[i],NIL,screen,NIL);
  SDL_FLIP(screen);
  READLN;
END;

The presented loop is the core of this demo program. It shows the preparation of the file locations and names, the creation of the image surfaces and the final blit to the screen surface to display them.

The loop will be cycled three times, once for each file. First we reseve some space on the heap and insert the constant file string by STRNEW. Next we concatenate the file path and the extension by STRCAT. For these operations we needed to include the STRINGS unit. Both commands aren’t related to the SDL library.

Then IMG_LOAD(absolute path/image file) function allows to load image data from image files with many different image formats to a surface. That is the key function of this demo program. However, be aware that the image data loaded to the surface is not compressed anymore and is stored in the usual RGBA format of SDL surfaces. The function returns NIL if it failed.

Since the example pictures have the same dimensions as the screen surface they can be blitted easily by SDL_BLITSURFACE as shown. The screen gets refreshed by SDL_FLIP. Both commands you know from the recent chapter. Finally we wait for the user to press enter after each cycle.

FOR i:=0 TO 2 DO
BEGIN
  SDL_FREESURFACE(picture[i]);
  STRDISPOSE(filepath[i]);
END;
SDL_FREESURFACE(screen);
SDL_QUIT;
END.

Now we have to clean up anything. The loop disposes the picture surfaces and the path strings. Finally the screen surface gets disposed and the SDL environment quit by SDL_QUIT. Except for STRDISPOSE you should remember any command from previous chapters. STRDISPOSE is a command from STRING unit, so again no SDL library related command.

This file contains the source code: chap3a.pas (right click and “save as”).
For this chapter no executable is provided.

Hope you are successful and have fun.

The final result should look and behave like this: First the “Free Pascal meets SDL” image appears in one of the the image formats (different of bitmap). Everytime you press return (or enter) in the console the same image with another format is displayed until the program quits.

Result of JEDI-SDL Chapter 3a

Chapter 1: Introduction, installation and configuration (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

The introduction is short. SDL library gives you the ability to develop powerful applications for many different operating systems (OS, e.g. Linux, Windows, MacOS) only by learning one set of commands. The SDL library takes on the assignment to translate your commands to the specific OS commands. It’s especially meaningful to use the SDL library if you plan to develop two-dimensional games or similar software. Role-playing-, Real time/turn based strategy-, side-scrolling-, arcade-, board-, card-, simulation-, multi-user dungeon-, puzzle games and so on are possible. SDL helps you to set up easily an SDL/OpenGL environment which allows for creation of 3d applications. This finally allows even for three-dimensional games.

Now something about the licensing: Both, Free Pascal and the SDL library are free and your created work can be used in commercial programs! The license of Free Pascal is: modified LGPL and the license of SDL: GNU LGPL.

By this the introduction to SDL is finished already. Before you accidently proceed reading the manual installation of the SDL units for the Free Pascal compiler (FPC) I’d like to express that since version 2.2.2 of FPC the pure, pre-compiled JEDI-SDL units are released along with the compiler so actually there is no need to install the SDL units manually anymore. Keep in mind though, many chapters assume you did the manual installation, so there are some envornment settings described you can skip. You don’t have to set individual pathes to the different unit files. However, what you always HAVE TO consider is the download and copy of the corresponding DLL files described in the chapters. If you already have installed Free Pascal (version 2.2.2 or later) just go on with Chapter 1a now.

If you decide to do the manual installation there are two different unit packages which allow the development of SDL applications with the Free Pascal environment. If you don’t know which to choose I recommend strongly to choose the package of the JEDI-SDL Project! It is more advanced and supports a greater variety of features. This is also the package which is included in FPC nowadays.

LINUX USERS: The following description of the configuration is related to the MS Windows OS. The further chapters (except chapter 1 and 1a) will work for Linux OS, too. (If you want to contribute a configuration tutorial for Linux OS let me know; you would be mentioned as author of course.)

Which package of SDL units you want to install (MS Windows OS)?

JEDI-SDL (recommended)

SDL4FP

Now this chapter concerns on the installation of all necessary software on your system for programming SDL applications with Free Pascal on Windows XP (probably this will work for other Windows systems as well; please report if you tried out; what about Vista?).

First of all, we have to consider which software is needed. We need three different software packages. We need the Free Pascal compiler of course. Furthermore we need the original SDL library files and finally we need something what translates our Pascal programs to the SDL library (written in C/C++). This will be done by a few units. These units come from the JEDI-SDL Project (Joint Endeavor of Delphi Innovators – Simple Direct Media Layer Project).

This table provides all information you need about the files you need to download with respect to installation steps 1) – 3).

Software Version Size, MB Source Description
fpc-2.2.4.i386-win32.exe 2.2.4 ~35 http://www.Free Pascal.org Download the latest stable Free Pascal compiler.
JEDI-SDLFullSetup.exe
(Full Installer!)
1.0 Final RC2 ~5 Main site:http://jedi-sdl.pascalgamedevelopment.com/
Downloads:http://sourceforge.net/forum/forum.php?forum_id=724777
Download the latest version of JEDI-SDL package.
SDL-1.2.13-win32.zip 1.2.13 ~0.1 http://www.libsdl.org (German readers may prefer: http://www.libsdl.de/) Download the latest stable runtime library of SDL.

1) Download the latest stable Free Pascal compiler, version 2.2.4 or higher.

2) Download the latest version of JEDI-SDL, version 1.0 Final or higher. Be sure it is the full installer (not headers only or demos only).

3) Download the latest version of SDL runtime library, version 1.2.13 or higher.

Of course, if you have installed Free Pascal already you haven’t to download it again and you can skip step 4.

4) Execute “fpc-2.2.4.i386-win32.exe” to install Free Pascal. Let the self-installer create a shortcut on your desktop. Don’t modify any checked options during installation process. The default path is: ‘C:\FPC\2.2.4\’.

5) Extract “SDL-1.2.13-win32.zip” to get the SDL runtime library. This leads to two extracted files. There is a text file and the very important SDL.dll.

6) Copy those files (especially the SDL.dll!) to your
system32-folder! Usually you find it at “C:\WINDOWS\system32\”.
Don’t confuse it with the similar system-folder. (This works for
WinXP, probably for NT-series and Win2000 as well; if you use Win9x
or WinME you should copy it to system-folder instead of system32-folder)

If it isn’t possible for any reason to copy the files into this folder you later have to copy that file into the same folder where the application is. Now you have installed Free Pascal and the SDL runtime library on your system. Finally JEDI-SDL has to be installed.

7) Execute the “JEDI-SDLFullSetup.exe” and follow the installer’s instructions. Since you should avoid a folder name like “JEDI-SDL Full” which is suggested by the install program when asking for the install path, I suggest as full path: “C:\FPC\2.2.4\units\JEDI\”. All later tutorials will assume you installed to this path.

Now the compiler has to be told about where to find the new units.

8) Open the Free Pascal IDE (for example by clicking the shortcut on desktop).

9) In the menue choose the following item “Options”. From “Options” choose “Directories…”. Now a window should pop up.

10) The first tab whitin this new window is called “Units”. Here you add the full path to your SDL-units (e.g. C:\FPC\2.2.0\units\JEDI\SDL\Pas) below the other pathes. Leave the last backslash out. Make sure the path leads to the folder, where the sdl.pas file is located! This file contains all the basic features of SDL.

JEDI-SDL Directories

The picture doesn’t show the correct path! It is old. We use “C:\FPC\2.2.4\units\JEDI\SDL\Pas” instead. Confirm by clicking “OK”.

Congratulations! You have configured your system for developing SDL applications with Free Pascal! Now let’s check if it really was that easy…

11) To check if you installed your system successfully download the file demo02.pp (right click and “save as”) into demo-folder (C:\FPC\2.2.4\demo\) or anywhere else.

12) Open this file and run it.

If you see some cool colourful red and blue lines you have done everything as it has to be done :)! Have fun with your configured system!

For those of you who try running the demo and get an abortion together with a messege saying “exitcode = 309”: You skipped step 6! Did you copy the SDL.dll to your system32- or system-folder respectively? If so and the error still occurs you should copy the SDL.dll into the folder where the demos are placed. Now it should work.

VERY IMPORTANT

Since most users will not install FPC/SDL manually I removed the information about setting up the unit pathes in the compiler settings part of each individual chapter. However for you, without setting the unit pathes in your IDE you will get errors. Next is a list of the settings for each chapter where necessary IF you do the manual installation.

Chapter 3a

1) Make sure your compiler finds the new unit. (IDE: Options –> Directories… –> Units)

JEDI-SDL Directories for font handling2) Copy the file “jedi-sdl.inc” from the “../SDL/Pas”-folder into the “../SDL_image/Pas/”-folder. Both folders are located in the JEDI-SDL project folder (e.g. C:\FPC\2.2.4\units\JEDI\). If you forget this you will get an error on compiling saying SDL_IMAGE is not finding jedi-sdl.inc.

3) You need this .dll file:

Software Version Source Description
SDL_image-1.2.7-win32.zip 1.2.7 http://www.libsdl.org/projects/SDL_image/ This is the corresponding dynamic link library file for unit and image formats.

Chapter 5

1) Make sure your compiler finds the new unit. (IDE: Options –> Directories… –> Units)

JEDI-SDL Directories for font handling2) Copy the file “jedi-sdl.inc” from the “../SDL/Pas”-folder into the “../SDL_tff/Pas/”-folder. Both folders are located in the JEDI-SDL project folder (e.g. C:\FPC\2.0.4\units\JEDI\). If you forget this you will get an error on compiling saying SDL_TFF is not finding jedi-sdl.inc.

3) You need this .dll file:

Software Version Source Description
SDL_ttf-2.0.10-win32.zip 2.0.10 http://www.libsdl.org/projects/SDL_ttf/ This is the corresponding dynamic link library file.

Chapter 7

1) Make sure your compiler finds the new units. (IDE: Options –> Directories… –> Units) For the usage of SDL_MIXER you have to include both, the SDL_MIXER itself and the SMPEG unit (which is used by SDL_MIXER unit). The SMPEG unit actually is only used for mp3 support but your programs will fail to run even if you don’t use mp3 files.

JEDI-SDL Directories for sound handling2) Copy the file “jedi-sdl.inc” from the “../SDL/Pas”-folder into the “../SDL_Mixer/Pas/”-folder and “../smpeg/Pas/”-folder. All folders are located in the JEDI-SDL project folder (e.g. C:\FPC\2.0.4\units\JEDI-SDLv1.0\). If you forget this you will get an error on compiling saying SDL_Mixer or smpeg is not finding jedi-sdl.inc.

3) You need this .dll file and a music and sound file:

Software Version Source Description
SDL_mixer-1.2.8-win32.zip 1.2.8 http://www.libsdl.org/projects/SDL_mixer/ This is the corresponding dynamic link library file.
In my mind.ogg In-my-mind.ogg A music file. “In my mind” by First. Of course you could also use any other music file you desire.
dial.wav dial.wav Public domain sounds: http://www.pdsounds.org. Of course you could also use any other sound file you desire.

Chapter 8

1) Make sure your compiler finds the new units. (IDE: Options –> Directories… –> Units) You will need to add the path to the OpenGL units GL and GLU. GL provides the whole basic OpenGL functionality and GLU (OpenGL Utilities) provides some additional functions which are very useful but not provided as basic functions of OpenGL (version 1.1).

JEDI-SDL Directories for OpenGL support2) Copy the file “jedi-sdl.inc” from the “../SDL/Pas”-folder into the “../OpenGL/Pas/”-folder. All folders are located in the JEDI-SDL project folder (e.g. C:\FPC\2.2.4\units\JEDI\). If you forget this you will get an error on compiling saying GL or GLU is not finding jedi-sdl.inc.

3) You need this software:

Software Version Source Description
OpenGL driver Usually your graphic card provides the corresponding OpenGL driver and you don’t have to do anything. And if so it is very likely that version 1.1 is fully supported. However if you are one of the few poor people whose graphic card doesn’t support OpenGL, check the graphic card’s manufacturer’s homepage for OpenGL drivers.

Chapter 7: Sound and Music (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

This chapter will introduce you on how to load music and sounds since these are key features of every game and many applications. An easy way to play music (.wav, .mod, .mid, .ogg, .mp3) and sound files (.wav, .aif, .ogg, .voc) is the usage of an additional unit called SDL_MIXER. This unit is maintained by the same author which also provides the core SDL library, Sam Lantinga and the co-authors Stephane Peter and Ryan Gordon. Fortunately the JEDI-SDL team translated this unit so it is available for Pascal programmers.

You need this .dll file and a music and sound file:

Software Version Source Description
SDL_mixer-1.2.8-win32.zip 1.2.8 http://www.libsdl.org/projects/SDL_mixer/ This is the corresponding dynamic link library file.
In my mind.ogg In-my-mind.ogg A music file. “In my mind” by First. Of course you could also use any other music file you desire.
dial.wav dial.wav Public domain sounds: http://www.pdsounds.org. Of course you could also use any other sound file you desire.

You should extract the zip-file (SDL_mixer-1.2.8-win32.zip) and get six files. A readme text file, the important SDL_mixer.dll and several further .dll files. Copy all of them to the system32-folder. If you forget this and run the examples below you will get an error with exitcode = 309.

Here we go now, the complete code at once. Go through it an look if you get what the program will do when you run it.

ATTENTION: I experience a strange runtime error (216: General Protection Fault) with some curious symbols in popup window when trying this example program from IDE. The compiled program runs well though. In other projects I don’t get this behaviour. Probably it has to do with the call of MIX_VOLUMECHUNK and you may comment it out. The music and sound can’t be heard when run from IDE. You have to compile the program and execute the compiled program. This by the way isn’t needed for other projects. Not sure on the reasons for this strange behaviour here.

PROGRAM chap7;
USES CRT, SDL, SDL_MIXER;

CONST
AUDIO_FREQUENCY:INTEGER=22050;
AUDIO_FORMAT:WORD=AUDIO_S16;
AUDIO_CHANNELS:INTEGER=2;
AUDIO_CHUNKSIZE:INTEGER=4096;

VAR
userkey:CHAR;
music:pMIX_MUSIC=NIL;
sound:pMIX_CHUNK=NIL;
soundchannel:INTEGER;

BEGIN
SDL_INIT(SDL_INIT_AUDIO);


IF MIX_OPENAUDIO(AUDIO_FREQUENCY, AUDIO_FORMAT,
                 AUDIO_CHANNELS, AUDIO_CHUNKSIZE)<>0 THEN HALT;

music:=MIX_LOADMUS('In my mind.ogg');
MIX_VOLUMEMUSIC(20);

sound:=MIX_LOADWAV('dial.wav');
MIX_VOLUMECHUNK(sound,50);

writeln('Music is playing now...');
MIX_PLAYMUSIC(music,0); //-1 = infinite, 0 = once, 1 = twice,...

writeln('"s" - play sound effect');
writeln('"z" - pause sound effect');
writeln('"t" - resume sound effect');
writeln('"p" - pause music');
writeln('"o" - resume music');
writeln('"q" - quit');
REPEAT
  REPEAT
  SDL_DELAY(20);
  UNTIL KEYPRESSED;
  userkey:=READKEY;
  CASE userkey OF
    's': soundchannel:=MIX_PLAYCHANNEL(-1,sound,0);
    'z','y': MIX_PAUSE(soundchannel);
    't': MIX_RESUME(soundchannel);
    'p': MIX_PAUSEMUSIC;
    'o': MIX_RESUMEMUSIC;
  END;
UNTIL userkey='q';


MIX_HALTMUSIC;
MIX_HALTCHANNEL(soundchannel);

MIX_FREEMUSIC(music);
MIX_FREECHUNK(sound);

MIX_CLOSEAUDIO;
SDL_QUIT;
END.

Probabably you looked through the code now. Now we can go step by step through the code from top to bottom.

PROGRAM chap7;
USES CRT, SDL, SDL_MIXER;

The program is called “chap7” (quite imaginative, isn’t it?). Now we include three units. The common SDL unit already known. The CRT unit is known from chapters 3 and 4 where we needed it to check when the user pressed a key. We need it here for the very same purpose. New is the SDL_MIXER unit which is needed to allow us to use the sound subsystem in an easy way.

You may ask why we didn’t use the event handling structure we know from chapter 6 to recognize when the user presses a key instead of the keypresed command from CRT unit. Well, I think it isn’t of an advantage if you mix up the different parts of SDL for learning purposes. Furthermore this “raw” way the source code stays tighter and therefore more readable.

CONST
AUDIO_FREQUENCY:INTEGER=22050;
AUDIO_FORMAT:WORD=AUDIO_S16;
AUDIO_CHANNELS:INTEGER=2;
AUDIO_CHUNKSIZE:INTEGER=4096;

Next we define some constants which will be used to intilize the sound system. Of course you also can add the different values directly when inilizing the sound system. We need the sample frequency in Hertz (1/s) which usually is 22050 Hz or MIX_DEFAULT_FREQUENCY in games but double as high for CD quality sound (44100 Hz).

Next we have to define the audio format. It determines how the audio data is stored. Different values are given in the following table (from official SDL Mixer documentation):

audio format Description
AUDIO_U8 Unsigned 8-bit samples
AUDIO_S8 Signed 8-bit samples
AUDIO_u16LSB Unsigned 16-bit samples, in little-endian byte order
AUDIO_S16LSB Signed 16-bit samples, in little-endian byte order
AUDIO_U16MSB Unsigned 16-bit samples, in big-endian byte order
AUDIO_S16MSB Signed 16-bit samples, in big-endian byte order
AUDIO_U16 same as AUDIO_U16LSB
AUDIO_S16 same as AUDIO_S16LSB
AUDIO_U16SYS Unsigned 16-bit samples, in system byte order
AUDIO_S16SYS Signed 16-bit samples, in system byte order

Then we decide for a sound type, which means either stereo or mono. For stereo sound we choose 2 and for mono we choose 1 as value. Finally the chunksize has to be set, where 4096 bytes per sample is a default value. The lower this value the worse the quality.

VAR
userkey:CHAR;
music:pMIX_MUSIC=NIL;
sound:pMIX_CHUNK=NIL;
soundchannel:INTEGER;

Now we define some variables. First we define a CHAR varibale which later reads the keyboard input of the user. This variable is just for this purpose and has no direct connection to the sound system or SDL. Variable music which is a pMIX_MUSIC pointer is very important. It points at the loaded music file later! Same applies for sound which is a pMIX_CHUNK pointer. Both pointers are initilized as NIL pointers here. Note: Each music song is referenced by an own pMIX_MUSIC pointer and each sound effect (e.g. explosions, shots, …) is referenced by an own pMIX_CHUNK pointer.

BEGIN
SDL_INIT(SDL_INIT_AUDIO);


IF MIX_OPENAUDIO(AUDIO_FREQUENCY, AUDIO_FORMAT,
                 AUDIO_CHANNELS, AUDIO_CHUNKSIZE)<>0 THEN HALT;

music:=MIX_LOADMUS('In my mind.ogg');
MIX_VOLUMEMUSIC(20);

sound:=MIX_LOADWAV('dial.wav');
MIX_VOLUMECHUNK(sound,50);

In this demo we just want to demonstrate the music/sound abilities of SDL so we only initialize the audio subsystem by SDL_INIT_AUDIO. Similar to the initilization of the graphic system we then have to set up the audio system by the function MIX_OPENAUDIO(parameters). The four paramteters needed we already defined and discussed above. The function will return 0 on error.

We will load the music to the music pointer by MIX_LOADMUS(absolute path to music file). If the Pascal file and the music file are in the same folder you can give the music file’s name without a path (as shown in the example). The same way you load up sound files with MIX_LOADWAV function. The same function is used to load other file formats instead of .wav sound files even though the function’s name doesn’t indicate this. Both functions return NIL on error (which we don’t check in the example though). The functions MIX_VOLUMEMUSIC(integer) and MIX_VOLUMECHUNK(sound pointer, integer) set the volume for the music sound file. Note: If you have more than one music and sound file you have to set the volume for each sound file individually but the music volume is set for all music files together. The volume values are integers between 0 (silence) and 128 (highest volume). The constant MIX_MAX_VOLUME corresponds to 128.

writeln('Music is playing now...');
MIX_PLAYMUSIC(music,0); //-1 = infinite, 0 = once, 1 = twice,...

writeln('"s" - play sound effect');
writeln('"z" - pause sound effect');
writeln('"t" - resume sound effect');
writeln('"p" - pause music');
writeln('"o" - resume music');
writeln('"q" - quit');
REPEAT
  REPEAT
  SDL_DELAY(20);
  UNTIL KEYPRESSED;
  userkey:=READKEY;
  CASE userkey OF
    's': soundchannel:=MIX_PLAYCHANNEL(-1,sound,0);
    'z','y': MIX_PAUSE(soundchannel);
    't': MIX_RESUME(soundchannel);
    'p': MIX_PAUSEMUSIC;
    'o': MIX_RESUMEMUSIC;
  END;
UNTIL userkey='q';

The writeln commands for displaying text at the shell should be known. The MIX_PLAYMUSIC(music pointer, integer) is used to play music files. Therefore we give the music pointer which we called “music” and we give the number of loops this music file should be played. As the comment states -1 means the music is replayed infinitly, 0 means the music is played once, 1 means the music is played twice and so on. The function returns -1 on error and 0 on success (which we don’t check in the example).

Similarly sounds are played. Contrary to music which has its own seperated channel, sounds have up to eight channels (channel numbers 0 to 7) where they can be played at. This means you can have up to eight sounds that play at the same time and can be heard at the same time! As for music files you have to say which sound you want to play and how often but additionally you have to say at which channel you want it to play. The function is MIX_PLAYCHANNEL(paramters) which returns the channel number the sound is played on or -1 on error. The first parameter is the channel you want the music to be played. -1 means the first free channel will be chosen automatically. What will happen when all channels are used but a further sound is going to be played? One playing sound will be stopped and the new sound will be played on this channel. The next two parameters are the same as for music, the sound pointer and the integer how often the sound will be played.

The repeat-until loops, the commands KEYPRESSED, READKEY and the variable userkey aren’t SDL specific but common Pascal commands and they will hold the user in the loop until he decides to leave it by pressing the “q” button. This will not be explained here in detail.

The different cases in the case block show the usage of playing a sound (which is discussed already) and different functions to pause/resume music and sounds. The procedures MIX_PAUSEMUSIC and MIX_RESUMEMUSIC pause and resume the actively playing music. Similar for sounds but you actually don’t pause/resume the sounds themselves but the channels they are played on. This channel we don’t know because we said MIX_PLAYCHANNEL should find the first free channel automatically but we returned the channel number in variable “soundchannel”. MIX_PAUSE(channel number) and MIX_RESUME(channel number) will pause and resume the desired channel when using “soundchannel” as parameter.

MIX_HALTMUSIC;
MIX_HALTCHANNEL(soundchannel);

MIX_FREEMUSIC(music);
MIX_FREECHUNK(sound);

MIX_CLOSEAUDIO;
SDL_QUIT;
END.

The commands MIX_HALTMUSIC and MIX_HALTCHANNEL(channel number) are used to stop playing the music/sound immediatly. As for surfaces the msuic/sound files have to be freed when they are not used anymore. The functions MIX_FREEMUSIC(music pointer) and MIX_FREECHUNK(sound pointer) are used for this. Finally the audiosystem and SDL have to be shut down by MIX_CLOSEAUDIO and SDL_QUIT.

This file contains the source code: chap7.pas (right click and “save as”)
This file is the executable: chap7.exe (right click and “save as”)

Chapter 4a: Primitives, Image rotation/zooming, Framerate (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

In the previous chapter it was shown how to draw one pixel to the screen. Of course you could now develop routines to draw primitives from this single pixel manipulation but to make life easier someone (Andreas Schiffler) did this already for you. And furthermore the JEDI project already translated it for you to use it in Free Pascal :).

Maybe you remember the Turbo Pascal GRAPH unit which allowed to draw simple primitives like lines, squares, rectangles, circles, polygons and so on. Exactly this part is now fulfilled by the SDL_GFX unit in a SDL environment. So from now on you can also draw sprites directly to the surfaces and don’t have to draw them externally in a painting program. If you choose to draw sprites directly with this set of primitives or create them externally depends on your needs and preferences. Of course you can combine both features, load up an image first, then manipulate it further by the features shown in this chapter.

As an example you could imagine a game where you have game objects of the same type for each player which only differ by the same coloured elements. You now could create several object images with the possible different colours externally. However, if you want to provide 100 different colours you have to load up the object’s image 100 hundred times into your painting program, colour it, save it. You need 100 times the colour images space on hard disk. – With the provided functions you could just create ONE image of the units with a “wild card colour”, let’s say white. Now you just have to write a function which recognizes white areas on the image and colour it in 100 different colours, save it to hard disk (same space usage as first method) or in heap (no further hard disk space is needed!). Especially useful is the SDL_GFX unit if the white areas are of shape rectangle, circle, polygon or just a single pixel.

Before starting with SDL_GFX be aware that it is distributed by a third party developer, Andreas Schiffler. Unfortunately he is not distributing the pre-compiled shared library (DLL file) but instead he provides the source code only. So you are in need of compiling this file yourself or download the compiled SDL_gfx.dll file (zipped) (version 2.0.19) I compiled for you. The source code is written in C++ so you can’t use the Free Pascal compiler to do the compilation yourself. In the first part of this chapter I will deal with the compilation of your own DLL. The provided a pre-compiled SDL_gfx.dll (zipped) was compiled by me under 32 Bit Windows XP Professional on an AMD processor and usage might cause problems for 64 Bit systems and differing operating systems and processors. Furthermore it might be outdated, it’s version 2.0.19. In all these cases you should really consider to compile your own DLL file if the provided file isn’t working for you. If you use the pre-compiled DLL file you can skip the compilation procedure, just go on reading after the 15 step procedure for compilation below.

For compilation we need several things. We need a free C++ compiler to compile the DLL file from the source code. Of course we need the source code of SDL_GFX itself. Furthermore we need the SDL Development Library for Windows. The following table shows the names of the most recent files and their location.

Software Version Source Description
MinGW C++ Compiler 5.1.4 http://www.mingw.org/ The mainpage of the MinGW C++ Compiler, there look for “downloads” and get the most recent version, preferably with autoinstaller.
SDL_gfx-2.0.22.tar.gz 2.0.22 http://www.ferzkopp.net/ SDL_gfx C++ source code from Andreas Schiffler’s homepage. Look for “Software”, there for “SDL_gfx”.
SDL-devel-1.2.14-mingw32.tar.gz 1.2.14 http://www.libsdl.org/ SDL development library. Look for “SDL 1.2” in the menu, then for the development libraries.

1) First of all, get the MinGW C++ compiler installed. Make sure you check the g++ compiler, which is actually the C++ compiler we are looking for! The other compilers you can leave unchecked. See the picture below.

MinGW Install window

2) Extract the Development Library of SDL (SDL-devel-1.2.14-mingw32.tar.gz or newer).

3) Among other things there should be one folder called “lib” within the newly created folder of step 2. Copy the complete content of folder “lib” (3 files) into a folder also named “lib” in the MinGW directory. Make sure only to copy these three files, not the folder “lib” itself.

4) There is also a folder “include” in the extracted SDL Development Library folder. It contains one further folder called “SDL” (contains 34 files). This time copy this folder “SDL” as a whole into the “include” folder of the MinGW installation.

5) Now MinGW is fully prepared for the compiling of SDL_GFX.

6) Extract now the source code of SDL_GFX (SDL_gfx-2.0.22.tar.gz or newer).

7) Within the newly created folder you find a folder called “Other Builds”. Enter this folder. Within this folder you will find several zip files.

8) Extract file “mingw.zip”. This creates a new folder “mingw” and contains exact two files, “Makefile” and “README”.

9) Copy “Makefile” into the root folder of SDL_gfx.

10) Before using this makefile some changes have to be applied. The following script shows the first few lines of the makefile. The bold red printed parts have to be changed according to your needs. [REMARK: Now the lines are marked.]

CC = gcc
AR=ar rc
RANLIB=ranlib

prefix=c:/dev/local
bin_dir=$(prefix)/bin
include_dir=$(prefix)/include
lib_dir=$(prefix)/lib

CFLAGS = -O3 -march=athlon-xp -mmmx -msse -m3dnow -DBUILD_DLL -DWIN32 -Ic:/dev/local/include/SDL
LIBS = -Lc:/dev/local/lib -lSDL

OBJS = SDL_framerate.o SDL_gfxPrimitives.o SDL_imageFilter.o SDL_rotozoom.o

.
.
.
.

11) “c:/dev/local” has to be the path to the MinGW root folder [Line 5]. Remove flag “-DWIN32” [Line 10] and change the include flag/path to the corresponding (MinGW root folder)/include/SDL [Line 10, “-I(your path)/include/SDL”]. By the way, make sure to use slashes “/” instead of backslashes “\” to separate folders. Also give the library path as (MinGW root folder)/lib [Line 11, “-L(your path)/lib”]. Now save this makefile.

12) To compile the DLL file now, open the command window of Windows by using Start–>Run…, there type “cmd” and the n “OK”. A new “DOS-like” window will pop up.

12) Change the directory to the SDL_GFX root folder by using the DOS commands “cd (foldername)” and “cd..” to enter or leave a folder.

13) Now just give the full path to the MinGW make program located at “(MinGW root folder)\bin\mingw32-make”. Now press enter and see how your DLL file gets compiled.

14) If everything worked well you find a brand new SDL_gfx.dll in your SDL_GFX root folder.

15) Copy this file to your WINDOWS\system32 folder or to the folder where the application is located which is using SDL_GFX.

Now, since compilation is finished (or you skipped the compilation procedure) just a short hint about the licensing, SDL_GFX is licensed under the LGPL, which essentially means you can also use it in proprietary software.

Before proceeding make sure Free Pascal finds the SDL and SDL_GFX units (Options–>Directories…), however, if you installed a recent binary Free Pascal package you don’t have to care about this. Free Pascal then is already well configured to work with SDL and SDL_GFX.

Now we can proceed to the source code. Here is the full source code of the example program. It will rotate and at the same time zoom an image constantly and additionally draw some primitives to the screen.

PROGRAM chap4a;
USES SDL, SDL_GFX, CRT;

CONST
x_array:ARRAY[0..5] OF SINT16 = (50, 150, 250, 250, 150, 50);
y_array:ARRAY[0..5] OF SINT16 = (100, 50, 100, 200, 250, 200);

VAR
screen,original_image,modified_image:pSDL_SURFACE;
angle_value, zoom_value:DOUBLE;
framerate:pFPSMANAGER;
calc_width, calc_height:LONGINT;

BEGIN
SDL_INIT(SDL_INIT_VIDEO);
screen:=SDL_SETVIDEOMODE(400,400,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;

original_image:=SDL_LOADBMP('C:\FPC\2.2.2\bin\i386-win32\test\fpsdl.bmp');
IF original_image=NIL THEN HALT;

NEW(modified_image);
angle_value:=0.0;
zoom_value:=0.0;

NEW(framerate);
SDL_INITFRAMERATE(framerate);
SDL_SETFRAMERATE(framerate, 30);

REPEAT
  angle_value:=angle_value+1.0;
  zoom_value:=zoom_value+0.05;
  IF angle_value>=359 THEN angle_value:=0.0;
  IF zoom_value>=2.0 THEN zoom_value:=0.0;

  ROTOZOOMSURFACESIZE(400, 400, angle_value, zoom_value, calc_width, calc_height);
  WRITELN('Width: ',calc_width,' Height: ',calc_height);
  modified_image:=ROTOZOOMSURFACE(original_image, angle_value, zoom_value, 1);

  CIRCLECOLOR(screen, 200, 200, 100, $FFFF00FF);
  FILLEDCIRCLECOLOR(screen, 200, 200, 50, $00FF00FF);
  ELLIPSECOLOR(screen, 200, 200, 175, 75, $00FFFFFF);
  FILLEDPIECOLOR(screen, 200, 200, 110, 10, 100, $FF0000FF);
  POLYGONCOLOR(screen, @x_array[0], @y_array[0], 6, $000000FF);
  BEZIERCOLOR(screen, @x_array[3], @y_array[3], 3, 2, $FFFFFFFF);


  SDL_BLITSURFACE(modified_image,NIL,screen,NIL);
  SDL_FLIP(screen);

  SDL_FRAMERATEDELAY(framerate);
UNTIL keypressed;

SDL_FREESURFACE(original_image);
SDL_FREESURFACE(modified_image);
SDL_FREESURFACE(screen);

DISPOSE(framerate);

SDL_QUIT;
END.

Now that you know the whole code, let’s discuss it step by step.

PROGRAM chap4a;
USES SDL, SDL_GFX, CRT;

CONST
x_array:ARRAY[0..5] OF SINT16 = (50, 150, 250, 250, 150, 50);
y_array:ARRAY[0..5] OF SINT16 = (100, 50, 100, 200, 250, 200);

VAR
screen,original_image,modified_image:pSDL_SURFACE;
angle_value, zoom_value:DOUBLE;
framerate:pFPSMANAGER;
calc_width, calc_height:LONGINT;

The program is called “chap4a” and uses the known units SDL and CRT (CRT for easy recognition of user pressing a key on the keyboard). Additionally the new unit SDL_GFX has to be included here. The latter unit provides the functionality described below.

There is a constant block defining two constant arrays “x_array” and “y_array” containing six elements of SINT16 (16 bit signed integer) corresponding to six x and y values. These will be needed later to define a polygon and a Bézier curve.

Three surface variables are defined. The “screen” variable represents the screen surface. The “original_image” surface stores an image, which then is manipulated (rotation and zooming) and the result is stored in the “modified_image” surface. The float number variables “angle_value” and “zoom_value” are storing a certain rotation angle and a zoom factor. Finally variable “framerate” is defined as a framerate manager variable pFPSMANAGER which is new and provided by SDL_GFX. This helpful tool is discussed in much detail later. Finally two variables “calc_width” and “calc_height” are defined and have to be of type LONGINT. They will store the estimated new size of the surface after rotation and zooming.

BEGIN
SDL_INIT(SDL_INIT_VIDEO);
screen:=SDL_SETVIDEOMODE(400,400,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;

original_image:=SDL_LOADBMP('C:\FPC\2.2.2\bin\i386-win32\test\fpsdl.bmp');
IF original_image=NIL THEN HALT;

NEW(modified_image);
angle_value:=0.0;
zoom_value:=0.0;

The “screen” surface is initialized as known from previous chapters with 400 x 400 px. The “Free Pascal-meets-SDL” bitmap image from Chapter 3 is loaded to the surface variable “original_image”. For those who don’t remember the image, here it is again:

Free Pascal meets SDL sample image bmp format
download (right click and “save as”)

Notice, this image has 200 x 200 dimension. The “original_surface” surface will be the source for any manipulation. The second surface “modified_image” gets some space allocated and is ready for usage. However until now it stays empty. The rotation angle and the zoom factor are set to zero.

NEW(framerate);
SDL_INITFRAMERATE(framerate);
SDL_SETFRAMERATE(framerate, 30);

The framerate manager “framerate” gets some space and is initialized by PROCEDURE SDL_INITFRAMERATE(manager:PFPSmanager). Then the framerate is set in Hz (Hertz) by FUNCTION SDL_SETFRAMERATE(manager:PFPSmanager; rate:INTEGER):INTEGER which returns 0 on success and -1 if something is wrong. The default value is 30 Hz. It is also stored in flag FPS_DEFAULT. Additionally there are FPS_UPPER_LIMIT and FPS_LOWER_LIMIT which correspond to 200 and 1 Hz. Not shown in the example code is FUNCTION SDL_GETFRAMERATE(manager:PFPSMANAGER):INTEGER which will just return the set FPS value, however it will not return the real framerate. Even if the framerate dropped to 1 Hz it will return 30 Hz since this is the set value.

Well, some notes about the framerate: The framerate is usually a value indicating how many frames per second are drawn and shown at the screen. 30 Hz means there are 30 frames drawn to the screen within one second. Often the frequency if abbreviated by FPS meaning Frames Per Second, thus 30 FPS is the same as 30 Hz. If you perform many drawing operation or costly drawing operations (especially 3d applications know this) then it might be that the framerate drops because the system (processor and/or graphic hardware) isn’t capable of drawing fast enough to keep 30 frames per second.

So what about the framerate manager, how does it help? In simple programs the SDL_DELAY(delay in milli seconds) function is used to control the framerate if placed in the rendering loop (the loop which flips the scene to the screen surface). Assuming you set is to 33, so SDL_DELAY(33) it means every 33 ms one frame is drawn, this means approximately 30 frames are drawn within one second! Thus the framerate is 30 Hz here as well. However this only applies if you assume that the drawing itself doesn’t need time, which is a good approximation for simple applications and simple scenes. When drawing more complex scenes the drawing itself will take some milli seconds or even more time, the SDL_DELAY function will just add its delay time, so this leads to a remarkable delay. In contrast a framerate manager recognizes that the framerate dropped and will skip the delay time to keep the framerate at 30 Hz (or whatever value is set). Additionally the framerate manager is keeping the actual framerate more accurately at the framerate, in the contrary SDL_DELAY is quite inaccurate.

REPEAT
  angle_value:=angle_value+1.0;
  zoom_value:=zoom_value+0.05;
  IF angle_value>=359 THEN angle_value:=0.0;
  IF zoom_value>=2.0 THEN zoom_value:=0.0;

  ROTOZOOMSURFACESIZE(400, 400, angle_value, zoom_value, calc_width, calc_height);
  WRITELN('Width: ',calc_width,' Height: ',calc_height);
  modified_image:=ROTOZOOMSURFACE(original_image, angle_value, zoom_value, 1);

Now the rendering loop is entered. Every cycle the rotation angle is increased by 1.0 degree. Also the zoom factor is increased by 0.05 every cycle. A zoom factor of 1.0 means no change of the picture. Values smaller than 1.0 mean shrinkage of the image, values larger than 1.0 mean enlargement of the image. A value of 0.5 and 2.0 mean half the size and double the size of the original image respectively. The two IF clauses ensure that the value of “angle_value” is restored to 0 (equals 360) degree (no rotation), if the image is rotated by 259 degree. If the image got zoomed to twice its original size, “zoom_value” gets restored to 0.0.

PROCEDURE ROTOZOOMSURFACESIZE(width:INTEGER; height:INTEGER; angle:DOUBLE; zoom:DOUBLE; VAR dstwidth:INTEGER; VAR dstheight:INTEGER) calculates the new surface size after rotation and zooming. The first two parameters are the width and height of the initial surface (in our example the image/surface has 200 x 200 dimension). The next two parameters expect the rotation and zoom values (in our example they are stored in “rotation_angle” and “zoom_angle”). The last two parameters store the return values. The results will be returned to “calc_width” and “calc_height” in our example. They are written to the screen. Now one of the most amazing functions of the SDL_GFX unit is introduced. It allows the rotation and zooming of an image. In FUNCTION ROTOZOOMSURFACE(src:pSDL_SURFACE; angle:DOUBLE; zoom:DOUBLE; smooth:INTEGER):pSDL_SURFACE you give the source surface first, which is the surface with the original image in our case, then the angle and zoom factor which we discussed right before and finally if antialiasing should be performed. Antialiasing means to smooth edges. 0 means no antialiasing, 1 means antialiasing, well, also two flags you could use, SMOOTHING_OFF and SMOOTHING_ON. (However I experienced that antialiasing didn’t work out even though a 32 bit surface is used which is necessary to perform antialiasing as the author stated.)

There are some accompied functions not demonstrated in the example code which should be mentioned now. FUNCTION ZOOMSURFACE(src:pSDL_SURFACE; zoomx:DOUBLE; zoomy:DOUBLE; smooth:INTEGER):pSDL_SURFACE zooms only without rotation but zooming in x direction and y direction can be set independently. FUNCTION ROTOZOOMSURFACEXY(src:pSDL_SURFACE; angle:DOUBLE; zoomx:DOUBLE; zoomy:DOUBLE; smooth:INTEGER):pSDL_SURFACE is the same function as before but with rotation additionally. For both of these functions also corresponding procedures exist which return the size of the newly created surfaces. Thay are PROCEDURE ZOOMSURFACESIZE(width:INTEGER; height:INTEGER; zoomx:DOUBLE; zoomy:DOUBLE; VAR dstwidth:INTEGER; VAR dstheight:INTEGER) and PROCEDURE ROTOZOOMSURFACEXYSIZE(width:INTEGER; height:INTEGER; angle:DOUBLE; zoomx:DOUBLE; zoomy:DOUBLE; VAR dstwidth:INTEGER; VAR dstheight:INTEGER). They work the very same way as demonstrated in the example code.

It is very important to keep in mind that every manipulating procedure (rotation and zooming) is distorting the image information. So the number of manipulations should be keep as small as possible. In the example every cycle of the loop the original image gets manipulated exactly two times. It gets rotated once and zoomed once. Instead of this you could also implement a recursive solution, namely rotating the original image in the first cycle by one degree, take the resulting image and rotate it in the second cycle again by one degree, and so on. Let’s check for the result after 25 cycles, in fact both methods rotated the image by 25 degree, but well, the quality is remarkable different. And this neglecting the zooming which has an even worse impact on quality if implemented recursively. The following drawing will illustrate what the results will look like. On the left the rotation of the original image by 25 degree once, on the right the rotation of the same image 25 times by one degree.

Results of different rotation implementations
Left: Rotation once by 25 degree; Right: 25 Rotations by one degree

The loss of information with each cycle will add up for each manipulation leading to ugly results like the right one. Compare this to the left image where no loss of information is noticable.

CIRCLECOLOR(screen, 200, 200, 100, $FFFF00FF);
  FILLEDCIRCLECOLOR(screen, 200, 200, 50, $00FF00FF);
  ELLIPSECOLOR(screen, 200, 200, 175, 75, $00FFFFFF);
  FILLEDPIECOLOR(screen, 200, 200, 110, 10, 100, $FF0000FF);
  POLYGONCOLOR(screen, @x_array[0], @y_array[0], 6, $000000FF);
  BEZIERCOLOR(screen, @x_array[3], @y_array[3], 3, 2, $FFFFFFFF);

As promised SDL_GFX is able to draw a lot of primitives. The primitives shown in the example code are just a few of them. The principle of implementing them is demonstrated anyway. The table below gives the complete list of primitives you can use. Most of the functions are intuitive, so a circle is defined by its position (x/y values), its radius r and its colour. The same applies for a filled circle. An ellipse is defined by its position (x/y values), its horizontal and vertical axes rx and ry, and its colour.

Especially polygon and Bézier curve calls may be not so intuitive. The polygon function is function POLYGONCOLOR(dst:pSDL_SURFACE; const vx:pSINT16; const vy:pSINT16; n:INTEGER; color:UINT32):INTEGER and the Bézier curve function is function BEZIERCOLOR(dst:pSDL_SURFACE; const vx:pSINT16; const vy :pSINT16; n:INTEGER; s:INTEGER; color:UINT32):INTEGER. As for all the other functions you define the surface first onto which you would like to draw these primitives. The next parameters expect a pointer (pSINT16) to an array of x values of type SINT16 (16 bit unsigned integer) and an array of y values of the same type. This is achieved by the @ operator at the first and fourth element of the arrays respectivly. These vectors were defined initially in the constant block, you should remember. The next parameter n is the number of points the polygon has or the number of reference points the Bézier curve has. If the arrays contain six elements, n should be six as well. Since in the case of the Bézier curve only the last three elements are of interest (at least in our example), n has to be three and the resulting curve is of order four (quadratic Bézier curve). The s value defines the smoothness of the curve. The higher s the higher the smoothness.

Some general information about these primitive functions: All functions presented here have the same suffix …COLOR. This means that the last parameter expects the colour you desire in hexadecimal form, $RRGGBBAA. The hexadecimal digit 00 corresponds to decimal digit 0, and hexadecimal digit FF corresponds to decimal digit 255. All of these function are accompanied by a function with the suffix …RGBA instead of …COLOR. Here, all parameters are the same except from the colour parameter, instead of ONE colour code in hexadecimal form, you enter four parameters r, g, b, a (red, green, blue, alpha/transparency). For example instead of $FF0000FF you put 255, 0, 0, 255. All functions return 0 as INTEGER value on success.

In many cases there are further accompanied functions with the prefix aa. This means the very same function with the same parameter list but the result is antialiased. For many functions furthermore the prefix filled is possible as shown for the circle in the example code. Also here the parameter list is completely the same but the resulting primitive is filled with the given colour.

SDL_BLITSURFACE(modified_image,NIL,screen,NIL); SDL_FLIP(screen); SDL_FRAMERATEDELAY(framerate); UNTIL keypressed;

Primitive Definition Description
Of all functions two types exist: …Color and …RGBA. They are in fact equal and differ only in the way you put in the colour information. For …Color functions there is the color:Uint32 argument where you put in the colour code in hexadecimal form, e.g. $FF0000FF for red without transparency. For …RGBA functions there are four arguments r:Uint8; g:Uint8; b:Uint8; a:Uint8 where you enter 255; 0; 0; 255 for red without transparency.
Functions with the prefix aa are equal to the functions without this prefix but are printing antialiased primitives. Functions with the prefix filled are equal to the functions without this prefix but the enclosed area of the primitive is filled with the given colour. Antialiased primitives are never filled and vice versa.
All functions return 0 as INTEGER on success.
Pixel (Dot) function pixelColor( dst : PSDL_Surface; x : Sint16; y : Sint16; color : Uint32 ) : integer Draws one pixel at position (x/y) in the given colour.
function pixelRGBA( dst : PSDL_Surface; x : Sint16; y : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
Line function hlineColor( dst : PSDL_Surface; x1: Sint16; x2 : Sint16; y : Sint16; color : Uint32 ) : integer Draws a horizontal line from x1 to x2 at height y in the given colour.
function hlineRGBA( dst : PSDL_Surface; x1: Sint16; x2 : Sint16; y : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function vlineColor( dst : PSDL_Surface; x : Sint16; y1 : Sint16; y2 : Sint16; color : Uint32 ) : integer Draws a vertical line at position x from y1 to y2 in the given colour.
function vlineRGBA( dst : PSDL_Surface; x : Sint16; y1 : Sint16; y2 : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function lineColor( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; color : Uint32 ) : integer Draws a free line from position (x1/y1) to (x2/y2) in the given colour.
function lineRGBA( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16;
x2 : Sint16; y2 : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function aalineColor( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; color : Uint32 ) : integer
function aalineRGBA( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16;
x2 : Sint16; y2 : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
Rectangle (Box) function rectangleColor( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; color : Uint32 ) : integer Draws a rectangle from position (x1/y1) to (x2/y2) in the given colour.
function rectangleRGBA( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16;
x2 : Sint16; y2 : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function boxColor( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; color : Uint32 ) : integer
function boxRGBA( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16;
y2 : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
Circle function circleColor( dst : PSDL_Surface; x : Sint16; y : Sint16; r : Sint16; color : Uint32 ) : integer Draws a circle with the center at position (x/y) and the radius r in the given colour.
function circleRGBA( dst : PSDL_Surface; x : Sint16; y : Sint16; rad : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function aacircleColor( dst : PSDL_Surface; x : Sint16; y : Sint16; r : Sint16; color : Uint32 ) : integer
function aacircleRGBA( dst : PSDL_Surface; x : Sint16; y : Sint16;
rad : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function filledCircleColor( dst : PSDL_Surface; x : Sint16; y : Sint16; r : Sint16; color : Uint32 ) : integer
function filledCircleRGBA( dst : PSDL_Surface; x : Sint16; y : Sint16;
rad : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
Pie function pieColor( dst : PSDL_Surface; x : Sint16; y : Sint16; rad : Sint16;
start : Sint16; finish : Sint16; color : Uint32 ) : integer
Draws a pie chart with the pie’s edge being at position (x/y) with the radius rad. “start” and “finish” define the arc length of the pie in degree. If “start” is 0 then the pie starts at the right (east) side of a circle. To start at the top, “start” should be 270 or -90. If “start” is 0 and “finish” is 90, you get one quarter of a circle.
function pieRGBA( dst : PSDL_Surface; x : Sint16; y : Sint16; rad : Sint16;
start : Sint16; finish : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function filledPieColor( dst : PSDL_Surface; x : Sint16; y : Sint16; rad : Sint16;
start : Sint16; finish : Sint16; color : Uint32 ) : integer
function filledPieRGBA( dst : PSDL_Surface; x : Sint16; y : Sint16; rad : Sint16;
start : Sint16; finish : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
Ellipse function ellipseColor( dst : PSDL_Surface; x : Sint16; y : Sint16; rx : Sint16; ry : Sint16; color : Uint32 ) : integer Draws an ellipse with its center at position (x/y) and the two radii rx and ry for its horizontal and vertical axes in the given colour.
function ellipseRGBA( dst : PSDL_Surface; x : Sint16; y : Sint16;
rx : Sint16; ry : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function aaellipseColor( dst : PSDL_Surface; xc : Sint16; yc : Sint16; rx : Sint16; ry : Sint16; color : Uint32 ) : integer
function aaellipseRGBA( dst : PSDL_Surface; x : Sint16; y : Sint16;
rx : Sint16; ry : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function filledEllipseColor( dst : PSDL_Surface; x : Sint16; y : Sint16; rx : Sint16; ry : Sint16; color : Uint32 ) : integer
function filledEllipseRGBA( dst : PSDL_Surface; x : Sint16; y : Sint16;
rx : Sint16; ry : Sint16; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
Polygon function trigonColor( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; x3 : Sint16; y3 : Sint16; color : Uint32 ) : integer Draws a trigon with its edges at (x1/y1), (x2/y2) and (x3/y3) in the given colour.
function trigonRGBA( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; x3 : Sint16; y3 : Sint16;
r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function aatrigonColor( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; x3 : Sint16; y3 : Sint16; color : Uint32 ) : integer
function aatrigonRGBA( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; x3 : Sint16; y3 : Sint16;
r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function filledTrigonColor( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; x3 : Sint16; y3 : Sint16; color : Uint32 ) : integer
function filledTrigonRGBA( dst : PSDL_Surface; x1 : Sint16; y1 : Sint16; x2 : Sint16; y2 : Sint16; x3 : Sint16; y3 : Sint16;
r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function polygonColor( dst : PSDL_Surface; const vx : PSint16; const vy : PSint16; n : integer; color : Uint32 ) : integer Draws a polygon with x values from array of SINT16 in vx. y values are from array of SINT16 in vy. n is the number of edges/points the polygon has.
function polygonRGBA( dst : PSDL_Surface; const vx : PSint16; const vy : PSint16;
n : integer; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function aapolygonColor( dst : PSDL_Surface; const vx : PSint16; const vy : PSint16; n : integer; color : Uint32 ) : integer
function aapolygonRGBA( dst : PSDL_Surface; const vx : PSint16; const vy : PSint16;
n : integer; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
function filledPolygonColor( dst : PSDL_Surface; const vx : PSint16; const vy : PSint16; n : integer; color : Uint32 ) : integer
function filledPolygonRGBA( dst : PSDL_Surface; const vx : PSint16;
const vy : PSint16; n : integer; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer
Bézier curve function bezierColor( dst : PSDL_Surface; const vx : PSint16; const vy : PSint16; n : integer; s : integer; color : Uint32 ) : integer Draws a Bézier curve of any order. n is the number of points, well n+1 is the order of the curve then. The x and y values are from arrays of SINT16. The higher value s the smoother the curve gets. s should be 2 at least.
function bezierRGBA( dst : PSDL_Surface; const vx : PSint16; const vy : PSint16;
n : integer; s : integer; r : Uint8; g : Uint8; b : Uint8; a : Uint8 ) : integer

 

  SDL_BLITSURFACE(modified_image,NIL,screen,NIL);
  SDL_FLIP(screen);

  SDL_FRAMERATEDELAY(framerate);
UNTIL keypressed;

Nothing really new here. The “modified_image” surface with the rotated, zoomed image and the primitives is blitted to the screen surface. The result is flipped to the screen. Then the extensivly discussed framerate manager decides by procedure SDL_FRAMERATEDELAY(manager:PFPSMANAGER) how long actually to wait until the loop is repeated. This procedure replaces the common SDL_DELAY command. The loop is stopped when the user presses any key.

SDL_FREESURFACE(original_image);
SDL_FREESURFACE(modified_image);
SDL_FREESURFACE(screen);

DISPOSE(framerate);

SDL_QUIT;
END.

Finally all the used surfaces are free’d and the framerate manager is disposed. SDL is quit and the program finished.

This file contains the source code: chap4a.pas (right click and “save as”)
This file is the executable: chap4a.exe (right click and “save as”)

The final result should look and behave like this: The image fpsdl.bmp is rotated and zoomed continouisly and some primitives (circle, filled circle, ellipse, pie, hexagon, Bézier curve) are drawn onto the image. If you use the pre-compiled exe file make sure to have the image copied to c:\.

Result of JEDI-SDL Chapter 4a

[Downloads transferred from old website]

SDL_gfx.dll; The pre-compiled SDL_gfx.dll (Version 2.0.19) for chapter 4a.

SDL_gfx-2.0.19.tar.gz; For license reasons here you can download the source code to compile the SDL_gfx.dll for chapter 4a. However, the most recent version for compilation you will find on the authour’s page.

Chapter 1a: Compilation and configuration of Lazarus (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

Before you start following the instructions given here to install Lazarus make sure you really need it. The Lazarus package is completly independent of SDL. If you are not interested in a DELPHI-like RAD (Rapid Application Development) tool skip this chapter. You could come back later after you finished all SDL chapters :). This chapter isn’t required to do the following chapters.

Otherwise the combination of Free Pascal together with the SDL Library and Lazarus’ abilities can lead to a very flexible and highly powerful development environment. The advantages of Free Pascal, like OS independence, open source code, a license which allows you to develop commercial programs (license: LGPL), and so on apply for Lazarus, too! Furthermore you gain all advantages modern OS and IDE provide. The DOS-like IDE of the Free Pascal compiler is legendary but nowadays not too comfortable anymore. The Lazarus environment provides a flexible and comfortable IDE.

The following description on how to compile/install and configure Lazarus is for WINDOWS ONLY. I am sorry that I am currently not able to make such a description to linux users. Any reader is welcome to send me one, so I could publish it here (you would get full credit of course). Futhermore it is necessary that you have installed Free Pascal.

The following table describes what software you will need additionally to the Free Pascal compiler:

Software Version Size, MB Source Description
Lazarus source files Latest revision depends directly through SVN How to get them will be described next!
TortoiseSVN 1.4.7 ~2 http://tortoisesvn.net/ Download the latest version of SVN client (except you already have such a client).
fpcbuild-2.2.0.zip 2.2.0 ~38 http://www.Free Pascal.org Download the latest source files of the Free Pascal compiler. Go to “Download” and scroll down to “Source”.

Now let’s begin. First of all I want you to know that there are some binaries downloadable from the official Lazarus page. These binaries shouldn’t be used because they are very old and the resulting Lazarus environment is terribly unstable! The environment is much more improved and gets improved daily. So I advise you to get the latest development state through SVN (it’s like the outdated CVS). If you neither know SVN nor CVS here a short description: SVN provides the latest state of development and ensures that any of several hundreds developers of a project (like Lazarus) is working on the latest version. Whenever a developer is changing anything at the project SVN generates a new revision number. Today the revision number of Lazarus is 13688.

How to get the latest development state (revision) of lazarus then? – That is described very clearly and easy here: http://wiki.lazarus.FreePascal.org/index.php/Getting_Lazarus. Therefore I will just describe short how to get it.

1) get the mentioned SVN client (TortoiseSVN) for Windows and install it

2) create a directory where you want to have Lazarus installed later (e.g. C:\FPC\Lazarus)

3) right-click on the new created lazarus folder and chose “SVN Checkout…”; you get the following window

Lazarus SVN Checkout window4) type in field URL of repository: “http://svn.Free Pascal.org/svn/lazarus/trunk” (without quotationmarks) as shown in the picture above; if you use a command line client (e.g. Subversion you have to type “svn co http://svn.Free Pascal.org/svn/lazarus/trunk lazarus” (without quotationmarks))

5) click “OK”

6) another window pops up called “trunk – SVN Checkout…”; now it will take some minutes; after all files got downloaded you receive a window similar to this:

Lazarus finished checkout(you can go on reading while waiting)

The files you download through SVN aren’t binaries. That means you have to compile them. The compiler you use to do so is the Free Pascal compiler of course (that is why you should have it installed already).

7) If the downloading is finished you have to go to the folder where all the files are placed, e.g. C:\FPC\lazarus\.

8) Now you have to run a command line tool like “cmd”. Choose “Start” and then “Run…” from Windows’ start menu.

9) Enter “cmd” or “command.com” (latter only if “cmd” doesn’t work) in the next pop-up window and click “OK”. Now a window like this should pop up:

Command window cmd.exe for make procedure10) Go to the directory where you downloaded the Lazarus files. (Use the DOS commands “cd [directory]” and “cd..” to enter or leave directory.)

11) When you entered the directory (C:\FPC\lazarus\) just type “make” and the make.exe of your Free Pascal compiler will compile Lazarus for you. Now relax and wait some minutes.

If this doesn’t work for you, you should try to copy the make.exe from your Free Pascal compiler (located in bin directory) to the Lazarus directory (or you could check if you set the environment variable/user variable PATH=C:\FPC\2.2.0\bin\i386-win32 in system control)

12) After successful compilation there should be a Lazarus.exe and a Startlazarus.exe right in the lazarus directory. Use Startlazarus.exe to run your brand new RAD tool :). After passing some error messeges you should get something similar to this:

Lazarus editor if installed successfully13) close Lazarus

The next steps describe how to configure Lazarus together with JEDI-SDL. This includes telling Lazarus where to find Free Pascal compiler, where to find Free Pascal source files and where to find JEDI-SDL units. ATTENTION: It is necessary to have the source files of Free Pascal, otherwise Lazarus won’t run properly. If you installed Free Pascal and JEDI-SDL as described in Chapter 1 you don’t have the source files because they are not included in the binary package. Fortunately this doesn’t matter because you can download them separately. If you already have the source files for some reason you can skip step 14 and 15.

14) Download the latest source files of the Free Pascal compiler (usually it doesn’t matter if the version of the source files differ slightly from the version of actual compiler).

15) Now extract the file. It doesn’t matter where the source files are located but you may prefer a folder like “C:\FPC\2.2.0\source\”.

16) The extraction generates a folder “fpcbuild-2.2.0”. The whole path should be “C:\FPC\2.2.0\source\fpcbuild-2.2.0\”.

17) Now run Lazarus and skip any error message. Go to “Environment” in the menu.

Lazarus editor environment options
Choose “Environment options” from the tab. The following window should come up.

Environment options window18) The text field “Lazarus directory (default for all projects)” should be filled with your Lazarus path, if not do so. The text field “Compiler path (ppc386.exe)” should be filled with your Free Pascal compiler path, if not do so.

19) The text field “FPC source directory” is probably empty. The sources are needed for Lazarus to compile projects successfully. Fill in the path where you extracted the source files in step 15. If you followed my suggestion the correct path would be “C:\FPC\2.2.0\source\fpcbuild-2.2.0\fpcsrc\”. Confirm by clicking “OK”.

20) If you haven’t done any mistakes there shouldn’t be any error anymore if you close and restart Lazarus.

21) If you didn’t install SDL manually you can skip steps 21 and 22, you are finished then! Now let’s tell Lazarus where to find the JEDI-SDL units. Go to “Project” in menu.

Lazarus compiler optionsChoose “Compiler Options…” from the tab. The following window should come up but without any entry in the text field “Other Unit Files (-Fu) (Delimiter is semicolon);”.

Lazarus compiler options window22) Enter the path to the JEDI-SDL units. If you installed JEDI-SDL as mentioned in Chapter 1 you should enter “C:\FPC\2.2.0\units\JEDI\SDL\Pas”. Now you are ready to write Free Pascal programs in Lazarus using JEDI-SDL. Note: The environment settings are saved for any project but the compiler settings (steps 20-22) are just related to the current project so you have to set the unit path to JEDI-SDL units separate for each project.

Now you have a brand new RAD tool installed. Well done! This tool you can use to write just plain Free Pascal or/and Free Pascal/SDL programs. You also can combine these programs with the features typical RAD kits provide but to learn how to use those features is described anywhere else. Have fun.

Chapter 6: Event handling (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

Whenever the user of your program is doing something related to it, for instance if the user is moving the mouse, pressing/releasing a button on the keyboard or pressing/releasing the fire key on the joystick, then you speak of events. Further events are resizing a window or switching between several applications. For games though the events described first are much more important. SDL provides a quite easy way to notice and react to such events, which is called event handling.

Each event has a special structure, for example a pressed key is an event or a moved mouse is an event. SDL differs altogether sixteen such events (check the table below). Every event stores different information depending on its kind. For example the key board event stores the information which key was pressed. The mouse motion event stores the information to which position the mouse got moved. If you get a mouse motion event you can’t read out key information, and from a key pressed event you can’t read out its mouse coordinates, and so on. Therefore you have the following general structure to the event data: event.eventstructure.data. The following list will give you an overview of all possible eventstructures in SDL:

eventstructure Description
SDL_NOEVENT Whenever the user is not raising an actual event.
SDL_ACTIVEEVENT Notices if application is active or inactive (for example when you switch to another windowed application and your applications gets into the background or gets minimized).
SDL_KEYDOWN Both have the same record structure (tSDL_KEYBOARDEVENT) which stores the triggering key with state SDL_PRESSED or SDL_RELEASED
SDL_KEYUP
SDL_MOUSEMOTION Notices the movement of the mouse cursor and stores it position and the relative movement from former origin.
SDL_MOUSEBUTTONDOWN Both have the same record structure (tSDL_MOUSEBUTTONEVENT) which stores the triggering mouse and its key with state SDL_PRESSED or SDL_RELEASED. Futhermore the position is stored.
SDL_MOUSEBUTTONUP
SDL_JOYAXISMOTION Notices the usage of the stick of a joystick.
SDL_JOYBALLMOTION Notices the usage of a joyball and the relative movement from its origin.
SDL_JOYHATMOTION Notices the triggering joystick and its hat. Furthermore one of the nine positions is stored (1=up, 2=right upper corner, 3=right and so on; 0=center).
SDL_JOYBUTTONDOWN Both have the same record structure (tSDL_JOYBUTTONEVENT) which stores the triggering joystick and its key with state SDL_PRESSED or SDL_RELEASED.
SDL_JOYBUTTONUP
SDL_VIDEORESIZE Notices when apllication window gets resized and stores the new height and width.
SDL_QUITEV Notices when user quits the application (by clicking application’s X-button at right upper corner)
SDL_USEREVENT Unedfined event which can be definied by user.
SDL_SYSWMEVENT Notices system window manager events.

The event handling subsystem is automatically initialized along with the video subsystem. Since all the events are related to the program’s window there is no sense of intilizing it individually from the video subsystem. Here is the code of the program all at once.

PROGRAM chap6_1;

USES SDL;

VAR
screen:pSDL_SURFACE;
loopstop:boolean=FALSE;
test_event:pSDL_EVENT;

BEGIN
SDL_INIT(SDL_INIT_VIDEO);

screen:=SDL_SETVIDEOMODE(200,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;

NEW(test_event);

WHILE loopstop=FALSE DO
BEGIN
  IF SDL_POLLEVENT(test_event)=1 THEN
  BEGIN
    write('pending event: ');
    CASE test_event^.type_ OF
      SDL_ACTIVEEVENT: writeln('Application is/is not active');
      SDL_KEYDOWN: BEGIN
                     write('Key pressed ');
                     writeln('(SDLKey=',test_event^.key.keysym.sym,')');

                     {SDLKey 27 = ESCAPE}
                     IF test_event^.key.keysym.sym=27 THEN loopstop:=TRUE;
                   END;
      SDL_KEYUP: writeln('Key released');
      SDL_MOUSEMOTION: writeln('Mouse motion');
      SDL_MOUSEBUTTONDOWN: writeln('Mouse button down');
      SDL_MOUSEBUTTONUP: writeln ('Mouse button up');
      SDL_JOYAXISMOTION: writeln ('Joystick axis motion');
      SDL_JOYBALLMOTION: writeln('Joystickïs trackball motion');
      SDL_JOYHATMOTION: writeln('Joystickïs hat position changed');
      SDL_JOYBUTTONDOWN: writeln('Joystick button pressed');
      SDL_JOYBUTTONUP: writeln('Joystick button released');
      SDL_QUITEV: writeln('User-requested quit');
    END;
  END
  ELSE writeln('no pending events');
  SDL_DELAY(150);
END;


DISPOSE(test_event);
SDL_FREESURFACE(screen);
SDL_QUIT;
END.

Okay, let’s right start with the first part.

PROGRAM chap6_1;

USES SDL;

VAR
screen:pSDL_SURFACE;
loopstop:boolean=FALSE;
test_event:pSDL_EVENT;

BEGIN
SDL_INIT(SDL_INIT_VIDEO);

screen:=SDL_SETVIDEOMODE(200,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;

NEW(test_event);

The first part to be discussed doesn’t contain a lot of new things. The program is called chap6_1, it uses the SDL unit and the screen variable is defined. New is the event variable test_event which is of pointer type pSDL_EVENT. We will create a second variable of boolean type just to control the while loop. Then SDL is initialized and the program’s window set with the width and height of 200 pixels. Finally the event variable gets allocated.

WHILE loopstop=FALSE DO
BEGIN
  IF SDL_POLLEVENT(test_event)=1 THEN
  BEGIN
    write('pending event: ');

We want to check if any event occured and if so, we want to know which kind of event happened. So we make a while loop which will run until loopstop gets true. This will be if the user presses the application’s X (right upper corner) or pressing ESC key.

The command SDL_POLLEVENT(parameter), or more precise SDL_POLLEVENT(event: pSDL_EVENT): INTEGER, checks if there are pending events and if so it will take the oldest and save it to parameter which is an event record of pSDL_EVENT type. For example if the user presses (then releases) left mouse button, then presses (then releases) space button on keyboard and finally moves the mouse there are altogether five events: 1. left mouse button pressed, 2. left mouse button released, 3. space key pressed, 4. space key released, 5. mouse moved. If you poll for events now you will get the first event (left mouse button pressed) and saved it’s properties to the event variable we specified as parameter. The next poll will save the properties of next event (left mouse button released) to event variable and so on. SDL_POLLEVENT(parameter) will return 1 if it has found pending event or 0 if there isn’t any pending event.

So, if SDL_POLLEVENT has an event, then the program writes “pending event:” and after that will add the type of event found. Notice that therefore write instead of writeln has been used here. The actual event is determined as follows:

CASE test_event^.type_ OF
      SDL_ACTIVEEVENT: writeln('Application is/is not active');
      SDL_KEYDOWN: BEGIN
                     write('Key pressed ');
                     writeln('(SDLKey=',test_event^.key.keysym.sym,')');

                     {SDLKey 27 = ESCAPE}
                     IF test_event^.key.keysym.sym=27 THEN loopstop:=TRUE;
                   END;
      SDL_KEYUP: writeln('Key released');
      SDL_MOUSEMOTION: writeln('Mouse motion');
      SDL_MOUSEBUTTONDOWN: writeln('Mouse button down');
      SDL_MOUSEBUTTONUP: writeln ('Mouse button up');
      SDL_JOYAXISMOTION: writeln ('Joystick axis motion');
      SDL_JOYBALLMOTION: writeln('Joystickïs trackball motion');
      SDL_JOYHATMOTION: writeln('Joystickïs hat position changed');
      SDL_JOYBUTTONDOWN: writeln('Joystick button pressed');
      SDL_JOYBUTTONUP: writeln('Joystick button released');
      SDL_QUITEV: writeln('User-requested quit');
    END;
END

Fortunately you don’t have to check manually for every event which is made by the user. In general by event^.eventtype you can easily check which type of event you got. The event is stored in test_event and the type is checked by type_ so the expression is test_event^.type_. Whatever event is stored in test_event, the corresponding string expression is then added to the previous “pending event:” string. For the example described before the returned event types would be SDL_MOUSEBUTTONDOWN, 2. SDL_MOUSEBUTTONUP, 3. SDL_KEYDOWN, 4. SDL_KEYUP, 5. SDL_MOUSEMOTION. It is senseful to check for the event type by using the CASE command.

You may have noticed that in case of SDL_KEYDOWN not just a string gets added. Actually in this case also the corresponding SDLKey code is determined and given. Furthermore if the pressed key is the escape key (Esc) the program should stop. An keyboard event record contains a field called keysym. Keysym is a record of SDL_KEYSYM. It contains four fields. Scancode which is a hardware dependent scancode and should be avoided if you want to make hardware independent programs. It is usually an INTEGER variable. Next is sym which stores the SDLKEY. These SDL keys are independent and it is strongly recommended to use them! In the example we want break up if the escape key gets pressed. Its SDL key code is 27. If you want to know what SDL keys are defined look up in the table page. The variable modifier stores modifier keys (like shift, ctrl,…) pressed and stores SDLMod. SDL modifier keys can be found at table page as well. The fourth variable is unicode which may be used to read out unicode characters which is enabled by function SDL_ENABLEUNICODE(enable: INTEGER): INTEGER. 1 enables and 0 disables the unicode translation, however by default it is turned off.

To read out or compare the key the user pressed we must use the expression test_event^.key.keysym.sym. test_event^ is the event, the event structure is defined as an key event by key and the data we want to know is the key pressed contained in keysym.sym. The key code is printed to th screen but if it corresponds to the code for the ESC key, it sets loopstop to TRUE meaning the the while loop will be stopped and finally stop the program.

  ELSE writeln('no pending events');
  SDL_DELAY(150);
END;


DISPOSE(test_event);
SDL_FREESURFACE(screen);
SDL_QUIT;
END.

In the case SDL_POLLEVENT finds no event all the checking for event is skipped and just “no pending events” is written. To slow down the program the known SDL_DELAY is used.

Finally the event and the screen are disposed and SDL as well as the program are quit.

Now I will present a command without showing its appliance at any code since it is easy to understand and very important. Imagine you open any text editor and just hold pressed any letter on the keyboard. What will happen? First the letter will be written to the text editor once and after a short break the letter will be written in a loop (without break). – You may want to change this behaviour, especially for action games and such. The command SDL_ENABLEKEYREPEAT(DELAY, INTERVAL), or more precise SDL_ENABLEKEYREPEAT(delay: INTEGER; interval: INTEGER): INTEGER, is used for changing this behaviour. If delay is set to 0 the key repetition is completly disabled and a held pressed key will just trigger ONCE. If you enable the the delay by setting it to 1 and setting an interval in ms the pressed key will trigger anytime the set interval has passed. E.g. SDL_ENABLEKEYREPEAT(1,1000) will trigger the key any second if pressed. Note: When using this command the specific text edior behaviour is removed and the first triggering will happen immediatly. To get back to the common behaviour use SDL_DEFAULT_REPEAT_DELAY and SDL_DEFAULT_REPEAT_INTERVAL.

Now we will discuss mouse handling. Since it is rather similar to keyboard handling I will keep it short. Here we go:

PROGRAM chap6_2;

USES SDL;

VAR
screen:pSDL_SURFACE;
loopstop:boolean=FALSE;
test_event:pSDL_EVENT;

PROCEDURE mouse_check;
BEGIN
  writeln('X: ',test_event^.motion.x,' Y: ',test_event^.motion.y,
        ' dX: ',test_event^.motion.xrel,' dY: ',test_event^.motion.yrel,
        ' button state: ',test_event^.motion.state);
  writeln('button state (pressed/released): ',test_event^.button.state,
        ' button index: ',test_event^.button.button);
END;

BEGIN
SDL_INIT(SDL_INIT_VIDEO);

screen:=SDL_SETVIDEOMODE(200,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;

NEW(test_event);

SDL_EVENTSTATE(SDL_ACTIVEEVENT,SDL_DISABLE);

WHILE loopstop=FALSE DO
BEGIN
  IF SDL_POLLEVENT(test_event)=1 THEN
  BEGIN
    CASE test_event^.type_ OF
      SDL_ACTIVEEVENT: writeln('Application is/is not active');
      SDL_KEYDOWN:   {SDLKey 27 = ESCAPE}
                     IF test_event^.key.keysym.sym=27 THEN loopstop:=TRUE;

      SDL_MOUSEMOTION: mouse_check;
      SDL_MOUSEBUTTONDOWN: mouse_check;
      SDL_MOUSEBUTTONUP: mouse_check;
    END;
  END;
END;


DISPOSE(test_event);
SDL_FREESURFACE(screen);
SDL_QUIT;
END.

Anyhow this code looks complicated, it is not.

PROGRAM chap6_2;

USES SDL;

VAR
screen:pSDL_SURFACE;
loopstop:boolean=FALSE;
test_event:pSDL_EVENT;

PROCEDURE mouse_check;
BEGIN
  writeln('X: ',test_event^.motion.x,' Y: ',test_event^.motion.y,
        ' dX: ',test_event^.motion.xrel,' dY: ',test_event^.motion.yrel,
        ' button state: ',test_event^.motion.state);
  writeln('button state (pressed/released): ',test_event^.button.state,
        ' button index: ',test_event^.button.button);
END;

BEGIN
SDL_INIT(SDL_INIT_VIDEO);

screen:=SDL_SETVIDEOMODE(200,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;

NEW(test_event);

This is exactly the same code as for the first part of this chapter, however a PROCEDURE mouse_check is created which will be used several times later. You can read out the position of the mouse (relative to your program window!) by test_event^.motion.x and test_event^.motion.y. The relative movement which means the difference from the actual position compared to last polled position you can get by test_event^.motion.xrel and test_event^.motion.yrel. Furthermore it is possbile to return the pressed buttons by test_event^.motion.state. All this data belongs to tSDL_MOUSEMOTIONEVENT record. All these information are just printed out by the mouse_check PROCEDURE later.

SDL_EVENTSTATE(SDL_ACTIVEEVENT,SDL_DISABLE);

Function SDL_EVENTSTATE(type_: UInt8; state: INTEGER): UInt8 allows you to enable/disable certain event structures. For example in the code SDL_ACTIVEEVENT got disabled by SDL_EVENTSTATE(SDL_ACTIVEEVENT, SDL_DISABLE). That is why you never will receive a note about an active or inactive application even though the status may change when you run the program. The same result you get by using SDL_IGNORE instead of SDL_DISABLE. You can reenable the event structure by SDL_ENABLE.

WHILE loopstop=FALSE DO
BEGIN
  IF SDL_POLLEVENT(test_event)=1 THEN
  BEGIN
    CASE test_event^.type_ OF
      SDL_ACTIVEEVENT: writeln('Application is/is not active');
      SDL_KEYDOWN:   {SDLKey 27 = ESCAPE}
                     IF test_event^.key.keysym.sym=27 THEN loopstop:=TRUE;

      SDL_MOUSEMOTION: mouse_check;
      SDL_MOUSEBUTTONDOWN: mouse_check;
      SDL_MOUSEBUTTONUP: mouse_check;
    END;
  END;
END;


DISPOSE(test_event);
SDL_FREESURFACE(screen);
SDL_QUIT;
END.

This part now looks also very similar to the code of the first part of this chapter. Since we are only treating mouse handling here all the other possible events are not treated in the CASE block. The SDL_ACTIVEEVENT event is just introduced here to demonstrate that it is succesfully disabled by SDL_EVENTSTATE as discussed right before.

The SDL_KEYDOWN event is introduced to make sure the user can exit the program by pressing the ESC key.

Similar to the handling of keyboard events the mouse key events are handled. It also provides the possibility to return the pressed button by test_event^.button.button. It has the very same meaning as test_event^.motion.state! Don’t confuse it with test_event^.button.state which here means to check if a button is pressed (SDL_PRESSED) or released (SDL_RELEASED). This data belongs to the tSDL_MOUSEBUTTONEVENT record. Whatever the user is doing with the mouse, moving or clicking, the mouse_ckeck PROCEDURE already discussed is entered and the corresponding data written to the screen.

By the way, you should check what happens if you combine several mouse buttons or use the mouse wheel. Some button combinations lead to the same button index as the usage of the mouse wheel and so on.

With this chapter you got introduced into a major concept of game programming.

This file contains the source code: chap6_1.pas (right click and “save as”)
This file is the executable: chap6_1.exe (right click and “save as”)
This file contains the source code: chap6_2.pas (right click and “save as”)
This file is the executable: chap6_2.exe (right click and “save as”)

The final result should look and behave like this: While being with the mouse onto the SDL application window any event from the keyboard is recognized and it is said what exactly happened (button down, up, …)

Result of JEDI-SDL Chapter 6, keyboard handling

The final result should look and behave like this: While being with the mouse onto the SDL application window any event from the mouse is recognized and its (x/y) position, the difference between last recognized position and new position (dX and dY) and if a button is pressed or released and the corresponding button’s index is shown.

Result of JEDI-SDL Chapter 6, mouse handling

Chapter 4: Drawing pixels (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

Okay. Maybe you think, why to learn how to display pictures first and then learning about the more basic feature to draw single pixels to the screen? – First of all blitting is one of the key features whereas drawing of single pixels to the screen is quite rarely used in applications and games. Though this chapter will get a little bit longer because several things have to be explained more detailed to impart a fully understanding.

We have to know about the meaning of pixels. The physical screen consists of many small units. Every unit consists itself of three different coloured lights. These colours are red, green and blue. If you mix them (additive) you can get every other colour. For example if you mix red and green you get yellow. For three colours that can be mixed with each other there are eight combinations possible which lead to different colours (RGB, RG, RB, R, GB, G, B, all lights off). Some of you may say RGB (white) and all lights off (black) are no colours. That is right but doesn’t matter here and to keep simpliness I will talk of colours even if I talk of black and white.

Did you get confused? Your screen definitvly has more than eight colours, doesn’t it? The reason is, your screen isn’t just able to switch lights on or off. Besides it is able to differ the intensities of the lights. The more intensity levels you have the more colours you can display. The case that you have eight colours as discussed before means that you just have one intensity level. If your screen is in 8 bit mode every pixel on the screen has the possibility to display 2 power 8 colours. That are 256 different colours. Every of the three lights has therefore a certain amount of different intensity levels. If you have 16 bit mode you have 2 power 16 and that are 65536 colours. Each light therefore has the appropriate amount of intensity levels. Since we prefer 32 bit mode, we have 4.29 billion different colours!

All the examples will be at 32 bit mode. If you want to write directly to the screen surface it is possibly necessary that you lock it before performing direct pixel manipulations. We work in software mode (SDL_SWSURFACE) so we can leave locking the screen surface. However, other modes will require you to lock it. Not shown in the example code you can check it by function SDL_MUSTLOCK(surface:pSDL_SURFACE):BOOLEAN. If it evaluates to FALSE or 0 you don’t need to worry about locking. Otherwise you have to lock the surface by function SDL_LOCKSURFACE(surface:pSDL_SURFACE):INTEGER for direct pixel manipulations. It will return 0 if locking was succesful and -1 if locking failed. After pixel manipulations you have to unlock the surface by procedure SDL_UNLOCKSURFACE(surface:pSDL_SURFACE) straight forward.

Now the whole example code is presented. The goal is to have 200 x 200 pixels wide black surface. A single yellow pixel will start to move from the top-left corner along the surface from left to right and line by line until it reaches the bottom of the surface. It then will restart at the top-left corner.

PROGRAM chap4;
USES CRT, SDL;

VAR
screen:pSDL_SURFACE;
pixellocation:^LONGWORD;
pixelcolor,i:LONGWORD;

BEGIN

SDL_INIT(SDL_INIT_VIDEO);
screen:=SDL_SETVIDEOMODE(200,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;

NEW(pixellocation);
WRITE('Pitch value: ',screen^.pitch DIV screen^.format^.BytesPerPixel);

i:=0;
REPEAT
inc(i); IF i>39999 THEN i:=0;

pixellocation:=screen^.pixels+i;               //delete old pixel
pixelcolor:=SDL_MAPRGB(screen^.format,0,0,0);
pixellocation^:=pixelcolor;


pixellocation:=screen^.pixels+i+1;             //draw new pixel
pixelcolor:=SDL_MAPRGB(screen^.format,255,255,0);
pixellocation^:=pixelcolor;

SDL_FLIP(screen);
SDL_DELAY(5);
UNTIL keypressed;

DISPOSE(pixellocation);
SDL_FREESURFACE(screen);
SDL_QUIT;
END.

Now we will go through the whole code step by step. Especially direct pixel manipulations are often misunderstood by newcomers to SDL.

PROGRAM chap4;
USES CRT, SDL;

VAR
screen:pSDL_SURFACE;
pixellocation:^LONGWORD;
pixelcolor,i:LONGWORD;

BEGIN

SDL_INIT(SDL_INIT_VIDEO);
screen:=SDL_SETVIDEOMODE(200,200,32,SDL_SWSURFACE);
IF screen=NIL THEN HALT;

The program is called chap4. As known from previous chapters unit CRT is needed here for a simple input handling and unit SDL is needed for SDL support. The screen variable is known from previous chapters as well and will contain the content which is displayed at the physical screen later. The pixellocation variable is a pointer type variable indicated by the tilde (^). Furthermore the space it’ll point at needs to be of size for a Longword value. The two variables pixelcolor and i are common Longword variables.

The SDL program is initilized as known and the display surface is of size 200 x 200 px and has 32 bit. Except from the pointer variable pixellocation there is nothing new up to here.

NEW(pixellocation);
WRITE('Pitch value: ',screen^.pitch DIV screen^.format^.BytesPerPixel);

i:=0;
REPEAT
inc(i); IF i>39999 THEN i:=0;

In the first line of this part of the code the pointer pixellocation gets some space by the common Free Pascal function NEW(pointer).

Next the pitch value of the surface is printed. Please note, if you create a surface of a certain width and height the true surface can be a little bit wider. In our example this means you have a window of width and height of 200 pixels displayed but there could be an additional unvisible piece of surface. Maybe its true width is 210, so the moving pixel wouldn’t restart at a new line after 200 cycles but 210 cycles. To solve problems caused by this pitch to the surface you can read the pitch out. The data type pitch will allow you to do this (e.g. screen^.pitch). Unfortunately the pitch is not given in pixels (as the width value) but in bytes so you have to devide its value by the BytesPerPixel value which is for our 32 bit surface format usually 4. This value can be read out by screen^.format^.BytesPerPixel. You will see that in our example the width and the pitch are equal (both 200) which means there is no exceeding surface part.

Counter variable i is set to 0. Then the repeat loop is entered. Variable i is increased by 1 each cycle until it reaches 39999. It controls the position of the dot. When i reaches 39999 exactly 40000 cycles are done and the dot should restart at the top-left corner.

Now it gets interesting.

pixellocation:=screen^.pixels+i;               //delete old pixel
pixelcolor:=SDL_MAPRGB(screen^.format,0,0,0);
pixellocation^:=pixelcolor;

Let’s first consider the second line concerning the colour of pixels. If you want to add colour to a single pixel you need to know the corresponding pixel value defining the colour of it. The function SDL_MAPRGB(format:pSDL_PIXELFORMAT; r:UInt8; g:UInt8; b:UInt8):UInt32 or in short SDL_MAPRGB(pixel format, red, green, blue) will make it easy for you to find it out. Just add the pixel format of the surface which is stored in the surface’s format field. For the example surface the expression is screen^.format. A RGB triple of (0,0,0) will lead to black, all lights off (which is also the background colour of the surface). The expected variable type for the RGB triple values are of UInt8 meaning an unsigned 8 bit integer which corresponds to a Free Pascal Byte type variable with a range from 0 to 255. The function will return an unsigned 32 bit integer (UInt32) value which corresponds Free Pascal’s Longword type variable.

Now we are concerned about the actual allocation of a colour to a specific pixel. Every surface provides the possibility to access its pixels, for example by screen^.pixels. The marker pixels is of type Pointer and points to a certain pixel of the surface. By adding to or substraction from it you can easily access any pixel on the surface. So any pixel’s coordinate on the screen can be accessed by using ONE number instead of a pair of (x,y) values. This is an important concept. The integer value screen^.pixels is pointing at represents the colour of this specific pixel. So, don’t confuse these two values. The value of the pixels pointer itself determines the pixel addressed, the value it is pointing at determines the pixel’s colour. The following diagram may help to explain this.

Diagram explaining pixel locationFor our 32 bit surface all the pixels are of size Uint32 (unsigned integer 32 bit which equals Longword). That is why we initially made variable pixellocation of type Pointer which will point at values of size Longword. In the first cycle i has the value of 1. So from the first line we see that pixellocation points at the colour value of the screens first pixel. This pixel is black. In the second line the colour value for a black pixel is generated and given to pixelcolor. The third line allocates the black colour value to the first pixel. This actually doesn’t change anything.

pixellocation:=screen^.pixels+i+1;             //draw new pixel
pixelcolor:=SDL_MAPRGB(screen^.format,255,255,0);
pixellocation^:=pixelcolor;

If you compare these three lines of code with the previous ones you will see just minor differences. In the first line pixelcolor additionally is increased by 1 which means we refer here to the pixel next to the previous one. The pixel colour defined here (line 2) is another one, it is yellow. Therefore red has to be 255, green has to be 255 and blue has to be 0. The third line is identical.

Now you get a clear picture of what actually is happening to get the effect of a moving pixel. For each cycle the yellow pixel from the previous cycle is overdrawn by a black pixel and then the very next pixel is drawn yellow. This leads to the impression of a moving pixel.

SDL_FLIP(screen);
SDL_DELAY(5);
UNTIL keypressed;

DISPOSE(pixellocation);
SDL_FREESURFACE(screen);
SDL_QUIT;
END.

When all pixel manipulations are done the screen has to be refreshed as known by SDL_FLIP. To be able to recognize all the pixel movement the loop has to be slown down by known SDL_DELAY. The loop is left if the user presses a key. Finally the allocated memory of the Pointer variable pixellocation and the screen surface are free’d by known DISPOSE and SDL_FREESURFACE. The SDL system is quit by known SDL_QUIT and the program stopped by END.

This file contains the source code: chap4.pas (right click and “save as”)
This file is the executable: chap4.exe (right click and “save as”)

If you didn’t check the demo03.pas program from last chapter you really should check it now. It demonstrates how you can use the pixel by pixel manipulation to create nice effects and the basis for a simple game.

This file contains the source code: demo03.pas (right click and “save as”)
This file is the executable: demo03.exe (right click and “save as”)

The final result should look and behave like this: You have to look quite carefully to recognize a single yellow pixel moving from the left top corner, line by line down until it reaches the right bottom corner.

Result of JEDI-SDL Chapter 4