Multithreading 34/34: OffscreenCanvas + proxied Canvas support#6254
Conversation
f6707dd to
d85e344
Compare
kripken
left a comment
There was a problem hiding this comment.
Some initial comments and questions, but I have not read this all yet - I need feedback from you to fully understand the approach here.
Overall I think an overview of the approach should be documented somewhere - perhaps in settings.js alongside the relevant options, or in library_gl.js at the top?
src/library_gl.js
Outdated
|
|
||
| var LibraryGL = { | ||
| #if USE_PTHREADS | ||
| // If GLctxIsOnParentThread is true, then |
There was a problem hiding this comment.
how about GLctxIsOnParentThread => proxiedGLctx? That seems more self-explanatory I think.
There was a problem hiding this comment.
GLctxIsOnParentThread is more self-explanatory to suggest the type is a boolean, whereas proxiedGLctx could be mistaken with GLctx to be some kind of object, but just a proxied flavor, instead of the real thing. I'll change to GLctxIsProxied to hint at the boolean nature?
| var handle = _malloc(8); // Make space on the heap to store GL context attributes that need to be accessible as shared between threads. | ||
| {{{ makeSetValue('handle', 0, 'webGLContextAttributes["explicitSwapControl"]', 'i32')}}}; // explicitSwapControl | ||
| #if USE_PTHREADS | ||
| {{{ makeSetValue('handle', 4, '_pthread_self()', 'i32')}}}; // the thread pointer of the thread that owns the control of the context |
There was a problem hiding this comment.
what does "own control of the context" mean?
A place with a general overview of how the proxying works would be good I think.
There was a problem hiding this comment.
"own control of the context" means that the JavaScript WebGLRenderingContext resides in that thread. Adjusted the comment.
| $emscripten_get_canvas_element_size_js: function(target) { | ||
| var stackTop = Runtime.stackSave(); | ||
| var w = Runtime.stackAlloc(8); | ||
| var h = w + 4; |
There was a problem hiding this comment.
this will not work on the LLVM wasm backend, the stack grows down there.
h = (w + stackTop) / 2 should work ;)
There was a problem hiding this comment.
Surely stackAlloc() will still return a pointer to the beginning of the allocated memory address on the stack? So if one calls var p = stackAlloc(8), then HEAPU8[p] through HEAPU8[p+7] are the bytes that were allocated?
There was a problem hiding this comment.
Oh right, you add to the beginning of the allocation. That seems fine then.
src/library_html5.js
Outdated
| var w = Runtime.stackAlloc(8); | ||
| var h = w + 4; | ||
|
|
||
| if (typeof target === 'string') { |
There was a problem hiding this comment.
is this path necessary? if called from JS, the caller can just convert the string to an int..?
There was a problem hiding this comment.
We would not want to give the caller the burden to have to allocate and freestrings on the heap - it is smaller code wise to convert here inside (1 call site), rather than requiring all callers to convert (N call sites). User might also accidentally malloc, running into perf issues compared to stackAlloc.
src/library_html5.js
Outdated
| var newWidth = (cssWidth * dpiScale)|0; | ||
| var newHeight = (cssHeight * dpiScale)|0; | ||
|
|
||
| if (!target.controlTransferredOffscreen) { |
There was a problem hiding this comment.
this pattern (if not control transferred, apply, othrewise do the call) appears 3 times. can the logic be moved into the call, that is, emscripten_set_canvas_element_size_js would check if the target has controlTransferredOffscreen and handle that?
There was a problem hiding this comment.
Really good point. Changed this, and while doing that, I realize that I am not actually ready to expose this function as a "public" function to JavaScript side, because that is something that will need a closer thought as part of the big elephant issue we have with the JSEvents object being a non-DCEable bloat (#2999, #3298). I think we need to remove that whole object and make all functions individually DCE well (even when Closure is not used), and that will require changes to how users expose the functions to JS code, if they do not call them from C++.
So for now changed the canvas set and get functions to be underscored internal functions - the above refactor would publicize them in a proper manner, while doing the same for other functions.
system/lib/gl/gl.c
Outdated
| @@ -1689,7 +1689,6 @@ void* emscripten_GetProcAddress(const char *name_) { | |||
| if (!strcmp(name, "glNormalPointer")) return emscripten_glNormalPointer; | |||
There was a problem hiding this comment.
i have some worry about how this stuff will work with the GetProcAddress logic - it would be good to make sure all our GPA tests also run in this proxied mode.
There was a problem hiding this comment.
This PR only moves this file around for organizational purposes. The intent of obtaining function pointers to JS functions remains same - pointer to JS function is returned on the calling thread, and the called GL function then proxies if needed.
|
Noticed while looking at the code with @kainino0x - some of the new tests have just cpp files, but no python setup to run them, specifically
I verified it's just those two. |
|
Having issues with a basic SDL test program against juj:multithreading_october_2018_old_sockets, which contains this patch. It's also based on 1.38.11, so could be out-of-date.
// WORKS: emcc -s WASM=1 -s USE_SDL=2
// BROKEN: emcc -s WASM=1 -s USE_SDL=2 -s USE_PTHREADS=1 -s PROXY_TO_PTHREAD=1
#include <assert.h>
#include <stdio.h>
#include <SDL.h>
#include <SDL_opengl.h>
int main() {
printf("main\n");
assert(0 == SDL_Init(SDL_INIT_VIDEO|SDL_INIT_EVENTS));
SDL_Window *window = SDL_CreateWindow("title", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 512, 512, SDL_WINDOW_OPENGL);
assert(window);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
assert(gl_context);
assert(0 == SDL_GL_MakeCurrent(window, gl_context));
printf("main VENDOR: %s\n", glGetString(GL_VENDOR));
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
SDL_GL_SwapWindow(window);
printf("main exit\n");
} |
|
I never had a chance to look to bring SDL2 library over to multithreading world, and reading the description above, the bit |
9dda33c to
0bdba72
Compare
Added documentation to https://github.com/kripken/emscripten/pull/6254/files#diff-16eca2b70acff1fd624285ab9390a9c7 |
fd51a60 to
2908e26
Compare
Good catch, this was result from first having authored all the multithreading work in one big branch, and then later splitting it up in different PRs, so test code for those two were lost in the change. Added tests for these now. Rebased against latest incoming, this should now be good. |
2d1e9fe to
9663ba2
Compare
|
|
||
| If a WebGL rendering context is created as an OffscreenCanvas-based context, it will have the limitation that only the pthread that created the context can enable access to it (via ``emscripten_webgl_make_context_current()`` function). Other threads will not be able to activate rendering to the context, i.e. OffscreenCanvas-based contexts are essentially "pinned" to the pthread that created them. | ||
|
|
||
| If the current browser does not support OffscreenCanvas, you can specify the ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_ALWAYS`` WebGL context creation flag. If this flag is passed, and code was compiled with ``-s OFFSCREEN_FRAMEBUFFER=1`` enabled, the WebGL context will be created as a "proxied context". In this context mode, the WebGLRenderingContext object will actually be created on the main browser thread, and all WebGL API calls will be proxied as asynchronous messages from the pthread into the main thread. This will have a performance and latency impact in comparison to OffscreenCanvas contexts, however unlike OffscreenCanvas-based contexts, proxied contexts can be shared across any number of pthreads: you can use the ``emscripten_webgl_make_context_current()`` function in any pthread to activate and deactivate access to the WebGL context: for example, you could have one WebGL loading thread, and another WebGL rendering thread that coordinate shared access to the WebGL rendering context by cooperatively acquiring and releasing access to the WebGL rendering context via the ``emscripten_webgl_make_context_current()`` function. Proxied contexts do not require any special support from the browser, but any WebGL capable browser can create a proxied WebGL context. |
There was a problem hiding this comment.
The final "but" (in "but any WebGL capable browser..") seems like it should be "so"? Or did I misunderstand the intention?
There was a problem hiding this comment.
Oh yeah, that's much better. The second statement in the sentence is a continuation to the first, not a contrary.
src/library_gl.js
Outdated
| // If GLctxIsProxied is true, then | ||
| // a) the running thread is not the main browser thread, and | ||
| // b) the currently active GL context is running on the main browser thread, so all GL calls need to be proxied to it. | ||
| $GL__postset: 'var GLctx; var GLctxIsProxied = false; GL.init()', |
There was a problem hiding this comment.
why do we have GLctxIsProxied instead of GL.ctxIsProxied (i.e. as a property on GL)?
There was a problem hiding this comment.
That is good point - at some point I had part of the proxying extend to src/pthread-main.js, which is currently outside minification, so it did not see the GL variable to access, but looking at it now, I no longer have that, so this no longer needs to be global. Given that we have GL.currentContext, I also renamed this once more to GL.currentContextIsProxied to make it look similar.
|
Thanks, great updates! I had a few minor questions and comments now, but aside from those I think this is good to land. |
9663ba2 to
174cc88
Compare
This PR improves supports for OffscreenCanvas, allowing pthreads to resize canvases from threads, and also creating proxied canvases on the main thread when OffscreenCanvas is not present. Bump version to 1.38.19.
174cc88 to
ee5f632
Compare
|
Since this includes a version bump, I'm going to go ahead and merge it so we can update the other tags and get CircleCI and the integration waterfall green again. |
This PR improves supports for OffscreenCanvas, allowing pthreads to resize canvases from threads, and also creating proxied canvases on the main thread when OffscreenCanvas is not present. Proxying GL has the benefit of enabling support for eglMakeCurrent(), for true multithreaded WebGL access, i.e. threads can acquire and release access to a single GL context. Additionally this PR optimizes the proxied GL calls to run asynchronously whenever possible, to avoid synchronous blocking. For example UE4 runs in general without having to block to wait for sync GL calls at all. Proxying naturally has a performance impact compared to native OffscreenCanvas. That is why proxying is optional, and codebases explicitly need to enable it via OFFSCREEN_FRAMEBUFFER flag when desired. Codebases will want to choose whether to do true OffscreenCanvas, whether to proxy, or whether to do both, with proxying as a fallback to OffscreenCanvas, so this PR follows an "all options open" type of approach so that codebases can experiment, to find bugs and gauge performance. This is the last PR of the Multithreading series, and also the largest, given that the WebGL proxying part touches all GL functions. This should be looked at very last after all of the other Multithreading PRs have landed.
The logic here for proxying this call seems to exist in both the JS code and in the native code. It seems to have been that way since it was added in emscripten-core#6254.
The logic here for proxying this call seems to exist in both the JS code and in the native code. It seems to have been that way since it was added in emscripten-core#6254.
This PR improves supports for OffscreenCanvas, allowing pthreads to resize canvases from threads, and also creating proxied canvases on the main thread when OffscreenCanvas is not present.
Proxying GL has the benefit of enabling support for
eglMakeCurrent(), for true multithreaded WebGL access, i.e. threads can acquire and release access to a single GL context.Additionally this PR optimizes the proxied GL calls to run asynchronously whenever possible, to avoid synchronous blocking. For example UE4 runs in general without having to block to wait for sync GL calls at all.
Proxying naturally has a performance impact compared to native OffscreenCanvas. That is why proxying is optional, and codebases explicitly need to enable it via
OFFSCREEN_FRAMEBUFFERflag when desired.Codebases will want to choose whether to do true OffscreenCanvas, whether to proxy, or whether to do both, with proxying as a fallback to OffscreenCanvas, so this PR follows an "all options open" type of approach so that codebases can experiment, to find bugs and gauge performance.
This is the last PR of the Multithreading series, and also the largest, given that the WebGL proxying part touches all GL functions. This should be looked at very last after all of the other Multithreading PRs have landed.