From 0d5d6bf8bfbb031679d64076102bff52ac68ddc0 Mon Sep 17 00:00:00 2001 From: mhoffman Date: Fri, 15 Apr 2022 15:08:01 -0700 Subject: [PATCH 1/5] Add a performance counter that exports all data at the end and avoids mallocs. --- src/app/app.pro | 2 + src/util/performance_counter.cc | 161 ++++++++++++++++++++++++++++++++ src/util/performance_counter.h | 107 +++++++++++++++++++++ src/util/register_type.h | 53 +++++++++++ src/util/util.pro | 2 + 5 files changed, 325 insertions(+) create mode 100644 src/util/performance_counter.cc create mode 100644 src/util/performance_counter.h create mode 100644 src/util/register_type.h diff --git a/src/app/app.pro b/src/app/app.pro index 05c9121..6b452f5 100644 --- a/src/app/app.pro +++ b/src/app/app.pro @@ -2,6 +2,8 @@ QT += core qml quick svg widgets quickcontrols2 +CONFIG += c++17 + SOURCES += \ event_filter.cc \ gui_tests.cc \ diff --git a/src/util/performance_counter.cc b/src/util/performance_counter.cc new file mode 100644 index 0000000..62757c9 --- /dev/null +++ b/src/util/performance_counter.cc @@ -0,0 +1,161 @@ +#include "performance_counter.h" + +#include + +#include + +namespace project { + + PerformanceCounter::PerformanceCounter(QObject* parent, QQuickWindow* window) : QObject(parent), m_window(window) { + + } + + void PerformanceCounter::ExportContextPropertiesToQml( QQmlEngine* engine) { + engine->rootContext()->setContextProperty( "performanceCounter", this ); + } + + void PerformanceCounter::ConnectToWindowIfNecessary() { + if (m_connectedToWindow) { + return; + } + connect(m_window, &QQuickWindow::afterAnimating, this, &PerformanceCounter::AfterAnimating); + connect(m_window, &QQuickWindow::beforeRendering, this, &PerformanceCounter::BeforeRendering); + connect(m_window, &QQuickWindow::afterRendering, this, &PerformanceCounter::AfterRendering); + } + + void PerformanceCounter::StartReport(const QString& name) { + for (auto& reportRequest : m_reports) { + if (reportRequest.m_name.data() == nullptr) { + reportRequest = ReportRequest(name); + return; + } + } + } + + void PerformanceCounter::AfterAnimating() { + for (auto& reportRequest : m_reports) { + reportRequest.Synchronize(); + if (reportRequest.m_report.has_value()) { + reportRequest.m_report->GetFrameReportToWriteInto().m_afterAnimating = static_cast(reportRequest.m_timer.elapsed()); + } + } + } + + void PerformanceCounter::BeforeRendering() { + for (auto& reportRequest : m_reports) { + if (reportRequest.m_report.has_value()) { + reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast(reportRequest.m_timer.elapsed()); + } + } + } + + void PerformanceCounter::AfterRendering() { + for (auto& reportRequest : m_reports) { + if (reportRequest.m_report.has_value()) { + reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast(reportRequest.m_timer.elapsed()); + reportRequest.m_report->FinishFrame(); + if (reportRequest.m_requestedToStopRenderThread) { + reportRequest.Finalize(); + } + } + } + } + + ReportRequest::ReportRequest() : m_name(), m_timer() {} + ReportRequest::ReportRequest(QString name) : m_name(name), m_timer() { + m_timer.start(); + } + + // Called automatically from the GUI thread while the render thread is waiting + void ReportRequest::Synchronize() { + if (m_finalized) { + *this = ReportRequest(); + } else { + if ((m_name.data() != nullptr) && !m_report.has_value()) { + m_report.emplace(); + } + m_requestedToStopRenderThread = m_requestedToStopGUIThread; + } + } + + // Called by users from GUI thread + void ReportRequest::RequestStop() { + m_requestedToStopGUIThread = true; + } + + // Called automatically from the render thread + void ReportRequest::Finalize() { + ExportReport(); + m_finalized = true; + } + + struct MinMax { + qint32 m_min = std::numeric_limits < qint32 >::max(); + qint32 m_max = std::numeric_limits < qint32 >::min(); + + void Update(qint32 val) { + m_min = std::min(m_min, val); + m_max = std::max(m_max, val); + } + }; + + void ReportRequest::ExportReport() { + auto log = qDebug(); + log = log << "Exporting report for: " << m_name << Qt::endl; + log = log << Qt::right << qSetFieldWidth(3); + qint32 previousGuiUpdate = 0; + qint32 previousRenderFinish = 0; + + MinMax guiFrameDurationExtents; + MinMax renderFrameDurationExtents; + MinMax synchronizingExtents; + MinMax renderingExtents; + + for (const FrameReport& frame : m_report->m_backloggedFrames) { + qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate; + previousGuiUpdate = frame.m_afterAnimating; + qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish; + previousRenderFinish = frame.m_afterRendering; + qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating; + qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering; + log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration; + guiFrameDurationExtents.Update(guiFrameDuration); + renderFrameDurationExtents.Update(renderFrameDuration); + synchronizingExtents.Update(synchronizing); + renderingExtents.Update(rendering); + } + for (size_t i = 0; i < m_report->m_framesFilledCount; i++) { + const FrameReport& frame = m_report->m_frames[i]; + qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate; + previousGuiUpdate = frame.m_afterAnimating; + qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish; + previousRenderFinish = frame.m_afterRendering; + qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating; + qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering; + log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration; + guiFrameDurationExtents.Update(guiFrameDuration); + renderFrameDurationExtents.Update(renderFrameDuration); + synchronizingExtents.Update(synchronizing); + renderingExtents.Update(rendering); + } + + log << "GUI Frame Duration Extents " << guiFrameDurationExtents.m_min << ", " << guiFrameDurationExtents.m_max; + log << "Render Frame Duration Extents " << renderFrameDurationExtents.m_min << ", " << renderFrameDurationExtents.m_max; + log << "Synchronizing Extents " << synchronizingExtents.m_min << ", " << synchronizingExtents.m_max; + log << "Rendering Extents " << renderingExtents.m_min << ", " << renderingExtents.m_max; + } + + FrameReport& Report::GetFrameReportToWriteInto() { + return m_frames[m_framesFilledCount]; + } + + void Report::FinishFrame() { + m_framesFilledCount++; + if (m_framesFilledCount == m_frames.size()) { + size_t currentBacklogSize = m_backloggedFrames.size(); + m_backloggedFrames.resize(currentBacklogSize + m_frames.size()); + memcpy(m_frames.data(), m_backloggedFrames.data() + currentBacklogSize, sizeof(FrameReport) * m_frames.size()); + m_framesFilledCount = 0; + } + } +} \ No newline at end of file diff --git a/src/util/performance_counter.h b/src/util/performance_counter.h new file mode 100644 index 0000000..ad2b67a --- /dev/null +++ b/src/util/performance_counter.h @@ -0,0 +1,107 @@ +#ifndef PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H +#define PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H + +#include +#include + +#include +#include +#include +#include + +namespace project +{ + +struct FrameReport { + qint32 m_afterAnimating; + qint32 m_beforeRendering; + qint32 m_afterRendering; +}; + +struct Report { + Report() = default; + Report& operator=(const Report&) = default; + + // Gets the current frame that timing data should be written into. + FrameReport& GetFrameReportToWriteInto(); + // Moves the internal pointer to the next frame to write into, moving the cache into the heap-allocated backlog if space is needed. + void FinishFrame(); + + static constexpr size_t k_maxNumberOfFrames = 15; + + // The number of frame reports that have been fully filled, with all 3 timestamps having been written. + // This means that this is also the index of the FrameReport where new values should be written, + // and that this should be incremented whenever the last timestamp is written. + size_t m_framesFilledCount; + std::array m_frames; + std::vector m_backloggedFrames; +}; + +struct ReportRequest { + ReportRequest(); + explicit ReportRequest(QString name); + + // Called automatically from the GUI thread while the render thread is waiting + void Synchronize(); + + // Called by users from GUI thread + void RequestStop(); + + // Called automatically from the render thread + void Finalize(); + + void ExportReport(); + + // Fully owned by the GUI thread + QString m_name; + // Written by the GUI thread before synchronization, read by the render thread after synchronization. + QElapsedTimer m_timer; + // Written by the GUI thread requesting that the report finish after the next render. + bool m_requestedToStopGUIThread = false; + // Copied based on m_requestedToStopGUIThread during synchronization. + bool m_requestedToStopRenderThread = false; + // Written to on the render thread once the frame that m_requestedToStopRenderThread was set in has finished. + // Once this is true, the report can be cleared out during the next synchronization. + bool m_finalized = false; + std::optional m_report; +}; + +// This object currently sits at 800 bytes, which means it should fit in a single page of memory. +class PerformanceCounter : public QObject +{ + Q_OBJECT + + public: + PerformanceCounter(QObject* parent, QQuickWindow* window); + + void ExportContextPropertiesToQml( QQmlEngine* engine ); + + // All public methods must be called by the GUI thread + void StartReport(const QString& reportName); + void StopReport(const QString& reportName); + + static constexpr size_t k_maxNumberOfReports = 3; + + private slots: + // This happens on the GUI thread and can be used to synchronize between GUI and rendering thread resources. + void AfterAnimating(); + // This happens on the rendering thread + void BeforeRendering(); + // This happens on the rendering thread + void AfterRendering(); + + private: + // This must be called on the rendering thread + void AddReportToInternalStorage(Report report); + void ConnectToWindowIfNecessary(); + + std::array m_reports; + + // Whether the private slots have been connected to the window's signals. + bool m_connectedToWindow = false; + QQuickWindow* m_window; +}; + +} // namespace project + +#endif // PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H diff --git a/src/util/register_type.h b/src/util/register_type.h new file mode 100644 index 0000000..a484f30 --- /dev/null +++ b/src/util/register_type.h @@ -0,0 +1,53 @@ +#include +#include + +// Internal helper function that registers a type with modifier (like being wrapped in a vector). +// aliases is the list of aliased names the underlying class uses, and aliasModifierOrNone is optionally a function that will apply the modifier to the alias. +template +void registerModifiedType(const std::vector& aliases, std::function aliasModifier = [](const std::string& s) {return s;}) { + if (aliases.size() == 0) { + qRegisterMetaType(); + } else { + for (const std::string& alias : aliases) { + qRegisterMetaType(aliasModifier(alias).c_str()); + } + } +} + +// Aliases is all aliases that will be registered for the type (such as both type name with and without the namespace). +// If the type is not anonymous, the first alias will be used as the name. +// It is valid to register a type without aliases, but then it must be anonymous. +// Template arguments control which variants on a type (pointer, Qt smart pointer, etc.) are created, as well as const versions (with const-ness binding most tightly to the main type). +template +void registerType(const std::vector& aliases) { + if constexpr (makeAnonymous) { + qmlRegisterAnonymousType("App", 1); + } else { + qmlRegisterType("App", 1, 0, aliases[0].c_str()); + } + + if constexpr (registerValueType) { + registerModifiedType(aliases); + if constexpr (registerConstVersions) { + registerModifiedType(aliases, [](const std::string& s){ return "const " + s;}); + } + } + if constexpr (registerPointer) { + registerModifiedType(aliases, [](const std::string& s){ return s + "*"; }); + if constexpr (registerConstVersions) { + registerModifiedType(aliases, [](const std::string& s){ return "const " + s + "*"; }); + } + } + if constexpr (registerSharedPointer) { + registerModifiedType>(aliases, [](const std::string& s){ return "QSharedPointer<" + s + ">"; }); + if constexpr (registerConstVersions) { + registerModifiedType>(aliases, [](const std::string& s){ return "QSharedPointer"; }); + } + } + if constexpr (registerVectorOfSharedPointers) { + registerModifiedType>>(aliases, [](const std::string& s){ return "QVector>"; }); + if constexpr (registerConstVersions) { + registerModifiedType>>(aliases, [](const std::string& s){ return "QVector>"; }); + } + } +} diff --git a/src/util/util.pro b/src/util/util.pro index ad46996..39c0fed 100644 --- a/src/util/util.pro +++ b/src/util/util.pro @@ -20,12 +20,14 @@ SOURCES += \ am_i_inside_debugger.cc \ deleter_with_qt_deferred_deletion.cc \ every_so_often.cc \ + performance_counter.cc \ qml_message_interceptor.cc HEADERS += \ am_i_inside_debugger.h \ deleter_with_qt_deferred_deletion.h \ every_so_often.h \ + performance_counter.h \ qml_list_property_helper.h \ qml_message_interceptor.h \ usage_log_t.hpp From d12084c513857f63ffcb161ce15cb4c4bb009dec Mon Sep 17 00:00:00 2001 From: pestophagous Date: Mon, 25 Apr 2022 15:29:28 -0700 Subject: [PATCH 2/5] run clang-format (so CI can pass) --- AUTHORS | 3 +- src/util/performance_counter.cc | 267 ++++++++++++++++++-------------- src/util/performance_counter.h | 86 +++++----- src/util/register_type.h | 74 +++++---- 4 files changed, 244 insertions(+), 186 deletions(-) diff --git a/AUTHORS b/AUTHORS index 5b22023..ad8efd1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,4 +2,5 @@ # following the format reported by `git shortlog -sen | cut -f 2`. Sean Moore - +mhoffman +pestophagous diff --git a/src/util/performance_counter.cc b/src/util/performance_counter.cc index 62757c9..a0af7a3 100644 --- a/src/util/performance_counter.cc +++ b/src/util/performance_counter.cc @@ -4,105 +4,137 @@ #include -namespace project { - - PerformanceCounter::PerformanceCounter(QObject* parent, QQuickWindow* window) : QObject(parent), m_window(window) { - - } - - void PerformanceCounter::ExportContextPropertiesToQml( QQmlEngine* engine) { - engine->rootContext()->setContextProperty( "performanceCounter", this ); +namespace project +{ +PerformanceCounter::PerformanceCounter( QObject* parent, QQuickWindow* window ) + : QObject( parent ), m_window( window ) +{ +} + +void PerformanceCounter::ExportContextPropertiesToQml( QQmlEngine* engine ) +{ + engine->rootContext()->setContextProperty( "performanceCounter", this ); +} + +void PerformanceCounter::ConnectToWindowIfNecessary() +{ + if( m_connectedToWindow ) + { + return; } - - void PerformanceCounter::ConnectToWindowIfNecessary() { - if (m_connectedToWindow) { + connect( m_window, &QQuickWindow::afterAnimating, this, &PerformanceCounter::AfterAnimating ); + connect( m_window, &QQuickWindow::beforeRendering, this, &PerformanceCounter::BeforeRendering ); + connect( m_window, &QQuickWindow::afterRendering, this, &PerformanceCounter::AfterRendering ); +} + +void PerformanceCounter::StartReport( const QString& name ) +{ + for( auto& reportRequest : m_reports ) + { + if( reportRequest.m_name.data() == nullptr ) + { + reportRequest = ReportRequest( name ); return; } - connect(m_window, &QQuickWindow::afterAnimating, this, &PerformanceCounter::AfterAnimating); - connect(m_window, &QQuickWindow::beforeRendering, this, &PerformanceCounter::BeforeRendering); - connect(m_window, &QQuickWindow::afterRendering, this, &PerformanceCounter::AfterRendering); } - - void PerformanceCounter::StartReport(const QString& name) { - for (auto& reportRequest : m_reports) { - if (reportRequest.m_name.data() == nullptr) { - reportRequest = ReportRequest(name); - return; - } +} + +void PerformanceCounter::AfterAnimating() +{ + for( auto& reportRequest : m_reports ) + { + reportRequest.Synchronize(); + if( reportRequest.m_report.has_value() ) + { + reportRequest.m_report->GetFrameReportToWriteInto().m_afterAnimating = static_cast( reportRequest.m_timer.elapsed() ); } } - - void PerformanceCounter::AfterAnimating() { - for (auto& reportRequest : m_reports) { - reportRequest.Synchronize(); - if (reportRequest.m_report.has_value()) { - reportRequest.m_report->GetFrameReportToWriteInto().m_afterAnimating = static_cast(reportRequest.m_timer.elapsed()); - } +} + +void PerformanceCounter::BeforeRendering() +{ + for( auto& reportRequest : m_reports ) + { + if( reportRequest.m_report.has_value() ) + { + reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast( reportRequest.m_timer.elapsed() ); } } - - void PerformanceCounter::BeforeRendering() { - for (auto& reportRequest : m_reports) { - if (reportRequest.m_report.has_value()) { - reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast(reportRequest.m_timer.elapsed()); +} + +void PerformanceCounter::AfterRendering() +{ + for( auto& reportRequest : m_reports ) + { + if( reportRequest.m_report.has_value() ) + { + reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast( reportRequest.m_timer.elapsed() ); + reportRequest.m_report->FinishFrame(); + if( reportRequest.m_requestedToStopRenderThread ) + { + reportRequest.Finalize(); } } } - - void PerformanceCounter::AfterRendering() { - for (auto& reportRequest : m_reports) { - if (reportRequest.m_report.has_value()) { - reportRequest.m_report->GetFrameReportToWriteInto().m_beforeRendering = static_cast(reportRequest.m_timer.elapsed()); - reportRequest.m_report->FinishFrame(); - if (reportRequest.m_requestedToStopRenderThread) { - reportRequest.Finalize(); - } - } - } - } - - ReportRequest::ReportRequest() : m_name(), m_timer() {} - ReportRequest::ReportRequest(QString name) : m_name(name), m_timer() { +} + +ReportRequest::ReportRequest() + : m_name(), m_timer() +{ +} +ReportRequest::ReportRequest( QString name ) + : m_name( name ), m_timer() +{ m_timer.start(); - } - - // Called automatically from the GUI thread while the render thread is waiting - void ReportRequest::Synchronize() { - if (m_finalized) { - *this = ReportRequest(); - } else { - if ((m_name.data() != nullptr) && !m_report.has_value()) { - m_report.emplace(); - } - m_requestedToStopRenderThread = m_requestedToStopGUIThread; +} + +// Called automatically from the GUI thread while the render thread is waiting +void ReportRequest::Synchronize() +{ + if( m_finalized ) + { + *this = ReportRequest(); + } + else + { + if( ( m_name.data() != nullptr ) && !m_report.has_value() ) + { + m_report.emplace(); + } + m_requestedToStopRenderThread = m_requestedToStopGUIThread; } - } +} - // Called by users from GUI thread - void ReportRequest::RequestStop() { +// Called by users from GUI thread +void ReportRequest::RequestStop() +{ m_requestedToStopGUIThread = true; - } +} - // Called automatically from the render thread - void ReportRequest::Finalize() { +// Called automatically from the render thread +void ReportRequest::Finalize() +{ ExportReport(); m_finalized = true; - } +} - struct MinMax { - qint32 m_min = std::numeric_limits < qint32 >::max(); - qint32 m_max = std::numeric_limits < qint32 >::min(); +struct MinMax +{ + qint32 m_min = std::numeric_limits::max(); + qint32 m_max = std::numeric_limits::min(); - void Update(qint32 val) { - m_min = std::min(m_min, val); - m_max = std::max(m_max, val); + void Update( qint32 val ) + { + m_min = std::min( m_min, val ); + m_max = std::max( m_max, val ); } - }; +}; - void ReportRequest::ExportReport() { +void ReportRequest::ExportReport() +{ auto log = qDebug(); log = log << "Exporting report for: " << m_name << Qt::endl; - log = log << Qt::right << qSetFieldWidth(3); + log = log << Qt::right << qSetFieldWidth( 3 ); qint32 previousGuiUpdate = 0; qint32 previousRenderFinish = 0; @@ -111,51 +143,56 @@ namespace project { MinMax synchronizingExtents; MinMax renderingExtents; - for (const FrameReport& frame : m_report->m_backloggedFrames) { - qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate; - previousGuiUpdate = frame.m_afterAnimating; - qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish; - previousRenderFinish = frame.m_afterRendering; - qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating; - qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering; - log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration; - guiFrameDurationExtents.Update(guiFrameDuration); - renderFrameDurationExtents.Update(renderFrameDuration); - synchronizingExtents.Update(synchronizing); - renderingExtents.Update(rendering); + for( const FrameReport& frame : m_report->m_backloggedFrames ) + { + qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate; + previousGuiUpdate = frame.m_afterAnimating; + qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish; + previousRenderFinish = frame.m_afterRendering; + qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating; + qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering; + log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration; + guiFrameDurationExtents.Update( guiFrameDuration ); + renderFrameDurationExtents.Update( renderFrameDuration ); + synchronizingExtents.Update( synchronizing ); + renderingExtents.Update( rendering ); } - for (size_t i = 0; i < m_report->m_framesFilledCount; i++) { - const FrameReport& frame = m_report->m_frames[i]; - qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate; - previousGuiUpdate = frame.m_afterAnimating; - qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish; - previousRenderFinish = frame.m_afterRendering; - qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating; - qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering; - log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration; - guiFrameDurationExtents.Update(guiFrameDuration); - renderFrameDurationExtents.Update(renderFrameDuration); - synchronizingExtents.Update(synchronizing); - renderingExtents.Update(rendering); + for( size_t i = 0; i < m_report->m_framesFilledCount; i++ ) + { + const FrameReport& frame = m_report->m_frames[ i ]; + qint32 guiFrameDuration = frame.m_afterAnimating - previousGuiUpdate; + previousGuiUpdate = frame.m_afterAnimating; + qint32 renderFrameDuration = frame.m_afterRendering - previousRenderFinish; + previousRenderFinish = frame.m_afterRendering; + qint32 synchronizing = frame.m_beforeRendering - frame.m_afterAnimating; + qint32 rendering = frame.m_afterRendering - frame.m_beforeRendering; + log << synchronizing << ", " << rendering << ", " << guiFrameDuration << ", " << renderFrameDuration; + guiFrameDurationExtents.Update( guiFrameDuration ); + renderFrameDurationExtents.Update( renderFrameDuration ); + synchronizingExtents.Update( synchronizing ); + renderingExtents.Update( rendering ); } log << "GUI Frame Duration Extents " << guiFrameDurationExtents.m_min << ", " << guiFrameDurationExtents.m_max; log << "Render Frame Duration Extents " << renderFrameDurationExtents.m_min << ", " << renderFrameDurationExtents.m_max; log << "Synchronizing Extents " << synchronizingExtents.m_min << ", " << synchronizingExtents.m_max; log << "Rendering Extents " << renderingExtents.m_min << ", " << renderingExtents.m_max; - } - - FrameReport& Report::GetFrameReportToWriteInto() { - return m_frames[m_framesFilledCount]; - } - - void Report::FinishFrame() { - m_framesFilledCount++; - if (m_framesFilledCount == m_frames.size()) { - size_t currentBacklogSize = m_backloggedFrames.size(); - m_backloggedFrames.resize(currentBacklogSize + m_frames.size()); - memcpy(m_frames.data(), m_backloggedFrames.data() + currentBacklogSize, sizeof(FrameReport) * m_frames.size()); - m_framesFilledCount = 0; - } +} + +FrameReport& Report::GetFrameReportToWriteInto() +{ + return m_frames[ m_framesFilledCount ]; +} + +void Report::FinishFrame() +{ + m_framesFilledCount++; + if( m_framesFilledCount == m_frames.size() ) + { + size_t currentBacklogSize = m_backloggedFrames.size(); + m_backloggedFrames.resize( currentBacklogSize + m_frames.size() ); + memcpy( m_frames.data(), m_backloggedFrames.data() + currentBacklogSize, sizeof( FrameReport ) * m_frames.size() ); + m_framesFilledCount = 0; } -} \ No newline at end of file +} +} // namespace project \ No newline at end of file diff --git a/src/util/performance_counter.h b/src/util/performance_counter.h index ad2b67a..82f46ac 100644 --- a/src/util/performance_counter.h +++ b/src/util/performance_counter.h @@ -1,26 +1,27 @@ #ifndef PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H #define PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H -#include #include +#include -#include #include -#include +#include #include +#include namespace project { - -struct FrameReport { +struct FrameReport +{ qint32 m_afterAnimating; qint32 m_beforeRendering; qint32 m_afterRendering; }; -struct Report { +struct Report +{ Report() = default; - Report& operator=(const Report&) = default; + Report& operator=( const Report& ) = default; // Gets the current frame that timing data should be written into. FrameReport& GetFrameReportToWriteInto(); @@ -37,52 +38,53 @@ struct Report { std::vector m_backloggedFrames; }; -struct ReportRequest { - ReportRequest(); - explicit ReportRequest(QString name); - - // Called automatically from the GUI thread while the render thread is waiting - void Synchronize(); - - // Called by users from GUI thread - void RequestStop(); - - // Called automatically from the render thread - void Finalize(); - - void ExportReport(); - - // Fully owned by the GUI thread - QString m_name; - // Written by the GUI thread before synchronization, read by the render thread after synchronization. - QElapsedTimer m_timer; - // Written by the GUI thread requesting that the report finish after the next render. - bool m_requestedToStopGUIThread = false; - // Copied based on m_requestedToStopGUIThread during synchronization. - bool m_requestedToStopRenderThread = false; - // Written to on the render thread once the frame that m_requestedToStopRenderThread was set in has finished. - // Once this is true, the report can be cleared out during the next synchronization. - bool m_finalized = false; - std::optional m_report; +struct ReportRequest +{ + ReportRequest(); + explicit ReportRequest( QString name ); + + // Called automatically from the GUI thread while the render thread is waiting + void Synchronize(); + + // Called by users from GUI thread + void RequestStop(); + + // Called automatically from the render thread + void Finalize(); + + void ExportReport(); + + // Fully owned by the GUI thread + QString m_name; + // Written by the GUI thread before synchronization, read by the render thread after synchronization. + QElapsedTimer m_timer; + // Written by the GUI thread requesting that the report finish after the next render. + bool m_requestedToStopGUIThread = false; + // Copied based on m_requestedToStopGUIThread during synchronization. + bool m_requestedToStopRenderThread = false; + // Written to on the render thread once the frame that m_requestedToStopRenderThread was set in has finished. + // Once this is true, the report can be cleared out during the next synchronization. + bool m_finalized = false; + std::optional m_report; }; // This object currently sits at 800 bytes, which means it should fit in a single page of memory. class PerformanceCounter : public QObject { Q_OBJECT - - public: - PerformanceCounter(QObject* parent, QQuickWindow* window); + +public: + PerformanceCounter( QObject* parent, QQuickWindow* window ); void ExportContextPropertiesToQml( QQmlEngine* engine ); // All public methods must be called by the GUI thread - void StartReport(const QString& reportName); - void StopReport(const QString& reportName); + void StartReport( const QString& reportName ); + void StopReport( const QString& reportName ); static constexpr size_t k_maxNumberOfReports = 3; - private slots: +private slots: // This happens on the GUI thread and can be used to synchronize between GUI and rendering thread resources. void AfterAnimating(); // This happens on the rendering thread @@ -90,9 +92,9 @@ class PerformanceCounter : public QObject // This happens on the rendering thread void AfterRendering(); - private: +private: // This must be called on the rendering thread - void AddReportToInternalStorage(Report report); + void AddReportToInternalStorage( Report report ); void ConnectToWindowIfNecessary(); std::array m_reports; diff --git a/src/util/register_type.h b/src/util/register_type.h index a484f30..4ed33c8 100644 --- a/src/util/register_type.h +++ b/src/util/register_type.h @@ -3,13 +3,19 @@ // Internal helper function that registers a type with modifier (like being wrapped in a vector). // aliases is the list of aliased names the underlying class uses, and aliasModifierOrNone is optionally a function that will apply the modifier to the alias. -template -void registerModifiedType(const std::vector& aliases, std::function aliasModifier = [](const std::string& s) {return s;}) { - if (aliases.size() == 0) { +template +void registerModifiedType( + const std::vector& aliases, std::function aliasModifier = []( const std::string& s ) { return s; } ) +{ + if( aliases.size() == 0 ) + { qRegisterMetaType(); - } else { - for (const std::string& alias : aliases) { - qRegisterMetaType(aliasModifier(alias).c_str()); + } + else + { + for( const std::string& alias : aliases ) + { + qRegisterMetaType( aliasModifier( alias ).c_str() ); } } } @@ -18,36 +24,48 @@ void registerModifiedType(const std::vector& aliases, std::function // If the type is not anonymous, the first alias will be used as the name. // It is valid to register a type without aliases, but then it must be anonymous. // Template arguments control which variants on a type (pointer, Qt smart pointer, etc.) are created, as well as const versions (with const-ness binding most tightly to the main type). -template -void registerType(const std::vector& aliases) { - if constexpr (makeAnonymous) { - qmlRegisterAnonymousType("App", 1); - } else { - qmlRegisterType("App", 1, 0, aliases[0].c_str()); +template +void registerType( const std::vector& aliases ) +{ + if constexpr( makeAnonymous ) + { + qmlRegisterAnonymousType( "App", 1 ); + } + else + { + qmlRegisterType( "App", 1, 0, aliases[ 0 ].c_str() ); } - if constexpr (registerValueType) { - registerModifiedType(aliases); - if constexpr (registerConstVersions) { - registerModifiedType(aliases, [](const std::string& s){ return "const " + s;}); + if constexpr( registerValueType ) + { + registerModifiedType( aliases ); + if constexpr( registerConstVersions ) + { + registerModifiedType( aliases, []( const std::string& s ) { return "const " + s; } ); } } - if constexpr (registerPointer) { - registerModifiedType(aliases, [](const std::string& s){ return s + "*"; }); - if constexpr (registerConstVersions) { - registerModifiedType(aliases, [](const std::string& s){ return "const " + s + "*"; }); + if constexpr( registerPointer ) + { + registerModifiedType( aliases, []( const std::string& s ) { return s + "*"; } ); + if constexpr( registerConstVersions ) + { + registerModifiedType( aliases, []( const std::string& s ) { return "const " + s + "*"; } ); } } - if constexpr (registerSharedPointer) { - registerModifiedType>(aliases, [](const std::string& s){ return "QSharedPointer<" + s + ">"; }); - if constexpr (registerConstVersions) { - registerModifiedType>(aliases, [](const std::string& s){ return "QSharedPointer"; }); + if constexpr( registerSharedPointer ) + { + registerModifiedType>( aliases, []( const std::string& s ) { return "QSharedPointer<" + s + ">"; } ); + if constexpr( registerConstVersions ) + { + registerModifiedType>( aliases, []( const std::string& s ) { return "QSharedPointer"; } ); } } - if constexpr (registerVectorOfSharedPointers) { - registerModifiedType>>(aliases, [](const std::string& s){ return "QVector>"; }); - if constexpr (registerConstVersions) { - registerModifiedType>>(aliases, [](const std::string& s){ return "QVector>"; }); + if constexpr( registerVectorOfSharedPointers ) + { + registerModifiedType>>( aliases, []( const std::string& s ) { return "QVector>"; } ); + if constexpr( registerConstVersions ) + { + registerModifiedType>>( aliases, []( const std::string& s ) { return "QVector>"; } ); } } } From eced455990a7acfc6dbab504d5f3a2f863ea1d6f Mon Sep 17 00:00:00 2001 From: pestophagous Date: Mon, 25 Apr 2022 16:29:20 -0700 Subject: [PATCH 3/5] libutil: exclude performance_counter from android build --- src/util/util.pro | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/util/util.pro b/src/util/util.pro index 39c0fed..3be7a62 100644 --- a/src/util/util.pro +++ b/src/util/util.pro @@ -20,17 +20,23 @@ SOURCES += \ am_i_inside_debugger.cc \ deleter_with_qt_deferred_deletion.cc \ every_so_often.cc \ - performance_counter.cc \ qml_message_interceptor.cc HEADERS += \ am_i_inside_debugger.h \ deleter_with_qt_deferred_deletion.h \ every_so_often.h \ - performance_counter.h \ qml_list_property_helper.h \ qml_message_interceptor.h \ usage_log_t.hpp +!android { + SOURCES += \ + performance_counter.cc + + HEADERS += \ + performance_counter.h +} + target.path = $$top_exe_dir INSTALLS += target From 67ccb84b83451d9d9799a8fd8a3ec834720fa73c Mon Sep 17 00:00:00 2001 From: pestophagous Date: Mon, 25 Apr 2022 16:31:41 -0700 Subject: [PATCH 4/5] libutil: fix macos build and windows build --- src/util/performance_counter.h | 2 ++ src/util/util.pro | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/util/performance_counter.h b/src/util/performance_counter.h index 82f46ac..8997818 100644 --- a/src/util/performance_counter.h +++ b/src/util/performance_counter.h @@ -2,6 +2,8 @@ #define PROJ_LIB_UTIL_PERFORMANCE_COUNTER_H #include +#include +#include #include #include diff --git a/src/util/util.pro b/src/util/util.pro index 3be7a62..4d21767 100644 --- a/src/util/util.pro +++ b/src/util/util.pro @@ -1,6 +1,6 @@ !include($$top_srcdir/compiler_flags.pri) { error() } -QT += core +QT += core qml quick android { QT += androidextras From 3a4d451c2ee256cd84a31a0ace69ad9153d964a0 Mon Sep 17 00:00:00 2001 From: pestophagous Date: Mon, 25 Apr 2022 17:54:58 -0700 Subject: [PATCH 5/5] app: instantiate our new PerformanceCounter during gui tests --- src/app/view_model_collection.cc | 15 +++++++++++++++ src/app/view_model_collection.h | 5 +++++ src/util/performance_counter.cc | 4 ++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/app/view_model_collection.cc b/src/app/view_model_collection.cc index e8cd9ef..e8ebf36 100644 --- a/src/app/view_model_collection.cc +++ b/src/app/view_model_collection.cc @@ -18,6 +18,11 @@ #include "src/libstyles/resource_helper.h" #include "src/util/qml_message_interceptor.h" #include "src/util/usage_log_t.hpp" +#include "util-assert.h" + +#if !defined( Q_OS_ANDROID ) +# include "src/util/performance_counter.h" +#endif // !defined(Q_OS_ANDROID) namespace project { @@ -66,5 +71,15 @@ void ViewModelCollection::ExportContextPropertiesToQml( QQmlEngine* engine ) void ViewModelCollection::SetRootObject( QObject* object ) { m_eventFilter->FilterEventsDirectedAtThisObject( object ); + +#if !defined( Q_OS_ANDROID ) + if( m_opts->RunningGuiTests() ) + { + auto rootWin = qobject_cast( object ); + FASSERT( rootWin, "the casting must succeed so we get a QQuickWindow" ); + m_perfCounter = std::make_unique( nullptr, rootWin ); + m_perfCounter->StartReport( "GuiTests" ); + } +#endif // !defined(Q_OS_ANDROID) } } // namespace project diff --git a/src/app/view_model_collection.h b/src/app/view_model_collection.h index e148d7e..6ebec98 100644 --- a/src/app/view_model_collection.h +++ b/src/app/view_model_collection.h @@ -20,6 +20,7 @@ class CliOptions; class EventFilter; class GuiTests; class LoggingTags; +class PerformanceCounter; class QmlMessageInterceptor; class ViewModelCollection @@ -43,6 +44,10 @@ class ViewModelCollection std::unique_ptr m_qmlLogger; std::unique_ptr m_logging; +#if !defined( Q_OS_ANDROID ) + std::unique_ptr m_perfCounter; +#endif // !defined(Q_OS_ANDROID) + std::unique_ptr m_guiTests; }; } // namespace project diff --git a/src/util/performance_counter.cc b/src/util/performance_counter.cc index a0af7a3..6762f4c 100644 --- a/src/util/performance_counter.cc +++ b/src/util/performance_counter.cc @@ -31,7 +31,7 @@ void PerformanceCounter::StartReport( const QString& name ) { for( auto& reportRequest : m_reports ) { - if( reportRequest.m_name.data() == nullptr ) + if( reportRequest.m_name.isEmpty() ) { reportRequest = ReportRequest( name ); return; @@ -195,4 +195,4 @@ void Report::FinishFrame() m_framesFilledCount = 0; } } -} // namespace project \ No newline at end of file +} // namespace project