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);
}
},
.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;

View File

@ -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));

View File

@ -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));

View File

@ -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| {

View File

@ -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");

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>