From d5a041f6b1726955934425a227ecc3203d4be9c8 Mon Sep 17 00:00:00 2001 From: Francis Hart Date: Tue, 23 Apr 2024 14:01:29 +0300 Subject: [PATCH] Fix QNanoPainter OpenGL context usage When using multiple QNanoWidgets, each widget will get a unique instance of a QOpenGLContext object. When the global instance of QNanoPainter is initialised, it happens against the OpenGL context owned by one of the QNanoWidgets (which ever is painted first). This means that the global instance of QNanoPainter may not work correctly when used with other QNanoWidget instances. There are 2 things needed to fix this: - The app must enable the Qt::AA_ShareOpenGLContexts attribute. This allows the QNanoPainter to be initialised against one QOpenGLContext, but also used with other QOpenGLContexts. - The QNanoPainter should maintain its own QOpenGLContext instance, and ensure the QNanoPainter backend is initialised against that. This is because, on Windows, the QNanoPainter backend retrieves the OpenGL function pointers from the active QOpenGLContext, but then uses them as a global variable. This means that once that particular QOpenGLContext is destroyed (ie. the QNanoWidget is destroyed), the OpenGL function pointers are also destroyed, but the global QNanoPainter backend still continues to reference them. --- examples/gallery/main.cpp | 2 ++ examples/helloitem/main.cpp | 2 ++ examples/hellowidget/main.cpp | 2 ++ examples/hellowindow/main.cpp | 2 ++ .../qnanopainter_vs_qpainter_demo/main.cpp | 2 ++ libqnanopainter/qnanopainter.cpp | 21 +++++++++++++++++-- libqnanopainter/qnanopainter.h | 6 ++++++ 7 files changed, 35 insertions(+), 2 deletions(-) diff --git a/examples/gallery/main.cpp b/examples/gallery/main.cpp index d23a587..760e696 100644 --- a/examples/gallery/main.cpp +++ b/examples/gallery/main.cpp @@ -10,6 +10,8 @@ int main(int argc, char *argv[]) // load res from libqnanopainter Q_INIT_RESOURCE(libqnanopainterdata); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + #ifdef Q_OS_WIN // Select between OpenGL and OpenGL ES (Angle) //QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); diff --git a/examples/helloitem/main.cpp b/examples/helloitem/main.cpp index 686731a..34bee6f 100644 --- a/examples/helloitem/main.cpp +++ b/examples/helloitem/main.cpp @@ -8,6 +8,8 @@ int main(int argc, char *argv[]) // load res from libqnanopainter Q_INIT_RESOURCE(libqnanopainterdata); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + #ifdef Q_OS_WIN // Select between OpenGL and OpenGL ES (Angle) //QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); diff --git a/examples/hellowidget/main.cpp b/examples/hellowidget/main.cpp index d7ff63f..ed2f775 100644 --- a/examples/hellowidget/main.cpp +++ b/examples/hellowidget/main.cpp @@ -27,6 +27,8 @@ int main(int argc, char *argv[]) // load res from libqnanopainter Q_INIT_RESOURCE(libqnanopainterdata); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + #ifdef Q_OS_WIN // Select between OpenGL and OpenGL ES (Angle) //QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); diff --git a/examples/hellowindow/main.cpp b/examples/hellowindow/main.cpp index 7c9ead5..1912cf1 100644 --- a/examples/hellowindow/main.cpp +++ b/examples/hellowindow/main.cpp @@ -27,6 +27,8 @@ int main(int argc, char *argv[]) // load res from libqnanopainter Q_INIT_RESOURCE(libqnanopainterdata); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + #ifdef Q_OS_WIN // Select between OpenGL and OpenGL ES (Angle) //QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); diff --git a/examples/qnanopainter_vs_qpainter_demo/main.cpp b/examples/qnanopainter_vs_qpainter_demo/main.cpp index b2e74c0..ec1aa70 100644 --- a/examples/qnanopainter_vs_qpainter_demo/main.cpp +++ b/examples/qnanopainter_vs_qpainter_demo/main.cpp @@ -13,6 +13,8 @@ int main(int argc, char *argv[]) // load res from libqnanopainter Q_INIT_RESOURCE(libqnanopainterdata); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + #ifdef Q_OS_WIN // Select between OpenGL and OpenGL ES (Angle) //QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); diff --git a/libqnanopainter/qnanopainter.cpp b/libqnanopainter/qnanopainter.cpp index 2cf292e..c45ecd2 100644 --- a/libqnanopainter/qnanopainter.cpp +++ b/libqnanopainter/qnanopainter.cpp @@ -138,6 +138,16 @@ Q_GLOBAL_STATIC(QNanoPainter, instance) QNanoPainter::QNanoPainter() { #ifndef QNANO_USE_RHI + // Save current OpenGL context so it can be restored later + QOpenGLContext *prevContext = QOpenGLContext::currentContext(); + + // Create new OpenGL context that can be owned by this QNanoPainter object + Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts)); + m_openglContext.setShareContext(QOpenGLContext::globalShareContext()); + m_openglContext.create(); + m_openglSurface.setFormat(m_openglContext.format()); + m_openglSurface.create(); + m_openglContext.makeCurrent(&m_openglSurface); // Request actual OpenGL context version and type QOpenGLContext *context = QOpenGLContext::currentContext(); @@ -156,6 +166,10 @@ QNanoPainter::QNanoPainter() m_nvgContext = m_backend->nvgCreate(NVG_ANTIALIAS); Q_ASSERT_X(m_nvgContext, "QNanoPainter::QNanoPainter", "Could not init nanovg!"); + // Restore previous OpenGL context + if (prevContext != nullptr) + prevContext->makeCurrent(prevContext->surface()); + #else m_backend.reset(QNanoBackendFactory::createBackend(0, 0, false)); #endif @@ -171,9 +185,12 @@ QNanoPainter::QNanoPainter() QNanoPainter::~QNanoPainter() { + if (m_backend && m_nvgContext) { + +#ifndef QNANO_USE_RHI + m_openglContext.makeCurrent(&m_openglSurface); +#endif - if (m_backend && m_nvgContext && QOpenGLContext::currentContext()) { - // Do NanoVG side cleanups only if OpenGL context still exists m_backend->nvgDelete(m_nvgContext); } diff --git a/libqnanopainter/qnanopainter.h b/libqnanopainter/qnanopainter.h index e4fbd8c..9a8f3a0 100644 --- a/libqnanopainter/qnanopainter.h +++ b/libqnanopainter/qnanopainter.h @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include "qnanocolor.h" #include "private/qnanobrush.h" #include "private/qnanodataelement.h" @@ -299,6 +301,10 @@ class QNanoPainter QSharedPointer m_defaultFont; QString m_openglContextName; +#ifndef QNANO_USE_RHI + QOpenGLContext m_openglContext; + QOffscreenSurface m_openglSurface; +#endif }; #endif // QNANOPAINTER_H