© Will Briggs 2021
W. BriggsC++20 for Lazy Programmershttps://doi.org/10.1007/978-1-4842-6306-8_29

29. Moving on with SDL

Will Briggs1  
(1)
Lynchburg, VA, USA
 
By using SSDL, you’ve gone most of the way toward becoming an SDL programmer. To keep going, you can
  • Dump SSDL and get a tutorial on SDL. You’ll see a lot that you recognize. Many SSDL functions are SDL functions with an “S” stuck on the front (as in, SDL_PollEvent became SSDL_PollEvent). Usually SDL functions need one more initial argument, often of type SDL_Window* or SDL_Renderer*, two types you’ll learn right away. You can usually guess what’ll be needed (hint: functions with “Render” in the name probably need SDL_Renderer*).

  • Or, keep SSDL, but extend with more SDL features – say, joystick support.

Either way it’ll be useful to look behind SSDL to what it’s been hiding from you. Let’s start with initialization and cleanup code.

The typical SDL program has a version of main that looks like Example 29-1.
// An SDL program that does nothing of interest (yet)
//      -- from _C++20 for Lazy Programmers_
#include <iostream>
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_mixer.h"
#include "SDL_ttf.h"
int main(int argc, char** argv)
{
    // initialization
    constexpr int DEFAULT_WIDTH = 640, DEFAULT_HEIGHT = 480;
    if (SDL_Init (SDL_INIT_EVERYTHING) < 0) return -1;
    SDL_Window* sdlWindow
        = SDL_CreateWindow ("My SDL program!",
                            SDL_WINDOWPOS_UNDEFINED,
                            SDL_WINDOWPOS_UNDEFINED,
                            DEFAULT_WIDTH, DEFAULT_HEIGHT,
                            0);      // flags are 0 by default
    if (!sdlWindow) return -1;       // nope, it failed
    int rendererIndex = -1;          //pick first renderer that works
    SDL_Renderer* sdlRenderer
        = SDL_CreateRenderer (sdlWindow, rendererIndex,
                              0);    // flags are 0 by default
    if (!sdlRenderer) return -1;    // nope, it failed
    SDL_ClearError();               // Initially, no errors
    static constexpr int IMG_FLAGS  // all available types
        = IMG_INIT_PNG | IMG_INIT_JPG | IMG_INIT_TIF;
    if (! (IMG_Init (IMG_FLAGS) & IMG_FLAGS))  // start SDL_Image
        return -1;
    if (TTF_Init() == -1) return -1;         // ...and SDL_TTF
                                             // ...and SDL_Mixer
    int soundsSupported = Mix_Init
                       (MIX_INIT_FLAC|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG);
    if (!soundsSupported) return -1;
    int soundInitialized = (Mix_OpenAudio(88020, MIX_DEFAULT_FORMAT,
                                          MIX_DEFAULT_CHANNELS, 4096) != -1);
    if (!soundInitialized) SDL_ClearError();
            // if it failed, we can still do the program
            //   -- just forget the error
    // STUFF YOU ACTUALLY WANT TO DO GOES HERE
    // cleanup -- we're about to end the program anyway, but it's considered nice anyway
    if (soundInitialized) { Mix_AllocateChannels(0); Mix_CloseAudio(); }
    Mix_Quit();
    TTF_Quit();
    IMG_Quit();
    SDL_DestroyRenderer(sdlRenderer);
    SDL_DestroyWindow  (sdlWindow);
    SDL_Quit();
    return 0;
}
Example 29-1

A simple SDL program

../images/477913_2_En_29_Chapter/477913_2_En_29_Fig1_HTML.jpg
Figure 29-1

The SDL program from Examples 29-1, 29-2, and 29-3. Was it worth it?

In SSDL, the initialization code in Example 29-1 is done by the constructors for SSDL_Display and SSDL_SoundSystem. Example 29-1 has simplifications. One biggie is, we can’t throw an SSDL_Exception without SSDL (duh), so instead we deal with failure to launch with return -1;.

See what it does: initializes SDL (this must be done first); creates the window (good!); creates a “renderer,” needed to draw or paste images; initializes SDL_Image and SDL_TTF, needed for image and fonts. If anything goes wrong, we give up, because you can’t really go on without these things.

It also supports sound by initializing SDL_Mixer if it can.

The cleanup code shuts down the helper libraries, kills the window and renderer, and finally shuts down SDL.

I obviously prefer my way, for organization, neatness, and not having to type all that code in every new program; but since we’re looking at the messy guts of it all, I guess we’ll leave it in main as game programmers often do. At least I’m not using global variables.

Writing code

