Quasi-support for offscreen surface on WASM

Since context sharing is not currently supported with WebGL, offscreen
contexts have a limited usability on Qt for WASM.

If a context is shared, use the same underlaying WebGL context so that
the two can actually share resources. This is not full-blown context
sharing by any means but it makes e.g. the Open GL widget work as the
readback texture for it is 'shared' between the virtual Qt contexts.

If no sharing is desired, we use an OffscreenCanvas and actually create
a separate WebGL context.

Fixes: QTBUG-107558
Change-Id: If57e44739ddb57c167d5f8881a74d8dee52531f6
Reviewed-by: Mikołaj Boc <Mikolaj.Boc@qt.io>
(cherry picked from commit 0eea2238f37a5eaa83ada6cd262eceaed20e415c)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Mikolaj Boc 2022-11-28 16:35:24 +01:00 committed by Qt Cherry-pick Bot
parent 841efd0779
commit 07f2ca775c
5 changed files with 64 additions and 28 deletions

View File

@ -186,7 +186,7 @@ void QWasmIntegration::removeBackingStore(QWindow* window)
#ifndef QT_NO_OPENGL #ifndef QT_NO_OPENGL
QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{ {
return new QWasmOpenGLContext(context->format()); return new QWasmOpenGLContext(context);
} }
#endif #endif

View File

