-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
[browser][MT] WebGL with threads - requestAnimationFrame #101421
Comments
Few ideas for start
|
In general WebGL calls should happen inside of the callback from So the typical scenario would be:
So WebGL rendering requires synchronous JS->.NET->JS calls directly on the main browser thread. Note that it's not possible to prepare everything as a "call list", since some WebGL calls can read data back from GPU in synchronous manner and we can't interrupt the call sequence by exiting the event callback since the browser would assume that we are done rendering the frame. |
Note that we don't really need |
Actually, I haven't considered locks inside of SkiaSharp. It utilizes locks extensively for its object handle tracking. Those are rather short lived (and won't even be contended in most apps), so spinlocks should be fine. |
This is nasty nested synchronous callback. The syscalls in the
You can try it with dotnet.withConfig({
jsThreadBlockingMode: "ThrowWhenBlockingWait",
}); or with which will allow you to make (un-nested) synchronous JSExport calls. My lessons learned from working on this for last 12 months is that you never know.
when UI thread is blocked, event loop is blocked, UI doesn't render, If there is wait/promise chain this could be deadlock. All that said, this is not to say "I give up" 😉 Does it have to be managed code, whatever is talking to webGL from Blazor people have |
Also would this help ? Or this We have draft of |
Why does the runtime need to marshal the call to a web-worker in the first place? Can't it just execute the code directly on the UI thread? The requirement for WebGL rendering is to specifically run everything rendering-related on the main thread inside of the
We only need the UI thread to run our rendering code. It doesn't do any IO nor wait on any long-running semaphores (i. e. spin-waits are fine). The need to run all of the rendering code directly inside of
Unfortunately, not really, some of WebGL calls are not fire-and-forget but require actual handling of returned values or are supposed to block on reading back from GPU (i. e. if we want to copy some computed data from GPU to CPU mid-frame). Also, we aren't doing most of the calls by ourselves, the usual chain is SkiaSharp > Skia (native) -> emscripten opengl layer -> JS, so even if we don't need readback, we can't even replay the commands on the UI thread without executing managed code there It doesn't allow a huge part of OpenGL calls to work. I. e. it doesn't support reading back from GPU memory.
IIRC OffscreenCanvas doesn't support WebGL contexts on some browsers (last time we've checked it didn't work with Safari), only 2d drawing. If it does work now, the requirements would be mostly the same: we'll need to call into .NET from JS running in a web worker and have callbacks executed in the same worker and without costly cross-worker marshalling, since there are potentially thousands of calls per-frame.
The worker's event loop shouldn't be occupied with anything but requestAnimationFrame and gl context loss/restore callbacks, so it should be fine to have blocking waits there as long as those don't consume CPU time. |
Because we are not willing support those deadlock scenarios. During the development we made many prototypes trying to make it possible. It randomly deadlocks like 30% of our unit tests.
That's what the |
I don't have any experience with it, but they say "Safari 16.4 (Released 2023-03-27)" |
Seems to work with macOS 14 and iOS 17.4. https://bugs.webkit.org/show_bug.cgi?id=183720#c18 states that WebGL is supported for OffscreenCanvas on Sonoma+ (released Sep 2023) and iOS 17+ and isn't supported with Ventura regardless of Safari version. By the time .NET 9 is out, those should be widespread enough to justify them as a requirement for MT support, I guess. |
Is there some doc on using |
Ah, the source suggests to just use reflection, gotcha. |
Also you can use nightly build. There are still good changes since last preview. |
We'll need to transfer the OffscreenCanvas object created on the UI thread to the worker via postMessage. |
It seems that people are using |
Good enough for the demo, but not long term. This is quite low level (not an API from our perspective). We also have emscripten C message queue. And also have I prefer that the handshake is initiated from the worker side and that users don't touch emscripten You can That will allow you to I'm thinking on how to design it in a clean way. |
Yes, but we need to transfer a transferable JS object from UI thread to the worker thread and that requires it to be passed in the second array argument of |
Or dedicated channel which could be somehow located in the JS of the UI thread |
|
I had to use this because of that error: public static class JSWebWorkerClone
{
private static readonly MethodInfo _setExtLoop;
private static readonly MethodInfo _intallInterop;
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "System.Runtime.InteropServices.JavaScript.JSSynchronizationContext",
"System.Runtime.InteropServices.JavaScript")]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "System.Runtime.InteropServices.JavaScript.JSHostImplementation",
"System.Runtime.InteropServices.JavaScript")]
[UnconditionalSuppressMessage("Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "Private runtime API")]
static JSWebWorkerClone()
{
#pragma warning disable IL2075
var syncContext = typeof(System.Runtime.InteropServices.JavaScript.JSHost)
.Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext")!;
var hostImpl = typeof(System.Runtime.InteropServices.JavaScript.JSHost)
.Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSHostImplementation")!;
_setExtLoop = hostImpl.GetMethod("SetHasExternalEventLoop")!;
_intallInterop = syncContext.GetMethod("InstallWebWorkerInterop")!;
#pragma warning restore IL2075
}
public static Task RunAsync(Func<Task> run)
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var th = new Thread(_ =>
{
_intallInterop.Invoke(null, [false, CancellationToken.None]);
try
{
run().ContinueWith(t =>
{
if (t.IsFaulted)
tcs.TrySetException(t.Exception);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult();
});
}
catch(Exception e)
{
tcs.TrySetException(e);
}
})
{
Name = "Manual JS worker"
};
_setExtLoop.Invoke(null, [th]);
th.Start();
return tcs.Task;
}
} Also had to replace your A PoC seems to work. |
Missing APIs:
|
This demo is now MT on Net9 preview 3 https://pavelsavara.github.io/dotnet-wasm-raytracer/ |
note to self: sending message to managed code and spin-waiting inside I don't really understand if emscripten's GL emulator could run on a worker and if it would proxy syscalls back to UI thread. Perhaps some tweaks to emscripten's message queue would do it ? cc @jeromelaban |
The other idea I have is to move all the emscripten's OS/syscalls emulator to dedicated web worker. That is to move VFS (or use WASMFS) and move thread spawning code and other syscalls. This could possibly free-up the UI thread to be able to run managed code (again). It would block render and spin-wait on anything cross-thread or on GC. |
The problem is not emscipten, but the way WebGL works in general:
So any complex OpenGL code that wants to use a UI-thread-bound WebGL context has to run on the UI thread too. In Avalonia we currently spawn a dedicated web-worker using a hacked JSWebWorker version, transfer a canvas to said webworker and use a worker-bound WebGL context. That approach prevents us from using several advanced features like using BTW, Avalonia is currently broken with the latest .NET 9 RC because you've bumped emscripten and SkiaSharp wasn't yet updated to adjust for that change, so you can't play with it, sorry. Note that aside from OpenGL there is a similar problem with event handlers. Some of browser events kinda expect you to answer synchronously (e. g. if you want to run some logic in keyDown event and decide if it should be marked as handled or not), so enforced async isn't always feasible in general. Some of JS->managed callbacks just need to be synchronous and need to support JS->managed->JS roundtrip, one just needs to be really careful to avoid those whenever possible. |
I would expect this issue to affect Blazor WASM applications too.
New SkiaSharp nightly was released with new emscripten builds. We need to adjust NativeFileReference to look for a newer version though. |
How we could make webGL (and other native scenarios) possible with MT ?
From #85592 (comment)
The text was updated successfully, but these errors were encountered: