add wasm support

This commit is contained in:
Rokas Puzonas 2026-09-23 02:57:50 +03:00
parent e4000646ad
commit 099c3c0607
6 changed files with 95 additions and 20 deletions

View File

@ -26,14 +26,18 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(&run_exe_tests.step); test_step.dependOn(&run_exe_tests.step);
} }
}, },
.wasm => { .wasm => |build_step| {
// TODO: b.getInstallStep().dependOn(build_step);
// b.getInstallStep().dependOn(build_step);
// const dep_sokol = b.dependency("sokol", .{});
// const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{}); const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{});
// const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk }); const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk });
// emrun_step.step.dependOn(build_step); emrun_step.step.dependOn(build_step);
// run_cmd_step = &emrun_step.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 is_debug = optimize == .debug;
const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse is_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; var has_tracy = false;
if (!is_wasm) { if (!is_wasm) {
@ -123,6 +130,7 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti
var slang = sokol.shdc.Slang{}; var slang = sokol.shdc.Slang{};
if (is_wasm) { if (is_wasm) {
slang.wgsl = true; slang.wgsl = true;
slang.glsl300es = true;
} else { } else {
slang.glsl410 = true; slang.glsl410 = true;
slang.hlsl4 = true; slang.hlsl4 = true;
@ -316,8 +324,9 @@ fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step {
.emsdk = dep_emsdk, .emsdk = dep_emsdk,
.use_webgl2 = true, .use_webgl2 = true,
.use_emmalloc = true, .use_emmalloc = true,
.use_filesystem = false, .use_filesystem = true,
.shell_file_path = b.path("src/engine/shell.html"), .shell_file_path = b.path("src/platform/shell.html"),
.extra_args = &.{ "-sALLOW_MEMORY_GROWTH", "-sENVIRONMENT=web" }
}) catch unreachable; }) catch unreachable;
return &link_step.step; return &link_step.step;

View File

@ -4,7 +4,7 @@ const c = @import("c");
const STBRectPack = @This(); const STBRectPack = @This();
ctx: c.stbrp_context, ctx: c.stbrp_context = .{},
pub fn init(self: *STBRectPack, width: u32, height: u32, nodes: []Node) void { 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)); c.stbrp_init_target(&self.ctx, @intCast(width), @intCast(height), nodes.ptr, @intCast(nodes.len));

View File

@ -164,6 +164,7 @@ pub fn init(io: Io, gpa: std.mem.Allocator, opts: InitOptions) !void {
.buffer_frames = @intCast(opts.buffer_frames), .buffer_frames = @intCast(opts.buffer_frames),
.sample_rate = g_sample_rate .sample_rate = g_sample_rate
}); });
assert(saudio.isvalid());
if (opts.track_elapsed_time) { if (opts.track_elapsed_time) {
const measurements_per_second: usize = @intCast(@divFloor(std.time.ns_per_s, getMaxAudioThreadDuration().nanoseconds)); const measurements_per_second: usize = @intCast(@divFloor(std.time.ns_per_s, getMaxAudioThreadDuration().nanoseconds));

View File

@ -130,7 +130,8 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
}); });
const index_count = max_quads * 6; 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| { for (0..max_quads) |i| {
indices[6*i + 0] = @intCast(4*i + 0); indices[6*i + 0] = @intCast(4*i + 0);
indices[6*i + 1] = @intCast(4*i + 1); 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(.{ bindings.index_buffer = sg.makeBuffer(.{
.data = sg.asRange(&indices), .data = sg.asRange(indices),
.usage = .{ .index_buffer = true, }, .usage = .{ .index_buffer = true, },
.label = "indices" .label = "indices"
}); });
@ -997,7 +998,7 @@ fn repackSpritesheet(id: Spritesheet.Id) void {
}; };
} }
spritesheet.packer = undefined; spritesheet.packer = .{};
const packer = &spritesheet.packer.?; const packer = &spritesheet.packer.?;
for (&texture_sizes) |texture_size| { for (&texture_sizes) |texture_size| {

View File

@ -36,7 +36,7 @@ fn PlatformType(App: type) type {
const Self = @This(); const Self = @This();
gpa: Allocator, gpa: Allocator,
arena: *std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
io: Io, io: Io,
cli_args: []const [:0]const u8, cli_args: []const [:0]const u8,
@ -406,6 +406,13 @@ fn PlatformType(App: type) type {
}); });
}, },
.KEY_DOWN => { .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; self.last_key_press_is_repeat = e.key_repeat;
if (!e.key_repeat) { if (!e.key_repeat) {
self.pushEvent(.{ self.pushEvent(.{
@ -478,6 +485,8 @@ fn PlatformType(App: type) type {
ImGUI.deinit(); ImGUI.deinit();
Gfx.deinit(); Gfx.deinit();
Audio.deinit(self.gpa); Audio.deinit(self.gpa);
self.arena.deinit();
} }
}; };
} }
@ -609,7 +618,6 @@ pub const Frame = struct {
}; };
pub fn run(App: type, opts: RunOptions) void { pub fn run(App: type, opts: RunOptions) void {
const arena = opts.init.arena.allocator();
const gpa = opts.init.gpa; const gpa = opts.init.gpa;
const io = opts.init.io; const io = opts.init.io;
@ -623,12 +631,14 @@ pub fn run(App: type, opts: RunOptions) void {
} }
const PlatformApp = PlatformType(App); 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{ plt.* = PlatformApp{
.gpa = gpa, .gpa = gpa,
.arena = opts.init.arena, .arena = undefined,
.io = io, .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, .app = undefined,
.input = undefined, .input = undefined,
.events_buffer = undefined, .events_buffer = undefined,
@ -643,6 +653,7 @@ pub fn run(App: type, opts: RunOptions) void {
.assets = .init(gpa, io, assets_dir), .assets = .init(gpa, io, assets_dir),
.last_mouse_position = null .last_mouse_position = null
}; };
plt.arena = arena;
inline for (EmbeddedAssets.files) |file| { inline for (EmbeddedAssets.files) |file| {
plt.assets.insertStaticFile(file.name, file.contents) catch @panic("OOM"); plt.assets.insertStaticFile(file.name, file.contents) catch @panic("OOM");

53
src/platform/shell.html Normal file
View File

@ -0,0 +1,53 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
<title>Sokol</title>
<style>
body { margin: 0; background-color: black }
.game {
position: absolute;
top: 0px;
left: 0px;
margin: 0px;
border: 0;
width: 100%;
height: 100%;
overflow: hidden;
display: block;
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
</style>
</head>
<body>
<canvas class="game" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
<script type='text/javascript'>
var Module = {
preRun: [],
print: (function() {
return function(text) {
text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
};
})(),
printErr: function(text) {
text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
};
window.onerror = function() {
console.log("onerror: " + event.message);
};
window.addEventListener("click", () => window.focus())
</script>
{{{ SCRIPT }}}
</body>
</html>