sdl3-bindgen-sys: Complete SDL3 bindings, raw + curated, generated by hs-bindgen

[ bsd3, graphics, library ] [ Propose Tags ] [ Report a vulnerability ]

Machine-generated low-level bindings to the whole SDL 3.4 API: the raw hs-bindgen output (SDL3.Sys.Bindgen.*) plus a curated SDL3.Sys layer with per-function safe/unsafe FFI decisions, typed constants, and native Haskell scalars. SDL's own header documentation rides along in the haddocks.

A generated translation unit of C _Static_asserts checks the baked layouts against your installed SDL at every build, so ABI divergence is a compile error rather than memory corruption.

Supports 64-bit Linux, macOS, and Windows, with SDL >= 3.2 development headers present. See the README for a quick start and the list of deliberate omissions.


[Skip to Readme]

Modules

[Index] [Quick Jump]

Flags

Manual Flags

NameDescriptionDefault
abi-assertions

Verify the baked ABI layout against the system SDL3 headers at build time via generated C static assertions (disable only to diagnose)

Enabled
abi-assertions-exact

Assert every sizeof exactly, including the union-member structs the default lets SDL append to. For maintainers checking a newer SDL; consumers should leave it off. Only has an effect together with abi-assertions.

Disabled
optimize

Build with -O2. Package ghc-options win over cabal's optimization setting, so use -f -optimize for faster iterative builds of this large package.

Enabled
optimize-aggressively

Build with expensive optimizations. Consider this the release build: slow, but paid once if caching is set up, so it may be worth it for artifacts you intend to ship.

Currently: -O2 -fexpose-all-unfoldings -flate-specialise -flate-dmd-anal -fstg-lift-lams.

Disabled
strict

EXPERIMENTAL: compile the generated modules with Strict.

The emitted hs-bindgen code has not been audited for laziness dependence; main library only.

NB: Implies StrictData (GHC semantics), so -f -strict-data does not undo strict fields.

Disabled
strict-data

Compile the generated modules with StrictData (strict fields on every generated record). Main library only; the vendored runtime keeps its upstream semantics. Disable if you need lazy fields.

Enabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.0.0.1, 0.0.0.2, 0.0.0.3
Change log CHANGELOG.md
Dependencies base (>=4.17 && <4.23), bytestring (>=0.11 && <0.13), containers (>=0.6 && <0.9), fin (>=0.3.2 && <0.4), primitive (>=0.9 && <0.10), record-hasfield (>=1.0 && <2), sdl3-bindgen-sys, some (>=1.0.6 && <1.1), template-haskell (>=2.18 && <2.25), vec (>=0.5 && <0.6), vector (>=0.13 && <0.14) [details]
Tested with ghc >=9.8 && <9.9 || >=9.10 && <9.11 || >=9.12 && <9.13
License BSD-3-Clause[multiple license files]
Copyright (c) 2026 Jeremy Nuttall
Author Jeremy Nuttall
Maintainer jeremy@jeremy-nuttall.com
Uploaded by jtnuttall at 2026-09-14T00:23:51Z
Category Graphics
Home page https://github.com/jtnuttall/lithon#readme
Bug tracker https://github.com/jtnuttall/lithon/issues
Source repo head: git clone https://github.com/jtnuttall/lithon(sdl3-bindgen-sys)
Distributions
Downloads 26 total (18 in the last 30 days)
Rating 2.25 (votes: 2) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for sdl3-bindgen-sys-0.0.0.3

[back to package description]

sdl3-bindgen-sys

Fully automated, luxury low-level Haskell bindings to SDL3: the whole SDL 3.4 API, machine-generated from the headers using an ever-so-slightly hacked version of hs-bindgen.

This package gets you SDL3 in full — windowing, input, audio, and the newfangled GPU API — today, if you dare.

CI runs against 64-bit Linux, macOS, and Windows, which is what this package aims to support.

