#include "dxvk_hud_item.h" #include #include namespace dxvk::hud { HudItem::~HudItem() { } void HudItem::update(dxvk::high_resolution_clock::time_point time) { // Do nothing by default. Some items won't need this. } HudItemSet::HudItemSet() { std::string configStr = env::getEnvVar("DXVK_HUD"); if (configStr == "full") { // Just enable everything m_enableFull = true; } else if (configStr == "1") { m_enabled.insert("devinfo"); m_enabled.insert("fps"); } else { std::string::size_type pos = 0; std::string::size_type end = 0; while (pos < configStr.size()) { end = configStr.find(',', pos); if (end == std::string::npos) end = configStr.size(); m_enabled.insert(configStr.substr(pos, end - pos)); pos = end + 1; } } } HudItemSet::~HudItemSet() { } void HudItemSet::update() { auto time = dxvk::high_resolution_clock::now(); for (const auto& item : m_items) item->update(time); } void HudItemSet::render(HudRenderer& renderer) { HudPos position = { 8.0f, 8.0f }; for (const auto& item : m_items) position = item->render(renderer, position); } HudPos HudVersionItem::render( HudRenderer& renderer, HudPos position) { position.y += 16.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, "DXVK " DXVK_VERSION); position.y += 8.0f; return position; } HudClientApiItem::HudClientApiItem(const Rc& device) : m_device(device) { } HudClientApiItem::~HudClientApiItem() { } HudPos HudClientApiItem::render( HudRenderer& renderer, HudPos position) { position.y += 16.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, m_device->clientApi()); position.y += 8.0f; return position; } HudDeviceInfoItem::HudDeviceInfoItem(const Rc& device) { VkPhysicalDeviceProperties props = device->adapter()->deviceProperties(); m_deviceName = props.deviceName; m_driverVer = str::format("Driver: ", VK_VERSION_MAJOR(props.driverVersion), ".", VK_VERSION_MINOR(props.driverVersion), ".", VK_VERSION_PATCH(props.driverVersion)); m_vulkanVer = str::format("Vulkan: ", VK_VERSION_MAJOR(props.apiVersion), ".", VK_VERSION_MINOR(props.apiVersion), ".", VK_VERSION_PATCH(props.apiVersion)); } HudDeviceInfoItem::~HudDeviceInfoItem() { } HudPos HudDeviceInfoItem::render( HudRenderer& renderer, HudPos position) { position.y += 16.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, m_deviceName); position.y += 24.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, m_driverVer); position.y += 20.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, m_vulkanVer); position.y += 8.0f; return position; } HudFpsItem::HudFpsItem() { } HudFpsItem::~HudFpsItem() { } void HudFpsItem::update(dxvk::high_resolution_clock::time_point time) { m_frameCount += 1; auto elapsed = std::chrono::duration_cast(time - m_lastUpdate); if (elapsed.count() >= UpdateInterval) { int64_t fps = (10'000'000ll * m_frameCount) / elapsed.count(); m_frameRate = str::format("FPS: ", fps / 10, ".", fps % 10); m_frameCount = 0; m_lastUpdate = time; } } HudPos HudFpsItem::render( HudRenderer& renderer, HudPos position) { position.y += 16.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, m_frameRate); position.y += 8.0f; return position; } HudFrameTimeItem::HudFrameTimeItem() { } HudFrameTimeItem::~HudFrameTimeItem() { } void HudFrameTimeItem::update(dxvk::high_resolution_clock::time_point time) { auto elapsed = std::chrono::duration_cast(time - m_lastUpdate); m_dataPoints[m_dataPointId] = float(elapsed.count()); m_dataPointId = (m_dataPointId + 1) % NumDataPoints; m_lastUpdate = time; } HudPos HudFrameTimeItem::render( HudRenderer& renderer, HudPos position) { std::array vData; position.y += 40.0f; // 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::min(std::max(-1.0f + us / targetUs, 0.0f), 1.0f); float g = std::min(std::max( 3.0f - us / targetUs, 0.0f), 1.0f); float l = std::sqrt(r * r + g * g); HudNormColor color = { uint8_t(255.0f * (r / l)), uint8_t(255.0f * (g / l)), uint8_t(0), uint8_t(255) }; float x = position.x + float(i); float y = position.y; float hVal = std::log2(std::max((us - minUs) / targetUs + 1.0f, 1.0f)) / std::log2((maxUs - minUs) / targetUs); float h = std::min(std::max(40.0f * hVal, 2.0f), 40.0f); vData[2 * i + 0] = HudLineVertex { { x, y }, color }; vData[2 * i + 1] = HudLineVertex { { x, y - h }, color }; } renderer.drawLines(vData.size(), vData.data()); // Paint min/max frame times in the entire window position.y += 20.0f; renderer.drawText(14.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, str::format("min: ", minMs / 10, ".", minMs % 10)); renderer.drawText(14.0f, { position.x + 150.0f, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, str::format("max: ", maxMs / 10, ".", maxMs % 10)); position.y += 2.0f; return position; } HudSubmissionStatsItem::HudSubmissionStatsItem(const Rc& device) : m_device(device) { } HudSubmissionStatsItem::~HudSubmissionStatsItem() { } void HudSubmissionStatsItem::update(dxvk::high_resolution_clock::time_point time) { DxvkStatCounters counters = m_device->getStatCounters(); uint32_t currCounter = counters.getCtr(DxvkStatCounter::QueueSubmitCount); m_diffCounter = currCounter - m_prevCounter; m_prevCounter = currCounter; } HudPos HudSubmissionStatsItem::render( HudRenderer& renderer, HudPos position) { position.y += 16.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, str::format("Queue submissions: ", m_diffCounter)); position.y += 8.0f; return position; } HudDrawCallStatsItem::HudDrawCallStatsItem(const Rc& device) : m_device(device) { } HudDrawCallStatsItem::~HudDrawCallStatsItem() { } void HudDrawCallStatsItem::update(dxvk::high_resolution_clock::time_point time) { DxvkStatCounters counters = m_device->getStatCounters(); m_diffCounters = counters.diff(m_prevCounters); m_prevCounters = counters; } HudPos HudDrawCallStatsItem::render( HudRenderer& renderer, HudPos position) { uint64_t gpCalls = m_diffCounters.getCtr(DxvkStatCounter::CmdDrawCalls); uint64_t cpCalls = m_diffCounters.getCtr(DxvkStatCounter::CmdDispatchCalls); uint64_t rpCalls = m_diffCounters.getCtr(DxvkStatCounter::CmdRenderPassCount); std::string strDrawCalls = str::format("Draw calls: ", gpCalls); std::string strDispatchCalls = str::format("Dispatch calls: ", cpCalls); std::string strRenderPasses = str::format("Render passes: ", rpCalls); position.y += 16.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, strDrawCalls); position.y += 20.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, strDispatchCalls); position.y += 20.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, strRenderPasses); position.y += 8.0f; return position; } HudPipelineStatsItem::HudPipelineStatsItem(const Rc& device) : m_device(device) { } HudPipelineStatsItem::~HudPipelineStatsItem() { } void HudPipelineStatsItem::update(dxvk::high_resolution_clock::time_point time) { DxvkStatCounters counters = m_device->getStatCounters(); m_graphicsPipelines = counters.getCtr(DxvkStatCounter::PipeCountGraphics); m_computePipelines = counters.getCtr(DxvkStatCounter::PipeCountCompute); } HudPos HudPipelineStatsItem::render( HudRenderer& renderer, HudPos position) { std::string strGpCount = str::format("Graphics pipelines: ", m_graphicsPipelines); std::string strCpCount = str::format("Compute pipelines: ", m_computePipelines); position.y += 16.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, strGpCount); position.y += 20.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, strCpCount); position.y += 8.0f; return position; } HudMemoryStatsItem::HudMemoryStatsItem(const Rc& device) : m_device(device), m_memory(device->adapter()->memoryProperties()) { } HudMemoryStatsItem::~HudMemoryStatsItem() { } void HudMemoryStatsItem::update(dxvk::high_resolution_clock::time_point time) { for (uint32_t i = 0; i < m_memory.memoryHeapCount; i++) m_heaps[i] = m_device->getMemoryStats(i); } HudPos HudMemoryStatsItem::render( HudRenderer& renderer, HudPos position) { for (uint32_t i = 0; i < m_memory.memoryHeapCount; i++) { bool isDeviceLocal = m_memory.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT; uint64_t memUsedMib = m_heaps[i].memoryUsed >> 20; uint64_t percentage = (100 * m_heaps[i].memoryUsed) / m_memory.memoryHeaps[i].size; std::string text = str::format(isDeviceLocal ? "Vidmem" : "Sysmem", " heap ", i, ": ", std::setfill(' '), std::setw(5), memUsedMib, " MB (", percentage, "%)"); position.y += 16.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, text); position.y += 4.0f; } position.y += 4.0f; return position; } HudGpuLoadItem::HudGpuLoadItem(const Rc& device) : m_device(device) { } HudGpuLoadItem::~HudGpuLoadItem() { } void HudGpuLoadItem::update(dxvk::high_resolution_clock::time_point time) { uint64_t ticks = std::chrono::duration_cast(time - m_lastUpdate).count(); if (ticks >= UpdateInterval) { DxvkStatCounters counters = m_device->getStatCounters(); uint64_t currGpuIdleTicks = counters.getCtr(DxvkStatCounter::GpuIdleTicks); m_diffGpuIdleTicks = currGpuIdleTicks - m_prevGpuIdleTicks; m_prevGpuIdleTicks = currGpuIdleTicks; uint64_t busyTicks = ticks > m_diffGpuIdleTicks ? uint64_t(ticks - m_diffGpuIdleTicks) : uint64_t(0); m_gpuLoadString = str::format("GPU: ", (100 * busyTicks) / ticks, "%"); m_lastUpdate = time; } } HudPos HudGpuLoadItem::render( HudRenderer& renderer, HudPos position) { position.y += 16.0f; renderer.drawText(16.0f, { position.x, position.y }, { 1.0f, 1.0f, 1.0f, 1.0f }, m_gpuLoadString); position.y += 8.0f; return position; } HudCompilerActivityItem::HudCompilerActivityItem(const Rc& device) : m_device(device) { } HudCompilerActivityItem::~HudCompilerActivityItem() { } void HudCompilerActivityItem::update(dxvk::high_resolution_clock::time_point time) { DxvkStatCounters counters = m_device->getStatCounters(); bool doShow = counters.getCtr(DxvkStatCounter::PipeCompilerBusy); if (!doShow) { auto elapsed = std::chrono::duration_cast(time - m_timeShown); doShow = elapsed.count() <= MinShowDuration; } if (doShow && !m_show) m_timeShown = time; m_show = doShow; } HudPos HudCompilerActivityItem::render( HudRenderer& renderer, HudPos position) { if (m_show) { renderer.drawText(16.0f, { position.x, renderer.surfaceSize().height - 20.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, "Compiling shaders..."); } return position; } }