From 2f84bc8cd8080d25bf97fbcf7b5d7905ff01a772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Catarino=20Fran=C3=A7a?= Date: Sat, 20 Jan 2024 15:01:49 -0300 Subject: [PATCH 1/7] initial impl (https://github.com/kassane/sokol-d/issues/7) --- .github/workflows/build.yml | 7 +- build.zig | 289 +++++++++++++++--- build.zig.zon | 10 +- src/examples/clear.d | 14 +- .../{debugtext-print.d => debugtext_print.d} | 0 src/examples/mrt.d | 3 +- src/examples/{sgl-context.d => sgl_context.d} | 2 +- src/examples/{user-data.d => user_data.d} | 4 +- src/handmade/math.d | 14 +- src/handmade/math.zig | 18 ++ 10 files changed, 290 insertions(+), 71 deletions(-) rename src/examples/{debugtext-print.d => debugtext_print.d} (100%) rename src/examples/{sgl-context.d => sgl_context.d} (99%) rename src/examples/{user-data.d => user_data.d} (95%) create mode 100644 src/handmade/math.zig diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3afa9ad..a53c372 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,9 +22,10 @@ jobs: sudo apt-get update sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev - - name: Build Summary - run: zig build -DzigCC --summary all - - name: Running Test if: runner.os != 'Windows' run: zig build test -DzigCC + - name: Build Native + run: zig build -DzigCC --summary all + - name: Build Wasm + run: zig build -DbetterC -DzigCC --summary all -Dtarget=wasm32-emscripten diff --git a/build.zig b/build.zig index b76548a..c8fa4f9 100644 --- a/build.zig +++ b/build.zig @@ -60,6 +60,23 @@ pub fn buildLibSokol(b: *Build, options: LibSokolOptions) !*CompileStep { }); lib.root_module.sanitize_c = false; lib.linkLibC(); + lib.root_module.root_source_file = .{ .path = "src/handmade/math.zig" }; + + if (options.target.result.isWasm()) { + // make sure we're building for the wasm32-emscripten target, not wasm32-freestanding + if (lib.rootModuleTarget().os.tag != .emscripten) { + std.log.err("Please build with 'zig build -Dtarget=wasm32-emscripten", .{}); + return error.Wasm32EmscriptenExpected; + } + // one-time setup of Emscripten SDK + if (try emSdkSetupStep(b, options.emsdk.?)) |emsdk_setup| { + lib.step.dependOn(&emsdk_setup.step); + } + // add the Emscripten system include seach path + const emsdk_sysroot = b.pathJoin(&.{ emSdkPath(b, options.emsdk.?), "upstream", "emscripten", "cache", "sysroot" }); + const include_path = b.pathJoin(&.{ emsdk_sysroot, "include" }); + lib.addSystemIncludePath(.{ .path = include_path }); + } // resolve .auto backend into specific backend by platform const backend = resolveSokolBackend(options.backend, lib.rootModuleTarget()); @@ -170,21 +187,22 @@ pub fn build(b: *Build) !void { const sokol_backend: SokolBackend = if (opt_use_gl) .gl else if (opt_use_wgpu) .wgpu else .auto; var target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); // ldc2 w/ druntime + phobos2 works on MSVC if (target.result.os.tag == .windows and target.query.isNative()) { target.result.abi = .msvc; // for ldc2 target.query.abi = .msvc; // for libsokol } - - const optimize = b.standardOptimizeOption(.{}); - const sokol = try buildLibSokol(b, .{ + const emsdk = b.dependency("emsdk", .{}); + const lib_sokol = try buildLibSokol(b, .{ .target = target, .optimize = optimize, .backend = sokol_backend, .use_wayland = opt_use_wayland, .use_x11 = opt_use_x11, .use_egl = opt_use_egl, + .emsdk = emsdk, }); // LDC-options options @@ -205,30 +223,29 @@ pub fn build(b: *Build) !void { "blend", "mrt", "saudio", - "sgl-context", - "debugtext-print", - "user-data", + "sgl_context", + "debugtext_print", + // "user_data", // Need GC for user data [associative array] }; inline for (examples) |example| { const ldc = try ldcBuildStep(b, .{ .name = example, - .artifact = sokol, + .artifact = lib_sokol, .sources = &[_][]const u8{b.fmt("{s}/src/examples/{s}.d", .{ rootPath(), example })}, - .d_packages = &[_][]const u8{ - b.dependency("automem", .{}).path("source").getPath(b), - b.dependency("ikod_containers", .{}).path("source").getPath(b), - }, - .betterC = enable_betterC, + .betterC = if (std.mem.eql(u8, example, "user-data")) false else enable_betterC, .dflags = &[_][]const u8{ "-w", // warnings as error // more info: ldc2 -preview=help (list all specs) "-preview=all", + "-lowmem", }, // fixme: https://github.com/kassane/sokol-d/issues/1 - betterC works on darwin .zig_cc = if (target.result.isDarwin() and !enable_betterC) false else enable_zigcc, .target = target, - .optimize = optimize, + .optimize = if (target.result.isWasm()) .ReleaseSmall else optimize, + .kind = if (target.result.isWasm()) .obj else .exe, + .emsdk = emsdk, }); b.getInstallStep().dependOn(&ldc.step); } @@ -301,60 +318,64 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { // betterC disable druntime and phobos if (options.betterC) - try cmds.append("--betterC"); + try cmds.append("-betterC"); switch (options.optimize) { .Debug => { + try cmds.append("-debug"); try cmds.append("-d-debug"); - try cmds.append("--gc"); // debuginfo for non D dbg - try cmds.append("-g"); // debuginfo - try cmds.append("--O"); + try cmds.append("-gc"); // debuginfo for non D dbg + try cmds.append("-g"); // debuginfo for D dbg + try cmds.append("-gf"); + try cmds.append("-gs"); try cmds.append("-vgc"); try cmds.append("-vtls"); try cmds.append("-verrors=context"); + try cmds.append("-boundscheck=on"); }, .ReleaseSafe => { - try cmds.append("--O3"); - try cmds.append("--release"); - try cmds.append("--enable-inlining"); - try cmds.append("--boundscheck=on"); + try cmds.append("-O3"); + try cmds.append("-release"); + try cmds.append("-enable-inlining"); + try cmds.append("-boundscheck=safeonly"); }, .ReleaseFast => { - try cmds.append("--O3"); - try cmds.append("--release"); - try cmds.append("--enable-inlining"); - try cmds.append("--boundscheck=off"); + try cmds.append("-O3"); + try cmds.append("-release"); + try cmds.append("-enable-inlining"); + try cmds.append("-boundscheck=off"); }, .ReleaseSmall => { - try cmds.append("--Oz"); - try cmds.append("--release"); - try cmds.append("--enable-inlining"); - try cmds.append("--boundscheck=off"); + try cmds.append("-Oz"); + try cmds.append("-release"); + try cmds.append("-enable-inlining"); + try cmds.append("-boundscheck=off"); }, } // Print character (column) numbers in diagnostics - try cmds.append("--vcolumns"); + try cmds.append("-vcolumns"); // object file output (zig-cache/o/{hash_id}/*.o) if (b.cache_root.path) |path| { // immutable state hash - try cmds.append(b.fmt("-od={s}", .{b.pathJoin(&.{ path, "o", &b.cache.hash.peek() })})); + if (options.kind != .obj) + try cmds.append(b.fmt("-od={s}", .{b.pathJoin(&.{ path, "o", &b.cache.hash.peek() })})); // mutable state hash try cmds.append(b.fmt("-cache={s}", .{b.pathJoin(&.{ path, "o", &b.cache.hash.final() })})); } // name object files uniquely (so the files don't collide) - try cmds.append("--oq"); + try cmds.append("-oq"); // remove object files after success build, and put them in a unique temp directory - try cmds.append("--cleanup-obj"); + // try cmds.append("-cleanup-obj"); // disable LLVM-IR verifier // https://llvm.org/docs/Passes.html#verify-module-verifier - try cmds.append("--disable-verify"); + try cmds.append("-disable-verify"); // keep all function bodies in .di files - try cmds.append("--Hkeep-all-bodies"); + try cmds.append("-Hkeep-all-bodies"); // automatically finds needed library files and builds try cmds.append("-i"); @@ -396,6 +417,12 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { try cmds.append("-link-defaultlib-shared"); } + // C include path + for (lib_sokol.root_module.include_dirs.items) |include_dir| { + const path = include_dir.path_system.getPath(b); + try cmds.append(b.fmt("-P-I{s}", .{path})); + } + // library paths for (lib_sokol.root_module.lib_paths.items) |libpath| { if (libpath.path.len > 0) // skip empty paths @@ -414,13 +441,13 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { const c_source_file = link_object.c_source_file; for (c_source_file.flags) |flag| if (flag.len > 0) // skip empty flags - try cmds.append(b.fmt("--Xcc={s}", .{flag})); + try cmds.append(b.fmt("-Xcc={s}", .{flag})); break; } // C defines for (lib_sokol.root_module.c_macros.items) |cdefine| { if (cdefine.len > 0) // skip empty cdefines - try cmds.append(b.fmt("--Xcc=-D{s}", .{cdefine})); + try cmds.append(b.fmt("-Xcc=-D{s}", .{cdefine})); break; } @@ -462,10 +489,10 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { // ldc2 doesn't support zig native (a.k.a: native-native or native) if (options.target.result.isDarwin()) try cmds.append(b.fmt("--mtriple={s}-apple-{s}", .{ if (options.target.result.cpu.arch.isAARCH64()) "arm64" else @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag) })) - else if (options.target.result.isWasm()) - try cmds.append(b.fmt("--mtriple={s}-unknown-unknown-{s}", .{ @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag) })) - else - try cmds.append(b.fmt("--mtriple={s}-{s}-{s}", .{ @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag), @tagName(options.target.result.abi) })); + else if (options.target.result.isWasm()) { + try cmds.append("-L-allow-undefined"); + try cmds.append(b.fmt("--mtriple={s}-unknown-unknown-wasm", .{@tagName(options.target.result.cpu.arch)})); + } else try cmds.append(b.fmt("--mtriple={s}-{s}-{s}", .{ @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag), @tagName(options.target.result.abi) })); // cpu model (e.g. "baseline") if (options.target.query.isNative()) @@ -477,15 +504,19 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { .@"test" => "test", .obj => "obj", }; + // output file - try cmds.append(b.fmt("--of={s}", .{b.pathJoin(&.{ b.install_prefix, outputDir, options.name })})); + if (options.kind != .obj) + try cmds.append(b.fmt("-of={s}", .{b.pathJoin(&.{ b.install_prefix, outputDir, options.name })})) + else + try cmds.append(b.fmt("-od={s}", .{b.pathJoin(&.{ b.install_prefix, outputDir })})); // run the command var ldc_exec = b.addSystemCommand(cmds.items); ldc_exec.setName(options.name); - if (options.artifact) |sokol| { - ldc_exec.addArtifactArg(sokol); + if (options.artifact) |lib_sokol| { + ldc_exec.addArtifactArg(lib_sokol); } const example_run = b.addSystemCommand(&.{b.pathJoin(&.{ b.install_path, outputDir, options.name })}); @@ -499,6 +530,17 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { tests.dependOn(&example_run.step); } + if (options.target.result.isWasm()) { + try wasmBuild(b, options.emsdk.?, .{ + .name = options.name, + .lib_main = b.fmt("{s}/examples.{s}.o", .{ b.pathJoin(&.{ b.install_prefix, outputDir }), options.name }), + .lib_sokol = options.artifact.?, + .target = options.target, + .optimize = options.optimize, + .step = &ldc_exec.step, + }); + } + return ldc_exec; } @@ -515,6 +557,7 @@ pub const DCompileStep = struct { zig_cc: bool = false, d_packages: ?[]const []const u8 = null, artifact: ?*Build.Step.Compile = null, + emsdk: ?*Build.Dependency = null, }; // -------------------------- Others Configuration -------------------------- @@ -575,3 +618,161 @@ fn buildShaders(b: *Build) void { shdc_step.dependOn(&cmd.step); } } + +// ------------------------ Wasm Configuration ------------------------ + +fn wasmBuild(b: *Build, emsdk: *Build.Dependency, options: struct { + name: []const u8, + lib_main: []const u8, + lib_sokol: *Build.Step.Compile, + target: Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + step: *Build.Step, +}) !void { + const link_step = try emLinkStep(b, .{ + .name = options.name, + .lib_main = options.lib_main, + .lib_sokol = options.lib_sokol, + .target = options.target, + .optimize = options.optimize, + .emsdk = emsdk, + .use_webgl2 = true, + .use_emmalloc = true, + .use_filesystem = false, + .shell_file_path = "src/sokol/web/shell.html", + // NOTE: This is required to make the Zig @returnAddress() builtin work, + // which is used heavily in the stdlib allocator code (not just + // the GeneralPurposeAllocator). + // The Emscripten runtime error message when the option is missing is: + // Cannot use convertFrameToPC (needed by __builtin_return_address) without -sUSE_OFFSET_CONVERTER + .extra_args = &.{"-sUSE_OFFSET_CONVERTER=1"}, + }); + link_step.step.dependOn(options.step); + const run = emRunStep(b, .{ .name = options.name, .emsdk = emsdk }); + run.step.dependOn(&link_step.step); + b.step(b.fmt("run-web-{s}", .{options.name}), b.fmt("Run {s} example", .{options.name})).dependOn(&run.step); +} + +// for wasm32-emscripten, need to run the Emscripten linker from the Emscripten SDK +// NOTE: ideally this would go into a separate emsdk-zig package +pub const EmLinkOptions = struct { + target: Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + name: []const u8, + lib_main: []const u8, + lib_sokol: *Build.Step.Compile, + emsdk: *Build.Dependency, + release_use_closure: bool = true, + release_use_lto: bool = true, + use_webgpu: bool = false, + use_webgl2: bool = false, + use_emmalloc: bool = false, + use_filesystem: bool = true, + shell_file_path: ?[]const u8 = null, + extra_args: []const []const u8 = &.{}, +}; + +pub fn emLinkStep(b: *Build, options: EmLinkOptions) !*Build.Step.Run { + const emcc_path = b.findProgram(&.{"emcc"}, &.{}) catch b.pathJoin(&.{ emSdkPath(b, options.emsdk), "upstream", "emscripten", "emcc" }); + + // create a separate output directory zig-out/web + try std.fs.cwd().makePath(b.fmt("{s}/web", .{b.install_path})); + + var emcc_cmd = std.ArrayList([]const u8).init(b.allocator); + defer emcc_cmd.deinit(); + + try emcc_cmd.append(emcc_path); + if (options.optimize == .Debug) { + try emcc_cmd.append("-Og"); + } else { + try emcc_cmd.append("-sASSERTIONS=0"); + if (options.optimize == .ReleaseSmall) { + try emcc_cmd.append("-Oz"); + } else { + try emcc_cmd.append("-O3"); + } + if (options.release_use_lto) { + try emcc_cmd.append("-flto"); + } + if (options.release_use_closure) { + try emcc_cmd.append("--closure"); + try emcc_cmd.append("1"); + } + } + if (options.use_webgpu) { + try emcc_cmd.append("-sUSE_WEBGPU=1"); + } + if (options.use_webgl2) { + try emcc_cmd.append("-sUSE_WEBGL2=1"); + } + if (!options.use_filesystem) { + try emcc_cmd.append("-sNO_FILESYSTEM=1"); + } + if (options.use_emmalloc) { + try emcc_cmd.append("-sMALLOC='emmalloc'"); + } + if (options.shell_file_path) |shell_file_path| { + try emcc_cmd.append(b.fmt("--shell-file={s}", .{shell_file_path})); + } + try emcc_cmd.append(b.fmt("-o{s}/web/{s}.html", .{ b.install_path, options.name })); + for (options.extra_args) |arg| { + try emcc_cmd.append(arg); + } + + const emcc = b.addSystemCommand(emcc_cmd.items); + emcc.setName("emcc"); // hide emcc path + + // add the main lib, and then scan for library dependencies and add those too + emcc.addArgs(&.{options.lib_main}); + emcc.addArtifactArg(options.lib_sokol); + + // get the emcc step to run on 'zig build' + b.getInstallStep().dependOn(&emcc.step); + return emcc; +} + +// build a run step which uses the emsdk emrun command to run a build target in the browser +// NOTE: ideally this would go into a separate emsdk-zig package +pub const EmRunOptions = struct { + name: []const u8, + emsdk: *Build.Dependency, +}; +pub fn emRunStep(b: *Build, options: EmRunOptions) *Build.Step.Run { + const emrun_path = b.findProgram(&.{"emrun"}, &.{}) catch b.pathJoin(&.{ emSdkPath(b, options.emsdk), "upstream", "emscripten", "emrun" }); + const emrun = b.addSystemCommand(&.{ emrun_path, b.fmt("{s}/web/{s}.html", .{ b.install_path, options.name }) }); + return emrun; +} + +// helper function to extract emsdk path from the emsdk package dependency +fn emSdkPath(b: *Build, emsdk: *Build.Dependency) []const u8 { + return emsdk.path("").getPath(b); +} + +// One-time setup of the Emscripten SDK (runs 'emsdk install + activate'). If the +// SDK had to be setup, a run step will be returned which should be added +// as dependency to the sokol library (since this needs the emsdk in place), +// if the emsdk was already setup, null will be returned. +// NOTE: ideally this would go into a separate emsdk-zig package +fn emSdkSetupStep(b: *Build, emsdk: *Build.Dependency) !?*Build.Step.Run { + const emsdk_path = emSdkPath(b, emsdk); + const dot_emsc_path = b.pathJoin(&.{ emsdk_path, ".emscripten" }); + const dot_emsc_exists = !std.meta.isError(std.fs.accessAbsolute(dot_emsc_path, .{})); + if (!dot_emsc_exists) { + var cmd = std.ArrayList([]const u8).init(b.allocator); + defer cmd.deinit(); + if (builtin.os.tag == .windows) + try cmd.append(b.pathJoin(&.{ emsdk_path, "emsdk.bat" })) + else { + try cmd.append("bash"); // or try chmod + try cmd.append(b.pathJoin(&.{ emsdk_path, "emsdk" })); + } + const emsdk_install = b.addSystemCommand(cmd.items); + emsdk_install.addArgs(&.{ "install", "latest" }); + const emsdk_activate = b.addSystemCommand(cmd.items); + emsdk_activate.addArgs(&.{ "activate", "latest" }); + emsdk_activate.step.dependOn(&emsdk_install.step); + return emsdk_activate; + } else { + return null; + } +} diff --git a/build.zig.zon b/build.zig.zon index cc6f063..e2561eb 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -9,13 +9,9 @@ "Readme.md", }, .dependencies = .{ - .ikod_containers = .{ - .url = "git+https://github.com/ikod/ikod-containers#6aabba3fdf3ebc4d2d6d55ac86727fa0c45f3ed5", - .hash = "1220aa60f94961a1755e42eb018493842dcd08c25ac1e619cbfcc7f981db3147983f", - }, - .automem = .{ - .url = "git+https://github.com/atilaneves/automem#7d7757913a4c89ce558ec2d7a062695beae388e7", - .hash = "1220a980693e9d341ca41176a9f82227468a42b35d49d09acdd8f7d56299d33cb6b5", + .emsdk = .{ + .url = "git+https://github.com/emscripten-core/emsdk#0bbae74935d57ff41739648c12cf90b56668398f", + .hash = "1220f1340cd871b444021c600661f921f96091ce0815fa43008528f4844cece7e245", }, }, } diff --git a/src/examples/clear.d b/src/examples/clear.d index 5c8d909..633fed5 100644 --- a/src/examples/clear.d +++ b/src/examples/clear.d @@ -28,15 +28,11 @@ void init() pass_action.colors[0].clear_value.b = 0; pass_action.colors[0].clear_value.a = 1; - version (D_BetterC) - { - import core.stdc.stdio: printf; - printf("Backend: %d\n", sg.queryBackend()); - } - else - { - import std.stdio; - writeln("Backend: ", sg.queryBackend()); + debug { + import std.stdio : writeln; + try { + writeln("Backend: ", sg.queryBackend()); + } catch (Exception) {} } } diff --git a/src/examples/debugtext-print.d b/src/examples/debugtext_print.d similarity index 100% rename from src/examples/debugtext-print.d rename to src/examples/debugtext_print.d diff --git a/src/examples/mrt.d b/src/examples/mrt.d index 34ec1d8..0d74933 100644 --- a/src/examples/mrt.d +++ b/src/examples/mrt.d @@ -14,8 +14,7 @@ module examples.mrt; import sg = sokol.gfx; import app = sokol.app; import log = sokol.log; -import handmade.math : Mat4, Vec3, Vec2; -import std.math : sin, cos; +import handmade.math : Mat4, Vec3, Vec2, sin, cos; import sgapp = sokol.glue; import shd = shaders.mrt; import sgutil = sokol.utils : asRange; diff --git a/src/examples/sgl-context.d b/src/examples/sgl_context.d similarity index 99% rename from src/examples/sgl-context.d rename to src/examples/sgl_context.d index b865dba..81cd193 100644 --- a/src/examples/sgl-context.d +++ b/src/examples/sgl_context.d @@ -11,7 +11,7 @@ import sgapp = sokol.glue; import sapp = sokol.app; import slog = sokol.log; import sgl = sokol.gl; -import std.math : sin, cos; +import handmade.math : sin, cos; extern (C): @safe: diff --git a/src/examples/user-data.d b/src/examples/user_data.d similarity index 95% rename from src/examples/user-data.d rename to src/examples/user_data.d index 2450820..263c42c 100644 --- a/src/examples/user-data.d +++ b/src/examples/user_data.d @@ -5,13 +5,11 @@ import sapp = sokol.app; import log = sokol.log; import sgapp = sokol.glue; -import ikod.containers.hashmap; - extern (C): struct ExampleUserData { ubyte data; - HashMap!(ubyte, int) map; + int[ubyte] map; // need druntime } void init() @safe diff --git a/src/handmade/math.d b/src/handmade/math.d index b7b6656..987ab26 100644 --- a/src/handmade/math.d +++ b/src/handmade/math.d @@ -9,8 +9,18 @@ module handmade.math; extern(C): -import std.math : PI; -import core.stdc.math : sqrt, sin, cos, tan; + +enum real PI = 0x1.921fb54442d18469898cc51701b84p+1L; +double zig_sqrt(double value) @nogc nothrow @trusted; +double zig_sqrtf(double value) @nogc nothrow @trusted; +double zig_cos(double value) @nogc nothrow @trusted; +double zig_sin(double value) @nogc nothrow @trusted; +double zig_tan(double value) @nogc nothrow @trusted; +alias sqrt = zig_sqrt; +alias sqrtf = zig_sqrtf; +alias cos = zig_cos; +alias sin = zig_sin; +alias tan = zig_tan; @safe: diff --git a/src/handmade/math.zig b/src/handmade/math.zig new file mode 100644 index 0000000..0e3b1c4 --- /dev/null +++ b/src/handmade/math.zig @@ -0,0 +1,18 @@ +//! replace "core.stdc.math" to "zig.std.math" +const std = @import("std"); + +export fn zig_sqrt(x: u64) callconv(.C) u64 { + return std.math.sqrt(x); +} +export fn zig_sqrtf(x: f64) callconv(.C) f64 { + return std.math.sqrt(x); +} +export fn zig_cos(x: f64) callconv(.C) f64 { + return std.math.cos(x); +} +export fn zig_sin(x: f64) callconv(.C) f64 { + return std.math.sin(x); +} +export fn zig_tan(x: f64) callconv(.C) f64 { + return std.math.tan(x); +} From c13375cc9d7947a74f524b00ff37ce929242157e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Catarino=20Fran=C3=A7a?= Date: Sat, 20 Jan 2024 15:09:04 -0300 Subject: [PATCH 2/7] fix zig comp_rt --- build.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.zig b/build.zig index c8fa4f9..3cae595 100644 --- a/build.zig +++ b/build.zig @@ -62,6 +62,11 @@ pub fn buildLibSokol(b: *Build, options: LibSokolOptions) !*CompileStep { lib.linkLibC(); lib.root_module.root_source_file = .{ .path = "src/handmade/math.zig" }; + switch (options.optimize) { + .Debug, .ReleaseSafe => lib.bundle_compiler_rt = true, + else => lib.root_module.strip = true, + } + if (options.target.result.isWasm()) { // make sure we're building for the wasm32-emscripten target, not wasm32-freestanding if (lib.rootModuleTarget().os.tag != .emscripten) { From 2320e279138505ed22dc8609b41f6810b297a961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Catarino=20Fran=C3=A7a?= Date: Sun, 21 Jan 2024 10:25:27 -0300 Subject: [PATCH 3/7] improvements --- build.zig | 164 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 97 insertions(+), 67 deletions(-) diff --git a/build.zig b/build.zig index 3cae595..69466c5 100644 --- a/build.zig +++ b/build.zig @@ -33,9 +33,7 @@ fn resolveSokolBackend(backend: SokolBackend, target: std.Target) SokolBackend { return .metal; } else if (target.os.tag == .windows) { return .d3d11; - } else if (target.isWasm()) { - return .gles3; - } else if (target.isAndroid()) { + } else if (target.isWasm() or target.isAndroid()) { return .gles3; } else { return .gl; @@ -362,18 +360,20 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { try cmds.append("-vcolumns"); // object file output (zig-cache/o/{hash_id}/*.o) + var objpath: []const u8 = undefined; // needed for wasm build if (b.cache_root.path) |path| { // immutable state hash - if (options.kind != .obj) - try cmds.append(b.fmt("-od={s}", .{b.pathJoin(&.{ path, "o", &b.cache.hash.peek() })})); - // mutable state hash + objpath = b.pathJoin(&.{ path, "o", &b.cache.hash.peek() }); + try cmds.append(b.fmt("-od={s}", .{objpath})); + // mutable state hash (ldc2 cache - llvm-ir2obj) try cmds.append(b.fmt("-cache={s}", .{b.pathJoin(&.{ path, "o", &b.cache.hash.final() })})); } // name object files uniquely (so the files don't collide) try cmds.append("-oq"); // remove object files after success build, and put them in a unique temp directory - // try cmds.append("-cleanup-obj"); + if (options.kind != .obj) + try cmds.append("-cleanup-obj"); // disable LLVM-IR verifier // https://llvm.org/docs/Passes.html#verify-module-verifier @@ -410,6 +410,9 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { // https://github.com/ldc-developers/ldc/issues/4501 try cmds.append("-L-w"); // hide linker warnings } + if (options.target.result.isWasm()) { + try cmds.append("-L-allow-undefined"); + } if (b.verbose) { try cmds.append("-vdmd"); @@ -492,16 +495,20 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { } // ldc2 doesn't support zig native (a.k.a: native-native or native) - if (options.target.result.isDarwin()) - try cmds.append(b.fmt("--mtriple={s}-apple-{s}", .{ if (options.target.result.cpu.arch.isAARCH64()) "arm64" else @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag) })) - else if (options.target.result.isWasm()) { - try cmds.append("-L-allow-undefined"); - try cmds.append(b.fmt("--mtriple={s}-unknown-unknown-wasm", .{@tagName(options.target.result.cpu.arch)})); - } else try cmds.append(b.fmt("--mtriple={s}-{s}-{s}", .{ @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag), @tagName(options.target.result.abi) })); + const mtriple = if (options.target.result.isDarwin()) + b.fmt("{s}-apple-{s}", .{ if (options.target.result.cpu.arch.isAARCH64()) "arm64" else @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag) }) + else if (options.target.result.isWasm()) + b.fmt("{s}-unknown-unknown-wasm", .{@tagName(options.target.result.cpu.arch)}) + else if (options.target.result.isMinGW()) + @panic("Mingw is not supported for ldc2 yet") + else + b.fmt("{s}-{s}-{s}", .{ @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag), @tagName(options.target.result.abi) }); + + try cmds.append(b.fmt("-mtriple={s}", .{mtriple})); // cpu model (e.g. "baseline") if (options.target.query.isNative()) - try cmds.append(b.fmt("--mcpu={s}", .{builtin.cpu.model.name})); + try cmds.append(b.fmt("-mcpu={s}", .{builtin.cpu.model.name})); const outputDir = switch (options.kind) { .lib => "lib", @@ -512,9 +519,7 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { // output file if (options.kind != .obj) - try cmds.append(b.fmt("-of={s}", .{b.pathJoin(&.{ b.install_prefix, outputDir, options.name })})) - else - try cmds.append(b.fmt("-od={s}", .{b.pathJoin(&.{ b.install_prefix, outputDir })})); + try cmds.append(b.fmt("-of={s}", .{b.pathJoin(&.{ b.install_prefix, outputDir, options.name })})); // run the command var ldc_exec = b.addSystemCommand(cmds.items); @@ -527,24 +532,42 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { const example_run = b.addSystemCommand(&.{b.pathJoin(&.{ b.install_path, outputDir, options.name })}); example_run.step.dependOn(&ldc_exec.step); - if (options.kind != .@"test") { - const run = b.step(b.fmt("run-{s}", .{options.name}), b.fmt("Run {s} example", .{options.name})); - run.dependOn(&example_run.step); - } else { - const tests = b.step("test", "Run all tests"); - tests.dependOn(&example_run.step); - } + const run = if (options.kind != .@"test") + b.step(b.fmt("run-{s}", .{options.name}), b.fmt("Run {s} example", .{options.name})) + else + b.step("test", "Run all tests"); if (options.target.result.isWasm()) { - try wasmBuild(b, options.emsdk.?, .{ + const artifact = addArtifact(options, b, .{ .name = options.name, - .lib_main = b.fmt("{s}/examples.{s}.o", .{ b.pathJoin(&.{ b.install_prefix, outputDir }), options.name }), - .lib_sokol = options.artifact.?, .target = options.target, .optimize = options.optimize, - .step = &ldc_exec.step, }); - } + artifact.addObjectFile(.{ .path = b.fmt("{s}/examples.{s}.o", .{ objpath, options.name }) }); + artifact.linkLibrary(options.artifact.?); + artifact.step.dependOn(&ldc_exec.step); + + const link_step = try emLinkStep(b, .{ + .lib_main = artifact, + .target = options.target, + .optimize = options.optimize, + .emsdk = options.emsdk.?, + .use_webgl2 = true, + .use_emmalloc = true, + .use_filesystem = false, + .shell_file_path = "src/sokol/web/shell.html", + // NOTE: This is required to make the Zig @returnAddress() builtin work, + // which is used heavily in the stdlib allocator code (not just + // the GeneralPurposeAllocator). + // The Emscripten runtime error message when the option is missing is: + // Cannot use convertFrameToPC (needed by __builtin_return_address) without -sUSE_OFFSET_CONVERTER + .extra_args = &.{"-sUSE_OFFSET_CONVERTER=1"}, + }); + link_step.step.dependOn(&ldc_exec.step); + const emrun = emRunStep(b, .{ .name = options.name, .emsdk = options.emsdk.? }); + emrun.step.dependOn(&link_step.step); + run.dependOn(&emrun.step); + } else run.dependOn(&example_run.step); return ldc_exec; } @@ -564,6 +587,30 @@ pub const DCompileStep = struct { artifact: ?*Build.Step.Compile = null, emsdk: ?*Build.Dependency = null, }; +pub fn addArtifact(self: DCompileStep, b: *Build, options: Build.ObjectOptions) *Build.Step.Compile { + return Build.Step.Compile.create(b, .{ + .name = options.name, + .root_module = .{ + .target = self.target, + .optimize = self.optimize, + .link_libc = options.link_libc, + .single_threaded = options.single_threaded, + .pic = options.pic, + .strip = options.strip, + .unwind_tables = options.unwind_tables, + .omit_frame_pointer = options.omit_frame_pointer, + .sanitize_thread = options.sanitize_thread, + .error_tracing = options.error_tracing, + .code_model = options.code_model, + }, + .linkage = self.linkage, + .kind = self.kind, + .max_rss = options.max_rss, + .use_llvm = options.use_llvm, + .use_lld = options.use_lld, + .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir, + }); +} // -------------------------- Others Configuration -------------------------- @@ -626,46 +673,14 @@ fn buildShaders(b: *Build) void { // ------------------------ Wasm Configuration ------------------------ -fn wasmBuild(b: *Build, emsdk: *Build.Dependency, options: struct { - name: []const u8, - lib_main: []const u8, - lib_sokol: *Build.Step.Compile, - target: Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, - step: *Build.Step, -}) !void { - const link_step = try emLinkStep(b, .{ - .name = options.name, - .lib_main = options.lib_main, - .lib_sokol = options.lib_sokol, - .target = options.target, - .optimize = options.optimize, - .emsdk = emsdk, - .use_webgl2 = true, - .use_emmalloc = true, - .use_filesystem = false, - .shell_file_path = "src/sokol/web/shell.html", - // NOTE: This is required to make the Zig @returnAddress() builtin work, - // which is used heavily in the stdlib allocator code (not just - // the GeneralPurposeAllocator). - // The Emscripten runtime error message when the option is missing is: - // Cannot use convertFrameToPC (needed by __builtin_return_address) without -sUSE_OFFSET_CONVERTER - .extra_args = &.{"-sUSE_OFFSET_CONVERTER=1"}, - }); - link_step.step.dependOn(options.step); - const run = emRunStep(b, .{ .name = options.name, .emsdk = emsdk }); - run.step.dependOn(&link_step.step); - b.step(b.fmt("run-web-{s}", .{options.name}), b.fmt("Run {s} example", .{options.name})).dependOn(&run.step); -} - // for wasm32-emscripten, need to run the Emscripten linker from the Emscripten SDK // NOTE: ideally this would go into a separate emsdk-zig package pub const EmLinkOptions = struct { target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, - name: []const u8, - lib_main: []const u8, - lib_sokol: *Build.Step.Compile, + // name: []const u8, + lib_main: *Build.Step.Compile, + // lib_sokol: *Build.Step.Compile, emsdk: *Build.Dependency, release_use_closure: bool = true, release_use_lto: bool = true, @@ -719,7 +734,7 @@ pub fn emLinkStep(b: *Build, options: EmLinkOptions) !*Build.Step.Run { if (options.shell_file_path) |shell_file_path| { try emcc_cmd.append(b.fmt("--shell-file={s}", .{shell_file_path})); } - try emcc_cmd.append(b.fmt("-o{s}/web/{s}.html", .{ b.install_path, options.name })); + try emcc_cmd.append(b.fmt("-o{s}/web/{s}.html", .{ b.install_path, options.lib_main.name })); for (options.extra_args) |arg| { try emcc_cmd.append(arg); } @@ -728,8 +743,23 @@ pub fn emLinkStep(b: *Build, options: EmLinkOptions) !*Build.Step.Run { emcc.setName("emcc"); // hide emcc path // add the main lib, and then scan for library dependencies and add those too - emcc.addArgs(&.{options.lib_main}); - emcc.addArtifactArg(options.lib_sokol); + emcc.addArtifactArg(options.lib_main); + var it = options.lib_main.root_module.iterateDependencies(options.lib_main, false); + while (it.next()) |item| { + for (item.module.link_objects.items) |link_object| { + switch (link_object) { + .other_step => |compile_step| { + switch (compile_step.kind) { + .lib => { + emcc.addArtifactArg(compile_step); + }, + else => {}, + } + }, + else => {}, + } + } + } // get the emcc step to run on 'zig build' b.getInstallStep().dependOn(&emcc.step); From 20cf00c3805357c81e93600cad907f136a499dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Catarino=20Fran=C3=A7a?= Date: Sun, 21 Jan 2024 10:39:15 -0300 Subject: [PATCH 4/7] fix windows target detection --- build.zig | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/build.zig b/build.zig index 69466c5..23a6e2b 100644 --- a/build.zig +++ b/build.zig @@ -189,14 +189,10 @@ pub fn build(b: *Build) !void { const opt_use_egl = b.option(bool, "egl", "Force EGL (default: false, Linux only)") orelse false; const sokol_backend: SokolBackend = if (opt_use_gl) .gl else if (opt_use_wgpu) .wgpu else .auto; - var target = b.standardTargetOptions(.{}); + // ldc2 w/ druntime + phobos2 works on MSVC + const target = b.standardTargetOptions(.{ .default_target = if (builtin.os.tag == .windows) try std.Target.Query.parse(.{ .arch_os_abi = "native-windows-msvc" }) else .{} }); const optimize = b.standardOptimizeOption(.{}); - // ldc2 w/ druntime + phobos2 works on MSVC - if (target.result.os.tag == .windows and target.query.isNative()) { - target.result.abi = .msvc; // for ldc2 - target.query.abi = .msvc; // for libsokol - } const emsdk = b.dependency("emsdk", .{}); const lib_sokol = try buildLibSokol(b, .{ .target = target, From f1ba5bbb9ccf70753e07c4576b7862e4f4a4f0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Catarino=20Fran=C3=A7a?= Date: Sun, 21 Jan 2024 11:04:43 -0300 Subject: [PATCH 5/7] remove target panic for windows --- build.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.zig b/build.zig index 23a6e2b..bfd47ec 100644 --- a/build.zig +++ b/build.zig @@ -495,8 +495,6 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*RunStep { b.fmt("{s}-apple-{s}", .{ if (options.target.result.cpu.arch.isAARCH64()) "arm64" else @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag) }) else if (options.target.result.isWasm()) b.fmt("{s}-unknown-unknown-wasm", .{@tagName(options.target.result.cpu.arch)}) - else if (options.target.result.isMinGW()) - @panic("Mingw is not supported for ldc2 yet") else b.fmt("{s}-{s}-{s}", .{ @tagName(options.target.result.cpu.arch), @tagName(options.target.result.os.tag), @tagName(options.target.result.abi) }); From bc4722bf888394c5aa8cba58a61b9a32a0896c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Catarino=20Fran=C3=A7a?= Date: Sun, 21 Jan 2024 12:37:20 -0300 Subject: [PATCH 6/7] Testing custom D runtime for Wasm targets. * This D custom runtime was started by Adam D. Ruppe, in arsd-webassembly. * Some of the features are not yet implemented. * This runtime may become version locked as the D runtime hooks keeps changing. * This runtime is not yet fully tested. --- README.md | 12 ++++++------ build.zig | 6 ++++-- build.zig.zon | 4 ++++ dub.json | 33 --------------------------------- src/examples/user_data.d | 19 +++++++++++++------ 5 files changed, 27 insertions(+), 47 deletions(-) delete mode 100644 dub.json diff --git a/README.md b/README.md index 7b87688..bb36fb5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Auto-generated [D](https://dlang.org) bindings for the [sokol headers](https://g **Required** - [zig](https://ziglang.org/download) v0.12.0 or master -- [ldc](https://ldc-developers.github.io) v1.35.0 or latest-CI (nightly) +- [ldc](https://ldc-developers.github.io) v1.36.0 or latest-CI (nightly) Supported platforms are: Windows, macOS, Linux (with X11) @@ -27,11 +27,11 @@ zig build -Doptimize=ReleaseFast -Dshared zig build run-blend -Doptimize=ReleaseFast zig build run-clear -Doptimize=ReleaseFast zig build run-cube -Doptimize=ReleaseFast -zig build run-debugtext-print -Doptimize=ReleaseFast +zig build run-debugtext_print -Doptimize=ReleaseFast zig build run-mrt -Doptimize=ReleaseFast zig build run-saudio -Doptimize=ReleaseFast -zig build run-sgl-context -Doptimize=ReleaseFast -zig build run-user-data -Doptimize=ReleaseFast +zig build run-sgl_context -Doptimize=ReleaseFast +zig build run-user_data -Doptimize=ReleaseFast zig build run-triangle -Doptimize=ReleaseFast zig build --help @@ -50,8 +50,8 @@ zig build --help # ReleaseFast # ReleaseSmall # -Dshared=[bool] Build sokol dynamic library (default: static) -# -DbetterC=[bool] Omit generating some runtime information and helper functions. (default: false) -# -DzigCC=[bool] Use zig cc as compiler and linker. (default: false) +# -DbetterC=[bool] Omit generating some runtime information and helper functions (default: false) +# -DzigCC=[bool] Use zig cc as compiler and linker (default: false) ``` ## Shaders diff --git a/build.zig b/build.zig index bfd47ec..c90c9e5 100644 --- a/build.zig +++ b/build.zig @@ -224,7 +224,7 @@ pub fn build(b: *Build) !void { "saudio", "sgl_context", "debugtext_print", - // "user_data", // Need GC for user data [associative array] + "user_data", // Need GC for user data [associative array] }; inline for (examples) |example| { @@ -237,8 +237,10 @@ pub fn build(b: *Build) !void { "-w", // warnings as error // more info: ldc2 -preview=help (list all specs) "-preview=all", - "-lowmem", }, + .d_packages = if (target.result.isWasm()) &[_][]const u8{ + b.dependency("wasmd", .{}).path("arsd-webassembly").getPath(b), + } else null, // fixme: https://github.com/kassane/sokol-d/issues/1 - betterC works on darwin .zig_cc = if (target.result.isDarwin() and !enable_betterC) false else enable_zigcc, .target = target, diff --git a/build.zig.zon b/build.zig.zon index e2561eb..6a0c2b5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -13,5 +13,9 @@ .url = "git+https://github.com/emscripten-core/emsdk#0bbae74935d57ff41739648c12cf90b56668398f", .hash = "1220f1340cd871b444021c600661f921f96091ce0815fa43008528f4844cece7e245", }, + .wasmd = .{ + .url = "git+https://github.com/kassane/webassembly#800870e68be1518eeffbb2c0814e23b9543a89cb", + .hash = "12203c2350903f04a07f746b448abb22824a452e8d81500b2fa11a0c3a5ea896b3f6", + }, }, } diff --git a/dub.json b/dub.json deleted file mode 100644 index 47cb4e8..0000000 --- a/dub.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "sokol-d", - "description": "Autogenerated D bindings for the sokol-headers", - "version": "0.1.0", - "license": "zlib", - "authors": ["Matheus Catarino França"], - "targetType": "sourceLibrary", - - "configurations": [ - { - "name": "debugtext_print", - "targetType": "executable", - "sourceFiles": ["src/examples/debugtext_print.d"], - "buildTypes": { - "debug": { - "buildOptions": ["debugMode", "debugInfo", "optimize"], - "dflags": ["-g"], - "lflags": ["-L$PWD/sokol/lib"], - "libs": ["sokol"], - "libs-posix": ["X11", "GL", "Xi", "Xcursor"] - }, - "release": { - "buildOptions": ["releaseMode", "optimize", "inline"], - "dflags": ["--release", "-boundscheck=on"], - "lflags": ["-L$PWD/sokol/lib"], - "cflags": ["-DSOKOL_GLCORE33"], - "libs": ["sokol","glibc"], - "libs-posix": ["X11", "GL", "Xi", "Xcursor"] - } - } - } - ] -} \ No newline at end of file diff --git a/src/examples/user_data.d b/src/examples/user_data.d index 263c42c..4671a23 100644 --- a/src/examples/user_data.d +++ b/src/examples/user_data.d @@ -26,13 +26,20 @@ void frame_userdata(scope void* userdata) @trusted auto state = cast(ExampleUserData*) userdata; state.data++; - if (state.data % 13 == 0) { - state.map[state.data] = state.data * 13 / 3; + + version(WebAssembly){ + // TODO support } - if (state.data % 12 == 0 && state.data % 15 == 0) { - state.map.clear(); + else + { + if (state.data % 13 == 0) { + state.map[state.data] = state.data * 13 / 3; + } + if (state.data % 12 == 0 && state.data % 15 == 0) { + state.map.clear(); + } } - debug { + debug { import std.stdio : writeln; try { writeln(*state); @@ -52,7 +59,7 @@ void cleanup() @safe void main() { - ExampleUserData userData; + auto userData = ExampleUserData(0, null); sapp.Desc runner = { window_title: "user-data.d", From e9c6a284f5094ddd346399b6ecff5bedee05bebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Catarino=20Fran=C3=A7a?= Date: Sun, 21 Jan 2024 12:43:59 -0300 Subject: [PATCH 7/7] CI: remove betterC flag on wasm build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a53c372..b118158 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,4 +28,4 @@ jobs: - name: Build Native run: zig build -DzigCC --summary all - name: Build Wasm - run: zig build -DbetterC -DzigCC --summary all -Dtarget=wasm32-emscripten + run: zig build -DzigCC --summary all -Dtarget=wasm32-emscripten