Cemu/src/Cafe/HW/Latte/Core/LatteOverlay.cpp
Exzap 3bceb39966
Remove PUBLIC_RELEASE flag and tie asserts to debug config (#287)
Removes the -DPUBLIC_RELEASE flag. Cemu's debug asserts are now only enabled if the build configuration is Debug. Similarly, on Windows the console is only shown for Debug builds.
2022-09-24 08:43:27 +02:00

687 lines
22 KiB
C++

#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "gui/guiWrapper.h"
#include "config/CemuConfig.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "config/ActiveSettings.h"
#include <imgui.h>
#include "resource/IconsFontAwesome5.h"
#include "imgui/imgui_extension.h"
#include "input/InputManager.h"
#include <cinttypes>
#if BOOST_OS_WINDOWS
#include <Psapi.h>
#include <winternl.h>
#pragma comment(lib, "ntdll.lib")
#endif
struct OverlayStats
{
OverlayStats() {};
int processor_count = 1;
// cemu cpu stats
uint64_t last_cpu{}, kernel{}, user{};
// global cpu stats
struct ProcessorTime
{
uint64_t idle{}, kernel{}, user{};
};
std::vector<ProcessorTime> processor_times;
double fps{};
uint32 draw_calls_per_frame{};
float cpu_usage{}; // cemu cpu usage in %
std::vector<float> cpu_per_core; // global cpu usage in % per core
uint32 ram_usage{}; // ram usage in MB
int vramUsage{}, vramTotal{}; // vram usage in mb
} g_state{};
extern std::atomic_int g_compiled_shaders_total;
extern std::atomic_int g_compiled_shaders_async;
std::atomic_int g_compiling_pipelines;
std::atomic_int g_compiling_pipelines_async;
std::atomic_uint64_t g_compiling_pipelines_syncTimeSum;
extern std::mutex g_friend_notification_mutex;
extern std::vector< std::pair<std::string, int> > g_friend_notifications;
std::mutex g_notification_mutex;
std::vector< std::pair<std::string, int> > g_notifications;
void LatteOverlay_pushNotification(const std::string& text, sint32 duration)
{
std::unique_lock lock(g_notification_mutex);
g_notifications.emplace_back(text, duration);
}
struct OverlayList
{
std::wstring text;
float pos_x = 0;
float pos_y = 0;
float width;
OverlayList(std::wstring text, float width)
: text(std::move(text)), width(width) {}
};
const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
const float kBackgroundAlpha = 0.65f;
void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 direction)
{
auto& config = GetConfig();
const auto font = ImGui_GetFont(14.0f * (float)config.overlay.text_scale / 100.0f);
ImGui::PushFont(font);
const ImVec4 color = ImGui::ColorConvertU32ToFloat4(config.overlay.text_color);
ImGui::PushStyleColor(ImGuiCol_Text, color);
// stats overlay
if (config.overlay.fps || config.overlay.drawcalls || config.overlay.cpu_usage || config.overlay.cpu_per_core_usage || config.overlay.ram_usage)
{
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowBgAlpha(kBackgroundAlpha);
if (ImGui::Begin("Stats overlay", nullptr, kPopupFlags))
{
if (config.overlay.fps)
ImGui::Text("FPS: %.2lf", g_state.fps);
if (config.overlay.drawcalls)
ImGui::Text("Draws/f: %d", g_state.draw_calls_per_frame);
if (config.overlay.cpu_usage)
ImGui::Text("CPU: %.2lf%%", g_state.cpu_usage);
if (config.overlay.cpu_per_core_usage)
{
for (sint32 i = 0; i < g_state.processor_count; ++i)
{
ImGui::Text("CPU #%d: %.2lf%%", i + 1, g_state.cpu_per_core[i]);
}
}
if (config.overlay.ram_usage)
ImGui::Text("RAM: %dMB", g_state.ram_usage);
if(config.overlay.vram_usage && g_state.vramUsage != -1 && g_state.vramTotal != -1)
ImGui::Text("VRAM: %dMB / %dMB", g_state.vramUsage, g_state.vramTotal);
if (config.overlay.debug)
g_renderer->AppendOverlayDebugInfo();
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
ImGui::End();
}
}
ImGui::PopStyleColor();
ImGui::PopFont();
}
void LatteOverlay_RenderNotifications(ImVec2& position, ImVec2& pivot, sint32 direction)
{
auto& config = GetConfig();
const auto font = ImGui_GetFont(14.0f * (float)config.notification.text_scale / 100.0f);
ImGui::PushFont(font);
const ImVec4 color = ImGui::ColorConvertU32ToFloat4(config.notification.text_color);
ImGui::PushStyleColor(ImGuiCol_Text, color);
// selected controller profiles in the beginning
if (config.notification.controller_profiles)
{
static bool s_init_overlay = false;
if (!s_init_overlay)
{
static std::chrono::steady_clock::time_point s_started = tick_cached();
const auto now = tick_cached();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_started).count() <= 5000)
{
// active account
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowBgAlpha(kBackgroundAlpha);
if (ImGui::Begin("Active account", nullptr, kPopupFlags))
{
ImGui::TextUnformatted((const char*)ICON_FA_USER);
ImGui::SameLine();
static std::string s_mii_name;
if (s_mii_name.empty())
{
auto tmp_view = Account::GetAccount(ActiveSettings::GetPersistentId()).GetMiiName();
std::wstring tmp{ tmp_view };
s_mii_name = boost::nowide::narrow(tmp);
}
ImGui::TextUnformatted(s_mii_name.c_str());
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
ImGui::End();
}
// controller
std::vector<std::pair<int, std::string>> profiles;
auto& input_manager = InputManager::instance();
for (int i = 0; i < InputManager::kMaxController; ++i)
{
const auto controller = input_manager.get_controller(i);
if (!controller)
continue;
const auto& profile_name = controller->get_profile_name();
if (profile_name.empty())
continue;
profiles.emplace_back(i, profile_name);
}
if (!profiles.empty())
{
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowBgAlpha(kBackgroundAlpha);
if (ImGui::Begin("Controller profile names", nullptr, kPopupFlags))
{
auto it = profiles.cbegin();
ImGui::TextUnformatted((const char*)ICON_FA_GAMEPAD);
ImGui::SameLine();
ImGui::Text("Player %d: %s", it->first + 1, it->second.c_str());
for (++it; it != profiles.cend(); ++it)
{
ImGui::Separator();
ImGui::TextUnformatted((const char*)ICON_FA_GAMEPAD);
ImGui::SameLine();
ImGui::Text("Player %d: %s", it->first + 1, it->second.c_str());
}
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
ImGui::End();
}
}
else
s_init_overlay = true;
}
else
s_init_overlay = true;
}
}
if (config.notification.friends)
{
static std::vector< std::pair<std::string, std::chrono::steady_clock::time_point> > s_friend_list;
std::unique_lock lock(g_friend_notification_mutex);
if (!g_friend_notifications.empty())
{
const auto tick = tick_cached();
for (const auto& entry : g_friend_notifications)
{
s_friend_list.emplace_back(entry.first, tick + std::chrono::milliseconds(entry.second));
}
g_friend_notifications.clear();
}
if (!s_friend_list.empty())
{
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowBgAlpha(kBackgroundAlpha);
if (ImGui::Begin("Friends overlay", nullptr, kPopupFlags))
{
const auto tick = tick_cached();
for (auto it = s_friend_list.cbegin(); it != s_friend_list.cend();)
{
ImGui::TextUnformatted(it->first.c_str(), it->first.c_str() + it->first.size());
if (tick >= it->second)
it = s_friend_list.erase(it);
else
++it;
}
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
ImGui::End();
}
}
}
// low battery warning
if (config.notification.controller_battery)
{
std::vector<int> batteries;
auto& input_manager = InputManager::instance();
for (int i = 0; i < InputManager::kMaxController; ++i)
{
const auto controller = input_manager.get_controller(i);
if (!controller)
continue;
if (controller->is_battery_low())
batteries.emplace_back(i);
}
if (!batteries.empty())
{
static std::chrono::steady_clock::time_point s_last_tick = tick_cached();
static bool s_blink_state = false;
const auto now = tick_cached();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 750)
{
s_blink_state = !s_blink_state;
s_last_tick = now;
}
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowBgAlpha(kBackgroundAlpha);
if (ImGui::Begin("Low battery overlay", nullptr, kPopupFlags))
{
auto it = batteries.cbegin();
ImGui::TextUnformatted((const char*)(s_blink_state ? ICON_FA_BATTERY_EMPTY : ICON_FA_BATTERY_QUARTER));
ImGui::SameLine();
ImGui::Text("Player %d", *it + 1);
for (++it; it != batteries.cend(); ++it)
{
ImGui::Separator();
ImGui::TextUnformatted((const char*)(s_blink_state ? ICON_FA_BATTERY_EMPTY : ICON_FA_BATTERY_QUARTER));
ImGui::SameLine();
ImGui::Text("Player %d", *it + 1);
}
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
ImGui::End();
}
}
}
if (config.notification.shader_compiling)
{
static int32_t s_shader_count = 0;
static int32_t s_shader_count_async = 0;
if (s_shader_count > 0 || g_compiled_shaders_total > 0)
{
const int tmp = g_compiled_shaders_total.exchange(0);
const int tmpAsync = g_compiled_shaders_async.exchange(0);
s_shader_count += tmp;
s_shader_count_async += tmpAsync;
static std::chrono::steady_clock::time_point s_last_tick = tick_cached();
const auto now = tick_cached();
if (tmp > 0)
s_last_tick = now;
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 2500)
{
s_shader_count = 0;
s_shader_count_async = 0;
}
if (s_shader_count > 0)
{
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowBgAlpha(kBackgroundAlpha);
if (ImGui::Begin("Compiling shaders overlay", nullptr, kPopupFlags))
{
ImRotateStart();
ImGui::TextUnformatted((const char*)ICON_FA_SPINNER);
const auto ticks = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
ImRotateEnd(0.001f * ticks.time_since_epoch().count());
ImGui::SameLine();
if (s_shader_count_async > 0 && GetConfig().async_compile) // the latter condition is to never show async count when async isn't enabled. Since it can be confusing to the user
{
if(s_shader_count > 1)
ImGui::Text("Compiled %d new shaders... (%d async)", s_shader_count, s_shader_count_async);
else
ImGui::Text("Compiled %d new shader... (%d async)", s_shader_count, s_shader_count_async);
}
else
{
if (s_shader_count > 1)
ImGui::Text("Compiled %d new shaders...", s_shader_count);
else
ImGui::Text("Compiled %d new shader...", s_shader_count);
}
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
ImGui::End();
}
}
}
static int32_t s_pipeline_count = 0;
static int32_t s_pipeline_count_async = 0;
if (s_pipeline_count > 0 || g_compiling_pipelines > 0)
{
const int tmp = g_compiling_pipelines.exchange(0);
const int tmpAsync = g_compiling_pipelines_async.exchange(0);
s_pipeline_count += tmp;
s_pipeline_count_async += tmpAsync;
static std::chrono::steady_clock::time_point s_last_tick = tick_cached();
const auto now = tick_cached();
if (tmp > 0)
s_last_tick = now;
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 2500)
{
s_pipeline_count = 0;
s_pipeline_count_async = 0;
}
if (s_pipeline_count > 0)
{
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowBgAlpha(kBackgroundAlpha);
if (ImGui::Begin("Compiling pipeline overlay", nullptr, kPopupFlags))
{
ImRotateStart();
ImGui::TextUnformatted((const char*)ICON_FA_SPINNER);
const auto ticks = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
ImRotateEnd(0.001f * ticks.time_since_epoch().count());
ImGui::SameLine();
#ifdef CEMU_DEBUG_ASSERT
uint64 totalTime = g_compiling_pipelines_syncTimeSum / 1000000ull;
if (s_pipeline_count_async > 0)
{
if (s_pipeline_count > 1)
ImGui::Text("Compiled %d new pipelines... (%d async) TotalSync: %" PRIu64 "ms", s_pipeline_count, s_pipeline_count_async, totalTime);
else
ImGui::Text("Compiled %d new pipeline... (%d async) TotalSync: %" PRIu64 "ms", s_pipeline_count, s_pipeline_count_async, totalTime);
}
else
{
if (s_pipeline_count > 1)
ImGui::Text("Compiled %d new pipelines... TotalSync: %" PRIu64 "ms", s_pipeline_count, totalTime);
else
ImGui::Text("Compiled %d new pipeline... TotalSync: %" PRIu64 "ms", s_pipeline_count, totalTime);
}
#else
if (s_pipeline_count_async > 0)
{
if (s_pipeline_count > 1)
ImGui::Text("Compiled %d new pipelines... (%d async)", s_pipeline_count, s_pipeline_count_async);
else
ImGui::Text("Compiled %d new pipeline... (%d async)", s_pipeline_count, s_pipeline_count_async);
}
else
{
if (s_pipeline_count > 1)
ImGui::Text("Compiled %d new pipelines...", s_pipeline_count);
else
ImGui::Text("Compiled %d new pipeline...", s_pipeline_count);
}
#endif
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
ImGui::End();
}
}
}
}
// misc notifications
static std::vector< std::pair<std::string, std::chrono::steady_clock::time_point> > s_misc_notifications;
std::unique_lock misc_lock(g_notification_mutex);
if (!g_notifications.empty())
{
const auto tick = tick_cached();
for (const auto& entry : g_notifications)
{
s_misc_notifications.emplace_back(entry.first, tick + std::chrono::milliseconds(entry.second));
}
g_notifications.clear();
}
misc_lock.unlock();
if (!s_misc_notifications.empty())
{
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowBgAlpha(kBackgroundAlpha);
if (ImGui::Begin("Misc notifications", nullptr, kPopupFlags))
{
const auto tick = tick_cached();
for (auto it = s_misc_notifications.cbegin(); it != s_misc_notifications.cend();)
{
ImGui::TextUnformatted(it->first.c_str(), it->first.c_str() + it->first.size());
if (tick >= it->second)
it = s_misc_notifications.erase(it);
else
++it;
}
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
ImGui::End();
}
}
ImGui::PopStyleColor();
ImGui::PopFont();
}
void LatteOverlay_translateScreenPosition(ScreenPosition pos, const Vector2f& window_size, ImVec2& position, ImVec2& pivot, sint32& direction)
{
switch (pos)
{
case ScreenPosition::kTopLeft:
position = { 10, 10 };
pivot = { 0, 0 };
direction = 1;
break;
case ScreenPosition::kTopCenter:
position = { window_size.x / 2.0f, 10 };
pivot = { 0.5f, 0 };
direction = 1;
break;
case ScreenPosition::kTopRight:
position = { window_size.x - 10, 10 };
pivot = { 1, 0 };
direction = 1;
break;
case ScreenPosition::kBottomLeft:
position = { 10, window_size.y - 10 };
pivot = { 0, 1 };
direction = -1;
break;
case ScreenPosition::kBottomCenter:
position = { window_size.x / 2.0f, window_size.y - 10 };
pivot = { 0.5f, 1 };
direction = -1;
break;
case ScreenPosition::kBottomRight:
position = { window_size.x - 10, window_size.y - 10 };
pivot = { 1, 1 };
direction = -1;
break;
default:
UNREACHABLE;
}
}
void LatteOverlay_render(bool pad_view)
{
const auto& config = GetConfig();
if(config.overlay.position == ScreenPosition::kDisabled && config.notification.position == ScreenPosition::kDisabled)
return;
sint32 w = 0, h = 0;
if (pad_view && gui_isPadWindowOpen())
gui_getPadWindowSize(&w, &h);
else
gui_getWindowSize(&w, &h);
if (w == 0 || h == 0)
return;
const Vector2f window_size{ (float)w,(float)h };
// test if fonts are already precached
if (!ImGui_GetFont(14.0f * (float)config.overlay.text_scale / 100.0f))
return;
if (!ImGui_GetFont(14.0f * (float)config.notification.text_scale / 100.0f))
return;
ImVec2 position{}, pivot{};
sint32 direction{};
if (config.overlay.position != ScreenPosition::kDisabled)
{
LatteOverlay_translateScreenPosition(config.overlay.position, window_size, position, pivot, direction);
LatteOverlay_renderOverlay(position, pivot, direction);
}
if (config.notification.position != ScreenPosition::kDisabled)
{
if(config.overlay.position != config.notification.position)
LatteOverlay_translateScreenPosition(config.notification.position, window_size, position, pivot, direction);
LatteOverlay_RenderNotifications(position, pivot, direction);
}
}
void LatteOverlay_init()
{
#if BOOST_OS_WINDOWS
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
g_state.processor_count = sys_info.dwNumberOfProcessors;
g_state.processor_times.resize(g_state.processor_count);
g_state.cpu_per_core.resize(g_state.processor_count);
#else
g_state.processor_count = 1;
#endif
}
void LatteOverlay_updateStats(double fps, sint32 drawcalls)
{
if (GetConfig().overlay.position == ScreenPosition::kDisabled)
return;
g_state.fps = fps;
g_state.draw_calls_per_frame = drawcalls;
#if BOOST_OS_WINDOWS
// update cemu cpu
FILETIME ftime, fkernel, fuser;
LARGE_INTEGER now, kernel, user;
GetSystemTimeAsFileTime(&ftime);
now.LowPart = ftime.dwLowDateTime;
now.HighPart = ftime.dwHighDateTime;
GetProcessTimes(GetCurrentProcess(), &ftime, &ftime, &fkernel, &fuser);
kernel.LowPart = fkernel.dwLowDateTime;
kernel.HighPart = fkernel.dwHighDateTime;
user.LowPart = fuser.dwLowDateTime;
user.HighPart = fuser.dwHighDateTime;
double percent = (kernel.QuadPart - g_state.kernel) + (user.QuadPart - g_state.user);
percent /= (now.QuadPart - g_state.last_cpu);
percent /= g_state.processor_count;
g_state.cpu_usage = percent * 100;
g_state.last_cpu = now.QuadPart;
g_state.user = user.QuadPart;
g_state.kernel = kernel.QuadPart;
// update cpu per core
std::vector<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION> sppi(g_state.processor_count);
if (NT_SUCCESS(NtQuerySystemInformation(SystemProcessorPerformanceInformation, sppi.data(), sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * g_state.processor_count, nullptr)))
{
for (sint32 i = 0; i < g_state.processor_count; ++i)
{
const uint64 kernel_diff = sppi[i].KernelTime.QuadPart - g_state.processor_times[i].kernel;
const uint64 user_diff = sppi[i].UserTime.QuadPart - g_state.processor_times[i].user;
const uint64 idle_diff = sppi[i].IdleTime.QuadPart - g_state.processor_times[i].idle;
const auto total = kernel_diff + user_diff; // kernel time already includes idletime
const double cpu = total == 0 ? 0 : (1.0 - ((double)idle_diff / total)) * 100.0;
g_state.cpu_per_core[i] = cpu;
//total_cpu += cpu;
g_state.processor_times[i].idle = sppi[i].IdleTime.QuadPart;
g_state.processor_times[i].kernel = sppi[i].KernelTime.QuadPart;
g_state.processor_times[i].user = sppi[i].UserTime.QuadPart;
}
//total_cpu /= g_state.processor_count;
//g_state.cpu_usage = total_cpu;
}
// update ram
PROCESS_MEMORY_COUNTERS pmc{};
pmc.cb = sizeof(pmc);
GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
g_state.ram_usage = (pmc.WorkingSetSize / 1000) / 1000;
#endif
// update vram
g_renderer->GetVRAMInfo(g_state.vramUsage, g_state.vramTotal);
}
void LatteOverlay_updateStatsPerFrame()
{
if (!ActiveSettings::FrameProfilerEnabled())
return;
// update frametime graph
uint32 frameTime_total = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_frameTime.getPreviousFrameValue());
uint32 frameTime_idle = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_idleTime.getPreviousFrameValue());
uint32 frameTime_dcStageTextures = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageTextures.getPreviousFrameValue());
uint32 frameTime_dcStageVertexMgr = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageVertexMgr.getPreviousFrameValue());
uint32 frameTime_dcStageShaderAndUniformMgr = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr.getPreviousFrameValue());
uint32 frameTime_dcStageIndexMgr = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageIndexMgr.getPreviousFrameValue());
uint32 frameTime_dcStageMRT = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageMRT.getPreviousFrameValue());
uint32 frameTime_dcStageDrawcallAPI = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageDrawcallAPI.getPreviousFrameValue());
uint32 frameTime_waitForAsync = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_waitForAsync.getPreviousFrameValue());
// make sure total frame time is not less than it's sums
uint32 minimumExpectedFrametime =
frameTime_idle +
frameTime_dcStageTextures +
frameTime_dcStageVertexMgr +
frameTime_dcStageShaderAndUniformMgr +
frameTime_dcStageIndexMgr +
frameTime_dcStageMRT +
frameTime_dcStageDrawcallAPI +
frameTime_waitForAsync;
frameTime_total = std::max(frameTime_total, minimumExpectedFrametime);
//g_state.frametimeGraph.appendEntry();
//g_state.frametimeGraph.setCurrentEntryValue(0xFF404040, frameTime_idle);
//g_state.frametimeGraph.setCurrentEntryValue(0xFFFFC0FF, frameTime_waitForAsync);
//g_state.frametimeGraph.setCurrentEntryValue(0xFF000040, frameTime_dcStageTextures); // dark red
//g_state.frametimeGraph.setCurrentEntryValue(0xFF004000, frameTime_dcStageVertexMgr); // dark green
//g_state.frametimeGraph.setCurrentEntryValue(0xFFFFFF80, frameTime_dcStageShaderAndUniformMgr); // blueish
//g_state.frametimeGraph.setCurrentEntryValue(0xFF800080, frameTime_dcStageIndexMgr); // purple
//g_state.frametimeGraph.setCurrentEntryValue(0xFF00FF00, frameTime_dcStageMRT); // green
//g_state.frametimeGraph.setCurrentEntryValue(0xFF00FFFF, frameTime_dcStageDrawcallAPI); // yellow
//g_state.frametimeGraph.setCurrentEntryValue(0xFFBBBBBB, frameTime_total - minimumExpectedFrametime);
}