diff --git a/examples/gbenchmark/Benchmark.cpp b/examples/gbenchmark/Benchmark.cpp index 8d9866d6..8c228f35 100644 --- a/examples/gbenchmark/Benchmark.cpp +++ b/examples/gbenchmark/Benchmark.cpp @@ -7,7 +7,8 @@ #include #include -#include +#include +#include #include #include @@ -116,14 +117,15 @@ bool Benchmark::run( const QString& dirName ) timer.start(); - const QRect targetRect( 0, 0, 200, 200 ); + const QSize targetSize( 200, 200 ); const QskColorFilter colorFilter; for ( int i = 0; i < qvgFiles.size(); i++ ) { - const auto textureId = QskGraphicTextureFactory::createTexture( - QskGraphicTextureFactory::OpenGL, targetRect, Qt::KeepAspectRatio, - graphics[ i ], colorFilter ); + using namespace QskTextureRenderer; + + const auto textureId = createTextureFromGraphic( + OpenGL, targetSize, graphics[ i ], colorFilter ); if ( textureId == 0 ) { @@ -140,14 +142,15 @@ bool Benchmark::run( const QString& dirName ) timer.start(); - const QRect targetRect( 0, 0, 100, 100 ); + const QSize targetSize( 200, 200 ); const QskColorFilter colorFilter; for ( int i = 0; i < qvgFiles.size(); i++ ) { - const auto textureId = QskGraphicTextureFactory::createTexture( - QskGraphicTextureFactory::Raster, targetRect, Qt::KeepAspectRatio, - graphics[ i ], colorFilter ); + using namespace QskTextureRenderer; + + const auto textureId = createTextureFromGraphic( + Raster, targetSize, graphics[ i ], colorFilter ); if ( textureId == 0 ) { diff --git a/src/controls/QskSkinlet.cpp b/src/controls/QskSkinlet.cpp index 128800bc..b3a9451e 100644 --- a/src/controls/QskSkinlet.cpp +++ b/src/controls/QskSkinlet.cpp @@ -76,11 +76,11 @@ static inline QSGNode* qskUpdateGraphicNode( if ( rect.isEmpty() ) return nullptr; - auto mode = QskGraphicTextureFactory::OpenGL; + auto mode = QskTextureRenderer::OpenGL; const auto control = skinnable->owningControl(); if ( control && control->testControlFlag( QskControl::PreferRasterForTextures ) ) - mode = QskGraphicTextureFactory::Raster; + mode = QskTextureRenderer::Raster; auto graphicNode = static_cast< QskGraphicNode* >( node ); if ( graphicNode == nullptr ) diff --git a/src/graphic/QskGraphicTextureFactory.cpp b/src/graphic/QskGraphicTextureFactory.cpp index c0adac6e..f95d65da 100644 --- a/src/graphic/QskGraphicTextureFactory.cpp +++ b/src/graphic/QskGraphicTextureFactory.cpp @@ -4,120 +4,10 @@ *****************************************************************************/ #include "QskGraphicTextureFactory.h" +#include "QskTextureRenderer.h" -#include -#include -#include -#include -#include -#include - -#include #include -static uint qskTextureFBO( - const QRect& rect, Qt::AspectRatioMode scalingMode, - const QskGraphic& graphic, const QskColorFilter& filter ) -{ - QOpenGLFramebufferObjectFormat format1; - format1.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil ); - // ### TODO: get samples from window instead - format1.setSamples( QOpenGLContext::currentContext()->format().samples() ); - - const QRect sourceRect( QPoint(), rect.size() ); - - QOpenGLFramebufferObject multisampledFbo( sourceRect.size(), format1 ); - - QOpenGLPaintDevice pd( sourceRect.size() ); - - QPainter painter( &pd ); - - graphic.render( &painter, sourceRect, filter, scalingMode ); - -#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.end(); - - QOpenGLFramebufferObjectFormat format2; - format2.setAttachment( QOpenGLFramebufferObject::NoAttachment ); - - QOpenGLFramebufferObject fbo( sourceRect.size(), format2 ); - - // mirror vertically to be compliant with what QQuickTextureFactory expects. - // for some reason we have to add 1 pixel to avoid that the mirrored - // image gets cut off. - - const QRect targetRect( sourceRect.x(), sourceRect.bottom() + 1, - sourceRect.width(), -sourceRect.height() ); - - QOpenGLFramebufferObject::blitFramebuffer( - &fbo, sourceRect, &multisampledFbo, targetRect ); - - return fbo.takeTexture(); -} - -static uint qskTextureRaster( - const QRect& rect, Qt::AspectRatioMode scalingMode, - const QskGraphic& graphic, const QskColorFilter& filter ) -{ - QImage image( rect.size(), QImage::Format_RGBA8888_Premultiplied ); - image.fill( Qt::transparent ); - - { - QPainter painter( &image ); - graphic.render( &painter, rect, filter, scalingMode ); - } - - 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; -} - QskGraphicTextureFactory::QskGraphicTextureFactory() { } @@ -135,10 +25,8 @@ QskGraphicTextureFactory::~QskGraphicTextureFactory() QSGTexture* QskGraphicTextureFactory::createTexture( QQuickWindow* window ) const { - const QRect textureRect( 0, 0, m_size.width(), m_size.height() ); - - const uint textureId = createTexture( QskGraphicTextureFactory::OpenGL, - textureRect, Qt::KeepAspectRatio, m_graphic, m_colorFilter ); + const uint textureId = QskTextureRenderer::createTextureFromGraphic( + QskTextureRenderer::OpenGL, m_size, m_graphic, m_colorFilter ); const auto flags = static_cast< QQuickWindow::CreateTextureOptions >( QQuickWindow::TextureHasAlphaChannel | QQuickWindow::TextureOwnsGLTexture ); @@ -160,14 +48,3 @@ QImage QskGraphicTextureFactory::image() const { return m_graphic.toImage( m_size, Qt::KeepAspectRatio ); } - -// ### TODO: get the FBO samples from the window -uint QskGraphicTextureFactory::createTexture( - RenderMode mode, const QRect& rect, Qt::AspectRatioMode scalingMode, - const QskGraphic& graphic, const QskColorFilter& filter ) -{ - if ( mode == QskGraphicTextureFactory::Raster ) - return qskTextureRaster( rect, scalingMode, graphic, filter ); - else - return qskTextureFBO( rect, scalingMode, graphic, filter ); -} diff --git a/src/graphic/QskGraphicTextureFactory.h b/src/graphic/QskGraphicTextureFactory.h index 4e79dc1a..284b01c7 100644 --- a/src/graphic/QskGraphicTextureFactory.h +++ b/src/graphic/QskGraphicTextureFactory.h @@ -8,17 +8,12 @@ #include "QskColorFilter.h" #include "QskGraphic.h" + #include class QSK_EXPORT QskGraphicTextureFactory : public QQuickTextureFactory { public: - enum RenderMode - { - Raster, - OpenGL - }; - QskGraphicTextureFactory(); QskGraphicTextureFactory( const QskGraphic&, const QSize& size ); @@ -39,9 +34,6 @@ class QSK_EXPORT QskGraphicTextureFactory : public QQuickTextureFactory QImage image() const override; - static uint createTexture( RenderMode, const QRect& rect, - Qt::AspectRatioMode, const QskGraphic&, const QskColorFilter& ); - private: QskGraphic m_graphic; QskColorFilter m_colorFilter; diff --git a/src/nodes/QskGraphicNode.cpp b/src/nodes/QskGraphicNode.cpp index bbf29f3d..272ba40a 100644 --- a/src/nodes/QskGraphicNode.cpp +++ b/src/nodes/QskGraphicNode.cpp @@ -4,10 +4,12 @@ *****************************************************************************/ #include "QskGraphicNode.h" +#include "QskGraphic.h" +#include "QskColorFilter.h" static inline uint qskHash( const QskGraphic& graphic, const QskColorFilter& colorFilter, - QskGraphicTextureFactory::RenderMode renderMode ) + QskTextureRenderer::RenderMode renderMode ) { uint hash = 0; @@ -45,7 +47,7 @@ QskGraphicNode::~QskGraphicNode() void QskGraphicNode::setGraphic( const QskGraphic& graphic, const QskColorFilter& colorFilter, - QskGraphicTextureFactory::RenderMode renderMode, const QRect& rect ) + QskTextureRenderer::RenderMode renderMode, const QRect& rect ) { bool isTextureDirty = ( QskTextureNode::textureId() == 0 ); @@ -67,10 +69,8 @@ void QskGraphicNode::setGraphic( if ( isTextureDirty ) { - const QRect textureRect( 0, 0, rect.width(), rect.height() ); - - uint textureId = QskGraphicTextureFactory::createTexture( - renderMode, textureRect, Qt::IgnoreAspectRatio, graphic, colorFilter ); + const uint textureId = QskTextureRenderer::createTextureFromGraphic( + renderMode, rect.size(), graphic, colorFilter ); QskTextureNode::setTextureId( textureId ); } diff --git a/src/nodes/QskGraphicNode.h b/src/nodes/QskGraphicNode.h index 2519f87a..e039d5be 100644 --- a/src/nodes/QskGraphicNode.h +++ b/src/nodes/QskGraphicNode.h @@ -6,7 +6,7 @@ #ifndef QSK_GRAPHIC_NODE_H #define QSK_GRAPHIC_NODE_H -#include "QskGraphicTextureFactory.h" +#include "QskTextureRenderer.h" #include "QskTextureNode.h" class QskGraphic; @@ -19,7 +19,7 @@ class QSK_EXPORT QskGraphicNode : public QskTextureNode ~QskGraphicNode() override; void setGraphic( const QskGraphic&, const QskColorFilter&, - QskGraphicTextureFactory::RenderMode, const QRect& ); + QskTextureRenderer::RenderMode, const QRect& ); private: void setTextureId( int ) = delete; diff --git a/src/nodes/QskPaintedNode.cpp b/src/nodes/QskPaintedNode.cpp new file mode 100644 index 00000000..c1ea7b83 --- /dev/null +++ b/src/nodes/QskPaintedNode.cpp @@ -0,0 +1,63 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskPaintedNode.h" +#include "QskTextureRenderer.h" + +class QskPaintedNode::PaintHelper : public QskTextureRenderer::PaintHelper +{ + public: + inline PaintHelper( QskPaintedNode* node ) + : m_node( node ) + { + } + + void paint( QPainter* painter, const QSize& size ) override + { + m_node->paint( painter, size ); + } + + private: + QskPaintedNode* m_node; +}; + +QskPaintedNode::QskPaintedNode() +{ +} + +QskPaintedNode::~QskPaintedNode() +{ +} + +void QskPaintedNode::update( + QskTextureRenderer::RenderMode renderMode, const QRect& rect ) +{ + bool isTextureDirty = ( QskTextureNode::textureId() == 0 ); + + if ( !isTextureDirty ) + { + const auto oldRect = QskTextureNode::rect(); + isTextureDirty = ( rect.width() != static_cast< int >( oldRect.width() ) ) || + ( rect.height() != static_cast< int >( oldRect.height() ) ); + } + + QskTextureNode::setRect( rect ); + + const uint newHash = hash(); + if ( ( newHash == 0 ) || ( newHash != m_hash ) ) + { + m_hash = newHash; + isTextureDirty = true; + } + + if ( isTextureDirty ) + { + PaintHelper helper( this ); + const uint textureId = + QskTextureRenderer::createTexture( renderMode, rect.size(), &helper ); + + QskTextureNode::setTextureId( textureId ); + } +} diff --git a/src/nodes/QskPaintedNode.h b/src/nodes/QskPaintedNode.h new file mode 100644 index 00000000..410c4742 --- /dev/null +++ b/src/nodes/QskPaintedNode.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_PAINTED_NODE_H +#define QSK_PAINTED_NODE_H + +#include "QskTextureNode.h" +#include "QskTextureRenderer.h" + +class QSK_EXPORT QskPaintedNode : public QskTextureNode +{ + public: + QskPaintedNode(); + ~QskPaintedNode() override; + + void update( QskTextureRenderer::RenderMode, const QRect& ); + + protected: + virtual void paint( QPainter*, const QSizeF& ) = 0; + + // a hash value of '0' always results in repainting + virtual uint hash() = 0; + + private: + class PaintHelper; + + void setTextureId( int ) = delete; + void setRect( const QRectF& ) = delete; + + uint m_hash; +}; + +#endif diff --git a/src/nodes/QskTextureRenderer.cpp b/src/nodes/QskTextureRenderer.cpp new file mode 100644 index 00000000..7b032183 --- /dev/null +++ b/src/nodes/QskTextureRenderer.cpp @@ -0,0 +1,176 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskTextureRenderer.h" +#include "QskColorFilter.h" +#include "QskGraphic.h" +#include "QskSetup.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +static uint qskCreateTextureOpenGL( + const QSize& size, QskTextureRenderer::PaintHelper* helper ) +{ + const int width = size.width(); + const int height = size.height(); + + QOpenGLFramebufferObjectFormat format1; + format1.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil ); + + // ### TODO: get samples from window instead + format1.setSamples( QOpenGLContext::currentContext()->format().samples() ); + + QOpenGLFramebufferObject multisampledFbo( width, height, format1 ); + + QOpenGLPaintDevice pd( width, height ); + + { + QPainter painter( &pd ); + + helper->paint( &painter, size ); + +#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 + } + + QOpenGLFramebufferObjectFormat format2; + format2.setAttachment( QOpenGLFramebufferObject::NoAttachment ); + + QOpenGLFramebufferObject fbo( width, height, format2 ); + + /* + Mirror vertically to be compliant with what QQuickTextureFactory expects. + for some reason we have to add 1 pixel to avoid that the mirrored + image gets cut off. + */ + + const QRect sourceRect( 0, 0, width, height ); + const QRect targetRect( 0, height + 1, width, -height ); + + QOpenGLFramebufferObject::blitFramebuffer( + &fbo, sourceRect, &multisampledFbo, targetRect ); + + return fbo.takeTexture(); +} + +static uint qskCreateTextureRaster( + const QSize& size, QskTextureRenderer::PaintHelper* helper ) +{ + QImage image( size, QImage::Format_RGBA8888_Premultiplied ); + image.fill( Qt::transparent ); + + { + QPainter painter( &image ); + 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; +} + +QskTextureRenderer::PaintHelper::~PaintHelper() +{ +} + +uint QskTextureRenderer::createTexture( + RenderMode renderMode, const QSize& size, PaintHelper* helper ) +{ + if ( renderMode == AutoDetect ) + { + if ( qskSetup->controlFlags() & QskSetup::PreferRasterForTextures ) + renderMode = Raster; + else + renderMode = OpenGL; + } + + if ( renderMode == Raster ) + return qskCreateTextureRaster( size, helper ); + else + return qskCreateTextureOpenGL( size, helper ); +} + +uint QskTextureRenderer::createTextureFromGraphic( + RenderMode renderMode, const QSize& size, + const QskGraphic& graphic, const QskColorFilter& colorFilter ) +{ + class PaintHelper : public QskTextureRenderer::PaintHelper + { + public: + PaintHelper( const QskGraphic& graphic, const QskColorFilter& filter ) + : m_graphic( graphic ) + , m_filter( filter ) + { + } + + void paint( QPainter* painter, const QSize& size ) override + { + const QRect rect( 0, 0, size.width(), size.height() ); + m_graphic.render( painter, rect, m_filter, Qt::KeepAspectRatio ); + } + + private: + const QskGraphic& m_graphic; + const QskColorFilter& m_filter; + }; + + PaintHelper helper( graphic, colorFilter ); + return createTexture( renderMode, size, &helper ); +} diff --git a/src/nodes/QskTextureRenderer.h b/src/nodes/QskTextureRenderer.h new file mode 100644 index 00000000..cc885024 --- /dev/null +++ b/src/nodes/QskTextureRenderer.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_TEXTURE_RENDERER_H +#define QSK_TEXTURE_RENDERER_H + +#include "QskGlobal.h" + +class QskGraphic; +class QskColorFilter; + +class QPainter; +class QSize; + +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 + { + public: + virtual ~PaintHelper(); + virtual void paint( QPainter*, const QSize& ) = 0; + }; + + QSK_EXPORT uint createTexture( + RenderMode, const QSize&, PaintHelper* helper ); + + QSK_EXPORT uint createTextureFromGraphic( + RenderMode, const QSize&, const QskGraphic&, const QskColorFilter& ); +} + +#endif diff --git a/src/src.pro b/src/src.pro index 4ca2b354..9daf76c9 100644 --- a/src/src.pro +++ b/src/src.pro @@ -95,11 +95,13 @@ HEADERS += \ nodes/QskBoxRenderer.h \ nodes/QskBoxRendererColorMap.h \ nodes/QskGraphicNode.h \ + nodes/QskPaintedNode.h \ nodes/QskPlainTextRenderer.h \ nodes/QskRichTextRenderer.h \ - nodes/QskTextRenderer.h \ nodes/QskTextNode.h \ + nodes/QskTextRenderer.h \ nodes/QskTextureNode.h \ + nodes/QskTextureRenderer.h \ nodes/QskVertex.h SOURCES += \ @@ -109,11 +111,13 @@ SOURCES += \ nodes/QskBoxRendererEllipse.cpp \ nodes/QskBoxRendererDEllipse.cpp \ nodes/QskGraphicNode.cpp \ + nodes/QskPaintedNode.cpp \ nodes/QskPlainTextRenderer.cpp \ nodes/QskRichTextRenderer.cpp \ - nodes/QskTextRenderer.cpp \ nodes/QskTextNode.cpp \ + nodes/QskTextRenderer.cpp \ nodes/QskTextureNode.cpp \ + nodes/QskTextureRenderer.cpp \ nodes/QskVertex.cpp HEADERS += \ @@ -295,6 +299,14 @@ SOURCES += \ dialogs/QskSelectionSubWindow.cpp \ dialogs/QskSelectionWindow.cpp +HEADERS += \ + inputpanel/QskTextPredictor.h \ + inputpanel/QskInputContext.h \ + inputpanel/QskInputPanel.h \ + inputpanel/QskInputPanelBox.h \ + inputpanel/QskInputPredictionBar.h \ + inputpanel/QskVirtualKeyboard.h + SOURCES += \ inputpanel/QskTextPredictor.cpp \ inputpanel/QskInputContext.cpp \ @@ -303,10 +315,3 @@ SOURCES += \ inputpanel/QskInputPredictionBar.cpp \ inputpanel/QskVirtualKeyboard.cpp -HEADERS += \ - inputpanel/QskTextPredictor.h \ - inputpanel/QskInputContext.h \ - inputpanel/QskInputPanel.h \ - inputpanel/QskInputPanelBox.h \ - inputpanel/QskInputPredictionBar.h \ - inputpanel/QskVirtualKeyboard.h