This library aims to be:

  • Complete by construction: Generated from all 58 headers of the SDL 3.4 API (291 modules), so a gap is either a code generation bug or a deliberate omission rather than a binding waiting to be hand-written.
  • Curated: The SDL3.Sys.* layer wraps hs-bindgen's output with best-effort Haskell casing, native scalar types, and FFI safety decisions and recommendations.
  • First-class SDL docs: Every binding carries SDL's header documentation, and notes from the code generator's curation layer.
  • ABI-verified: A generated translation unit of C _Static_asserts verifies the library's baked layouts against your SDL at every build. Divergence is a compile error naming the declaration, not memory corruption. See ABI verification for more.

Quick start

Install build requirements

Install SDL >= 3.2 development files where pkg-config can find them. The development headers must be present, not just the shared library.

Common setups:

  • Debian/Ubuntu: apt install libsdl3-dev
  • macOS: brew install sdl3
  • Arch: pacman -S sdl3
  • Fedora: dnf install SDL3-devel
  • Windows: WSL2 with the Linux instructions, or native MSYS2. See Windows set up

Set up your project

  1. Add sdl3-bindgen-sys to build-depends
  2. Replace the contents of Main.hs with:
{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE BlockArguments #-}

import Control.Monad (unless)
import Foreign.C.ConstPtr (ConstPtr (..))
import Foreign.C.String (peekCString, withCString)
import SDL3.Sys qualified as SDL3

main :: IO ()
main = do
  ok <- SDL3.init SDL3.SDL_INIT_VIDEO
  unless ok do
    err <- peekCString . unConstPtr =<< SDL3.getError
    fail ("SDL_Init: " <> err)
  window <- withCString "hello" \title ->
    SDL3.createWindow (ConstPtr title) 640 480 0
  SDL3.delaySafe 2000
  SDL3.destroyWindow window
  SDL3.quit

For more examples and templates, see lithon-examples in the repository:

  • sdl3-raw is a minimal triangle with an event loop
  • shmup is a playable apecs game running on and rendering through these bindings

Windows set up

There are two ways to set this up that I am aware of. In order of convenience:

WSL2

The Linux instructions apply unchanged. Under WSLg an SDL window displays like any Linux app.

Native (MSYS2)

Install build dependencies:

pacman -Syyu # repeat/restart terminal if pacman asks you to
pacman -S mingw-w64-ucrt-x86_64-sdl3 mingw-w64-ucrt-x86_64-pkgconf

[!NOTE] The UCRT64 pkgconf is required. MSYS2 pkg-config reports POSIX-style paths that GHC can't use on Windows.

Stack users

[!IMPORTANT] Stack users need some additional setup.

Adjust the library version in extra-deps to your desired target.

  1. Run the pacman commands above through stack exec -- pacman ... so that the packages are installed in stack's MSYS2.
  2. Add msys-environment: UCRT64 to your stack.yaml.
  3. Add sdl3-bindgen-sys-0.0.0.3 to extra-deps in your stack.yaml. Running stack build should print out a helpful, pasteable entry for this purpose.

Your stack.yaml should look something like this:

snapshot: lts-24.51
packages:
  - .
extra-deps:
  - sdl3-bindgen-sys-0.0.0.3 # hash may be here if you copy from stack build
msys-environment: UCRT64 # important: build will not work without this
Direct cabal build using an MSYS2 Bash session (e.g., Git Bash)

This can additionally be used with Git bash. You can point cabal at the UCRT64 toolchain and your ghcup GHC:

export PKG_CONFIG_PATH="/c/msys64/ucrt64/lib/pkgconfig"
export PATH="/c/msys64/ucrt64/bin:$PATH"
cabal build \
  --with-compiler=/c/ghcup/ghc/9.12.2/bin/ghc.exe \
  --extra-lib-dirs=/c/msys64/ucrt64/lib \
  --extra-include-dirs=/c/msys64/ucrt64/include

Adjust the GHC path to match your install. This provides a simple way to build and run the examples.

Library structure

For most uses, you will import SDL3.Sys qualified as SDL3. SDL3.Sys re-exports one module per SDL header.

The raw hs-bindgen output lives underneath as SDL3.Sys.Bindgen.*, if you need to drop down to C types.

Safe and unsafe FFI

Most functions come in both FFI flavors: createWindow is an unsafe foreign import; createWindowSafe is the safe one.

General rules for safe vs. unsafe FFI:

  • You must use a safe call if C will call back into Haskell.
  • You should use a safe call if the C call could take a while (e.g., waiting on some OS event, or a locking mechanism, etc.).
  • You should use an unsafe call if the C call is fast; unsafe calls block the current GHC thread (capability) and the garbage collector, but their overhead is very low compared to safe calls.

Typed constants

SDL declares its flag and constant vocabularies as typedef UintN plus #defines, an association C never states, so binding generators cannot recover it.

The curated layer restores this information from an explicit registry maintained alongside the generator.

Similar to the vulkan library, every group member is a pattern synonym typed at its newtype, e.g. SDL_INIT_VIDEO :: SDL_InitFlags.

You can combine bitmask groups with .|. from Data.Bits:

SDL3.init (SDL3.SDL_INIT_VIDEO .|. SDL3.SDL_INIT_AUDIO)

Conversion to and from C types

SDL3.Sys re-exports SDL3.Sys.Runtime, the conversion vocabulary you'll actually reach for: toBool/fromBool for C-typed struct fields (e.g. a keyboard event's repeat), and the CEnum classes for moving between enum newtypes and their integral representations.

