default graphic pipeline without native OpenGL calls.

code is QRHI compiant now
This commit is contained in:
Uwe Rathmann 2022-06-02 16:02:42 +02:00
parent 5dc4200cdc
commit 54b55c0324
15 changed files with 338 additions and 748 deletions

View File

@ -26,6 +26,5 @@ SUBDIRS += \
boxes \ boxes \
buttons \ buttons \
frames \ frames \
gbenchmark \
glabels \ glabels \
messageboxQml messageboxQml

View File

@ -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 <QskGraphic.h>
#include <QskGraphicIO.h>
#include <QskColorFilter.h>
#include <QskTextureRenderer.h>
#include <QDebug>
#include <QDir>
#include <QElapsedTimer>
#include <QPainter>
#include <QQuickWindow>
#include <QStringList>
#include <QSvgRenderer>
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;
}

View File

@ -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 );
}

View File

@ -1,10 +0,0 @@
CONFIG += qskexample
QT += svg
HEADERS += \
Benchmark.h
SOURCES += \
Benchmark.cpp \
main.cpp

View File

@ -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 <QskLinearBox.h>
#include <QskPushButton.h>
#include <QskWindow.h>
#include <QCommandLineParser>
#include <QGuiApplication>
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();
}

View File

@ -113,18 +113,17 @@ static inline QSGNode* qskUpdateGraphicNode(
if ( control == nullptr ) if ( control == nullptr )
return nullptr; return nullptr;
auto mode = QskTextureRenderer::OpenGL;
auto graphicNode = static_cast< QskGraphicNode* >( node ); auto graphicNode = static_cast< QskGraphicNode* >( node );
if ( graphicNode == nullptr ) if ( graphicNode == nullptr )
graphicNode = new QskGraphicNode(); graphicNode = new QskGraphicNode();
if ( control->testUpdateFlag( QskControl::PreferRasterForTextures ) ) const bool useRaster = control->testUpdateFlag( QskControl::PreferRasterForTextures );
mode = QskTextureRenderer::Raster; graphicNode->setRenderHint( useRaster ? QskPaintedNode::Raster : QskPaintedNode::OpenGL );
graphicNode->setMirrored( mirrored );
const auto r = qskSceneAlignedRect( control, rect ); const auto r = qskSceneAlignedRect( control, rect );
graphicNode->setGraphic( control->window(), graphic, graphicNode->setGraphic( control->window(), graphic, colorFilter, r );
colorFilter, mode, r, mirrored );
return graphicNode; return graphicNode;
} }

View File

@ -54,16 +54,30 @@ QSize QskGraphicTextureFactory::size() const
return m_size; return m_size;
} }
QSGTexture* QskGraphicTextureFactory::createTexture( QQuickWindow* window ) const 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( void paint( QPainter* painter, const QSize& size ) override
window, QskTextureRenderer::OpenGL, m_size, m_graphic, m_colorFilter, {
Qt::IgnoreAspectRatio ); 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 QSize QskGraphicTextureFactory::textureSize() const

View File

@ -30,10 +30,10 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& metrics,
const QskGradient& gradient, QQuickWindow* window ) const QskGradient& gradient, QQuickWindow* window )
{ {
const ArcData arcData { metrics, gradient }; 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 ); const auto arcData = reinterpret_cast< const ArcData* >( nodeData );

View File

@ -23,7 +23,7 @@ class QSK_EXPORT QskArcNode : public QskPaintedNode
const QskGradient&, QQuickWindow* ); const QskGradient&, QQuickWindow* );
protected: 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; QskHashValue hash( const void* nodeData ) const override;
}; };

View File

@ -8,27 +8,17 @@
#include "QskColorFilter.h" #include "QskColorFilter.h"
#include "QskPainterCommand.h" #include "QskPainterCommand.h"
static inline QskHashValue qskHash( namespace
const QskGraphic& graphic, const QskColorFilter& colorFilter,
QskTextureRenderer::RenderMode renderMode )
{ {
QskHashValue hash = 12000; class GraphicData
const auto& substitutions = colorFilter.substitutions();
if ( substitutions.size() > 0 )
{ {
hash = qHashBits( substitutions.constData(), public:
substitutions.size() * sizeof( substitutions[ 0 ] ), hash ); const QskGraphic& graphic;
} const QskColorFilter& colorFilter;
};
hash = graphic.hash( hash );
hash = qHash( renderMode, hash );
return hash;
} }
QskGraphicNode::QskGraphicNode() QskGraphicNode::QskGraphicNode()
: m_hash( 0 )
{ {
} }
@ -36,14 +26,10 @@ QskGraphicNode::~QskGraphicNode()
{ {
} }
void QskGraphicNode::setGraphic( void QskGraphicNode::setGraphic( QQuickWindow* window, const QskGraphic& graphic,
QQuickWindow* window, const QskGraphic& graphic, const QskColorFilter& colorFilter, const QskColorFilter& colorFilter, const QRectF& rect )
QskTextureRenderer::RenderMode renderMode, const QRectF& rect,
Qt::Orientations mirrored )
{ {
bool isTextureDirty = isNull(); QSizeF size;
QSize textureSize;
if ( graphic.commandTypes() == QskGraphic::RasterData ) if ( graphic.commandTypes() == QskGraphic::RasterData )
{ {
@ -52,34 +38,43 @@ void QskGraphicNode::setGraphic(
There is no benefit in rescaling it into the target rectangle There is no benefit in rescaling it into the target rectangle
by the CPU and creating a new texture. by the CPU and creating a new texture.
*/ */
textureSize = graphic.defaultSize().toSize(); size = graphic.defaultSize();
}
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() ) );
}
} }
const auto hash = qskHash( graphic, colorFilter, renderMode ); const GraphicData graphicData { graphic, colorFilter };
if ( hash != m_hash ) update( window, rect, size, &graphicData );
{ }
m_hash = hash;
isTextureDirty = true; void QskGraphicNode::paint( QPainter* painter, const QSize& size, const void* nodeData )
} {
const auto graphicData = reinterpret_cast< const GraphicData* >( nodeData );
auto textureId = QskTextureNode::textureId();
const auto& graphic = graphicData->graphic;
if ( isTextureDirty ) const auto& colorFilter = graphicData->colorFilter;
{
textureId = QskTextureRenderer::createTextureFromGraphic( if ( graphic.commandTypes() == QskGraphic::RasterData )
window, renderMode, textureSize, graphic, colorFilter, Qt::IgnoreAspectRatio ); {
} qDebug() << size;
}
QskTextureNode::setTexture( window, rect, textureId, mirrored );
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 );
} }

View File

@ -6,29 +6,23 @@
#ifndef QSK_GRAPHIC_NODE_H #ifndef QSK_GRAPHIC_NODE_H
#define QSK_GRAPHIC_NODE_H #define QSK_GRAPHIC_NODE_H
#include "QskTextureRenderer.h" #include "QskPaintedNode.h"
#include "QskTextureNode.h"
class QskGraphic; class QskGraphic;
class QskColorFilter; class QskColorFilter;
class QQuickWindow;
class QSK_EXPORT QskGraphicNode : public QskTextureNode class QSK_EXPORT QskGraphicNode : public QskPaintedNode
{ {
public: public:
QskGraphicNode(); QskGraphicNode();
~QskGraphicNode() override; ~QskGraphicNode() override;
void setGraphic( QQuickWindow*, void setGraphic( QQuickWindow*, const QskGraphic&,
const QskGraphic&, const QskColorFilter&, const QskColorFilter&, const QRectF& );
QskTextureRenderer::RenderMode, const QRectF&,
Qt::Orientations mirrored = Qt::Orientations() );
private: private:
void setTexture( QQuickWindow*, virtual void paint( QPainter*, const QSize&, const void* nodeData ) override;
const QRectF&, uint id, Qt::Orientations ) = delete; virtual QskHashValue hash( const void* nodeData ) const override;
QskHashValue m_hash;
}; };
#endif #endif

View File

