diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5673b8e00..4ea37af81 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,7 @@ set(Magnum_SRCS Framebuffer.cpp IndexedMesh.cpp Mesh.cpp + Profiler.cpp Query.cpp Renderbuffer.cpp Shader.cpp @@ -46,6 +47,7 @@ set(Magnum_HEADERS IndexedMesh.h Magnum.h Mesh.h + Profiler.h Query.h Renderbuffer.h Shader.h diff --git a/src/Profiler.cpp b/src/Profiler.cpp new file mode 100644 index 000000000..dcede5b73 --- /dev/null +++ b/src/Profiler.cpp @@ -0,0 +1,111 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "Profiler.h" + +#include +#include +#include + +using namespace std; + +namespace Magnum { + +Profiler::Section Profiler::addSection(const string& name) { + CORRADE_ASSERT(!enabled, "Profiler: cannot add section when profiling is enabled", 0); + sections.push_back(name); + return sections.size()-1; +} + +void Profiler::setMeasureDuration(size_t frames) { + CORRADE_ASSERT(!enabled, "Profiler: cannot set measure duration when profiling is enabled", ); + measureDuration = frames; +} + +void Profiler::enable() { + enabled = true; + frameData.assign(measureDuration*sections.size(), chrono::high_resolution_clock::duration::zero()); + totalData.assign(sections.size(), chrono::high_resolution_clock::duration::zero()); + frameCount = 0; +} + +void Profiler::disable() { + enabled = false; +} + +void Profiler::start(Section section) { + if(!enabled) return; + CORRADE_ASSERT(section < sections.size(), "Profiler: unknown section passed to start()", ); + + save(); + + currentSection = section; +} + +void Profiler::stop() { + if(!enabled) return; + + save(); + + previousTime = chrono::high_resolution_clock::time_point(); +} + +void Profiler::save() { + auto now = chrono::high_resolution_clock::now(); + + /* If the profiler is already running, add time to given section */ + if(previousTime != chrono::high_resolution_clock::time_point()) + frameData[currentFrame*sections.size()+currentSection] += now-previousTime; + + /* Set current time as previous for next section */ + previousTime = now; +} + +void Profiler::nextFrame() { + if(!enabled) return; + + /* Next frame index */ + size_t nextFrame = (currentFrame+1) % measureDuration; + + /* Add times of current frame to total */ + for(size_t i = 0; i != sections.size(); ++i) + totalData[i] += frameData[currentFrame*sections.size()+i]; + + /* Subtract times of next frame from total and erase them */ + for(size_t i = 0; i != sections.size(); ++i) { + totalData[i] -= frameData[nextFrame*sections.size()+i]; + frameData[nextFrame*sections.size()+i] = chrono::high_resolution_clock::duration::zero(); + } + + /* Advance to next frame */ + currentFrame = nextFrame; + + if(frameCount < measureDuration) ++frameCount; +} + +void Profiler::printStatistics() { + if(!enabled) return; + + vector totalSorted(sections.size()); + iota(totalSorted.begin(), totalSorted.end(), 0); + + sort(totalSorted.begin(), totalSorted.end(), [this](size_t i, size_t j){return totalData[i] > totalData[j];}); + + Corrade::Utility::Debug() << "Statistics for last" << measureDuration << "frames:"; + for(size_t i = 0; i != sections.size(); ++i) + Corrade::Utility::Debug() << ' ' << sections[totalSorted[i]] << chrono::microseconds(totalData[totalSorted[i]]).count()/frameCount << u8"µs"; +} + +} diff --git a/src/Profiler.h b/src/Profiler.h new file mode 100644 index 000000000..a3da6ef56 --- /dev/null +++ b/src/Profiler.h @@ -0,0 +1,206 @@ +#ifndef Magnum_Profiler_h +#define Magnum_Profiler_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Profiler + */ + +#include +#include +#include +#include + +#include "magnumVisibility.h" + +namespace Magnum { + +/** +@brief Measuring elapsed time in each frame + +Measures time passed during specified sections of each frame. It's meant to be +used in rendering and event loops (e.g. Contexts::GlutContext::drawEvent()), +but it's possible to use it standalone elsewhere. Example usage: +@code +Profiler p; + +// Register named sections +struct { + Profiler::Section ai, physics, draw, bufferSwap; +} sections; +sections.ai = p.addSection("AI"); +sections.physics = p.addSection("Physics"); +sections.draw = p.addSection("Drawing"); +sections.bufferSwap = p.addSection("Buffer swap"); + +// Enable profiling +p.enable(); + +// Mark sections in draw function +void MyContext::drawEvent() { + p.start(); + + // ... misc stuff belogning to "Other" section + + p.start(sections.ai); + + // ... AI computation + + p.start(sections.physics); + + // ... physics simulation + + p.start(sections.draw); + + scene.draw() + + p.start(sections.bufferSwap); + + swapBuffers(); + + // Count everything before next call to drawEvent() into "Other" section + p.start(); + + // Mark start of next frame + p.nextFrame(); +} + +// Print statistics to debug output, showing how much time each section took +p.printStatistics(); +@endcode + +It's possible to start profiler only for certain parts of the code and then +stop it again using stop(), if you are not interested in profiling the rest. + +@todo Some unit testing +@todo More time intervals +*/ +class MAGNUM_EXPORT Profiler { + public: + /** + * @brief Section ID + * + * @see otherSection, addSection(), start(Section) + */ + typedef unsigned int Section; + + /** + * @brief Default section + * + * @see start() + */ + static const Section otherSection = 0; + + #ifndef DOXYGEN_GENERATING_OUTPUT + Profiler(): enabled(false), measureDuration(60), currentFrame(0), frameCount(0), sections{"Other"}, currentSection(otherSection) {} + #endif + + /** + * @brief Set measure duration + * + * Measured data are averaged through given frame count. Default value + * is 60. + * @attention This function cannot be called if profiling is enabled. + */ + void setMeasureDuration(size_t frames); + + /** + * @brief Add named section + * + * @attention This function cannot be called if profiling is enabled. + * @see otherSection, start(Section), stop() + */ + Section addSection(const std::string& name); + + /** + * @brief Whether profiling is enabled + * + * If the profiling is not enabled, calls to start() and stop() have + * no effect. + */ + bool isEnabled() { return enabled; } + + /** + * @brief Enable profiling + * + * Clears already mesaured data. + * @see disable(), isEnabled() + */ + void enable(); + + /** + * @brief Disable profiling + * + * @see enable(), isEnabled() + */ + void disable(); + + /** + * @brief Start profiling of given named section + * + * If profiling is already running, current time is saved for previous + * section. + * @see @ref otherSection, start(Section) + */ + void start(Section section); + + /** + * @brief Start profiling of "other" section + * + * Same as calling `start(Profiler::otherSection)`. + * @note Does nothing if profiling is disabled. + */ + inline void start() { start(otherSection); } + + /** + * @brief Stop profiling + * + * Current time is saved for previous section. + * @note Does nothing if profiling is disabled. + */ + void stop(); + + /** + * @brief Save data from previous frame and advance to another + * + * Call at the end of each frame. + * @note Does nothing if profiling is disabled. + */ + void nextFrame(); + + /** + * @brief Print statistics + * + * Prints statistics about previous frame ordered by duration. + * @note Does nothing if profiling is disabled. + */ + void printStatistics(); + + private: + void save(); + + bool enabled; + size_t measureDuration, currentFrame, frameCount; + std::vector sections; + std::vector frameData; + std::vector totalData; + std::chrono::high_resolution_clock::time_point previousTime; + Section currentSection; +}; + +} + +#endif