diff --git a/examples/testbed/framework/main.cpp b/examples/testbed/framework/main.cpp index 03dcebd..3917fd1 100644 --- a/examples/testbed/framework/main.cpp +++ b/examples/testbed/framework/main.cpp @@ -104,9 +104,13 @@ static void Run() while (glfwWindowShouldClose(g_window) == 0) { g_profiler->Begin(); + + g_profilerSt->Begin(); g_profiler->BeginScope("Frame"); - + + g_profilerSt->BeginScope("Frame"); + g_view->BeginInterface(); if (g_model->IsPaused()) @@ -122,6 +126,8 @@ static void Run() g_model->Update(); + g_profilerSt->EndScope(); + g_profiler->EndScope(); if (g_settings->drawProfileTree) @@ -129,20 +135,13 @@ static void Run() g_view->InterfaceProfileTree(); } - g_profilerRecorder->BuildRecords(); - - if (g_settings->drawProfile) + if (g_settings->drawProfileTreeStats) { - b3StackArray records; - g_profilerRecorder->BuildSortedRecords(records); - - for (u32 i = 0; i < records.Count(); ++i) - { - ProfilerRecord* r = records[i]; - g_draw->DrawString(b3Color_white, "%s %.4f (min = %.4f) (max = %.4f) (calls = %d) [ms]", r->name, r->elapsed, r->minElapsed, r->maxElapsed, r->callCount); - } + g_view->InterfaceProfileTreeStats(); } + g_profilerSt->End(); + g_profiler->End(); g_view->EndInterface(); diff --git a/examples/testbed/framework/model.cpp b/examples/testbed/framework/model.cpp index 325bfa0..d15067b 100644 --- a/examples/testbed/framework/model.cpp +++ b/examples/testbed/framework/model.cpp @@ -26,7 +26,7 @@ Model::Model() g_draw = &m_draw; g_camera = &m_camera; g_profiler = &m_profiler; - g_profilerRecorder = &m_profilerRecorder; + g_profilerSt = &m_profilerSt; #if (PROFILE_JSON == 1) g_profilerListener = &m_jsonListener; @@ -56,7 +56,7 @@ Model::~Model() g_draw = nullptr; g_camera = nullptr; g_profiler = nullptr; - g_profilerRecorder = nullptr; + g_profilerSt = nullptr; #if (PROFILE_JSON == 1) g_profilerListener = nullptr; diff --git a/examples/testbed/framework/model.h b/examples/testbed/framework/model.h index 0354f56..017c7ce 100644 --- a/examples/testbed/framework/model.h +++ b/examples/testbed/framework/model.h @@ -21,7 +21,7 @@ #include #include -#include +#include // Set to 1 to write profile events into a .json file. Set to 0 otherwise. #define PROFILE_JSON 0 @@ -71,7 +71,7 @@ private: Draw m_draw; Camera m_camera; Profiler m_profiler; - ProfilerRecorder m_profilerRecorder; + ProfilerSt m_profilerSt; #if (PROFILE_JSON == 1) JsonProfiler m_jsonListener; diff --git a/examples/testbed/framework/profiler.cpp b/examples/testbed/framework/profiler.cpp index 0422084..faef74e 100644 --- a/examples/testbed/framework/profiler.cpp +++ b/examples/testbed/framework/profiler.cpp @@ -127,12 +127,13 @@ void Profiler::End() listener->BeginEvents(); } - RecurseEvents(m_root); - - RecurseDestroyNode(m_root); - m_root = nullptr; + if (m_root) + { + RecurseEvents(m_root); - assert(m_root == nullptr); + RecurseDestroyNode(m_root); + m_root = nullptr; + } if (listener) { diff --git a/examples/testbed/framework/profiler.h b/examples/testbed/framework/profiler.h index 7c27dac..75861f8 100644 --- a/examples/testbed/framework/profiler.h +++ b/examples/testbed/framework/profiler.h @@ -62,8 +62,6 @@ public: // Get the root profiler node. ProfilerNode* GetRoot() { return m_root; } private: - friend class ProfilerRecorder; - ProfilerNode* CreateNode(); void DestroyNode(ProfilerNode* node); diff --git a/examples/testbed/framework/profiler_recorder.cpp b/examples/testbed/framework/profiler_recorder.cpp deleted file mode 100644 index 94b0dc7..0000000 --- a/examples/testbed/framework/profiler_recorder.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* -* Copyright (c) 2016-2019 Irlan Robson https://irlanrobson.github.io -* -* This software is provided 'as-is', without any express or implied -* warranty. In no event will the authors be held liable for any damages -* arising from the use of this software. -* Permission is granted to anyone to use this software for any purpose, -* including commercial applications, and to alter it and redistribute it -* freely, subject to the following restrictions: -* 1. The origin of this software must not be misrepresented; you must not -* claim that you wrote the original software. If you use this software -* in a product, an acknowledgment in the product documentation would be -* appreciated but is not required. -* 2. Altered source versions must be plainly marked as such, and must not be -* misrepresented as being the original software. -* 3. This notice may not be removed or altered from any source distribution. -*/ - -#include -#include - -ProfilerRecorder* g_profilerRecorder = nullptr; - -ProfilerRecord* ProfilerRecorder::FindRecord(const char* name) -{ - for (u32 i = 0; i < m_records.Count(); ++i) - { - ProfilerRecord& r = m_records[i]; - if (r.name == name) - { - return &r; - } - } - return nullptr; -} - -void ProfilerRecorder::RecurseBuildRecords(ProfilerNode* node) -{ - ProfilerRecord* fr = FindRecord(node->name); - if (fr) - { - float64 elapsedTime = node->t1 - node->t0; - - fr->elapsed += elapsedTime; - fr->minElapsed = b3Min(fr->minElapsed, elapsedTime); - fr->maxElapsed = b3Max(fr->maxElapsed, elapsedTime); - ++fr->callCount; - } - else - { - float64 elapsedTime = node->t1 - node->t0; - - ProfilerRecord r; - r.name = node->name; - r.elapsed = elapsedTime; - r.minElapsed = elapsedTime; - r.maxElapsed = elapsedTime; - r.callCount = 1; - - m_records.PushBack(r); - } - - for (u32 i = 0; i < node->children.Count(); ++i) - { - RecurseBuildRecords(node->children[i]); - } -} - -void ProfilerRecorder::BuildRecords() -{ - for (u32 i = 0; i < m_records.Count(); ++i) - { - m_records[i].elapsed = 0.0; - m_records[i].callCount = 0; - } - - RecurseBuildRecords(g_profiler->m_root); -} - -static ProfilerRecord* FindSortedRecord(b3Array& records, const char* name) -{ - for (u32 i = 0; i < records.Count(); ++i) - { - ProfilerRecord* r = records[i]; - if (r->name == name) - { - return r; - } - } - return nullptr; -} - -void ProfilerRecorder::RecurseBuildSortedRecords(ProfilerNode* node, b3Array& output) -{ - ProfilerRecord* fsr = FindSortedRecord(output, node->name); - - if (fsr == nullptr) - { - ProfilerRecord* fr = FindRecord(node->name); - - assert(fr != nullptr); - - // Push back the first ocurrence of call in calling order - output.PushBack(fr); - } - - for (u32 i = 0; i < node->children.Count(); ++i) - { - RecurseBuildSortedRecords(node->children[i], output); - } -} - -void ProfilerRecorder::BuildSortedRecords(b3Array& output) -{ - assert(output.Count() == 0); - - output.Reserve(m_records.Count()); - - RecurseBuildSortedRecords(g_profiler->m_root, output); -} \ No newline at end of file diff --git a/examples/testbed/framework/profiler_recorder.h b/examples/testbed/framework/profiler_recorder.h deleted file mode 100644 index b3ea5b4..0000000 --- a/examples/testbed/framework/profiler_recorder.h +++ /dev/null @@ -1,58 +0,0 @@ -/* -* Copyright (c) 2016-2019 Irlan Robson https://irlanrobson.github.io -* -* This software is provided 'as-is', without any express or implied -* warranty. In no event will the authors be held liable for any damages -* arising from the use of this software. -* Permission is granted to anyone to use this software for any purpose, -* including commercial applications, and to alter it and redistribute it -* freely, subject to the following restrictions: -* 1. The origin of this software must not be misrepresented; you must not -* claim that you wrote the original software. If you use this software -* in a product, an acknowledgment in the product documentation would be -* appreciated but is not required. -* 2. Altered source versions must be plainly marked as such, and must not be -* misrepresented as being the original software. -* 3. This notice may not be removed or altered from any source distribution. -*/ - -#ifndef PROFILER_RECORDER_H -#define PROFILER_RECORDER_H - -#include -#include - -struct ProfilerNode; - -// A persistent profiler record -struct ProfilerRecord -{ - const char* name; - float64 elapsed; - float64 minElapsed; - float64 maxElapsed; - u32 callCount; -}; - -// This class maintains persistent profiler records -class ProfilerRecorder -{ -public: - void BuildRecords(); - - void BuildSortedRecords(b3Array& output); - - const b3Array& GetRecords() const { return m_records; } -private: - void RecurseBuildRecords(ProfilerNode* node); - - void RecurseBuildSortedRecords(ProfilerNode* node, b3Array& output); - - ProfilerRecord* FindRecord(const char* name); - - b3StackArray m_records; // persistent profiler records -}; - -extern ProfilerRecorder* g_profilerRecorder; - -#endif \ No newline at end of file diff --git a/examples/testbed/framework/profiler_st.cpp b/examples/testbed/framework/profiler_st.cpp new file mode 100644 index 0000000..bb3523a --- /dev/null +++ b/examples/testbed/framework/profiler_st.cpp @@ -0,0 +1,199 @@ +/* +* Copyright (c) 2016-2019 Irlan Robson https://irlanrobson.github.io +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +#include + +ProfilerSt* g_profilerSt = nullptr; + +ProfilerSt::ProfilerSt() : m_pool(sizeof(ProfilerStNode)) +{ + m_root = nullptr; + m_top = nullptr; +} + +ProfilerSt::~ProfilerSt() +{ + if (m_root) + { + RecurseDestroyNode(m_root); + } +} + +ProfilerStNodeStat* ProfilerSt::FindStat(const char* name) +{ + for (u32 i = 0; i < m_stats.Count(); ++i) + { + if (m_stats[i].name == name) + { + return &m_stats[i]; + } + } + + return nullptr; +} + +ProfilerStNodeStat* ProfilerSt::CreateStat() +{ + m_stats.PushBack(ProfilerStNodeStat()); + return &m_stats.Back(); +} + +ProfilerStNode* ProfilerSt::CreateNode() +{ + void* block = m_pool.Allocate(); + return new (block) ProfilerStNode(); +} + +void ProfilerSt::DestroyNode(ProfilerStNode* node) +{ + node->~ProfilerStNode(); + m_pool.Free(node); +} + +void ProfilerSt::RecurseDestroyNode(ProfilerStNode* node) +{ + for (u32 i = 0; i < node->children.Count(); ++i) + { + return RecurseDestroyNode(node->children[i]); + } + + DestroyNode(node); +} + +static ProfilerStNode* RecurseFindNode(ProfilerStNode* node, const char* name) +{ + if (node->name == name) + { + return node; + } + + ProfilerStNode* result = nullptr; + for (u32 i = 0; result == nullptr && i < node->children.Count(); ++i) + { + result = RecurseFindNode(node->children[i], name); + } + + return result; +} + +ProfilerStNode* ProfilerSt::FindNode(const char* name) +{ + if (m_top) + { + return RecurseFindNode(m_top, name); + } + + if (m_root) + { + return RecurseFindNode(m_root, name); + } + + return nullptr; +} + +void ProfilerSt::BeginScope(const char* name) +{ + ProfilerStNode* fn = FindNode(name); + + if (fn) + { + m_time.Update(); + fn->t0 = m_time.GetCurrentMilis(); + ++fn->callCount; + m_top = fn; + return; + } + + m_time.Update(); + + ProfilerStNode* n = CreateNode(); + n->name = name; + n->t0 = m_time.GetCurrentMilis(); + n->elapsed = 0.0f; + n->callCount = 1; + n->parent = m_top; + n->stat = nullptr; + + if (m_root == nullptr) + { + m_root = n; + m_top = n; + + return; + } + + if (m_top) + { + m_top->children.PushBack(n); + } + + m_top = n; +} + +void ProfilerSt::EndScope() +{ + assert(m_top != nullptr); + + m_time.Update(); + + m_top->t1 = m_time.GetCurrentMilis(); + + float64 elapsedTime = m_top->t1 - m_top->t0; + + m_top->elapsed += elapsedTime; + + ProfilerStNodeStat* stat = FindStat(m_top->name); + + if (stat == nullptr) + { + stat = CreateStat(); + stat->name = m_top->name; + stat->minElapsed = elapsedTime; + stat->maxElapsed = elapsedTime; + } + else + { + stat->minElapsed = b3Min(stat->minElapsed, elapsedTime); + stat->maxElapsed = b3Max(stat->maxElapsed, elapsedTime); + } + + if (m_top->stat == nullptr) + { + m_top->stat = stat; + } + + assert(m_top->stat == stat); + + m_top = m_top->parent; +} + +void ProfilerSt::Begin() +{ + assert(m_top == nullptr); +} + +void ProfilerSt::End() +{ + assert(m_top == nullptr); + + if (m_root) + { + RecurseDestroyNode(m_root); + m_root = nullptr; + } +} \ No newline at end of file diff --git a/examples/testbed/framework/profiler_st.h b/examples/testbed/framework/profiler_st.h new file mode 100644 index 0000000..9cb1290 --- /dev/null +++ b/examples/testbed/framework/profiler_st.h @@ -0,0 +1,94 @@ +/* +* Copyright (c) 2016-2019 Irlan Robson https://irlanrobson.github.io +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef PROFILER_ST_H +#define PROFILER_ST_H + +#include +#include +#include +#include + +// Profiler tree node statistics +struct ProfilerStNodeStat +{ + const char* name; + float64 minElapsed; + float64 maxElapsed; +}; + +// A profiler tree node +struct ProfilerStNode +{ + const char* name; + float64 t0; + float64 t1; + + float64 elapsed; + u32 callCount; + + ProfilerStNode* parent; + b3StackArray children; + + ProfilerStNodeStat* stat; +}; + +// A profiler tree +class ProfilerSt +{ +public: + ProfilerSt(); + + ~ProfilerSt(); + + // Must be called before profiling. + void Begin(); + + // Must be called after profiling. + void End(); + + // Begin a new scope. + void BeginScope(const char* name); + + // End the top scope. + void EndScope(); + + ProfilerStNode* GetRoot() { return m_root; } +private: + ProfilerStNode* CreateNode(); + void DestroyNode(ProfilerStNode* node); + + void RecurseDestroyNode(ProfilerStNode* node); + + ProfilerStNode* FindNode(const char* name); + + ProfilerStNodeStat* CreateStat(); + + ProfilerStNodeStat* FindStat(const char* name); + + b3BlockPool m_pool; // pool of nodes + b3Time m_time; // timer + ProfilerStNode* m_root; // tree root node + ProfilerStNode* m_top; // top node + + b3StackArray m_stats; // node statistics +}; + +extern ProfilerSt* g_profilerSt; + +#endif \ No newline at end of file diff --git a/examples/testbed/framework/test.cpp b/examples/testbed/framework/test.cpp index f5779aa..d6dc8b0 100644 --- a/examples/testbed/framework/test.cpp +++ b/examples/testbed/framework/test.cpp @@ -18,6 +18,7 @@ #include #include +#include extern u32 b3_allocCalls, b3_maxAllocCalls; extern u32 b3_convexCalls, b3_convexCacheHits; @@ -27,11 +28,13 @@ extern bool b3_convexCache; void b3BeginProfileScope(const char* name) { g_profiler->BeginScope(name); + g_profilerSt->BeginScope(name); } void b3EndProfileScope() { g_profiler->EndScope(); + g_profilerSt->EndScope(); } Test::Test() : diff --git a/examples/testbed/framework/view.cpp b/examples/testbed/framework/view.cpp index adaea83..0f4cffb 100644 --- a/examples/testbed/framework/view.cpp +++ b/examples/testbed/framework/view.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #if defined (U_OPENGL_2) @@ -218,8 +219,8 @@ void View::Interface() if (ImGui::BeginMenu("View")) { - ImGui::MenuItem("Profile", "", &settings.drawProfile); ImGui::MenuItem("Profile Tree", "", &settings.drawProfileTree); + ImGui::MenuItem("Profile Tree Statistics", "", &settings.drawProfileTreeStats); ImGui::MenuItem("Statistics", "", &settings.drawStats); ImGui::Separator(); @@ -435,7 +436,7 @@ void View::InterfaceProfileTree() ImGui::SetNextWindowPos(ImVec2(0.0f, wp.y + ws.y)); ImGui::SetNextWindowSize(ImVec2(g_camera->m_width - 250.0f, 0.0f)); - ImGui::Begin("##ProfileTree", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Begin("Profile Tree", NULL, ImGuiWindowFlags_AlwaysAutoResize); ProfilerNode* root = g_profiler->GetRoot(); if (root) @@ -449,6 +450,64 @@ void View::InterfaceProfileTree() ImGui::PopStyleVar(); } +static void TreeNode(ProfilerStNode* node, u32& index) +{ + ImGui::PushID(index); + ++index; + + if (ImGui::TreeNode(node->name)) + { + ImGui::Text("%.4f (min = %.4f) (max = %.4f) (calls = %d) [ms]", node->elapsed, node->stat->minElapsed, node->stat->maxElapsed, node->callCount); + + for (u32 i = 0; i < node->children.Count(); ++i) + { + TreeNode(node->children[i], index); + } + ImGui::TreePop(); + } + + ImGui::PopID(); +} + +void View::InterfaceProfileTreeStats() +{ + ImGui::Begin("Overlay", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar); + ImVec2 wp = ImGui::GetWindowPos(); + ImVec2 ws = ImGui::GetWindowSize(); + ImGui::End(); + + wp.y = wp.y + ws.y; + + if (g_settings->drawProfileTree) + { + ImGui::Begin("Profile Tree", NULL, ImGuiWindowFlags_AlwaysAutoResize); + ImVec2 ptwp = ImGui::GetWindowPos(); + ImVec2 ptws = ImGui::GetWindowSize(); + ImGui::End(); + + wp.y = ptwp.y + ptws.y; + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + + ImGui::SetNextWindowBgAlpha(0.0f); + ImGui::SetNextWindowPos(ImVec2(0.0f, wp.y)); + ImGui::SetNextWindowSize(ImVec2(g_camera->m_width - 250.0f, 0.0f)); + + ImGui::Begin("Profile Tree Statistics", NULL, ImGuiWindowFlags_AlwaysAutoResize); + + ProfilerStNode* root = g_profilerSt->GetRoot(); + if (root) + { + u32 index = 0; + TreeNode(root, index); + } + + ImGui::End(); + + ImGui::PopStyleVar(); +} + void View::EndInterface() { ImGui::PopStyleVar(); diff --git a/examples/testbed/framework/view.h b/examples/testbed/framework/view.h index 2650e81..3851e0c 100644 --- a/examples/testbed/framework/view.h +++ b/examples/testbed/framework/view.h @@ -42,6 +42,7 @@ public: void BeginInterface(); void Interface(); void InterfaceProfileTree(); + void InterfaceProfileTreeStats(); void EndInterface(); private: friend class ViewModel; diff --git a/examples/testbed/framework/view_model.h b/examples/testbed/framework/view_model.h index 2d133ef..09f46ab 100644 --- a/examples/testbed/framework/view_model.h +++ b/examples/testbed/framework/view_model.h @@ -32,8 +32,8 @@ struct Settings drawLines = true; drawTriangles = true; drawGrid = true; - drawProfile = false; drawProfileTree = false; + drawProfileTreeStats = false; drawStats = false; } @@ -43,8 +43,8 @@ struct Settings bool drawLines; bool drawTriangles; bool drawGrid; - bool drawProfile; bool drawProfileTree; + bool drawProfileTreeStats; bool drawStats; };