Platform support

64-bit platforms only. Linux, macOS (aarch64, Homebrew sdl3), and Windows (MSYS2 UCRT64, including the LLP64 layouts) are all targets, and CI builds the released package on all three. 32-bit targets are rejected by the ABI assertions — 64-bit layouts are baked in.

Common issues

  • Blank screen or crash on macOS — SDL's Cocoa backend requires video and event calls on the process main thread. Keep the SDL loop on main (don't forkIO it); runOnMainThreadSafe is bound for marshalling work onto it.
  • init fails on Windows — the bindings are generated with SDL_MAIN_HANDLED: call setMainReady before init.
  • foo or fooSafe? — every function's haddock states its flavor choice, and the rationale, under its sdl3-bindgen-sys notes section.
  • Branching on SDL3.Sys.PlatformDefines — don't: its two constants (sDL_PLATFORM_LINUX, sDL_PLATFORM_UNIX) are baked to the generation host's value of 1 on every platform.
  • setLinuxThreadPriority(AndPolicy) off-Linux — both exist everywhere but fail with an SDL_GetError message.

What is not bound

Known gaps, so you can discover them here instead of mid-build:

  • Variadic functions: Haskell's FFI cannot express C varargs, so hs-bindgen has nothing to bind them to; the SDL_Log family, SDL_SetError, and SDL_RenderDebugTextFormat are currently unbound. In a future version, these will be bound via a fixed-arity C shim.
  • Most function-like macros: a macro has no linkable symbol, but hs-bindgen does not need one: it parses and typechecks macro bodies and translates them to Haskell functions on a best-effort basis. From the SDL 3.4.2 headers, 31 function-like macros translate and ship in the raw layer (e.g. sDL_AUDIO_BITSIZE, sDL_VERSION_ATLEAST, sDL_DEFINE_PIXELFORMAT, sDL_WINDOWPOS_CENTERED_DISPLAY in SDL3.Sys.Bindgen.*). Two caveats: the curated SDL3.Sys.* layer does not alias them yet, and pending hs-bindgen#2184 they accept only the underlying C integer types, not SDL's newtypes such as SDL_AudioFormat (unwrap first). The hs-bindgen team's survey of SDL's macros counts 108 user-facing function-like macros, so most remain unbound for now: upstream is extending coverage, and a capi import or a C shim can reach the rest. Macro constants are bound; see Typed constants.
  • The seven long-typed SDL_stdinc.h libc clones (strtol/ltoa families, lround/lroundf): their FFI types cannot be correct on both LP64 and LLP64, so they are omitted.
  • Three Windows-only interop functions (SDL_SetWindowsMessageHook, SDL_GetDirect3D9AdapterIndex, SDL_GetDXGIOutputInfo) — niche; native window handles are reached through the bound SDL_GetWindowProperties keys instead.

