sokol-zig と build.zig の構成

2024/08/05に公開

以前 zig で OpenGL、そして wasm
という記事を書きましたが、
2024 年の8月 zig-0.13.0 では sokol-zig が快適です。
sokol-zig により desktop と wasm 共用の 3D プログラムがさくっとできます。

c の sokol プログラムを zig に写経中。
https://ousttrue.github.io/learnopengl-examples/
小さい C のプログラムを zig に移植して感じを掴むのがおススメです。
github-action で wasm 生成できました。

手順その1。sokol-zig を自作プログラムで使う

zig init

> mkdir hello
> cd hello
> zig init

sokol-zig への依存を追加

> zig fetch --save=sokol git+https://github.com/floooh/sokol-zig.git

src/main.zig から @import("sokol") できるようにする

build.zigに追記
    const dep_sokol = b.dependency("sokol", .{
        .target = target,
        .optimize = optimize,
    });
    exe.root_module.addImport("sokol", dep_sokol.module("sokol"));

src/main.zig で sokol を使う

https://github.com/floooh/sokol-zig/blob/master/src/examples/clear.zig

src/main.zig
const sokol = @import("sokol");
const sg = sokol.gfx;

var pass_action = sg.PassAction{};

export fn init() void {
    sg.setup(.{
        .environment = sokol.glue.environment(),
        .logger = .{ .func = sokol.log.func },
    });
    pass_action.colors[0] = .{
        .load_action = .CLEAR,
        .clear_value = .{ .r = 1.0, .g = 0.0, .b = 0.0, .a = 1.0 },
    };
}

export fn frame() void {
    const g = pass_action.colors[0].clear_value.g + 0.01;
    pass_action.colors[0].clear_value.g = if (g > 1.0) 0.0 else g;
    sg.beginPass(.{
        .action = pass_action,
        .swapchain = sokol.glue.swapchain(),
    });
    sg.endPass();
    sg.commit();
}

export fn cleanup() void {
    sg.shutdown();
}

pub fn main() void {
    sokol.app.run(.{
        .init_cb = init,
        .frame_cb = frame,
        .cleanup_cb = cleanup,
        // .event_cb = __dbgui_event,
        .width = 400,
        .height = 300,
        .window_title = "Clear (sokol app)",
        .icon = .{ .sokol_default = true },
        .logger = .{ .func = sokol.log.func },
    });
}

build

> zig build run

これで動けば成功。

sokol-zig は C の sokol よりも簡単かもしれない

sokol の存在は前から知っていたのだけどビルドシステムがよくわからなくて敬遠しておりました。

しかし sokol-zig では build.zig でコード生成をこなしてしまえるので、
本家 csokol よりプロジェクトの構成が簡単なのです。

手順その2。sokol の shader 変換を含むプロジェクト

最初の例は sokol の動確で CreateWindow まで。
Shader を使って三角形描画するのが真の HelloWorld.

TODO: まとめる

prebuild の sokol-tools-bin をダウンロードし glsl からコード生成する build step を作ります。

*.glsl.zig
# sokol-tools-bin への依存を登録
> zig fetch --save=sokol-tools-bin git+https://github.com/floooh/sokol-tools-bin.git
// a separate step to compile shaders
pub fn sokolShdc(
    b: *std.Build,
    target: std.Build.ResolvedTarget,
    comptime shader: []const u8,
) *std.Build.Step {
    const optional_shdc = comptime switch (builtin.os.tag) {
        .windows => "win32/sokol-shdc.exe",
        .linux => "linux/sokol-shdc",
        .macos => if (builtin.cpu.arch.isX86()) "osx/sokol-shdc" else "osx_arm64/sokol-shdc",
        else => @panic("unsupported host platform, skipping shader compiler step"),
    };
    const tools = b.dependency("sokol-tools-bin", .{}); // download して cache される
    const shdc_path = tools.path(b.pathJoin(&.{ "bin", optional_shdc })).getPath(b);
    const glsl = if (target.result.isDarwin()) "glsl410" else "glsl430";
    const slang = glsl ++ ":metal_macos:hlsl5:glsl300es:wgsl";
    return &b.addSystemCommand(&.{
        shdc_path,
        "-i",
        shader,
        "-o",
        shader ++ ".zig",
        "-l",
        slang,
        "-f",
        "sokol_zig",
    }).step;
}
build.zig
            // 下記のようにすると
            // "src/hoge.glsl" から "src/hoge.glsl.zig" を生成します
            // dependOn により exe より前に生成します。
            exe.step.dependOn(sokolShdc(
                b,
                target,
                "src/hoge.glsl",
            ));           
src/main.zig
const shader = @import("hoge.glsl.zig"); // glsl から生成された zig

Discussion