@hackage / sdl3-bindgen-sys

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

0.0.0.1

About

Metadata

  • Last updated , by jtnuttall
  • License BSD-3-Clause
  • Categories Graphics
  • Maintained by: jeremy@jeremy-nuttall.com

  • Lottery factor: 1

Links

Installation

Tested Compilers

  1. 9.12.2
  2. 9.12.1
  3. 9.10.3
  4. 9.10.2
  5. 9.10.1
  6. 9.8.4
  7. 9.8.3
  8. 9.8.2
  9. 9.8.1

Package Flags

Use the -f option with cabal commands to enable flags

    abi-assertions (on by default)

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

    optimize (on by default)

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

    optimize-aggressively (off by default)

    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.

    strict (off by default)

    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.

    strict-data (on by default)

    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.

Readme

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.

Quick start

Install SDL >= 3.2 development files where pkg-config can find them: apt install libsdl3-dev (Debian/Ubuntu), brew install sdl3 (macOS), pacman -S sdl3 (Arch), MSYS2's mingw-w64-ucrt-x86_64-sdl3 (Windows), or build from source on distros that don't package SDL3 yet.

The development headers must be present, not just the shared library.

Add sdl3-bindgen-sys to build-depends, and:

{-# 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 something real, 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

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 — hs-bindgen skips them. That means the SDL_Log family (including the V-suffixed va_list variants), SDL_SetError, and SDL_RenderDebugTextFormat (the fixed-string SDL_RenderDebugText is bound). For logging, format in Haskell first and write a one-line "%s" C shim if you need SDL's log routing; for SDL_SetError there is currently no workaround in this package.
  • Function-like macros — no linkable symbol exists. 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.2. 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.

Two semantic deltas to know when running against a 3.2-series 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.

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.2 headers. The generated tree is never hand-edited, but bugs are mine, not SDL'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).