summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
diff options
context:
space:
mode:
authorMorten Sørvig <[email protected]>2025-06-03 09:27:58 +0200
committerMorten Sørvig <[email protected]>2025-06-12 15:37:19 +0200
commitc2ec20b226f0613db74a1e9fadffcf423ff1a180 (patch)
tree11e3556ecc0ea580ef55982a1f6ad1aaf45b7016 /src/plugins/platforms/wasm/qwasmopenglcontext.cpp
parent59c29436db8731fb7a8c9a932bf55d79b370668c (diff)
wasm: don't recreate WebGL context on surface change
The native WebGL context is tied to a single canvas, and can only be used with that canvas. (Qt creates one canvas per QPlatformWindow). This means that we need new native contexts in cases where a QWindow is repeatedly created and destroyed. It can be tempting to just create a new WebGL context "behind the scenes" on the makeCurrent() call in this case, but this does not work since GL resources created by user code with the original WebGL context in place are now invalid. Inform user code that this has happened by signaling a context loss. This is done by returning false from makeCurrent(), and then making sure isValid() returns false as well. The context becomes invalid whenever the owning platform window is destroyed; add a call from ~QWasmWindow() which handles that bookkeeping. Pick-to: 6.10 Task-number: QTBUG-120138 Change-Id: I929b9bb51153007c16630b1a991399f01ebffa62 Reviewed-by: Morten Johan Sørvig <[email protected]>
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmopenglcontext.cpp')
-rw-r--r--src/plugins/platforms/wasm/qwasmopenglcontext.cpp141
1 files changed, 75 insertions, 66 deletions
diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
index 8a4664ec8cd..2c6c1a81cb8 100644
--- a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
+++ b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
@@ -20,8 +20,10 @@ EMSCRIPTEN_BINDINGS(qwasmopenglcontext)
QT_BEGIN_NAMESPACE
+QHash<QPlatformSurface *, EMSCRIPTEN_WEBGL_CONTEXT_HANDLE> QWasmOpenGLContext::s_contexts;
+
QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)
- : m_actualFormat(context->format()), m_qGlContext(context)
+ : m_actualFormat(context->format())
{
m_actualFormat.setRenderableType(QSurfaceFormat::OpenGLES);
@@ -35,10 +37,7 @@ QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)
QWasmOpenGLContext::~QWasmOpenGLContext()
{
- // Destroy GL context. Work around bug in emscripten_webgl_destroy_context
- // which removes all event handlers on the canvas by temporarily replacing the function
- // that does the removal with a function that does nothing.
- destroyWebGLContext(m_ownedWebGLContext.handle);
+ destroyWebGLContext(m_contextOwningSurface);
}
bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
@@ -51,56 +50,23 @@ bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
(format.majorVersion() == 3 && format.minorVersion() == 0));
}
-EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
-QWasmOpenGLContext::obtainEmscriptenContext(QPlatformSurface *surface)
+void QWasmOpenGLContext::destroyWebGLContext(QPlatformSurface *surface)
{
- if (m_ownedWebGLContext.surface == surface)
- return m_ownedWebGLContext.handle;
-
- if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
- // Reuse the existing context for offscreen drawing, even if it happens to be a canvas
- // context. This is because it is impossible to re-home an existing context to the
- // new surface and works as an emulation measure.
- if (m_ownedWebGLContext.handle)
- return m_ownedWebGLContext.handle;
-
- // The non-shared offscreen context is heavily limited on WASM, but we provide it
- // anyway for potential pixel readbacks.
- m_ownedWebGLContext =
- QOpenGLContextData{ .surface = surface,
- .handle = createEmscriptenContext(
- static_cast<QWasmOffscreenSurface *>(surface)->id(),
- m_actualFormat) };
- } else {
- destroyWebGLContext(m_ownedWebGLContext.handle);
-
- // Create a full on-screen context for the window canvas.
- m_ownedWebGLContext = QOpenGLContextData{
- .surface = surface,
- .handle = createEmscriptenContext(static_cast<QWasmWindow *>(surface)->canvasSelector(),
- m_actualFormat)
- };
- }
-
- EmscriptenWebGLContextAttributes actualAttributes;
-
- EMSCRIPTEN_RESULT attributesResult = emscripten_webgl_get_context_attributes(m_ownedWebGLContext.handle, &actualAttributes);
- if (attributesResult == EMSCRIPTEN_RESULT_SUCCESS) {
- if (actualAttributes.majorVersion == 1) {
- m_actualFormat.setMajorVersion(2);
- } else if (actualAttributes.majorVersion == 2) {
- m_actualFormat.setMajorVersion(3);
- }
- m_actualFormat.setMinorVersion(0);
- }
-
- return m_ownedWebGLContext.handle;
+ if (surface == nullptr)
+ return;
+ int context = s_contexts.take(surface);
+ if (context)
+ destroyWebGLContext(context);
}
void QWasmOpenGLContext::destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle)
{
if (!contextHandle)
return;
+
+ // Destroy GL context. Work around bug in emscripten_webgl_destroy_context
+ // which removes all event handlers on the canvas by temporarily replacing the function
+ // that does the removal with a function that does nothing.
emscripten::val jsEvents = emscripten::val::module_property("JSEvents");
emscripten::val savedRemoveAllHandlersOnTargetFunction = jsEvents["removeAllHandlersOnTarget"];
jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing"));
@@ -108,8 +74,7 @@ void QWasmOpenGLContext::destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE con
jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
}
-EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
-QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
+EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
QSurfaceFormat format)
{
EmscriptenWebGLContextAttributes attributes;
@@ -137,6 +102,24 @@ QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
attributes.majorVersion = 1;
contextResult = emscripten_webgl_create_context(canvasSelector.c_str(), &attributes);
}
+
+ if (contextResult <= 0) {
+ qWarning() << "WebGL context creation failed";
+ return contextResult;
+ }
+
+ // Sync up actual format
+ EmscriptenWebGLContextAttributes actualAttributes;
+ EMSCRIPTEN_RESULT attributesResult = emscripten_webgl_get_context_attributes(contextResult, &actualAttributes);
+ if (attributesResult == EMSCRIPTEN_RESULT_SUCCESS) {
+ if (actualAttributes.majorVersion == 1) {
+ m_actualFormat.setMajorVersion(2);
+ } else if (actualAttributes.majorVersion == 2) {
+ m_actualFormat.setMajorVersion(3);
+ }
+ m_actualFormat.setMinorVersion(0);
+ }
+
return contextResult;
}
@@ -152,20 +135,31 @@ GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) c
bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface)
{
- static bool sentSharingWarning = false;
- if (!sentSharingWarning && isSharing()) {
- qWarning() << "The functionality for sharing OpenGL contexts is limited, see documentation";
- sentSharingWarning = true;
- }
-
- if (auto *shareContext = m_qGlContext->shareContext())
- return shareContext->makeCurrent(surface->surface());
+ // Record this makeCurrent() attempt, since isValid() must repeat the answer
+ // from this function in order to signal context loss to calling code.
+ m_madeCurrentSurface = surface;
- const auto context = obtainEmscriptenContext(surface);
- if (!context)
+ // The native webgl context is tied to a single surface, and can't
+ // be made current for a different surface.
+ if (m_contextOwningSurface && m_contextOwningSurface != surface)
return false;
- m_usedWebGLContextHandle = context;
+ // Return existing context or crate a new one.
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
+ if (m_contextOwningSurface && s_contexts.contains(m_contextOwningSurface)) {
+ context = s_contexts.value(surface);
+ } else {
+ m_contextOwningSurface = surface;
+ bool isOffscreen = surface->surface()->surfaceClass() == QSurface::Offscreen;
+ auto canvasId = isOffscreen ? static_cast<QWasmOffscreenSurface *>(surface)->id() :
+ static_cast<QWasmWindow *>(surface)->canvasSelector();
+
+ context = createEmscriptenContext(canvasId, m_actualFormat);
+ s_contexts.insert(surface, context);
+ }
+
+ if (context == 0)
+ return false;
return emscripten_webgl_make_context_current(context) == EMSCRIPTEN_RESULT_SUCCESS;
}
@@ -178,12 +172,15 @@ void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface)
void QWasmOpenGLContext::doneCurrent()
{
- // No doneCurrent on WebGl
+ m_madeCurrentSurface = nullptr;
}
bool QWasmOpenGLContext::isSharing() const
{
- return m_qGlContext->shareContext();
+ // Return false to signal that context sharing is not supported.
+ // This will in turn make QOpenGLContext::shareContext() return
+ // a null context to the application.
+ return false;
}
bool QWasmOpenGLContext::isValid() const
@@ -191,9 +188,21 @@ bool QWasmOpenGLContext::isValid() const
if (!isOpenGLVersionSupported(m_actualFormat))
return false;
- // Note: we get isValid() calls before we see the surface and can
- // create a native context, so no context is also a valid state.
- return !m_usedWebGLContextHandle || !emscripten_is_webgl_context_lost(m_usedWebGLContextHandle);
+ // We get isValid() calls before we see the surface and are able to
+ // create a native context, which means that "no context" is a valid state.
+ if (!m_madeCurrentSurface && !m_contextOwningSurface)
+ return true;
+
+ // Can't use this context for a different surface, since the native
+ // webgl context is tied to a single canvas.
+ if (m_madeCurrentSurface != m_contextOwningSurface)
+ return false;
+
+ // If the owning surfce/canvas has been deleted then this context is invalid
+ if (!s_contexts.contains(m_contextOwningSurface))
+ return false;
+
+ return !emscripten_is_webgl_context_lost(s_contexts.value(m_contextOwningSurface));
}
QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName)