diff --git a/site/source/docs/api_reference/html5.h.rst b/site/source/docs/api_reference/html5.h.rst
index caef9b5c95a75..45068332a74dd 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``, ``boost::thread`` or C11 threads), 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..67180a9520439 100644
--- a/system/include/emscripten/threading.h
+++ b/system/include/emscripten/threading.h
@@ -87,6 +87,20 @@ 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, boost::thread, or
+// C11 threads where the caller cannot provide a pthread_attr_t before the
+// underlying pthread is launched.
+//
+// The next pthread creation 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 * _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/pthread_create.c b/system/lib/pthread/pthread_create.c
index f467131bbac2a..81a56b0515a8d 100644
--- a/system/lib/pthread/pthread_create.c
+++ b/system/lib/pthread/pthread_create.c
@@ -28,6 +28,13 @@
#define dbg(fmt, ...)
#endif
+static _Thread_local const char* next_thread_transferredcanvases;
+
+int emscripten_set_next_thread_transferredcanvases(const char* str) {
+ next_thread_transferredcanvases = (str && str[0]) ? str : NULL;
+ return 0;
+}
+
// See musl's pthread_create.c
static void dummy_0() {}
@@ -142,6 +149,12 @@ int __pthread_create(pthread_t* restrict res,
if (!attr._a_stacksize) {
attr._a_stacksize = __default_stacksize;
}
+ if (!attr._a_transferredcanvases) {
+ if (next_thread_transferredcanvases) {
+ attr._a_transferredcanvases = next_thread_transferredcanvases;
+ next_thread_transferredcanvases = NULL;
+ }
+ }
// 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..8b80c85927cf3
--- /dev/null
+++ b/test/std_thread_transferred_canvas.cpp
@@ -0,0 +1,56 @@
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
+static std::atomic g_done = false;
+static std::atomic g_ok = false;
+
+static void thread_main() {
+ EmscriptenWebGLContextAttributes attr;
+ emscripten_webgl_init_context_attributes(&attr);
+ attr.explicitSwapControl = true;
+
+ 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 = true;
+ }
+
+ g_done = true;
+}
+
+static void poll_done(void*) {
+ if (!g_done) {
+ emscripten_async_call(poll_done, nullptr, 20);
+ return;
+ }
+ emscripten_force_exit(g_ok ? 0 : 1);
+}
+
+int main() {
+ if (!emscripten_supports_offscreencanvas()) {
+ printf("Current browser does not support OffscreenCanvas. Skipping this test.\n");
+ 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(thread_main);
+ worker.detach();
+
+ emscripten_async_call(poll_done, nullptr, 20);
+ emscripten_exit_with_live_runtime();
+ __builtin_trap();
+}
diff --git a/test/test_browser.py b/test/test_browser.py
index 9f601dd8a4355..91bed577f9bda 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_exit('std_thread_transferred_canvas.cpp', cflags=['-pthread', '-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):