Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

SDL Library

Table of Contents

  1. SDL Library
    1. Prerequisites
    2. Initialization
  2. Library Reference
    1. Setup and Teardown
      1. sdl.init(flags: int)
      2. sdl.quit()
      3. sdl.wasInit(flags: int): int
      4. sdl.initSubSystem(flags: int)
      5. sdl.quitSubSystem(flags: int)
      6. sdl.videoModeOK(w: int, h: int, bpp: int, flags: int): bool
      7. sdl.setVideoMode(w: int, h: int, bpp: int, flags: int): bool
    2. Window Management
      1. sdl.showCursor([show: bool]): bool
      2. sdl.grabInput([grab: bool]): bool
      3. sdl.warpMouse(x: int, y: int)
      4. sdl.caption([cap: string]): string
    3. OpenGL Support
      1. sdl.gl.setAttribute(attr: int, val: int)
      2. sdl.gl.getAttribute(attr: int): int
      3. sdl.gl.swapBuffers()
    4. Event Handling
      1. "active" event flags
      2. sdl.event.poll(): string, ...
      3. sdl.event.wait()
      4. sdl.event.enableUnicode([enable: bool]): bool
      5. sdl.event.keyRepeat(delay: int = blah, interval: int = blurb)
    5. Keysym and Modifier Definitions
    6. Reverse Keysym Map
    7. Joystick
      1. sdl.joystick.count()
      2. sdl.joystick.info(idx: int): ...
      3. sdl.joystick.open(idx: int)
      4. sdl.joystick.close(idx: int)
      5. sdl.joystick.isOpen(idx: int): bool
      6. sdl.joystick.enableEvents([enable: bool]): bool
    8. class SdlSurface
      1. constructor()
      2. width(): int
      3. height(): int
      4. pitch(): int
      5. format(): table
      6. pixels(): int
      7. lock(): Vector(u8)
      8. unlock()
      9. free()
    9. Image (SDL_Image)
      1. sdl.image.version
      2. sdl.image.init(flags: int)
      3. sdl.image.quit()
      4. sdl.image.load(filename: string): SdlSurface

Work in Progress

This library is not yet finished. It may be useful for some tasks, but is still missing features and some features may change or be removed.

SDL is a popular cross-platform library for setting up a window suitable for multimedia applications such as games, and for dealing with various subsystems such as graphics, sound, and input. SDL isn't always the best option, but for getting things set up quickly and easily, it's hard to beat.

The following is a list of SDL's subsystems which are wrapped, and how much support they have:

  • General - Just about fully supported.
  • Video - Just enough to set up an OpenGL context, and basic SDL_Surface support for loading textures. I really doubt I'll be wrapping too much more of this, because frankly, 2D graphics just are not that useful anymore. Seriously, the 2D performance of modern video cards - even embedded ones - is abysmal. Use 3D graphics.
  • Window Management - Some functions supported, not that hard to wrap the rest.
  • Event - In place and functional; a few events are not yet supported.
  • Joystick - Fully supported.

In addition, the following SDL addon libraries are wrapped:

  • SDL_Image - Fully supported, at least the parts that don't depend on the SDL_RWops

The following subsystems are not (and will likely never be) wrapped:

  • Audio - SDL's audio subsystem is not that great. Chances are I'll wrap OpenAL instead, or maybe SDL_Mixer. Or both.
  • CDROM - The CDROM subsystem will be dropped in SDL 1.3, probably because no one uses Redbook audio anymore.
  • Thread - SDL threads + D = bad times (the GC does not liek those threads).
  • Time - The Timer system has the same GC problems as threads, and the rest of the functionality is already handled by the time standard library.
  • RWops - It's not useful for MiniD (and io and stream already provide all the functionality).

Prerequisites

This library requires Derelict. You'll also need a recent update of version 1.2 of the SDL library installed. Linux users probably already have it installed, but if not, it's easy enough to get it. You can download the runtime libraries from SDL's Downloads page. If you want to use the SDL_Image wrappings, you'll also need to grab that library.

Initialization

To initialize the library within your host app, just do this:

import minid.addons.sdl;
...

// after opening a VM and loading the standard libraries into it
SdlLib.init(t);

That's it. Now any MiniD code loaded by your host program will be able to import the "sdl" module.

Library Reference

This library is exposed through the "sdl" module in MiniD. This library is not meant to be a "transliteration" of SDL; rather it is a more idiomatic wrapping, using somewhat different naming and organization from the original. This is partly for aesthetics, partly for performance, and partly to avoid excessive wrapping work.

For information on SDL itself, please see SDL's documentation page.

Setup and Teardown

sdl.init(flags: int)

The first function you must call before using anything else. This initializes SDL and any subsystems you specify using the flags parameter. You specify which subsystems to initialize by performing a bitwise OR of the following flags:

  • sdl.initAudio - Initialize the audio subsystem. May be removed
  • sdl.initVideo - Initialize the video subsystem.
  • sdl.initJoystick - Initialize the joystick subsystem.
  • sdl.initEverything - A bitwise OR of all the previous flag, for initializing all subsystems at once.

For example, if you wanted to initialize SDL and the Audio and Video subsystems, you would do:

sdl.init(sdl.initAudio | sdl.initVideo)

An exception will be thrown if any part of the initialization failed.

sdl.quit()

This uninitializes all subsystems and SDL. This must be called before your app exits. On Windows, at least, I get an "unhandled exception" error if I don't call it. I'm assuming similarly unsavory things would happen on other OSes.

To ensure that this gets called before program exit, consider putting it in a scope(exit) statement in your app's main function:

function main()
{
	sdl.init(sdl.initEverything)

	scope(exit)
		sdl.quit()
}

That way it'll always be called.

sdl.wasInit(flags: int): int

Check if the given subsystem(s) has been initialized. flags is a bitmask of subsystems, the same as for sdl.init(). Returns a bitmask of all initialized subsystems, masked with the flags you passed in. For example:

// Initialize Video if it hasn't been already
if(!sdl.wasInit(sdl.initVideo))
	sdl.initSubSystem(sdl.initVideo)

sdl.initSubSystem(flags: int)

Initialize the subsystems indicated by the flags bitmask. Throws an exception if anything goes wrong.

sdl.quitSubSystem(flags: int)

Quits the subsystems indicated by the flags bitmask. Throws an exception if anything goes wrong.

sdl.videoModeOK(w: int, h: int, bpp: int, flags: int): bool

This checks if the given set of parameters is a legal video mode, and returns whether or not it is as a bool. This does not change the video mode. For a description of the parameters, see the next function.

sdl.setVideoMode(w: int, h: int, bpp: int, flags: int): bool

Attempts to set the video mode, which creates (or resizes) an application window. w and h are the width and height of the graphics area you want, in pixels. bpp is the desired bit depth of the display, or 0 to indicate that you don't care (in which case it will use the desktop's current bit depth). 15, 16, 24, and 32 are common values. flags is a bitwise OR of the following options, or 0:

  • sdl.swSurface - Create the video surface in system memory.
  • sdl.hwSurface - Create the video surface in video memory.
  • sdl.asyncBlit - Enables asynchronous updates of the display. Might improve performance if multiple threads are updating it.
  • sdl.anyFormat - Normally, if a video surface of the requested bits-per-pixel (bpp) is not available, SDL will emulate one with a shadow surface. This flag prevents this behavior and causes SDL to use the video surface, regardless of its pixel depth.
  • sdl.doubleBuf - Use double-buffering. Only valid in combination with sdl.hwSurface.
  • sdl.fullscreen - Use a full-screen video mode. If a hardware resolution change is not possible (for whatever reason), the next higher resolution will be used and the display window centered on a black background.
  • sdl.opengl - Create an OpenGL context. When you use this flag, you must have previously set the GL video attributes with sdl.gl.setAttribute.
  • sdl.openglBlit - Same as above but allows 2D blitting operations as well. May be removed
  • sdl.resizable - Creates a window which can be resized. You can handle "resize" messages whenever the window is resized.
  • sdl.noFrame - Attempts to create a window without a titlebar and external window decorations.

This function returns a boolean indicating whether or not the video mode was successfully set, rather than throwing an exception. A common thing to do, for instance, is to first try using a hardware-accelerated video surface, and fall back to a software surface, as demonstrated here:

if(!sdl.setVideoMode(800, 600, 24, sdl.hwSurface))
	if(!sdl.setVideoMode(800, 600, 24, sdl.swSurface))
		throw "Could not set video mode"

It first attempts to use a hardware surface, and falls back to the software, throwing an exception if neither worked.

Window Management

sdl.showCursor([show: bool]): bool

Gets or sets whether the OS cursor is visible when it is above the application window. Called without params, returns a bool which indicates that. Called with true, shows the cursor, and with false, hides it, returning nothing.

sdl.grabInput([grab: bool]): bool

Gets or sets the "input grab" status. Works like sdl.showCursor. When input grabbing is enabled, the mouse is not permitted to leave the bounds of the window and almost all keypresses are passed directly to it.

sdl.warpMouse(x: int, y: int)

Move the mouse cursor to the given position. Generates a mouse movement event if the cursor is not already there.

sdl.caption([cap: string]): string

Gets or sets the window's caption (the name shown by the window manager in the title bar, taskbar, etc.). When passed a parameter, sets the caption; when passed no parameters, gets it.

OpenGL Support

The functions and flags for SDL's OpenGL support are held in the sdl.gl sub-namespace.

sdl.gl.setAttribute(attr: int, val: int)

Sets the attribute specified by attr to the value val. val is interpreted differently based on attr. attr must be one of the following values:

  • sdl.gl.redSize, sdl.gl.greenSize, sdl.gl.blueSize, sdl.gl.alphaSize - The size, in bits, of the red, green, blue, and alpha channels respectively of the GL backbuffer. You can probably just use sdl.gl.bufferSize instead; chances are you don't need this much control most of the time.
  • sdl.gl.bufferSize - The size, in bits, of one pixel of the backbuffer. Common values are 16, 24, and 32.
  • sdl.gl.doubleBuffer - Whether or not to perform double-buffering. val can be 0 or 1. If double-buffering is enabled, you'll have to use sdl.gl.swapBuffers at the end of each frame to display anything.
  • sdl.gl.depthSize - How many bits per pixel the depth buffer should use. 16 is a very common value.
  • sdl.gl.stencilSize - How many bits per pixel the stencil buffer should use. Note that many cards can combine the depth and stencil buffers for better performance, so you should try to set them so the sum of the depth and stencil sizes is 32 (i.e. depth and stencil both 16, or depth of 24 and stencil of 8).
  • sdl.gl.accumRedSize, sdl.gl.accumGreenSize, sdl.gl.accumBlueSize, sdl.gl.accumAlphaSize - The size, in bits, of the red, green, blue, and alpha channels respectively of the GL accumulation buffer.
  • sdl.gl.stereo - Whether to enable stereo buffers (for use in generating a 3D image). val can be 0 or 1.
  • sdl.gl.multiSampleBuffers - Whether to enable backbuffer multisampling (antialiasing). val can be 0 or 1. The OpenGL driver must support the GL_ARB_multisample extension.
  • sdl.gl.multiSampleSamples - Goes with the previous flag. Usually a power of 2, like 1, 2, or 4.
  • sdl.gl.acceleratedVisual - If val is 1, guarantees hardware acceleration.
  • sdl.gl.swapControl - Vertical syncing (vsync). If val is 1, enables vertical syncing. (Supposedly this is deprecated in SDL 1.3 but I don't know why..)

Throws an exception if anything went wrong.

Here's a common setup:

sdl.gl.setAttribute(sdl.gl.bufferSize, 32)
sdl.gl.setAttribute(sdl.gl.depthSize, 16)
sdl.gl.setAttribute(sdl.gl.doubleBuffer, 1)

Note that you'd do this before calling sdl.setVideoMode.

sdl.gl.getAttribute(attr: int): int

Gets the current value of the given attribute (as described in the above list). Throws an exception if anything went wrong.

sdl.gl.swapBuffers()

If double-buffering was enabled with sdl.gl.setAttribute, you need to call this at the end of rendering each frame for anything to show up onscreen.

Event Handling

All the event-handling-related functionality is kept in the sdl.event sub-namespace.

sdl.event.poll and sdl.event.wait return event data as multiple values: the first value is a string giving the type of the event, and then there are up to four data values that go with each event. Here is a listing of the possible event types, when they are generated, and any associated data:

TypeDescriptionDataData Description
"active"The mouse enters or leaves the application window, or the application gains or loses keyboard focus, or the application is minimized or maximized.gain: bool, flags: intgain indicates whether the app is gaining (true) or losing (false) some kind of focus. What focus(es) are changing are indicated by the flags value, which is an OR-ing of the values given after this table.
"keyDown"A keyboard key goes from being up to being pressed.sym: int, mod: int, ch: charsym is the keysym, as documented below (sdl.key). mod is a bitmask of modifiers, also documented below. ch is the translated Unicode character of the key, which is only meaningful if you've actually enabled it with sdl.event.enableUnicode.
"keyUp"A keyboard key goes from being pressed to being up.sym: int, mod: intSame meaning as for "keyDown".
"mouseMotion"The mouse (or other pointing device) is moved.x: int, y: int, dx: int, dy: intx and y are the absolute coordinates of the mouse within the application window, while dx and dy are the change in x and y from the previous mouse position.
"mouseDown"A mouse button goes from being up to being pressed.button: intbutton indicates which button was pressed, with 1 being left, 2 middle, 3 right, 4 rolling the scrollwheel up, and 5 rolling the scrollwheel down. Each "click" of the scrollwheel counts as pressing and then releasing these buttons. TODO: get some kind of mouse button enumeration?
"mouseUp"A mouse button goes from being pressed to being up.button: intSame meaning as for "mouseDown".
"joyAxis"A joystick axis changes value.joy: int, axis: int, val: intjoy is the index of the joystick, axis is the index of the axis on that joystick, and val is the new position of that axis in the range -32768 to 32767.
"joyBall"A joystick trackball is moved.joy: int, ball: int, dx: int, dy: intjoy is the index of the joystick, ball is the index of the trackball on that joystick, and dx and dy are the relative change in position of that trackball since the last update. Trackballs only report relative values.
"joyHat"A joystick HAT changes value.joy: int, hat: int, val: intjoy is the index of the joystick, hat is the index of the HAT on that joystick, and val is a bitflag indicating which direction it is. 0 means "no direction", 1 is up, 2 is right, 4 is down, and 8 is left; directions can also be pressed simultaneously. TODO: get some kind of HAT enumeration?
"joyButtonDown"A joystick button goes from being up to being pressed.joy: int, button: intjoy is the index of the joystick, and button is the index of the button being pressed.
"joyButtonUp"A joystick button goes from being pressed to being up.joy: int, button: intjoy is the index of the joystick, and button is the index of the button being released.
"quit"The user closes the application, i.e. by clicking on the "close" button on the titlebar.none
"resize"The window is resized (only happens if you set the video mode to use the resizable flag).w:int, h: intw and h are the new width and height of the graphical area of the window.
"expose"The window manager has drawn over part of the window and the window needs to be redrawn.none

"active" event flags

The "active" event has a bitflag value that tells what kind of focus is being gained or lost. This bitflag can be an OR-ing of any of the following values:

TypeDescription
sdl.event.mouseFocusThe mouse moves into or out of the viewable area of the window.
sdl.event.inputFocusThe window is made the current window or is backgrounded.
sdl.event.activeThe window is minimized (to a taskbar or equivalent) or maximized.

sdl.event.poll(): string, ...

If there is an event waiting on the event queue, returns it and its data. Otherwise, returns nothing. You must handle events for your app to be responsive!

Because of the return signature of this function, you can actually use it as an iterator in a foreach loop. This enables a straightforward translation of a C-style SDL event loop:

SDL_Event e;

while(SDL_PollEvent(&e))
{
	switch(e.type)
	{
		case SDL_KEYUP:
			...
	}
}

into MiniD:

foreach(type, a, b, c, d; sdl.event.poll) // NO parens here
{
	switch(type)
	{
		case "keyUp":
			... // here a, b, c, d correspond to the event values
	}
}

Notice that you are iterating over the function and so you don't call it when using it like this. In this way the foreach loop will iterate over all available events in the queue and iteration will end when all events have been processed.

Here's a simple test of the event system using poll:

import sdl

// just need to set up a window, so only video is needed. you HAVE to set up a window for events
// to be generated (otherwise where is the OS going to send the events?).
sdl.init(sdl.initVideo)

// be sure to do this!
scope(exit)
	sdl.quit()

// create the window
if(!sdl.setVideoMode(640, 480, 32, sdl.hwSurface))
	throw "oh no!  not all of dallas!" // which was target of where they were

// our quitting flag
local quitting = false

// the main loop
while(!quitting)
{
	foreach(e, a, b, c, d; sdl.event.poll)
	{
		switch(e)
		{
			case "quit":
				// if the user hits the close button on the window, quit!
				quitting = true
				break

			case "keyDown":
				if(a >= sdl.key.a && a <= sdl.key.z)
					writeln("it's a letter!")
				else if(a == sdl.key.escape)
					quitting = true // we can quit by hitting escape, too
				else
					writeln("some other key!")

			default:
				// some other event
				break
		}
	}
}

sdl.event.wait()

sdl.event.wait waits (blocks) until at least one event is available, and then returns the first event. This is in contrast to sdl.event.poll, which always returns immediately. This is useful for "passive" apps which don't need to be constantly polling for user input or redrawing the screen. Apps like this will likely respond to the "expose" event to do their screen updates.

sdl.event.enableUnicode([enable: bool]): bool

Gets or sets the keypress Unicode translation mode. By default this is off. If you enable it (by passing true), the "keyDown" events will be accompanied by the character that corresponds to the keypress. You can use this to get accurate text input. This is somewhat time-consuming, however, so you should disable Unicode translation if you're not getting text input. You can check the mode by calling this without arguments.

sdl.event.keyRepeat(delay: int = blah, interval: int = blurb)

Enables or disables key autorepeat. This is useful for text entry. The delay param indicates how long, in milliseconds, after the initial keypress that the repeating should begin. interval indicates how long in milliseconds between repeated keypress events. Each repeated keypress is sent as a separate key event to the event queue.

If called without params, enables key repeat and sets the repeat delay and interval to reasonable defaults. To disable key repeat, call it with a delay of 0: "sdl.event.keyRepeat(0)".

Keysym and Modifier Definitions

Each key on your keyboard has a corresponding code, known as its "keysym", which SDL uses to identify it. Keysyms are given with "keyDown" and "keyUp" events to indicate which key is being pressed or released.

There are far too many keysyms to list them all here, so instead I'll link you to the official list on the SDL Wiki. All the SDL keysyms begin with SDLK_ and are all caps, but in MiniD, the naming is a bit different. In general, if a keysym is called SDLK_ABC, the MiniD name for it will be sdl.key.abc. That is, remove the SDLK_ prefix and lowercase the rest of the name. There are a couple exceptions to this rule:

  • SDLK_0 through SDLK_9 are called sdl.key._0 through sdl.key._9, because identifiers can't start with numerals.
  • The SDLK_KP_* symbols (other than SDLK_KP0 through SDLK_KP9) use camelCase instead of an underscore, i.e. SDLK_KP_PERIOD becomes sdl.key.kpPeriod.

In addition to the SDL keysyms, sdl.key contains a namespace mod which holds key modifiers (like shift, alt etc.). The official list shows that these modifiers start with KMOD_. Similarly to the SDLK_ enumeration, in MiniD these names are lowercased and become things like sdl.key.mod.shift.

Here's a tiny example showing a key event handler that can check for Ctrl+z as a keypress:

case "keyDown":
	if(a == sdl.key.z && (b & sdl.key.mod.ctrl))
		writeln("undooooo")
	break

Reverse Keysym Map

There is a member called sdl.niceKeys which is the inverse of the sdl.key table; that is, you index it with a keysym, and you get the name of the key as given in sdl.key. This is better for switching on keys than switching on the keysym, as it allows you to use constants in the switch, giving better performance.

switch(sdl.niceKeys[a])
{
	case "up": // up arrow
	case "down": // down arrow
}

This translation isn't done for you by the normal event mechanism as this kind of fixed-key layout is only good for small apps; larger apps will likely allow custom key mapping, and would employ some mechanism that doesn't care about the specific keys being pressed.

Joystick

SDL supports an arbitrary number of game controllers ("joysticks"), each with an arbitrary number of axes, buttons, HATs, and trackballs. You can query how many joysticks there are and information about them. You can then open joysticks, which makes them start producing events that you can handle in your event loop.

All joystick-related functionality is contained in the sdl.joystick sub-namespace.

sdl.joystick.count()

Returns how many joysticks are attached to the system. Joysticks are uniquely identified by an integer index in the range [0 .. sdl.joystick.count()).

sdl.joystick.info(idx: int): ...

Gets information about a joystick. This function returns five values: the name (a string), the number of axes, the number of buttons, the number of HATs, and the number of trackballs.

sdl.joystick.open(idx: int)

Opens the given joystick. When a joystick is open, it will produce events. Note that all joysticks start as closed, so you have to open at least one before you will get any joystick events.

sdl.joystick.close(idx: int)

Closes the given joystick, so that it no longer produces any events.

sdl.joystick.isOpen(idx: int): bool

Returns whether or not the given joystick is open.

sdl.joystick.enableEvents([enable: bool]): bool

Gets or sets the global joystick event enabled flag. By default this is true, so any joysticks you open will produce events. If you set this to false, no joystick events will be produced, even by open joysticks. Note that changing the value of this flag can empty out the event queue completely (for some reason).

class SdlSurface

This class wraps an SDL_Surface object. It really only supports enough functionality to be able to load textures into OpenGL.

constructor()

Not useful for MiniD code, as this creates an empty object, and trying to do anything with it will fail. Instead you'd usually get an instance of this from image loading functions.

width(): int

Gets the width of this surface in pixels.

height(): int

Gets the height of this surface in pixels.

pitch(): int

Gets the pitch of this surface in pixels. (I forget what the pitch is..)

format(): table

Gets this surface's pixel format as a table. The returned table will have the following fields, all integers:

NameDescription
bppThe number of bytes used to represent each pixel.
rshift, gshift, bshift, ashiftThe position of each of the four color components, in bits, measured from the LSB.
rmask, gmask, bmask, amaskThe (unshifted) bitmasks of each of the four color components.

You can use this information to read the image data in a format-independent way by using the lock() method and reading it out of the vector. For instance, assuming bpp were 4, you could extract the color components as so:

local fmt = sfc.format()
local p = sfc.lock()
scope(exit) p.unlock() // good idea

// we're assuming fmt.bpp == 4, so this would be slightly different if it weren't
for(i: 0 .. #p, 4)
{
	local pixel = p.readUInt(i) // a uint is 4 bytes
	local r = (pixel >> fmt.rshift) & fmt.rmask // shift and mask to get the red component
}

pixels(): int

Gets an integer representation of the pointer to this surface's pixel data. This is useful for passing as a pointer to the OpenGL texture data functions.

lock(): Vector(u8)

Locks this surface's pixel data for reading and writing. This returns a Vector that is mapped to this data. Once you're done with the data, you should unlock the surface.

unlock()

Unlocks a locked surface. Attempting to access the data returned by lock() after the surface has been unlocked has undefined behavior (TODO: make this safer?).

free()

Deallocates the image data associated with this surface. It's fine to call this on an already-freed surface. Calling any other method on a freed surface will give an error.

If you're using the surface as a way to load texture data into OpenGL, technically you can free a surface after you've used e.g. glTexImage2D. However if the texture is invalidated (that is, your app lost the GL context and it has to be reloaded), you'll have to load the texture again, so it might be a good idea to keep the surface around in case you need to reload the texture data into OpenGL.

Image (SDL_Image)

This is a wrapping of the SDL_Image SDL addon library. It really.. just supports one function, since the rest depend on SDL's RWops, which will probably never be wrapped. TODO: would be nice to load from Streams, though.. could they be wrapped in an RWop object?

All these members are kept in the sdl.image sub-namespace.

sdl.image.version

A string representation of the loaded version of the SDL_Image library, in the form "major.minor.patch".

sdl.image.init(flags: int)

Initialize the SDL_Image library. This has to be called before you can load images with it. The flags parameter takes a bitwise ORing of the following flags:

  • sdl.image.initJPG - Initialize the JPG loader.
  • sdl.image.initPNG - Initialize the PNG loader.
  • sdl.image.initTIF - Initialize the TIFF loader.
  • sdl.image.initAll - Initialize all of the above three.

Note that SDL_Image can actually load a number of other formats as well, but for some reason it's only these three that have to be initialized separately. I don't get it either. Probably licensing crap.

sdl.image.quit()

Unloads the SDL_Image library.

sdl.image.load(filename: string): SdlSurface

Loads the given image file from disk, and if successful, returns the image data as an SdlSurface object.