| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the examples of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:BSD$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** BSD License Usage |
| ** Alternatively, you may use this file under the terms of the BSD license |
| ** as follows: |
| ** |
| ** "Redistribution and use in source and binary forms, with or without |
| ** modification, are permitted provided that the following conditions are |
| ** met: |
| ** * Redistributions of source code must retain the above copyright |
| ** notice, this list of conditions and the following disclaimer. |
| ** * Redistributions in binary form must reproduce the above copyright |
| ** notice, this list of conditions and the following disclaimer in |
| ** the documentation and/or other materials provided with the |
| ** distribution. |
| ** * Neither the name of The Qt Company Ltd nor the names of its |
| ** contributors may be used to endorse or promote products derived |
| ** from this software without specific prior written permission. |
| ** |
| ** |
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| // Simple QRhiProfiler receiver app. Start it and then in a QRhi-based |
| // application connect with a QTcpSocket to 127.0.0.1:30667 and set that as the |
| // QRhiProfiler's device. |
| |
| #include <QTcpServer> |
| #include <QTcpSocket> |
| #include <QApplication> |
| #include <QWidget> |
| #include <QVBoxLayout> |
| #include <QGroupBox> |
| #include <QTextEdit> |
| #include <QLabel> |
| #include <QTime> |
| #include <QtGui/private/qrhiprofiler_p.h> |
| |
| const int MIN_KNOWN_OP = 1; |
| const int MAX_KNOWN_OP = 18; |
| |
| class Parser : public QObject |
| { |
| Q_OBJECT |
| |
| public: |
| void feed(const QByteArray &line); |
| |
| struct Event { |
| QRhiProfiler::StreamOp op; |
| qint64 timestamp; |
| quint64 resource; |
| QByteArray resourceName; |
| |
| struct Param { |
| enum ValueType { |
| Int64, |
| Float |
| }; |
| QByteArray key; |
| ValueType valueType; |
| union { |
| qint64 intValue; |
| float floatValue; |
| }; |
| }; |
| |
| QVector<Param> params; |
| |
| const Param *param(const char *key) const { |
| auto it = std::find_if(params.cbegin(), params.cend(), [key](const Param &p) { |
| return !strcmp(p.key.constData(), key); |
| }); |
| return it == params.cend() ? nullptr : &*it; |
| } |
| }; |
| |
| signals: |
| void eventReceived(const Event &e); |
| }; |
| |
| void Parser::feed(const QByteArray &line) |
| { |
| const QList<QByteArray> elems = line.split(','); |
| if (elems.count() < 4) { |
| qWarning("Malformed line '%s'", line.constData()); |
| return; |
| } |
| bool ok = false; |
| const int op = elems[0].toInt(&ok); |
| if (!ok) { |
| qWarning("Invalid op %s", elems[0].constData()); |
| return; |
| } |
| if (op < MIN_KNOWN_OP || op > MAX_KNOWN_OP) { |
| qWarning("Unknown op %d", op); |
| return; |
| } |
| |
| Event e; |
| e.op = QRhiProfiler::StreamOp(op); |
| e.timestamp = elems[1].toLongLong(); |
| e.resource = elems[2].toULongLong(); |
| e.resourceName = elems[3]; |
| |
| const int elemCount = elems.count(); |
| for (int i = 4; i < elemCount; i += 2) { |
| if (i + 1 < elemCount && !elems[i].isEmpty() && !elems[i + 1].isEmpty()) { |
| QByteArray key = elems[i]; |
| if (key.startsWith('F')) { |
| key = key.mid(1); |
| bool ok = false; |
| const float value = elems[i + 1].toFloat(&ok); |
| if (!ok) { |
| qWarning("Failed to parse float %s in line '%s'", elems[i + 1].constData(), line.constData()); |
| continue; |
| } |
| Event::Param param; |
| param.key = key; |
| param.valueType = Event::Param::Float; |
| param.floatValue = value; |
| e.params.append(param); |
| } else { |
| const qint64 value = elems[i + 1].toLongLong(); |
| Event::Param param; |
| param.key = key; |
| param.valueType = Event::Param::Int64; |
| param.intValue = value; |
| e.params.append(param); |
| } |
| } |
| } |
| |
| emit eventReceived(e); |
| } |
| |
| class Tracker : public QObject |
| { |
| Q_OBJECT |
| |
| public slots: |
| void handleEvent(const Parser::Event &e); |
| |
| signals: |
| void buffersTouched(); |
| void texturesTouched(); |
| void swapchainsTouched(); |
| void frameTimeTouched(); |
| void gpuFrameTimeTouched(); |
| void gpuMemAllocStatsTouched(); |
| |
| public: |
| Tracker() { |
| reset(); |
| } |
| |
| static const int MAX_STAGING_SLOTS = 3; |
| |
| struct Buffer { |
| Buffer() |
| { |
| memset(stagingExtraSize, 0, sizeof(stagingExtraSize)); |
| } |
| quint64 lastTimestamp; |
| QByteArray resourceName; |
| qint64 effectiveSize = 0; |
| int backingGpuBufCount = 1; |
| qint64 stagingExtraSize[MAX_STAGING_SLOTS]; |
| }; |
| QHash<qint64, Buffer> m_buffers; |
| qint64 m_totalBufferApproxByteSize; |
| qint64 m_peakBufferApproxByteSize; |
| qint64 m_totalStagingBufferApproxByteSize; |
| qint64 m_peakStagingBufferApproxByteSize; |
| |
| struct Texture { |
| Texture() |
| { |
| memset(stagingExtraSize, 0, sizeof(stagingExtraSize)); |
| } |
| quint64 lastTimestamp; |
| QByteArray resourceName; |
| qint64 approxByteSize = 0; |
| bool ownsNativeResource = true; |
| qint64 stagingExtraSize[MAX_STAGING_SLOTS]; |
| }; |
| QHash<qint64, Texture> m_textures; |
| qint64 m_totalTextureApproxByteSize; |
| qint64 m_peakTextureApproxByteSize; |
| qint64 m_totalTextureStagingBufferApproxByteSize; |
| qint64 m_peakTextureStagingBufferApproxByteSize; |
| |
| struct SwapChain { |
| quint64 lastTimestamp; |
| QByteArray resourceName; |
| qint64 approxByteSize = 0; |
| }; |
| QHash<qint64, SwapChain> m_swapchains; |
| qint64 m_totalSwapChainApproxByteSize; |
| qint64 m_peakSwapChainApproxByteSize; |
| |
| struct FrameTime { |
| qint64 framesSinceResize = 0; |
| int minDelta = 0; |
| int maxDelta = 0; |
| float avgDelta = 0; |
| }; |
| FrameTime m_lastFrameTime; |
| |
| struct GpuFrameTime { |
| float minTime = 0; |
| float maxTime = 0; |
| float avgTime = 0; |
| }; |
| GpuFrameTime m_lastGpuFrameTime; |
| |
| struct GpuMemAllocStats { |
| qint64 realAllocCount; |
| qint64 subAllocCount; |
| qint64 totalSize; |
| qint64 unusedSize; |
| }; |
| GpuMemAllocStats m_lastGpuMemAllocStats; |
| |
| void reset() { |
| m_buffers.clear(); |
| m_textures.clear(); |
| m_totalBufferApproxByteSize = 0; |
| m_peakBufferApproxByteSize = 0; |
| m_totalStagingBufferApproxByteSize = 0; |
| m_peakStagingBufferApproxByteSize = 0; |
| m_totalTextureApproxByteSize = 0; |
| m_peakTextureApproxByteSize = 0; |
| m_totalTextureStagingBufferApproxByteSize = 0; |
| m_peakTextureStagingBufferApproxByteSize = 0; |
| m_totalSwapChainApproxByteSize = 0; |
| m_peakSwapChainApproxByteSize = 0; |
| m_lastFrameTime = FrameTime(); |
| m_lastGpuFrameTime = GpuFrameTime(); |
| m_lastGpuMemAllocStats = GpuMemAllocStats(); |
| emit buffersTouched(); |
| emit texturesTouched(); |
| emit swapchainsTouched(); |
| emit frameTimeTouched(); |
| emit gpuFrameTimeTouched(); |
| emit gpuMemAllocStatsTouched(); |
| } |
| }; |
| |
| Q_DECLARE_TYPEINFO(Tracker::Buffer, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Tracker::Texture, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Tracker::FrameTime, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Tracker::GpuFrameTime, Q_MOVABLE_TYPE); |
| |
| void Tracker::handleEvent(const Parser::Event &e) |
| { |
| switch (e.op) { |
| case QRhiProfiler::NewBuffer: |
| { |
| Buffer b; |
| b.lastTimestamp = e.timestamp; |
| b.resourceName = e.resourceName; |
| // type,0,usage,1,logical_size,84,effective_size,84,backing_gpu_buf_count,1,backing_cpu_buf_count,0 |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("effective_size")) |
| b.effectiveSize = p.intValue; |
| else if (p.key == QByteArrayLiteral("backing_gpu_buf_count")) |
| b.backingGpuBufCount = p.intValue; |
| } |
| m_totalBufferApproxByteSize += b.effectiveSize * b.backingGpuBufCount; |
| m_peakBufferApproxByteSize = qMax(m_peakBufferApproxByteSize, m_totalBufferApproxByteSize); |
| m_buffers.insert(e.resource, b); |
| emit buffersTouched(); |
| } |
| break; |
| case QRhiProfiler::ReleaseBuffer: |
| { |
| auto it = m_buffers.find(e.resource); |
| if (it != m_buffers.end()) { |
| m_totalBufferApproxByteSize -= it->effectiveSize * it->backingGpuBufCount; |
| m_buffers.erase(it); |
| emit buffersTouched(); |
| } |
| } |
| break; |
| |
| case QRhiProfiler::NewBufferStagingArea: |
| { |
| qint64 slot = -1; |
| qint64 size = 0; |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("slot")) |
| slot = p.intValue; |
| else if (p.key == QByteArrayLiteral("size")) |
| size = p.intValue; |
| } |
| if (slot >= 0 && slot < MAX_STAGING_SLOTS) { |
| auto it = m_buffers.find(e.resource); |
| if (it != m_buffers.end()) { |
| it->stagingExtraSize[slot] = size; |
| m_totalStagingBufferApproxByteSize += size; |
| m_peakStagingBufferApproxByteSize = qMax(m_peakStagingBufferApproxByteSize, m_totalStagingBufferApproxByteSize); |
| emit buffersTouched(); |
| } |
| } |
| } |
| break; |
| case QRhiProfiler::ReleaseBufferStagingArea: |
| { |
| qint64 slot = -1; |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("slot")) |
| slot = p.intValue; |
| } |
| if (slot >= 0 && slot < MAX_STAGING_SLOTS) { |
| auto it = m_buffers.find(e.resource); |
| if (it != m_buffers.end()) { |
| m_totalStagingBufferApproxByteSize -= it->stagingExtraSize[slot]; |
| it->stagingExtraSize[slot] = 0; |
| emit buffersTouched(); |
| } |
| } |
| } |
| break; |
| |
| case QRhiProfiler::NewTexture: |
| { |
| Texture t; |
| t.lastTimestamp = e.timestamp; |
| t.resourceName = e.resourceName; |
| // width,256,height,256,format,1,owns_native_resource,1,mip_count,9,layer_count,1,effective_sample_count,1,approx_byte_size,349524 |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("approx_byte_size")) |
| t.approxByteSize = p.intValue; |
| else if (p.key == QByteArrayLiteral("owns_native_resource")) |
| t.ownsNativeResource = p.intValue; |
| } |
| if (t.ownsNativeResource) { |
| m_totalTextureApproxByteSize += t.approxByteSize; |
| m_peakTextureApproxByteSize = qMax(m_peakTextureApproxByteSize, m_totalTextureApproxByteSize); |
| } |
| m_textures.insert(e.resource, t); |
| emit texturesTouched(); |
| } |
| break; |
| case QRhiProfiler::ReleaseTexture: |
| { |
| auto it = m_textures.find(e.resource); |
| if (it != m_textures.end()) { |
| if (it->ownsNativeResource) |
| m_totalTextureApproxByteSize -= it->approxByteSize; |
| m_textures.erase(it); |
| emit texturesTouched(); |
| } |
| } |
| break; |
| |
| case QRhiProfiler::NewTextureStagingArea: |
| { |
| qint64 slot = -1; |
| qint64 size = 0; |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("slot")) |
| slot = p.intValue; |
| else if (p.key == QByteArrayLiteral("size")) |
| size = p.intValue; |
| } |
| if (slot >= 0 && slot < MAX_STAGING_SLOTS) { |
| auto it = m_textures.find(e.resource); |
| if (it != m_textures.end()) { |
| it->stagingExtraSize[slot] = size; |
| m_totalTextureStagingBufferApproxByteSize += size; |
| m_peakTextureStagingBufferApproxByteSize = qMax(m_peakTextureStagingBufferApproxByteSize, m_totalTextureStagingBufferApproxByteSize); |
| emit texturesTouched(); |
| } |
| } |
| } |
| break; |
| case QRhiProfiler::ReleaseTextureStagingArea: |
| { |
| qint64 slot = -1; |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("slot")) |
| slot = p.intValue; |
| } |
| if (slot >= 0 && slot < MAX_STAGING_SLOTS) { |
| auto it = m_textures.find(e.resource); |
| if (it != m_textures.end()) { |
| m_totalTextureStagingBufferApproxByteSize -= it->stagingExtraSize[slot]; |
| it->stagingExtraSize[slot] = 0; |
| emit texturesTouched(); |
| } |
| } |
| } |
| break; |
| |
| case QRhiProfiler::ResizeSwapChain: |
| { |
| auto it = m_swapchains.find(e.resource); |
| if (it != m_swapchains.end()) |
| m_totalSwapChainApproxByteSize -= it->approxByteSize; |
| |
| SwapChain s; |
| s.lastTimestamp = e.timestamp; |
| s.resourceName = e.resourceName; |
| // width,1280,height,720,buffer_count,2,msaa_buffer_count,0,effective_sample_count,1,approx_total_byte_size,7372800 |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("approx_total_byte_size")) |
| s.approxByteSize = p.intValue; |
| } |
| m_totalSwapChainApproxByteSize += s.approxByteSize; |
| m_peakSwapChainApproxByteSize = qMax(m_peakSwapChainApproxByteSize, m_totalSwapChainApproxByteSize); |
| m_swapchains.insert(e.resource, s); |
| emit swapchainsTouched(); |
| } |
| break; |
| case QRhiProfiler::ReleaseSwapChain: |
| { |
| auto it = m_swapchains.find(e.resource); |
| if (it != m_swapchains.end()) { |
| m_totalSwapChainApproxByteSize -= it->approxByteSize; |
| m_swapchains.erase(it); |
| emit swapchainsTouched(); |
| } |
| } |
| break; |
| |
| case QRhiProfiler::GpuFrameTime: |
| { |
| // Fmin_ms_gpu_frame_time,0.15488,Fmax_ms_gpu_frame_time,0.494592,Favg_ms_gpu_frame_time,0.33462 |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("min_ms_gpu_frame_time")) |
| m_lastGpuFrameTime.minTime = p.floatValue; |
| else if (p.key == QByteArrayLiteral("max_ms_gpu_frame_time")) |
| m_lastGpuFrameTime.maxTime = p.floatValue; |
| else if (p.key == QByteArrayLiteral("avg_ms_gpu_frame_time")) |
| m_lastGpuFrameTime.avgTime = p.floatValue; |
| } |
| emit gpuFrameTimeTouched(); |
| } |
| break; |
| case QRhiProfiler::FrameToFrameTime: |
| { |
| // frames_since_resize,121,min_ms_frame_delta,9,max_ms_frame_delta,33,Favg_ms_frame_delta,16.1167 |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("frames_since_resize")) |
| m_lastFrameTime.framesSinceResize = p.intValue; |
| else if (p.key == QByteArrayLiteral("min_ms_frame_delta")) |
| m_lastFrameTime.minDelta = p.intValue; |
| else if (p.key == QByteArrayLiteral("max_ms_frame_delta")) |
| m_lastFrameTime.maxDelta = p.intValue; |
| else if (p.key == QByteArrayLiteral("avg_ms_frame_delta")) |
| m_lastFrameTime.avgDelta = p.floatValue; |
| } |
| emit frameTimeTouched(); |
| } |
| break; |
| |
| case QRhiProfiler::GpuMemAllocStats: |
| { |
| // real_alloc_count,2,sub_alloc_count,154,total_size,10752,unused_size,50320896 |
| for (const Parser::Event::Param &p : e.params) { |
| if (p.key == QByteArrayLiteral("real_alloc_count")) |
| m_lastGpuMemAllocStats.realAllocCount = p.intValue; |
| else if (p.key == QByteArrayLiteral("sub_alloc_count")) |
| m_lastGpuMemAllocStats.subAllocCount = p.intValue; |
| else if (p.key == QByteArrayLiteral("total_size")) |
| m_lastGpuMemAllocStats.totalSize = p.intValue; |
| else if (p.key == QByteArrayLiteral("unused_size")) |
| m_lastGpuMemAllocStats.unusedSize = p.intValue; |
| } |
| emit gpuMemAllocStatsTouched(); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| class Server : public QTcpServer |
| { |
| Q_OBJECT |
| |
| protected: |
| void incomingConnection(qintptr socketDescriptor) override; |
| |
| signals: |
| void clientConnected(); |
| void clientDisconnected(); |
| void receiveStarted(); |
| void lineReceived(const QByteArray &line); |
| |
| private: |
| bool m_valid = false; |
| QTcpSocket m_socket; |
| QByteArray m_buf; |
| }; |
| |
| void Server::incomingConnection(qintptr socketDescriptor) |
| { |
| if (m_valid) |
| return; |
| |
| m_socket.setSocketDescriptor(socketDescriptor); |
| m_valid = true; |
| emit clientConnected(); |
| connect(&m_socket, &QAbstractSocket::readyRead, this, [this] { |
| bool receiveStartedSent = false; |
| m_buf += m_socket.readAll(); |
| while (m_buf.contains('\n')) { |
| const int lfpos = m_buf.indexOf('\n'); |
| const QByteArray line = m_buf.left(lfpos).trimmed(); |
| m_buf = m_buf.mid(lfpos + 1); |
| if (!receiveStartedSent) { |
| receiveStartedSent = true; |
| emit receiveStarted(); |
| } |
| emit lineReceived(line); |
| } |
| }); |
| connect(&m_socket, &QAbstractSocket::disconnected, this, [this] { |
| if (m_valid) { |
| m_valid = false; |
| emit clientDisconnected(); |
| } |
| }); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| QApplication app(argc, argv); |
| |
| Tracker tracker; |
| Parser parser; |
| QObject::connect(&parser, &Parser::eventReceived, &tracker, &Tracker::handleEvent); |
| |
| Server server; |
| if (!server.listen(QHostAddress::Any, 30667)) |
| qFatal("Failed to start server: %s", qPrintable(server.errorString())); |
| |
| QVBoxLayout *layout = new QVBoxLayout; |
| |
| QLabel *infoLabel = new QLabel(QLatin1String("<i>Launch a Qt Quick application with QSG_RHI_PROFILE=1 and QSG_RHI_PROFILE_HOST set to the IP address.<br>" |
| "(resource memory usage reporting works best with the Vulkan backend)</i>")); |
| layout->addWidget(infoLabel); |
| |
| QGroupBox *groupBox = new QGroupBox(QLatin1String("RHI statistics")); |
| QVBoxLayout *groupLayout = new QVBoxLayout; |
| |
| QLabel *buffersLabel = new QLabel; |
| QObject::connect(&tracker, &Tracker::buffersTouched, buffersLabel, [buffersLabel, &tracker] { |
| const QString msg = QString::asprintf("%d buffers with ca. %lld bytes of current memory (sub)allocations (peak %lld) + %lld bytes of known staging buffers (peak %lld)", |
| tracker.m_buffers.count(), |
| tracker.m_totalBufferApproxByteSize, tracker.m_peakBufferApproxByteSize, |
| tracker.m_totalStagingBufferApproxByteSize, tracker.m_peakStagingBufferApproxByteSize); |
| buffersLabel->setText(msg); |
| }); |
| groupLayout->addWidget(buffersLabel); |
| |
| QLabel *texturesLabel = new QLabel; |
| QObject::connect(&tracker, &Tracker::texturesTouched, texturesLabel, [texturesLabel, &tracker] { |
| const QString msg = QString::asprintf("%d textures with ca. %lld bytes of current memory (sub)allocations (peak %lld) + %lld bytes of known staging buffers (peak %lld)", |
| tracker.m_textures.count(), |
| tracker.m_totalTextureApproxByteSize, tracker.m_peakTextureApproxByteSize, |
| tracker.m_totalTextureStagingBufferApproxByteSize, tracker.m_peakTextureStagingBufferApproxByteSize); |
| texturesLabel->setText(msg); |
| }); |
| groupLayout->addWidget(texturesLabel); |
| |
| QLabel *swapchainsLabel = new QLabel; |
| QObject::connect(&tracker, &Tracker::swapchainsTouched, swapchainsLabel, [swapchainsLabel, &tracker] { |
| const QString msg = QString::asprintf("Estimated total swapchain color buffer size is %lld bytes (peak %lld)", |
| tracker.m_totalSwapChainApproxByteSize, tracker.m_peakSwapChainApproxByteSize); |
| swapchainsLabel->setText(msg); |
| }); |
| groupLayout->addWidget(swapchainsLabel); |
| |
| QLabel *frameTimeLabel = new QLabel; |
| QObject::connect(&tracker, &Tracker::frameTimeTouched, frameTimeLabel, [frameTimeLabel, &tracker] { |
| const QString msg = QString::asprintf("Frames since resize %lld Frame delta min %d ms max %d ms avg %f ms", |
| tracker.m_lastFrameTime.framesSinceResize, |
| tracker.m_lastFrameTime.minDelta, |
| tracker.m_lastFrameTime.maxDelta, |
| tracker.m_lastFrameTime.avgDelta); |
| frameTimeLabel->setText(msg); |
| }); |
| groupLayout->addWidget(frameTimeLabel); |
| |
| QLabel *gpuFrameTimeLabel = new QLabel; |
| QObject::connect(&tracker, &Tracker::gpuFrameTimeTouched, gpuFrameTimeLabel, [gpuFrameTimeLabel, &tracker] { |
| const QString msg = QString::asprintf("GPU frame time min %f ms max %f ms avg %f ms", |
| tracker.m_lastGpuFrameTime.minTime, |
| tracker.m_lastGpuFrameTime.maxTime, |
| tracker.m_lastGpuFrameTime.avgTime); |
| gpuFrameTimeLabel->setText(msg); |
| }); |
| groupLayout->addWidget(gpuFrameTimeLabel); |
| |
| QLabel *gpuMemAllocStatsLabel = new QLabel; |
| QObject::connect(&tracker, &Tracker::gpuMemAllocStatsTouched, gpuMemAllocStatsLabel, [gpuMemAllocStatsLabel, &tracker] { |
| const QString msg = QString::asprintf("GPU memory allocator status: %lld real allocations %lld sub-allocations %lld total bytes %lld unused bytes", |
| tracker.m_lastGpuMemAllocStats.realAllocCount, |
| tracker.m_lastGpuMemAllocStats.subAllocCount, |
| tracker.m_lastGpuMemAllocStats.totalSize, |
| tracker.m_lastGpuMemAllocStats.unusedSize); |
| gpuMemAllocStatsLabel->setText(msg); |
| }); |
| groupLayout->addWidget(gpuMemAllocStatsLabel); |
| |
| groupBox->setLayout(groupLayout); |
| layout->addWidget(groupBox); |
| |
| QTextEdit *rawLog = new QTextEdit; |
| rawLog->setReadOnly(true); |
| layout->addWidget(rawLog); |
| |
| QObject::connect(&server, &Server::clientConnected, rawLog, [rawLog] { |
| rawLog->append(QLatin1String("\nCONNECTED\n")); |
| }); |
| QObject::connect(&server, &Server::clientDisconnected, rawLog, [rawLog, &tracker] { |
| rawLog->append(QLatin1String("\nDISCONNECTED\n")); |
| tracker.reset(); |
| }); |
| QObject::connect(&server, &Server::receiveStarted, rawLog, [rawLog] { |
| rawLog->setFontItalic(true); |
| rawLog->append(QLatin1String("[") + QTime::currentTime().toString() + QLatin1String("]")); |
| rawLog->setFontItalic(false); |
| }); |
| |
| QObject::connect(&server, &Server::lineReceived, rawLog, [rawLog, &parser](const QByteArray &line) { |
| rawLog->append(QString::fromUtf8(line)); |
| parser.feed(line); |
| }); |
| |
| QWidget w; |
| w.resize(800, 600); |
| w.setLayout(layout); |
| w.show(); |
| |
| return app.exec(); |
| } |
| |
| #include "qrhiprof.moc" |