diff --git a/README.md b/README.md index e498670e..6154934f 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Manipulation of Direct3D libraries in multi-player games may be considered cheat The `DXVK_HUD` environment variable controls a HUD which can display the framerate and some stat counters. It accepts a comma-separated list of the following options: - `devinfo`: Displays the name of the GPU and the driver version. - `fps`: Shows the current frame rate. +- `frametimes`: Shows a frame time graph. - `submissions`: Shows the number of command buffers submitted per frame. - `drawcalls`: Shows the number of draw calls and render passes per frame. - `pipelines`: Shows the total number of graphics and compute pipelines. diff --git a/src/dxvk/hud/dxvk_hud.cpp b/src/dxvk/hud/dxvk_hud.cpp index dab8cf69..ee24df3a 100644 --- a/src/dxvk/hud/dxvk_hud.cpp +++ b/src/dxvk/hud/dxvk_hud.cpp @@ -13,6 +13,7 @@ namespace dxvk::hud { m_renderer (m_device, m_context), m_uniformBuffer (createUniformBuffer()), m_hudDeviceInfo (device), + m_hudFramerate (config.elements), m_hudStats (config.elements) { this->setupConstantState(); } @@ -31,7 +32,7 @@ namespace dxvk::hud { this->setupFramebuffer(size); } - m_hudFps.update(); + m_hudFramerate.update(); m_hudStats.update(m_device); this->beginRenderPass(recreateFbo); @@ -74,13 +75,8 @@ namespace dxvk::hud { m_context, m_renderer, position); } - if (m_config.elements.test(HudElement::Framerate)) { - position = m_hudFps.render( - m_context, m_renderer, position); - } - - position = m_hudStats.render( - m_context, m_renderer, position); + position = m_hudFramerate.render(m_context, m_renderer, position); + position = m_hudStats .render(m_context, m_renderer, position); } diff --git a/src/dxvk/hud/dxvk_hud.h b/src/dxvk/hud/dxvk_hud.h index 025cd95b..c3887f4e 100644 --- a/src/dxvk/hud/dxvk_hud.h +++ b/src/dxvk/hud/dxvk_hud.h @@ -83,7 +83,7 @@ namespace dxvk::hud { Rc m_renderTargetFbo; HudDeviceInfo m_hudDeviceInfo; - HudFps m_hudFps; + HudFps m_hudFramerate; HudStats m_hudStats; void render(); diff --git a/src/dxvk/hud/dxvk_hud_config.cpp b/src/dxvk/hud/dxvk_hud_config.cpp index 051359de..1c0108df 100644 --- a/src/dxvk/hud/dxvk_hud_config.cpp +++ b/src/dxvk/hud/dxvk_hud_config.cpp @@ -7,6 +7,7 @@ namespace dxvk::hud { const std::unordered_map g_hudElements = {{ { "devinfo", HudElement::DeviceInfo }, { "fps", HudElement::Framerate }, + { "frametimes", HudElement::Frametimes }, { "drawcalls", HudElement::StatDrawCalls }, { "submissions", HudElement::StatSubmissions }, { "pipelines", HudElement::StatPipelines }, diff --git a/src/dxvk/hud/dxvk_hud_config.h b/src/dxvk/hud/dxvk_hud_config.h index 64de9f26..77516dc8 100644 --- a/src/dxvk/hud/dxvk_hud_config.h +++ b/src/dxvk/hud/dxvk_hud_config.h @@ -13,10 +13,11 @@ namespace dxvk::hud { enum class HudElement { DeviceInfo = 0, Framerate = 1, - StatDrawCalls = 2, - StatSubmissions = 3, - StatPipelines = 4, - StatMemory = 5, + Frametimes = 2, + StatDrawCalls = 3, + StatSubmissions = 4, + StatPipelines = 5, + StatMemory = 6, }; using HudElements = Flags; diff --git a/src/dxvk/hud/dxvk_hud_fps.cpp b/src/dxvk/hud/dxvk_hud_fps.cpp index a20b0325..d497cf35 100644 --- a/src/dxvk/hud/dxvk_hud_fps.cpp +++ b/src/dxvk/hud/dxvk_hud_fps.cpp @@ -1,12 +1,15 @@ #include "dxvk_hud_fps.h" +#include #include namespace dxvk::hud { - HudFps::HudFps() - : m_fpsString("FPS: "), - m_prevUpdate(Clock::now()) { + HudFps::HudFps(HudElements elements) + : m_elements (elements), + m_fpsString ("FPS: "), + m_prevFpsUpdate(Clock::now()), + m_prevFtgUpdate(Clock::now()) { } @@ -19,20 +22,45 @@ namespace dxvk::hud { void HudFps::update() { m_frameCount += 1; - const TimePoint now = Clock::now(); - const TimeDiff elapsed = std::chrono::duration_cast(now - m_prevUpdate); + TimePoint now = Clock::now(); + TimeDiff elapsedFps = std::chrono::duration_cast(now - m_prevFpsUpdate); + TimeDiff elapsedFtg = std::chrono::duration_cast(now - m_prevFtgUpdate); + m_prevFtgUpdate = now; - if (elapsed.count() >= UpdateInterval) { - const int64_t fps = (10'000'000ll * m_frameCount) / elapsed.count(); + // Update FPS string + if (elapsedFps.count() >= UpdateInterval) { + const int64_t fps = (10'000'000ll * m_frameCount) / elapsedFps.count(); m_fpsString = str::format("FPS: ", fps / 10, ".", fps % 10); - m_prevUpdate = now; + m_prevFpsUpdate = now; m_frameCount = 0; } + + // Update frametime stuff + m_dataPoints[m_dataPointId] = float(elapsedFtg.count()); + m_dataPointId = (m_dataPointId + 1) % NumDataPoints; } HudPos HudFps::render( + const Rc& context, + HudRenderer& renderer, + HudPos position) { + if (m_elements.test(HudElement::Framerate)) { + position = this->renderFpsText( + context, renderer, position); + } + + if (m_elements.test(HudElement::Frametimes)) { + position = this->renderFrametimeGraph( + context, renderer, position); + } + + return position; + } + + + HudPos HudFps::renderFpsText( const Rc& context, HudRenderer& renderer, HudPos position) { @@ -44,4 +72,62 @@ namespace dxvk::hud { return HudPos { position.x, position.y + 24 }; } + + HudPos HudFps::renderFrametimeGraph( + const Rc& context, + HudRenderer& renderer, + HudPos position) { + std::array vData; + + // 60 FPS = optimal, 10 FPS = worst + const float targetUs = 16'666.6f; + const float minUs = 5'000.0f; + const float maxUs = 100'000.0f; + + // Ten times the maximum/minimum number + // of milliseconds for a single frame + uint32_t minMs = 0xFFFFFFFFu; + uint32_t maxMs = 0x00000000u; + + // Paint the time points + for (uint32_t i = 0; i < NumDataPoints; i++) { + float us = m_dataPoints[(m_dataPointId + i) % NumDataPoints]; + + minMs = std::min(minMs, uint32_t(us / 100.0f)); + maxMs = std::max(maxMs, uint32_t(us / 100.0f)); + + float r = std::clamp(-1.0f + us / targetUs, 0.0f, 1.0f); + float g = std::clamp( 3.0f - us / targetUs, 0.0f, 1.0f); + float l = std::sqrt(r * r + g * g); + + HudTexCoord tc = { 0u, 0u }; + HudColor color = { r / l, g / l, 0.0f, 1.0f }; + + float x = position.x + float(i); + float y = position.y + 24.0f; + + float hVal = std::log2(std::max((us - minUs) / targetUs + 1.0f, 1.0f)) + / std::log2((maxUs - minUs) / targetUs); + float h = std::clamp(40.0f * hVal, 2.0f, 40.0f); + + vData[2 * i + 0] = HudVertex { { x, y }, tc, color }; + vData[2 * i + 1] = HudVertex { { x, y - h }, tc, color }; + } + + renderer.drawLines(context, vData.size(), vData.data()); + + // Paint min/max frame times in the entire window + renderer.drawText(context, 14.0f, + { position.x, position.y + 44.0f }, + { 1.0f, 1.0f, 1.0f, 1.0f }, + str::format("min: ", minMs / 10, ".", minMs % 10)); + + renderer.drawText(context, 14.0f, + { position.x + 150.0f, position.y + 44.0f }, + { 1.0f, 1.0f, 1.0f, 1.0f }, + str::format("max: ", maxMs / 10, ".", maxMs % 10)); + + return HudPos { position.x, position.y + 66.0f }; + } + } \ No newline at end of file diff --git a/src/dxvk/hud/dxvk_hud_fps.h b/src/dxvk/hud/dxvk_hud_fps.h index 9ba37625..c8c4b984 100644 --- a/src/dxvk/hud/dxvk_hud_fps.h +++ b/src/dxvk/hud/dxvk_hud_fps.h @@ -2,6 +2,7 @@ #include +#include "dxvk_hud_config.h" #include "dxvk_hud_renderer.h" namespace dxvk::hud { @@ -16,10 +17,11 @@ namespace dxvk::hud { using TimeDiff = std::chrono::microseconds; using TimePoint = typename Clock::time_point; - constexpr static int64_t UpdateInterval = 500'000; + constexpr static uint32_t NumDataPoints = 300; + constexpr static int64_t UpdateInterval = 500'000; public: - HudFps(); + HudFps(HudElements elements); ~HudFps(); void update(); @@ -31,10 +33,26 @@ namespace dxvk::hud { private: + const HudElements m_elements; + std::string m_fpsString; - TimePoint m_prevUpdate; - int64_t m_frameCount = 0; + TimePoint m_prevFpsUpdate; + TimePoint m_prevFtgUpdate; + int64_t m_frameCount = 0; + + std::array m_dataPoints = {}; + uint32_t m_dataPointId = 0; + + HudPos renderFpsText( + const Rc& context, + HudRenderer& renderer, + HudPos position); + + HudPos renderFrametimeGraph( + const Rc& context, + HudRenderer& renderer, + HudPos position); }; diff --git a/src/dxvk/hud/dxvk_hud_renderer.h b/src/dxvk/hud/dxvk_hud_renderer.h index 3f9ebe57..0ab04c7e 100644 --- a/src/dxvk/hud/dxvk_hud_renderer.h +++ b/src/dxvk/hud/dxvk_hud_renderer.h @@ -35,10 +35,10 @@ namespace dxvk::hud { * will use this color for the most part. */ struct HudColor { - float x; - float y; - float z; - float w; + float r; + float g; + float b; + float a; }; /**