@ -6,10 +6,25 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
QWasmOffscreenSurface::QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface) QWasmOffscreenSurface::QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface)
:QPlatformOffscreenSurface(offscreenSurface) : QPlatformOffscreenSurface(offscreenSurface), m_offscreenCanvas(emscripten::val::undefined())
{ {
const auto offscreenCanvasClass = emscripten::val::global("OffscreenCanvas");
// The OffscreenCanvas is not supported on some browsers, most notably on Safari.
if (!offscreenCanvasClass)
return;
m_offscreenCanvas = offscreenCanvasClass.new_(offscreenSurface->size().width(),
offscreenSurface->size().height());
m_specialTargetId = std::string("!qtoffscreen_") + std::to_string(uintptr_t(this));
emscripten::val::module_property("specialHTMLTargets")
.set(m_specialTargetId, m_offscreenCanvas);
} }
QWasmOffscreenSurface::~QWasmOffscreenSurface() = default; QWasmOffscreenSurface::~QWasmOffscreenSurface()
{
emscripten::val::module_property("specialHTMLTargets").delete_(m_specialTargetId);
}
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -6,6 +6,10 @@
#include <qpa/qplatformoffscreensurface.h> #include <qpa/qplatformoffscreensurface.h>
#include <emscripten/val.h>
#include <string>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QOffscreenSurface; class QOffscreenSurface;
@ -14,8 +18,12 @@ class QWasmOffscreenSurface final : public QPlatformOffscreenSurface
public: public:
explicit QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface); explicit QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface);
~QWasmOffscreenSurface() final; ~QWasmOffscreenSurface() final;
private:
const std::string &id() const { return m_specialTargetId; }
private:
std::string m_specialTargetId;
emscripten::val m_offscreenCanvas;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -2,6 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmopenglcontext.h" #include "qwasmopenglcontext.h"
#include "qwasmoffscreensurface.h"
#include "qwasmintegration.h" #include "qwasmintegration.h"
#include <EGL/egl.h> #include <EGL/egl.h>
#include <emscripten/bind.h> #include <emscripten/bind.h>
@ -18,23 +20,22 @@ EMSCRIPTEN_BINDINGS(qwasmopenglcontext)
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
QWasmOpenGLContext::QWasmOpenGLContext(const QSurfaceFormat &format) QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)
: m_requestedFormat(format) : m_requestedFormat(context->format()), m_qGlContext(context)
{ {
m_requestedFormat.setRenderableType(QSurfaceFormat::OpenGLES); m_requestedFormat.setRenderableType(QSurfaceFormat::OpenGLES);
// if we set one, we need to set the other as well since in webgl, these are tied together // if we set one, we need to set the other as well since in webgl, these are tied together
if (format.depthBufferSize() < 0 && format.stencilBufferSize() > 0) if (m_requestedFormat.depthBufferSize() < 0 && m_requestedFormat.stencilBufferSize() > 0)
m_requestedFormat.setDepthBufferSize(16); m_requestedFormat.setDepthBufferSize(16);
if (format.stencilBufferSize() < 0 && format.depthBufferSize() > 0) if (m_requestedFormat.stencilBufferSize() < 0 && m_requestedFormat.depthBufferSize() > 0)
m_requestedFormat.setStencilBufferSize(8); m_requestedFormat.setStencilBufferSize(8);
} }
QWasmOpenGLContext::~QWasmOpenGLContext() QWasmOpenGLContext::~QWasmOpenGLContext()
{ {
if (!m_context) if (!m_webGLContext)
return; return;
// Destroy GL context. Work around bug in emscripten_webgl_destroy_context // Destroy GL context. Work around bug in emscripten_webgl_destroy_context
@ -44,9 +45,9 @@ QWasmOpenGLContext::~QWasmOpenGLContext()
emscripten::val savedRemoveAllHandlersOnTargetFunction = emscripten::val savedRemoveAllHandlersOnTargetFunction =
jsEvents["removeAllHandlersOnTarget"]; jsEvents["removeAllHandlersOnTarget"];
jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing")); jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing"));
emscripten_webgl_destroy_context(m_context); emscripten_webgl_destroy_context(m_webGLContext);
jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction); jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
m_context = 0; m_webGLContext = 0;
} }
bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format) bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
@ -61,18 +62,28 @@ bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
bool QWasmOpenGLContext::maybeCreateEmscriptenContext(QPlatformSurface *surface) bool QWasmOpenGLContext::maybeCreateEmscriptenContext(QPlatformSurface *surface)
{ {
if (m_context && m_surface == surface) if (m_webGLContext && m_surface == surface)
return true; return true;
// TODO(mikolajboc): Use OffscreenCanvas if available.
if (surface->surface()->surfaceClass() == QSurface::Offscreen)
return false;
m_surface = surface; m_surface = surface;
if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
auto *window = static_cast<QWasmWindow *>(surface); if (const auto *shareContext = m_qGlContext->shareContext()) {
m_context = createEmscriptenContext(window->canvasSelector(), m_requestedFormat); // Since there are no resource sharing capabilities with WebGL whatsoever, we use the
return true; // same actual underlaying WebGL context. This is not perfect, but it works in most
// cases.
m_webGLContext =
static_cast<QWasmOpenGLContext *>(shareContext->handle())->m_webGLContext;
} else {
// The non-shared offscreen context is heavily limited on WASM, but we provide it anyway
// for potential pixel readbacks.
m_webGLContext = createEmscriptenContext(
static_cast<QWasmOffscreenSurface *>(surface)->id(), m_requestedFormat);
}
} else {
m_webGLContext = createEmscriptenContext(
static_cast<QWasmWindow *>(surface)->canvasSelector(), m_requestedFormat);
}
return m_webGLContext > 0;
} }
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
@ -116,7 +127,7 @@ bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface)
if (!maybeCreateEmscriptenContext(surface)) if (!maybeCreateEmscriptenContext(surface))
return false; return false;
return emscripten_webgl_make_context_current(m_context) == EMSCRIPTEN_RESULT_SUCCESS; return emscripten_webgl_make_context_current(m_webGLContext) == EMSCRIPTEN_RESULT_SUCCESS;
} }
void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface) void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface)
@ -132,7 +143,7 @@ void QWasmOpenGLContext::doneCurrent()
bool QWasmOpenGLContext::isSharing() const bool QWasmOpenGLContext::isSharing() const
{ {
return false; return m_qGlContext->shareContext();
} }
bool QWasmOpenGLContext::isValid() const bool QWasmOpenGLContext::isValid() const
@ -142,7 +153,7 @@ bool QWasmOpenGLContext::isValid() const
// Note: we get isValid() calls before we see the surface and can // Note: we get isValid() calls before we see the surface and can
// create a native context, so no context is also a valid state. // create a native context, so no context is also a valid state.
return !m_context || !emscripten_is_webgl_context_lost(m_context); return !m_webGLContext || !emscripten_is_webgl_context_lost(m_webGLContext);
} }
QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName) QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName)

View File

@ -8,11 +8,12 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QOpenGLContext;
class QPlatformScreen; class QPlatformScreen;
class QWasmOpenGLContext : public QPlatformOpenGLContext class QWasmOpenGLContext : public QPlatformOpenGLContext
{ {
public: public:
QWasmOpenGLContext(const QSurfaceFormat &format); explicit QWasmOpenGLContext(QOpenGLContext *context);
~QWasmOpenGLContext(); ~QWasmOpenGLContext();
QSurfaceFormat format() const override; QSurfaceFormat format() const override;
@ -32,7 +33,8 @@ private:
QSurfaceFormat m_requestedFormat; QSurfaceFormat m_requestedFormat;
QPlatformSurface *m_surface = nullptr; QPlatformSurface *m_surface = nullptr;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_context = 0; QOpenGLContext *m_qGlContext;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_webGLContext = 0;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE