diff options
Diffstat (limited to 'tests/manual/rhi/qrhiprof/qrhiprof.cpp')
-rw-r--r-- | tests/manual/rhi/qrhiprof/qrhiprof.cpp | 671 |
1 files changed, 671 insertions, 0 deletions
diff --git a/tests/manual/rhi/qrhiprof/qrhiprof.cpp b/tests/manual/rhi/qrhiprof/qrhiprof.cpp new file mode 100644 index 0000000000..7e3027a951 --- /dev/null +++ b/tests/manual/rhi/qrhiprof/qrhiprof.cpp @@ -0,0 +1,671 @@ +/**************************************************************************** +** +** 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" |