53599592e0
Comes with backends for Vulkan, Metal, Direct3D 11.1, and OpenGL (ES). All APIs are private for now. Shader conditioning (i.e. generating a QRhiShader in memory or on disk from some shader source code) is done via the tools and APIs provided by qt-labs/qtshadertools. The OpenGL support follows the cross-platform tradition of requiring ES 2.0 only, while optionally using some (ES) 3.x features. It can operate in core profile contexts as well. Task-number: QTBUG-70287 Change-Id: I246f2e36d562e404012c05db2aa72487108aa7cc Reviewed-by: Lars Knoll <lars.knoll@qt.io>
672 lines
24 KiB
C++
672 lines
24 KiB
C++
/****************************************************************************
|
|
**
|
|
** 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"
|