diff --git a/examples/examples.pro b/examples/examples.pro index 2148d806..76bb9b66 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -26,6 +26,5 @@ SUBDIRS += \ boxes \ buttons \ frames \ - gbenchmark \ glabels \ messageboxQml diff --git a/examples/gbenchmark/Benchmark.cpp b/examples/gbenchmark/Benchmark.cpp deleted file mode 100644 index eeef334a..00000000 --- a/examples/gbenchmark/Benchmark.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - -#include "Benchmark.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -bool Benchmark::run( const QString& dirName ) -{ - QDir svgDir( dirName ); - - QStringList svgFiles = svgDir.entryList( QStringList() << "*.svg", QDir::Files ); - if ( svgFiles.isEmpty() ) - return false; - - const char qvgPath[] = "/tmp/benchmark/qvg"; - if ( !svgDir.mkpath( qvgPath ) ) - return false; - - QStringList qvgFiles = svgFiles; - for ( int i = 0; i < qvgFiles.size(); i++ ) - { - svgFiles[ i ].prepend( "/" ); - svgFiles[ i ].prepend( dirName ); - - qvgFiles[ i ].replace( ".svg", ".qvg" ); - qvgFiles[ i ].prepend( "/" ); - qvgFiles[ i ].prepend( qvgPath ); - } - - QVector< QskGraphic > graphics( qvgFiles.size() ); - QVector< QSvgRenderer* > renderers( svgFiles.size() ); - - qint64 msElapsed[ 6 ]; - - QElapsedTimer timer; - - { - // compile step - - timer.start(); - - for ( int i = 0; i < svgFiles.size(); i++ ) - { - renderers[ i ] = new QSvgRenderer(); - if ( !renderers[ i ]->load( svgFiles[ i ] ) ) - { - qCritical() << "Can't load" << svgFiles[ i ]; - return false; - } - } - - msElapsed[ 0 ] = timer.elapsed(); - } - - { - // converting into graphics and storing to disk - - timer.start(); - - for ( int i = 0; i < renderers.size(); i++ ) - { - QPainter painter( &graphics[ i ] ); - renderers[ i ]->render( &painter ); - painter.end(); - } - - msElapsed[ 1 ] = timer.elapsed(); - } - - { - // writing them to disk - - timer.start(); - - for ( int i = 0; i < graphics.size(); i++ ) - { - QskGraphicIO::write( graphics[ i ], qvgFiles[ i ] ); - } - - msElapsed[ 2 ] = timer.elapsed(); - } - - { - // loading qvg files to memory - - timer.start(); - - for ( int i = 0; i < qvgFiles.size(); i++ ) - { - graphics[ i ] = QskGraphicIO::read( qvgFiles[ i ] ); - if ( graphics[ i ].isNull() ) - { - qCritical() << "Can't load" << qvgFiles[ i ]; - return false; - } - } - - msElapsed[ 3 ] = timer.elapsed(); - } - - { - // creating textures using OpenGL - - timer.start(); - - const QSize targetSize( 200, 200 ); - const QskColorFilter colorFilter; - - for ( int i = 0; i < qvgFiles.size(); i++ ) - { - using namespace QskTextureRenderer; - - const auto textureId = createTextureFromGraphic( - nullptr, OpenGL, targetSize, graphics[ i ], colorFilter, - Qt::IgnoreAspectRatio ); - - if ( textureId == 0 ) - { - qCritical() << "Can't render texture for" << qvgFiles[ i ]; - return false; - } - } - - msElapsed[ 4 ] = timer.elapsed(); - } - - { - // creating textures using Raster - - timer.start(); - - const QSize targetSize( 200, 200 ); - const QskColorFilter colorFilter; - - for ( int i = 0; i < qvgFiles.size(); i++ ) - { - using namespace QskTextureRenderer; - - const auto textureId = createTextureFromGraphic( - nullptr, Raster, targetSize, graphics[ i ], colorFilter, - Qt::IgnoreAspectRatio ); - - if ( textureId == 0 ) - { - qCritical() << "Can't render texture for" << qvgFiles[ i ]; - return false; - } - } - - msElapsed[ 5 ] = timer.elapsed(); - } - - qDebug() << "#Icons:" << svgFiles.count() << - "Compiled:" << msElapsed[ 0 ] << - "Converted:" << msElapsed[ 1 ] << - "Stored:" << msElapsed[ 2 ] << - "Loaded:" << msElapsed[ 3 ] << - "Rendered OpenGL:" << msElapsed[ 4 ] << - "Rendered Raster:" << msElapsed[ 5 ]; - - svgDir.rmdir( qvgPath ); - - return true; -} diff --git a/examples/gbenchmark/Benchmark.h b/examples/gbenchmark/Benchmark.h deleted file mode 100644 index 4a6efc78..00000000 --- a/examples/gbenchmark/Benchmark.h +++ /dev/null @@ -1,13 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - -#pragma once - -class QString; - -namespace Benchmark -{ - bool run( const QString& svgDir ); -} diff --git a/examples/gbenchmark/gbenchmark.pro b/examples/gbenchmark/gbenchmark.pro deleted file mode 100644 index 850763cf..00000000 --- a/examples/gbenchmark/gbenchmark.pro +++ /dev/null @@ -1,10 +0,0 @@ -CONFIG += qskexample - -QT += svg - -HEADERS += \ - Benchmark.h - -SOURCES += \ - Benchmark.cpp \ - main.cpp diff --git a/examples/gbenchmark/main.cpp b/examples/gbenchmark/main.cpp deleted file mode 100644 index 2a5b00a2..00000000 --- a/examples/gbenchmark/main.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - -#include "Benchmark.h" - -#include -#include -#include - -#include -#include - -class Button : public QskPushButton -{ - public: - Button( const QString& testDir ) - : m_testDir( testDir ) - { - setText( QString( "Run: " ) + testDir ); - setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); - } - - void run() - { - Benchmark::run( m_testDir ); - } - - private: - QString m_testDir; -}; - -int main( int argc, char* argv[] ) -{ - QGuiApplication app( argc, argv ); - - QCommandLineParser parser; - parser.setApplicationDescription( "Benchmark for creating textures from SVGs" ); - parser.addHelpOption(); - parser.addPositionalArgument( "svgdir", "Directory with SVG files.", "[pathname]" ); - - parser.process( app ); - - const QStringList args = parser.positionalArguments(); - - if ( args.count() != 1 ) - parser.showHelp( 1 ); - - auto button = new Button( args[ 0 ] ); - button->setLayoutAlignmentHint( Qt::AlignCenter ); - QObject::connect( button, &Button::clicked, button, &Button::run ); - - auto box = new QskLinearBox(); - box->addItem( button ); - - QskWindow window; - window.setColor( Qt::white ); - window.resize( 400, 400 ); - window.addItem( box ); - - window.show(); - - return app.exec(); -} diff --git a/src/controls/QskSkinlet.cpp b/src/controls/QskSkinlet.cpp index 14319207..29fc172b 100644 --- a/src/controls/QskSkinlet.cpp +++ b/src/controls/QskSkinlet.cpp @@ -113,18 +113,17 @@ static inline QSGNode* qskUpdateGraphicNode( if ( control == nullptr ) return nullptr; - auto mode = QskTextureRenderer::OpenGL; - auto graphicNode = static_cast< QskGraphicNode* >( node ); if ( graphicNode == nullptr ) graphicNode = new QskGraphicNode(); - if ( control->testUpdateFlag( QskControl::PreferRasterForTextures ) ) - mode = QskTextureRenderer::Raster; + const bool useRaster = control->testUpdateFlag( QskControl::PreferRasterForTextures ); + graphicNode->setRenderHint( useRaster ? QskPaintedNode::Raster : QskPaintedNode::OpenGL ); + + graphicNode->setMirrored( mirrored ); const auto r = qskSceneAlignedRect( control, rect ); - graphicNode->setGraphic( control->window(), graphic, - colorFilter, mode, r, mirrored ); + graphicNode->setGraphic( control->window(), graphic, colorFilter, r ); return graphicNode; } diff --git a/src/graphic/QskGraphicTextureFactory.cpp b/src/graphic/QskGraphicTextureFactory.cpp index 3a256448..25210b87 100644 --- a/src/graphic/QskGraphicTextureFactory.cpp +++ b/src/graphic/QskGraphicTextureFactory.cpp @@ -54,16 +54,30 @@ QSize QskGraphicTextureFactory::size() const return m_size; } - QSGTexture* QskGraphicTextureFactory::createTexture( QQuickWindow* window ) const { - using namespace QskTextureRenderer; + class PaintHelper : public QskTextureRenderer::PaintHelper + { + public: + PaintHelper( const QskGraphic& graphic, const QskColorFilter& filter ) + : m_graphic( graphic ) + , m_filter( filter ) + { + } - const uint textureId = createTextureFromGraphic( - window, QskTextureRenderer::OpenGL, m_size, m_graphic, m_colorFilter, - Qt::IgnoreAspectRatio ); + void paint( QPainter* painter, const QSize& size ) override + { + const QRect rect( 0, 0, size.width(), size.height() ); + m_graphic.render( painter, rect, m_filter ); + } - return textureFromId( window, textureId, m_size ); + private: + const QskGraphic& m_graphic; + const QskColorFilter& m_filter; + }; + + PaintHelper helper( m_graphic, m_colorFilter ); + return QskTextureRenderer::createPaintedTexture( window, m_size, &helper ); } QSize QskGraphicTextureFactory::textureSize() const diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index 3b9f3726..be8feb07 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -30,10 +30,10 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& metrics, const QskGradient& gradient, QQuickWindow* window ) { const ArcData arcData { metrics, gradient }; - update( window, rect.toRect(), &arcData ); + update( window, rect, QSizeF(), &arcData ); } -void QskArcNode::paint( QPainter* painter, const QSizeF& size, const void* nodeData ) +void QskArcNode::paint( QPainter* painter, const QSize& size, const void* nodeData ) { const auto arcData = reinterpret_cast< const ArcData* >( nodeData ); diff --git a/src/nodes/QskArcNode.h b/src/nodes/QskArcNode.h index 411c7582..24c66654 100644 --- a/src/nodes/QskArcNode.h +++ b/src/nodes/QskArcNode.h @@ -23,7 +23,7 @@ class QSK_EXPORT QskArcNode : public QskPaintedNode const QskGradient&, QQuickWindow* ); protected: - void paint( QPainter*, const QSizeF&, const void* nodeData ) override; + void paint( QPainter*, const QSize&, const void* nodeData ) override; QskHashValue hash( const void* nodeData ) const override; }; diff --git a/src/nodes/QskGraphicNode.cpp b/src/nodes/QskGraphicNode.cpp index 4e3e4d79..86023740 100644 --- a/src/nodes/QskGraphicNode.cpp +++ b/src/nodes/QskGraphicNode.cpp @@ -8,27 +8,17 @@ #include "QskColorFilter.h" #include "QskPainterCommand.h" -static inline QskHashValue qskHash( - const QskGraphic& graphic, const QskColorFilter& colorFilter, - QskTextureRenderer::RenderMode renderMode ) +namespace { - QskHashValue hash = 12000; - - const auto& substitutions = colorFilter.substitutions(); - if ( substitutions.size() > 0 ) + class GraphicData { - hash = qHashBits( substitutions.constData(), - substitutions.size() * sizeof( substitutions[ 0 ] ), hash ); - } - - hash = graphic.hash( hash ); - hash = qHash( renderMode, hash ); - - return hash; + public: + const QskGraphic& graphic; + const QskColorFilter& colorFilter; + }; } QskGraphicNode::QskGraphicNode() - : m_hash( 0 ) { } @@ -36,14 +26,10 @@ QskGraphicNode::~QskGraphicNode() { } -void QskGraphicNode::setGraphic( - QQuickWindow* window, const QskGraphic& graphic, const QskColorFilter& colorFilter, - QskTextureRenderer::RenderMode renderMode, const QRectF& rect, - Qt::Orientations mirrored ) +void QskGraphicNode::setGraphic( QQuickWindow* window, const QskGraphic& graphic, + const QskColorFilter& colorFilter, const QRectF& rect ) { - bool isTextureDirty = isNull(); - - QSize textureSize; + QSizeF size; if ( graphic.commandTypes() == QskGraphic::RasterData ) { @@ -52,34 +38,43 @@ void QskGraphicNode::setGraphic( There is no benefit in rescaling it into the target rectangle by the CPU and creating a new texture. */ - textureSize = graphic.defaultSize().toSize(); - } - else - { - textureSize = rect.size().toSize(); - - if ( !isTextureDirty ) - { - const auto oldRect = QskTextureNode::rect(); - isTextureDirty = ( rect.width() != static_cast< int >( oldRect.width() ) ) || - ( rect.height() != static_cast< int >( oldRect.height() ) ); - } + size = graphic.defaultSize(); } - const auto hash = qskHash( graphic, colorFilter, renderMode ); - if ( hash != m_hash ) - { - m_hash = hash; - isTextureDirty = true; - } - - auto textureId = QskTextureNode::textureId(); - - if ( isTextureDirty ) - { - textureId = QskTextureRenderer::createTextureFromGraphic( - window, renderMode, textureSize, graphic, colorFilter, Qt::IgnoreAspectRatio ); - } - - QskTextureNode::setTexture( window, rect, textureId, mirrored ); + const GraphicData graphicData { graphic, colorFilter }; + update( window, rect, size, &graphicData ); +} + +void QskGraphicNode::paint( QPainter* painter, const QSize& size, const void* nodeData ) +{ + const auto graphicData = reinterpret_cast< const GraphicData* >( nodeData ); + + const auto& graphic = graphicData->graphic; + const auto& colorFilter = graphicData->colorFilter; + + if ( graphic.commandTypes() == QskGraphic::RasterData ) + { + qDebug() << size; + } + + const QRectF rect( 0, 0, size.width(), size.height() ); + graphic.render( painter, rect, colorFilter, Qt::IgnoreAspectRatio ); +} + +QskHashValue QskGraphicNode::hash( const void* nodeData ) const +{ + const auto graphicData = reinterpret_cast< const GraphicData* >( nodeData ); + + const auto& graphic = graphicData->graphic; + + QskHashValue hash = 12000; + + const auto& substitutions = graphicData->colorFilter.substitutions(); + if ( substitutions.size() > 0 ) + { + hash = qHashBits( substitutions.constData(), + substitutions.size() * sizeof( substitutions[ 0 ] ), hash ); + } + + return graphic.hash( hash ); } diff --git a/src/nodes/QskGraphicNode.h b/src/nodes/QskGraphicNode.h index 3d5925b5..26b5930c 100644 --- a/src/nodes/QskGraphicNode.h +++ b/src/nodes/QskGraphicNode.h @@ -6,29 +6,23 @@ #ifndef QSK_GRAPHIC_NODE_H #define QSK_GRAPHIC_NODE_H -#include "QskTextureRenderer.h" -#include "QskTextureNode.h" +#include "QskPaintedNode.h" class QskGraphic; class QskColorFilter; -class QQuickWindow; -class QSK_EXPORT QskGraphicNode : public QskTextureNode +class QSK_EXPORT QskGraphicNode : public QskPaintedNode { public: QskGraphicNode(); ~QskGraphicNode() override; - void setGraphic( QQuickWindow*, - const QskGraphic&, const QskColorFilter&, - QskTextureRenderer::RenderMode, const QRectF&, - Qt::Orientations mirrored = Qt::Orientations() ); + void setGraphic( QQuickWindow*, const QskGraphic&, + const QskColorFilter&, const QRectF& ); private: - void setTexture( QQuickWindow*, - const QRectF&, uint id, Qt::Orientations ) = delete; - - QskHashValue m_hash; + virtual void paint( QPainter*, const QSize&, const void* nodeData ) override; + virtual QskHashValue hash( const void* nodeData ) const override; }; #endif diff --git a/src/nodes/QskPaintedNode.cpp b/src/nodes/QskPaintedNode.cpp index beedd175..101f9641 100644 --- a/src/nodes/QskPaintedNode.cpp +++ b/src/nodes/QskPaintedNode.cpp @@ -5,65 +5,17 @@ #include "QskPaintedNode.h" #include "QskSGNode.h" +#include "QskTextureRenderer.h" #include #include #include #include -#include -#include -#include - QSK_QT_PRIVATE_BEGIN #include -#include -#include QSK_QT_PRIVATE_END -#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) -#include -#endif - -static GLuint qskTakeTexture( QOpenGLFramebufferObject& fbo ) -{ - /* - 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(); - } - - attachment.guard = guard; - - return textureId; -} - static inline QSGImageNode::TextureCoordinatesTransformMode qskEffectiveTransformMode( const Qt::Orientations mirrored ) { @@ -89,15 +41,6 @@ namespace return static_cast< QSGImageNode* >( node ); } - - static inline bool qskHasOpenGLRenderer( QQuickWindow* window ) - { - if ( window == nullptr ) - return false; - - const auto renderer = window->rendererInterface(); - return renderer->graphicsApi() == QSGRendererInterface::OpenGL; - } } QskPaintedNode::QskPaintedNode() @@ -137,6 +80,17 @@ Qt::Orientations QskPaintedNode::mirrored() const return m_mirrored; } +QSize QskPaintedNode::textureSize() const +{ + if ( const auto imageNode = findImageNode( this ) ) + { + if ( auto texture = imageNode->texture() ) + return texture->textureSize(); + } + + return QSize(); +} + QRectF QskPaintedNode::rect() const { const auto imageNode = findImageNode( this ); @@ -144,7 +98,7 @@ QRectF QskPaintedNode::rect() const } void QskPaintedNode::update( QQuickWindow* window, - const QRectF& rect, const void* nodeData ) + const QRectF& rect, const QSizeF& size, const void* nodeData ) { auto imageNode = findImageNode( this ); @@ -159,186 +113,123 @@ void QskPaintedNode::update( QQuickWindow* window, return; } - bool isDirty = false; + if ( imageNode == nullptr ) + { + imageNode = window->createImageNode(); + + imageNode->setOwnsTexture( true ); + QskSGNode::setNodeRole( imageNode, imageRole ); + + appendChildNode( imageNode ); + } + + QSize imageSize; + + { + auto scaledSize = size.isEmpty() ? rect.size() : size; + scaledSize *= window->effectiveDevicePixelRatio(); + + imageSize = scaledSize.toSize(); + } + + bool isTextureDirty = false; const auto newHash = hash( nodeData ); if ( ( newHash == 0 ) || ( newHash != m_hash ) ) { m_hash = newHash; - isDirty = true; + isTextureDirty = true; + } + else + { + isTextureDirty = ( imageSize != textureSize() ); } - if ( !isDirty ) - isDirty = ( imageNode == nullptr ) || ( imageNode->rect() != rect ); - if ( isDirty ) + if ( isTextureDirty ) + updateTexture( window, imageSize, nodeData ); + + imageNode->setRect( rect ); + imageNode->setTextureCoordinatesTransform( + qskEffectiveTransformMode( m_mirrored ) ); +} + +void QskPaintedNode::updateTexture( QQuickWindow* window, + const QSize& size, const void* nodeData ) +{ + auto imageNode = findImageNode( this ); + + if ( ( m_renderHint == OpenGL ) && QskTextureRenderer::isOpenGLWindow( window ) ) { - if ( ( m_renderHint == OpenGL ) && qskHasOpenGLRenderer( window ) ) - updateImageNodeGL( window, rect, nodeData ); + const auto textureId = createTextureGL( window, size, nodeData ); + + auto texture = qobject_cast< QSGPlainTexture* >( imageNode->texture() ); + if ( texture == nullptr ) + { + texture = new QSGPlainTexture; + texture->setHasAlphaChannel( true ); + texture->setOwnsTexture( true ); + + imageNode->setTexture( texture ); + } + + QskTextureRenderer::setTextureId( window, textureId, size, texture ); + } + else + { + const auto image = createImage( window, size, nodeData ); + + if ( auto texture = qobject_cast< QSGPlainTexture* >( imageNode->texture() ) ) + texture->setImage( image ); else - updateImageNode( window, rect, nodeData ); - } - - imageNode = findImageNode( this ); - if ( imageNode ) - { - imageNode->setRect( rect ); - imageNode->setTextureCoordinatesTransform( - qskEffectiveTransformMode( m_mirrored ) ); + imageNode->setTexture( window->createTextureFromImage( image ) ); } } -void QskPaintedNode::updateImageNode( - QQuickWindow* window, const QRectF& rect, const void* nodeData ) +QImage QskPaintedNode::createImage( QQuickWindow* window, + const QSize& size, const void* nodeData ) { - const auto ratio = window->effectiveDevicePixelRatio(); - const auto size = rect.size() * ratio; - - QImage image( size.toSize(), QImage::Format_RGBA8888_Premultiplied ); + QImage image( size, 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(), nodeData ); - } - - 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 ) ); -} - -void QskPaintedNode::updateImageNodeGL( - QQuickWindow* window, const QRectF& rect, const void* nodeData ) -{ - 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 ); - } + QPainter painter( &image ); /* - QQuickFramebufferObject does the FBO rendering early - ( QQuickWindow::beforeRendering ). However doing it below updatePaintNode - seems to work as well. Let's see if we run into issues ... + setting a devicePixelRatio for the image only works for + value >= 1.0. So we have to scale manually. */ - const auto textureId = createTexture( window, size, nodeData ); + const auto ratio = window->effectiveDevicePixelRatio(); + painter.scale( ratio, ratio ); - auto rhi = QQuickWindowPrivate::get( window )->rhi; + paint( &painter, size / ratio, nodeData ); -#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 + painter.end(); + + return image; } -uint32_t QskPaintedNode::createTexture( +quint32 QskPaintedNode::createTextureGL( QQuickWindow* window, const QSize& size, const void* nodeData ) { - /* - Binding GL_ARRAY_BUFFER/GL_ELEMENT_ARRAY_BUFFER to 0 seems to be enough. - - However - as we do not know what is finally painted and what the - OpenGL paint engine is doing with better reinitialize everything. - - Hope this has no side effects as the context will leave the function - in a modified state. Otherwise we could try to change the buffers - only and reset them, before leaving. - */ - - window->beginExternalCommands(); - -#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) - QQuickOpenGLUtils::resetOpenGLState(); -#else - window->resetOpenGLState(); -#endif - - 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 ); - + class PaintHelper : public QskTextureRenderer::PaintHelper { - const auto ratio = window->effectiveDevicePixelRatio(); + public: + PaintHelper( QskPaintedNode* node, const void* nodeData ) + : m_node( node ) + , m_nodeData( nodeData ) + { + } - QPainter painter( &pd ); - painter.scale( ratio, ratio ); + void paint( QPainter* painter, const QSize& size ) override + { + m_node->paint( painter, size, m_nodeData ); + } - painter.setCompositionMode( QPainter::CompositionMode_Source ); - painter.fillRect( 0, 0, size.width(), size.height(), Qt::transparent ); - painter.setCompositionMode( QPainter::CompositionMode_SourceOver ); + private: + QskPaintedNode* m_node; + const void* m_nodeData; + }; - paint( &painter, size, nodeData ); - } - - 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 ); - - window->endExternalCommands(); - - return qskTakeTexture( fbo ); + PaintHelper helper( this, nodeData ); + return createPaintedTextureGL( window, size, &helper ); } diff --git a/src/nodes/QskPaintedNode.h b/src/nodes/QskPaintedNode.h index 755b8dac..cda4250f 100644 --- a/src/nodes/QskPaintedNode.h +++ b/src/nodes/QskPaintedNode.h @@ -11,6 +11,7 @@ class QQuickWindow; class QPainter; +class QImage; class QSK_EXPORT QskPaintedNode : public QSGNode { @@ -42,20 +43,21 @@ class QSK_EXPORT QskPaintedNode : public QSGNode Qt::Orientations mirrored() const; QRectF rect() const; + QSize textureSize() const; + + virtual void paint( QPainter*, const QSize&, const void* nodeData ) = 0; protected: - void update( QQuickWindow*, const QRectF&, const void* nodeData ); - - virtual void paint( QPainter*, const QSizeF&, const void* nodeData ) = 0; + void update( QQuickWindow*, const QRectF&, const QSizeF&, const void* nodeData ); // a hash value of '0' always results in repainting virtual QskHashValue hash( const void* nodeData ) const = 0; private: - void updateImageNode( QQuickWindow*, const QRectF&, const void* nodeData ); - void updateImageNodeGL( QQuickWindow*, const QRectF&, const void* nodeData ); + void updateTexture( QQuickWindow*, const QSize&, const void* nodeData ); - uint32_t createTexture( QQuickWindow*, const QSize&, const void* nodeData ); + QImage createImage( QQuickWindow*, const QSize&, const void* nodeData ); + quint32 createTextureGL( QQuickWindow*, const QSize&, const void* nodeData ); RenderHint m_renderHint = OpenGL; Qt::Orientations m_mirrored; diff --git a/src/nodes/QskTextureRenderer.cpp b/src/nodes/QskTextureRenderer.cpp index 16bc8074..072b1dd1 100644 --- a/src/nodes/QskTextureRenderer.cpp +++ b/src/nodes/QskTextureRenderer.cpp @@ -4,92 +4,177 @@ *****************************************************************************/ #include "QskTextureRenderer.h" -#include "QskColorFilter.h" -#include "QskGraphic.h" -#include "QskSetup.h" #include -#include #include -#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 + #include #endif -static inline bool qskHasOpenGLRenderer( const QQuickWindow* window ) +static GLuint qskTakeTexture( QOpenGLFramebufferObject& fbo ) +{ + /* + 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(); + } + + attachment.guard = guard; + + return textureId; +} + +bool QskTextureRenderer::isOpenGLWindow( const QQuickWindow* window ) { if ( window == nullptr ) return false; const auto renderer = window->rendererInterface(); - return renderer->graphicsApi() == QSGRendererInterface::OpenGL; + switch( renderer->graphicsApi() ) + { + case QSGRendererInterface::OpenGL: +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + case QSGRendererInterface::OpenGLRhi: +#endif + return true; + + default: + return false; + } } -static uint qskCreateTextureOpenGL( QQuickWindow* window, - const QSize& size, QskTextureRenderer::PaintHelper* helper ) +void QskTextureRenderer::setTextureId( QQuickWindow* window, + quint32 textureId, const QSize& size, QSGTexture* texture ) { - const auto ratio = window ? window->effectiveDevicePixelRatio() : 1.0; + auto plainTexture = qobject_cast< QSGPlainTexture* >( texture ); + if ( plainTexture == nullptr ) + return; - const int width = ratio * size.width(); - const int height = ratio * size.height(); + auto rhi = QQuickWindowPrivate::get( window )->rhi; + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + plainTexture->setTextureFromNativeTexture( + rhi, quint64( textureId ), 0, size, {}, {} ); +#else + if ( rhi ) + { + // enabled with: "export QSG_RHI=1" + plainTexture->setTextureFromNativeObject( rhi, + QQuickWindow::NativeObjectTexture, &textureId, 0, size, false ); + } + else + { + plainTexture->setTextureId( textureId ); + plainTexture->setTextureSize( size ); + } +#endif +} + +quint32 QskTextureRenderer::createPaintedTextureGL( + QQuickWindow* window, const QSize& size, QskTextureRenderer::PaintHelper* helper ) +{ + /* + Binding GL_ARRAY_BUFFER/GL_ELEMENT_ARRAY_BUFFER to 0 seems to be enough. + + However - as we do not know what is finally painted and what the + OpenGL paint engine is doing with better reinitialize everything. + + Hope this has no side effects as the context will leave the function + in a modified state. Otherwise we could try to change the buffers + only and reset them, before leaving. + + QQuickFramebufferObject does the FBO rendering early + ( QQuickWindow::beforeRendering ). But so far doing it below updatePaintNode + seems to work as well. Let's see if we run into issues ... + */ + + window->beginExternalCommands(); + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + QQuickOpenGLUtils::resetOpenGLState(); +#else + window->resetOpenGLState(); +#endif + + auto context = QOpenGLContext::currentContext(); QOpenGLFramebufferObjectFormat format1; format1.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil ); - // ### TODO: get samples from window instead - format1.setSamples( QOpenGLContext::currentContext()->format().samples() ); + format1.setSamples( context->format().samples() ); - QOpenGLFramebufferObject multisampledFbo( width, height, format1 ); + QOpenGLFramebufferObject multisampledFbo( size, format1 ); - QOpenGLPaintDevice pd( width, height ); + QOpenGLPaintDevice pd( size ); pd.setPaintFlipped( true ); { QPainter painter( &pd ); - painter.scale( ratio, ratio ); painter.setCompositionMode( QPainter::CompositionMode_Source ); - painter.fillRect( 0, 0, width, height, Qt::transparent ); + painter.fillRect( 0, 0, size.width(), size.height(), Qt::transparent ); painter.setCompositionMode( QPainter::CompositionMode_SourceOver ); - helper->paint( &painter, size ); + const auto ratio = window->effectiveDevicePixelRatio(); -#if 1 - if ( format1.samples() > 0 ) - { - /* - Multisampling in the window surface might get lost - as a side effect of rendering to the FBO. - weired, needs to be investigated more - */ - painter.setRenderHint( QPainter::Antialiasing, true ); - } -#endif + painter.scale( ratio, ratio ); + helper->paint( &painter, size / ratio ); } QOpenGLFramebufferObjectFormat format2; format2.setAttachment( QOpenGLFramebufferObject::NoAttachment ); - QOpenGLFramebufferObject fbo( width, height, format2 ); + QOpenGLFramebufferObject fbo( size, format2 ); - const QRect fboRect( 0, 0, width, height ); + const QRect fboRect( 0, 0, size.width(), size.height() ); QOpenGLFramebufferObject::blitFramebuffer( &fbo, fboRect, &multisampledFbo, fboRect ); - return fbo.takeTexture(); + window->endExternalCommands(); + + return qskTakeTexture( fbo ); } -static uint qskCreateTextureRaster( QQuickWindow* window, +static QSGTexture* qskCreateTextureRaster( QQuickWindow* window, const QSize& size, QskTextureRenderer::PaintHelper* helper ) { const auto ratio = window ? window->effectiveDevicePixelRatio() : 1.0; @@ -109,130 +194,26 @@ static uint qskCreateTextureRaster( QQuickWindow* window, helper->paint( &painter, size ); } - const auto target = QOpenGLTexture::Target2D; + return window->createTextureFromImage( image, QQuickWindow::TextureHasAlphaChannel ); +} - auto context = QOpenGLContext::currentContext(); - if ( context == nullptr ) - return 0; - - auto& f = *context->functions(); - - GLint oldTexture; // we can't rely on having OpenGL Direct State Access - f.glGetIntegerv( QOpenGLTexture::BindingTarget2D, &oldTexture ); - - GLuint textureId; - f.glGenTextures( 1, &textureId ); - - f.glBindTexture( target, textureId ); - - f.glTexParameteri( target, GL_TEXTURE_MIN_FILTER, QOpenGLTexture::Nearest ); - f.glTexParameteri( target, GL_TEXTURE_MAG_FILTER, QOpenGLTexture::Nearest ); - - f.glTexParameteri( target, GL_TEXTURE_WRAP_S, QOpenGLTexture::ClampToEdge ); - f.glTexParameteri( target, GL_TEXTURE_WRAP_T, QOpenGLTexture::ClampToEdge ); - - if ( QOpenGLTexture::hasFeature( QOpenGLTexture::ImmutableStorage ) ) +QSGTexture* QskTextureRenderer::createPaintedTexture( + QQuickWindow* window, const QSize& size, PaintHelper* helper ) +{ + if ( isOpenGLWindow( window ) ) { - auto& ef = *context->extraFunctions(); - ef.glTexStorage2D( target, 1, - QOpenGLTexture::RGBA8_UNorm, image.width(), image.height() ); + const auto textureId = createPaintedTextureGL( window, size, helper ); - f.glTexSubImage2D( target, 0, 0, 0, image.width(), image.height(), - QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, image.constBits() ); + auto texture = new QSGPlainTexture; + texture->setHasAlphaChannel( true ); + texture->setOwnsTexture( true ); + + setTextureId( window, textureId, size, texture ); + + return texture; } else { - f.glTexImage2D( target, 0, QOpenGLTexture::RGBA8_UNorm, - image.width(), image.height(), 0, - QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, image.constBits() ); - } - - f.glBindTexture( target, oldTexture ); - - return textureId; -} - -QSGTexture* QskTextureRenderer::textureFromId( - QQuickWindow* window, uint textureId, const QSize& size ) -{ - const auto flags = static_cast< QQuickWindow::CreateTextureOptions >( - QQuickWindow::TextureHasAlphaChannel | QQuickWindow::TextureOwnsGLTexture ); - - QSGTexture* texture; - -#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) - - texture = QNativeInterface::QSGOpenGLTexture::fromNative( - textureId, window, size, flags ); - -#else - - const int nativeLayout = 0; // VkImageLayout in case of Vulkan - - texture = window->createTextureFromNativeObject( - QQuickWindow::NativeObjectTexture, &textureId, nativeLayout, size, flags ); -#endif - - return texture; -} - -uint QskTextureRenderer::createTexture( - QQuickWindow* window, RenderMode renderMode, - const QSize& size, PaintHelper* helper ) -{ -#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) - // Qt6.0.0 is buggy when using FBOs. So let's disable it for the moment TODO ... - renderMode = Raster; -#endif - - if ( renderMode != Raster ) - { - if ( !qskHasOpenGLRenderer( window ) ) - renderMode = Raster; - } - - if ( renderMode == AutoDetect ) - { - if ( qskSetup->testItemUpdateFlag( QskQuickItem::PreferRasterForTextures ) ) - renderMode = Raster; - else - renderMode = OpenGL; - } - - if ( renderMode == Raster ) return qskCreateTextureRaster( window, size, helper ); - else - return qskCreateTextureOpenGL( window, size, helper ); -} - -uint QskTextureRenderer::createTextureFromGraphic( - QQuickWindow* window, RenderMode renderMode, const QSize& size, - const QskGraphic& graphic, const QskColorFilter& colorFilter, - Qt::AspectRatioMode aspectRatioMode ) -{ - class PaintHelper : public QskTextureRenderer::PaintHelper - { - public: - PaintHelper( const QskGraphic& graphic, - const QskColorFilter& filter, Qt::AspectRatioMode aspectRatioMode ) - : m_graphic( graphic ) - , m_filter( filter ) - , m_aspectRatioMode( aspectRatioMode ) - { - } - - void paint( QPainter* painter, const QSize& size ) override - { - const QRect rect( 0, 0, size.width(), size.height() ); - m_graphic.render( painter, rect, m_filter, m_aspectRatioMode ); - } - - private: - const QskGraphic& m_graphic; - const QskColorFilter& m_filter; - const Qt::AspectRatioMode m_aspectRatioMode; - }; - - PaintHelper helper( graphic, colorFilter, aspectRatioMode ); - return createTexture( window, renderMode, size, &helper ); + } } diff --git a/src/nodes/QskTextureRenderer.h b/src/nodes/QskTextureRenderer.h index 4e83b6fd..afdec3ba 100644 --- a/src/nodes/QskTextureRenderer.h +++ b/src/nodes/QskTextureRenderer.h @@ -7,35 +7,15 @@ #define QSK_TEXTURE_RENDERER_H #include "QskGlobal.h" -#include -class QskGraphic; -class QskColorFilter; - -class QPainter; class QSize; +class QPainter; class QSGTexture; class QQuickWindow; namespace QskTextureRenderer { - /* - Raster usually provides a better antialiasing and is less buggy, - while OpenGL might be faster - depending on the content that has - to be painted. - - Since Qt 5.10 X11 is back and could be an interesting option - with good quality and hardware accelerated performance. TODO ... - */ - enum RenderMode - { - AutoDetect, // depends on QskSetup::controlFlags() - - Raster, - OpenGL - }; - - class QSK_EXPORT PaintHelper + class PaintHelper { public: PaintHelper() = default; @@ -47,15 +27,16 @@ namespace QskTextureRenderer Q_DISABLE_COPY( PaintHelper ) }; - QSK_EXPORT uint createTexture( - QQuickWindow*, RenderMode, const QSize&, PaintHelper* ); + bool isOpenGLWindow( const QQuickWindow* ); - QSK_EXPORT uint createTextureFromGraphic( - QQuickWindow*, RenderMode, const QSize&, const QskGraphic&, - const QskColorFilter&, Qt::AspectRatioMode ); + void setTextureId( QQuickWindow*, + quint32 textureId, const QSize&, QSGTexture* ); - QSK_EXPORT QSGTexture* textureFromId( - QQuickWindow*, uint textureId, const QSize& ); + quint32 createPaintedTextureGL( + QQuickWindow*, const QSize&, QskTextureRenderer::PaintHelper* ); + + QSK_EXPORT QSGTexture* createPaintedTexture( + QQuickWindow* window, const QSize& size, PaintHelper* helper ); } #endif