Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion site/source/docs/api_reference/html5.h.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
18 changes: 18 additions & 0 deletions system/include/emscripten/threading.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions system/lib/pthread/library_pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions system/lib/pthread/pthread_create.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
59 changes: 59 additions & 0 deletions test/std_thread_transferred_canvas.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include <atomic>
#include <thread>

#include <GLES2/gl2.h>

#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/threading.h>

static std::atomic<int> g_done = 0;
static std::atomic<int> 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;
}
5 changes: 5 additions & 0 deletions test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down