diff --git a/src/Common/CMakeLists.txt b/src/Common/CMakeLists.txt index dcdd167d..92fd78c2 100644 --- a/src/Common/CMakeLists.txt +++ b/src/Common/CMakeLists.txt @@ -3,6 +3,7 @@ add_library( STATIC ${CMAKE_CURRENT_LIST_DIR}/Logger.cpp + ${CMAKE_CURRENT_LIST_DIR}/Profiler.cpp ) target_include_directories( diff --git a/src/Common/Profiler.cpp b/src/Common/Profiler.cpp new file mode 100644 index 00000000..37fc3ccd --- /dev/null +++ b/src/Common/Profiler.cpp @@ -0,0 +1,50 @@ +#include + +namespace shkyera { + +ProfileBlock& ProfileBlock::operator+=(const ProfileBlock& other) +{ + totalLengthInNanoSeconds += other.totalLengthInNanoSeconds; + numberOfCalls += other.numberOfCalls; + return *this; +} + +ProfileGuard::ProfileGuard(std::string&& name) : mProfileName(std::move(name)), mStartTime(std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch())) {} + +ProfileGuard::~ProfileGuard() +{ + const auto endTime = std::chrono::high_resolution_clock::now(); + const auto guardDuration = endTime - mStartTime; + const auto guardDurationInNanoSeconds = std::chrono::duration_cast(guardDuration); + Profiler::getInstance().addBlock(std::move(mProfileName), guardDurationInNanoSeconds); +} + +Profiler& Profiler::getInstance() +{ + static Profiler p; + return p; +} + +void Profiler::clear() +{ + std::unique_lock lock(mMutex); + mProfileBlocks.clear(); +} + +void Profiler::addBlock(std::string&& name, std::chrono::nanoseconds time) +{ + const auto threadId = std::this_thread::get_id(); + ProfileBlock newBlock { .totalLengthInNanoSeconds = static_cast(time.count()), .numberOfCalls = 1 }; + + std::unique_lock lock(mMutex); + mProfileBlocks[threadId][name] += newBlock; +} + +Profiler::BlocksPerThread Profiler::getProfiles() +{ + std::unique_lock lock(mMutex); + return mProfileBlocks; +} + + +} diff --git a/src/Common/Profiler.hpp b/src/Common/Profiler.hpp new file mode 100644 index 00000000..a69b6fe7 --- /dev/null +++ b/src/Common/Profiler.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace shkyera { + +struct ProfileBlock { + double totalLengthInNanoSeconds = 0; + size_t numberOfCalls = 0; + + ProfileBlock& operator+=(const ProfileBlock& other); +}; + +class ProfileGuard { + public: + ProfileGuard(std::string&& name); + ~ProfileGuard(); + + private: + std::string mProfileName; + std::chrono::high_resolution_clock::time_point mStartTime; +}; + +class Profiler { + public: + using BlocksPerThread = std::map>; + static Profiler& getInstance(); + + void clear(); + void addBlock(std::string&& name, std::chrono::nanoseconds time); + BlocksPerThread getProfiles(); + + private: + Profiler() = default; + + std::mutex mMutex; + BlocksPerThread mProfileBlocks; +}; + +#define SHKYERA_PROFILE(name) ProfileGuard __SHKYERA_UNIQUE_NAME(__LINE__) (name) +#define SHKYERA_READ_PROFILE Profiler::getInstance().getProfiles() +#define SHKYERA_CLEAR_PROFILE Profiler::getInstance().clear() + +#define __SHKYERA_UNIQUE_NAME(LINE) __SHKYERA_CONCAT(profileGuard_, LINE) +#define __SHKYERA_CONCAT(X, Y) __SHKYERA_CONCAT_IMPL(X, Y) +#define __SHKYERA_CONCAT_IMPL(X, Y) X##Y + +} diff --git a/src/InputManager/InputManager.cpp b/src/InputManager/InputManager.cpp index d155afd1..4e45396c 100644 --- a/src/InputManager/InputManager.cpp +++ b/src/InputManager/InputManager.cpp @@ -1,4 +1,5 @@ #include +#include namespace shkyera { @@ -72,6 +73,8 @@ void InputManager::unregisterMouseButtonDownCallback(MouseButton button) { } void InputManager::processInput(GLFWwindow* window) { + SHKYERA_PROFILE("InputManager::processInput"); + for (const auto& [key, callbacks] : _keyCallbacks) { if (glfwGetKey(window, key) == GLFW_PRESS) { for (const auto& callback : callbacks) { diff --git a/src/Systems/GizmoSystem.cpp b/src/Systems/GizmoSystem.cpp index bd5b8f82..ebe28876 100644 --- a/src/Systems/GizmoSystem.cpp +++ b/src/Systems/GizmoSystem.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -178,6 +179,8 @@ GizmoSystem::~GizmoSystem() void GizmoSystem::update() { + SHKYERA_PROFILE("GizmoSystem::update"); + selectEntity(); styleOnHover(); diff --git a/src/Systems/RenderingSystem.cpp b/src/Systems/RenderingSystem.cpp index 07def370..30ce1a7a 100644 --- a/src/Systems/RenderingSystem.cpp +++ b/src/Systems/RenderingSystem.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -103,6 +104,8 @@ RenderingSystem::RenderingSystem(std::shared_ptr registry) void RenderingSystem::setSize(uint32_t width, uint32_t height) { + SHKYERA_PROFILE("RenderingSystem::setSize"); + _litModelsFrameBuffer.setSize(width, height); _toneMappedFrameBuffer.setSize(width, height); _silhouetteFrameBuffer.setSize(width, height); @@ -130,6 +133,8 @@ GLuint RenderingSystem::getRenderFrameBuffer() void RenderingSystem::clearFrameBuffers() { + SHKYERA_PROFILE("RenderingSystem::clearFrameBuffers"); + _litModelsFrameBuffer.clear(); _toneMappedFrameBuffer.clear(); _bloomedFrameBuffer.clear(); @@ -155,6 +160,8 @@ void RenderingSystem::clearFrameBuffers() void RenderingSystem::render() { + SHKYERA_PROFILE("RenderingSystem::render"); + _mostRecentFrameBufferPtr = &_litModelsFrameBuffer; // Rendering Preparation @@ -178,11 +185,13 @@ void RenderingSystem::render() void RenderingSystem::renderOutline(const std::unordered_set& entities) { - if(entities.empty()) + if(std::none_of(entities.begin(), entities.end(), [this](auto e) { return _registry->hasComponent(e); })) { return; } + SHKYERA_PROFILE("RenderingSystem::renderOutline"); + for(const auto& entity : entities) { const auto& children = _registry->getHierarchy().getChildren(entity); @@ -255,6 +264,8 @@ void RenderingSystem::renderOutline(const std::unordered_set& entities) void RenderingSystem::renderDirectionalLightShadowMaps() { + SHKYERA_PROFILE("RenderingSystem::renderDirectionalLightShadowMaps"); + glEnable(GL_DEPTH_TEST); const auto& cameraTransform = _registry->getComponent(_registry->getCamera()); @@ -321,6 +332,8 @@ void RenderingSystem::renderDirectionalLightShadowMaps() void RenderingSystem::renderPointLightShadowMaps() { + SHKYERA_PROFILE("RenderingSystem::renderPointLightShadowMaps"); + glEnable(GL_DEPTH_TEST); const auto& cameraTransform = _registry->getComponent(_registry->getCamera()); @@ -400,6 +413,8 @@ void RenderingSystem::renderPointLightShadowMaps() void RenderingSystem::renderSpotLightShadowMaps() { + SHKYERA_PROFILE("RenderingSystem::renderSpotLightShadowMaps"); + glEnable(GL_DEPTH_TEST); const auto& cameraTransform = _registry->getComponent(_registry->getCamera()); @@ -463,6 +478,8 @@ void RenderingSystem::renderSpotLightShadowMaps() void RenderingSystem::renderModels() { + SHKYERA_PROFILE("RenderingSystem::renderModels"); + glEnable(GL_DEPTH_TEST); // ********* Rendering the shadow maps ********* @@ -579,6 +596,8 @@ void RenderingSystem::renderModels() void RenderingSystem::renderBloom() { + SHKYERA_PROFILE("RenderingSystem::renderBloom"); + glDisable(GL_DEPTH_TEST); // Downscaling Pass @@ -669,6 +688,8 @@ void RenderingSystem::renderBloom() void RenderingSystem::toneMapping() { + SHKYERA_PROFILE("RenderingSystem::toneMapping"); + glDisable(GL_DEPTH_TEST); utils::applyShaderToFrameBuffer( @@ -684,6 +705,8 @@ void RenderingSystem::toneMapping() void RenderingSystem::renderWireframes() { + SHKYERA_PROFILE("RenderingSystem::renderWireframes"); + glEnable(GL_DEPTH_TEST); _mostRecentFrameBufferPtr->bind(); @@ -711,6 +734,8 @@ void RenderingSystem::renderWireframes() void RenderingSystem::renderSkybox() { + SHKYERA_PROFILE("RenderingSystem::renderSkybox"); + const auto& cameraTransform = _registry->getComponent(_registry->getCamera()); const glm::mat4& viewMatrix = _registry->getComponent(_registry->getCamera()).getViewMatrix(cameraTransform); const glm::mat4& projectionMatrix = _registry->getComponent(_registry->getCamera()).getProjectionMatrix(); @@ -739,6 +764,8 @@ void RenderingSystem::renderSkybox() void RenderingSystem::renderOverlayModels() { + SHKYERA_PROFILE("RenderingSystem::renderOverlayModels"); + glDisable(GL_DEPTH_TEST); _mostRecentFrameBufferPtr->bind(); @@ -766,6 +793,8 @@ void RenderingSystem::renderOverlayModels() void RenderingSystem::antiAliasing() { + SHKYERA_PROFILE("RenderingSystem::antiAliasing"); + utils::applyShaderToFrameBuffer( _antiAliasedFrameBuffer, _antiAliasingShaderProgram, diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index d2324ada..b7836673 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -18,10 +18,10 @@ add_library( ${CMAKE_CURRENT_LIST_DIR}/Components/AmbientLightComponentUI.cpp ${CMAKE_CURRENT_LIST_DIR}/Components/WireframeComponentUI.cpp - ${CMAKE_CURRENT_LIST_DIR}/Widgets/PreviewWidget.cpp ${CMAKE_CURRENT_LIST_DIR}/Widgets/ObjectsWidget.cpp ${CMAKE_CURRENT_LIST_DIR}/Widgets/ConsoleWidget.cpp ${CMAKE_CURRENT_LIST_DIR}/Widgets/PropertiesWidget.cpp + ${CMAKE_CURRENT_LIST_DIR}/Widgets/ProfilerWidget.cpp ${CMAKE_CURRENT_LIST_DIR}/Widgets/FilesystemWidget.cpp ${CMAKE_CURRENT_LIST_DIR}/Widgets/SceneWidget.cpp ) diff --git a/src/ui/UI.cpp b/src/ui/UI.cpp index 51eceab7..9681e156 100644 --- a/src/ui/UI.cpp +++ b/src/ui/UI.cpp @@ -15,8 +15,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -93,11 +93,11 @@ void UI::initializeSystems() { } void UI::initializeWidgets() { - _widgets.emplace_back(std::make_unique("Console")); - + _widgets.emplace_back(std::make_unique("Console")); _widgets.emplace_back(std::make_unique(_registry)); _widgets.emplace_back(std::make_unique(_registry)); _widgets.emplace_back(std::make_unique(_registry)); + _widgets.emplace_back(std::make_unique("Profiler")); _widgets.emplace_back(std::make_unique(_registry)); @@ -187,6 +187,8 @@ void UI::styleImgui() { } void UI::beginFrame() { + SHKYERA_PROFILE("UI::beginFrame"); + glfwPollEvents(); glClearColor(0.1f, 0.1f, 0.1f, 0.1f); @@ -241,6 +243,7 @@ void UI::beginFrame() { ImGui::DockBuilderDockWindow("Scene", dock_id_left_up_right); ImGui::DockBuilderDockWindow("Properties", dock_id_right); ImGui::DockBuilderDockWindow("Scene Camera", dock_id_right); + ImGui::DockBuilderDockWindow("Profiler", dock_id_right); ImGui::DockBuilderDockWindow("Environment", dock_id_right); ImGui::DockBuilderDockWindow("Assets", dock_id_left_bottom); ImGui::DockBuilderDockWindow("Console", dock_id_left_bottom); @@ -252,6 +255,8 @@ void UI::beginFrame() { } void UI::renderFrame() { + SHKYERA_PROFILE("UI::renderFrame"); + const auto& windowSize = ImGui::GetWindowSize(); InputManager::getInstance().setCoordinateSystem(InputManager::CoordinateSystem::ABSOLUTE, {0, 0}, {windowSize.x, windowSize.y}); InputManager::getInstance().processInput(_window); @@ -284,6 +289,8 @@ void UI::renderFrame() { } void UI::endFrame() { + SHKYERA_PROFILE("UI::endFrame"); + ImGui::Render(); int display_w, display_h; diff --git a/src/ui/widgets/PreviewWidget.cpp b/src/ui/widgets/PreviewWidget.cpp deleted file mode 100644 index f035092b..00000000 --- a/src/ui/widgets/PreviewWidget.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include "imgui.h" - -#include - -namespace shkyera { - -void PreviewWidget::draw() { - ImGui::Begin(_name.c_str()); - ImGui::End(); -} - -} // namespace shkyera \ No newline at end of file diff --git a/src/ui/widgets/PreviewWidget.hpp b/src/ui/widgets/PreviewWidget.hpp deleted file mode 100644 index 7a2897fb..00000000 --- a/src/ui/widgets/PreviewWidget.hpp +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @file PreviewWidget.hpp - * - * @brief Contains the declaration of the `PreviewWidget` class, a user interface widget for previewing content. - */ - -#pragma once - -#include - -namespace shkyera { - -/** - * @brief A user interface widget for previewing content. - * - * The `PreviewWidget` class provides a graphical user interface widget for previewing various types of content, - * which may include visual or interactive previews. - */ -class PreviewWidget : public Widget { - public: - using Widget::Widget; - - /** - * @brief Implementation of the abstract `draw` method to render the preview widget. - */ - virtual void draw() override; -}; - -} // namespace shkyera diff --git a/src/ui/widgets/ProfilerWidget.cpp b/src/ui/widgets/ProfilerWidget.cpp new file mode 100644 index 00000000..26767a92 --- /dev/null +++ b/src/ui/widgets/ProfilerWidget.cpp @@ -0,0 +1,99 @@ +#include +#include "imgui.h" + +#include +#include +#include + +namespace shkyera { + +ProfilerWidget::ProfilerWidget(std::string name) : Widget(std::move(name)) +{ + reset(); +} + +void ProfilerWidget::draw() +{ + ImGui::Begin(_name.c_str()); + + ImGui::PushFont(style::HUGE_FONT); + ImGui::TextUnformatted("Timing profiles"); + ImGui::PopFont(); + + size_t threadNumber = 0; + const auto profileBlocks = SHKYERA_READ_PROFILE; + const auto currentTime = std::chrono::high_resolution_clock::now(); + const auto timeSinceLastReset = currentTime - mTimeOfLastReset; + const double timeSinceLastResetNanoseconds = static_cast(std::chrono::duration_cast(timeSinceLastReset).count()); + + mFramesSinceLastReset++; + bool shouldReset = false; + ImGui::Checkbox("Reset after each frame", &mResetOnEachFrame); + if(mResetOnEachFrame) + { + shouldReset = true; + } + else + { + ImGui::SameLine(); + if(ImGui::Button("Reset")) + { + shouldReset = true; + } + } + + for(const auto& [threadId, blocks] : profileBlocks) + { + if (ImGui::BeginTable("Timing Profiles", 4, ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) + { + + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_None, 0.0f, 0); + ImGui::TableSetupColumn("Mean [ms]", ImGuiTableColumnFlags_None, 0.0f, 1); + ImGui::TableSetupColumn("Calls", ImGuiTableColumnFlags_None, 0.0f, 2); + ImGui::TableSetupColumn("\% of Frame", ImGuiTableColumnFlags_None, 0.0f, 3); + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + + ImGui::PushFont(style::BIG_FONT); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Full Frame"); + ImGui::TableNextColumn(); + ImGui::Text("%.3f", timeSinceLastResetNanoseconds / 1e6 / mFramesSinceLastReset); + ImGui::TableNextColumn(); + ImGui::Text("%zu", mFramesSinceLastReset); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("100"); + ImGui::PopFont(); + + for(const auto& [name, block] : blocks) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(name.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%.3f", block.totalLengthInNanoSeconds / 1e6 / block.numberOfCalls); + ImGui::TableNextColumn(); + ImGui::Text("%zu", block.numberOfCalls); + ImGui::TableNextColumn(); + ImGui::Text("%.3f", 100 * block.totalLengthInNanoSeconds / timeSinceLastResetNanoseconds); + } + ImGui::EndTable(); + } + } + + ImGui::End(); + + if(shouldReset) + { + reset(); + } +} + +void ProfilerWidget::reset() +{ + SHKYERA_CLEAR_PROFILE; + mTimeOfLastReset = std::chrono::high_resolution_clock::now(); + mFramesSinceLastReset = 0; +} + +} // namespace shkyera diff --git a/src/ui/widgets/ProfilerWidget.hpp b/src/ui/widgets/ProfilerWidget.hpp new file mode 100644 index 00000000..3f8118a2 --- /dev/null +++ b/src/ui/widgets/ProfilerWidget.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +namespace shkyera { + +class ProfilerWidget : public Widget { + public: + using Widget::Widget; + + ProfilerWidget(std::string name); + + /** + * @brief Implementation of the abstract `draw` method to render the profiler widget. + */ + virtual void draw() override; + + private: + void reset(); + + bool mResetOnEachFrame = false; + size_t mFramesSinceLastReset = 0; + std::chrono::high_resolution_clock::time_point mTimeOfLastReset = std::chrono::high_resolution_clock::now(); +}; + +} // namespace shkyera diff --git a/src/ui/widgets/SceneWidget.cpp b/src/ui/widgets/SceneWidget.cpp index 4eebe4e5..d9bff31b 100644 --- a/src/ui/widgets/SceneWidget.cpp +++ b/src/ui/widgets/SceneWidget.cpp @@ -24,7 +24,7 @@ void SceneWidget::draw() { auto renderSize = ImGui::GetContentRegionAvail(); updateWindowCoordinateSystem(); - _runtime.getRenderingSystem().setSize(renderSize.x * 1.5f, renderSize.y * 1.5f); + _runtime.getRenderingSystem().setSize(renderSize.x * 2.0f, renderSize.y * 2.0f); const auto aspectRatio = static_cast(renderSize.x) / renderSize.y; _registry->getComponent(_registry->getCamera()).aspectRatio = aspectRatio;