Skip to content
/ SDL.zig Public

A shallow wrapper around SDL that provides object API and error handling

License

Notifications You must be signed in to change notification settings

ikskuh/SDL.zig

Repository files navigation

SDL.zig

A Zig package that provides you with the means to link SDL2 to your project, as well as a Zig-infused header implementation (allows you to not have the SDL2 headers on your system and still compile for SDL2) and a shallow wrapper around the SDL apis that allow a more Zig-style coding with Zig error handling and tagged unions.

Getting started

Linking SDL2 to your project

This is an example build.zig that will link the SDL2 library to your project.

const std = @import("std");
const sdl = @import("sdl"); // Replace with the actual name in your build.zig.zon

pub fn build(b: *std.Build) !void {
    // Determine compilation target
    const target = b.standardTargetOptions(.{});

    // Create a new instance of the SDL2 Sdk
    // Specifiy dependency name explicitly if necessary (use sdl by default) 
    const sdk = sdl.init(b, .{});

    // Create executable for our example
    const demo_basic = b.addExecutable(.{
        .name = "demo-basic",
        .root_source_file = b.path("my-game.zig"),
        .target = target,
    });

    sdk.link(demo_basic, .dynamic, sdl.Library.SDL2); // link SDL2 as a shared library

    // Add "sdl2" package that exposes the SDL2 api (like SDL_Init or SDL_CreateWindow)
    demo_basic.root_module.addImport("sdl2", sdk.getNativeModule());

    // Install the executable into the prefix when invoking "zig build"
    b.installArtifact(demo_basic);
}

Using the native API

This package exposes the SDL2 API as defined in the SDL headers. Use this to create a normal SDL2 program:

const std = @import("std");
const SDL = @import("sdl2"); // Add this package by using sdk.getNativeModule

pub fn main() !void {
    if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0)
        sdlPanic();
    defer SDL.SDL_Quit();

    var window = SDL.SDL_CreateWindow(
        "SDL2 Native Demo",
        SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED,
        640, 480,
        SDL.SDL_WINDOW_SHOWN,
    ) orelse sdlPanic();
    defer _ = SDL.SDL_DestroyWindow(window);

    var renderer = SDL.SDL_CreateRenderer(window, -1, SDL.SDL_RENDERER_ACCELERATED) orelse sdlPanic();
    defer _ = SDL.SDL_DestroyRenderer(renderer);

    mainLoop: while (true) {
        var ev: SDL.SDL_Event = undefined;
        while (SDL.SDL_PollEvent(&ev) != 0) {
            if(ev.type == SDL.SDL_QUIT)
                break :mainLoop;
        }

        _ = SDL.SDL_SetRenderDrawColor(renderer, 0xF7, 0xA4, 0x1D, 0xFF);
        _ = SDL.SDL_RenderClear(renderer);

        SDL.SDL_RenderPresent(renderer);
    }
}

fn sdlPanic() noreturn {
    const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error";
    @panic(std.mem.sliceTo(str, 0));
}

Using the wrapper API

This package also exposes the SDL2 API with a more Zig-style API. Use this if you want a more convenient Zig experience.

Note: This API is experimental and might change in the future

const std = @import("std");
const SDL = @import("sdl2"); // Created in build.zig by using exe.root_module.addImport("sdl2", sdk.getWrapperModule());

pub fn main() !void {
    try SDL.init(.{
        .video = true,
        .events = true,
        .audio = true,
    });
    defer SDL.quit();

    var window = try SDL.createWindow(
        "SDL2 Wrapper Demo",
        .{ .centered = {} }, .{ .centered = {} },
        640, 480,
        .{ .vis = .shown },
    );
    defer window.destroy();

    var renderer = try SDL.createRenderer(window, null, .{ .accelerated = true });
    defer renderer.destroy();

    mainLoop: while (true) {
        while (SDL.pollEvent()) |ev| {
            switch (ev) {
                .quit => break :mainLoop,
                else => {},
            }
        }

        try renderer.setColorRGB(0xF7, 0xA4, 0x1D);
        try renderer.clear();

        renderer.present();
    }
}

