diff --git a/site/source/docs/api_reference/html5.h.rst b/site/source/docs/api_reference/html5.h.rst index caef9b5c95a75..5d9f52bb34968 100644 --- a/site/source/docs/api_reference/html5.h.rst +++ b/site/source/docs/api_reference/html5.h.rst @@ -2040,7 +2040,9 @@ Struct .. c:member:: bool proxyContextToMainThread - This member specifies the threading model that will be used for the created WebGL context, when the WebGL context is created in a pthread. Three values are possible: ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_DISALLOW``, ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_FALLBACK`` or ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_ALWAYS``. If ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_DISALLOW`` is specified, the WebGLRenderingContext object will be created inside the pthread that is calling the ``emscripten_webgl_create_context()`` function as an OffscreenCanvas-based rendering context. This is only possible if 1) current browser supports OffscreenCanvas specification, 2) code was compiled with ``-sOFFSCREENCANVAS_SUPPORT`` linker flag enabled, 3) the Canvas object that the context is being created on was transferred over to the calling pthread with function ``emscripten_pthread_attr_settransferredcanvases()`` when the pthread was originally created, and 4) no OffscreenCanvas-based context already exists from the given Canvas at the same time. + This member specifies the threading model that will be used for the created WebGL context, when the WebGL context is created in a pthread. Three values are possible: ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_DISALLOW``, ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_FALLBACK`` or ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_ALWAYS``. + + If ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_DISALLOW`` is specified, the WebGLRenderingContext object will be created inside the pthread that is calling the ``emscripten_webgl_create_context()`` function as an OffscreenCanvas-based rendering context. This is only possible if 1) current browser supports OffscreenCanvas specification, 2) code was compiled with ``-sOFFSCREENCANVAS_SUPPORT`` linker flag enabled, 3) the Canvas object that the context is being created on was transferred over to the calling pthread with function ``emscripten_pthread_attr_settransferredcanvases()`` when the pthread was originally created, and 4) no OffscreenCanvas-based context already exists from the given Canvas at the same time. For thread creation APIs that do not let you pass a ``pthread_attr_t`` (for example ``std::thread`` or ``boost::thread``), use ``emscripten_set_next_thread_transferredcanvases()`` before creating the thread. The pending canvas selector string is consumed by the next ``pthread_create()`` on that thread and then cleared automatically. 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. diff --git a/system/include/emscripten/threading.h b/system/include/emscripten/threading.h index ffb63b7eb9f12..6f26992387fbd 100644 --- a/system/include/emscripten/threading.h +++ b/system/include/emscripten/threading.h @@ -87,6 +87,24 @@ int emscripten_pthread_attr_gettransferredcanvases(const pthread_attr_t * _Nonnu // The special value "#canvas" denotes the element stored in Module.canvas. int emscripten_pthread_attr_settransferredcanvases(pthread_attr_t * _Nonnull a, const char * _Nonnull str); +// Specifies a comma-delimited list of canvas DOM element IDs to transfer to +// the next thread created by the current thread when no explicit +// pthread_attr_t::_a_transferredcanvases value is provided. +// +// This is intended for creation paths such as std::thread or boost::thread +// where the caller cannot provide a pthread_attr_t before the underlying +// pthread is launched. +// +// The next pthread_create() on the current thread consumes this value and +// clears it automatically. Pass 0 or "" to clear any pending setting manually. +// The pointer is weakly stored and must remain valid until the next +// pthread_create() call returns. +int emscripten_set_next_thread_transferredcanvases(const char *str); + +// Gets the currently pending transferred canvas string for the next +// pthread_create() on the current thread. +int emscripten_get_next_thread_transferredcanvases(const char **_Nonnull str); + // Called when blocking on the main thread. This will error if main thread // blocking is not enabled, see ALLOW_BLOCKING_ON_MAIN_THREAD. void emscripten_check_blocking_allowed(void); diff --git a/system/lib/pthread/library_pthread.c b/system/lib/pthread/library_pthread.c index cd0c0ba1a71ad..2c7a1fc431566 100644 --- a/system/lib/pthread/library_pthread.c +++ b/system/lib/pthread/library_pthread.c @@ -35,6 +35,8 @@ #include "threading_internal.h" #include "emscripten_internal.h" +static _Thread_local const char* next_thread_transferredcanvases; + int emscripten_pthread_attr_gettransferredcanvases(const pthread_attr_t* a, const char** str) { *str = a->_a_transferredcanvases; return 0; @@ -45,6 +47,16 @@ int emscripten_pthread_attr_settransferredcanvases(pthread_attr_t* a, const char return 0; } +int emscripten_set_next_thread_transferredcanvases(const char* str) { + next_thread_transferredcanvases = (str && str[0]) ? str : 0; + return 0; +} + +int emscripten_get_next_thread_transferredcanvases(const char** str) { + *str = next_thread_transferredcanvases; + return 0; +} + int sched_get_priority_max(int policy) { // Web workers do not actually support prioritizing threads, // but mimic values that Linux apparently reports, see diff --git a/system/lib/pthread/pthread_create.c b/system/lib/pthread/pthread_create.c index f467131bbac2a..af4be56db5124 100644 --- a/system/lib/pthread/pthread_create.c +++ b/system/lib/pthread/pthread_create.c @@ -142,6 +142,14 @@ int __pthread_create(pthread_t* restrict res, if (!attr._a_stacksize) { attr._a_stacksize = __default_stacksize; } + if (!attr._a_transferredcanvases) { + const char* next_thread_transferredcanvases = 0; + emscripten_get_next_thread_transferredcanvases(&next_thread_transferredcanvases); + if (next_thread_transferredcanvases) { + attr._a_transferredcanvases = next_thread_transferredcanvases; + emscripten_set_next_thread_transferredcanvases(0); + } + } // Allocate memory for new thread. The layout of the thread block is // as follows. From low to high address: diff --git a/test/std_thread_transferred_canvas.cpp b/test/std_thread_transferred_canvas.cpp new file mode 100644 index 0000000000000..2f0c58fce3f4f --- /dev/null +++ b/test/std_thread_transferred_canvas.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include + +#include +#include +#include + +static std::atomic g_done = 0; +static std::atomic g_ok = 0; + +static void worker_main() { + EmscriptenWebGLContextAttributes attr; + emscripten_webgl_init_context_attributes(&attr); + attr.explicitSwapControl = 1; + + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr); + if (ctx > 0 && emscripten_webgl_make_context_current(ctx) == EMSCRIPTEN_RESULT_SUCCESS) { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + emscripten_webgl_commit_frame(); + emscripten_webgl_make_context_current(0); + emscripten_webgl_destroy_context(ctx); + g_ok = 1; + } + + g_done = 1; +} + +static void poll_done(void*) { + if (!g_done.load()) { + emscripten_async_call(poll_done, nullptr, 20); + return; + } + +#ifdef REPORT_RESULT + REPORT_RESULT(g_ok.load() ? 1 : 0); +#endif +} + +int main() { + if (!emscripten_supports_offscreencanvas()) { +#ifdef REPORT_RESULT + REPORT_RESULT(1); +#endif + return 0; + } + + // The new API is intended for std::thread users that cannot pass + // pthread_attr_t into thread construction. + emscripten_set_next_thread_transferredcanvases("#canvas"); + + std::thread worker(worker_main); + worker.detach(); + + emscripten_async_call(poll_done, nullptr, 20); + return 0; +} \ No newline at end of file diff --git a/test/test_browser.py b/test/test_browser.py index 9f601dd8a4355..e9450b4b3ec17 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -4212,6 +4212,11 @@ def test_webgl_offscreen_canvas_in_mainthread_after_pthread(self, args): def test_webgl_offscreen_canvas_only_in_pthread(self): self.btest_exit('gl_only_in_pthread.c', cflags=['-pthread', '-sPTHREAD_POOL_SIZE', '-sOFFSCREENCANVAS_SUPPORT', '-lGL', '-sOFFSCREEN_FRAMEBUFFER']) + @requires_offscreen_canvas + @requires_graphics_hardware + def test_std_thread_transferred_canvas(self): + self.btest('std_thread_transferred_canvas.cpp', expected='1', cflags=['-pthread', '-sPTHREAD_POOL_SIZE=2', '-sOFFSCREENCANVAS_SUPPORT', '-lGL']) + # Tests that rendering from client side memory without default-enabling extensions works. @requires_graphics_hardware def test_webgl_from_client_side_memory_without_default_enabled_extensions(self):