375 lines
13 KiB
Zig
375 lines
13 KiB
Zig
const std = @import("std");
|
|
const Build = std.Build;
|
|
const sokol = @import("sokol");
|
|
|
|
pub fn build(b: *std.Build) !void {
|
|
const target = b.standardTargetOptions(.{});
|
|
const optimize = b.standardOptimizeOption(.{});
|
|
|
|
const dep_stb = b.dependency("stb", .{});
|
|
const ctx = Context.init(b, dep_stb.module("stb_image"));
|
|
|
|
var run_cmd_step: *std.Build.Step = undefined;
|
|
switch (try buildProject(ctx, target, optimize)) {
|
|
.native => |exe| {
|
|
b.installArtifact(exe);
|
|
|
|
const run_cmd = b.addRunArtifact(exe);
|
|
run_cmd.addPassthruArgs();
|
|
run_cmd_step = &run_cmd.step;
|
|
|
|
{
|
|
const exe_tests = b.addTest(.{ .root_module = exe.root_module, });
|
|
const run_exe_tests = b.addRunArtifact(exe_tests);
|
|
|
|
const test_step = b.step("test", "Run tests");
|
|
test_step.dependOn(&run_exe_tests.step);
|
|
}
|
|
},
|
|
.wasm => |build_step| {
|
|
b.getInstallStep().dependOn(build_step);
|
|
|
|
const dep_sokol = b.dependency("sokol", .{});
|
|
const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{});
|
|
const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk });
|
|
emrun_step.step.dependOn(build_step);
|
|
run_cmd_step = &emrun_step.step;
|
|
|
|
// add an emsdk install step
|
|
const emsdk_install_step = sokol.emSdkInstallStep(b, dep_sokol.builder.dependency("emsdk", .{}), .{});
|
|
b.step("install-emsdk", "Install Emscripten SDK in zig-pkg").dependOn(emsdk_install_step);
|
|
}
|
|
}
|
|
|
|
|
|
const run_step = b.step("run", "Run game");
|
|
run_step.dependOn(run_cmd_step);
|
|
}
|
|
|
|
const ProjectResult = union(enum) {
|
|
native: *std.Build.Step.Compile,
|
|
wasm: *std.Build.Step,
|
|
};
|
|
|
|
fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !ProjectResult {
|
|
const b = ctx.b;
|
|
|
|
const name = "sokol_template_v2";
|
|
|
|
const assets_dir = b.path("assets");
|
|
const asset_files = [_][]const u8{
|
|
"sokoban/sokoban_tilesheet.png",
|
|
"sokoban/sokoban_tilesheet@2.png",
|
|
"roboto-font/Roboto-Regular.ttf",
|
|
"tiled/map.tmx",
|
|
"tiled/tileset.tsx",
|
|
"impact-sounds/footstep_concrete_000.ogg"
|
|
};
|
|
|
|
const is_wasm = target.result.cpu.arch.isWasm();
|
|
const is_debug = optimize == .debug;
|
|
|
|
const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse is_debug;
|
|
var assets_from_filesystem = false;
|
|
if (!is_wasm) {
|
|
assets_from_filesystem = b.option(bool, "assets-filesystem", "Allow reading assets from filesystem") orelse is_debug;
|
|
}
|
|
|
|
var has_tracy = false;
|
|
if (!is_wasm) {
|
|
has_tracy = b.option(bool, "tracy", "Tracy integration") orelse is_debug;
|
|
}
|
|
|
|
const win32_has_console = null;
|
|
const win32_icon_png: ?Build.LazyPath = b.path("assets/icon.png");
|
|
|
|
const exe_mod = b.createModule(.{
|
|
.root_source_file = b.path("src/main.zig"),
|
|
.target = target,
|
|
.optimize = optimize,
|
|
});
|
|
|
|
const dep_tiled = b.dependency("tiled", .{});
|
|
exe_mod.addImport("tiled", dep_tiled.module("tiled"));
|
|
|
|
const dep_stb = b.dependency("stb", .{});
|
|
exe_mod.addImport("stb_image", dep_stb.module("stb_image"));
|
|
exe_mod.addImport("stb_truetype", dep_stb.module("stb_truetype"));
|
|
|
|
var dep_sokol: *Build.Dependency = undefined;
|
|
const platform_mod = b.createModule(.{
|
|
.root_source_file = b.path("src/platform/root.zig")
|
|
});
|
|
exe_mod.addImport("platform", platform_mod);
|
|
{
|
|
const build_options = b.addOptions();
|
|
build_options.addOption(bool, "has_imgui", has_imgui);
|
|
if (assets_from_filesystem) {
|
|
build_options.addOptionPathDirectory("assets_dir", assets_dir);
|
|
}
|
|
platform_mod.addOptions("build_options", build_options);
|
|
|
|
platform_mod.addImport("embedded_assets", try createAssetsModule(b, assets_dir, &asset_files));
|
|
|
|
const math_mod = b.createModule(.{
|
|
.root_source_file = b.path("src/platform/math.zig")
|
|
});
|
|
platform_mod.addImport("math", math_mod);
|
|
|
|
dep_sokol = b.dependency("sokol", .{
|
|
.target = target,
|
|
.optimize = optimize,
|
|
.with_sokol_imgui = has_imgui
|
|
});
|
|
const mod_sokol = dep_sokol.module("sokol");
|
|
platform_mod.linkLibrary(dep_sokol.artifact("sokol_clib"));
|
|
platform_mod.addImport("sokol", mod_sokol);
|
|
|
|
const dep_shdc = dep_sokol.builder.dependency("shdc", .{});
|
|
|
|
var slang = sokol.shdc.Slang{};
|
|
if (is_wasm) {
|
|
slang.wgsl = true;
|
|
slang.glsl300es = true;
|
|
} else {
|
|
slang.glsl410 = true;
|
|
slang.hlsl4 = true;
|
|
slang.metal_macos = true;
|
|
slang.glsl300es = true;
|
|
}
|
|
const mod_shader = try sokol.shdc.createModule(b, "shader", mod_sokol, .{
|
|
.shdc_dep = dep_shdc,
|
|
.input = "src/platform/shader.glsl",
|
|
.output = "src/platform/shader.zig",
|
|
.slang = slang
|
|
});
|
|
mod_shader.addImport("math", math_mod);
|
|
platform_mod.addImport("shader", mod_shader);
|
|
|
|
if (has_imgui) {
|
|
if (b.lazyDependency("cimgui", .{
|
|
.target = target,
|
|
.optimize = optimize,
|
|
})) |dep_cimgui| {
|
|
const cimgui = b.lazyImport(@This(), "cimgui").?;
|
|
const cimgui_conf = cimgui.getConfig(true);
|
|
platform_mod.addImport("cimgui", dep_cimgui.module(cimgui_conf.module_name));
|
|
dep_sokol.artifact("sokol_clib").root_module.addIncludePath(dep_cimgui.path(cimgui_conf.include_dir));
|
|
}
|
|
}
|
|
|
|
platform_mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
|
|
platform_mod.addImport("stb_rect_pack", dep_stb.module("stb_rect_pack"));
|
|
|
|
const dep_tracy = b.dependency("tracy", .{
|
|
.target = target,
|
|
.optimize = optimize,
|
|
.tracy_enable = has_tracy,
|
|
.tracy_only_localhost = true
|
|
});
|
|
if (has_tracy) {
|
|
platform_mod.linkLibrary(dep_tracy.artifact("tracy"));
|
|
}
|
|
platform_mod.addImport("tracy", dep_tracy.module("tracy"));
|
|
}
|
|
|
|
if (is_wasm) {
|
|
return ProjectResult{
|
|
.wasm = buildWasm(b, .{
|
|
.name = "index",
|
|
.root_module = exe_mod,
|
|
.dep_sokol = dep_sokol,
|
|
})
|
|
};
|
|
} else {
|
|
const exe = b.addExecutable(.{
|
|
.name = name,
|
|
.root_module = exe_mod,
|
|
});
|
|
|
|
if (target.result.os.tag == .windows) {
|
|
const has_console = win32_has_console orelse is_debug;
|
|
exe.subsystem = if (has_console) .console else .windows;
|
|
|
|
if (win32_icon_png) |icon_png| {
|
|
buildWin32ResourceFile(ctx, exe.root_module, .{
|
|
.icon_png = icon_png
|
|
});
|
|
}
|
|
}
|
|
|
|
return ProjectResult{
|
|
.native = exe
|
|
};
|
|
}
|
|
}
|
|
|
|
fn createAssetsModule(
|
|
b: *std.Build,
|
|
assets_dir: Build.LazyPath,
|
|
asset_files: []const []const u8
|
|
) !*std.Build.Module {
|
|
const write_files = b.addWriteFiles();
|
|
|
|
var mod_contents: std.ArrayList(u8) = .empty;
|
|
defer mod_contents.deinit(b.allocator);
|
|
|
|
try mod_contents.appendSlice(b.allocator, "pub const files = .{\n");
|
|
const mod = b.createModule(.{});
|
|
for (asset_files) |asset_file| {
|
|
mod.addAnonymousImport(asset_file, .{
|
|
.root_source_file = assets_dir.path(b, asset_file)
|
|
});
|
|
|
|
try mod_contents.appendSlice(b.allocator, " .{ ");
|
|
|
|
try mod_contents.appendSlice(b.allocator, ".name = \"");
|
|
try mod_contents.appendSlice(b.allocator, asset_file);
|
|
try mod_contents.appendSlice(b.allocator, "\"");
|
|
|
|
try mod_contents.appendSlice(b.allocator, ", ");
|
|
|
|
try mod_contents.appendSlice(b.allocator, ".contents = @embedFile(\"");
|
|
try mod_contents.appendSlice(b.allocator, asset_file);
|
|
try mod_contents.appendSlice(b.allocator, "\")");
|
|
|
|
try mod_contents.appendSlice(b.allocator, " },\n");
|
|
}
|
|
try mod_contents.appendSlice(b.allocator, "};\n");
|
|
mod.root_source_file = write_files.add("assets.zig", mod_contents.items);
|
|
|
|
return mod;
|
|
}
|
|
|
|
const Context = struct {
|
|
b: *std.Build,
|
|
png_to_icon: *Build.Step.Compile,
|
|
create_win32_resource: *Build.Step.Compile,
|
|
|
|
pub fn init(b: *std.Build, stb_image: *Build.Module) Context {
|
|
return Context{
|
|
.b = b,
|
|
.png_to_icon = b.addExecutable(.{
|
|
.name = "png-to-icon",
|
|
.root_module = b.createModule(.{
|
|
.target = b.graph.host,
|
|
.root_source_file = b.path("scripts/png-to-icon.zig"),
|
|
.imports = &.{
|
|
.{ .name = "stb_image", .module = stb_image }
|
|
}
|
|
})
|
|
}),
|
|
.create_win32_resource = b.addExecutable(.{
|
|
.name = "create-win32-resource",
|
|
.root_module = b.createModule(.{
|
|
.target = b.graph.host,
|
|
.root_source_file = b.path("scripts/create-win32-resource.zig"),
|
|
})
|
|
})
|
|
};
|
|
}
|
|
};
|
|
|
|
const Win32ResourceFileOptions = struct {
|
|
icon_png: Build.LazyPath
|
|
};
|
|
|
|
fn buildWin32ResourceFile(ctx: Context, mod: *Build.Module, opts: Win32ResourceFileOptions) void {
|
|
const b = ctx.b;
|
|
|
|
const png_to_icon_step = b.addRunArtifact(ctx.png_to_icon);
|
|
png_to_icon_step.addFileArg(opts.icon_png);
|
|
const icon_file = png_to_icon_step.addOutputFileArg("icon.ico");
|
|
|
|
const resource_file_step = b.addRunArtifact(ctx.create_win32_resource);
|
|
resource_file_step.addFileArg(icon_file);
|
|
const resource_file = resource_file_step.addOutputFileArg("resource-file.rc");
|
|
|
|
mod.addWin32ResourceFile(.{ .file = resource_file });
|
|
}
|
|
|
|
const BuildWasmOptions = struct {
|
|
name: []const u8,
|
|
root_module: *std.Build.Module,
|
|
dep_sokol: *std.Build.Dependency,
|
|
};
|
|
|
|
fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step {
|
|
opts.root_module.sanitize_c = .off;
|
|
|
|
// build the main file into a library, this is because the WASM 'exe'
|
|
// needs to be linked in a separate build step with the Emscripten linker
|
|
const main_lib = b.addLibrary(.{
|
|
.name = opts.name,
|
|
.root_module = opts.root_module,
|
|
});
|
|
|
|
const dep_sokol = opts.dep_sokol;
|
|
const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{});
|
|
|
|
// TODO:
|
|
// const emsdk_install_step = sokol.emSdkInstallStep(b, dep_emsdk, .{});
|
|
// main_lib.step.dependOn(emsdk_install_step);
|
|
|
|
patchWasmIncludeDirs(
|
|
opts.root_module,
|
|
dep_emsdk.path("upstream/emscripten/cache/sysroot/include"),
|
|
&(dep_sokol.artifact("sokol_clib").step)
|
|
);
|
|
|
|
const link_step = sokol.emLinkStep(b, .{
|
|
.lib_main = main_lib,
|
|
.target = opts.root_module.resolved_target.?,
|
|
.optimize = opts.root_module.optimize.?,
|
|
.emsdk = dep_emsdk,
|
|
.use_webgl2 = true,
|
|
.use_emmalloc = true,
|
|
.use_filesystem = true,
|
|
.shell_file_path = b.path("src/platform/shell.html"),
|
|
.extra_args = &.{ "-sALLOW_MEMORY_GROWTH", "-sENVIRONMENT=web" }
|
|
}) catch unreachable;
|
|
|
|
return &link_step.step;
|
|
}
|
|
|
|
fn patchWasmIncludeDirs(
|
|
module: *std.Build.Module,
|
|
path: std.Build.LazyPath,
|
|
depend_step: *std.Build.Step
|
|
) void {
|
|
if (module.link_libc != null and module.link_libc.?) {
|
|
// need to inject the Emscripten system header include path into
|
|
// the cimgui C library otherwise the C/C++ code won't find
|
|
// C stdlib headers
|
|
module.addSystemIncludePath(path);
|
|
}
|
|
|
|
for (module.import_table.values()) |imported_module| {
|
|
patchWasmIncludeDirs(imported_module, path, depend_step);
|
|
}
|
|
|
|
for (module.link_objects.items) |link_object| {
|
|
if (link_object != .other_step) {
|
|
continue;
|
|
}
|
|
const lib = link_object.other_step;
|
|
if (&lib.step == depend_step) {
|
|
continue;
|
|
}
|
|
|
|
if (lib.root_module.link_libc != null and lib.root_module.link_libc.?) {
|
|
// need to inject the Emscripten system header include path into
|
|
// the cimgui C library otherwise the C/C++ code won't find
|
|
// C stdlib headers
|
|
lib.root_module.addSystemIncludePath(path);
|
|
|
|
// all C libraries need to depend on the sokol library, when building for
|
|
// WASM this makes sure that the Emscripten SDK has been setup before
|
|
// C compilation is attempted (since the sokol C library depends on the
|
|
// Emscripten SDK setup step)
|
|
lib.step.dependOn(depend_step);
|
|
}
|
|
patchWasmIncludeDirs(lib.root_module, path, depend_step);
|
|
}
|
|
}
|