Skip to content

Commit

Permalink
Reuse the existing canvas context for offscreen surfaces
Browse files Browse the repository at this point in the history
Due to the nature of WebGL contexts, which cannot be reassigned to
targets other than they were created for, we will now reuse the created
canvas context for offscreen surfaces, since those (hopefully) mostly
operate on textures anyway. If this is not done, any switch to an
offscreen surface for the main RHI context invalidates resources on
contexts in a share group with it.

Fixes: QTBUG-111617
Change-Id: I9752f7eec396a3ef11414881f5f79f26e1e2c859
Reviewed-by: Morten Johan Sørvig <[email protected]>
  • Loading branch information
mboc-qt committed Mar 12, 2023
1 parent cdbfaf1 commit 6852627
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 29 deletions.
74 changes: 48 additions & 26 deletions src/plugins/platforms/wasm/qwasmopenglcontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,10 @@ QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)

QWasmOpenGLContext::~QWasmOpenGLContext()
{
if (!m_webGLContext)
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"));
emscripten_webgl_destroy_context(m_webGLContext);
jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
m_webGLContext = 0;
destroyWebGLContext(m_ownedWebGLContext.handle);
}

bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
Expand All @@ -60,30 +51,58 @@ bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
(format.majorVersion() == 3 && format.minorVersion() == 0));
}

bool QWasmOpenGLContext::maybeCreateEmscriptenContext(QPlatformSurface *surface)
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
QWasmOpenGLContext::obtainEmscriptenContext(QPlatformSurface *surface)
{
if (m_webGLContext && m_surface == surface)
return true;
if (m_ownedWebGLContext.surface == surface)
return m_ownedWebGLContext.handle;

m_surface = surface;
if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
if (const auto *shareContext = m_qGlContext->shareContext()) {
// Since there are no resource sharing capabilities with WebGL whatsoever, we use the
// same actual underlaying WebGL context. This is not perfect, but it works in most
// cases.
m_webGLContext =
static_cast<QWasmOpenGLContext *>(shareContext->handle())->m_webGLContext;
return static_cast<QWasmOpenGLContext *>(shareContext->handle())
->m_ownedWebGLContext.handle;
} 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);
// 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_requestedFormat) };
return m_ownedWebGLContext.handle;
}
} else {
m_webGLContext = createEmscriptenContext(
static_cast<QWasmWindow *>(surface)->canvasSelector(), m_requestedFormat);
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_requestedFormat)
};

return m_ownedWebGLContext.handle;
}
return m_webGLContext > 0;
}

void QWasmOpenGLContext::destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle)
{
if (!contextHandle)
return;
emscripten::val jsEvents = emscripten::val::module_property("JSEvents");
emscripten::val savedRemoveAllHandlersOnTargetFunction = jsEvents["removeAllHandlersOnTarget"];
jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing"));
emscripten_webgl_destroy_context(contextHandle);
jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
}

EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
Expand Down Expand Up @@ -124,10 +143,13 @@ GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) c

bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface)
{
if (!maybeCreateEmscriptenContext(surface))
const auto context = obtainEmscriptenContext(surface);
if (!context)
return false;

return emscripten_webgl_make_context_current(m_webGLContext) == EMSCRIPTEN_RESULT_SUCCESS;
m_usedWebGLContextHandle = context;

return emscripten_webgl_make_context_current(context) == EMSCRIPTEN_RESULT_SUCCESS;
}

void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface)
Expand All @@ -153,7 +175,7 @@ bool QWasmOpenGLContext::isValid() const

// 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_webGLContext || !emscripten_is_webgl_context_lost(m_webGLContext);
return !m_usedWebGLContextHandle || !emscripten_is_webgl_context_lost(m_usedWebGLContextHandle);
}

QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName)
Expand Down
15 changes: 12 additions & 3 deletions src/plugins/platforms/wasm/qwasmopenglcontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ QT_BEGIN_NAMESPACE

class QOpenGLContext;
class QPlatformScreen;
class QPlatformSurface;
class QWasmOpenGLContext : public QPlatformOpenGLContext
{
public:
Expand All @@ -26,15 +27,23 @@ class QWasmOpenGLContext : public QPlatformOpenGLContext
QFunctionPointer getProcAddress(const char *procName) override;

private:
struct QOpenGLContextData
{
QPlatformSurface *surface = nullptr;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle = 0;
};

static bool isOpenGLVersionSupported(QSurfaceFormat format);
bool maybeCreateEmscriptenContext(QPlatformSurface *surface);
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE obtainEmscriptenContext(QPlatformSurface *surface);
static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
createEmscriptenContext(const std::string &canvasSelector, QSurfaceFormat format);

static void destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle);

QSurfaceFormat m_requestedFormat;
QPlatformSurface *m_surface = nullptr;
QOpenGLContext *m_qGlContext;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_webGLContext = 0;
QOpenGLContextData m_ownedWebGLContext;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_usedWebGLContextHandle = 0;
};

QT_END_NAMESPACE
Expand Down

0 comments on commit 6852627

Please sign in to comment.