Skip to content

Commit

Permalink
Move render stats to a new class RenderStatistics.
Browse files Browse the repository at this point in the history
  • Loading branch information
Flone-dnb committed Dec 28, 2023
1 parent 5152232 commit 5bbfb68
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 204 deletions.
10 changes: 4 additions & 6 deletions docs/Manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -2515,21 +2515,19 @@ addDeferredTask([this, iNodeId](){ // capturing `this` to use `Node` (self) func
});
```
# Frame statistics
# Render statistics
If you want to know your game's FPS you can use `Renderer::getFramesPerSecond`. For example, you can display the FPS on your game's UI for debugging purposes:
If you want to know your game's FPS or other similar statistics you can use `Renderer::getRenderStatistics`. For example, you can display the FPS on your game's UI for debugging purposes:
```Cpp
void MyUiNode::onBeforeNewFrame(float timeSincePrevCallInSec) {
void MyUiNode::onLoopingTimerTimeout() {
#if defined(DEBUG)
const auto iFps = getWindow()->getRenderer()->getFramesPerSecond();
const auto iFps = getWindow()->getRenderer()->getRenderStatistics()->getFramesPerSecond();
// ... display on UI ...
#endif
}
```

You can also use `Renderer::getTimeSpentLastFrameWaitingForGpu` to determine if you are CPU or GPU bound.

# Using profiler

The engine has https://github.com/Celtoys/Remotery integrated and you can use this profiler in order to detect slow parts of your game.
Expand Down
15 changes: 8 additions & 7 deletions src/editor_lib/private/EditorGameInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,22 @@ namespace ne {
// Get window and renderer.
const auto pWindow = getWindow();
const auto pRenderer = pWindow->getRenderer();
const auto pRenderStats = pRenderer->getRenderStatistics();

// Show window title.
pWindow->setTitle(std::format(
"{} FPS: {} | draw calls: {} | VRAM used: {} MB | frustum culled: {} took {:.1F} ms (~{}% of "
"frame time) | waiting GPU: {:.1F} ms",
pEditorWindowTitle,
pRenderer->getFramesPerSecond(),
pRenderer->getLastFrameDrawCallCount(),
pRenderStats->getFramesPerSecond(),
pRenderStats->getLastFrameDrawCallCount(),
pRenderer->getResourceManager()->getUsedVideoMemoryInMb(),
pRenderer->getLastFrameCulledObjectCount(),
pRenderer->getTimeSpentLastFrameOnFrustumCulling(),
pRenderStats->getLastFrameCulledObjectCount(),
pRenderStats->getTimeSpentLastFrameOnFrustumCulling(),
static_cast<size_t>(
pRenderer->getTimeSpentLastFrameOnFrustumCulling() /
(1000.0F / static_cast<float>(pRenderer->getFramesPerSecond())) * 100.0F), // NOLINT
pRenderer->getTimeSpentLastFrameWaitingForGpu()));
pRenderStats->getTimeSpentLastFrameOnFrustumCulling() /
(1000.0F / static_cast<float>(pRenderStats->getFramesPerSecond())) * 100.0F), // NOLINT
pRenderStats->getTimeSpentLastFrameWaitingForGpu()));
}

void EditorGameInstance::bindCameraInput() {
Expand Down
2 changes: 2 additions & 0 deletions src/engine_lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ set(PROJECT_SOURCES
private/render/general/Renderer.cpp
public/render/RenderSettings.h
private/render/general/RenderSettings.cpp
public/render/RenderStatistics.h
private/render/general/RenderStatistics.cpp
private/render/general/pipeline/PipelineManager.h
private/render/general/pipeline/PipelineManager.cpp
private/render/general/pipeline/Pipeline.h
Expand Down
4 changes: 2 additions & 2 deletions src/engine_lib/private/render/directx/DirectXRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ namespace ne {
pCommandList->DrawIndexedInstanced(indexBufferInfo.iIndexCount, 1, 0, 0, 0);

// Increment draw call counter.
*pDrawCallCounter += 1;
pDrawCallCounter->fetch_add(1);
}
}
}
Expand Down Expand Up @@ -962,7 +962,7 @@ namespace ne {
pCommandList->DrawIndexedInstanced(indexBufferInfo.iIndexCount, 1, 0, 0, 0);

// Increment draw call counter.
*pDrawCallCounter += 1;
pDrawCallCounter->fetch_add(1);
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/engine_lib/private/render/general/RenderStatistics.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "render/RenderStatistics.h"

namespace ne {

RenderStatistics::FrameTemporaryStatistics::FrameTemporaryStatistics() {
// Initialize some variables.
mtxFrustumCullingTimeInMs.second = 0.0F;
}

void RenderStatistics::FrameTemporaryStatistics::reset() {
{
std::scoped_lock guard(mtxFrustumCullingTimeInMs.first);
mtxFrustumCullingTimeInMs.second = 0.0F;
}

iCulledObjectCount.store(0);
iDrawCallCount.store(0);

#if defined(DEBUG)
static_assert(sizeof(FrameTemporaryStatistics) == 104, "reset new statistics here"); // NOLINT
#endif
}

size_t RenderStatistics::getFramesPerSecond() const { return fpsInfo.iFramesPerSecond; }

size_t RenderStatistics::getLastFrameDrawCallCount() const { return counters.iLastFrameDrawCallCount; }

size_t RenderStatistics::getLastFrameCulledObjectCount() const {
return counters.iLastFrameCulledObjectCount;
}

float RenderStatistics::getTimeSpentLastFrameWaitingForGpu() const {
return taskTimeInfo.timeSpentLastFrameWaitingForGpuInMs;
}

float RenderStatistics::getTimeSpentLastFrameOnFrustumCulling() const {
return taskTimeInfo.timeSpentLastFrameOnFrustumCullingInMs;
}

}
162 changes: 89 additions & 73 deletions src/engine_lib/private/render/general/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "game/camera/CameraProperties.h"
#include "shader/general/EngineShaders.hpp"
#include "game/nodes/light/PointLightNode.h"
#include "misc/shapes/AABB.h"
#include "game/nodes/light/SpotlightNode.h"
#if defined(WIN32)
#pragma comment(lib, "Winmm.lib")
Expand Down Expand Up @@ -166,65 +167,86 @@ namespace ne {

using namespace std::chrono;

// Save time spent on frustum culling.
frameStats.timeSpentLastFrameOnFrustumCullingInMs = accumulatedTimeSpentLastFrameOnFrustumCullingInMs;
accumulatedTimeSpentLastFrameOnFrustumCullingInMs = 0.0F; // reset accumulated time
// Update frame stats.
{
// Create a short reference.
auto& frameStats = renderStats.frameTemporaryStatistics;

// Save time spent on frustum culling.
{
std::scoped_lock floatGuard(frameStats.mtxFrustumCullingTimeInMs.first);

renderStats.taskTimeInfo.timeSpentLastFrameOnFrustumCullingInMs =
frameStats.mtxFrustumCullingTimeInMs.second;
}

// Save culled object count.
frameStats.iLastFrameCulledObjectCount = iLastFrameCulledObjectCount;
iLastFrameCulledObjectCount = 0;
// Save culled object count.
renderStats.counters.iLastFrameCulledObjectCount = frameStats.iCulledObjectCount.load();

// Save draw call count.
frameStats.iLastFrameDrawCallCount = iLastFrameDrawCallCount;
iLastFrameDrawCallCount = 0;
// Save draw call count.
renderStats.counters.iLastFrameDrawCallCount = frameStats.iDrawCallCount.load();

// Get elapsed time.
const auto iTimeSinceFpsUpdateInSec =
duration_cast<seconds>(steady_clock::now() - frameStats.timeAtLastFpsUpdate).count();
// Reset frame stats.
frameStats.reset();
}

// Count the new present call.
frameStats.iPresentCountSinceFpsUpdate += 1;
// Update FPS stats.
{
// Get elapsed time.
const auto iTimeSinceFpsUpdateInSec =
duration_cast<seconds>(steady_clock::now() - renderStats.fpsInfo.timeAtLastFpsUpdate).count();

// See if 1 second has passed.
if (iTimeSinceFpsUpdateInSec >= 1) {
// Save FPS.
frameStats.iFramesPerSecond = frameStats.iPresentCountSinceFpsUpdate;
// Count the new present call.
renderStats.fpsInfo.iPresentCountSinceFpsUpdate += 1;

// Reset present count.
frameStats.iPresentCountSinceFpsUpdate = 0;
// See if 1 second has passed.
if (iTimeSinceFpsUpdateInSec >= 1) {
// Save FPS.
renderStats.fpsInfo.iFramesPerSecond = renderStats.fpsInfo.iPresentCountSinceFpsUpdate;

// Restart time.
frameStats.timeAtLastFpsUpdate = steady_clock::now();
}
// Reset present count.
renderStats.fpsInfo.iPresentCountSinceFpsUpdate = 0;

// Check if FPS limit is set.
if (frameStats.timeToRenderFrameInNs.has_value()) {
// Get frame time.
const auto frameTimeInNs =
duration<double, nanoseconds::period>(steady_clock::now() - frameStats.frameStartTime)
.count();
// Restart time.
renderStats.fpsInfo.timeAtLastFpsUpdate = steady_clock::now();
}
}

// Check if the last frame was rendered too fast.
if (*frameStats.timeToRenderFrameInNs > frameTimeInNs) {
// Calculate time to wait.
const auto timeToWaitInNs = *frameStats.timeToRenderFrameInNs - frameTimeInNs;
// Update FPS limit stats.
{
// Check if FPS limit is set.
if (renderStats.fpsLimitInfo.optionalTargetTimeToRenderFrameInNs.has_value()) {
// Get time that we should keep.
const auto targetTimeToRenderFrameInNs =
renderStats.fpsLimitInfo.optionalTargetTimeToRenderFrameInNs.value();

// Get time spent on last frame.
const auto frameTimeInNs = duration<double, nanoseconds::period>(
steady_clock::now() - renderStats.fpsLimitInfo.frameStartTime)
.count();

// Check if the last frame was rendered too fast.
if (targetTimeToRenderFrameInNs > frameTimeInNs) {
// Calculate time to wait.
const auto timeToWaitInNs = targetTimeToRenderFrameInNs - frameTimeInNs;

#if defined(WIN32)
timeBeginPeriod(1);
nanosleep(static_cast<long long>(std::floor(timeToWaitInNs * 0.98))); // NOLINT
timeEndPeriod(1);
timeBeginPeriod(1);
nanosleep(static_cast<long long>(std::floor(timeToWaitInNs * 0.98))); // NOLINT
timeEndPeriod(1);
#else
struct timespec tim;
struct timespec tim2;
tim.tv_sec = 0;
tim.tv_nsec = static_cast<long>(timeToWaitInNs);
nanosleep(&tim, &tim2);
struct timespec tim;
struct timespec tim2;
tim.tv_sec = 0;
tim.tv_nsec = static_cast<long>(timeToWaitInNs);
nanosleep(&tim, &tim2);
#endif
}
}
}

// Update frame start/end time.
frameStats.frameStartTime = steady_clock::now();
// Update frame start/end time.
renderStats.fpsLimitInfo.frameStartTime = steady_clock::now();
}
}

void Renderer::resetGpuResourceManager() {
Expand Down Expand Up @@ -379,8 +401,8 @@ namespace ne {
std::scoped_lock guard(*getRenderResourcesMutex());
waitForGpuToFinishWorkUpToThisPoint();

// Update FPS cap.
updateFpsLimitSetting();
// Update target FPS.
updateTargetTimeToRenderFrame();

if (bShadowMapSizeChanged) {
// Notify shadow map manager.
Expand Down Expand Up @@ -570,17 +592,18 @@ namespace ne {
return VulkanRenderer::create(pGameManager, vBlacklistedGpuNames);
}

void Renderer::updateFpsLimitSetting() {
void Renderer::updateTargetTimeToRenderFrame() {
// Get render setting.
const auto pMtxRenderSettings = getRenderSettings();
std::scoped_lock guard(pMtxRenderSettings->first);

// Update time to render a frame.
const auto iFpsLimit = pMtxRenderSettings->second->getFpsLimit();
if (iFpsLimit == 0) {
frameStats.timeToRenderFrameInNs = {};
renderStats.fpsLimitInfo.optionalTargetTimeToRenderFrameInNs = {};
} else {
frameStats.timeToRenderFrameInNs = 1000000000.0 / static_cast<double>(iFpsLimit); // NOLINT
renderStats.fpsLimitInfo.optionalTargetTimeToRenderFrameInNs =
1000000000.0 / static_cast<double>(iFpsLimit); // NOLINT
}
}

Expand Down Expand Up @@ -651,29 +674,17 @@ namespace ne {
}

// Setup frame statistics.
pCreatedRenderer->setupFrameStats();
pCreatedRenderer->setupRenderStats();

return pCreatedRenderer;
}

size_t Renderer::getFramesPerSecond() const { return frameStats.iFramesPerSecond; }

size_t Renderer::getLastFrameDrawCallCount() const { return frameStats.iLastFrameDrawCallCount; }

float Renderer::getTimeSpentLastFrameWaitingForGpu() const {
return frameStats.timeSpentLastFrameWaitingForGpuInMs;
}

float Renderer::getTimeSpentLastFrameOnFrustumCulling() const {
return frameStats.timeSpentLastFrameOnFrustumCullingInMs;
}

size_t Renderer::getLastFrameCulledObjectCount() const { return frameStats.iLastFrameCulledObjectCount; }

std::pair<std::recursive_mutex, std::shared_ptr<RenderSettings>>* Renderer::getRenderSettings() {
return &mtxRenderSettings;
}

RenderStatistics* Renderer::getRenderStatistics() { return &renderStats; }

size_t Renderer::getTotalVideoMemoryInMb() const {
return getResourceManager()->getTotalVideoMemoryInMb();
}
Expand Down Expand Up @@ -738,9 +749,9 @@ namespace ne {
}
}

void Renderer::setupFrameStats() {
frameStats.timeAtLastFpsUpdate = std::chrono::steady_clock::now();
frameStats.frameStartTime = std::chrono::steady_clock::now();
void Renderer::setupRenderStats() {
renderStats.fpsInfo.timeAtLastFpsUpdate = std::chrono::steady_clock::now();
renderStats.fpsLimitInfo.frameStartTime = std::chrono::steady_clock::now();
}

#if defined(WIN32)
Expand Down Expand Up @@ -815,7 +826,7 @@ namespace ne {
mtxRenderSettings.second->notifyRendererAboutChangedSettings();

// Apply initial FPS limit setting.
updateFpsLimitSetting();
updateTargetTimeToRenderFrame();

return {};
}
Expand Down Expand Up @@ -900,7 +911,7 @@ namespace ne {

// Measure the time it took to wait.
const auto endTime = std::chrono::steady_clock::now();
frameStats.timeSpentLastFrameWaitingForGpuInMs =
renderStats.taskTimeInfo.timeSpentLastFrameWaitingForGpuInMs =
std::chrono::duration<float, std::chrono::milliseconds::period>(endTime - startTime).count();
}

Expand Down Expand Up @@ -971,7 +982,7 @@ namespace ne {
// Get camera frustum (camera should be updated at this point).
const auto pCameraFrustum = pActiveCameraProperties->getCameraFrustum();

// Prepare lambda to cull pipelines.
// Prepare lambda to cull meshes by pipelines.
const auto frustumCullPipelines =
[this, pCameraFrustum](
const std::unordered_map<std::string, PipelineManager::ShaderPipelines>& pipelinesToScan,
Expand Down Expand Up @@ -1011,7 +1022,8 @@ namespace ne {
*pMeshNode->getAABB(), pMtxMeshShaderConstants->second.world);

// Increment culled object count.
iLastFrameCulledObjectCount += static_cast<size_t>(!bIsVisible);
renderStats.frameTemporaryStatistics.iCulledObjectCount.fetch_add(
static_cast<size_t>(!bIsVisible));

if (!bIsVisible) {
// This mesh is outside of camera's frustum.
Expand Down Expand Up @@ -1058,8 +1070,12 @@ namespace ne {
frustumCullPipelines(transparentPipelines, meshesInFrustumLastFrame.vTransparentPipelines);

// Increment total time spent in frustum culling.
accumulatedTimeSpentLastFrameOnFrustumCullingInMs +=
duration<float, milliseconds::period>(steady_clock::now() - startFrustumCullingTime).count();
{
std::scoped_lock floatGuard(renderStats.frameTemporaryStatistics.mtxFrustumCullingTimeInMs.first);

renderStats.frameTemporaryStatistics.mtxFrustumCullingTimeInMs.second +=
duration<float, milliseconds::period>(steady_clock::now() - startFrustumCullingTime).count();
}

return &meshesInFrustumLastFrame;
}
Expand Down
Loading

0 comments on commit 5bbfb68

Please sign in to comment.