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 \
buttons \
frames \
gbenchmark \
glabels \
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 )
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;
}

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -5,65 +5,17 @@
#include "QskPaintedNode.h"
#include "QskSGNode.h"
#include "QskTextureRenderer.h"
#include <qsgimagenode.h>
#include <qquickwindow.h>
#include <qimage.h>
#include <qpainter.h>
#include <qopenglframebufferobject.h>
#include <qopenglpaintdevice.h>
#include <qopenglfunctions.h>
QSK_QT_PRIVATE_BEGIN
#include <private/qsgplaintexture_p.h>
#include <private/qquickwindow_p.h>
#include <private/qopenglframebufferobject_p.h>
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
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,90 +113,55 @@ 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;
}
if ( !isDirty )
isDirty = ( imageNode == nullptr ) || ( imageNode->rect() != rect );
if ( isDirty )
{
if ( ( m_renderHint == OpenGL ) && qskHasOpenGLRenderer( window ) )
updateImageNodeGL( window, rect, nodeData );
else
updateImageNode( window, rect, nodeData );
{
isTextureDirty = ( imageSize != textureSize() );
}
imageNode = findImageNode( this );
if ( imageNode )
{
if ( isTextureDirty )
updateTexture( window, imageSize, nodeData );
imageNode->setRect( rect );
imageNode->setTextureCoordinatesTransform(
qskEffectiveTransformMode( m_mirrored ) );
}
}
void QskPaintedNode::updateImageNode(
QQuickWindow* window, const QRectF& rect, const void* nodeData )
void QskPaintedNode::updateTexture( 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 );
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 )
if ( ( m_renderHint == OpenGL ) && QskTextureRenderer::isOpenGLWindow( window ) )
{
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 );
}
const auto textureId = createTextureGL( window, size, nodeData );
auto texture = qobject_cast< QSGPlainTexture* >( imageNode->texture() );
if ( texture == nullptr )
@ -254,91 +173,63 @@ void QskPaintedNode::updateImageNodeGL(
imageNode->setTexture( texture );
}
/*
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 ...
*/
const auto textureId = createTexture( window, size, nodeData );
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 );
QskTextureRenderer::setTextureId( window, textureId, size, texture );
}
else
{
texture->setTextureId( textureId );
texture->setTextureSize( size );
const auto image = createImage( window, size, nodeData );
if ( auto texture = qobject_cast< QSGPlainTexture* >( imageNode->texture() ) )
texture->setImage( image );
else
imageNode->setTexture( window->createTextureFromImage( image ) );
}
#endif
}
uint32_t QskPaintedNode::createTexture(
QQuickWindow* window, const QSize& size, const void* nodeData )
QImage QskPaintedNode::createImage( QQuickWindow* window,
const QSize& size, const void* nodeData )
{
QImage image( size, QImage::Format_RGBA8888_Premultiplied );
image.fill( Qt::transparent );
QPainter painter( &image );
/*
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.
setting a devicePixelRatio for the image only works for
value >= 1.0. So we have to scale manually.
*/
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();
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 / ratio, nodeData );
paint( &painter, size, nodeData );
painter.end();
return image;
}
quint32 QskPaintedNode::createTextureGL(
QQuickWindow* window, const QSize& size, const void* nodeData )
{
class PaintHelper : public QskTextureRenderer::PaintHelper
{
public:
PaintHelper( QskPaintedNode* node, const void* nodeData )
: m_node( node )
, m_nodeData( nodeData )
{
}
QOpenGLFramebufferObjectFormat format2;
format2.setAttachment( QOpenGLFramebufferObject::NoAttachment );
void paint( QPainter* painter, const QSize& size ) override
{
m_node->paint( painter, size, m_nodeData );
}
QOpenGLFramebufferObject fbo( size, format2 );
private:
QskPaintedNode* m_node;
const void* m_nodeData;
};
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 );
}

View File

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

View File

@ -4,92 +4,177 @@
*****************************************************************************/
#include "QskTextureRenderer.h"
#include "QskColorFilter.h"
#include "QskGraphic.h"
#include "QskSetup.h"
#include <qopenglcontext.h>
#include <qopenglextrafunctions.h>
#include <qopenglframebufferobject.h>
#include <qopenglfunctions.h>
#include <qopenglpaintdevice.h>
#include <qopengltexture.h>
#include <qimage.h>
#include <qpainter.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 )
#include <qsgtexture_platform.h>
#include <qquickopenglutils.h>
#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;
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 ) )
{
auto& ef = *context->extraFunctions();
ef.glTexStorage2D( target, 1,
QOpenGLTexture::RGBA8_UNorm, image.width(), image.height() );
f.glTexSubImage2D( target, 0, 0, 0, image.width(), image.height(),
QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, image.constBits() );
}
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;
return window->createTextureFromImage( image, QQuickWindow::TextureHasAlphaChannel );
}
QSGTexture* QskTextureRenderer::textureFromId(
QQuickWindow* window, uint textureId, const QSize& size )
QSGTexture* QskTextureRenderer::createPaintedTexture(
QQuickWindow* window, const QSize& size, PaintHelper* helper )
{
const auto flags = static_cast< QQuickWindow::CreateTextureOptions >(
QQuickWindow::TextureHasAlphaChannel | QQuickWindow::TextureOwnsGLTexture );
if ( isOpenGLWindow( window ) )
{
const auto textureId = createPaintedTextureGL( window, size, helper );
QSGTexture* texture;
auto texture = new QSGPlainTexture;
texture->setHasAlphaChannel( true );
texture->setOwnsTexture( true );
#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
setTextureId( window, textureId, size, texture );
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 );
}

View File

@ -7,35 +7,15 @@
#define QSK_TEXTURE_RENDERER_H
#include "QskGlobal.h"
#include <qnamespace.h>
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