ikskuh / SDL.zig

A shallow wrapper around SDL that provides object API and error handling
MIT License
360 stars 79 forks source link
gamedev sdl sdl2 zig zig-package ziglang

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:

Contributing

You can contribute to this project in several ways: