2018-10-04 16:15:42 +02:00
|
|
|
/******************************************************************************
|
|
|
|
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
|
|
|
* This file may be used under the terms of the QSkinny License, Version 1.0
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
#include "QskPaintedNode.h"
|
2022-05-31 17:56:49 +02:00
|
|
|
#include "QskSGNode.h"
|
2018-10-04 16:15:42 +02:00
|
|
|
|
2022-05-31 17:56:49 +02:00
|
|
|
#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>
|
2022-06-01 17:50:16 +02:00
|
|
|
#include <private/qopenglframebufferobject_p.h>
|
2022-05-31 17:56:49 +02:00
|
|
|
QSK_QT_PRIVATE_END
|
|
|
|
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
|
2022-06-01 16:57:57 +02:00
|
|
|
#include <qquickopenglutils.h>
|
2022-06-01 17:50:16 +02:00
|
|
|
#endif
|
2022-05-31 17:56:49 +02:00
|
|
|
|
|
|
|
static GLuint qskTakeTexture( QOpenGLFramebufferObject& fbo )
|
2018-10-04 16:15:42 +02:00
|
|
|
{
|
2022-05-31 17:56:49 +02:00
|
|
|
/*
|
|
|
|
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 )
|
2018-10-04 16:15:42 +02:00
|
|
|
{
|
2022-05-31 17:56:49 +02:00
|
|
|
class MyGuard : public QOpenGLSharedResourceGuard
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void invalidateTexture() { invalidateResource(); }
|
|
|
|
};
|
|
|
|
|
|
|
|
reinterpret_cast< MyGuard* >( guard )->invalidateTexture();
|
2018-10-04 16:15:42 +02:00
|
|
|
}
|
|
|
|
|
2022-05-31 17:56:49 +02:00
|
|
|
attachment.guard = guard;
|
|
|
|
|
|
|
|
return textureId;
|
|
|
|
}
|
|
|
|
|
2022-06-01 18:27:05 +02:00
|
|
|
static inline QSGImageNode::TextureCoordinatesTransformMode
|
|
|
|
qskEffectiveTransformMode( const Qt::Orientations mirrored )
|
|
|
|
{
|
|
|
|
QSGImageNode::TextureCoordinatesTransformMode mode;
|
|
|
|
|
|
|
|
if ( mirrored & Qt::Vertical )
|
|
|
|
mode |= QSGImageNode::MirrorVertically;
|
|
|
|
|
|
|
|
if ( mirrored & Qt::Horizontal )
|
|
|
|
mode |= QSGImageNode::MirrorHorizontally;
|
|
|
|
|
|
|
|
return mode;
|
|
|
|
}
|
|
|
|
|
2022-05-31 17:56:49 +02:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
const quint8 imageRole = 250; // reserved for internal use
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
inline QSGImageNode* findImageNode( const QSGNode* parentNode )
|
2018-10-04 16:15:42 +02:00
|
|
|
{
|
2022-06-01 16:57:57 +02:00
|
|
|
auto node = QskSGNode::findChildNode(
|
|
|
|
const_cast< QSGNode* >( parentNode ), imageRole );
|
|
|
|
|
|
|
|
return static_cast< QSGImageNode* >( node );
|
2018-10-04 16:15:42 +02:00
|
|
|
}
|
|
|
|
|
2022-05-31 17:56:49 +02:00
|
|
|
static inline bool qskHasOpenGLRenderer( QQuickWindow* window )
|
|
|
|
{
|
|
|
|
if ( window == nullptr )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const auto renderer = window->rendererInterface();
|
|
|
|
return renderer->graphicsApi() == QSGRendererInterface::OpenGL;
|
|
|
|
}
|
|
|
|
}
|
2018-10-04 16:15:42 +02:00
|
|
|
|
|
|
|
QskPaintedNode::QskPaintedNode()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
QskPaintedNode::~QskPaintedNode()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
void QskPaintedNode::setRenderHint( RenderHint renderHint )
|
|
|
|
{
|
|
|
|
m_renderHint = renderHint;
|
|
|
|
}
|
|
|
|
|
|
|
|
QskPaintedNode::RenderHint QskPaintedNode::renderHint() const
|
|
|
|
{
|
|
|
|
return m_renderHint;
|
|
|
|
}
|
|
|
|
|
2022-06-01 18:27:05 +02:00
|
|
|
void QskPaintedNode::setMirrored( Qt::Orientations orientations )
|
|
|
|
{
|
|
|
|
if ( orientations != m_mirrored )
|
|
|
|
{
|
|
|
|
m_mirrored == orientations;
|
|
|
|
|
|
|
|
if ( auto imageNode = findImageNode( this ) )
|
|
|
|
{
|
|
|
|
imageNode->setTextureCoordinatesTransform(
|
|
|
|
qskEffectiveTransformMode( orientations ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::Orientations QskPaintedNode::mirrored() const
|
|
|
|
{
|
|
|
|
return m_mirrored;
|
|
|
|
}
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
QRectF QskPaintedNode::rect() const
|
|
|
|
{
|
|
|
|
const auto imageNode = findImageNode( this );
|
|
|
|
return imageNode ? imageNode->rect() : QRectF();
|
|
|
|
}
|
|
|
|
|
2020-11-01 15:44:15 +01:00
|
|
|
void QskPaintedNode::update( QQuickWindow* window,
|
2022-06-01 16:57:57 +02:00
|
|
|
const QRectF& rect, const void* nodeData )
|
2022-05-31 17:56:49 +02:00
|
|
|
{
|
|
|
|
auto imageNode = findImageNode( this );
|
|
|
|
|
|
|
|
if ( rect.isEmpty() )
|
|
|
|
{
|
|
|
|
if ( imageNode )
|
|
|
|
{
|
|
|
|
removeChildNode( imageNode );
|
|
|
|
delete imageNode;
|
|
|
|
}
|
2022-06-01 16:57:57 +02:00
|
|
|
|
|
|
|
return;
|
2022-05-31 17:56:49 +02:00
|
|
|
}
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
bool isDirty = false;
|
2022-05-31 17:56:49 +02:00
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
const auto newHash = hash( nodeData );
|
|
|
|
if ( ( newHash == 0 ) || ( newHash != m_hash ) )
|
|
|
|
{
|
|
|
|
m_hash = newHash;
|
|
|
|
isDirty = true;
|
|
|
|
}
|
2022-05-31 17:56:49 +02:00
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
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 );
|
2022-05-31 17:56:49 +02:00
|
|
|
}
|
2022-06-01 18:27:05 +02:00
|
|
|
|
|
|
|
imageNode = findImageNode( this );
|
|
|
|
if ( imageNode )
|
|
|
|
{
|
|
|
|
imageNode->setRect( rect );
|
|
|
|
imageNode->setTextureCoordinatesTransform(
|
|
|
|
qskEffectiveTransformMode( m_mirrored ) );
|
|
|
|
}
|
2022-05-31 17:56:49 +02:00
|
|
|
}
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
void QskPaintedNode::updateImageNode(
|
|
|
|
QQuickWindow* window, const QRectF& rect, const void* nodeData )
|
2022-05-31 17:56:49 +02:00
|
|
|
{
|
|
|
|
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 );
|
2022-06-01 16:57:57 +02:00
|
|
|
paint( &painter, rect.size(), nodeData );
|
2022-05-31 17:56:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 ) );
|
|
|
|
}
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
void QskPaintedNode::updateImageNodeGL(
|
|
|
|
QQuickWindow* window, const QRectF& rect, const void* nodeData )
|
2018-10-04 16:15:42 +02:00
|
|
|
{
|
2022-05-31 17:56:49 +02:00
|
|
|
const auto ratio = window->effectiveDevicePixelRatio();
|
|
|
|
const QSize size( ratio * rect.width(), ratio * rect.height() );
|
|
|
|
|
|
|
|
auto imageNode = findImageNode( this );
|
|
|
|
|
|
|
|
if ( imageNode == nullptr )
|
|
|
|
{
|
|
|
|
imageNode = window->createImageNode();
|
2018-10-04 16:15:42 +02:00
|
|
|
|
2022-05-31 17:56:49 +02:00
|
|
|
imageNode->setOwnsTexture( true );
|
|
|
|
QskSGNode::setNodeRole( imageNode, imageRole );
|
|
|
|
|
|
|
|
appendChildNode( imageNode );
|
|
|
|
}
|
|
|
|
|
|
|
|
auto texture = qobject_cast< QSGPlainTexture* >( imageNode->texture() );
|
|
|
|
if ( texture == nullptr )
|
2018-10-04 16:15:42 +02:00
|
|
|
{
|
2022-05-31 17:56:49 +02:00
|
|
|
texture = new QSGPlainTexture;
|
|
|
|
texture->setHasAlphaChannel( true );
|
|
|
|
texture->setOwnsTexture( true );
|
|
|
|
|
|
|
|
imageNode->setTexture( texture );
|
2018-10-04 16:15:42 +02:00
|
|
|
}
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
/*
|
|
|
|
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 );
|
2022-05-31 17:56:49 +02:00
|
|
|
|
|
|
|
auto rhi = QQuickWindowPrivate::get( window )->rhi;
|
|
|
|
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
|
|
|
|
texture->setTextureFromNativeTexture(
|
|
|
|
rhi, quint64( textureId ), 0, size, {}, {} );
|
|
|
|
#else
|
|
|
|
if ( rhi )
|
|
|
|
{
|
|
|
|
// enabled with: "export QSG_RHI=1"
|
|
|
|
texture->setTextureFromNativeObject( rhi,
|
|
|
|
QQuickWindow::NativeObjectTexture, &textureId, 0, size, false );
|
|
|
|
}
|
|
|
|
else
|
2018-10-04 16:15:42 +02:00
|
|
|
{
|
2022-05-31 17:56:49 +02:00
|
|
|
texture->setTextureId( textureId );
|
|
|
|
texture->setTextureSize( size );
|
2018-10-04 16:15:42 +02:00
|
|
|
}
|
2022-05-31 17:56:49 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
uint32_t QskPaintedNode::createTexture(
|
|
|
|
QQuickWindow* window, const QSize& size, const void* nodeData )
|
2022-05-31 17:56:49 +02:00
|
|
|
{
|
2022-06-01 16:57:57 +02:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
|
2022-05-31 17:56:49 +02:00
|
|
|
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 );
|
2020-11-01 15:44:15 +01:00
|
|
|
|
2018-10-04 16:15:42 +02:00
|
|
|
{
|
2022-05-31 17:56:49 +02:00
|
|
|
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 );
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
paint( &painter, size, nodeData );
|
2018-10-04 16:15:42 +02:00
|
|
|
}
|
2020-11-01 15:44:15 +01:00
|
|
|
|
2022-05-31 17:56:49 +02:00
|
|
|
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 );
|
|
|
|
|
2022-06-01 16:57:57 +02:00
|
|
|
window->endExternalCommands();
|
|
|
|
|
2022-05-31 17:56:49 +02:00
|
|
|
return qskTakeTexture( fbo );
|
2018-10-04 16:15:42 +02:00
|
|
|
}
|