diff --git a/examples/testbed/framework/json_profiler.cpp b/examples/testbed/framework/json_profiler.cpp new file mode 100644 index 0000000..d11fa5a --- /dev/null +++ b/examples/testbed/framework/json_profiler.cpp @@ -0,0 +1,122 @@ +/* +* Copyright (c) 2016-2016 Irlan Robson http://www.irlan.net +* +* 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 + +#define STRING(x) String(x, sizeof(x) - 1) + +JsonProfiler::JsonProfiler() +{ + m_file = nullptr; + m_stream = nullptr; + m_writer = nullptr; +} + +JsonProfiler::~JsonProfiler() +{ + +} + +void JsonProfiler::BeginEvents() +{ + if (m_file) + { + return; + } + + m_file = fopen("profile.json", "wt"); + if (!m_file) + { + return; + } + + static char buffer[512]; + m_stream = new FileWriteStream(m_file, buffer, sizeof(buffer)); + + m_writer = new Writer(*m_stream); + + m_writer->StartObject(); + m_writer->STRING("traceEvents"); + m_writer->StartArray(); +} + +void JsonProfiler::EndEvents() +{ + if (!m_writer) + { + return; + } + + m_writer->EndArray(); + m_writer->EndObject(); + + delete m_writer; + m_writer = nullptr; + + delete m_stream; + m_stream = nullptr; + + fclose(m_file); + m_file = nullptr; +} + +void JsonProfiler::BeginEvent(i32 tid, i32 pid, const char* name, float64 t) +{ + if (!m_writer) + { + return; + } + + const char* phase = "B"; + + float64 scale = 1000.0; + + m_writer->StartObject(); + m_writer->STRING("pid"); m_writer->Int(pid); + m_writer->STRING("tid"); m_writer->Int(tid); + m_writer->STRING("ts"); m_writer->Int64((u64)(t * scale)); + m_writer->STRING("ph"); m_writer->String(phase, 1); + m_writer->STRING("cat"); m_writer->STRING("physics"); + m_writer->STRING("name"); m_writer->String(name, strlen(name)); + m_writer->STRING("args"); m_writer->StartObject(); m_writer->EndObject(); + m_writer->EndObject(); +} + +void JsonProfiler::EndEvent(i32 tid, i32 pid, const char* name, float64 t) +{ + if (!m_writer) + { + return; + } + + const char* phase = "E"; + + float64 scale = 1000.0; + + m_writer->StartObject(); + m_writer->STRING("pid"); m_writer->Int(pid); + m_writer->STRING("tid"); m_writer->Int(tid); + m_writer->STRING("ts"); m_writer->Int64((u64)(t * scale)); + m_writer->STRING("ph"); m_writer->String(phase, 1); + m_writer->STRING("cat"); m_writer->STRING("physics"); + m_writer->STRING("name"); m_writer->String(name, strlen(name)); + m_writer->STRING("args"); m_writer->StartObject(); m_writer->EndObject(); + m_writer->EndObject(); +} + +#undef STRING \ No newline at end of file diff --git a/examples/testbed/framework/json_profiler.h b/examples/testbed/framework/json_profiler.h new file mode 100644 index 0000000..3ff6fb8 --- /dev/null +++ b/examples/testbed/framework/json_profiler.h @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2016-2016 Irlan Robson http://www.irlan.net +* +* 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 JSON_PROFILER_H +#define JSON_PROFILER_H + +#include + +#include +#include + +using namespace rapidjson; + +// The following profiler listener is notified by a profiler when events are initiated +// or terminated. +// When it receives the notification it immediately saves its data into a .json file format. +// The .json file can be read and interpreted by the Google Chrome Tracing. +// Say chrome://tracing to the web browser and load the file +// This file is by default called "profile.json". Any name can be given. +// For implementation details, see json_profile.cpp. +class JsonProfiler +{ +public: + JsonProfiler(); + ~JsonProfiler(); + + void BeginEvents(); + + void EndEvents(); + + void BeginEvent(i32 tid, i32 pid, const char* name, float64 time); + + void EndEvent(i32 tid, i32 pid, const char* name, float64 time); +private: + FILE * m_file; + FileWriteStream* m_stream; + Writer* m_writer; +}; + +#endif \ No newline at end of file diff --git a/examples/testbed/framework/main.cpp b/examples/testbed/framework/main.cpp index 5d4cfab..17224b4 100644 --- a/examples/testbed/framework/main.cpp +++ b/examples/testbed/framework/main.cpp @@ -40,9 +40,11 @@ // error #endif +#include #include #include +// GLFWwindow* g_window; Settings g_settings; Test* g_test; @@ -50,12 +52,27 @@ u32 g_testCount; Camera g_camera; DebugDraw* g_debugDraw; Profiler* g_profiler; +TestbedListener g_testbedListener; +ProfilerListener* g_profilerListener = &g_testbedListener; +RecorderProfiler g_recorderProfiler; bool g_leftDown; bool g_rightDown; bool g_shiftDown; b3Vec2 g_ps0; const char* g_logName = { "log" }; +// These two functions below implement Bounce's profiling interfaces. +// If you're not concerned with profiling then just define two slut functions. +bool b3PushProfileScope(const char* name) +{ + return g_profiler->PushEvent(name); +} + +void b3PopProfileScope() +{ + g_profiler->PopEvent(); +} + static void WindowSize(int w, int h) { g_camera.m_width = float32(w); diff --git a/examples/testbed/framework/profiler.cpp b/examples/testbed/framework/profiler.cpp index e240ce8..1c4aec9 100644 --- a/examples/testbed/framework/profiler.cpp +++ b/examples/testbed/framework/profiler.cpp @@ -17,200 +17,72 @@ */ #include -#include -#include -struct Event +Profiler::Profiler() { - i32 tid; - i32 pid; - const char* name; - float64 t0; - float64 t1; - Event* parent; -}; + m_top = nullptr; +} -static b3Time s_time; -static b3BoundedQueue s_events; -static Event* s_top = NULL; - -bool b3PushProfileScope(const char* name) +Profiler::~Profiler() { - s_time.Update(); +} - Event e; +bool Profiler::PushEvent(const char* name) +{ + m_time.Update(); + + ProfilerEvent e; e.tid = -1; e.pid = -1; - e.t0 = s_time.GetCurrentMilis(); - e.t1 = 0; + e.t0 = m_time.GetCurrentMilis(); + e.t1 = 0.0; e.name = name; - e.parent = s_top; + e.parent = m_top; - Event* back = s_events.Push(e); + ProfilerEvent* back = m_events.Push(e); if (back) { - s_top = back; + m_top = back; } return back != NULL; } -void b3PopProfileScope() +void Profiler::PopEvent() { - B3_ASSERT(s_top); - B3_ASSERT(s_top->t1 == 0); + B3_ASSERT(m_top); + B3_ASSERT(m_top->t1 == 0.0); - s_time.Update(); - s_top->t1 = s_time.GetCurrentMilis(); - B3_ASSERT(s_top->t1 != 0); - s_top = s_top->parent; + m_time.Update(); + m_top->t1 = m_time.GetCurrentMilis(); + B3_ASSERT(m_top->t1 != 0.0); + m_top = m_top->parent; } -void ProfileBegin() +void Profiler::Begin() { - B3_ASSERT(s_events.IsEmpty()); + // If this assert is hit then it means Profiler::End hasn't been called. + B3_ASSERT(m_events.IsEmpty()); } -void ProfileEnd() +void Profiler::End(ProfilerListener* listener) { - ProfileBeginEvents(); + listener->BeginEvents(); - while (s_events.IsEmpty() == false) + while (m_events.IsEmpty() == false) { - const Event& e = s_events.Front(); - s_events.Pop(); + const ProfilerEvent& e = m_events.Front(); - ProfileEvent(e.tid, e.pid, e.name, e.t0, e_begin); - ProfileEvent(e.tid, e.pid, e.name, e.t1, e_end); - ProfileEvent(e.tid, e.pid, e.name, e.t1 - e.t0); + m_events.Pop(); + + listener->BeginEvent(e.tid, e.pid, e.name, e.t0); + + listener->EndEvent(e.tid, e.pid, e.name, e.t1); + + listener->Duration(e.name, e.t1 - e.t0); } - B3_ASSERT(s_events.IsEmpty()); + B3_ASSERT(m_events.IsEmpty()); - ProfileEndEvents(); -} - -// - -#define PROFILER_SCREEN 1 -#define PROFILER_JSON 2 - -#define PROFILER_OUTPUT PROFILER_SCREEN - -#if PROFILER_OUTPUT == PROFILER_SCREEN - -extern Profiler* g_profiler; - -void ProfileBeginEvents() -{ - -} - -void ProfileEvent(i32 tid, i32 pid, const char* name, float64 t, ProfileType type) -{ - -} - -void ProfileEvent(i32 tid, i32 pid, const char* name, float64 elapsed) -{ - g_profiler->Add(name, elapsed); -} - -void ProfileEndEvents() -{ - -} - -#elif PROFILER_OUTPUT == PROFILER_JSON - -#include -#include - -using namespace rapidjson; - -static FILE* s_file = NULL; -static FileWriteStream* s_stream = NULL; -static Writer* s_writer = NULL; - -#define STRING(x) String(x, sizeof(x) - 1) - -void ProfileBeginEvents() -{ - if (s_file) - { - return; - } - - s_file = fopen("profile.json", "wt"); - if (!s_file) - { - return; - } - - static char buffer[512]; - s_stream = new FileWriteStream(s_file, buffer, sizeof(buffer)); - - s_writer = new Writer(*s_stream); - - s_writer->StartObject(); - s_writer->STRING("traceEvents"); - s_writer->StartArray(); -} - -void ProfileEvent(i32 tid, i32 pid, const char* name, float64 t, ProfileType type) -{ - if (!s_writer) - { - return; - } - - const char* phase = 0; - switch (type) - { - case ProfileType::e_begin: phase = "B"; break; - case ProfileType::e_end: phase = "E"; break; - default: B3_ASSERT(false); - } - - float64 scale = 1000.0; - - s_writer->StartObject(); - s_writer->STRING("pid"); s_writer->Int(pid); - s_writer->STRING("tid"); s_writer->Int(tid); - s_writer->STRING("ts"); s_writer->Int64((u64)(t * scale)); - s_writer->STRING("ph"); s_writer->String(phase, 1); - s_writer->STRING("cat"); s_writer->STRING("physics"); - s_writer->STRING("name"); s_writer->String(name, strlen(name)); - s_writer->STRING("args"); s_writer->StartObject(); s_writer->EndObject(); - s_writer->EndObject(); -} - -void ProfileEvent(i32 tid, i32 pid, const char* name, float64 elapsed) -{ -} - -void ProfileEndEvents() -{ - if (!s_writer) - { - return; - } - - s_writer->EndArray(); - s_writer->EndObject(); - - delete s_writer; - s_writer = NULL; - - delete s_stream; - s_stream = NULL; - - fclose(s_file); - s_file = NULL; -} - -#undef STRING - -#else - -#endif \ No newline at end of file + listener->EndEvents(); +} \ No newline at end of file diff --git a/examples/testbed/framework/profiler.h b/examples/testbed/framework/profiler.h index 575300c..441863c 100644 --- a/examples/testbed/framework/profiler.h +++ b/examples/testbed/framework/profiler.h @@ -20,64 +20,94 @@ #define PROFILER_H #include +#include +#include #include -enum ProfileType +// This defines the maximum number of profiler events that can be +// queued per frame until the function Profiler::Flush is called. +#define MAX_PROFILER_EVENTS 256 + +class ProfilerListener; + +// A time-stamped profiler event. +struct ProfilerEvent { - e_begin, - e_end -}; - -void ProfileBeginEvents(); - -void ProfileEvent(i32 tid, i32 pid, const char* name, float64 time, ProfileType type); -void ProfileEvent(i32 tid, i32 pid, const char* name, float64 elapsed); - -void ProfileEndEvents(); - -void ProfileBegin(); -void ProfileEnd(); - -struct ProfileRecord -{ - float64 elapsed; - float64 maxElapsed; + i32 tid; + i32 pid; const char* name; + float64 t0; + float64 t1; + ProfilerEvent* parent; }; +// A single-threaded event-based profiler. class Profiler { public: - void Clear() + Profiler(); + + ~Profiler(); + + // Must be called before profiling. + void Begin(); + + // Must be called after profiling. + // The function will report all events in this profiler + // to the given event listener in the correct calling order. + // This function also flushes the profiler. + void End(ProfilerListener* listener); + + // Add a profiler event to the queue. + // Return true if the even has been added to the event queue + // or false if the queue is full. + bool PushEvent(const char* name); + + // Remove the top profiler event. + void PopEvent(); +private: + b3Time m_time; + b3BoundedQueue m_events; + ProfilerEvent* m_top; +}; + +// Any implementation of this interface passed to Profiler::End will listen to profile events. +class ProfilerListener +{ +public: + virtual ~ProfilerListener() { } + + // This function is called when profiling has began. + virtual void BeginEvents() { } + + // This function is called when profiling has ended. + virtual void EndEvents() { } + + // This function is called when a profiler event begins. + virtual void BeginEvent(i32 tid, i32 pid, const char* name, float64 time) { - for (u32 i = 0; i < m_records.Count(); ++i) - { - m_records[i].elapsed = 0; - } + B3_NOT_USED(tid); + B3_NOT_USED(pid); + B3_NOT_USED(name); + B3_NOT_USED(time); } - void Add(const char* name, float64 elapsed) + // This function is called when a profiler event ends. + virtual void EndEvent(i32 tid, i32 pid, const char* name, float64 time) { - for (u32 i = 0; i < m_records.Count(); ++i) - { - ProfileRecord& r = m_records[i]; - if (r.name == name) - { - r.elapsed += elapsed; - r.maxElapsed = b3Max(r.maxElapsed, r.elapsed); - return; - } - } - - ProfileRecord r; - r.elapsed = elapsed; - r.maxElapsed = 0; - r.name = name; - - m_records.PushBack(r); + B3_NOT_USED(tid); + B3_NOT_USED(pid); + B3_NOT_USED(name); + B3_NOT_USED(time); } - b3StackArray m_records; + // This function is called when a profiler event ends. + // However it supplies the duration of the last begin and end events. + virtual void Duration(const char* name, float64 duration) + { + B3_NOT_USED(name); + B3_NOT_USED(duration); + } }; #endif \ No newline at end of file diff --git a/examples/testbed/framework/recorder_profiler.cpp b/examples/testbed/framework/recorder_profiler.cpp new file mode 100644 index 0000000..0327635 --- /dev/null +++ b/examples/testbed/framework/recorder_profiler.cpp @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2016-2016 Irlan Robson http://www.irlan.net +* +* 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 + +void RecorderProfiler::BeginEvents() +{ + for (u32 i = 0; i < m_records.Count(); ++i) + { + m_records[i].elapsed = 0.0; + } +} + +void RecorderProfiler::EndEvents() +{ +} + +void RecorderProfiler::Add(const char* name, float64 elapsedTime) +{ + for (u32 i = 0; i < m_records.Count(); ++i) + { + ProfilerRecord& r = m_records[i]; + if (r.name == name) + { + r.elapsed += elapsedTime; + r.maxElapsed = b3Max(r.maxElapsed, elapsedTime); + return; + } + } + + ProfilerRecord r; + r.name = name; + r.elapsed = 0.0; + r.maxElapsed = 0.0; + + m_records.PushBack(r); +} \ No newline at end of file diff --git a/examples/testbed/framework/recorder_profiler.h b/examples/testbed/framework/recorder_profiler.h new file mode 100644 index 0000000..2f822f6 --- /dev/null +++ b/examples/testbed/framework/recorder_profiler.h @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2016-2016 Irlan Robson http://www.irlan.net +* +* 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 RECORDER_PROFILER_H +#define RECORDER_PROFILER_H + +#include +#include + +// An event in the profiler event recorder. +struct ProfilerRecord +{ + float64 elapsed; + float64 maxElapsed; + const char* name; +}; + +// The profiler recorder simply keeps profile events in an event buffer, +// so that they can be rendered, saved to a file, etc. later in a +// particular position in code. +class RecorderProfiler +{ +public: + void BeginEvents(); + + void EndEvents(); + + void Add(const char* name, float64 elapsedTime); + + const b3Array& GetRecords() const { return m_records; } +private: + b3StackArray m_records; +}; + +#endif \ No newline at end of file diff --git a/examples/testbed/framework/test.cpp b/examples/testbed/framework/test.cpp index 528766b..4bac0c5 100644 --- a/examples/testbed/framework/test.cpp +++ b/examples/testbed/framework/test.cpp @@ -28,6 +28,8 @@ extern Settings g_settings; extern DebugDraw* g_debugDraw; extern Camera g_camera; extern Profiler* g_profiler; +extern ProfilerListener* g_profilerListener; +extern RecorderProfiler g_recorderProfiler; Test::Test() { @@ -117,13 +119,13 @@ void Test::Step() b3_convexCacheHits = 0; // Step - ProfileBegin(); + g_profiler->Begin(); m_world.SetSleeping(g_settings.sleep); m_world.SetWarmStart(g_settings.warmStart); m_world.Step(dt, g_settings.velocityIterations, g_settings.positionIterations); - ProfileEnd(); + g_profiler->End(g_profilerListener); g_debugDraw->Submit(); @@ -191,14 +193,14 @@ void Test::Step() if (g_settings.drawProfile) { - for (u32 i = 0; i < g_profiler->m_records.Count(); ++i) + const b3Array& records = g_recorderProfiler.GetRecords(); + for (u32 i = 0; i < records.Count(); ++i) { - const ProfileRecord& r = g_profiler->m_records[i]; + const ProfilerRecord& r = records[i]; + ImGui::Text("%s %.4f (%.4f) [ms]", r.name, r.elapsed, r.maxElapsed); } } - - g_profiler->Clear(); ImGui::End(); } diff --git a/examples/testbed/framework/testbed_listener.h b/examples/testbed/framework/testbed_listener.h new file mode 100644 index 0000000..dd7f60f --- /dev/null +++ b/examples/testbed/framework/testbed_listener.h @@ -0,0 +1,85 @@ +/* +* Copyright (c) 2016-2016 Irlan Robson http://www.irlan.net +* +* 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 TESTBED_LISTENER_H +#define TESTBED_LISTENER_H + +#include +#include + +// Set to 1 then the testbed listener will write profile events into a .json file. +// Set to 0 otherwise. +#define PROFILE_JSON 0 + +#if (PROFILE_JSON == 1) + #include +#endif + +extern RecorderProfiler g_recorderProfiler; + +class TestbedListener : public ProfilerListener +{ +public: + void BeginEvents() override + { + g_recorderProfiler.BeginEvents(); + +#if (PROFILE_JSON == 1) + m_jsonListener.BeginEvents(); +#endif + + } + + void EndEvents() override + { + g_recorderProfiler.EndEvents(); + +#if (PROFILE_JSON == 1) + m_jsonListener.EndEvents(); +#endif + + } + + void BeginEvent(i32 tid, i32 pid, const char* name, float64 time) override + { +#if (PROFILE_JSON == 1) + m_jsonListener.BeginEvent(tid, pid, name, time); +#endif + + } + + void EndEvent(i32 tid, i32 pid, const char* name, float64 time) override + { +#if (PROFILE_JSON == 1) + m_jsonListener.EndEvent(tid, pid, name, time); +#endif + + } + + void Duration(const char* name, float64 time) override + { + g_recorderProfiler.Add(name, time); + } + +#if (PROFILE_JSON == 1) + JsonProfiler m_jsonListener; +#endif + +}; + +#endif \ No newline at end of file diff --git a/examples/testbed/tests/test.h b/examples/testbed/tests/test.h index 96514d9..fcce8af 100644 --- a/examples/testbed/tests/test.h +++ b/examples/testbed/tests/test.h @@ -26,6 +26,7 @@ #include #include +#include inline float32 RandomFloat(float32 a, float32 b) {