build.zig API

/// Just call `Sdk.init(b, .{})` to obtain a handle to the Sdk!
/// Use `sdl` as dependency name by default.
const Sdk = @This();

/// Creates a instance of the Sdk and initializes internal steps.
/// Initialize once, use everywhere (in your `build` function).
///
/// const SdkOption = struct {
///     dep_name: ?[]const u8 = "sdl",
///     maybe_config_path: ?[]const u8 = null,
///     maybe_sdl_ttf_config_path: ?[]const u8 = null,
/// };
pub fn init(b: *Build, opt: SdkOption) *Sdk

/// Returns a module with the raw SDL api with proper argument types, but no functional/logical changes
/// for a more *ziggy* feeling.
/// This is similar to the *C import* result.
pub fn getNativeModule(sdk: *Sdk) *Build.Module;

/// Returns a module with the raw SDL api with proper argument types, but no functional/logical changes
/// for a more *ziggy* feeling, with Vulkan support! The Vulkan module provided by `vulkan-zig` must be
/// provided as an argument.
/// This is similar to the *C import* result.
pub fn getNativeModuleVulkan(sdk: *Sdk, vulkan: *Build.Module) *Build.Module;

/// Returns the smart wrapper for the SDL api. Contains convenient zig types, tagged unions and so on.
pub fn getWrapperModule(sdk: *Sdk) *Build.Module;

/// Returns the smart wrapper with Vulkan support. The Vulkan module provided by `vulkan-zig` must be
/// provided as an argument.
pub fn getWrapperModuleVulkan(sdk: *Sdk, vulkan: *Build.Module) *Build.Module;

/// Links SDL2 or SDL2_ttf to the given exe and adds required installs if necessary.
/// **Important:** The target of the `exe` must already be set, otherwise the Sdk will do the wrong thing!
pub fn link(sdk: *Sdk, exe: *Build.Step.Compile, linkage: std.builtin.LinkMode, comptime library: Library) void;

Dependencies

All of those are dependencies for the target platform, not for your host. Zig will run/build the same on all source platforms.

Windows

For Windows, you need to fetch the correct dev libraries from the SDL download page. It is recommended to use the MinGW versions if you don't require MSVC compatibility.

MacOS

Right now, cross-compiling for MacOS isn't possible. On a Mac, install SDL2 via brew.

Linux

If you are cross-compiling, no dependencies exist. The build Sdk compiles a libSDL2.so stub which is used for linking.

If you compile to your target platform, you require SDL2 to be installed via your OS package manager.

Support Matrix

This project tries to provide you the best possible development experience for SDL2. Thus, this project supports the maximum amount of cross-compilation targets for SDL2.

The following table documents this. The rows document the target whereas the columns are the build host:

Windows (x86_64) Windows (i386) Linux (x86_64) MacOS (x86_64) MacOS (aarch64)
i386-windows-gnu ⚠️
i386-windows-msvc ⚠️
x86_64-windows-gnu ⚠️
x86_64-windows-msvc ⚠️
x86_64-macos
aarch64-macos ⚠️
x86_64-linux-gnu 🧪 🧪 🧪 ⚠️
aarch64-linux-gnu 🧪 🧪 🧪 🧪 ⚠️

Legend:

  • ✅ Cross-compilation is known to work and tested via CI
  • 🧪 Experimental cross-compilation support, covered via CI
  • ⚠️ Cross-compilation might work, but is not tested via CI
  • ❌ Cross-compilation is not possible right now

Contributing

You can contribute to this project in several ways:

  • Use it!
    This helps me to track bugs (which i know that there are some), and usability defects (which we can resolve then). I want this library to have the best development experience possible.
  • Implement/improve the linking experience:
    Right now, it's not possible to cross-compile for MacOS, which is very sad. We might find a way to do so, though! Also VCPKG is not well supported on windows platforms.
  • Improve the wrapper.
    Just add the functions you need and make a PR. Or improve existing ones. I won't do it for you, so you have to get your own hands dirty!