Skip to content

Commit ad65e2d

Browse files
committed
Add canvas transfer API for next thread
Useful for thread creation APIs that do not let you pass a pthread_attr_t. Fixes #10307.
1 parent 7968c8c commit ad65e2d

File tree

6 files changed

+99
-1
lines changed

6 files changed

+99
-1
lines changed

site/source/docs/api_reference/html5.h.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2040,7 +2040,9 @@ Struct
20402040
20412041
.. c:member:: bool proxyContextToMainThread
20422042
2043-
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.
2043+
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``.
2044+
2045+
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.
20442046
20452047
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.
20462048

system/include/emscripten/threading.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ int emscripten_pthread_attr_gettransferredcanvases(const pthread_attr_t * _Nonnu
8787
// The special value "#canvas" denotes the element stored in Module.canvas.
8888
int emscripten_pthread_attr_settransferredcanvases(pthread_attr_t * _Nonnull a, const char * _Nonnull str);
8989

90+
// Specifies a comma-delimited list of canvas DOM element IDs to transfer to
91+
// the next thread created by the current thread when no explicit
92+
// pthread_attr_t::_a_transferredcanvases value is provided.
93+
//
94+
// This is intended for creation paths such as std::thread or boost::thread
95+
// where the caller cannot provide a pthread_attr_t before the underlying
96+
// pthread is launched.
97+
//
98+
// The next pthread_create() on the current thread consumes this value and
99+
// clears it automatically. Pass 0 or "" to clear any pending setting manually.
100+
// The pointer is weakly stored and must remain valid until the next
101+
// pthread_create() call returns.
102+
int emscripten_set_next_thread_transferredcanvases(const char *str);
103+
104+
// Gets the currently pending transferred canvas string for the next
105+
// pthread_create() on the current thread.
106+
int emscripten_get_next_thread_transferredcanvases(const char **_Nonnull str);
107+
90108
// Called when blocking on the main thread. This will error if main thread
91109
// blocking is not enabled, see ALLOW_BLOCKING_ON_MAIN_THREAD.
92110
void emscripten_check_blocking_allowed(void);

system/lib/pthread/library_pthread.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include "threading_internal.h"
3636
#include "emscripten_internal.h"
3737

38+
static _Thread_local const char* next_thread_transferredcanvases;
39+
3840
int emscripten_pthread_attr_gettransferredcanvases(const pthread_attr_t* a, const char** str) {
3941
*str = a->_a_transferredcanvases;
4042
return 0;
@@ -45,6 +47,16 @@ int emscripten_pthread_attr_settransferredcanvases(pthread_attr_t* a, const char
4547
return 0;
4648
}
4749

50+
int emscripten_set_next_thread_transferredcanvases(const char* str) {
51+
next_thread_transferredcanvases = (str && str[0]) ? str : NULL;
52+
return 0;
53+
}
54+
55+
int emscripten_get_next_thread_transferredcanvases(const char** str) {
56+
*str = next_thread_transferredcanvases;
57+
return 0;
58+
}
59+
4860
int sched_get_priority_max(int policy) {
4961
// Web workers do not actually support prioritizing threads,
5062
// but mimic values that Linux apparently reports, see

system/lib/pthread/pthread_create.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ int __pthread_create(pthread_t* restrict res,
142142
if (!attr._a_stacksize) {
143143
attr._a_stacksize = __default_stacksize;
144144
}
145+
if (!attr._a_transferredcanvases) {
146+
const char* next_thread_transferredcanvases = NULL;
147+
emscripten_get_next_thread_transferredcanvases(&next_thread_transferredcanvases);
148+
if (next_thread_transferredcanvases) {
149+
attr._a_transferredcanvases = next_thread_transferredcanvases;
150+
emscripten_set_next_thread_transferredcanvases(NULL);
151+
}
152+
}
145153

146154
// Allocate memory for new thread. The layout of the thread block is
147155
// as follows. From low to high address:
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <atomic>
2+
#include <thread>
3+
#include <stdio.h>
4+
5+
#include <GLES2/gl2.h>
6+
7+
#include <emscripten/emscripten.h>
8+
#include <emscripten/html5.h>
9+
#include <emscripten/threading.h>
10+
11+
static std::atomic<bool> g_done = false;
12+
static std::atomic<bool> g_ok = false;
13+
14+
static void thread_main() {
15+
EmscriptenWebGLContextAttributes attr;
16+
emscripten_webgl_init_context_attributes(&attr);
17+
attr.explicitSwapControl = true;
18+
19+
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
20+
if (ctx > 0 && emscripten_webgl_make_context_current(ctx) == EMSCRIPTEN_RESULT_SUCCESS) {
21+
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
22+
glClear(GL_COLOR_BUFFER_BIT);
23+
emscripten_webgl_commit_frame();
24+
emscripten_webgl_make_context_current(0);
25+
emscripten_webgl_destroy_context(ctx);
26+
g_ok = true;
27+
}
28+
29+
g_done = true;
30+
}
31+
32+
static void poll_done(void*) {
33+
if (!g_done) {
34+
emscripten_async_call(poll_done, nullptr, 20);
35+
}
36+
}
37+
38+
int main() {
39+
if (!emscripten_supports_offscreencanvas()) {
40+
printf("Current browser does not support OffscreenCanvas. Skipping this test.\n");
41+
return 0;
42+
}
43+
44+
// The new API is intended for std::thread users that cannot pass
45+
// pthread_attr_t into thread construction.
46+
emscripten_set_next_thread_transferredcanvases("#canvas");
47+
48+
std::thread worker(thread_main);
49+
worker.detach();
50+
51+
emscripten_async_call(poll_done, nullptr, 20);
52+
return 0;
53+
}

test/test_browser.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4212,6 +4212,11 @@ def test_webgl_offscreen_canvas_in_mainthread_after_pthread(self, args):
42124212
def test_webgl_offscreen_canvas_only_in_pthread(self):
42134213
self.btest_exit('gl_only_in_pthread.c', cflags=['-pthread', '-sPTHREAD_POOL_SIZE', '-sOFFSCREENCANVAS_SUPPORT', '-lGL', '-sOFFSCREEN_FRAMEBUFFER'])
42144214

4215+
@requires_offscreen_canvas
4216+
@requires_graphics_hardware
4217+
def test_std_thread_transferred_canvas(self):
4218+
self.btest_exit('std_thread_transferred_canvas.cpp', cflags=['-pthread', '-sPTHREAD_POOL_SIZE=2', '-sOFFSCREENCANVAS_SUPPORT', '-lGL'])
4219+
42154220
# Tests that rendering from client side memory without default-enabling extensions works.
42164221
@requires_graphics_hardware
42174222
def test_webgl_from_client_side_memory_without_default_enabled_extensions(self):

0 commit comments

Comments
 (0)