qskinny/src/nodes/QskSceneTexture.cpp

421 lines
11 KiB
C++
Raw Normal View History

2023-12-17 16:32:07 +00:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskSceneTexture.h"
#include "QskTreeNode.h"
#include <qmath.h>
2023-12-17 16:32:07 +00:00
QSK_QT_PRIVATE_BEGIN
#include <private/qquickwindow_p.h>
2023-12-22 12:52:01 +00:00
#include <private/qsgtexture_p.h>
#define QT_BUILD_QUICK_LIB // suppress Qt5 warnings
2023-12-17 16:32:07 +00:00
#include <private/qsgbatchrenderer_p.h>
2023-12-22 12:52:01 +00:00
#undef QT_BUILD_QUICK_LIB
2023-12-17 16:32:07 +00:00
QSK_QT_PRIVATE_END
2023-12-22 12:52:01 +00:00
/*
With Qt 5.15 Rhi can optionally be enbled by setting "export QSG_RHI=1".
So we need to have a native QOpenGL implementation and one using
the Rhi abstraction layer. For Qt6 we can rely on Rhi.
Once Qt5 support has been dropped we can eliminate this #ifdef jungle
*/
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
#include <qopenglframebufferobject.h>
#endif
2023-12-17 16:32:07 +00:00
namespace
{
2023-12-22 12:52:01 +00:00
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
2023-12-17 16:32:07 +00:00
inline QSGRendererInterface::RenderMode contextRenderMode(
QSGDefaultRenderContext* context )
{
return context->useDepthBufferFor2D()
? QSGRendererInterface::RenderMode2D
: QSGRendererInterface::RenderMode2DNoDepthBuffer;
}
2023-12-22 12:52:01 +00:00
#endif
2023-12-17 16:32:07 +00:00
class Renderer final : public QSGBatchRenderer::Renderer
{
using Inherited = QSGBatchRenderer::Renderer;
public:
Renderer( QskSceneTexture*, QSGDefaultRenderContext* );
~Renderer() override;
2023-12-22 12:52:01 +00:00
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
inline int textureId() const { return m_fbo ? m_fbo->texture() : 0; }
inline void renderScene() { Inherited::renderScene( textureId() ); }
#endif
inline QRhiTexture* rhiTexture() const { return m_rhiTexture; }
inline bool isDirty() const { return m_dirty; }
2023-12-17 16:32:07 +00:00
void setFinalNode( QSGTransformNode* );
void setProjection( const QRectF& );
void setTextureSize( const QSize& );
protected:
void nodeChanged( QSGNode*, QSGNode::DirtyState ) override;
void render() override;
private:
void createTarget( const QSize& );
void clearTarget();
QSGTransformNode* m_finalNode = nullptr;
QskSceneTexture* m_texture = nullptr;
2023-12-22 12:52:01 +00:00
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
QOpenGLFramebufferObject* m_fbo;
struct RenderTarget
{
QRhiRenderTarget* rt = nullptr;
QRhiRenderPassDescriptor* rpDesc = nullptr;
QRhiCommandBuffer* cb = nullptr;
} m_rt;
#endif
QRhiTexture* m_rhiTexture = nullptr;
bool m_dirty = true;
2023-12-17 16:32:07 +00:00
};
Renderer::Renderer( QskSceneTexture* texture, QSGDefaultRenderContext* context )
2023-12-22 12:52:01 +00:00
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
: Inherited( context )
#else
2023-12-17 16:32:07 +00:00
: Inherited( context, contextRenderMode( context ) )
2023-12-22 12:52:01 +00:00
#endif
2023-12-17 16:32:07 +00:00
, m_texture( texture )
{
setClearColor( Qt::transparent );
}
Renderer::~Renderer()
{
clearTarget();
}
void Renderer::setFinalNode( QSGTransformNode* node )
{
if ( node != m_finalNode )
{
m_finalNode = node;
}
}
void Renderer::setProjection( const QRectF& rect )
{
2023-12-22 12:52:01 +00:00
bool flipFramebuffer = true;
bool flipMatrix = false;
if ( const auto rhi = context()->rhi() )
{
flipFramebuffer = rhi->isYUpInFramebuffer();
flipMatrix = !rhi->isYUpInNDC();
}
2023-12-17 16:32:07 +00:00
auto r = rect;
2023-12-22 12:52:01 +00:00
if ( flipFramebuffer )
2023-12-17 16:32:07 +00:00
{
r.moveTop( r.bottom() );
r.setHeight( -r.height() );
}
MatrixTransformFlags matrixFlags;
2023-12-22 12:52:01 +00:00
if ( flipMatrix )
2023-12-17 16:32:07 +00:00
matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY;
setProjectionMatrixToRect( r, matrixFlags );
}
void Renderer::setTextureSize( const QSize& size )
{
2023-12-22 12:52:01 +00:00
if ( const auto rhi = context()->rhi() )
{
if ( m_rt.rt && m_rt.rt->pixelSize() != size )
clearTarget();
if ( m_rt.rt == nullptr )
createTarget( size );
}
else
{
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
if ( m_fbo && m_fbo->size() != size )
clearTarget();
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
if ( m_fbo == nullptr )
createTarget( size );
#endif
}
2023-12-17 16:32:07 +00:00
const QRect r( 0, 0, size.width(), size.height() );
setDeviceRect( r );
setViewportRect( r );
}
void Renderer::render()
{
2023-12-22 12:52:01 +00:00
m_dirty = false;
2023-12-17 16:32:07 +00:00
qskTryBlockTrailingNodes( m_finalNode, rootNode(), true, false );
2023-12-22 12:52:01 +00:00
2023-12-17 16:32:07 +00:00
#if 0
2023-12-22 12:52:01 +00:00
static int counter = 0;
qDebug() << ++counter;
2023-12-17 16:32:07 +00:00
QSGNodeDumper::dump( rootNode() );
#endif
Inherited::render();
qskTryBlockTrailingNodes( m_finalNode, rootNode(), false, false );
}
void Renderer::nodeChanged( QSGNode* node, QSGNode::DirtyState state )
{
2023-12-22 12:52:01 +00:00
Inherited::nodeChanged( node, state );
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
/*
We want to limit updates to nodes, that are actually rendered. TODO ...
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
In any case we need to block update requests, when the textureNode reports
that it has been updated by us to the renderer of the window.
*/
if ( ( state != QSGNode::DirtyMaterial )
|| ( node != m_texture->textureNode() ) )
2023-12-17 16:32:07 +00:00
{
2023-12-22 12:52:01 +00:00
m_dirty = true;
2023-12-17 16:32:07 +00:00
Q_EMIT m_texture->updateRequested();
}
}
void Renderer::createTarget( const QSize& size )
{
2023-12-22 12:52:01 +00:00
if ( const auto rhi = context()->rhi() )
{
auto flags = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
m_rhiTexture = rhi->newTexture( QRhiTexture::RGBA8, size, 1, flags );
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
m_rhiTexture->build();
#else
m_rhiTexture->create();
#endif
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
QRhiColorAttachment color0( m_rhiTexture );
auto target = rhi->newTextureRenderTarget( { color0 } );
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
target->setRenderPassDescriptor(
target->newCompatibleRenderPassDescriptor() );
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
target->build();
#else
target->create();
#endif
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
m_rt.rt = target;
m_rt.rpDesc = target->renderPassDescriptor();
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
auto defaultContext = qobject_cast< QSGDefaultRenderContext* >( context() );
m_rt.cb = defaultContext->currentFrameCommandBuffer();
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
setRenderTarget( m_rt.rt );
setCommandBuffer( m_rt.cb );
setRenderPassDescriptor( m_rt.rpDesc );
#endif
}
else
{
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
QOpenGLFramebufferObjectFormat format;
format.setInternalTextureFormat( GL_RGBA8 );
format.setSamples( 0 );
format.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil );
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
m_fbo = new QOpenGLFramebufferObject( size, format );
#endif
}
2023-12-17 16:32:07 +00:00
}
void Renderer::clearTarget()
{
2023-12-22 12:52:01 +00:00
if ( const auto rhi = context()->rhi() )
{
delete m_rt.rt;
m_rt.rt = nullptr;
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
delete m_rt.rpDesc;
m_rt.rpDesc = nullptr;
2023-12-17 16:32:07 +00:00
2023-12-22 12:52:01 +00:00
delete m_rhiTexture;
m_rhiTexture = nullptr;
}
else
{
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
delete m_fbo;
m_fbo = nullptr;
#endif
}
2023-12-17 16:32:07 +00:00
}
}
class QskSceneTexturePrivate final : public QSGTexturePrivate
{
public:
QskSceneTexturePrivate( const QQuickWindow* window, QskSceneTexture* texture )
2023-12-22 12:52:01 +00:00
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
: QSGTexturePrivate()
#else
2023-12-17 16:32:07 +00:00
: QSGTexturePrivate( texture )
2023-12-22 12:52:01 +00:00
#endif
2023-12-17 16:32:07 +00:00
, devicePixelRatio( window->effectiveDevicePixelRatio() )
{
2023-12-22 12:52:01 +00:00
Q_UNUSED( texture );
// Qt5 needs the extra const_cast
auto dw = QQuickWindowPrivate::get( const_cast< QQuickWindow* >( window ) );
context = dynamic_cast< QSGDefaultRenderContext* >( dw->context );
2023-12-17 16:32:07 +00:00
}
2023-12-22 12:52:01 +00:00
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
int comparisonKey() const override
{ return int( qintptr( rhiTexture() ) ); }
QRhiTexture *rhiTexture() const override
{ return renderer ? renderer->rhiTexture() : nullptr; }
#endif
2023-12-17 16:32:07 +00:00
QRectF rect;
const qreal devicePixelRatio;
Renderer* renderer = nullptr;
QSGDefaultRenderContext* context = nullptr;
2023-12-22 12:52:01 +00:00
const QSGGeometryNode* textureNode = nullptr;
2023-12-17 16:32:07 +00:00
};
QskSceneTexture::QskSceneTexture( const QQuickWindow* window )
2023-12-22 12:52:01 +00:00
: Inherited( *new QskSceneTexturePrivate( window, this ) )
2023-12-17 16:32:07 +00:00
{
Q_ASSERT( d_func()->context );
}
QskSceneTexture::~QskSceneTexture()
{
delete d_func()->renderer;
}
2023-12-22 12:52:01 +00:00
void QskSceneTexture::setTextureNode( const QSGGeometryNode* node )
{
d_func()->textureNode = node;
}
const QSGGeometryNode* QskSceneTexture::textureNode() const
{
return d_func()->textureNode;
}
2023-12-17 16:32:07 +00:00
QSize QskSceneTexture::textureSize() const
{
Q_D( const QskSceneTexture );
QSize size( qCeil( d->rect.width() ), qCeil( d->rect.height() ) );
size *= d->devicePixelRatio;
const QSize minSize = d->context->sceneGraphContext()->minimumFBOSize();
while ( size.width() < minSize.width() )
size.rwidth() *= 2;
while ( size.height() < minSize.height() )
size.rheight() *= 2;
return size;
}
void QskSceneTexture::render( const QSGRootNode* rootNode,
const QSGTransformNode* finalNode, const QRectF& rect )
{
Q_D( QskSceneTexture );
d->rect = rect;
if ( d->renderer == nullptr )
{
d->renderer = new Renderer( this, d->context );
d->renderer->setDevicePixelRatio( d->devicePixelRatio );
}
d->renderer->setRootNode( const_cast< QSGRootNode* >( rootNode ) );
d->renderer->setFinalNode( const_cast< QSGTransformNode* >( finalNode ) );
d->renderer->setProjection( d->rect );
2023-12-22 12:52:01 +00:00
d->renderer->setTextureSize( textureSize() );
2023-12-17 16:32:07 +00:00
d->renderer->renderScene();
}
QRectF QskSceneTexture::normalizedTextureSubRect() const
{
return QRectF( 0, 1, 1, -1 );
}
bool QskSceneTexture::hasAlphaChannel() const
{
return false;
}
bool QskSceneTexture::hasMipmaps() const
{
return false;
}
2023-12-22 12:52:01 +00:00
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
void QskSceneTexture::bind()
{
if ( d_func()->rhiTexture() == nullptr )
{
auto funcs = QOpenGLContext::currentContext()->functions();
funcs->glBindTexture( GL_TEXTURE_2D, textureId() );
updateBindOptions();
}
}
int QskSceneTexture::textureId() const
{
Q_D( const QskSceneTexture );
return d->renderer ? d->renderer->textureId() : 0;
}
#else
qint64 QskSceneTexture::comparisonKey() const
2023-12-17 16:32:07 +00:00
{
2023-12-22 12:52:01 +00:00
return qint64( rhiTexture() );
2023-12-17 16:32:07 +00:00
}
2023-12-22 12:52:01 +00:00
QRhiTexture* QskSceneTexture::rhiTexture() const
{
Q_D( const QskSceneTexture );
return d->renderer ? d->renderer->rhiTexture() : nullptr;
}
#endif
2023-12-17 16:32:07 +00:00
#include "moc_QskSceneTexture.cpp"