@ -5,65 +5,17 @@
#include "QskPaintedNode.h" #include "QskPaintedNode.h"
#include "QskSGNode.h" #include "QskSGNode.h"
#include "QskTextureRenderer.h"
#include <qsgimagenode.h> #include <qsgimagenode.h>
#include <qquickwindow.h> #include <qquickwindow.h>
#include <qimage.h> #include <qimage.h>
#include <qpainter.h> #include <qpainter.h>
#include <qopenglframebufferobject.h>
#include <qopenglpaintdevice.h>
#include <qopenglfunctions.h>
QSK_QT_PRIVATE_BEGIN QSK_QT_PRIVATE_BEGIN
#include <private/qsgplaintexture_p.h> #include <private/qsgplaintexture_p.h>
#include <private/qquickwindow_p.h>
#include <private/qopenglframebufferobject_p.h>
QSK_QT_PRIVATE_END QSK_QT_PRIVATE_END
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
#include <qquickopenglutils.h>
#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 static inline QSGImageNode::TextureCoordinatesTransformMode
qskEffectiveTransformMode( const Qt::Orientations mirrored ) qskEffectiveTransformMode( const Qt::Orientations mirrored )
{ {
@ -89,15 +41,6 @@ namespace
return static_cast< QSGImageNode* >( node ); 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() QskPaintedNode::QskPaintedNode()
@ -137,6 +80,17 @@ Qt::Orientations QskPaintedNode::mirrored() const
return m_mirrored; 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 QRectF QskPaintedNode::rect() const
{ {
const auto imageNode = findImageNode( this ); const auto imageNode = findImageNode( this );
@ -144,7 +98,7 @@ QRectF QskPaintedNode::rect() const
} }
void QskPaintedNode::update( QQuickWindow* window, void QskPaintedNode::update( QQuickWindow* window,
const QRectF& rect, const void* nodeData ) const QRectF& rect, const QSizeF& size, const void* nodeData )
{ {
auto imageNode = findImageNode( this ); auto imageNode = findImageNode( this );
@ -159,186 +113,123 @@ void QskPaintedNode::update( QQuickWindow* window,
return; 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 ); const auto newHash = hash( nodeData );
if ( ( newHash == 0 ) || ( newHash != m_hash ) ) if ( ( newHash == 0 ) || ( newHash != m_hash ) )
{ {
m_hash = newHash; 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 ) ) const auto textureId = createTextureGL( window, size, nodeData );
updateImageNodeGL( window, rect, 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 else
updateImageNode( window, rect, nodeData ); imageNode->setTexture( window->createTextureFromImage( image ) );
}
imageNode = findImageNode( this );
if ( imageNode )
{
imageNode->setRect( rect );
imageNode->setTextureCoordinatesTransform(
qskEffectiveTransformMode( m_mirrored ) );
} }
} }
void QskPaintedNode::updateImageNode( QImage QskPaintedNode::createImage( QQuickWindow* window,
QQuickWindow* window, const QRectF& rect, const void* nodeData ) const QSize& size, const void* nodeData )
{ {
const auto ratio = window->effectiveDevicePixelRatio(); QImage image( size, QImage::Format_RGBA8888_Premultiplied );
const auto size = rect.size() * ratio;
QImage image( size.toSize(), QImage::Format_RGBA8888_Premultiplied );
image.fill( Qt::transparent ); image.fill( Qt::transparent );
{ QPainter painter( &image );
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 );
}
/* /*
QQuickFramebufferObject does the FBO rendering early setting a devicePixelRatio for the image only works for
( QQuickWindow::beforeRendering ). However doing it below updatePaintNode value >= 1.0. So we have to scale manually.
seems to work as well. Let's see if we run into issues ...
*/ */
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 ) painter.end();
texture->setTextureFromNativeTexture(
rhi, quint64( textureId ), 0, size, {}, {} ); return image;
#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
} }
uint32_t QskPaintedNode::createTexture( quint32 QskPaintedNode::createTextureGL(
QQuickWindow* window, const QSize& size, const void* nodeData ) QQuickWindow* window, const QSize& size, const void* nodeData )
{ {
/* class PaintHelper : public QskTextureRenderer::PaintHelper
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 );
{ {
const auto ratio = window->effectiveDevicePixelRatio(); public:
PaintHelper( QskPaintedNode* node, const void* nodeData )
: m_node( node )
, m_nodeData( nodeData )
{
}
QPainter painter( &pd ); void paint( QPainter* painter, const QSize& size ) override
painter.scale( ratio, ratio ); {
m_node->paint( painter, size, m_nodeData );
}
painter.setCompositionMode( QPainter::CompositionMode_Source ); private:
painter.fillRect( 0, 0, size.width(), size.height(), Qt::transparent ); QskPaintedNode* m_node;
painter.setCompositionMode( QPainter::CompositionMode_SourceOver ); const void* m_nodeData;
};
paint( &painter, size, nodeData ); PaintHelper helper( this, nodeData );
} return createPaintedTextureGL( window, size, &helper );
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 );
} }

View File

@ -11,6 +11,7 @@
class QQuickWindow; class QQuickWindow;
class QPainter; class QPainter;
class QImage;
class QSK_EXPORT QskPaintedNode : public QSGNode class QSK_EXPORT QskPaintedNode : public QSGNode
{ {
@ -42,20 +43,21 @@ class QSK_EXPORT QskPaintedNode : public QSGNode
Qt::Orientations mirrored() const; Qt::Orientations mirrored() const;
QRectF rect() const; QRectF rect() const;
QSize textureSize() const;
virtual void paint( QPainter*, const QSize&, const void* nodeData ) = 0;
protected: protected:
void update( QQuickWindow*, const QRectF&, const void* nodeData ); void update( QQuickWindow*, const QRectF&, const QSizeF&, const void* nodeData );
virtual void paint( QPainter*, const QSizeF&, const void* nodeData ) = 0;
// a hash value of '0' always results in repainting // a hash value of '0' always results in repainting
virtual QskHashValue hash( const void* nodeData ) const = 0; virtual QskHashValue hash( const void* nodeData ) const = 0;
private: private:
void updateImageNode( QQuickWindow*, const QRectF&, const void* nodeData ); void updateTexture( QQuickWindow*, const QSize&, const void* nodeData );
void updateImageNodeGL( QQuickWindow*, const QRectF&, 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; RenderHint m_renderHint = OpenGL;
Qt::Orientations m_mirrored; Qt::Orientations m_mirrored;

View File

@ -4,92 +4,177 @@
*****************************************************************************/ *****************************************************************************/
#include "QskTextureRenderer.h" #include "QskTextureRenderer.h"
#include "QskColorFilter.h"
#include "QskGraphic.h"
#include "QskSetup.h"
#include <qopenglcontext.h> #include <qopenglcontext.h>
#include <qopenglextrafunctions.h>
#include <qopenglframebufferobject.h> #include <qopenglframebufferobject.h>
#include <qopenglfunctions.h>
#include <qopenglpaintdevice.h> #include <qopenglpaintdevice.h>
#include <qopengltexture.h>
#include <qimage.h> #include <qimage.h>
#include <qpainter.h> #include <qpainter.h>
#include <qquickwindow.h> #include <qquickwindow.h>
#include <qsgtexture.h>
QSK_QT_PRIVATE_BEGIN
#include <private/qsgplaintexture_p.h>
#include <private/qopenglframebufferobject_p.h>
QSK_QT_PRIVATE_END
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) #if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
#include <qsgtexture_platform.h> #include <qquickopenglutils.h>
#endif #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 ) if ( window == nullptr )
return false; return false;
const auto renderer = window->rendererInterface(); 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, void QskTextureRenderer::setTextureId( QQuickWindow* window,
const QSize& size, QskTextureRenderer::PaintHelper* helper ) 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(); auto rhi = QQuickWindowPrivate::get( window )->rhi;
const int height = ratio * size.height();
#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; QOpenGLFramebufferObjectFormat format1;
format1.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil ); format1.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil );
// ### TODO: get samples from window instead format1.setSamples( context->format().samples() );
format1.setSamples( QOpenGLContext::currentContext()->format().samples() );
QOpenGLFramebufferObject multisampledFbo( width, height, format1 ); QOpenGLFramebufferObject multisampledFbo( size, format1 );
QOpenGLPaintDevice pd( width, height ); QOpenGLPaintDevice pd( size );
pd.setPaintFlipped( true ); pd.setPaintFlipped( true );
{ {
QPainter painter( &pd ); QPainter painter( &pd );
painter.scale( ratio, ratio );
painter.setCompositionMode( QPainter::CompositionMode_Source ); 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 ); painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
helper->paint( &painter, size ); const auto ratio = window->effectiveDevicePixelRatio();
#if 1 painter.scale( ratio, ratio );
if ( format1.samples() > 0 ) helper->paint( &painter, size / ratio );
{
/*
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
} }
QOpenGLFramebufferObjectFormat format2; QOpenGLFramebufferObjectFormat format2;
format2.setAttachment( QOpenGLFramebufferObject::NoAttachment ); 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( QOpenGLFramebufferObject::blitFramebuffer(
&fbo, fboRect, &multisampledFbo, fboRect ); &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 QSize& size, QskTextureRenderer::PaintHelper* helper )
{ {
const auto ratio = window ? window->effectiveDevicePixelRatio() : 1.0; const auto ratio = window ? window->effectiveDevicePixelRatio() : 1.0;
@ -109,130 +194,26 @@ static uint qskCreateTextureRaster( QQuickWindow* window,
helper->paint( &painter, size ); helper->paint( &painter, size );
} }
const auto target = QOpenGLTexture::Target2D; return window->createTextureFromImage( image, QQuickWindow::TextureHasAlphaChannel );
}
auto context = QOpenGLContext::currentContext(); QSGTexture* QskTextureRenderer::createPaintedTexture(
if ( context == nullptr ) QQuickWindow* window, const QSize& size, PaintHelper* helper )
return 0; {
if ( isOpenGLWindow( window ) )
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 ) )
{ {
auto& ef = *context->extraFunctions(); const auto textureId = createPaintedTextureGL( window, size, helper );
ef.glTexStorage2D( target, 1,
QOpenGLTexture::RGBA8_UNorm, image.width(), image.height() );
f.glTexSubImage2D( target, 0, 0, 0, image.width(), image.height(), auto texture = new QSGPlainTexture;
QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, image.constBits() ); texture->setHasAlphaChannel( true );
texture->setOwnsTexture( true );
setTextureId( window, textureId, size, texture );
return texture;
} }
else 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 ); 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 );
} }

View File

@ -7,35 +7,15 @@
#define QSK_TEXTURE_RENDERER_H #define QSK_TEXTURE_RENDERER_H
#include "QskGlobal.h" #include "QskGlobal.h"
#include <qnamespace.h>
class QskGraphic;
class QskColorFilter;
class QPainter;
class QSize; class QSize;
class QPainter;
class QSGTexture; class QSGTexture;
class QQuickWindow; class QQuickWindow;
namespace QskTextureRenderer namespace QskTextureRenderer
{ {
/* class PaintHelper
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
{ {
public: public:
PaintHelper() = default; PaintHelper() = default;
@ -47,15 +27,16 @@ namespace QskTextureRenderer
Q_DISABLE_COPY( PaintHelper ) Q_DISABLE_COPY( PaintHelper )
}; };
QSK_EXPORT uint createTexture( bool isOpenGLWindow( const QQuickWindow* );
QQuickWindow*, RenderMode, const QSize&, PaintHelper* );
QSK_EXPORT uint createTextureFromGraphic( void setTextureId( QQuickWindow*,
QQuickWindow*, RenderMode, const QSize&, const QskGraphic&, quint32 textureId, const QSize&, QSGTexture* );
const QskColorFilter&, Qt::AspectRatioMode );
QSK_EXPORT QSGTexture* textureFromId( quint32 createPaintedTextureGL(
QQuickWindow*, uint textureId, const QSize& ); QQuickWindow*, const QSize&, QskTextureRenderer::PaintHelper* );
QSK_EXPORT QSGTexture* createPaintedTexture(
QQuickWindow* window, const QSize& size, PaintHelper* helper );
} }
#endif #endif