From c64d3864775d6280f1d3a44d61bb2fd893c0bc26 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Tue, 31 May 2022 17:56:49 +0200 Subject: [PATCH] QskPaintedNode reimplemented to work with RHI and software renderer Code will be moved in parts to QskTextureRenderer to do something similar with QskTextureNode. --- src/nodes/QskPaintedNode.cpp | 293 +++++++++++++++++++++++++++++++---- src/nodes/QskPaintedNode.h | 12 +- src/src.pro | 6 +- 3 files changed, 272 insertions(+), 39 deletions(-) diff --git a/src/nodes/QskPaintedNode.cpp b/src/nodes/QskPaintedNode.cpp index 574cae82..518fb717 100644 --- a/src/nodes/QskPaintedNode.cpp +++ b/src/nodes/QskPaintedNode.cpp @@ -4,24 +4,101 @@ *****************************************************************************/ #include "QskPaintedNode.h" -#include "QskTextureRenderer.h" +#include "QskSGNode.h" -class QskPaintedNode::PaintHelper : public QskTextureRenderer::PaintHelper +#include +#include +#include +#include + +#include +#include +#include + +QSK_QT_PRIVATE_BEGIN +#include +#include +QSK_QT_PRIVATE_END + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + +#include + +static GLuint qskTakeTexture( QOpenGLFramebufferObject& fbo ) { - public: - inline PaintHelper( QskPaintedNode* node ) - : m_node( node ) + /* + See https://bugreports.qt.io/browse/QTBUG-103929 + + As we create a FBO for each update of a node we can't live + without having this ( ugly ) workaround. + */ + class MyFBO { + public: + virtual ~MyFBO() = default; + QScopedPointer< QOpenGLFramebufferObjectPrivate > d_ptr; + }; + + static_assert( sizeof( MyFBO ) == sizeof( QOpenGLFramebufferObject ), + "Bad cast: QOpenGLFramebufferObject does not match" ); + + auto& attachment = reinterpret_cast< MyFBO* >( &fbo )->d_ptr->colorAttachments[0]; + auto guard = attachment.guard; + + const auto textureId = fbo.takeTexture(); + + if ( guard ) + { + class MyGuard : public QOpenGLSharedResourceGuard + { + public: + void invalidateTexture() { invalidateResource(); } + }; + + reinterpret_cast< MyGuard* >( guard )->invalidateTexture(); } - void paint( QPainter* painter, const QSize& size ) override + attachment.guard = guard; + + return textureId; +} + +#else + +static GLuint qskTakeTexture( QOpenGLFramebufferObject& fbo ) +{ + return fbo.takeTexture(); +} + +#endif + +namespace +{ + const quint8 imageRole = 250; // reserved for internal use + + inline QSGImageNode* findImageNode( QSGNode* parentNode ) { - m_node->paint( painter, size ); + return static_cast< QSGImageNode* >( + QskSGNode::findChildNode( parentNode, imageRole ) ); } - private: - QskPaintedNode* m_node; -}; + static inline bool qskHasOpenGLRenderer( QQuickWindow* window ) + { + if ( window == nullptr ) + return false; + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + if ( QQuickWindowPrivate::get( window )->rhi ) + { + // does not yet work with the experimental RHI implementation in Qt5 + return false; + } +#endif + + const auto renderer = window->rendererInterface(); + return renderer->graphicsApi() == QSGRendererInterface::OpenGL; + } +} QskPaintedNode::QskPaintedNode() { @@ -32,32 +109,184 @@ QskPaintedNode::~QskPaintedNode() } void QskPaintedNode::update( QQuickWindow* window, - QskTextureRenderer::RenderMode renderMode, const QRect& rect ) + QskTextureRenderer::RenderMode renderMode, const QRectF& rect ) { - bool isTextureDirty = isNull(); + auto imageNode = findImageNode( this ); - if ( !isTextureDirty ) + if ( rect.isEmpty() ) { - const auto oldRect = QskTextureNode::rect(); - isTextureDirty = ( rect.width() != static_cast< int >( oldRect.width() ) ) || - ( rect.height() != static_cast< int >( oldRect.height() ) ); + if ( imageNode ) + { + removeChildNode( imageNode ); + delete imageNode; + } } - - const auto newHash = hash(); - if ( ( newHash == 0 ) || ( newHash != m_hash ) ) + else { - m_hash = newHash; - isTextureDirty = true; + bool isDirty = false; + + const auto newHash = hash(); + if ( ( newHash == 0 ) || ( newHash != m_hash ) ) + { + m_hash = newHash; + isDirty = true; + } + + if ( !isDirty ) + isDirty = ( imageNode == nullptr ) || ( imageNode->rect() != rect ); + + if ( isDirty ) + { + if ( renderMode != QskTextureRenderer::Raster ) + { + if ( !qskHasOpenGLRenderer( window ) ) + renderMode = QskTextureRenderer::Raster; + } + + if ( renderMode == QskTextureRenderer::Raster ) + updateImageNode( window, rect ); + else + updateImageNodeGL( window, rect ); + } } - - auto textureId = QskTextureNode::textureId(); - - if ( isTextureDirty ) - { - PaintHelper helper( this ); - textureId = QskTextureRenderer::createTexture( - window, renderMode, rect.size(), &helper ); - } - - QskTextureNode::setTexture( window, rect, textureId ); +} + +void QskPaintedNode::updateImageNode( QQuickWindow* window, const QRectF& rect ) +{ + const auto ratio = window->effectiveDevicePixelRatio(); + const auto size = rect.size() * ratio; + + QImage image( size.toSize(), QImage::Format_RGBA8888_Premultiplied ); + image.fill( Qt::transparent ); + + { + QPainter painter( &image ); + + /* + setting a devicePixelRatio for the image only works for + value >= 1.0. So we have to scale manually. + */ + painter.scale( ratio, ratio ); + paint( &painter, rect.size() ); + } + + auto imageNode = findImageNode( this ); + + if ( imageNode == nullptr ) + { + imageNode = window->createImageNode(); + + imageNode->setOwnsTexture( true ); + QskSGNode::setNodeRole( imageNode, imageRole ); + + appendChildNode( imageNode ); + } + + if ( auto texture = qobject_cast< QSGPlainTexture* >( imageNode->texture() ) ) + texture->setImage( image ); + else + imageNode->setTexture( window->createTextureFromImage( image ) ); + + imageNode->setRect( rect ); +} + +void QskPaintedNode::updateImageNodeGL( QQuickWindow* window, const QRectF& rect ) +{ + const auto ratio = window->effectiveDevicePixelRatio(); + const QSize size( ratio * rect.width(), ratio * rect.height() ); + + auto imageNode = findImageNode( this ); + + if ( imageNode == nullptr ) + { + imageNode = window->createImageNode(); + + imageNode->setOwnsTexture( true ); + QskSGNode::setNodeRole( imageNode, imageRole ); + + appendChildNode( imageNode ); + } + + auto texture = qobject_cast< QSGPlainTexture* >( imageNode->texture() ); + if ( texture == nullptr ) + { + texture = new QSGPlainTexture; + texture->setHasAlphaChannel( true ); + texture->setOwnsTexture( true ); + + imageNode->setTexture( texture ); + } + + const auto textureId = createTexture( window, size ); + + auto rhi = QQuickWindowPrivate::get( window )->rhi; + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + texture->setTextureFromNativeTexture( + rhi, quint64( textureId ), 0, size, {}, {} ); +#else + if ( rhi ) + { + // enabled with: "export QSG_RHI=1" + texture->setTextureFromNativeObject( rhi, + QQuickWindow::NativeObjectTexture, &textureId, 0, size, false ); + } + else + { + texture->setTextureId( textureId ); + texture->setTextureSize( size ); + } +#endif + + imageNode->setRect( rect ); +} + +// this method will be moved to QskTextureRenderer. TODO ... +uint32_t QskPaintedNode::createTexture( QQuickWindow* window, const QSize& size ) +{ + auto context = QOpenGLContext::currentContext(); + + QOpenGLFramebufferObjectFormat format1; + format1.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil ); + + format1.setSamples( context->format().samples() ); + + QOpenGLFramebufferObject multisampledFbo( size, format1 ); + + QOpenGLPaintDevice pd( size ); + pd.setPaintFlipped( true ); + + { + int bufferId; + + auto gl = context->functions(); + + gl->glGetIntegerv( GL_ARRAY_BUFFER_BINDING, &bufferId); + gl->glBindBuffer( GL_ARRAY_BUFFER, 0 ); + + const auto ratio = window->effectiveDevicePixelRatio(); + + QPainter painter( &pd ); + painter.scale( ratio, ratio ); + + painter.setCompositionMode( QPainter::CompositionMode_Source ); + painter.fillRect( 0, 0, size.width(), size.height(), Qt::transparent ); + painter.setCompositionMode( QPainter::CompositionMode_SourceOver ); + + paint( &painter, size ); + + gl->glBindBuffer( GL_ARRAY_BUFFER, bufferId ); + } + + QOpenGLFramebufferObjectFormat format2; + format2.setAttachment( QOpenGLFramebufferObject::NoAttachment ); + + QOpenGLFramebufferObject fbo( size, format2 ); + + const QRect fboRect( 0, 0, size.width(), size.height() ); + + QOpenGLFramebufferObject::blitFramebuffer( + &fbo, fboRect, &multisampledFbo, fboRect ); + + return qskTakeTexture( fbo ); } diff --git a/src/nodes/QskPaintedNode.h b/src/nodes/QskPaintedNode.h index 6b53ab6a..06065406 100644 --- a/src/nodes/QskPaintedNode.h +++ b/src/nodes/QskPaintedNode.h @@ -6,17 +6,17 @@ #ifndef QSK_PAINTED_NODE_H #define QSK_PAINTED_NODE_H -#include "QskTextureNode.h" #include "QskTextureRenderer.h" +#include -class QSK_EXPORT QskPaintedNode : public QskTextureNode +class QSK_EXPORT QskPaintedNode : public QSGNode { public: QskPaintedNode(); ~QskPaintedNode() override; void update( QQuickWindow*, - QskTextureRenderer::RenderMode, const QRect& ); + QskTextureRenderer::RenderMode, const QRectF& ); protected: virtual void paint( QPainter*, const QSizeF& ) = 0; @@ -25,10 +25,10 @@ class QSK_EXPORT QskPaintedNode : public QskTextureNode virtual QskHashValue hash() const = 0; private: - class PaintHelper; + void updateImageNode( QQuickWindow*, const QRectF& ); + void updateImageNodeGL( QQuickWindow*, const QRectF& ); - void setTexture( QQuickWindow*, - const QRectF&, uint id, Qt::Orientations ) = delete; + uint32_t createTexture( QQuickWindow*, const QSize& ); QskHashValue m_hash; }; diff --git a/src/src.pro b/src/src.pro index 366590c0..58d26aaa 100644 --- a/src/src.pro +++ b/src/src.pro @@ -1,7 +1,11 @@ TEMPLATE = lib TARGET = $$qskLibraryTarget(qskinny) -QT += quick quick-private +QT += quick quick-private opengl-private + +greaterThan( QT_MAJOR_VERSION, 5 ) { + QT += opengl-private +} contains(QSK_CONFIG, QskDll): DEFINES += QSK_MAKEDLL