emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.84k stars 3.31k forks source link

Question: Using WebGPU without enabled ASYNCIFY flag #22207

Open MikhailGorobets opened 4 months ago

MikhailGorobets commented 4 months ago

Is there a way to interact with the current version of WebGPU without using the ASYNCIFY flag? Functions like wgpuInstanceRequestAdapter, wgpuAdapterRequestDevice, and wgpuBufferMapAsync require calling emscripten_sleep (which in turn requires using ASYNCIFY). Enabled ASYNCIFY significantly increases the program size (in my case from 10MB to 18MB). Besides this, the linking time becomes several minutes in Release mode, and when building in Debug mode, I get a runtime error https://github.com/emscripten-core/emscripten/issues/19346

sbc100 commented 4 months ago

As with any callback-based API you just need to yield to the event loop while you wait for the results.

There are several ways to do that. See https://emscripten.org/docs/porting/emscripten-runtime-environment.html#browser-main-loop. The simplest way is probably use emscripten_set_main_loop.

Having said that ASYNCIFY (along with its modern/experimental counterpart -sJSPI) is the only way to achieve this in blocking manner (i.e. without returning from your entry point).

sbc100 commented 4 months ago

ASYNCIFY is really just last resort for folks that cannot easily break up their main loop

MikhailGorobets commented 4 months ago

@sbc100 Yes, we can move the initialization to emscripten_set_main_loop, but this won’t solve the problem with asyncMap for readback and the recently added asynchronous operations for creating shaders and pipelines

sbc100 commented 4 months ago

Can you explain, perhaps with an example? I'm pretty sure WebGPU does not depend on use of ASYNCIFY. I imagine when you use an asynchronous operator to create your shader you simply get a callback when its done and continue from there?

MikhailGorobets commented 4 months ago

How can we rewrite this code without ASYNCIFY? https://github.com/DiligentGraphics/DiligentCore/blob/2e9a5fbcc2539275f73cb8834e8c90939ccd683e/Graphics/GraphicsEngineWebGPU/src/BufferWebGPUImpl.cpp#L236 Currently RenderDeviceWebGPUImpl::PollEvents function calls emscripten_sleep inside

sbc100 commented 4 months ago

Without ASYNCIFY (or JSPI) you would need to rewrite your BufferWebGPUImpl::Map function to itself be async (i.e. either take callback functions or return some kind of future/promise object).

MikhailGorobets commented 4 months ago

@sbc100 Well, we can’t redesign the API of the front-end renderer since it assumes the function is synchronous. Does using JSPI increase link time? (Enabling ASYNCIFY increases link time by an order of magnitude). From what I understand, JSPI is an experimental feature and requires special flags in the browser to activate. Is it worth investing time to implement this feature, considering it might take years before JSPI is available to end users?

sbc100 commented 4 months ago

@sbc100 Well, we can’t redesign the API of the front-end renderer since it assumes the function is synchronous.

Right, sadly this is exactly the kind of situation where something like ASYNCIFY is needed.

Does using JSPI increase link time? (Enabling ASYNCIFY increases link time by an order of magnitude). From what I understand, JSPI is an experimental feature and requires special flags in the browser to activate. Is it worth investing time to implement this feature, considering it might take years before JSPI is available to end users?

JSPI works a lot like ASYNCIFY but does not have the link time costs of the runtime overheads. You are correct that it could be a while (not years I hope) before its enabled by default in browsers. You could consider using JSPI for local/debug biulds that you want to be fast and ASYNCIFY for your release builds I suppose?

MikhailGorobets commented 4 months ago

Yes, we can probably consider this possibility. How are async-await operations handled in Rust? I looked at the source code of Bevy, and they simply use await on asynchronous WebGPU functions there

sbc100 commented 4 months ago

The Bevy (sorry I don't know what that is) is using await then presumably that means that are exposing an async API externally to their users (something its sounds like you don't want to do/change)? (not sure if async/await in rust works like this, e.g. how it does in JS).

MikhailGorobets commented 4 months ago

I don’t know Rust either, but judging by the cod, they pass the async function to block_on (which executes async functions). This way, the end user has a synchronous API

juj commented 2 months ago

https://github.com/juj/wasm_webgpu/blob/cb2f20154aba141c3e80d485a59d4eb7f41f8946/samples/buffer_map_sync/buffer_map_sync.c#L19-L31 shows a demo of doing synchronous buffer mapping from C code with Emscripten and WebGPU by using the upcoming JSPI API. (i.e. using -sJSPI link flag in Emscripten)

https://github.com/juj/wasm_webgpu/blob/cb2f20154aba141c3e80d485a59d4eb7f41f8946/samples/clear_screen/clear_screen_sync.c#L27-L58 takes this a bit further, and implements a custom for(;;) render loop using JSPI.