Repository navigation
SDL3 Tutorial
Home › Using C Libraries › SDL3
📘 Start with: Using C Libraries, the ideas these steps rely on.
📖 In the book: Chapter 30, SDL3. Measured with SDL 3.4.18, on Windows and Linux.
SDL, the Simple DirectMedia Layer, is the broadest of the common game libraries. It covers windows, a 2D renderer, a GPU interface, audio, gamepads, cameras, file dialogs, threads and timers, on desktop and mobile alike. SDL3 is its current version.
SDL3 needs three things the other tutorials did not: an include directory for #cinclude, a C macro defined before its headers are read, and its "main callbacks". Each is explained where it comes up.
From github.com/libsdl-org/SDL/releases, release 3.4.18:
| File | Contains | You need it for |
|---|---|---|
SDL3-devel-3.4.18-VC.zip |
include/SDL3/*.h; lib/x64/SDL3.lib (import library) and lib/x64/SDL3.dll
|
building on Windows, linked dynamically |
SDL3-3.4.18.tar.gz |
the source | Linux, and a static library on Windows |
SDL3-3.4.18-win32-x64.zip |
SDL3.dll only |
people who run your program, not for building it |
There is no static library in the Windows package and no Linux package. Both come from building the source.
| Copy | To |
|---|---|
include/ (the whole folder, with SDL3/ inside) |
vendor/sdl3/include/ |
lib/x64/SDL3.lib |
vendor/sdl3/SDL3_windows_x86_64.lib |
lib/x64/SDL3.dll |
SDL3.dll, beside your program
|
SDL's docs/README-linux.md lists the packages its build can use. The build enables whatever it finds, so install the list for full audio and video support. Then:
tar -xzf SDL3-3.4.18.tar.gz
cmake -S SDL3-3.4.18 -B build -DCMAKE_BUILD_TYPE=Release -DSDL_STATIC=ON -DSDL_SHARED=ON
cmake --build build| Copy | To |
|---|---|
SDL3-3.4.18/include/ |
vendor/sdl3/include/ |
build/libSDL3.a |
vendor/sdl3/SDL3-static_linux_x86_64.a |
build/libSDL3.so.0 |
vendor/sdl3/SDL3_linux_x86_64.so |
build/libSDL3.so.0 |
libSDL3.so.0, beside your program
|
Copy the .so with cp -L, because libSDL3.so.0 is a link.
SDL's example renderer/01-clear opens a window whose colour cycles smoothly. It is written with SDL3's main callbacks. Instead of writing the main loop yourself, you give SDL four functions:
| Callback | Called |
|---|---|
AppInit |
once, at the start |
AppEvent |
for every event: a key, the mouse, closing the window |
AppIterate |
once per frame |
AppQuit |
once, at the end |
SDL recommends this style because it works unchanged on platforms where a program cannot own its main loop, such as phones and the web. Here is the example in tiko:
'' SDL's example renderer/01-clear: a window whose colour cycles, built
'' on SDL3's main callbacks. A number on the command line stops it
'' after that many frames instead of when the window is closed.
#cmdline "-cinclude vendor/sdl3/include -cdefine SDL_MAIN_HANDLED"
#cinclude once "SDL3/SDL.h"
#cinclude once "SDL3/SDL_main.h"
#foreignlib SDL3 dynamic "vendor/sdl3/SDL3"
'' Everything the callbacks share, reached through SDL's appstate.
type AppState
window as SDL_Window ptr
renderer as SDL_Renderer ptr
frames as int32
maxFrames as int32
end type
function AppInit cdecl( byval appstate as any ptr ptr, _
byval argc as int32, byval argv as zstring ptr ptr ) _
as SDL_AppResult
SDL_SetAppMetadata( "Example Renderer Clear", "1.0", _
"com.example.renderer-clear" )
if SDL_Init( SDL_INIT_VIDEO ) = false then
print "Couldn't initialize SDL: "; *SDL_GetError( )
return SDL_APP_FAILURE
end if
var st = new AppState
st->maxFrames = valint( command( 1 ) ) '' 0: until closed
*appstate = st
if SDL_CreateWindowAndRenderer( "examples/renderer/clear", _
640, 480, SDL_WINDOW_RESIZABLE, _
@st->window, @st->renderer ) = false then
print "Couldn't create window/renderer: "; *SDL_GetError( )
return SDL_APP_FAILURE
end if
SDL_SetRenderLogicalPresentation( st->renderer, 640, 480, _
SDL_LOGICAL_PRESENTATION_LETTERBOX )
return SDL_APP_CONTINUE
end function
function AppEvent cdecl( byval appstate as any ptr, _
byval event as SDL_Event ptr ) as SDL_AppResult
if event->type = SDL_EVENT_QUIT then return SDL_APP_SUCCESS
return SDL_APP_CONTINUE
end function
function AppIterate cdecl( byval appstate as any ptr ) as SDL_AppResult
var st = cast( AppState ptr, appstate )
var now = SDL_GetTicks( ) / 1000.0
var red = 0.5 + 0.5 * SDL_sin( now )
var green = 0.5 + 0.5 * SDL_sin( now + SDL_PI_D * 2 / 3 )
var blue = 0.5 + 0.5 * SDL_sin( now + SDL_PI_D * 4 / 3 )
SDL_SetRenderDrawColorFloat( st->renderer, red, green, blue, _
SDL_ALPHA_OPAQUE_FLOAT )
SDL_RenderClear( st->renderer )
SDL_RenderPresent( st->renderer )
st->frames += 1
if st->frames = st->maxFrames then return SDL_APP_SUCCESS
return SDL_APP_CONTINUE
end function
sub AppQuit cdecl( byval appstate as any ptr, _
byval result as SDL_AppResult )
var st = cast( AppState ptr, appstate )
if st <> nullptr then
print "drew "; str( st->frames ); " frames"
delete st
end if
end sub
'' SDL calls AppInit once, then AppEvent and AppIterate until one of
'' them returns something other than SDL_APP_CONTINUE, then AppQuit.
var rc = SDL_EnterAppMainCallbacks( 0, nullptr, @AppInit, _
@AppIterate, @AppEvent, @AppQuit )
end rctiko sdl-clear.tiko
./sdl-clear # Windows: SDL3.dll beside it
LD_LIBRARY_PATH=. ./sdl-clear # LinuxClose the window and the program prints how many frames it drew. With a number, ./sdl-clear 60 stops by itself:
drew 60 frames
#cmdline "-cinclude vendor/sdl3/include -cdefine SDL_MAIN_HANDLED"
#cinclude once "SDL3/SDL.h"
#cinclude once "SDL3/SDL_main.h"
#foreignlib SDL3 dynamic "vendor/sdl3/SDL3"-
-cinclude vendor/sdl3/include. SDL's headers include each other as#include <SDL3/SDL_stdinc.h>. For angle-bracket includes clang searches only the include directories, so SDL'sincludefolder has to be one. A program then writes#cinclude once "SDL3/SDL.h", as C writes#include <SDL3/SDL.h>. Inside#cmdlinethe path is relative to where you runtiko. In a project, put it intiko.toml, where it is relative totiko.toml:[target.game] main = "main.tiko" options = ["-cinclude", "vendor/sdl3/include", "-cdefine", "SDL_MAIN_HANDLED"]
-
-cdefine SDL_MAIN_HANDLED. In C,SDL_main.hsupplies the program'smainfunction, which runs the callbacks. A tiko program has its own start, so this macro tellsSDL_main.hto declare things only. -
SDL_main.hdeclaresSDL_EnterAppMainCallbacks, which the program's last statement calls with the addresses of the four callbacks. That is exactly what C'smainwould have done.
SDL's C example keeps the window and renderer in global variables. SDL also offers something better: AppInit receives the address of a pointer, appstate, and whatever you store there is handed to every later callback. The tiko version allocates one AppState with new in AppInit and frees it in AppQuit.
-
The callbacks must match SDL's types exactly:
cdecl, with the same parameter types as the header'sSDL_AppInit_func,SDL_AppEvent_funcand the others. A mismatch is only warningTKO0833: passing different pointer types, and the program still builds, so treat that warning as an error. -
SDL3 functions return C
bool. Compare them withfalse:if SDL_Init( SDL_INIT_VIDEO ) = false then. -
event->type.SDL_Eventis a C union, and->reaches its fields as for any pointer to a struct. -
SDL_PI_D,SDL_ALPHA_OPAQUE_FLOATare#defines with number values, so they come across as constants.
💡 Running without a screen. SDL reads the environment variable
SDL_VIDEO_DRIVER. WithSDL_VIDEO_DRIVER=dummy, it draws nowhere, so a program that opens windows still runs on a build server, for example:SDL_VIDEO_DRIVER=dummy ./sdl-clear 60.
Windows. Build the static library from the source with the Visual Studio Build Tools, using SDL's own CMake build:
tar -xzf SDL3-3.4.18.tar.gz
cmake -S SDL3-3.4.18 -B build -DSDL_STATIC=ON -DSDL_SHARED=OFF
cmake --build build --config ReleaseCopy build/Release/SDL3-static.lib (13 MB) to vendor/sdl3/SDL3-static_windows_x86_64.lib, then change the link lines:
#cmdline "-cinclude vendor/sdl3/include -cdefine SDL_MAIN_HANDLED"
#cinclude once "SDL3/SDL.h"
#cinclude once "SDL3/SDL_main.h"
#ifdef __FB_WIN32__
#foreignlib SDL3 static "vendor/sdl3/SDL3-static", _
"system:cfgmgr32", "system:ole32", "system:oleaut32", _
"system:setupapi", "system:shell32"
#else
#foreignlib SDL3 static "vendor/sdl3/SDL3-static"
#endifLinked statically, SDL needs five Windows libraries: cfgmgr32, ole32, oleaut32, setupapi and shell32. They were found by linking without any and reading the undefined symbol errors. SetupDiGetClassDevsA is in setupapi, CoInitializeEx in ole32, and so on, as Microsoft's documentation for each function says.
Linux. vendor/sdl3/SDL3-static_linux_x86_64.a from step 3 links with nothing more. SDL loads the window system and the sound system while the program runs. The program is 3.6 MB, with no .so to ship.
Linux: distributions newer than Ubuntu 24.04 package it as libsdl3-dev. Otherwise run sudo cmake --install build after step 3, then sudo ldconfig. Headers go to /usr/local/include/SDL3 and the library to /usr/local/lib, both searched by default, so no -cinclude is needed:
#cmdline "-cdefine SDL_MAIN_HANDLED"
#cinclude once "SDL3/SDL.h"
#cinclude once "SDL3/SDL_main.h"
#foreignlib SDL3 dynamic "system:SDL3"Linux, without LD_LIBRARY_PATH: add -Wl -rpath,$ORIGIN to the link (in #cmdline inside #ifdef __FB_LINUX__, or in tiko.toml's options), and the program finds libSDL3.so.0 beside itself.
Windows: keep vendor/sdl3/ in a shared folder such as C:\tiko-libs\sdl3, and give every project -cinclude C:/tiko-libs/sdl3/include and -cdefine SDL_MAIN_HANDLED in its tiko.toml. Point #foreignlib at the shared folder with an absolute path, or in a binding header. Copy SDL3.dll beside each program or put its folder on PATH.
| Problem | Fix |
|---|---|
TKO0336: unable to import C header with 'SDL3/SDL_stdinc.h' file not found
|
add -cinclude for SDL's include folder |
'sdl_init' is not declared after a TKO0336
|
the header failed to import; fix the first error |
undefined symbol: ... SetupDiGetClassDevsA (and others) linking statically |
add the five system libraries in step 5 |
undefined symbol: __declspec(dllimport) strnlen |
your tiko toolchain predates the fix to its C runtime import library; update tiko |
Couldn't initialize SDL: No available video device on Linux |
no display SDL can use: check DISPLAY/WAYLAND_DISPLAY, install the X11 or Wayland development packages before building SDL, or use SDL_VIDEO_DRIVER=dummy
|
warning TKO0833 at SDL_EnterAppMainCallbacks
|
a callback's parameters differ from SDL's type; make them match |
Back: GLFW · raylib · SQLite · Using C Libraries
tiko-lang · Home · Feature Index · Keyword Index · License
FreeBASIC manual pages reproduced under the GNU FDL 1.3.
- Generics
- Interfaces
- For Each
- Iterators
- Tuples
- Array Results
- Defer
- Lambdas
- Block Strings
- Partial Types
- String Slices
- Embed
- Typed Variadics
- C Header Import
- Foreign Library Linking
- UString
- Fixed-Width Types
- Nullptr
- String Library
- Format
- Array
- Map
- Set
- LinkedList
- OrderedMap
- OrderedSet
- Sorting
- Optional
- Result
- Smart Pointers
- Date and Time (Chrono)
- Filesystem
- File Streams
- Config (INI and TOML)
- Codec
- Hash
- UUID
- OS
- Process
- Net
- HTTP and TLS
- Math Library
- Finance
- Logging
- Terminal TUI Library
- PsControls
- tkControls
- sys/win
- Goroutines and Channels
- LLVM Backend
- tiko build
- TIKO_COMPILER
- Variable Declarations
- Standard Data Types
- User Defined Types
- Procedures
- Operators
- Control Flow Statements
- Converting Data Types
- Array Functions
- String Functions
- Mathematical Functions
- Bit Manipulation
- Date and Time Functions
- File I/O Functions
- Console Functions
- User Input (Runtime Library)
- User Input Functions (Graphics Library)
- Screen Functions
- 2D Drawing Functions
- Graphics Library Reference
- Graphics Keyword List
- Memory Functions
- Threading Support Functions
- Error Handling Functions
- Operating System Functions
- Preprocessor
- Intrinsic Defines
- Miscellaneous Keywords
- Modularizing