Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emscripten support: wasm & webgpu in browser #309

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

Deins
Copy link

@Deins Deins commented May 31, 2023

Feel free to reject this if you think this is out of scope or if some of the required workarounds are too ugly etc. But I thought to share this in case this interesting/useful and hear what you think.

This adds support and a sample for web compilation with emscripten toolchain. In general it just adds additional linking step with emsdk on top of regular wasm32-freestanding build which generates all the nececary js browser binding glue code. But also needs some platform specific modifications.
Main notes with required changes are in triangle_wgpu_emscripten sample readme.
Live demo (needs nightly browser with webgpu support): https://deins.lv/triangle_wgpu_emscripten/.

Its quite raw and its possible that some of the emscripten specific stuff could be better abstracted and included in zgpu for example. But currently I tried to avoid modifying api in any way requiring user/sample_code to do it instead.

Known issues

  • doesn't display any user friendly message if browser doesn't support webgpu
  • [fixed] not all keyboard input works correctly in imgui, such as arrow keys / backspace. Might be glfw version incompatibility, has to be debugged.
  • mapAsync is much slower and there is larger chance of running out of uniform buffers. Blocking is not viable and requires added canRender fn to be called before each frame to prevent crashes in such scenarios. Or in most extreme situations skipping frame. Also requires emscripten from upstream as I had to make pull request in regards to mapAsync alignment.

@michal-z
Copy link
Collaborator

michal-z commented Jun 1, 2023

Thanks for the PR and nice work!

I will be looking at this in coming days but please note that I have zero experience with the web stuff. That said, I would like to have this functionality in the project.

@hazeycode Can you please help with the review?

Also, how does this compare to #224?

@hazeycode
Copy link
Member

hazeycode commented Jun 1, 2023

Very nice work @Deins !

@michal-z This is focused on linking glfw-webgpu using emscipten where in #224 I tried linking sdl-webgl. Also the way the program entry point is resolved is different. I need to revisit some of the details. But I will try to review before the end of the week.

Interesting that Firefox is not playing along. I think that's what I was testing with last time I looked at this - I'll use Chromium as a default from now on, it seems to generally work more often than Firefox.

@Deins
Copy link
Author

Deins commented Jun 1, 2023

Sorry, missed #224. It is similar, but yes main difference is glfw vs sdl and webgl vs webgpu.

I don't see a reason why they both could not work. As in this project user can choose which libs to use and which backend. Main requirement would be to create a nice way from build system to be able to choose correct emscripten link flags, etc.

Another difference is that this uses wasm32-freestanding target instead of wasm32-emscripten. Reason is: I ran into issues with zig std that had almost no support for emscripten os tag even for most basic things. And it might be that it is the reason why the c entry point was needed for #224. Overall there is no need to use wasm32-emscripten its only needed for C/C++ libraries that use emscripten macro magic to inline JS in C source files. Otherwise freestanding for zig code is much nicer and I don't know any downsides other than not being able to use builtin os.tag for platform specific checks. So most ergonomic build flow from my experience is to:

  • use freestanding for zig code
  • use freestanding for C libraries that have no emscripten specific macros
  • use emscripten target tag for C libraries that use emscripten specific macros

In regards to firefox, I got webgpu running on different pc. But it still didn't run: webgpu function onSubmittedWorkDone, is not implemented in it. I am not sure how critical it is in zgpu, probably can be worked around. But it might get implemented in future so might not need to worry too much about it until any browser actually enables webgpu by default in stable releases and doesn't have it (as from my understanding it is in webgpu spec).

@hazeycode
Copy link
Member

I don't see a reason why they both could not work. As in this project user can choose which libs to use and which backend. Main requirement would be to create a nice way from build system to be able to choose correct emscripten link flags, etc.

Agreed

Overall there is no need to use wasm32-emscripten its only needed for C/C++ libraries that use emscripten macro magic to inline JS in C source files. Otherwise freestanding for zig code is much nicer and I don't know any downsides other than not being able to use builtin os.tag for platform specific checks. So most ergonomic build flow from my experience is to:
use freestanding for zig code
use freestanding for C libraries that have no emscripten specific macros
use emscripten target tag for C libraries that use emscripten specific macros

Also agreed. IIRC SDL makes use of these macros and there is some complication with how the main shim works. New SDL 3 simplifies this FWIW. Regardless, we can focus on GLFW+WebGPU here.

