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):