SDL3 bindings
BSGameLib.Sdl3.Interop is a set of .NET bindings for SDL3, SDL3_image and SDL3_mixer. Its
functions, structs and enums are generated from the SDL headers, and a few helpers sit beside them.
The engine, BSGameLib.Game, is built on these bindings. You can also use them on their own,
without the engine, to open a window, read input or play sound yourself. The bundled libraries are
SDL 3.4.2, SDL_image 3.4.0 and SDL_mixer 3.2.0.
SDL's own documentation, with a page for every function, is on the SDL wiki. The bindings keep SDL's names and signatures, so the wiki applies to them directly.
dotnet add package BSGameLib.Sdl3.Interop --prerelease
Namespaces
| Namespace | What is in it |
|---|---|
BSGameLib.Sdl3.Interop.Generated |
The static classes SDL, SDL_image and SDL_mixer, with every function, and the structs and enums they use. The functions are raw P/Invoke calls. |
BSGameLib.Sdl3.Interop.Manual |
Helpers written by hand: Sdl for error handling, SdlConstants for flags and sizes, SdlKeycodes and SdlKeymod for keys, and SdlVulkan for Vulkan. |
Add a using static for the class you call most. Names are the same as in SDL's C headers, so
SDL_Init is SDL.SDL_Init:
using BSGameLib.Sdl3.Interop.Generated;
using BSGameLib.Sdl3.Interop.Manual;
using static BSGameLib.Sdl3.Interop.Generated.SDL;
using static BSGameLib.Sdl3.Interop.Manual.SdlConstants;
Pointers and unsafe code
The bindings pass SDL's pointers through unchanged. A window, a surface or an event is a pointer,
and the functions that use them are unsafe. Your project sets AllowUnsafeBlocks:
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
Nothing is freed for you. Each object SDL gives you has a function to release it, such as
SDL_DestroyWindow, and SDL_Quit ends the library. Memory that SDL allocates is freed with
SDL_free.
Strings
SDL takes UTF-8 text that ends in a zero byte, and the bindings type it as sbyte*. A C# string
literal has no terminator, and neither does a u8 literal. End the literal with \0 yourself,
and cast the pointer:
using BSGameLib.Sdl3.Interop.Generated;
using BSGameLib.Sdl3.Interop.Manual;
using static BSGameLib.Sdl3.Interop.Generated.SDL;
using static BSGameLib.Sdl3.Interop.Manual.SdlConstants;
unsafe
{
fixed (byte* title = "My game\0"u8)
{
var window = Sdl.Check(SDL_CreateWindow((sbyte*)title, 800, 600, SDL_WINDOW_RESIZABLE));
}
}
A string SDL returns is a pointer to the same kind of zero-terminated text. Convert it with
Marshal.PtrToStringUTF8, which is what Sdl.GetError does for SDL's error text.
Results and errors
SDL's functions that return bool return a byte here: 1 for true and 0 for false. SDL_Init
is one of them. Functions that return a pointer return null on failure.
Sdl.Check turns those results into exceptions. It has an overload for each kind:
Sdl.Check(byte)throws when the result is0.Sdl.Check<T>(T*)throws when the pointer isnull, and otherwise returns it.
Both throw an InvalidOperationException whose message names the method that called Check, and
then SDL's error text: SDL error in Start: <error>. The text comes from SDL_GetError, through
GetError().
Constants and key codes
SdlConstants holds the flags and sizes you pass to SDL, such as SDL_INIT_VIDEO and
SDL_WINDOW_RESIZABLE. Most of them are typed the way SDL3 types them, as uint or ulong.
SdlKeycodes holds the key codes that SDL reports, such as SDLK_ESCAPE. A printable key's code is
its Unicode code point. A key that does not print is 0x40000000 plus its scan code. SdlKeymod
holds the modifier bits, such as KMOD_CTRL.
Vulkan
SdlVulkan holds the Vulkan calls SDL provides: loading the Vulkan library, reading the instance
extensions a window needs, and making a surface. Its functions are plain P/Invoke, like the
generated ones.
The native libraries
The package brings SDL's native libraries with it, and copies the ones for your platform beside your executable when you build or publish:
| Package | Libraries | Platforms |
|---|---|---|
BSGameLib.Sdl3.Native |
SDL3, SDL3_image and SDL3_mixer | Windows x64 and arm64, Linux x64 and arm64, macOS |
BSGameLib.MoltenVK.Native |
MoltenVK, which SDL uses for Vulkan | macOS |
The Linux libraries are built against glibc 2.35 (Ubuntu 22.04), so they run on any distribution with glibc 2.34 or newer and need nothing else installed. The licences for the bundled libraries are copied into your game's output with them; see Licences in your game.
A window
This opens a window. The loop ends when the window is asked to quit, such as by its close button:
using BSGameLib.Sdl3.Interop.Generated;
using BSGameLib.Sdl3.Interop.Manual;
using static BSGameLib.Sdl3.Interop.Generated.SDL;
using static BSGameLib.Sdl3.Interop.Manual.SdlConstants;
unsafe
{
Sdl.Check(SDL_Init(SDL_INIT_VIDEO));
SDL_Window* window;
fixed (byte* title = "Hello SDL3\0"u8)
window = Sdl.Check(SDL_CreateWindow((sbyte*)title, 800, 600, SDL_WINDOW_RESIZABLE));
var running = true;
while (running)
{
SDL_Event e;
while (SDL_PollEvent(&e) != 0)
if (e.type == (uint)SDL_EventType.SDL_EVENT_QUIT)
running = false;
}
SDL_DestroyWindow(window);
SDL_Quit();
}
To find the version of the libraries you have loaded:
using BSGameLib.Sdl3.Interop.Generated;
using static BSGameLib.Sdl3.Interop.Generated.SDL;
int version = SDL_GetVersion();
Console.WriteLine($"SDL {version / 1000000}.{version / 1000 % 1000}.{version % 1000}");