Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qquick3drenderstats.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5#include <QtQuick3DRuntimeRender/private/qssgrendermesh_p.h>
6#include <QtQuick/qquickwindow.h>
7#include <QtQuick/qquickitem.h>
8
10
24{
25 m_frameTimer.start();
26}
27
35{
36 return m_fps;
37}
38
47{
48 return m_results.frameTime;
49}
50
60{
61 return m_results.renderTime;
62}
63
73{
74 return m_results.renderPrepareTime;
75}
76
86{
87 return m_results.syncTime;
88}
89
98{
99 return m_maxFrameTime;
100}
101
102float QQuick3DRenderStats::timestamp() const
103{
104 return m_frameTimer.nsecsElapsed() / 1000000.0f;
105}
106
108{
109 m_syncStartTime = timestamp();
110}
111
113{
114 m_results.syncTime = timestamp() - m_syncStartTime;
115
116 if (dump)
117 qDebug("Sync took: %f ms", m_results.syncTime);
118}
119
121{
122 m_renderStartTime = timestamp();
123}
124
126{
127 m_renderPrepareStartTime = timestamp();
128}
129
131{
132 m_results.renderPrepareTime = timestamp() - m_renderPrepareStartTime;
133}
134
136{
137 // Threading-wise this and onFrameSwapped are not perfect. These are called
138 // on the render thread (if there is one) outside of the sync step, so
139 // writing the data in m_results, which then may be read by the properties
140 // on the main thread concurrently, is not ideal. But at least the data the
141 // results are generated from (the m_* timings and all the stuff from
142 // QSSGRhiContextStats) belong to the render thread, so that's good.
143
144 m_renderingThisFrame = true;
145 const float endTime = timestamp();
146 m_results.renderTime = endTime - m_renderStartTime;
147
148 if (dump)
149 qDebug("Render took: %f ms (of which prep: %f ms)", m_results.renderTime, m_results.renderPrepareTime);
150}
151
152void QQuick3DRenderStats::onFrameSwapped()
153{
154 // NOTE: This is called on the render thread
155 // This is the real start and end of a frame
156
157 if (m_renderingThisFrame) {
158 ++m_frameCount;
159 m_results.frameTime = timestamp();
160 m_internalMaxFrameTime = qMax(m_results.frameTime, m_internalMaxFrameTime);
161
162 m_secTimer += m_results.frameTime;
163 m_notifyTimer += m_results.frameTime;
164
165 m_results.renderTime = m_results.frameTime - m_renderStartTime;
166
167 processRhiContextStats();
168
169 if (m_window) {
170 QRhiSwapChain *sc = m_window->swapChain();
171 if (sc) {
173 if (cb) {
174 const float msecs = float(cb->lastCompletedGpuTime() * 1000.0);
175 if (!qFuzzyIsNull(msecs))
176 m_results.lastCompletedGpuTime = msecs;
177 }
178 }
179 }
180
181 const float notifyInterval = 200.0f;
182 if (m_notifyTimer >= notifyInterval) {
183 m_notifyTimer -= notifyInterval;
184
185 if (m_results.frameTime != m_notifiedResults.frameTime) {
186 m_notifiedResults.frameTime = m_results.frameTime;
188 }
189
190 if (m_results.syncTime != m_notifiedResults.syncTime) {
191 m_notifiedResults.syncTime = m_results.syncTime;
193 }
194
195 if (m_results.renderTime != m_notifiedResults.renderTime) {
196 m_notifiedResults.renderTime = m_results.renderTime;
197 m_notifiedResults.renderPrepareTime = m_results.renderPrepareTime;
199 }
200
201 if (m_results.lastCompletedGpuTime != m_notifiedResults.lastCompletedGpuTime) {
202 m_notifiedResults.lastCompletedGpuTime = m_results.lastCompletedGpuTime;
204 }
205
206 notifyRhiContextStats();
207 }
208
209 const float fpsInterval = 1000.0f;
210 if (m_secTimer >= fpsInterval) {
211 m_secTimer -= fpsInterval;
212
213 m_fps = m_frameCount;
214 m_frameCount = 0;
216
217 m_maxFrameTime = m_internalMaxFrameTime;
218 m_internalMaxFrameTime = 0;
220 }
221
222 m_renderingThisFrame = false; // reset for next frame
223 }
224
225 // Always reset the frame timer
226 m_frameTimer.restart();
227}
228
230{
231 // called from synchronize(), so on the render thread with gui blocked
232
233 m_layer = layer;
234 m_contextStats = &ctx->stats();
235
236 // setExtendedDataCollectionEnabled will likely get called at some point
237 // before this (so too early), sync the flag here as well now that we know
238 // all we need to know.
239 if (m_extendedDataCollectionEnabled)
240 m_contextStats->dynamicDataSources.insert(layer);
241
242 if (m_contextStats && m_contextStats->context.rhi()) {
243 const QString backendName = QString::fromUtf8(m_contextStats->context.rhi()->backendName());
244 if (m_graphicsApiName != backendName) {
245 m_graphicsApiName = backendName;
247 }
248 }
249}
250
252{
253 if (m_window == window)
254 return;
255
256 if (m_window)
257 disconnect(m_frameSwappedConnection);
258
259 m_window = window;
260
261 if (m_window) {
262 m_frameSwappedConnection = connect(m_window, &QQuickWindow::afterFrameEnd,
263 this, &QQuick3DRenderStats::onFrameSwapped,
265 }
266}
267
287{
288 return m_extendedDataCollectionEnabled;
289}
290
292{
293 if (enable != m_extendedDataCollectionEnabled) {
294 m_extendedDataCollectionEnabled = enable;
296 }
297 if (m_contextStats) {
298 // This is what allows recognizing that there is at least one DebugView
299 // that is visible and wants all the data, and also helps in not
300 // performing all the processing if the set is empty (because then we
301 // know that no DebugView wants to display the data)
302 if (m_extendedDataCollectionEnabled)
303 m_contextStats->dynamicDataSources.insert(m_layer);
304 else
305 m_contextStats->dynamicDataSources.remove(m_layer);
306 }
307}
308
310{
311 switch (format) {
313 return "RGBA8";
315 return "BGRA8";
316 case QRhiTexture::R8:
317 return "R8";
318 case QRhiTexture::RG8:
319 return "RG8";
320 case QRhiTexture::R16:
321 return "R16";
323 return "RG16";
325 return "R8/A8";
327 return "RGBA16F";
329 return "RGBA32F";
331 return "R16F";
333 return "R32F";
335 return "RGB10A2";
336 case QRhiTexture::D16:
337 return "D16";
338 case QRhiTexture::D24:
339 return "D24";
341 return "D24S8";
343 return "D32F";
344 case QRhiTexture::BC1:
345 return "BC1";
346 case QRhiTexture::BC2:
347 return "BC2";
348 case QRhiTexture::BC3:
349 return "BC3";
350 case QRhiTexture::BC4:
351 return "BC4";
352 case QRhiTexture::BC5:
353 return "BC5";
355 return "BC6H";
356 case QRhiTexture::BC7:
357 return "BC7";
359 return "ETC2_RGB8";
361 return "ETC2_RGB8A1";
363 return "ETC2_RGBA8";
365 return "ASTC_4x4";
367 return "ASTC_5x4";
369 return "ASTC_5x5";
371 return "ASTC_6x5";
373 return "ASTC_6x6";
375 return "ASTC_8x5";
377 return "ASTC_8x6";
379 return "ASTC_8x8";
381 return "ASTC_10x5";
383 return "ASTC_10x6";
385 return "ASTC_10x8";
387 return "ASTC_10x10";
389 return "ASTC_12x10";
391 return "ASTC_12x12";
392 default:
393 break;
394 }
395 return "<unknown>";
396}
397
399{
400 *dst += QString::asprintf("| %s | %dx%d | %llu | %llu |\n",
401 rp.rtName.constData(),
402 rp.pixelSize.width(),
403 rp.pixelSize.height(),
406}
407
409{
410 if (!mesh->subsets.isEmpty()) {
411 auto buf = mesh->subsets[0].rhi.vertexBuffer;
412 if (buf)
413 return buf->buffer()->name();
414 }
415 return {};
416}
417
418void QQuick3DRenderStats::processRhiContextStats()
419{
420 if (!m_contextStats || !m_extendedDataCollectionEnabled)
421 return;
422
423 // the render pass list is per renderer, i.e. per View3D
424 const QSSGRhiContextStats::PerLayerInfo data = m_contextStats->perLayerInfo[m_layer];
425
426 // textures and meshes include all assets registered to the per-QQuickWindow QSSGRhiContext
427 const QSSGRhiContextStats::GlobalInfo globalData = m_contextStats->globalInfo;
428 const QSet<QRhiTexture *> textures = m_contextStats->context.registeredTextures();
429 const QSet<QSSGRenderMesh *> meshes = m_contextStats->context.registeredMeshes();
431
432 m_results.drawCallCount = 0;
433 m_results.drawVertexCount = 0;
434 for (const auto &pass : data.renderPasses) {
435 m_results.drawCallCount += QSSGRhiContextStats::totalDrawCallCountForPass(pass);
436 m_results.drawVertexCount += QSSGRhiContextStats::totalVertexCountForPass(pass);
437 }
438 m_results.drawCallCount += QSSGRhiContextStats::totalDrawCallCountForPass(data.externalRenderPass);
439 m_results.drawVertexCount += QSSGRhiContextStats::totalVertexCountForPass(data.externalRenderPass);
440
441 m_results.imageDataSize = globalData.imageDataSize;
442 m_results.meshDataSize = globalData.meshDataSize;
443
444 m_results.renderPassCount = data.renderPasses.size()
445 + (data.externalRenderPass.pixelSize.isEmpty() ? 0 : 1);
446
448| Name | Size | Vertices | Draw calls |
449| ---- | ---- | -------- | ---------- |
450)");
451
452 if (!data.externalRenderPass.pixelSize.isEmpty())
453 printRenderPassDetails(&renderPassDetails, data.externalRenderPass);
454 for (const auto &pass : data.renderPasses) {
455 if (!pass.pixelSize.isEmpty())
457 }
458 renderPassDetails += QString::asprintf("\nGenerated from QSSGRenderLayer %p", m_layer);
459 m_results.renderPassDetails = renderPassDetails;
460
461 if (m_results.activeTextures != textures) {
462 m_results.activeTextures = textures;
463 QString texDetails = QLatin1String(R"(
464| Name | Size | Format | Mip | Flags |
465| ---- | ---- | ------ | --- | ----- |
466)");
467 QList<QRhiTexture *> textureList = textures.values();
468 std::sort(textureList.begin(), textureList.end(), [](QRhiTexture *a, QRhiTexture *b) {
469 return a->name() < b->name();
470 });
471 for (QRhiTexture *tex : textureList) {
472 int mipCount = 1;
473 const QRhiTexture::Flags flags = tex->flags();
474 if (flags.testFlag(QRhiTexture::MipMapped))
475 mipCount = m_contextStats->context.rhi()->mipLevelsForSize(tex->pixelSize());
476 QByteArray flagMsg;
477 if (flags.testFlag(QRhiTexture::CubeMap))
478 flagMsg += QByteArrayLiteral("[cube]");
479 texDetails += QString::asprintf("| %s | %dx%d | %s | %d | %s |\n",
480 tex->name().constData(),
481 tex->pixelSize().width(),
482 tex->pixelSize().height(),
483 textureFormatStr(tex->format()),
484 mipCount,
485 flagMsg.constData());
486 }
487 texDetails += QString::asprintf("\nAsset textures registered with QSSGRhiContext %p", &m_contextStats->context);
488 m_results.textureDetails = texDetails;
489 }
490
491 if (m_results.activeMeshes != meshes) {
492 m_results.activeMeshes = meshes;
494| Name | Submeshes | Vertices | V.buf size | I.buf size |
495| ---- | --------- | -------- | ---------- | ---------- |
496)");
497 QList<QSSGRenderMesh *> meshList = meshes.values();
498 std::sort(meshList.begin(), meshList.end(), [](QSSGRenderMesh *a, QSSGRenderMesh *b) {
499 return nameForRenderMesh(a) < nameForRenderMesh(b);
500 });
501 for (QSSGRenderMesh *mesh : meshList) {
502 const QByteArray name = nameForRenderMesh(mesh);
503 const int subsetCount = int(mesh->subsets.size());
504 quint64 vertexCount = 0;
505 quint32 vbufSize = 0;
506 quint32 ibufSize = 0;
507 if (subsetCount > 0) {
508 for (const QSSGRenderSubset &subset : std::as_const(mesh->subsets))
509 vertexCount += subset.count;
510 // submeshes ref into the same vertex and index buffer
511 const QSSGRhiBuffer *vbuf = mesh->subsets[0].rhi.vertexBuffer.get();
512 if (vbuf)
513 vbufSize = vbuf->buffer()->size();
514 const QSSGRhiBuffer *ibuf = mesh->subsets[0].rhi.indexBuffer.get();
515 if (ibuf)
516 ibufSize = ibuf->buffer()->size();
517 }
518 meshDetails += QString::asprintf("| %s | %d | %llu | %u | %u |\n",
519 name.constData(),
520 subsetCount,
521 vertexCount,
522 vbufSize,
523 ibufSize);
524
525 }
526 meshDetails += QString::asprintf("\nAsset meshes registered with QSSGRhiContext %p", &m_contextStats->context);
527 m_results.meshDetails = meshDetails;
528 }
529
530 m_results.pipelineCount = pipelines.count();
531
532 m_results.materialGenerationTime = m_contextStats->globalInfo.materialGenerationTime;
533 m_results.effectGenerationTime = m_contextStats->globalInfo.effectGenerationTime;
534
535 m_results.rhiStats = m_contextStats->context.rhi()->statistics();
536}
537
538void QQuick3DRenderStats::notifyRhiContextStats()
539{
540 if (!m_contextStats || !m_extendedDataCollectionEnabled)
541 return;
542
543 if (m_results.drawCallCount != m_notifiedResults.drawCallCount) {
544 m_notifiedResults.drawCallCount = m_results.drawCallCount;
546 }
547
548 if (m_results.drawVertexCount != m_notifiedResults.drawVertexCount) {
549 m_notifiedResults.drawVertexCount = m_results.drawVertexCount;
551 }
552
553 if (m_results.imageDataSize != m_notifiedResults.imageDataSize) {
554 m_notifiedResults.imageDataSize = m_results.imageDataSize;
556 }
557
558 if (m_results.meshDataSize != m_notifiedResults.meshDataSize) {
559 m_notifiedResults.meshDataSize = m_results.meshDataSize;
561 }
562
563 if (m_results.renderPassCount != m_notifiedResults.renderPassCount) {
564 m_notifiedResults.renderPassCount = m_results.renderPassCount;
566 }
567
568 if (m_results.renderPassDetails != m_notifiedResults.renderPassDetails) {
569 m_notifiedResults.renderPassDetails = m_results.renderPassDetails;
571 }
572
573 if (m_results.textureDetails != m_notifiedResults.textureDetails) {
574 m_notifiedResults.textureDetails = m_results.textureDetails;
576 }
577
578 if (m_results.meshDetails != m_notifiedResults.meshDetails) {
579 m_notifiedResults.meshDetails = m_results.meshDetails;
581 }
582
583 if (m_results.pipelineCount != m_notifiedResults.pipelineCount) {
584 m_notifiedResults.pipelineCount = m_results.pipelineCount;
586 }
587
588 if (m_results.materialGenerationTime != m_notifiedResults.materialGenerationTime) {
589 m_notifiedResults.materialGenerationTime = m_results.materialGenerationTime;
591 }
592
593 if (m_results.effectGenerationTime != m_notifiedResults.effectGenerationTime) {
594 m_notifiedResults.effectGenerationTime = m_results.effectGenerationTime;
596 }
597
598 if (m_results.rhiStats.totalPipelineCreationTime != m_notifiedResults.rhiStats.totalPipelineCreationTime) {
599 m_notifiedResults.rhiStats.totalPipelineCreationTime = m_results.rhiStats.totalPipelineCreationTime;
601 }
602
603 if (m_results.rhiStats.allocCount != m_notifiedResults.rhiStats.allocCount) {
604 m_notifiedResults.rhiStats.allocCount = m_results.rhiStats.allocCount;
606 }
607
608 if (m_results.rhiStats.usedBytes != m_notifiedResults.rhiStats.usedBytes) {
609 m_notifiedResults.rhiStats.usedBytes = m_results.rhiStats.usedBytes;
611 }
612}
613
627{
628 return m_results.drawCallCount;
629}
630
648{
649 return m_results.drawVertexCount;
650}
651
670{
671 return m_results.imageDataSize;
672}
673
692{
693 return m_results.meshDataSize;
694}
695
714{
715 return m_results.renderPassCount;
716}
717
725{
726 return m_results.renderPassDetails;
727}
728
736{
737 return m_results.textureDetails;
738}
739
747{
748 return m_results.meshDetails;
749}
750
767{
768 return m_results.pipelineCount;
769}
770
788{
789 return m_results.materialGenerationTime;
790}
791
809{
810 return m_results.effectGenerationTime;
811}
812
852{
853 return m_results.rhiStats.totalPipelineCreationTime;
854}
855
875{
876 return m_results.rhiStats.allocCount;
877}
878
898{
899 return m_results.rhiStats.usedBytes;
900}
901
912{
913 return m_graphicsApiName;
914}
915
939{
940 return m_results.lastCompletedGpuTime;
941}
942
947{
948 if (m_window)
949 m_window->releaseResources();
950 else
951 qWarning("QQuick3DRenderStats: No window, cannot request releasing cached resources");
952}
953
\inmodule QtCore
Definition qbytearray.h:57
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
qint64 restart() noexcept
Restarts the timer and returns the number of milliseconds elapsed since the previous start.
void start() noexcept
Starts this timer.
qint64 nsecsElapsed() const noexcept
\inmodule QtCore
Definition qhash.h:818
Definition qlist.h:74
iterator end()
Definition qlist.h:609
iterator begin()
Definition qlist.h:608
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
void setWindow(QQuickWindow *window)
void graphicsApiNameChanged()
QQuick3DRenderStats(QObject *parent=nullptr)
\qmltype RenderStats \inqmlmodule QtQuick3D
void lastCompletedGpuTimeChanged()
void setExtendedDataCollectionEnabled(bool enable)
void pipelineCreationTimeChanged()
void endSync(bool dump=false)
void setRhiContext(QSSGRhiContext *ctx, QSSGRenderLayer *layer)
void materialGenerationTimeChanged()
void textureDetailsChanged()
void vmemAllocCountChanged()
void drawVertexCountChanged()
Q_INVOKABLE void releaseCachedResources()
void effectGenerationTimeChanged()
void extendedDataCollectionEnabledChanged()
void endRender(bool dump=false)
void renderPassDetailsChanged()
void renderPassCountChanged()
\qmltype Window \instantiates QQuickWindow \inqmlmodule QtQuick
void releaseResources()
This function tries to release redundant resources currently held by the QML scene.
quint32 size() const
Definition qrhi.h:863
\inmodule QtGui
Definition qrhi.h:1614
\inmodule QtGui
Definition qrhi.h:1513
virtual QRhiCommandBuffer * currentFrameCommandBuffer()=0
\inmodule QtGui
Definition qrhi.h:883
@ MipMapped
Definition qrhi.h:888
@ CubeMap
Definition qrhi.h:887
Format
Specifies the texture format.
Definition qrhi.h:902
@ ASTC_10x8
Definition qrhi.h:947
@ ASTC_12x12
Definition qrhi.h:950
@ ASTC_8x5
Definition qrhi.h:942
@ ASTC_10x5
Definition qrhi.h:945
@ RGBA32F
Definition qrhi.h:914
@ ETC2_RGBA8
Definition qrhi.h:935
@ ASTC_5x5
Definition qrhi.h:939
@ ASTC_4x4
Definition qrhi.h:937
@ ASTC_6x6
Definition qrhi.h:941
@ ASTC_12x10
Definition qrhi.h:949
@ ETC2_RGB8
Definition qrhi.h:933
@ ASTC_5x4
Definition qrhi.h:938
@ RED_OR_ALPHA8
Definition qrhi.h:911
@ ASTC_6x5
Definition qrhi.h:940
@ ASTC_8x8
Definition qrhi.h:944
@ RGBA16F
Definition qrhi.h:913
@ RGB10A2
Definition qrhi.h:918
@ ASTC_10x6
Definition qrhi.h:946
@ ASTC_10x10
Definition qrhi.h:948
@ ETC2_RGB8A1
Definition qrhi.h:934
@ ASTC_8x6
Definition qrhi.h:943
QRhiStats statistics() const
Gathers and returns statistics about the timings and allocations of graphics resources.
Definition qrhi.cpp:10027
static int mipLevelsForSize(const QSize &size)
Definition qrhi.cpp:9579
const char * backendName() const
Definition qrhi.cpp:8321
QRhiBuffer * buffer() const
static quint64 totalVertexCountForPass(const QSSGRhiContextStats::RenderPassInfo &pass)
QSSGRhiContext & context
QHash< QSSGRenderLayer *, PerLayerInfo > perLayerInfo
static quint64 totalDrawCallCountForPass(const QSSGRhiContextStats::RenderPassInfo &pass)
QSet< QSSGRenderLayer * > dynamicDataSources
QSet< QSSGRenderMesh * > registeredMeshes() const
QRhi * rhi() const
QSet< QRhiTexture * > registeredTextures() const
QHash< QSSGGraphicsPipelineStateKey, QRhiGraphicsPipeline * > pipelines() const
Definition qset.h:18
QList< T > values() const
Definition qset.h:297
bool remove(const T &value)
Definition qset.h:63
iterator insert(const T &value)
Definition qset.h:155
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
static QString static QString asprintf(const char *format,...) Q_ATTRIBUTE_FORMAT_PRINTF(1
Definition qstring.cpp:7005
EGLContext ctx
Combined button and popup list for selecting options.
@ DirectConnection
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
EGLOutputLayerEXT layer
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:303
static QString backendName
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLboolean GLboolean GLboolean b
const GLuint * pipelines
GLboolean GLboolean GLboolean GLboolean a
[7]
GLuint const GLuint GLuint const GLuint * textures
GLenum GLenum GLsizei count
GLenum GLenum dst
GLenum GLuint GLenum GLsizei const GLchar * buf
GLbitfield flags
GLboolean enable
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint name
GLint GLsizei GLsizei GLenum format
static QByteArray nameForRenderMesh(const QSSGRenderMesh *mesh)
static void printRenderPassDetails(QString *dst, const QSSGRhiContextStats::RenderPassInfo &rp)
static const char * textureFormatStr(QRhiTexture::Format format)
static QString dump(const QByteArray &)
SSL_CTX int(* cb)(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define emit
unsigned int quint32
Definition qtypes.h:45
unsigned long long quint64
Definition qtypes.h:56
long long qint64
Definition qtypes.h:55
myObject disconnect()
[26]
aWidget window() -> setWindowTitle("New Window Title")
[2]
QVector< QSSGRenderSubset > subsets
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent