From d098726c80e571310b38542e3965ea2f3c635e0d Mon Sep 17 00:00:00 2001 From: Gota7 Date: Sun, 12 Oct 2025 21:43:37 -0400 Subject: [PATCH 1/6] Emscripten Template WIP --- build.zig | 7 +++ template/build.zig | 105 +++++++++++++++++++++++++++++++++++++---- template/build.zig.zon | 8 ++++ 3 files changed, 111 insertions(+), 9 deletions(-) diff --git a/build.zig b/build.zig index 22f54a7..195c7bf 100644 --- a/build.zig +++ b/build.zig @@ -58,6 +58,11 @@ pub fn build(b: *std.Build) !void { "c_sdl_install_build_config_h", "Additionally install 'SDL_build_config.h' when installing SDL (default: false)", ) orelse false; + const sdl_system_include_path = b.option( + std.Build.LazyPath, + "sdl_system_include_path", + "System include path for SDL", + ); const sdl_dep = b.dependency("sdl", .{ .target = target, @@ -71,6 +76,8 @@ pub fn build(b: *std.Build) !void { }); const sdl_dep_lib = sdl_dep.artifact("SDL3"); + if (sdl_system_include_path) |val| + sdl_dep_lib.addSystemIncludePath(val); // SDL options. const extension_options = b.addOptions(); diff --git a/template/build.zig b/template/build.zig index 6fbb650..79bd95a 100644 --- a/template/build.zig +++ b/template/build.zig @@ -1,9 +1,7 @@ const std = @import("std"); +const zemscripten = @import("zemscripten"); -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - +fn buildBin(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !void { const exe_mod = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, @@ -32,13 +30,102 @@ pub fn build(b: *std.Build) void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); +} - const exe_unit_tests = b.addTest(.{ - .root_module = exe_mod, +fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !void { + const activateEmsdk = zemscripten.activateEmsdkStep(b); + b.default_step.dependOn(activateEmsdk); + + const wasm = b.addLibrary(.{ + .name = "zig_sdl3_cross_template", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main-web.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), + .linkage = .static, }); - const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + const zemscripten_dep = b.dependency("zemscripten", .{}); + wasm.root_module.addImport("zemscripten", zemscripten_dep.module("root")); + + const emsdk_dep = b.dependency("emsdk", .{}); + const emsdk_sysroot_include_path = emsdk_dep.path("upstream/emscripten/cache/sysroot/include"); + // sdl3.artifact("SDL3").addSystemIncludePath(emsdk_sysroot_include_path); - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_exe_unit_tests.step); + const sdl3 = b.dependency("sdl3", .{ + .target = target, + .optimize = optimize, + .callbacks = true, + .ext_image = true, + .sdl_system_include_path = emsdk_sysroot_include_path, + }); + wasm.root_module.addImport("sdl3", sdl3.module("sdl3")); + + const sysroot_include = b.pathJoin(&.{ b.sysroot.?, "include" }); + var dir = std.fs.openDirAbsolute(sysroot_include, .{ .access_sub_paths = true, .no_follow = true }) catch @panic("No emscripten cache. Generate it!"); + dir.close(); + wasm.addSystemIncludePath(.{ .cwd_relative = sysroot_include }); + + const emcc_flags = zemscripten.emccDefaultFlags(b.allocator, .{ + .optimize = optimize, + .fsanitize = true, + }); + + var emcc_settings = zemscripten.emccDefaultSettings(b.allocator, .{ + .optimize = optimize, + }); + try emcc_settings.put("ALLOW_MEMORY_GROWTH", "1"); + emcc_settings.put("USE_SDL", "3") catch unreachable; + + const emcc_step = zemscripten.emccStep( + b, + wasm, + .{ + .optimize = optimize, + .flags = emcc_flags, // Pass the modified flags + .settings = emcc_settings, + .use_preload_plugins = true, + .embed_paths = &.{}, + .preload_paths = &.{}, + .install_dir = .{ .custom = "web" }, + .shell_file_path = b.path("src/html/shell.html"), + }, + ); + + b.getInstallStep().dependOn(emcc_step); + + var run_emrun_step = b.step("emrun", "Run the WebAssembly app using emrun"); + + const emrun_cmd = b.addSystemCommand(&.{ + "emrun", + "--no_browser", + "--port", + "8080", + wasm.name, + }); + + emrun_cmd.step.dependOn(b.getInstallStep()); + + run_emrun_step.dependOn(&emrun_cmd.step); + + const run_step = b.step("run", "Run the app (via emrun)"); + run_step.dependOn(run_emrun_step); + + if (b.args) |args| { + emrun_cmd.addArgs(args); + } +} + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const os = target.result.os.tag; + if (os == .emscripten) { + try buildWeb(b, target, optimize); + } else { + try buildBin(b, target, optimize); + } } diff --git a/template/build.zig.zon b/template/build.zig.zon index 74ad5dd..49bd420 100644 --- a/template/build.zig.zon +++ b/template/build.zig.zon @@ -2,11 +2,19 @@ .name = .sdl3_template, .version = "0.0.0", .dependencies = .{ + .emsdk = .{ + .url = "https://github.com/emscripten-core/emsdk/archive/refs/tags/4.0.3.tar.gz", + .hash = "N-V-__8AAOG3BQCJ9cn-N2swm2o5cLmDhmdHmtwNngOChK78", + }, .sdl3 = .{ // .url = "git+https://github.com/Gota7/zig-sdl3#v0.1.0", // .hash = "sdl3-0.0.1-NmT1Q5LaIAC1lVdodEX_pnLOI44bfRvW1ZuOlqrMY17E" .path = "../", }, + .zemscripten = .{ + .url = "git+https://github.com/zig-gamedev/zemscripten#00da03b188220374a57cb34cda6230b8d53737ea", + .hash = "zemscripten-0.2.0-dev-sRlDqFJSAAB8hgnRt5DDMKP3zLlDtMnUDwYRJVCa5lGY", + }, }, .paths = .{ "build.zig", From 0f1725351d3bb5a35b30020627a29bc79b30e27d Mon Sep 17 00:00:00 2001 From: mozbeel Date: Thu, 16 Oct 2025 17:22:09 +0200 Subject: [PATCH 2/6] I did it (Added Emscripten port2 --- build.zig | 21 +++- build/image.zig | 4 + build/net.zig | 5 + build/ttf.zig | 5 + template/build.zig | 39 +++++-- template/src/html/shell.html | 34 +++++++ template/src/main-web.zig | 190 +++++++++++++++++++++++++++++++++++ 7 files changed, 287 insertions(+), 11 deletions(-) create mode 100644 template/src/html/shell.html create mode 100644 template/src/main-web.zig diff --git a/build.zig b/build.zig index 195c7bf..bfa2c85 100644 --- a/build.zig +++ b/build.zig @@ -63,6 +63,15 @@ pub fn build(b: *std.Build) !void { "sdl_system_include_path", "System include path for SDL", ); + const sdl_sysroot_path = b.option( + std.Build.LazyPath, + "sdl_sysroot_path", + "System include path for SDL", + ); + + if (sdl_sysroot_path) |val| { + b.sysroot = val.getPath(b); + } const sdl_dep = b.dependency("sdl", .{ .target = target, @@ -117,6 +126,8 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }); translate_c.addIncludePath(sdl_dep.path("include")); + if (sdl_system_include_path) |val| + translate_c.addSystemIncludePath(val); const c_module = translate_c.createModule(); @@ -129,6 +140,9 @@ pub fn build(b: *std.Build) !void { }, }); + if (sdl_system_include_path) |val| + sdl3.addSystemIncludePath(val); + const example_options = ExampleOptions{ .ext_image = ext_image, .ext_net = ext_net, @@ -137,23 +151,24 @@ pub fn build(b: *std.Build) !void { sdl3.addOptions("extension_options", extension_options); sdl3.linkLibrary(sdl_dep_lib); + if (ext_image) { image.setup(b, sdl3, translate_c, sdl_dep_lib, c_sdl_preferred_linkage, .{ .optimize = optimize, .target = target, - }); + }, sdl_system_include_path); } if (ext_net) { net.setup(b, sdl3, translate_c, sdl_dep_lib, c_sdl_preferred_linkage, .{ .optimize = optimize, .target = target, - }); + }, sdl_system_include_path); } if (ext_ttf) { ttf.setup(b, sdl3, translate_c, sdl_dep_lib, c_sdl_preferred_linkage, .{ .optimize = optimize, .target = target, - }); + }, sdl_system_include_path); } _ = setupDocs(b, sdl3); diff --git a/build/image.zig b/build/image.zig index 1262b77..9cf2330 100644 --- a/build/image.zig +++ b/build/image.zig @@ -8,6 +8,7 @@ pub fn setup( sdl_dep_lib: *std.Build.Step.Compile, linkage: std.builtin.LinkMode, cfg: struct { optimize: std.builtin.OptimizeMode, target: std.Build.ResolvedTarget }, + system_include_path: ?std.Build.LazyPath, ) void { const upstream = b.lazyDependency("sdl_image", .{}) orelse return; @@ -22,6 +23,9 @@ pub fn setup( .link_libc = true, }), }); + if (system_include_path) |val| { + lib.root_module.addSystemIncludePath(val); + } lib.root_module.linkLibrary(sdl_dep_lib); // Use stb_image for loading JPEG and PNG files. Native alternatives such as diff --git a/build/net.zig b/build/net.zig index 3b0105a..c03355d 100644 --- a/build/net.zig +++ b/build/net.zig @@ -7,6 +7,7 @@ pub fn setup( sdl_dep_lib: *std.Build.Step.Compile, linkage: std.builtin.LinkMode, cfg: struct { optimize: std.builtin.OptimizeMode, target: std.Build.ResolvedTarget }, + system_include_path: ?std.Build.LazyPath, ) void { const target = cfg.target; const optimize = cfg.optimize; @@ -30,6 +31,10 @@ pub fn setup( .linkage = linkage, }); + if (system_include_path) |val| { + lib.addSystemIncludePath(val); + } + var lib_c_flags: std.ArrayListUnmanaged([]const u8) = .empty; defer lib_c_flags.deinit(b.allocator); lib_c_flags.appendSlice(b.allocator, &.{"-std=c99"}) catch @panic("OOM"); diff --git a/build/ttf.zig b/build/ttf.zig index 24e5d0d..c0a050c 100644 --- a/build/ttf.zig +++ b/build/ttf.zig @@ -8,6 +8,7 @@ pub fn setup( sdl_dep_lib: *std.Build.Step.Compile, linkage: std.builtin.LinkMode, cfg: struct { optimize: std.builtin.OptimizeMode, target: std.Build.ResolvedTarget }, + system_include_path: ?std.Build.LazyPath, ) void { const target = cfg.target; const optimize: std.builtin.OptimizeMode = .ReleaseFast; // https://github.com/libsdl-org/SDL_ttf/issues/566 (ReleaseFast prevents UBSAN from running) @@ -26,6 +27,10 @@ pub fn setup( }), }); + if (system_include_path) |val| { + lib.addSystemIncludePath(val); + } + translate_c.addIncludePath(upstream.path("include")); lib.addIncludePath(upstream.path("include")); lib.addIncludePath(upstream.path("src")); diff --git a/template/build.zig b/template/build.zig index 79bd95a..fd252d2 100644 --- a/template/build.zig +++ b/template/build.zig @@ -37,7 +37,7 @@ fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.built b.default_step.dependOn(activateEmsdk); const wasm = b.addLibrary(.{ - .name = "zig_sdl3_cross_template", + .name = "template", .root_module = b.createModule(.{ .root_source_file = b.path("src/main-web.zig"), .target = target, @@ -51,6 +51,7 @@ fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.built wasm.root_module.addImport("zemscripten", zemscripten_dep.module("root")); const emsdk_dep = b.dependency("emsdk", .{}); + const emsdk_sysroot_path = emsdk_dep.path("upstream/emscripten/cache/sysroot"); const emsdk_sysroot_include_path = emsdk_dep.path("upstream/emscripten/cache/sysroot/include"); // sdl3.artifact("SDL3").addSystemIncludePath(emsdk_sysroot_include_path); @@ -60,13 +61,19 @@ fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.built .callbacks = true, .ext_image = true, .sdl_system_include_path = emsdk_sysroot_include_path, + .sdl_sysroot_path = emsdk_sysroot_path, }); + + wasm.root_module.addSystemIncludePath(emsdk_sysroot_include_path); + + const sdl_module = sdl3.module("sdl3"); + sdl_module.addSystemIncludePath(emsdk_sysroot_include_path); wasm.root_module.addImport("sdl3", sdl3.module("sdl3")); - const sysroot_include = b.pathJoin(&.{ b.sysroot.?, "include" }); - var dir = std.fs.openDirAbsolute(sysroot_include, .{ .access_sub_paths = true, .no_follow = true }) catch @panic("No emscripten cache. Generate it!"); - dir.close(); - wasm.addSystemIncludePath(.{ .cwd_relative = sysroot_include }); + // const sysroot_include = b.pathJoin(&.{ b.sysroot.?, "include" }); + // var dir = std.fs.openDirAbsolute(sysroot_include, .{ .access_sub_paths = true, .no_follow = true }) catch @panic("No emscripten cache. Generate it!"); + // dir.close(); + wasm.addSystemIncludePath(emsdk_sysroot_include_path); const emcc_flags = zemscripten.emccDefaultFlags(b.allocator, .{ .optimize = optimize, @@ -98,12 +105,28 @@ fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.built var run_emrun_step = b.step("emrun", "Run the WebAssembly app using emrun"); + const base_name = if (wasm.name_only_filename) |n| n else wasm.name; + + // Create filename with extension + const html_file = try std.fmt.allocPrint(b.allocator, "{s}.html", .{base_name}); + defer b.allocator.free(html_file); + + std.debug.print("HTML FILE: {s}\n", .{html_file}); + + // output set in emcc_step + const html_path = b.pathJoin(&.{ "zig-out", "web", html_file }); + + std.debug.print("HTML PATH: {s}\n", .{html_path}); + + // Absolute path to emrun + const emrun_path = emsdk_dep.path("upstream/emscripten/emrun"); + + // System command const emrun_cmd = b.addSystemCommand(&.{ - "emrun", - "--no_browser", + emrun_path.getPath(b), "--port", "8080", - wasm.name, + html_path, }); emrun_cmd.step.dependOn(b.getInstallStep()); diff --git a/template/src/html/shell.html b/template/src/html/shell.html new file mode 100644 index 0000000..ddfc3dc --- /dev/null +++ b/template/src/html/shell.html @@ -0,0 +1,34 @@ + + + + + + + + + + + {{{ SCRIPT }}} + + + + + diff --git a/template/src/main-web.zig b/template/src/main-web.zig new file mode 100644 index 0000000..d162f43 --- /dev/null +++ b/template/src/main-web.zig @@ -0,0 +1,190 @@ +const sdl3 = @import("sdl3"); +const std = @import("std"); + +// Use main callbacks. +comptime { + _ = sdl3.main_callbacks; +} + +// https://www.pexels.com/photo/green-trees-on-the-field-1630049/ +const my_image = @embedFile("data/trees.jpeg"); + +const fps = 60; +const window_width = 640; +const window_height = 480; + +// Disable main hack. +pub const _start = void; +pub const WinMainCRTStartup = void; + +/// Allocator we will use. +const allocator = std.heap.c_allocator; // std.heap.smp_allocator doesnt work in WASI / Emscripten + +/// For logging system messages. +const log_app = sdl3.log.Category.application; + +/// Sample structure to use to hold our app state. +const AppState = struct { + frame_capper: sdl3.extras.FramerateCapper(f32), + window: sdl3.video.Window, + renderer: sdl3.render.Renderer, + tree_tex: sdl3.render.Texture, +}; + +/// Do our initialization logic here. +/// +/// ## Function Parameters +/// * `app_state`: Where to store a pointer representing the state to use for the application. +/// * `args`: Slice of arguments provided to the application. +/// +/// ## Return Value +/// Returns if the app should continue running, or result in success or failure. +/// +/// ## Remarks +/// Note that for further callbacks (except for `quit()`), we assume that we did end up setting `app_state`. +/// If this function does not return `AppResult.run` or errors, then `quit()` will be invoked. +/// Do not worry about logging errors from SDL yourself and just use `try` and `catch` as you please. +/// If you set the error callback for every thread, then zig-sdl3 will be automatically logging errors. +pub fn init( + app_state: *?*AppState, + args: [][*:0]u8, +) !sdl3.AppResult { + _ = args; + + // Setup logging. + sdl3.errors.error_callback = &sdl3.extras.sdlErrZigLog; + sdl3.log.setAllPriorities(.info); + sdl3.log.setLogOutputFunction(void, &sdl3.extras.sdlLogZigLog, null); + + try log_app.logInfo("Starting application...", .{}); + + // Prepare app state. + const state = try allocator.create(AppState); + errdefer allocator.destroy(state); + + // Setup initial data. + const window_renderer = try sdl3.render.Renderer.initWithWindow( + "Hello SDL3", + window_width, + window_height, + .{}, + ); + errdefer window_renderer.renderer.deinit(); + errdefer window_renderer.window.deinit(); + var frame_capper = sdl3.extras.FramerateCapper(f32){ .mode = .{ .unlimited = {} } }; + window_renderer.renderer.setVSync(.{ .on_each_num_refresh = 1 }) catch { + + // We don't want to run at unlimited FPS, cap frame rate to the default FPS if vsync is not available so we don't burn CPU time. + frame_capper.mode = .{ .limited = fps }; + }; + const tree_tex = try sdl3.image.loadTextureIo( + window_renderer.renderer, + try sdl3.io_stream.Stream.initFromConstMem(my_image), + true, + ); + errdefer tree_tex.deinit(); + + // Prove error handling works. + const dummy: ?sdl3.video.Window = sdl3.video.Window.fromId(99999) catch null; + _ = dummy; + + // Set app state. + state.* = .{ + .frame_capper = frame_capper, + .window = window_renderer.window, + .renderer = window_renderer.renderer, + .tree_tex = tree_tex, + }; + app_state.* = state; + + try log_app.logInfo("Finished initializing", .{}); + return .run; +} + +/// Do our render and update logic here. +/// +/// ## Function Parameters +/// * `app_state`: Application state set from `init()`. +/// +/// ## Return Value +/// Returns if the app should continue running, or result in success or failure. +/// +/// ## Remarks +/// If this function does not return `AppResult.run` or errors, then `quit()` will be invoked. +/// We assume that `app_state` was set by `init()`. +/// If this function takes too long, your application will lag. +pub fn iterate( + app_state: *AppState, +) !sdl3.AppResult { + const dt = app_state.frame_capper.delay(); + _ = dt; // We don't need dt for this example, but might be useful to you. + + // Draw main scene. + try app_state.renderer.setDrawColor(.{ .r = 128, .g = 30, .b = 255, .a = 255 }); + try app_state.renderer.clear(); + const border = 10; + try app_state.renderer.renderTexture(app_state.tree_tex, null, .{ + .x = border, + .y = border, + .w = window_width - border * 2, + .h = window_height - border * 2, + }); + try app_state.renderer.setDrawColor(.{ .r = 0, .g = 0, .b = 0, .a = 255 }); + + // Draw debug FPS. + var fps_text_buf: [32]u8 = undefined; + const fps_text = std.fmt.bufPrintZ(&fps_text_buf, "FPS: {d}", .{app_state.frame_capper.getObservedFps()}) catch "[Err]"; + try app_state.renderer.renderDebugText(.{ .x = 0, .y = 0 }, fps_text); + + // Finish and return. + try app_state.renderer.present(); + return .run; +} + +/// Handle events here. +/// +/// ## Function Parameter +/// * `app_state`: Application state set from `init()`. +/// * `event`: Event that the application has just received. +/// +/// ## Return Value +/// Returns if the app should continue running, or result in success or failure. +/// +/// ## Remarks +/// If this function does not return `AppResult.run` or errors, then `quit()` will be invoked. +/// We assume that `app_state` was set by `init()`. +/// If this function takes too long, your application will lag. +pub fn event( + app_state: *AppState, + curr_event: sdl3.events.Event, +) !sdl3.AppResult { + _ = app_state; + switch (curr_event) { + .terminating => return .success, + .quit => return .success, + else => {}, + } + return .run; +} + +/// Quit logic here. +/// +/// ## Function Parameters +/// * `app_state`: Application state if it was set by `init()`, or `null` if `init()` did not set it (because of say an error). +/// * `result`: Result indicating the success of the application. Should never be `AppResult.run`. +/// +/// ## Remarks +/// Make sure you clean up any resources here. +/// Or don't the OS would take care of it anyway but any leak detection you use will yell at you :> +pub fn quit( + app_state: ?*AppState, + result: sdl3.AppResult, +) void { + _ = result; + if (app_state) |val| { + val.tree_tex.deinit(); + val.renderer.deinit(); + val.window.deinit(); + allocator.destroy(val); + } +} From dbdbbfaa26095e54b5d82ba5ce7dc7ad05a5dd5c Mon Sep 17 00:00:00 2001 From: mozbeel Date: Thu, 16 Oct 2025 17:33:37 +0200 Subject: [PATCH 3/6] Unified main-web.zig and main.zig --- template/build.zig | 2 +- template/src/main.zig | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/template/build.zig b/template/build.zig index fd252d2..9f26d95 100644 --- a/template/build.zig +++ b/template/build.zig @@ -39,7 +39,7 @@ fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.built const wasm = b.addLibrary(.{ .name = "template", .root_module = b.createModule(.{ - .root_source_file = b.path("src/main-web.zig"), + .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, .link_libc = true, diff --git a/template/src/main.zig b/template/src/main.zig index 6bbfd18..0ed4d96 100644 --- a/template/src/main.zig +++ b/template/src/main.zig @@ -1,5 +1,6 @@ const sdl3 = @import("sdl3"); const std = @import("std"); +const builtin = @import("builtin"); // Use main callbacks. comptime { @@ -18,7 +19,7 @@ pub const _start = void; pub const WinMainCRTStartup = void; /// Allocator we will use. -const allocator = std.heap.smp_allocator; +const allocator = if (builtin.os.tag != .emscripten) std.heap.smp_allocator else std.heap.c_allocator; /// For logging system messages. const log_app = sdl3.log.Category.application; From c212328fef31f5ae1017fa1bfc39292621d268eb Mon Sep 17 00:00:00 2001 From: Gota7 Date: Thu, 16 Oct 2025 19:14:26 -0400 Subject: [PATCH 4/6] Polish template --- template/build.zig | 13 +-- template/src/main-web.zig | 190 -------------------------------------- 2 files changed, 2 insertions(+), 201 deletions(-) delete mode 100644 template/src/main-web.zig diff --git a/template/build.zig b/template/build.zig index 9f26d95..a9f307e 100644 --- a/template/build.zig +++ b/template/build.zig @@ -53,7 +53,6 @@ fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.built const emsdk_dep = b.dependency("emsdk", .{}); const emsdk_sysroot_path = emsdk_dep.path("upstream/emscripten/cache/sysroot"); const emsdk_sysroot_include_path = emsdk_dep.path("upstream/emscripten/cache/sysroot/include"); - // sdl3.artifact("SDL3").addSystemIncludePath(emsdk_sysroot_include_path); const sdl3 = b.dependency("sdl3", .{ .target = target, @@ -69,10 +68,6 @@ fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.built const sdl_module = sdl3.module("sdl3"); sdl_module.addSystemIncludePath(emsdk_sysroot_include_path); wasm.root_module.addImport("sdl3", sdl3.module("sdl3")); - - // const sysroot_include = b.pathJoin(&.{ b.sysroot.?, "include" }); - // var dir = std.fs.openDirAbsolute(sysroot_include, .{ .access_sub_paths = true, .no_follow = true }) catch @panic("No emscripten cache. Generate it!"); - // dir.close(); wasm.addSystemIncludePath(emsdk_sysroot_include_path); const emcc_flags = zemscripten.emccDefaultFlags(b.allocator, .{ @@ -103,8 +98,6 @@ fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.built b.getInstallStep().dependOn(emcc_step); - var run_emrun_step = b.step("emrun", "Run the WebAssembly app using emrun"); - const base_name = if (wasm.name_only_filename) |n| n else wasm.name; // Create filename with extension @@ -125,16 +118,14 @@ fn buildWeb(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.built const emrun_cmd = b.addSystemCommand(&.{ emrun_path.getPath(b), "--port", - "8080", + b.fmt("{d}", .{b.option(u16, "port", "Port to run the webapp on (default: 8080)") orelse 8080}), html_path, }); emrun_cmd.step.dependOn(b.getInstallStep()); - run_emrun_step.dependOn(&emrun_cmd.step); - const run_step = b.step("run", "Run the app (via emrun)"); - run_step.dependOn(run_emrun_step); + run_step.dependOn(&emrun_cmd.step); if (b.args) |args| { emrun_cmd.addArgs(args); diff --git a/template/src/main-web.zig b/template/src/main-web.zig deleted file mode 100644 index d162f43..0000000 --- a/template/src/main-web.zig +++ /dev/null @@ -1,190 +0,0 @@ -const sdl3 = @import("sdl3"); -const std = @import("std"); - -// Use main callbacks. -comptime { - _ = sdl3.main_callbacks; -} - -// https://www.pexels.com/photo/green-trees-on-the-field-1630049/ -const my_image = @embedFile("data/trees.jpeg"); - -const fps = 60; -const window_width = 640; -const window_height = 480; - -// Disable main hack. -pub const _start = void; -pub const WinMainCRTStartup = void; - -/// Allocator we will use. -const allocator = std.heap.c_allocator; // std.heap.smp_allocator doesnt work in WASI / Emscripten - -/// For logging system messages. -const log_app = sdl3.log.Category.application; - -/// Sample structure to use to hold our app state. -const AppState = struct { - frame_capper: sdl3.extras.FramerateCapper(f32), - window: sdl3.video.Window, - renderer: sdl3.render.Renderer, - tree_tex: sdl3.render.Texture, -}; - -/// Do our initialization logic here. -/// -/// ## Function Parameters -/// * `app_state`: Where to store a pointer representing the state to use for the application. -/// * `args`: Slice of arguments provided to the application. -/// -/// ## Return Value -/// Returns if the app should continue running, or result in success or failure. -/// -/// ## Remarks -/// Note that for further callbacks (except for `quit()`), we assume that we did end up setting `app_state`. -/// If this function does not return `AppResult.run` or errors, then `quit()` will be invoked. -/// Do not worry about logging errors from SDL yourself and just use `try` and `catch` as you please. -/// If you set the error callback for every thread, then zig-sdl3 will be automatically logging errors. -pub fn init( - app_state: *?*AppState, - args: [][*:0]u8, -) !sdl3.AppResult { - _ = args; - - // Setup logging. - sdl3.errors.error_callback = &sdl3.extras.sdlErrZigLog; - sdl3.log.setAllPriorities(.info); - sdl3.log.setLogOutputFunction(void, &sdl3.extras.sdlLogZigLog, null); - - try log_app.logInfo("Starting application...", .{}); - - // Prepare app state. - const state = try allocator.create(AppState); - errdefer allocator.destroy(state); - - // Setup initial data. - const window_renderer = try sdl3.render.Renderer.initWithWindow( - "Hello SDL3", - window_width, - window_height, - .{}, - ); - errdefer window_renderer.renderer.deinit(); - errdefer window_renderer.window.deinit(); - var frame_capper = sdl3.extras.FramerateCapper(f32){ .mode = .{ .unlimited = {} } }; - window_renderer.renderer.setVSync(.{ .on_each_num_refresh = 1 }) catch { - - // We don't want to run at unlimited FPS, cap frame rate to the default FPS if vsync is not available so we don't burn CPU time. - frame_capper.mode = .{ .limited = fps }; - }; - const tree_tex = try sdl3.image.loadTextureIo( - window_renderer.renderer, - try sdl3.io_stream.Stream.initFromConstMem(my_image), - true, - ); - errdefer tree_tex.deinit(); - - // Prove error handling works. - const dummy: ?sdl3.video.Window = sdl3.video.Window.fromId(99999) catch null; - _ = dummy; - - // Set app state. - state.* = .{ - .frame_capper = frame_capper, - .window = window_renderer.window, - .renderer = window_renderer.renderer, - .tree_tex = tree_tex, - }; - app_state.* = state; - - try log_app.logInfo("Finished initializing", .{}); - return .run; -} - -/// Do our render and update logic here. -/// -/// ## Function Parameters -/// * `app_state`: Application state set from `init()`. -/// -/// ## Return Value -/// Returns if the app should continue running, or result in success or failure. -/// -/// ## Remarks -/// If this function does not return `AppResult.run` or errors, then `quit()` will be invoked. -/// We assume that `app_state` was set by `init()`. -/// If this function takes too long, your application will lag. -pub fn iterate( - app_state: *AppState, -) !sdl3.AppResult { - const dt = app_state.frame_capper.delay(); - _ = dt; // We don't need dt for this example, but might be useful to you. - - // Draw main scene. - try app_state.renderer.setDrawColor(.{ .r = 128, .g = 30, .b = 255, .a = 255 }); - try app_state.renderer.clear(); - const border = 10; - try app_state.renderer.renderTexture(app_state.tree_tex, null, .{ - .x = border, - .y = border, - .w = window_width - border * 2, - .h = window_height - border * 2, - }); - try app_state.renderer.setDrawColor(.{ .r = 0, .g = 0, .b = 0, .a = 255 }); - - // Draw debug FPS. - var fps_text_buf: [32]u8 = undefined; - const fps_text = std.fmt.bufPrintZ(&fps_text_buf, "FPS: {d}", .{app_state.frame_capper.getObservedFps()}) catch "[Err]"; - try app_state.renderer.renderDebugText(.{ .x = 0, .y = 0 }, fps_text); - - // Finish and return. - try app_state.renderer.present(); - return .run; -} - -/// Handle events here. -/// -/// ## Function Parameter -/// * `app_state`: Application state set from `init()`. -/// * `event`: Event that the application has just received. -/// -/// ## Return Value -/// Returns if the app should continue running, or result in success or failure. -/// -/// ## Remarks -/// If this function does not return `AppResult.run` or errors, then `quit()` will be invoked. -/// We assume that `app_state` was set by `init()`. -/// If this function takes too long, your application will lag. -pub fn event( - app_state: *AppState, - curr_event: sdl3.events.Event, -) !sdl3.AppResult { - _ = app_state; - switch (curr_event) { - .terminating => return .success, - .quit => return .success, - else => {}, - } - return .run; -} - -/// Quit logic here. -/// -/// ## Function Parameters -/// * `app_state`: Application state if it was set by `init()`, or `null` if `init()` did not set it (because of say an error). -/// * `result`: Result indicating the success of the application. Should never be `AppResult.run`. -/// -/// ## Remarks -/// Make sure you clean up any resources here. -/// Or don't the OS would take care of it anyway but any leak detection you use will yell at you :> -pub fn quit( - app_state: ?*AppState, - result: sdl3.AppResult, -) void { - _ = result; - if (app_state) |val| { - val.tree_tex.deinit(); - val.renderer.deinit(); - val.window.deinit(); - allocator.destroy(val); - } -} From 31c70adf91964af1508c9aef81ad03975bf82971 Mon Sep 17 00:00:00 2001 From: Gota7 Date: Thu, 16 Oct 2025 19:15:51 -0400 Subject: [PATCH 5/6] Add Template Web Build CI --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 15df466..14499f7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -48,6 +48,8 @@ jobs: run: cd template && zig build - name: Build Template Windows run: cd template && zig build -Dtarget=x86_64-windows + - name: Build Template Web + run: cd template && zig build -Dtarget=wasm32-emscripten test: name: Testing runs-on: ubuntu-latest From 813401858537073c5f1c4736fc0144eeeb7c1647 Mon Sep 17 00:00:00 2001 From: Gota7 Date: Thu, 16 Oct 2025 19:22:21 -0400 Subject: [PATCH 6/6] Temporarily Disable Web CI --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 14499f7..472e61c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -48,8 +48,8 @@ jobs: run: cd template && zig build - name: Build Template Windows run: cd template && zig build -Dtarget=x86_64-windows - - name: Build Template Web - run: cd template && zig build -Dtarget=wasm32-emscripten + # - name: Build Template Web + # run: cd template && zig build -Dtarget=wasm32-emscripten test: name: Testing runs-on: ubuntu-latest