diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4e2d9cbe..2e147b34 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -65,6 +65,13 @@ if(ARMADILLO_FOUND) target_link_libraries(fps OpenGL::GL glfw Freetype::Freetype) endif() + add_executable(fps_with_profiler fps_with_profiler.cpp) + if(OpenMP_CXX_FOUND) + target_link_libraries(fps_with_profiler OpenMP::OpenMP_CXX OpenGL::GL glfw Freetype::Freetype) + else() + target_link_libraries(fps_with_profiler OpenGL::GL glfw Freetype::Freetype) + endif() + add_executable(cartgrid cartgrid.cpp) target_link_libraries(cartgrid OpenGL::GL glfw Freetype::Freetype) diff --git a/examples/fps.cpp b/examples/fps.cpp index 689878f8..d8a934a4 100644 --- a/examples/fps.cpp +++ b/examples/fps.cpp @@ -74,7 +74,7 @@ int main() all_dur += sc::now() - t00; t00 = sc::now(); - v.waitevents (0.00001); + v.poll(); if (k > 8.0f) { k = 1.0f; } t0 = sc::now(); @@ -109,6 +109,7 @@ int main() update_dur = sc::duration{0}; all_dur = sc::duration{0}; fcount = 0; + v.waitevents (0.00001); } v.render(); diff --git a/examples/fps_with_profiler.cpp b/examples/fps_with_profiler.cpp new file mode 100644 index 00000000..bb01b14d --- /dev/null +++ b/examples/fps_with_profiler.cpp @@ -0,0 +1,95 @@ +/* + * An example mplot::Visual scene, containing a HexGrid. + * + * Using mplot::fps::profiler for the FPS profile + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +int main() +{ + mplot::Visual v(1600, 1000, "mplot::Visual"); + v.fov = 15.0f; + v.zFar = 200.0f; + v.lightingEffects(); + mplot::VisualTextModel<>* fps_tm; + v.addLabel ("0 FPS", {0.13f, -0.23f, 0.0f}, fps_tm); // With fps_tm can update the VisualTextModel with fps_tm->setupText("new text") + + // Create a hexgrid to show in the scene + constexpr float hex_to_hex = 0.02f; + + sm::hexgrid hg(hex_to_hex, 15.0f, 0.0f); + hg.setEllipticalBoundary (4.0f, 4.0f); + std::cout << "Number of hexes in grid:" << hg.num() << std::endl; + std::stringstream sss; + sss << "Surface evaluated at " << hg.num() << " coordinates"; + v.addLabel (sss.str(), {0.0f, 0.0f, 0.0f}); + + // Make some dummy data (a radially symmetric Bessel fn) to make an interesting surface + std::vector data(hg.num(), 0.0f); + std::vector r(hg.num(), 0.0f); + float k = 1.0f; + for (unsigned int hi = 0; hi < hg.num(); ++hi) { + // x/y: hg.d_x[hi] hg.d_y[hi] + r[hi] = std::sqrt (hg.d_x[hi] * hg.d_x[hi] + hg.d_y[hi] * hg.d_y[hi]); + data[hi] = std::sin (k * r[hi]) / k * r[hi]; + } + + // Add a HexGridVisual to display the HexGrid within the mplot::Visual scene + sm::vec offset = { 0.0f, -0.05f, 0.0f }; + auto hgv = std::make_unique>(&hg, offset); + v.bindmodel (hgv); + hgv->setScalarData (&data); + hgv->hexVisMode = mplot::HexVisMode::Triangles; + hgv->finalize(); + auto hgvp = v.addVisualModel (hgv); + + unsigned int fcount = 0u; + + // Our profiler object + mplot::fps::profiler fps_profiler; + + while (v.readyToFinish() == false) { + + v.poll(); + + fps_profiler.at_begin (1000); + + if (k > 8.0f) { k = 1.0f; } + +#pragma omp parallel for shared(r,k,data) + for (unsigned int hi = 0; hi < hg.num(); ++hi) { + data[hi] = std::sin (k * r[hi]) / k * r[hi]; + } + if (v.validVisualModel (hgvp) != nullptr) { // Test hgvp is still valid + hgvp->updateData (&data); + } + k += 0.02f; + + if (fcount == 500) { // Update FPS text + fps_tm->setupText (fps_profiler.fps_txt); + fcount = 0; + v.waitevents (0.00001); + } + + v.render(); + fcount++; + + fps_profiler.at_end(); + } + + return 0; +} diff --git a/mplot/CMakeLists.txt b/mplot/CMakeLists.txt index 830f9412..701f78b7 100644 --- a/mplot/CMakeLists.txt +++ b/mplot/CMakeLists.txt @@ -125,6 +125,9 @@ add_subdirectory(wx) # Compound ray glue code add_subdirectory(compoundray) +# FPS profiling +add_subdirectory(fps) + # Install the EXPORT so that mathplot has its own .cmake file and find_package(mathplot) should work install(FILES mathplot-config.cmake DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/mathplot) #install(EXPORT mathplot DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/mathplot) diff --git a/mplot/fps/CMakeLists.txt b/mplot/fps/CMakeLists.txt new file mode 100644 index 00000000..694e8732 --- /dev/null +++ b/mplot/fps/CMakeLists.txt @@ -0,0 +1 @@ +install(FILES profiler.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mplot/fps) diff --git a/mplot/fps/profiler.h b/mplot/fps/profiler.h new file mode 100644 index 00000000..f0ca6e43 --- /dev/null +++ b/mplot/fps/profiler.h @@ -0,0 +1,65 @@ +/* + * A profiler that computes FPS and manages an std::string that can be used on a graphical info + * screen. + * + * Author: Seb James + * Date: 2024-2025 + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace mplot::fps +{ + using namespace std::chrono; + using sc = std::chrono::steady_clock; + + struct profiler + { + sc::time_point t0 = sc::now(); + sc::time_point t1 = sc::now(); + + std::deque fps; + double fps_mean = 0.0; // a running-mean of fps + unsigned int fps_mean_over_n_samples_last = 0; + + // Current FPS text + std::string fps_txt; + + // Call at the start of the loop that you're timing + void at_begin (unsigned int fps_mean_over_n_samples) + { + sc::duration t_d = this->t1 - this->t0; + if (fps_mean_over_n_samples != fps_mean_over_n_samples_last) { + // Reset counters + this->fps.clear(); + this->fps_mean = 0.0; + this->fps_mean_over_n_samples_last = fps_mean_over_n_samples; + } + double fps_mean_period = 1.0 / fps_mean_over_n_samples; + double fps_now = 0.0; + double usecs = static_cast(duration_cast(t_d).count()); + if (usecs > 0.0) { fps_now = 1000000.0 / usecs; } + this->fps.push_back (fps_now * fps_mean_period); + this->fps_mean += this->fps.back(); + if (this->fps.size() > fps_mean_over_n_samples) { + this->fps_mean -= this->fps.front(); + this->fps.pop_front(); + } + std::stringstream ss; + // Stream into ss ready for display + ss << static_cast(std::round(this->fps_mean)) << " FPS"; + this->fps_txt = ss.str(); + + this->t0 = sc::now(); + } + + // Call at the end of the loop that you're timing + void at_end () { this->t1 = sc::now(); } + }; + +} // namespace