So can we get a program that will actually do something? Sure, but first let me talk about what else SSDL has been covering up:
  • Many SSDL types represents SDL types, usually pointers.
    • SSDL_Color is essentially an SDL_Color.

    • SSDL_Display is essentially an SDL_Renderer * and an SDL_Window *. (If you care how these get passed into SDL functions that want them, see the SSDL_Display class definition, specifically two user-defined conversions or cast operators.)

    • SSDL_Font is a TTF_Font *.

    • SSDL_Image is an SDL_Texture *.

    • SSDL_Music is a Mix_Music *.

    • SSDL_Sound is a Mix_Chunk * and an int (for channel).

    • SSDL_Sprite is an SDL_Texture* plus a lot of fields, to be sent to SDL_RenderCopyEx in a complicated call (see SSDL_RenderSprite).

      These classes exist mostly to protect beginners from pointers and everyone from having to do his/her own dynamic allocation and cleanup.

  • Besides RGB, SDL_Color and SSDL_Color have an “alpha” member which also ranges from 0 to 255. 0 means completely transparent and 255 means completely opaque. To use it you’ll need SDL functions with “blend” in the name.

  • Forget ssin and sout; you’ll use TTF_RenderText_Solid (see SSDL_Display::RenderTextLine).

  • SDL is always using dynamic memory, but you can’t use new and delete. SDL and its helpers provide their own allocation and deallocation functions, for example, SDL_CreateTexture and SDL_DestroyTexture; TTF_OpenFont and TTF_CloseFont. You have to use them.

OK, so let’s do something, cheating by seeing how SSDL did it. I’ll put an image on the screen and wait for someone to press a key. Hoo-ah!

I’ll use code from SSDL_LoadImage and SSDL_RenderImage for the image (searching for these in the SSDL code – they’ve got to be there somewhere). If you’re following along by searching yourself – please do! – you’ll see I leave out calls to SSDL_Display::Instance (that’s just there to ensure the initialization code gets called first, and we did that already). We won’t stretch the image, so I’ll omit references to stretchWidth and stretchHeight and use the image’s actual size. I rename variables as needed. With a little more cleanup, I get the code in Example 29-2, which goes immediately after the initialization code in Example 29-1.
// Draw an image
SDL_Surface* sdlSurface = IMG_Load("media/pupdog.png");
if (!sdlSurface) return -1;
SDL_Texture* image      = SDL_CreateTextureFromSurface
                                  (sdlRenderer, sdlSurface);
if (!image) return -1;
SDL_FreeSurface(sdlSurface);
SDL_Rect dst;                // dst is where it's going on screen
dst.x = 0; dst.y = 0;
SDL_QueryTexture(image, nullptr, nullptr, &dst.w, &dst.h);
                             // get width and height of image
SDL_RenderCopy(sdlRenderer, image, nullptr, &dst);
Example 29-2

Displaying an image in SDL

Waiting for a key…I read SSDL_WaitKey, then the thing that it calls, then the things it calls, and eventually can construct the monstrosity in Example 29-3. It goes right after the image display code in Example 29-2.

And, finally, I can see the output displayed in Figure 29-1.
// Waiting for a response
SDL_Event sdlEvent;
SDL_RenderPresent(sdlRenderer);       // display everything
bool isTimeToQuit = false;
while (!isTimeToQuit)
{
    if (SDL_WaitEvent(&sdlEvent) == 0) return -1;
                                     // handle quit messages
    if (sdlEvent.type == SDL_QUIT) isTimeToQuit = true;
    if (sdlEvent.type == SDL_KEYDOWN
        && sdlEvent.key.keysym.scancode == SDL_SCANCODE_ESCAPE)
      isTimeToQuit = true;
    if (sdlEvent.type == SDL_KEYDOWN)// Got that key? quit
            isTimeToQuit = true;
}
Example 29-3

Waiting for a keystroke in SDL

Well, what do you know, it works. And it only took me 100 lines!

Admittedly, I wrote some awful code there: everything’s in main. But I already did my good coding (I hope) in building the SSDL library. If I were going to write good code, I’d have just said
int main (int argc, char** argv)
{
    const SSDL_Image PUPPY = SSDL_LoadImage("media/pupdog.png");
    SSDL_RenderImage(PUPPY, 0, 0);
    SSDL_WaitKey();
    return 0;
}

Game programs are notorious for bad practices: long functions like this one, global variables, and pointers out the wazoo. As you start programming SDL, you can show everyone how to do it right.

Antibugging

Sometimes I get a crash at program’s end: SDL or a helper library fails on part of its cleanup. I’m probably not supposed to, but I admit, I comment out cleanup code. It won’t matter after the program ends anyway.

Compiling

In Unix or MinGW, take an SSDL-capable Makefile for your platform (MinGW or Unix) and remove all references to SSDL.

In Microsoft Visual Studio, take an SSDL-capable project (.vcxproj, plus .vcxproj.filters and .vcxproj.user), load it, and remove all references to SSDL – that is, under Project Properties ➤ Configuration Properties, for all platforms (if possible) and all configurations....
  • C/C++ ➤ General ➤ Additional Include Directories: Take out the path to SSDL includes.

  • Linker ➤ General ➤ Additional Library Directories: Take out the path to SSDL libraries.

  • Linker ➤ Input ➤ Additional Dependencies: Take out ssdl<whatever it is>.lib.

Then compile, and run, as you would an SSDL project.

Further resources

I think the best reference is libsdl.org. Documentation on SDL_Image, and others, is there; you just have to find it (I do a web search for what I want and it’ll take me there). And it’s hard to beat Lazy Foo’ (lazyfoo.net) for tutorials.