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. Task-number: QTBUG-120138 Change-Id: I929b9bb51153007c16630b1a991399f01ebffa62 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io> (cherry picked from commit c2ec20b226f0613db74a1e9fadffcf423ff1a180) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
355619c2a3
commit
f0d89ec153
@ -20,8 +20,10 @@ EMSCRIPTEN_BINDINGS(qwasmopenglcontext)
|
|||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
QHash<QPlatformSurface *, EMSCRIPTEN_WEBGL_CONTEXT_HANDLE> QWasmOpenGLContext::s_contexts;
|
||||||
|
|
||||||
QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)
|
QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)
|
||||||
: m_actualFormat(context->format()), m_qGlContext(context)
|
: m_actualFormat(context->format())
|
||||||
{
|
{
|
||||||
m_actualFormat.setRenderableType(QSurfaceFormat::OpenGLES);
|
m_actualFormat.setRenderableType(QSurfaceFormat::OpenGLES);
|
||||||
|
|
||||||
@ -35,10 +37,7 @@ QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)
|
|||||||
|
|
||||||
QWasmOpenGLContext::~QWasmOpenGLContext()
|
QWasmOpenGLContext::~QWasmOpenGLContext()
|
||||||
{
|
{
|
||||||
// Destroy GL context. Work around bug in emscripten_webgl_destroy_context
|
destroyWebGLContext(m_contextOwningSurface);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
|
bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
|
||||||
@ -51,56 +50,23 @@ bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
|
|||||||
(format.majorVersion() == 3 && format.minorVersion() == 0));
|
(format.majorVersion() == 3 && format.minorVersion() == 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
|
void QWasmOpenGLContext::destroyWebGLContext(QPlatformSurface *surface)
|
||||||
QWasmOpenGLContext::obtainEmscriptenContext(QPlatformSurface *surface)
|
|
||||||
{
|
{
|
||||||
if (m_ownedWebGLContext.surface == surface)
|
if (surface == nullptr)
|
||||||
return m_ownedWebGLContext.handle;
|
return;
|
||||||
|
int context = s_contexts.take(surface);
|
||||||
if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
|
if (context)
|
||||||
// Reuse the existing context for offscreen drawing, even if it happens to be a canvas
|
destroyWebGLContext(context);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWasmOpenGLContext::destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle)
|
void QWasmOpenGLContext::destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle)
|
||||||
{
|
{
|
||||||
if (!contextHandle)
|
if (!contextHandle)
|
||||||
return;
|
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 jsEvents = emscripten::val::module_property("JSEvents");
|
||||||
emscripten::val savedRemoveAllHandlersOnTargetFunction = jsEvents["removeAllHandlersOnTarget"];
|
emscripten::val savedRemoveAllHandlersOnTargetFunction = jsEvents["removeAllHandlersOnTarget"];
|
||||||
jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing"));
|
jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing"));
|
||||||
@ -108,8 +74,7 @@ void QWasmOpenGLContext::destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE con
|
|||||||
jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
|
jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
|
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
|
||||||
QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
|
|
||||||
QSurfaceFormat format)
|
QSurfaceFormat format)
|
||||||
{
|
{
|
||||||
EmscriptenWebGLContextAttributes attributes;
|
EmscriptenWebGLContextAttributes attributes;
|
||||||
@ -137,6 +102,24 @@ QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
|
|||||||
attributes.majorVersion = 1;
|
attributes.majorVersion = 1;
|
||||||
contextResult = emscripten_webgl_create_context(canvasSelector.c_str(), &attributes);
|
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;
|
return contextResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,20 +135,31 @@ GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) c
|
|||||||
|
|
||||||
bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface)
|
bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface)
|
||||||
{
|
{
|
||||||
static bool sentSharingWarning = false;
|
// Record this makeCurrent() attempt, since isValid() must repeat the answer
|
||||||
if (!sentSharingWarning && isSharing()) {
|
// from this function in order to signal context loss to calling code.
|
||||||
qWarning() << "The functionality for sharing OpenGL contexts is limited, see documentation";
|
m_madeCurrentSurface = surface;
|
||||||
sentSharingWarning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto *shareContext = m_qGlContext->shareContext())
|
// The native webgl context is tied to a single surface, and can't
|
||||||
return shareContext->makeCurrent(surface->surface());
|
// be made current for a different surface.
|
||||||
|
if (m_contextOwningSurface && m_contextOwningSurface != surface)
|
||||||
const auto context = obtainEmscriptenContext(surface);
|
|
||||||
if (!context)
|
|
||||||
return false;
|
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;
|
return emscripten_webgl_make_context_current(context) == EMSCRIPTEN_RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
@ -178,12 +172,15 @@ void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface)
|
|||||||
|
|
||||||
void QWasmOpenGLContext::doneCurrent()
|
void QWasmOpenGLContext::doneCurrent()
|
||||||
{
|
{
|
||||||
// No doneCurrent on WebGl
|
m_madeCurrentSurface = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QWasmOpenGLContext::isSharing() const
|
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
|
bool QWasmOpenGLContext::isValid() const
|
||||||
@ -191,9 +188,21 @@ bool QWasmOpenGLContext::isValid() const
|
|||||||
if (!isOpenGLVersionSupported(m_actualFormat))
|
if (!isOpenGLVersionSupported(m_actualFormat))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Note: we get isValid() calls before we see the surface and can
|
// We get isValid() calls before we see the surface and are able to
|
||||||
// create a native context, so no context is also a valid state.
|
// create a native context, which means that "no context" is a valid state.
|
||||||
return !m_usedWebGLContextHandle || !emscripten_is_webgl_context_lost(m_usedWebGLContextHandle);
|
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)
|
QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName)
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
#ifndef QWASMOPENGLCONTEXT_H
|
#ifndef QWASMOPENGLCONTEXT_H
|
||||||
#define QWASMOPENGLCONTEXT_H
|
#define QWASMOPENGLCONTEXT_H
|
||||||
|
|
||||||
|
#include <QtCore/qhash.h>
|
||||||
|
|
||||||
#include <qpa/qplatformopenglcontext.h>
|
#include <qpa/qplatformopenglcontext.h>
|
||||||
|
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
@ -29,24 +31,17 @@ public:
|
|||||||
bool isValid() const override;
|
bool isValid() const override;
|
||||||
QFunctionPointer getProcAddress(const char *procName) override;
|
QFunctionPointer getProcAddress(const char *procName) override;
|
||||||
|
|
||||||
|
static void destroyWebGLContext(QPlatformSurface *surface);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct QOpenGLContextData
|
|
||||||
{
|
|
||||||
QPlatformSurface *surface = nullptr;
|
|
||||||
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool isOpenGLVersionSupported(QSurfaceFormat format);
|
static bool isOpenGLVersionSupported(QSurfaceFormat format);
|
||||||
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE obtainEmscriptenContext(QPlatformSurface *surface);
|
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE createEmscriptenContext(const std::string &canvasSelector, QSurfaceFormat format);
|
||||||
static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
|
|
||||||
createEmscriptenContext(const std::string &canvasSelector, QSurfaceFormat format);
|
|
||||||
|
|
||||||
static void destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle);
|
static void destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle);
|
||||||
|
|
||||||
QSurfaceFormat m_actualFormat;
|
QSurfaceFormat m_actualFormat;
|
||||||
QOpenGLContext *m_qGlContext;
|
QPlatformSurface *m_madeCurrentSurface = nullptr;
|
||||||
QOpenGLContextData m_ownedWebGLContext;
|
QPlatformSurface *m_contextOwningSurface = nullptr;
|
||||||
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_usedWebGLContextHandle = 0;
|
static QHash<QPlatformSurface *, EMSCRIPTEN_WEBGL_CONTEXT_HANDLE> s_contexts;
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#if QT_CONFIG(draganddrop)
|
#if QT_CONFIG(draganddrop)
|
||||||
#include "qwasmdrag.h"
|
#include "qwasmdrag.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "qwasmopenglcontext.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@ -210,6 +211,8 @@ void QWasmWindow::registerEventHandlers()
|
|||||||
|
|
||||||
QWasmWindow::~QWasmWindow()
|
QWasmWindow::~QWasmWindow()
|
||||||
{
|
{
|
||||||
|
QWasmOpenGLContext::destroyWebGLContext(this);
|
||||||
|
|
||||||
#if QT_CONFIG(accessibility)
|
#if QT_CONFIG(accessibility)
|
||||||
QWasmAccessibility::onRemoveWindow(window());
|
QWasmAccessibility::onRemoveWindow(window());
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user