From 099c3c0607d670a71e18cd5c694877a0d090afe2 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Wed, 23 Sep 2026 02:57:50 +0300 Subject: [PATCH] add wasm support --- build.zig | 31 +++++++++++++------- libs/stb/src/stb_rect_pack.zig | 2 +- src/platform/audio.zig | 1 + src/platform/graphics.zig | 7 +++-- src/platform/root.zig | 21 ++++++++++---- src/platform/shell.html | 53 ++++++++++++++++++++++++++++++++++ 6 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 src/platform/shell.html diff --git a/build.zig b/build.zig index 19331f1..5d66c64 100644 --- a/build.zig +++ b/build.zig @@ -26,14 +26,18 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(&run_exe_tests.step); } }, - .wasm => { - // TODO: - // b.getInstallStep().dependOn(build_step); - // - // 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; + .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); } } @@ -66,7 +70,10 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti const is_debug = optimize == .debug; const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse is_debug; - const assets_from_filesystem = b.option(bool, "assets-filesystem", "Allow reading assets from filesystem") 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) { @@ -123,6 +130,7 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti var slang = sokol.shdc.Slang{}; if (is_wasm) { slang.wgsl = true; + slang.glsl300es = true; } else { slang.glsl410 = true; slang.hlsl4 = true; @@ -316,8 +324,9 @@ fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step { .emsdk = dep_emsdk, .use_webgl2 = true, .use_emmalloc = true, - .use_filesystem = false, - .shell_file_path = b.path("src/engine/shell.html"), + .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; diff --git a/libs/stb/src/stb_rect_pack.zig b/libs/stb/src/stb_rect_pack.zig index 791a5d6..59bc756 100644 --- a/libs/stb/src/stb_rect_pack.zig +++ b/libs/stb/src/stb_rect_pack.zig @@ -4,7 +4,7 @@ const c = @import("c"); const STBRectPack = @This(); -ctx: c.stbrp_context, +ctx: c.stbrp_context = .{}, pub fn init(self: *STBRectPack, width: u32, height: u32, nodes: []Node) void { c.stbrp_init_target(&self.ctx, @intCast(width), @intCast(height), nodes.ptr, @intCast(nodes.len)); diff --git a/src/platform/audio.zig b/src/platform/audio.zig index 1257f9c..7e1bdce 100644 --- a/src/platform/audio.zig +++ b/src/platform/audio.zig @@ -164,6 +164,7 @@ pub fn init(io: Io, gpa: std.mem.Allocator, opts: InitOptions) !void { .buffer_frames = @intCast(opts.buffer_frames), .sample_rate = g_sample_rate }); + assert(saudio.isvalid()); if (opts.track_elapsed_time) { const measurements_per_second: usize = @intCast(@divFloor(std.time.ns_per_s, getMaxAudioThreadDuration().nanoseconds)); diff --git a/src/platform/graphics.zig b/src/platform/graphics.zig index ac80ec0..45ec422 100644 --- a/src/platform/graphics.zig +++ b/src/platform/graphics.zig @@ -130,7 +130,8 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void { }); const index_count = max_quads * 6; - var indices: [index_count]u16 = undefined; + var indices: []u16 = try gpa.alloc(u16, index_count); + defer gpa.free(indices); for (0..max_quads) |i| { indices[6*i + 0] = @intCast(4*i + 0); indices[6*i + 1] = @intCast(4*i + 1); @@ -142,7 +143,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void { } bindings.index_buffer = sg.makeBuffer(.{ - .data = sg.asRange(&indices), + .data = sg.asRange(indices), .usage = .{ .index_buffer = true, }, .label = "indices" }); @@ -997,7 +998,7 @@ fn repackSpritesheet(id: Spritesheet.Id) void { }; } - spritesheet.packer = undefined; + spritesheet.packer = .{}; const packer = &spritesheet.packer.?; for (&texture_sizes) |texture_size| { diff --git a/src/platform/root.zig b/src/platform/root.zig index e072183..fb21f8c 100644 --- a/src/platform/root.zig +++ b/src/platform/root.zig @@ -36,7 +36,7 @@ fn PlatformType(App: type) type { const Self = @This(); gpa: Allocator, - arena: *std.heap.ArenaAllocator, + arena: std.heap.ArenaAllocator, io: Io, cli_args: []const [:0]const u8, @@ -406,6 +406,13 @@ fn PlatformType(App: type) type { }); }, .KEY_DOWN => { + if (builtin.cpu.arch.isWasm()) { + // Pass through developer tools keybind + if (e.key_code == .I and e.modifiers & sapp.modifier_shift != 0 and e.modifiers & sapp.modifier_ctrl != 0) { + return false; + } + } + self.last_key_press_is_repeat = e.key_repeat; if (!e.key_repeat) { self.pushEvent(.{ @@ -478,6 +485,8 @@ fn PlatformType(App: type) type { ImGUI.deinit(); Gfx.deinit(); Audio.deinit(self.gpa); + + self.arena.deinit(); } }; } @@ -609,7 +618,6 @@ pub const Frame = struct { }; pub fn run(App: type, opts: RunOptions) void { - const arena = opts.init.arena.allocator(); const gpa = opts.init.gpa; const io = opts.init.io; @@ -623,12 +631,14 @@ pub fn run(App: type, opts: RunOptions) void { } const PlatformApp = PlatformType(App); - const plt = arena.create(PlatformApp) catch @panic("OOM"); + + var arena = std.heap.ArenaAllocator.init(gpa); + const plt = arena.allocator().create(PlatformApp) catch @panic("OOM"); plt.* = PlatformApp{ .gpa = gpa, - .arena = opts.init.arena, + .arena = undefined, .io = io, - .cli_args = opts.init.minimal.args.toSlice(arena) catch @panic("OOM"), + .cli_args = opts.init.minimal.args.toSlice(arena.allocator()) catch @panic("OOM"), .app = undefined, .input = undefined, .events_buffer = undefined, @@ -643,6 +653,7 @@ pub fn run(App: type, opts: RunOptions) void { .assets = .init(gpa, io, assets_dir), .last_mouse_position = null }; + plt.arena = arena; inline for (EmbeddedAssets.files) |file| { plt.assets.insertStaticFile(file.name, file.contents) catch @panic("OOM"); diff --git a/src/platform/shell.html b/src/platform/shell.html new file mode 100644 index 0000000..5b3aae6 --- /dev/null +++ b/src/platform/shell.html @@ -0,0 +1,53 @@ + + + + + + Sokol + + + + + + {{{ SCRIPT }}} + +