QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskSceneTexture.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskSceneTexture.h"
7#include "QskTreeNode.h"
8
9#include <qmath.h>
10
11QSK_QT_PRIVATE_BEGIN
12#include <private/qquickwindow_p.h>
13#include <private/qsgtexture_p.h>
14
15#define QT_BUILD_QUICK_LIB // suppress Qt5 warnings
16#include <private/qsgbatchrenderer_p.h>
17#undef QT_BUILD_QUICK_LIB
18
19QSK_QT_PRIVATE_END
20
21/*
22 With Qt 5.15 Rhi can optionally be enbled by setting "export QSG_RHI=1".
23 So we need to have a native QOpenGL implementation and one using
24 the Rhi abstraction layer. For Qt6 we can rely on Rhi.
25 Once Qt5 support has been dropped we can eliminate this #ifdef jungle
26 */
27
28#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
29#include <qopenglframebufferobject.h>
30#endif
31
32static int qskRenderOrderCompare( const QSGNode* rootNode,
33 const QSGNode* node1, const QSGNode* node2 )
34{
35 if ( rootNode == node1 )
36 return 1;
37
38 if ( rootNode == node2 )
39 return -1;
40
41 for ( auto node = rootNode->firstChild();
42 node != nullptr; node = node->nextSibling() )
43 {
44 const auto ret = qskRenderOrderCompare( node, node1, node2 );
45 if ( ret )
46 return ret;
47 }
48
49 return 0;
50}
51
52namespace
53{
54#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
55 inline QSGRendererInterface::RenderMode contextRenderMode(
56 QSGDefaultRenderContext* context )
57 {
58 return context->useDepthBufferFor2D()
59 ? QSGRendererInterface::RenderMode2D
60 : QSGRendererInterface::RenderMode2DNoDepthBuffer;
61 }
62#endif
63
64 class Renderer final : public QSGBatchRenderer::Renderer
65 {
66 using Inherited = QSGBatchRenderer::Renderer;
67
68 public:
69 Renderer( QskSceneTexture*, QSGDefaultRenderContext* );
70 ~Renderer() override;
71
72#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
73 inline int textureId() const { return m_fbo ? m_fbo->texture() : 0; }
74
75 inline void renderScene()
76 {
77 class Bindable : public QSGBindable
78 {
79 public:
80 Bindable( QOpenGLFramebufferObject* fbo ) : m_fbo( fbo ) {}
81 void bind() const override { m_fbo->bind(); }
82 private:
83 QOpenGLFramebufferObject* m_fbo;
84 };
85
86 Inherited::renderScene( Bindable( m_fbo ) );
87 }
88#endif
89
90 inline QRhiTexture* rhiTexture() const { return m_rhiTexture; }
91
92 inline bool isDirty() const { return m_dirty; }
93
94 void setFinalNode( QSGTransformNode* );
95
96 void setProjection( const QRectF& );
97 void setTextureSize( const QSize& );
98 QSize textureSize() const;
99
100 protected:
101 void nodeChanged( QSGNode*, QSGNode::DirtyState ) override;
102 void render() override;
103
104 private:
105 void createTarget( const QSize& );
106 void clearTarget();
107 void markDirty();
108
109 QSGTransformNode* m_finalNode = nullptr;
110 QskSceneTexture* m_texture = nullptr;
111
112#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
113 QOpenGLFramebufferObject* m_fbo = nullptr;
114#endif
115
116#if QT_VERSION < QT_VERSION_CHECK( 6, 4, 0 )
117 struct RenderTarget
118 {
119 QRhiRenderTarget* rt = nullptr;
120 QRhiRenderPassDescriptor* rpDesc = nullptr;
121 QRhiCommandBuffer* cb = nullptr;
122 } m_rt;
123#endif
124 QRhiTexture* m_rhiTexture = nullptr;
125
126 bool m_dirty = true;
127 };
128
129 Renderer::Renderer( QskSceneTexture* texture, QSGDefaultRenderContext* context )
130#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
131 : Inherited( context )
132#else
133 : Inherited( context, contextRenderMode( context ) )
134#endif
135 , m_texture( texture )
136 {
137 setClearColor( Qt::transparent );
138
139 connect( this, &QSGRenderer::sceneGraphChanged,
140 this, &Renderer::markDirty );
141 }
142
143 Renderer::~Renderer()
144 {
145 clearTarget();
146 }
147
148 void Renderer::setFinalNode( QSGTransformNode* node )
149 {
150 if ( node != m_finalNode )
151 {
152 m_finalNode = node;
153 markDirty();
154 }
155 }
156
157 void Renderer::setProjection( const QRectF& rect )
158 {
159 bool flipFramebuffer = true;
160 bool flipMatrix = false;
161
162 if ( const auto rhi = context()->rhi() )
163 {
164 flipFramebuffer = rhi->isYUpInFramebuffer();
165 flipMatrix = !rhi->isYUpInNDC();
166 }
167
168 auto r = rect;
169
170 if ( flipFramebuffer )
171 {
172 r.moveTop( r.bottom() );
173 r.setHeight( -r.height() );
174 }
175
176 MatrixTransformFlags matrixFlags;
177
178 if ( flipMatrix )
179 matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY;
180
181 setProjectionMatrixToRect( r, matrixFlags );
182 }
183
184 void Renderer::setTextureSize( const QSize& size )
185 {
186 if ( const auto rhi = context()->rhi() )
187 {
188 if ( m_rt.rt && m_rt.rt->pixelSize() != size )
189 clearTarget();
190
191 if ( m_rt.rt == nullptr )
192 createTarget( size );
193 }
194 else
195 {
196#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
197 if ( m_fbo && m_fbo->size() != size )
198 clearTarget();
199
200 if ( m_fbo == nullptr )
201 createTarget( size );
202#endif
203 }
204
205 const QRect r( 0, 0, size.width(), size.height() );
206
207 setDeviceRect( r );
208 setViewportRect( r );
209 }
210
211 QSize Renderer::textureSize() const
212 {
213#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
214 if ( m_fbo ) return m_fbo->size();
215#else
216 if( m_rt.rt ) return m_rt.rt->pixelSize();
217#endif
218 return QSize();
219 }
220
221 void Renderer::render()
222 {
223 m_dirty = false;
224
225 qskTryBlockTrailingNodes( m_finalNode, rootNode(), true, false );
226
227#if 0
228 static int counter = 0;
229 qDebug() << ++counter;
230 QSGNodeDumper::dump( rootNode() );
231#endif
232 Inherited::render();
233 qskTryBlockTrailingNodes( m_finalNode, rootNode(), false, false );
234 }
235
236 void Renderer::nodeChanged( QSGNode* node, QSGNode::DirtyState state )
237 {
238 /*
239 No need to update the texture for changes of nodes behind
240 the final node.
241
242 Unfortunately QQuickWindow does not update the scene graph in
243 rendering order and we might be called for relevant nodes after
244 the texture has already been updated. In these situations we
245 update the texture twice. Not so good ...
246 */
247 if ( qskRenderOrderCompare( rootNode(), node, m_finalNode ) > 0 )
248 {
249 // triggering QSGRenderer::sceneGraphChanged signals
250 Inherited::nodeChanged( node, state );
251 }
252 }
253
254 void Renderer::markDirty()
255 {
256 if ( !m_dirty )
257 {
258 m_dirty = true;
259 Q_EMIT m_texture->updateRequested();
260 }
261 }
262
263 void Renderer::createTarget( const QSize& size )
264 {
265 if ( const auto rhi = context()->rhi() )
266 {
267 auto flags = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
268
269 m_rhiTexture = rhi->newTexture( QRhiTexture::RGBA8, size, 1, flags );
270#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
271 m_rhiTexture->build();
272#else
273 m_rhiTexture->create();
274#endif
275
276 QRhiColorAttachment color0( m_rhiTexture );
277 auto target = rhi->newTextureRenderTarget( { color0 } );
278
279 target->setRenderPassDescriptor(
280 target->newCompatibleRenderPassDescriptor() );
281
282#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
283 target->build();
284#else
285 target->create();
286#endif
287
288 m_rt.rt = target;
289 m_rt.rpDesc = target->renderPassDescriptor();
290
291 auto defaultContext = qobject_cast< QSGDefaultRenderContext* >( context() );
292 m_rt.cb = defaultContext->currentFrameCommandBuffer();
293
294#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
295 setRenderTarget( m_rt.rt );
296 setCommandBuffer( m_rt.cb );
297 setRenderPassDescriptor( m_rt.rpDesc );
298#endif
299 }
300 else
301 {
302#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
303 QOpenGLFramebufferObjectFormat format;
304 format.setInternalTextureFormat( GL_RGBA8 );
305 format.setSamples( 0 );
306 format.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil );
307
308 m_fbo = new QOpenGLFramebufferObject( size, format );
309#endif
310 }
311 }
312
313 void Renderer::clearTarget()
314 {
315 if ( const auto rhi = context()->rhi() )
316 {
317 delete m_rt.rt;
318 m_rt.rt = nullptr;
319
320 delete m_rt.rpDesc;
321 m_rt.rpDesc = nullptr;
322
323 delete m_rhiTexture;
324 m_rhiTexture = nullptr;
325 }
326 else
327 {
328#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
329 delete m_fbo;
330 m_fbo = nullptr;
331#endif
332 }
333 }
334}
335
336class QskSceneTexturePrivate final : public QSGTexturePrivate
337{
338 public:
339 QskSceneTexturePrivate( const QQuickWindow* window, QskSceneTexture* texture )
340#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
341 : QSGTexturePrivate()
342#else
343 : QSGTexturePrivate( texture )
344#endif
345 , devicePixelRatio( window->effectiveDevicePixelRatio() )
346 {
347 Q_UNUSED( texture );
348
349 // Qt5 needs the extra const_cast
350 auto dw = QQuickWindowPrivate::get( const_cast< QQuickWindow* >( window ) );
351 context = dynamic_cast< QSGDefaultRenderContext* >( dw->context );
352 }
353
354#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
355 int comparisonKey() const override
356 {
357 if ( renderer )
358 {
359 if ( renderer->textureId() )
360 return renderer->textureId();
361
362 if ( renderer->rhiTexture() )
363 return int( qintptr( renderer->rhiTexture() ) );
364 }
365
366 return int( qintptr( this ) );
367 }
368
369 QRhiTexture *rhiTexture() const override
370 { return renderer ? renderer->rhiTexture() : nullptr; }
371#endif
372
373 QSize pixelSize() const
374 {
375 QSize size( qCeil( rect.width() ), qCeil( rect.height() ) );
376 size *= devicePixelRatio;
377
378 const QSize minSize = context->sceneGraphContext()->minimumFBOSize();
379
380 while ( size.width() < minSize.width() )
381 size.rwidth() *= 2;
382
383 while ( size.height() < minSize.height() )
384 size.rheight() *= 2;
385
386 return size;
387 }
388
389 QRectF rect;
390 const qreal devicePixelRatio;
391
392 Renderer* renderer = nullptr;
393 QSGDefaultRenderContext* context = nullptr;
394};
395
396QskSceneTexture::QskSceneTexture( const QQuickWindow* window )
397 : Inherited( *new QskSceneTexturePrivate( window, this ) )
398{
399 Q_ASSERT( d_func()->context );
400}
401
402QskSceneTexture::~QskSceneTexture()
403{
404 delete d_func()->renderer;
405}
406
407QSize QskSceneTexture::textureSize() const
408{
409 Q_D( const QskSceneTexture );
410 return d->renderer ? d->renderer->textureSize() : QSize();
411}
412
413void QskSceneTexture::render( const QSGRootNode* rootNode,
414 const QSGTransformNode* finalNode, const QRectF& rect )
415{
416 Q_D( QskSceneTexture );
417
418 d->rect = rect;
419
420 if ( d->renderer == nullptr )
421 {
422 d->renderer = new Renderer( this, d->context );
423 d->renderer->setDevicePixelRatio( d->devicePixelRatio );
424 }
425
426 d->renderer->setRootNode( const_cast< QSGRootNode* >( rootNode ) );
427 d->renderer->setFinalNode( const_cast< QSGTransformNode* >( finalNode ) );
428
429 d->renderer->setProjection( d->rect );
430 d->renderer->setTextureSize( d->pixelSize() );
431 d->renderer->renderScene();
432}
433
434bool QskSceneTexture::isDirty() const
435{
436 Q_D( const QskSceneTexture );
437 return d->renderer ? d->renderer->isDirty() : true;
438}
439
440QRectF QskSceneTexture::normalizedTextureSubRect() const
441{
442 return QRectF( 0, 1, 1, -1 );
443}
444
445bool QskSceneTexture::hasAlphaChannel() const
446{
447 return false;
448}
449
450bool QskSceneTexture::hasMipmaps() const
451{
452 return false;
453}
454
455#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
456
457void QskSceneTexture::bind()
458{
459 if ( d_func()->rhiTexture() == nullptr )
460 {
461 auto funcs = QOpenGLContext::currentContext()->functions();
462 funcs->glBindTexture( GL_TEXTURE_2D, textureId() );
463
464 updateBindOptions();
465 }
466}
467
468int QskSceneTexture::textureId() const
469{
470 Q_D( const QskSceneTexture );
471 return d->renderer ? d->renderer->textureId() : 0;
472}
473
474#else
475
476qint64 QskSceneTexture::comparisonKey() const
477{
478 return qint64( rhiTexture() );
479}
480
481QRhiTexture* QskSceneTexture::rhiTexture() const
482{
483 Q_D( const QskSceneTexture );
484 return d->renderer ? d->renderer->rhiTexture() : nullptr;
485}
486
487#endif
488
489#include "moc_QskSceneTexture.cpp"