Versioning

The 0.0.x series is experimental: pin to the minor (e.g., >=0.0.0.1 && <0.0.1) and expect surface-shaping changes.

SDL >= 3.2.0 is required; the surface is generated from 3.4.16. Declarations newer than your SDL still compile and link — their wrapper C is gated on SDL's own version macros, so calling one on an older SDL fails at the call site via SDL_GetError (exactly like the Linux-only functions off Linux). The wrapper gates and the ABI assertion layer are driven by an empirically verified availability registry rather than SDL's (occasionally wrong) \since annotations; a handful of haddock @since lines therefore repeat an upstream floor the registry corrects — where they disagree, the registry wins, and a gated call's SDL_GetError message states the true floor.

Three semantic deltas to know when running against an older SDL:

  • SDL_COLORSPACE_YUV_DEFAULT is baked at its 3.4 value (BT601_LIMITED); 3.2 defined it as JPEG.
  • Below SDL 3.2.12, SDL_MouseWheelEvent.integer_x/integer_y read bytes SDL never wrote — memory-safe (SDL_Event is 128 bytes), but meaningless.
  • Below SDL 3.4.16, SDL_PenProximityEvent.pen_state likewise reads bytes SDL never wrote.

ABI verification

The cabal flag abi-assertions (default) turns on ABI verification, which guards against unexpected layout divergence between SDL3 header versions and varying operating systems.

When ABI assertions are on, cbits/abi_assertions.c statically checks every layout the Haskell side expects against your SDL headers.

Layouts

  • Most structs are asserted at their exact size. A future SDL that expects a bigger allocation for the struct would be an out-of-bounds memory write.
  • Structs the bindings associated solely with a union (like the event structs), are asserted as a prefix. The union's size provides the exact ceiling, so upstream can and will add fields in minors.

Maintainers can build with -f abi-assertions-exact to assert every sizeof exactly. That is how a CI job against a newer SDL flags that the bindings need regenerating; consumers should leave it off.

What to do when you get "static assertion failed"

  1. Check your SDL: pkg-config --modversion sdl3. SDL >= 3.2.0 is required
  2. Make sure you are on a supported architecture. 32-bit targets are not presently supported (see Platform support).
  3. Report it at the issue tracker with the failing lines, your SDL version, and your platform.
  4. If you are comfortable doing so, open a PR regenerating the bindings from the newer SDL. The lithon-codegen README describes the pipeline.

Building with -f-abi-assertions turns off the check, not the mismatch: the bindings would then read and write the baked layout against headers that disagree with it.

Known documentation issues

  • A few module overviews absorb the opening of the first declaration's documentation. Seems to be upstream — Doxygen fuses SDL's file-level category comments before any other tool sees them in these cases.
  • Cross-header references in the raw SDL3.Sys.Bindgen.* docs may appear as plain text; the curated SDL3.Sys.* modules should contain repaired links.

Provenance and licensing

Generated by hs-bindgen driven by the repository's lithon-codegen, from the SDL 3.4.16 headers. The generated tree is never hand-edited, but bugs are mine, not SDL's or hs-bindgen's: report them at the issue tracker.

  • sdl3-bindgen-sys is BSD-3-Clause (see LICENSE).
  • The SDL header documentation embedded in the haddocks is covered by SDL's zlib license (LICENSE_SDL).
  • The vendored hs-bindgen and c-expr runtimes are BSD-3-Clause, (c) Well-Typed LLP and Anduril Industries (LICENSE_hs-bindgen-runtime, LICENSE_c-expr-runtime).