Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
23 : QObject(parent)
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 = &QSSGRhiContextStats::get(*ctx);
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->rhiCtx->rhi()) {
243 const QString backendName = QString::fromUtf8(m_contextStats->rhiCtx->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
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 const QSSGRhiContext *rhiCtx = m_contextStats->rhiCtx;
427 const QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
428
429 // textures and meshes include all assets registered to the per-QQuickWindow QSSGRhiContext
430 const QSSGRhiContextStats::GlobalInfo globalData = m_contextStats->globalInfo;
431 const auto textures = rhiCtxD->m_textures;
432 const auto meshes = rhiCtxD->m_meshes;
433 const auto pipelines = rhiCtxD->m_pipelines;
434
435 m_results.drawCallCount = 0;
436 m_results.drawVertexCount = 0;
437 for (const auto &pass : data.renderPasses) {
438 m_results.drawCallCount += QSSGRhiContextStats::totalDrawCallCountForPass(pass);
439 m_results.drawVertexCount += QSSGRhiContextStats::totalVertexCountForPass(pass);
440 }
441 m_results.drawCallCount += QSSGRhiContextStats::totalDrawCallCountForPass(data.externalRenderPass);
442 m_results.drawVertexCount += QSSGRhiContextStats::totalVertexCountForPass(data.externalRenderPass);
443
444 m_results.imageDataSize = globalData.imageDataSize;
445 m_results.meshDataSize = globalData.meshDataSize;
446
447 m_results.renderPassCount = data.renderPasses.size()
448 + (data.externalRenderPass.pixelSize.isEmpty() ? 0 : 1);
449
451| Name | Size | Vertices | Draw calls |
452| ---- | ---- | -------- | ---------- |
453)");
454
455 if (!data.externalRenderPass.pixelSize.isEmpty())
456 printRenderPassDetails(&renderPassDetails, data.externalRenderPass);
457 for (const auto &pass : data.renderPasses) {
458 if (!pass.pixelSize.isEmpty())
460 }
461 renderPassDetails += QString::asprintf("\nGenerated from QSSGRenderLayer %p", m_layer);
462 m_results.renderPassDetails = renderPassDetails;
463
464 if (m_results.activeTextures != textures) {
465 m_results.activeTextures = textures;
466 QString texDetails = QLatin1String(R"(
467| Name | Size | Format | Mip | Flags |
468| ---- | ---- | ------ | --- | ----- |
469)");
470 QList<QRhiTexture *> textureList = textures.values();
471 std::sort(textureList.begin(), textureList.end(), [](QRhiTexture *a, QRhiTexture *b) {
472 return a->name() < b->name();
473 });
474 for (QRhiTexture *tex : textureList) {
475 int mipCount = 1;
476 const QRhiTexture::Flags flags = tex->flags();
477 if (flags.testFlag(QRhiTexture::MipMapped))
478 mipCount = m_contextStats->rhiCtx->rhi()->mipLevelsForSize(tex->pixelSize());
479 QByteArray flagMsg;
480 if (flags.testFlag(QRhiTexture::CubeMap))
481 flagMsg += QByteArrayLiteral("[cube]");
482 texDetails += QString::asprintf("| %s | %dx%d | %s | %d | %s |\n",
483 tex->name().constData(),
484 tex->pixelSize().width(),
485 tex->pixelSize().height(),
486 textureFormatStr(tex->format()),
487 mipCount,
488 flagMsg.constData());
489 }
490 texDetails += QString::asprintf("\nAsset textures registered with QSSGRhiContext %p", m_contextStats->rhiCtx);
491 m_results.textureDetails = texDetails;
492 }
493
494 if (m_results.activeMeshes != meshes) {
495 m_results.activeMeshes = meshes;
497| Name | Submeshes | Vertices | V.buf size | I.buf size |
498| ---- | --------- | -------- | ---------- | ---------- |
499)");
500 QList<QSSGRenderMesh *> meshList = meshes.values();
501 std::sort(meshList.begin(), meshList.end(), [](QSSGRenderMesh *a, QSSGRenderMesh *b) {
502 return nameForRenderMesh(a) < nameForRenderMesh(b);
503 });
504 for (QSSGRenderMesh *mesh : meshList) {
505 const QByteArray name = nameForRenderMesh(mesh);
506 const int subsetCount = int(mesh->subsets.size());
507 quint64 vertexCount = 0;
508 quint32 vbufSize = 0;
509 quint32 ibufSize = 0;
510 if (subsetCount > 0) {
511 for (const QSSGRenderSubset &subset : std::as_const(mesh->subsets))
512 vertexCount += subset.count;
513 // submeshes ref into the same vertex and index buffer
514 const QSSGRhiBuffer *vbuf = mesh->subsets[0].rhi.vertexBuffer.get();
515 if (vbuf)
516 vbufSize = vbuf->buffer()->size();
517 const QSSGRhiBuffer *ibuf = mesh->subsets[0].rhi.indexBuffer.get();
518 if (ibuf)
519 ibufSize = ibuf->buffer()->size();
520 }
521 meshDetails += QString::asprintf("| %s | %d | %llu | %u | %u |\n",
522 name.constData(),
523 subsetCount,
524 vertexCount,
525 vbufSize,
526 ibufSize);
527
528 }
529 meshDetails += QString::asprintf("\nAsset meshes registered with QSSGRhiContext %p", m_contextStats->rhiCtx);
530 m_results.meshDetails = meshDetails;
531 }
532
533 m_results.pipelineCount = pipelines.count();
534
535 m_results.materialGenerationTime = m_contextStats->globalInfo.materialGenerationTime;
536 m_results.effectGenerationTime = m_contextStats->globalInfo.effectGenerationTime;
537
538 m_results.rhiStats = m_contextStats->rhiCtx->rhi()->statistics();
539}
540
541void QQuick3DRenderStats::notifyRhiContextStats()
542{
543 if (!m_contextStats || !m_extendedDataCollectionEnabled)
544 return;
545
546 if (m_results.drawCallCount != m_notifiedResults.drawCallCount) {
547 m_notifiedResults.drawCallCount = m_results.drawCallCount;
549 }
550
551 if (m_results.drawVertexCount != m_notifiedResults.drawVertexCount) {
552 m_notifiedResults.drawVertexCount = m_results.drawVertexCount;
554 }
555
556 if (m_results.imageDataSize != m_notifiedResults.imageDataSize) {
557 m_notifiedResults.imageDataSize = m_results.imageDataSize;
559 }
560
561 if (m_results.meshDataSize != m_notifiedResults.meshDataSize) {
562 m_notifiedResults.meshDataSize = m_results.meshDataSize;
564 }
565
566 if (m_results.renderPassCount != m_notifiedResults.renderPassCount) {
567 m_notifiedResults.renderPassCount = m_results.renderPassCount;
569 }
570
571 if (m_results.renderPassDetails != m_notifiedResults.renderPassDetails) {
572 m_notifiedResults.renderPassDetails = m_results.renderPassDetails;
574 }
575
576 if (m_results.textureDetails != m_notifiedResults.textureDetails) {
577 m_notifiedResults.textureDetails = m_results.textureDetails;
579 }
580
581 if (m_results.meshDetails != m_notifiedResults.meshDetails) {
582 m_notifiedResults.meshDetails = m_results.meshDetails;
584 }
585
586 if (m_results.pipelineCount != m_notifiedResults.pipelineCount) {
587 m_notifiedResults.pipelineCount = m_results.pipelineCount;
589 }
590
591 if (m_results.materialGenerationTime != m_notifiedResults.materialGenerationTime) {
592 m_notifiedResults.materialGenerationTime = m_results.materialGenerationTime;
594 }
595
596 if (m_results.effectGenerationTime != m_notifiedResults.effectGenerationTime) {
597 m_notifiedResults.effectGenerationTime = m_results.effectGenerationTime;
599 }
600
601 if (m_results.rhiStats.totalPipelineCreationTime != m_notifiedResults.rhiStats.totalPipelineCreationTime) {
602 m_notifiedResults.rhiStats.totalPipelineCreationTime = m_results.rhiStats.totalPipelineCreationTime;
604 }
605
606 if (m_results.rhiStats.allocCount != m_notifiedResults.rhiStats.allocCount) {
607 m_notifiedResults.rhiStats.allocCount = m_results.rhiStats.allocCount;
609 }
610
611 if (m_results.rhiStats.usedBytes != m_notifiedResults.rhiStats.usedBytes) {
612 m_notifiedResults.rhiStats.usedBytes = m_results.rhiStats.usedBytes;
614 }
615}
616
630{
631 return m_results.drawCallCount;
632}
633
651{
652 return m_results.drawVertexCount;
653}
654
673{
674 return m_results.imageDataSize;
675}
676
695{
696 return m_results.meshDataSize;
697}
698
717{
718 return m_results.renderPassCount;
719}
720
728{
729 return m_results.renderPassDetails;
730}
731
739{
740 return m_results.textureDetails;
741}
742
750{
751 return m_results.meshDetails;
752}
753
770{
771 return m_results.pipelineCount;
772}
773
791{
792 return m_results.materialGenerationTime;
793}
794
812{
813 return m_results.effectGenerationTime;
814}
815
855{
856 return m_results.rhiStats.totalPipelineCreationTime;
857}
858
878{
879 return m_results.rhiStats.allocCount;
880}
881
901{
902 return m_results.rhiStats.usedBytes;
903}
904
915{
916 return m_graphicsApiName;
917}
918
942{
943 return m_results.lastCompletedGpuTime;
944}
945
950{
951 if (m_window)
952 m_window->releaseResources();
953 else
954 qWarning("QQuick3DRenderStats: No window, cannot request releasing cached resources");
955}
956
\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:124
qint64 restart() noexcept
Restarts the timer and returns the number of milliseconds elapsed since the previous start.
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
qint64 nsecsElapsed() const noexcept
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
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:875
\inmodule QtGui
Definition qrhi.h:1651
\inmodule QtGui
Definition qrhi.h:1549
virtual QRhiCommandBuffer * currentFrameCommandBuffer()=0
\inmodule QtGui
Definition qrhi.h:895
@ MipMapped
Definition qrhi.h:900
@ CubeMap
Definition qrhi.h:899
Format
Specifies the texture format.
Definition qrhi.h:914
@ ASTC_10x8
Definition qrhi.h:959
@ ASTC_12x12
Definition qrhi.h:962
@ ASTC_8x5
Definition qrhi.h:954
@ ASTC_10x5
Definition qrhi.h:957
@ RGBA32F
Definition qrhi.h:926
@ ETC2_RGBA8
Definition qrhi.h:947
@ ASTC_5x5
Definition qrhi.h:951
@ ASTC_4x4
Definition qrhi.h:949
@ ASTC_6x6
Definition qrhi.h:953
@ ASTC_12x10
Definition qrhi.h:961
@ ETC2_RGB8
Definition qrhi.h:945
@ ASTC_5x4
Definition qrhi.h:950
@ RED_OR_ALPHA8
Definition qrhi.h:923
@ ASTC_6x5
Definition qrhi.h:952
@ ASTC_8x8
Definition qrhi.h:956
@ RGBA16F
Definition qrhi.h:925
@ RGB10A2
Definition qrhi.h:930
@ ASTC_10x6
Definition qrhi.h:958
@ ASTC_10x10
Definition qrhi.h:960
@ ETC2_RGB8A1
Definition qrhi.h:946
@ ASTC_8x6
Definition qrhi.h:955
QRhiStats statistics() const
Gathers and returns statistics about the timings and allocations of graphics resources.
Definition qrhi.cpp:10456
static int mipLevelsForSize(const QSize &size)
Definition qrhi.cpp:10008
const char * backendName() const
Definition qrhi.cpp:8683
QRhiBuffer * buffer() const
static QSSGRhiContextPrivate * get(QSSGRhiContext *q)
QSSGRhiContext * rhiCtx
static quint64 totalVertexCountForPass(const QSSGRhiContextStats::RenderPassInfo &pass)
static QSSGRhiContextStats & get(QSSGRhiContext &rhiCtx)
QHash< QSSGRenderLayer *, PerLayerInfo > perLayerInfo
static quint64 totalDrawCallCountForPass(const QSSGRhiContextStats::RenderPassInfo &pass)
QSet< QSSGRenderLayer * > dynamicDataSources
\inmodule QtQuick3D
QRhi * rhi() const
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:133
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:130
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
static QString static QString asprintf(const char *format,...) Q_ATTRIBUTE_FORMAT_PRINTF(1
Definition qstring.cpp:7263
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:349
static QString backendName
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
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
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLenum dst
GLenum GLuint GLenum GLsizei const GLchar * buf
GLbitfield flags
GLboolean enable
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:50
unsigned long long quint64
Definition qtypes.h:61
long long qint64
Definition qtypes.h:60
myObject disconnect()
[26]
aWidget window() -> setWindowTitle("New Window Title")
[2]
quint64 usedBytes
Definition qrhi.h:1787
quint32 allocCount
Definition qrhi.h:1786
qint64 totalPipelineCreationTime
Definition qrhi.h:1783
QVector< QSSGRenderSubset > subsets