diff options
author | Morten Sørvig <[email protected]> | 2025-06-03 09:27:58 +0200 |
---|---|---|
committer | Morten Sørvig <[email protected]> | 2025-06-12 15:37:19 +0200 |
commit | c2ec20b226f0613db74a1e9fadffcf423ff1a180 (patch) | |
tree | 11e3556ecc0ea580ef55982a1f6ad1aaf45b7016 /src/plugins/platforms/wasm/qwasmopenglcontext.cpp | |
parent | 59c29436db8731fb7a8c9a932bf55d79b370668c (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.cpp | 141 |
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) |