build.zig Outdated
@@ -22,8 +22,9 @@ pub fn build(b: *std.Build) void {
"Enable DirectX 12 GPU-Based Validation (GBV)",
) orelse false,
.zpix_enable = b.option(bool, "zpix-enable", "Enable PIX for Windows profiler") orelse false,
.emscripten = b.option(bool, "emscripten", "Build for linking with emscripten toolchain") orelse false,
Copy link
Member

@hazeycode hazeycode Jun 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding another option, I think we should drive emsc builds using Zig's built-in emscripten os target, the same way as when x-compiling other targets. it can be "overridden" to freestanding wherever appropriate.

build.zig Outdated
@@ -168,6 +171,7 @@ fn packagesWindows(b: *std.Build, options: Options) void {
fn samplesCrossPlatform(b: *std.Build, options: Options) void {
const minimal_gl = @import("samples/minimal_gl/build.zig");
const triangle_wgpu = @import("samples/triangle_wgpu/build.zig");
const triangle_wgpu_emscripten = @import("samples/triangle_wgpu_emscripten/build.zig");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good way to develop initially but I think we should make the existing cross-platform samples emscripten compatible instead of adding new sample variants. Any samples that have not yet been made compatible can be individually skipped.

build.zig Outdated
run_cmd.step.dependOn(install_step);
run_step.dependOn(&run_cmd.step);
} else {
const link_step = linkEmscripten(b, install_options.?, exe) catch unreachable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linking should not happen inside install

build.zig Outdated
Comment on lines 531 to 532
emlink.addArg("-sUSE_GLFW=3");
emlink.addArg("-sUSE_WEBGPU=1");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Library/sample specific flags could be added separately i.e. linkEmscripten only has the common stuff

Comment on lines 13 to 17
if (pkg.options.emscripten) return;

switch (host.os.tag) {
.windows => {},
.macos => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just leverage the Zigs builtin emscripten target here and treat it like the others.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just commited this. Not sure if its better. Its still tricky because zig modules reuse target from target its linked with (as I understand), now zglfw and zgpu etc. has to assume both .emscripten and .freestanding target means emscripten. It works as long as we don't need any other .freestanding target. But still we have to support wasm32-freestanding and emscripten os, or beter workaround has to be found to be able to use .emscripten target everywhere.

Copy link
Member

@hazeycode hazeycode Jun 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now zglfw and zgpu etc. has to assume both .emscripten and .freestanding target means emscripten

Why? I don't understand. Do you mean that if we compile our game as freestanding we are unable to link zglfw if has been built with emscripten os tag? I thought that it would link because they are both wasm32.

Copy link
Author

@Deins Deins Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically issue is that I had to use wasm32-freestanding instead of emscripten for main user/sample code, otherwise I think I got issues with entry point or or _start , don't rememember, but it didn't work for me.

In regards to zglfw & libs issue is not with c but zig modules that don't support specifying different target for them: build.createModule - can't specify target. addModule() just uses target from exe its added to.
So zig bindings code such as zglfw.zig in end has to assume that builtin.target freestanding is emscripten.
Hope I explained it better this time.

Copy link
Member

@hazeycode hazeycode Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, i follow now. Thanks for clarifying. I'm not sure whether it's better to deal with the entry point or not in the long run; but let's just go with this for now.

unreachable;
}
// more checks? iterate struct fields?
// emscripten in .Debug mode asserts at runtime if required field is missing etc. but something always slips through
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what is meant by something slips through

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

core issue is why I added these tests is that webgpu.zig basically rewrites c header webgpu.h. By default it matches dawn webgpu.h but emscripten ships with its own that can have version mismatch. If zig structs doesn't match with ones in actual header problems start. For emscripten that means it will try read some struct field but might get value from different field etc. Size checks help to quickly at comptime show these. But if u32 field was removed to struct and other added at end, it will slip through leading to unknown behavior and most likely crash at runtime or worse. But its not straight forward to test at comptime whole struct due to camel-case/snake-case naming changes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michal-z do you have any thoughts regarding how struct layouts can be validated?

@@ -345,6 +368,11 @@ pub const GraphicsContext = struct {
}

if (gctx.uniforms.stage.num >= uniforms_staging_pipeline_len) {
if (emscripten) {
// we can't block in requestAnimationFrame
slog.debug("uniformsNextStagingBuffer: Out of buffers! canRender() must be checked next frame, otherwise we will crash!", .{});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be slog.warning

Comment on lines +350 to +355
// Change current working directory to where the executable is located.
if (!emscripten) {
var buffer: [1024]u8 = undefined;
const path = std.fs.selfExeDirPath(buffer[0..]) catch ".";
std.os.chdir(path) catch {};
}
Copy link
Member

@hazeycode hazeycode Jun 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we need to come up with a better file abstraction We can just do the hacky thing for now (assuming a single emscripten sample).

if (emscripten) {
// by default emscripten initializes on window creation WebGL context
// this flag skips context creation. otherwise we later can't create webgpu surface
zglfw.WindowHint.set(.client_api, @enumToInt(zglfw.ClientApi.no_api));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this just be set anyway?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure, if someone wants to use webgl +glfw, different flags might be needed, but I haven't tested

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a wgpu sample so I think it's ok

Comment on lines 434 to 550
};
buf[slice.len] = 0;
switch (level) {
.err => emscripten_console_error(@ptrCast([*:0]u8, slice.ptr)),
.warn => emscripten_console_warn(@ptrCast([*:0]u8, slice.ptr)),
else => emscripten_console_log(@ptrCast([*:0]u8, slice.ptr)),
}
}

pub const EmBool = enum(u32) {
true = 1,
false = 0,
};
// https://emscripten.org/docs/api_reference/html5.h.html#c.emscripten_request_animation_frame_loop
pub const AnimationFrameCallback = *const fn (time: f64, user_data: ?*anyopaque) callconv(.C) EmBool;
extern fn emscripten_request_animation_frame(cb: AnimationFrameCallback, user_data: ?*anyopaque) c_long;
extern fn emscripten_cancel_animation_frame(requestAnimationFrameId: c_long) void;
extern fn emscripten_request_animation_frame_loop(cb: AnimationFrameCallback, user_data: ?*anyopaque) void;
extern fn emscripten_console_log(utf8_string: [*:0]const u8) void;
extern fn emscripten_console_warn(utf8_string: [*:0]const u8) void;
extern fn emscripten_console_error(utf8_string: [*:0]const u8) void;
extern fn emscripten_sleep(ms: u32) void;

/// EmmalocAllocator allocator
/// use with linker flag -sMALLOC=emmalloc
/// for details see docs: https://github.com/emscripten-core/emscripten/blob/main/system/lib/emmalloc.c
extern fn emmalloc_memalign(alignment: usize, size: usize) ?[*]u8;
extern fn emmalloc_realloc_try(ptr: ?[*]u8, size: usize) ?[*]u8;
extern fn emmalloc_free(ptr: ?[*]u8) void;
pub const EmmalocAllocator = struct {
const Self = @This();
dummy: u32 = undefined,

pub fn allocator(self: *Self) std.mem.Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = &alloc,
.resize = &resize,
.free = &free,
},
};
}

fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align_log2: u8,
return_address: usize,
) ?[*]u8 {
_ = ctx;
_ = return_address;
const ptr_align: u32 = @intCast(u32, 1) << @intCast(u5, ptr_align_log2);
if (!std.math.isPowerOfTwo(ptr_align)) unreachable;
const ptr = emmalloc_memalign(ptr_align, len) orelse return null;
return @ptrCast([*]u8, ptr);
}

fn resize(
ctx: *anyopaque,
buf: []u8,
buf_align_log2: u8,
new_len: usize,
return_address: usize,
) bool {
_ = ctx;
_ = return_address;
_ = buf_align_log2;
return emmalloc_realloc_try(buf.ptr, new_len) != null;
}

fn free(
ctx: *anyopaque,
buf: []u8,
buf_align_log2: u8,
return_address: usize,
) void {
_ = ctx;
_ = buf_align_log2;
_ = return_address;
return emmalloc_free(buf.ptr);
}
};

usingnamespace if (@import("builtin").cpu.arch == .wasm32) struct {
// GLFW - emscripten uses older version that doesn't have these functions - implement dummies
/// use glfwSetCallback instead
pub export fn glfwGetError() i32 {
return 0; // no error
}

pub export fn glfwGetGamepadState(_: i32, _: ?*anyopaque) i32 {
return 0; // false - failure
}

pub export fn wgpuDeviceTick() void {
std.log.warn("use of device.tick() should be avoided! It can break if used with callbacks such as requestAnimationFrame etc.", .{});
emscripten_sleep(1); // requires -sASYNCIFY
}
} else struct {};
Copy link
Member

@hazeycode hazeycode Jun 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this into zglfw? Or perhaps a new glue library? So that the users don't need to put this stuff in their code

@Deins
Copy link
Author

Deins commented Jun 11, 2023

Fixed some of the requested changes, still might need some cleanup.

  • removed/ switched to target wasm32-emscripten.
  • separated / moved emscripten build and utility stuff in separate lib zems. I tried to avoid adding it as dependency to zglfw, zgpu etc. So that mostly stays the same.
  • ported existing gui_test_wgpu to see how easy it is to port existing sample with refactored build stuff demo. Includes usage of emscripten asset file preloading that worked well. But file ops are mostly called from c, haven't tested how / if std.fs would work from zig code. Most likely would require some hacks to make std.fs to use posix filesystem calls in either .emscripten or .freestanding target (has to be investigated).
  • ported instanced_pills_wgpu for quick benchmarks. (mostly probably stresses browser wgpu overhead than cpu/wasm). But gives some feeling of overhead: 1M pills with 7 segments - browser 15FPS vs 20FPS native.

ztracy_pkg = ztracy.package(b, target, optimize, .{
.options = .{
.enable_ztracy = !target.isDarwin(), // TODO: ztracy fails to compile on macOS.
.enable_fibers = !target.isDarwin(),
.enable_ztracy = !target.isDarwin() and options.target.getOsTag() != .emscripten, // TODO: ztracy fails to compile on macOS.
Copy link
Member

@hazeycode hazeycode Jun 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michal-z is this TODO comment still valid? I think we fixed it here: #262?

Comment on lines 18 to 36
pub const AnimationFrameCallback = *const fn (time: f64, user_data: ?*anyopaque) callconv(.C) EmBool;

pub inline fn requestAnimationFrame(cb: AnimationFrameCallback, user_data: ?*anyopaque) c_long {
const cb_ptr = @ptrCast(*const fn (time: f64, user_data: ?*anyopaque) callconv(.C) c_int, cb); // return enum doesn't pass typesafety
return c.emscripten_request_animation_frame(cb_ptr, user_data);
}

pub inline fn requestAnimationFrameLoop(cb: AnimationFrameCallback, user_data: ?*anyopaque) void {
const cb_ptr = @ptrCast(*const fn (time: f64, user_data: ?*anyopaque) callconv(.C) c_int, cb); // return enum doesn't pass typesafety
return c.emscripten_request_animation_frame_loop(cb_ptr, user_data);
}

pub inline fn cancelAnimationFrame(request_animation_frame_id: c_long) void {
c.emscripten_cancel_animation_frame(request_animation_frame_id);
}

pub inline fn sleep(ms: u32) void {
c.emscripten_sleep(ms);
}
Copy link
Member

@hazeycode hazeycode Jun 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just declare extern C fns instead of wrapping them. Like you had originally?

Comment on lines 620 to 649
if (zems.is_emscripten) {
// requestAnimationFrame usage is needed to avoid flickering, otherwise could work in loop as well
zems.requestAnimationFrameLoop(&tickEmcripten, demo);
// TODO: ideally we would store all state in globals and return, but for now I don't want to refactor it, so lets spin forever
while (true) zems.sleep(1000);
} else while (!window.shouldClose() and window.getKey(.escape) != .press) {
try tick(demo);
}
}

pub fn tick(demo: *DemoState) !void {
zglfw.pollEvents();
try update(demo);
draw(demo);
}

usingnamespace if (zems.is_emscripten) struct {
pub export fn tickEmcripten(time: f64, user_data: ?*anyopaque) callconv(.C) zems.EmBool {
_ = time;
const demo = @ptrCast(*DemoState, @alignCast(@alignOf(DemoState), user_data.?));
if (demo.gctx.canRender()) tick(demo) catch |err| {
std.log.err("animation frame canceled! tick failed with: {}", .{err});
return .false;
} else {
std.log.warn("canRender(): Frame skipped!", .{});
}
return .true;
}
} else struct {};
extern fn tickEmcripten(time: f64, user_data: ?*anyopaque) callconv(.C) zems.EmBool;
Copy link
Member

@hazeycode hazeycode Jun 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should look into abstracting this away in zglfw? I'm not sure exactly what it would look like.... something like zglfw init sets up a js animation callback which sets some redraw bool that we can check in our application loop?

This would not be the ideal way of handling display sync but I think it's "good enough". It would of course mean than zems becomes an optional dependency of zglfw, which I think is fine.

For proper display sync, GLFW sucks, you need a better platform abstraction and I'm not sure it's the job of these samples to do that. It gets complicated. On browsers you basically have to use the animation callback but on other platforms you get to pick a scheme. Should we also, for example, set up a CVDisplayLink on macOS in these samples? Or do the crazy stuff on windows? Probably not.

Perhaps I was wrong to suggest merging emscripten support into existing samples.... Maybe there should just be one emscripten sample that does a bunch of stuff. Should that sample use GLFW or SDL or something else? or should there be a sample for each? I don't know. But I wouldn't want to maintain it all.

If it was up to me, I would just delete GLFW, LOL! That is to say, that my opinion doesn't count for much here.

So what do you guys think? @Deins @michal-z

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its not glfws fault. (at least for web)
Sdl2 had the same issue a while back, it required user to use emscripten specific callbacks for web (at least in older versions that I remember). Random example from web: https://github.com/erik-larsen/emscripten-sdl2-ogles2/blob/master/src/hello_triangle.cpp#L144

Core issue is that on web nothing can block main thread. (and WebWorker threads can't access any web api, so its not even worth mentioning). Emscripten provides asyncify magic that will try to detect functions that can block and do some crazy stuff to unwind stack etc. to allow blocking code to actually not block. But it has its limitations.
In general to smoothly render there is no alternative as far as I know than using requestAnimationFrame.

I think unless API is ground up built, not to ever block, it doesn't matter, it will require getting rid of main loop and using web specific callback interface. Nice example of how such api might look like is C library sokol which just tries to provide cross platform window, input etc.
But I think its out of scope of zig-gamedev which mostly just provides zig bindings and libraries which you match together as you wish.

So yeah, currently maybe is good idea just to have one example that show how to compile for web with emscripten.

Copy link
Member

@hazeycode hazeycode Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you're right, the problem is trying to fudge a desktop platform abstraction to work with browsers and they we're never designed to do so.

In my own projects I use a custom platform abstraction with completely separate desktop and web backends. It's totally different architecturally (maybe when it is more mature I will look at contributing the web portion as a lib). The use-case here I think is being to quickly be able to take something written on-top of glfw and get it going in a browser. Which wouldn't the path I would recommend for any serious project. But it's a usecase that can be supported nonetheless.

So yeah, currently maybe is good idea just to have one example that show how to compile for web with emscripten.

Yeah I think this is the way to go, for now it can just be glfw+webgpu. But in the future we could potentially reuse zems to show how to get sdl+opengl in the browser too. Sorry for the distraction by suggesting that we should try to get existing samples to work.

Let's see if @michal-z agrees

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. One sample compiled for the web is enough for this PR. It will provide nice example and give us experience/knowledge about what exactly is needed - we can think about better/different abstraction in the future.

#### Wasm for web
#### Install emscripten sdk
* [Follow these instructions](https://emscripten.org/docs/getting_started/downloads.html#installation-instructions-using-the-emsdk-recommended)
* emscripten needs this [patch](https://github.com/emscripten-core/emscripten/pull/19477/commits/f4bb4f578131578cd13abbbf78d7f4273788d76f) currently for this sample to run until it gets merged/released.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has now been merged 🍾


#### Compile
```bash
zig build triangle_wgpu_emscripten -Dtarget=wasm32-emscripten
Copy link
Member

@hazeycode hazeycode Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently failing for me:

zig build-lib zgui Debug wasm32-emscripten: error: error(compilation): clang failed with stderr: /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/backends/imgui_impl_wgpu.cpp:337:15: error: no member named 'source' in 'WGPUShaderModuleWGSLDescriptor'
/home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/backends/imgui_impl_wgpu.cpp:606:37: error: assigning to 'WGPUMipmapFilterMode' from incompatible type 'const WGPUFilterMode'


zig build-lib zgui Debug wasm32-emscripten: error: the following command failed with 1 compilation errors:
/home/hazeycode/zig/0.11.0-dev.3971+6bc9c4f71/files/zig build-lib -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/src/zgui.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/imgui.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/imgui_widgets.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/imgui_tables.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/imgui_draw.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/imgui_demo.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/implot_demo.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/implot.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/implot_items.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/backends/imgui_impl_glfw.cpp -cflags -fno-sanitize=undefined -- /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/backends/imgui_impl_wgpu.cpp --cache-dir /home/hazeycode/Desktop/zig-gamedev/zig-cache --global-cache-dir /home/hazeycode/.cache/zig --name zgui -static -fno-stack-check -fno-stack-protector -target wasm32-emscripten -mcpu generic -I /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs -I /home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui -isystem /home/hazeycode/Desktop/emsdk/upstream/emscripten/cache/sysroot/include -D __EMSCRIPTEN__ -D __EMSCRIPTEN_major__=3 -D __EMSCRIPTEN_minor__=1 --listen=- 
Build Summary: 3/7 steps succeeded; 1 failed (disable with --summary none)
triangle_wgpu_emscripten transitive failure
└─ run emcc transitive failure
   ├─ zig build-lib triangle_wgpu_emscripten Debug wasm32-freestanding transitive failure
   │  └─ zig build-lib zgui Debug wasm32-emscripten 1 errors
   ├─ zig build-lib zgui Debug wasm32-emscripten (reused)
   └─ zig build-lib triangle_wgpu_emscripten Debug wasm32-freestanding (+6 more reused dependencies)
/home/hazeycode/Desktop/zig-gamedev/libs/zgui/libs/imgui/backends/imgui_impl_wgpu.cpp:1:1: error: unable to build C object: clang exited with code 1

Note I am able to compile for native host target. This is only a problem when compiling for wasm32-emscripten

Zig 0.11.0-dev.3971+6bc9c4f71
emsdk 3.1.43

@hazeycode
Copy link
Member

Needs a rebase

@ckrowland
Copy link
Contributor

Hello everyone.

I'm not sure exactly how to do this, but I rebased deins:emscripten onto michal-z:main on my branch ckrowland:em. Hopefully it can save someone some time.
If my attempt is acceptable, Should I create a PR on deins:emscripten branch or create a new pull request here on michal-z:main?

I got all the emscripten examples running on the web with
chrome, zig 0.11.0-dev.4403+e84cda0eb, macos 12.6.7 and the current zig-gamedev main.

Firefox nightly is throwing a
queue.onSubmittedWorkDone is not a function error

What else needs to be done? I'm still getting up to speed on emscripten/wasm.

Screen Shot 2023-08-04 at 5 54 08 PM

@michal-z
Copy link
Collaborator

michal-z commented Aug 6, 2023

@Deins Could you please rebase this? We can take it from there if you don't have time to work on it anymore. I think we are very close to finally merge it. Thanks for your work!

@Deins
Copy link
Author

Deins commented Aug 6, 2023

sorry, have been too busy to work much on this.
Overall I think its in ok state. Since wgpu.zig was updated to newer version, as well wgpuCreateInstance implementation in emscripten, some of the ugly hacks are gone now.
Just have to be careful with emscripten versions as wgpu still slightly changes.
Tested now with zig 11 and emscripten sdk from latest master commit ef2a8e929d5337755e9b1d1e1d4ad859dc694eee seems to be working.

The last thing that still might be nice to get rid of is zgpu canRender check that I added to safeguard against running out of buffers.
Otherwise just testing it in more complex use cases. I think there might be more of minor usize/u64 mistakes in some wgpu functions but those should be easy to identify and fix.

@hazeycode
Copy link
Member

Some useful developments/info here: ziglang/zig#10836 (comment)

@hazeycode
Copy link
Member

hazeycode commented Jan 11, 2024

Zig now has emscripten support proper ziglang/zig#10836

Also this is pretty cool, emsdk can be consumed via package manager: floooh/sokol-zig#50 (comment)

@ckrowland
Copy link
Contributor

I was able to get my demo running live on chromium browsers with this. So much thanks Deins!

Only a few minor things I found:

  • Needed to pass -sUSE_OFFSET_CONVERTER to emsdk
  • Needed to copy shell.html file emscripten relies on into zems
    • Maybe we should include a basic template to start?

@hazeycode hazeycode marked this pull request as draft February 7, 2024 23:49
@hazeycode
Copy link
Member

Having another go at emscripten support here -> #480, based largely on this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants