You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CrySystem/PerfHUD.cpp

2167 lines
64 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : Button implementation in the MiniGUI
#include "CrySystem_precompiled.h"
#ifdef USE_PERFHUD
#include "PerfHUD.h"
#include "MiniGUI/MiniInfoBox.h"
#include "MiniGUI/MiniMenu.h"
#include "MiniGUI/MiniTable.h"
#include <IConsole.h>
#include <ITimer.h>
#include "System.h"
#include <CryExtension/CryCreateClassInstance.h>
#include <LyShine/Bus/UiCursorBus.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
#include <AzCore/Casting/lossy_cast.h>
#define PERFHUD_CONFIG_FILE "Config/PerfHud_PC.xml"
using namespace minigui;
const float CPerfHUD::OVERSCAN_X = 15;
const float CPerfHUD::OVERSCAN_Y = 15;
const ColorB CPerfHUD::COL_NORM = ColorB(255, 255, 255, 255);
const ColorB CPerfHUD::COL_WARN = ColorB(255, 255, 0, 255);
const ColorB CPerfHUD::COL_ERROR = ColorB(255, 0, 0, 255);
const float CPerfHUD::TEXT_SIZE_NORM = 14.f;
const float CPerfHUD::TEXT_SIZE_WARN = 18.f;
const float CPerfHUD::TEXT_SIZE_ERROR = 26.f;
const float CPerfHUD::ACTIVATE_TIME_FROM_GAME = 1.f;
const float CPerfHUD::ACTIVATE_TIME_FROM_HUD = 0.1f;
CRYREGISTER_SINGLETON_CLASS(CPerfHUD)
CPerfHUD::CPerfHUD()
: m_menuStartX(OVERSCAN_X)
, m_menuStartY(OVERSCAN_Y)
, m_hudCreated(false)
, m_L1Pressed(false)
, m_L2Pressed(false)
, m_R1Pressed(false)
, m_R2Pressed(false)
, m_changingState(false)
, m_hwMouseEnabled(false)
, m_triggersDownStartTime(-1.f)
, m_hudState(eHudOff)
, m_hudLastState(eHudOff)
{
m_widgets.reserve(ICryPerfHUDWidget::eWidget_Num);
}
CPerfHUD::~CPerfHUD() {}
void CPerfHUD::Destroy()
{
m_widgets.clear();
m_rootMenus.clear();
IMiniGUIPtr pGUI;
if (CryCreateClassInstanceForInterface(cryiidof<IMiniGUI>(), pGUI))
{
pGUI->RemoveAllCtrl();
}
}
//
// CONSOLE COMMAND CALLBACKS
//
int CPerfHUD::m_sys_perfhud = 0;
int CPerfHUD::m_sys_perfhud_pause = 0;
void CPerfHUD::CVarChangeCallback(ICVar* pCvar)
{
ICryPerfHUD* pPerfHud = gEnv->pSystem->GetPerfHUD();
if (pPerfHud)
{
int val = pCvar->GetIVal();
//Check for invalid value
if (val >= 0 && val < eHudNumStates)
{
pPerfHud->SetState((EHudState)val);
}
}
}
SET_WIDGET_DEF(Warnings, eWidget_Warnings)
SET_WIDGET_DEF(RenderSummary, eWidget_RenderStats)
SET_WIDGET_DEF(RenderBatchStats, eWidget_RenderBatchStats)
SET_WIDGET_DEF(FpsBuckets, eWidget_FpsBuckets)
SET_WIDGET_DEF(Particles, eWidget_Particles)
SET_WIDGET_DEF(PakFile, eWidget_PakFile)
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::Init()
{
m_sys_perfhud_prev = 0;
if (gEnv->pConsole)
{
REGISTER_CVAR2_CB("sys_perfhud", &m_sys_perfhud, 0, VF_ALWAYSONCHANGE, "PerfHUD 0:off, 1:In focus, 2:Out of focus", CVarChangeCallback);
REGISTER_CVAR2("sys_perfhud_pause", &m_sys_perfhud_pause, 0, VF_NULL, "Toggle FPS Buckets exclusive / inclusive");
SET_WIDGET_COMMAND("stats_Warnings", Warnings);
SET_WIDGET_COMMAND("stats_RenderSummary", RenderSummary);
SET_WIDGET_COMMAND("stats_RenderBatchStats", RenderBatchStats);
SET_WIDGET_COMMAND("stats_FpsBuckets", FpsBuckets);
SET_WIDGET_COMMAND("stats_Particles", Particles);
SET_WIDGET_COMMAND("stats_PakFile", PakFile);
}
AzFramework::InputChannelEventListener::Connect();
IMiniGUIPtr pGUI;
if (CryCreateClassInstanceForInterface(cryiidof<IMiniGUI>(), pGUI))
{
InitUI(pGUI.get());
}
}
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::LoadBudgets()
{
XmlNodeRef budgets = gEnv->pSystem->LoadXmlFromFile(PERFHUD_CONFIG_FILE);
if (budgets)
{
const int nWidgets = m_widgets.size();
for (int i = 0; i < nWidgets; i++)
{
m_widgets[i]->LoadBudgets(budgets);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::SaveStats(const char* filename)
{
XmlNodeRef rootNode = GetISystem()->CreateXmlNode("PerfHudStats");
if (rootNode)
{
const int nWidgets = m_widgets.size();
for (int i = 0; i < nWidgets; i++)
{
m_widgets[i]->SaveStats(rootNode);
}
if (!filename)
{
filename = "PerfHudStats.xml";
}
rootNode->saveToFile(filename);
}
}
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::ResetWidgets()
{
TWidgetIterator itWidget;
for (itWidget = m_widgets.begin(); itWidget != m_widgets.end(); ++itWidget)
{
(*itWidget)->Reset();
}
}
void CPerfHUD::AddWidget(ICryPerfHUDWidget* pWidget)
{
assert(pWidget);
static int s_widgetUID = ICryPerfHUDWidget::eWidget_Num;
if (pWidget->m_id == -1)
{
pWidget->m_id = s_widgetUID++;
}
m_widgets.push_back(pWidget);
}
void CPerfHUD::RemoveWidget(ICryPerfHUDWidget* pWidget)
{
TWidgetIterator widgetIter = m_widgets.begin();
TWidgetIterator widgetEnd = m_widgets.end();
while (widgetIter != widgetEnd)
{
if (pWidget == (*widgetIter).get())
{
m_widgets.erase(widgetIter);
break;
}
++widgetIter;
}
}
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::Done()
{
AzFramework::InputChannelEventListener::Disconnect();
}
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::Draw()
{
FUNCTION_PROFILER_FAST(GetISystem(), PROFILE_SYSTEM, g_bProfilerEnabled);
if (m_hudState != m_hudLastState)
{
//restore gui state if the last state was off
bool bRestoreState = (m_hudLastState == eHudOff);
Show(bRestoreState);
m_hudLastState = m_hudState;
}
if (m_hudState != eHudOff)
{
//Pause
if (m_sys_perfhud_pause)
{
if ((gEnv->pRenderer->GetFrameID(false) % 40) < 20)
{
float col[4] = {1.f, 1.f, 0.f, 1.f};
gEnv->pRenderer->Draw2dLabel(500.f, 220.f, 2.f, col, false, "PefHUD Paused");
}
}
else //Update
{
const int nWidgets = m_widgets.size();
for (int i = 0; i < nWidgets; i++)
{
if (m_widgets[i]->ShouldUpdate())
{
m_widgets[i]->Update();
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::Show(bool bRestoreState)
{
IMiniGUIPtr pGUI;
if (!CryCreateClassInstanceForInterface(cryiidof<IMiniGUI>(), pGUI))
{
return;
}
switch (m_hudState)
{
case eHudInFocus:
{
pGUI->SetEventListener(this);
if (!m_hudCreated)
{
InitUI(pGUI.get());
}
else if (bRestoreState)
{
pGUI->RestoreState();
}
pGUI->SetEnabled(true);
pGUI->SetInFocus(true);
int nRootMenus = m_rootMenus.size();
for (int i = 0; i < nRootMenus; i++)
{
m_rootMenus[i]->SetVisible(1);
}
}
break;
case eHudOutOfFocus:
{
if (bRestoreState)
{
pGUI->RestoreState();
}
int nRootMenus = m_rootMenus.size();
for (int i = 0; i < nRootMenus; i++)
{
m_rootMenus[i]->SetVisible(0);
}
pGUI->SetEventListener(0);
pGUI->SetEnabled(true);
pGUI->SetInFocus(false);
}
break;
case eHudOff:
{
pGUI->SaveState();
pGUI->Reset();
pGUI->SetEnabled(false);
}
break;
default:
break;
}
}
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::InitUI(IMiniGUI* pGUI)
{
assert(m_hudCreated == false);
IMiniCtrl* pMenu;
XmlNodeRef perfXML = gEnv->pSystem->LoadXmlFromFile(PERFHUD_CONFIG_FILE);
//
// RENDERING MENU
//
pMenu = CreateMenu("Rendering");
IMiniCtrl* pDebugMenu = CreateMenu("Debug", pMenu);
CreateCVarMenuItem(pDebugMenu, "Wireframe", "r_wireframe", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Overdraw", "r_MeasureOverdrawScale", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Freeze Camera", "e_CameraFreeze", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Post Effects", "r_PostProcessEffects", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Deferred decals debug", "r_deferredDecalsDebug", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Shadows", "e_Shadows", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Ocean", "e_WaterOcean", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Characters", "ca_DrawChr", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Coverage Buffer", "e_CoverageBuffer", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Sun", "e_Sun", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Unlit", "r_Unlit", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Disable Normal Maps", "r_texbindmode", 0, 5);
CreateCVarMenuItem(pDebugMenu, "Env Probes", "r_deferredShadingEnvProbes", 0, 1);
CreateCVarMenuItem(pDebugMenu, "Lighting View", "r_texbindmode", 0, 11);
CreateCVarMenuItem(pDebugMenu, "Normal and Lighting View", "r_texbindmode", 0, 6);
CreateCVarMenuItem(pDebugMenu, "Default Material", "e_defaultMaterial", 0, 1);
//this cvar is not created when perfHUD is initialised - so it doesn't work
//CreateCVarMenuItem(pDebugMenu, "MFX visual debug", "mfx_DebugVisual", 0, 1 );
IMiniCtrl* pStatsMenu = CreateMenu("Stats", pMenu);
//Render Info
CRenderStatsWidget* pRenderStats = new CRenderStatsWidget(pStatsMenu, this);
if (pRenderStats)
{
if (perfXML)
{
pRenderStats->LoadBudgets(perfXML);
}
m_widgets.push_back(pRenderStats);
}
CRenderBatchWidget* pRenderBatchStats = new CRenderBatchWidget(pStatsMenu, this);
if (pRenderBatchStats)
{
m_widgets.push_back(pRenderBatchStats);
}
#ifndef _RELEASE
CreateCVarMenuItem(pStatsMenu, "Debug Gun", "e_debugDraw", 0, 16);
#endif
CreateCVarMenuItem(pStatsMenu, "Poly / Lod info", "e_debugDraw", 0, 1);
CreateCVarMenuItem(pStatsMenu, "Texture Memory Usage", "e_debugDraw", 0, 4);
CreateCVarMenuItem(pStatsMenu, "Detailed Render Stats", "r_Stats", 0, 1);
CreateCVarMenuItem(pStatsMenu, "Shader Stats", "r_ProfileShaders", 0, 1);
//
// SYSTEM MENU
//
pMenu = CreateMenu("System");
//FPS Buckets
CWarningsWidget* pWarningsWidget = new CWarningsWidget(pMenu, this);
if (pWarningsWidget)
{
m_widgets.push_back(pWarningsWidget);
}
CreateCVarMenuItem(pMenu, "Profiler", "profile", 0, 1);
CreateCVarMenuItem(pMenu, "Thread Summary", "r_showmt", 0, 1);
CreateCVarMenuItem(pMenu, "Track File Access", "sys_PakLogInvalidFileAccess", 0, 1);
//FPS Buckets
CFpsWidget* pFpsBuckets = new CFpsWidget(pMenu, this);
if (pFpsBuckets)
{
if (perfXML)
{
pFpsBuckets->LoadBudgets(perfXML);
}
m_widgets.push_back(pFpsBuckets);
}
//
// STREAMING MENU
//
pMenu = CreateMenu("Streaming");
CreateCVarMenuItem(pMenu, "Streaming Debug", "sys_streaming_debug", 0, 1);
CreateCVarMenuItem(pMenu, "Loaded Geometry Info", "e_streamcgfdebug", 0, 3);
CreateCVarMenuItem(pMenu, "Texture Load/Unload Debug", "r_TexBindMode", 0, 9);
CreateCVarMenuItem(pMenu, "Textures by Size", "r_TexturesStreamingDebug", 0, 4);
CreateCVarMenuItem(pMenu, "Textures by Prio", "r_TexturesStreamingDebug", 0, 5);
//
// SETUP MENU
//
pMenu = CreateMenu("Setup");
CreateCallbackMenuItem(pMenu, "Reset HUD", CPerfHUD::ResetCallback, NULL);
CreateCallbackMenuItem(pMenu, "Reload Budgets", CPerfHUD::ReloadBudgetsCallback, NULL);
CreateCallbackMenuItem(pMenu, "Save Stats", CPerfHUD::SaveStatsCallback, NULL);
CreateCVarMenuItem(pMenu, "Pause PerfHUD", "sys_perfhud_pause", 0, 1);
//save default windows
pGUI->SaveState();
m_hudCreated = true;
}
//////////////////////////////////////////////////////////////////////////
// GUI CREATION HELPER FUNCS
//////////////////////////////////////////////////////////////////////////
IMiniCtrl* CPerfHUD::CreateMenu(const char* name, IMiniCtrl* pParent)
{
assert(name);
IMiniGUIPtr pGUI;
if (CryCreateClassInstanceForInterface(cryiidof<IMiniGUI>(), pGUI))
{
bool subMenu = false;
if (pParent)
{
assert(pParent->GetType() == eCtrlType_Menu);
subMenu = true;
}
const float ButtonWidth = 10.f; //arbitrary, button will be scaled based on contained text
int ctrlFlags = 0;
if (!subMenu)
{
ctrlFlags = eCtrl_TextAlignCentre | eCtrl_AutoResize;
}
Rect rcMenuBtn = Rect(m_menuStartX, m_menuStartY, m_menuStartX + ButtonWidth, m_menuStartY + 20);
IMiniCtrl* pMenu = pGUI->CreateCtrl(pParent, 1, eCtrlType_Menu, ctrlFlags, rcMenuBtn, name);
if (pMenu && !subMenu)
{
const float MenuBtnSepration = 10.f;
//Update Menu Positions
rcMenuBtn = pMenu->GetRect();
m_menuStartX = rcMenuBtn.right + MenuBtnSepration;
m_menuStartY = rcMenuBtn.top;
m_rootMenus.push_back(pMenu);
}
return pMenu;
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////
bool CPerfHUD::CreateCVarMenuItem(IMiniCtrl* pMenu, const char* name, const char* controlVar, float controlVarOff, float controlVarOn)
{
assert(pMenu && name && controlVar);
assert(pMenu->GetType() == eCtrlType_Menu);
IMiniGUIPtr pGUI;
if (CryCreateClassInstanceForInterface(cryiidof<IMiniGUI>(), pGUI))
{
IMiniCtrl* pCtrl = pGUI->CreateCtrl(pMenu, 100, eCtrlType_Button, eCtrl_CheckButton, Rect(0, 0, 100, 20), name);
if (pCtrl)
{
if (controlVar)
{
pCtrl->SetControlCVar(controlVar, controlVarOff, controlVarOn);
}
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CPerfHUD::CreateCallbackMenuItem(IMiniCtrl* pMenu, const char* name, ClickCallback clickCallback, void* pCallbackData)
{
assert(pMenu && name && clickCallback);
assert(pMenu->GetType() == eCtrlType_Menu);
IMiniGUIPtr pGUI;
if (CryCreateClassInstanceForInterface(cryiidof<IMiniGUI>(), pGUI))
{
IMiniCtrl* pCtrl = pGUI->CreateCtrl(pMenu, 100, eCtrlType_Button, 0 /*eCtrl_CheckButton*/, Rect(0, 0, 100, 20), name);
if (pCtrl)
{
pCtrl->SetClickCallback(clickCallback, pCallbackData);
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
IMiniInfoBox* CPerfHUD::CreateInfoMenuItem(IMiniCtrl* pMenu, const char* name, RenderCallback renderCallback, const Rect& rect, bool onAtStart)
{
assert(pMenu->GetType() == eCtrlType_Menu);
IMiniGUIPtr pGUI;
if (CryCreateClassInstanceForInterface(cryiidof<IMiniGUI>(), pGUI))
{
IMiniCtrl* pCtrl = pGUI->CreateCtrl(pMenu, 100, eCtrlType_Button, eCtrl_CheckButton, Rect(0, 0, 100, 20), name);
int infoFlags = eCtrl_Moveable | eCtrl_CloseButton;
if (!renderCallback)
{
infoFlags |= eCtrl_AutoResize;
}
CMiniInfoBox* pInfo = (CMiniInfoBox*)pGUI->CreateCtrl(NULL, 200, eCtrlType_InfoBox, infoFlags, rect, name);
if (pCtrl)
{
pCtrl->SetConnectedCtrl(pInfo);
if (onAtStart)
{
pCtrl->SetFlag(eCtrl_Checked);
}
else
{
pInfo->SetVisible(false);
}
if (renderCallback)
{
pInfo->SetRenderCallback(renderCallback);
}
return pInfo;
}
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////
IMiniTable* CPerfHUD::CreateTableMenuItem(IMiniCtrl* pMenu, const char* name)
{
assert(pMenu && name);
assert(pMenu->GetType() == eCtrlType_Menu);
IMiniGUIPtr pGUI;
if (CryCreateClassInstanceForInterface(cryiidof<IMiniGUI>(), pGUI))
{
CMiniTable* pTable = (CMiniTable*)pGUI->CreateCtrl(NULL, 200, eCtrlType_Table, eCtrl_AutoResize | eCtrl_Moveable | eCtrl_CloseButton, Rect(50, 100, 400, 350), name);
if (pTable)
{
IMiniCtrl* pCtrl = pGUI->CreateCtrl(pMenu, 100, eCtrlType_Button, eCtrl_CheckButton, Rect(0, 0, 100, 20), name);
if (pCtrl)
{
pCtrl->SetConnectedCtrl(pTable);
pTable->SetVisible(false);
return pTable;
}
}
}
return NULL;
}
IMiniCtrl* CPerfHUD::GetMenu(const char* name)
{
int nRootMenus = m_rootMenus.size();
for (int i = 0; i < nRootMenus; i++)
{
if (strcmp(m_rootMenus[i]->GetTitle(), name) == 0)
{
return m_rootMenus[i];
}
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::OnCommand([[maybe_unused]] SCommand& cmd)
{
//CryLog( "Button Clicked = %d",cmd.nCtrlID );
}
//////////////////////////////////////////////////////////////////////////
bool CPerfHUD::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
{
const AzFramework::InputChannelId& channelId = inputChannel.GetInputChannelId();
const AzFramework::InputDeviceId& deviceId = inputChannel.GetInputDevice().GetInputDeviceId();
if (AzFramework::InputDeviceKeyboard::IsKeyboardDevice(deviceId) && inputChannel.IsStateBegan())
{
if (channelId == AzFramework::InputDeviceKeyboard::Key::WindowsSystemPrint)
{
//Cycle modes
const AzFramework::ModifierKeyStates* customData = inputChannel.GetCustomData<AzFramework::ModifierKeyStates>();
const AzFramework::ModifierKeyStates modifierKeyStates = customData ? *customData : AzFramework::ModifierKeyStates();
const bool isAltModifierActive = modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::AltAny);
const bool isCtrlModifierActive = modifierKeyStates.IsActive(AzFramework::ModifierKeyMask::CtrlAny);
if (isAltModifierActive || isCtrlModifierActive)
{
SetNextState();
}
}
else if (channelId == AzFramework::InputDeviceKeyboard::Key::WindowsSystemScrollLock)
{
//toggle pause
m_sys_perfhud_pause ^= 1;
}
}
if (deviceId == AzFramework::InputDeviceGamepad::IdForIndex0)
{
if (inputChannel.IsStateBegan())
{
bool checkState = false;
if (channelId == AzFramework::InputDeviceGamepad::Button::L1)
{
m_L1Pressed = true;
checkState = true;
}
else if (channelId == AzFramework::InputDeviceGamepad::Button::R1)
{
m_R1Pressed = true;
checkState = true;
}
else if (channelId == AzFramework::InputDeviceGamepad::Button::X)
{
if (m_changingState)
{
SetNextState();
}
}
//if(checkState&&m_L1Pressed&&m_L2Pressed&&m_R1Pressed&&m_R2Pressed)
if (checkState && m_L1Pressed && m_R1Pressed)
{
m_triggersDownStartTime = gEnv->pTimer->GetAsyncCurTime();
}
}
else if (inputChannel.IsStateUpdated())
{
float activateTime = (m_hudState == eHudOff) ? ACTIVATE_TIME_FROM_GAME : ACTIVATE_TIME_FROM_HUD;
if (m_triggersDownStartTime > 0.f && gEnv->pTimer->GetAsyncCurTime() - m_triggersDownStartTime > activateTime)
{
m_changingState = true;
const char* hudStateStr = NULL;
switch (m_hudState)
{
case eHudInFocus:
hudStateStr = "CryPerfHUD Edit Mode";
break;
case eHudOutOfFocus:
hudStateStr = "CryPerfHUD Game Mode";
break;
case eHudOff:
hudStateStr = "CryPerfHUD Off";
break;
default:
hudStateStr = "CryPerfHud unknown";
break;
}
float col[4] = {1.f, 1.f, 1.f, 1.f};
gEnv->pRenderer->Draw2dLabel(450.f, 200.f, 2.f, col, false, hudStateStr);
gEnv->pRenderer->Draw2dLabel(450.f, 220.f, 2.f, col, false, "Press X to change Mode");
}
}
else if (inputChannel.IsStateEnded())
{
bool triggerReleased = false;
if (channelId == AzFramework::InputDeviceGamepad::Button::L1)
{
m_L1Pressed = false;
triggerReleased = true;
}
else if (channelId == AzFramework::InputDeviceGamepad::Button::R1)
{
m_R1Pressed = false;
triggerReleased = true;
}
if (triggerReleased)
{
m_triggersDownStartTime = 0.f;
if (m_changingState)
{
m_changingState = false;
//workaround: hardware mouse resets all input states when enabled
//this breaks perfhud selection mode (as triggers are released)
//don't enable mouse until we've finished selection mode
if (m_hudState == eHudInFocus)
{
if (!m_hwMouseEnabled)
{
UiCursorBus::Broadcast(&UiCursorInterface::IncrementVisibleCounter);
m_hwMouseEnabled = true;
}
}
else if (m_hwMouseEnabled)
{
UiCursorBus::Broadcast(&UiCursorInterface::DecrementVisibleCounter);
m_hwMouseEnabled = false;
}
}
}
}
if (m_hudState == eHudInFocus)
{
//PerfHUD takes control of the input
return true;
}
}
return false;
}
void CPerfHUD::SetNextState()
{
if (m_sys_perfhud != eHudOff)
{
m_hudState = (EHudState)((m_hudState + 1) % eHudNumStates);
//CryLogAlways("Setting new HUD state: %d", m_hudState);
//what to do about cvar?
//ICVar *pPerfCVar = GetISystem()->GetIConsole()->GetCVar("sys_perfhud");
//pPerfCVar->Set( 1 );
}
}
void CPerfHUD::SetState(EHudState state)
{
if (state != m_hudState)
{
if (m_hwMouseEnabled)
{
if (state != eHudInFocus)
{
UiCursorBus::Broadcast(&UiCursorInterface::DecrementVisibleCounter);
m_hwMouseEnabled = false;
}
}
else if (state == eHudInFocus)
{
UiCursorBus::Broadcast(&UiCursorInterface::IncrementVisibleCounter);
m_hwMouseEnabled = true;
}
m_hudState = state;
}
}
void CPerfHUD::Reset()
{
IMiniGUIPtr pGUI;
if (CryCreateClassInstanceForInterface(cryiidof<IMiniGUI>(), pGUI))
{
pGUI->Reset();
}
}
//////////////////////////////////////////////////////////////////////////
// CLICK CALLBACKS
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::ResetCallback([[maybe_unused]] void* data, [[maybe_unused]] bool status)
{
ICryPerfHUDPtr pPerfHUD;
if (CryCreateClassInstanceForInterface(cryiidof<ICryPerfHUD>(), pPerfHUD))
{
pPerfHUD->Reset();
}
}
void CPerfHUD::ReloadBudgetsCallback([[maybe_unused]] void* data, [[maybe_unused]] bool status)
{
ICryPerfHUDPtr pPerfHUD;
if (CryCreateClassInstanceForInterface(cryiidof<ICryPerfHUD>(), pPerfHUD))
{
pPerfHUD->LoadBudgets();
}
}
void CPerfHUD::SaveStatsCallback([[maybe_unused]] void* data, [[maybe_unused]] bool status)
{
ICryPerfHUDPtr pPerfHUD;
if (CryCreateClassInstanceForInterface(cryiidof<ICryPerfHUD>(), pPerfHUD))
{
pPerfHUD->SaveStats();
}
}
//////////////////////////////////////////////////////////////////////////
// RENDER CALLBACKS
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::EnableWidget(ICryPerfHUDWidget::EWidgetID id, int mode)
{
const int nWidgets = m_widgets.size();
for (int i = 0; i < nWidgets; i++)
{
if (m_widgets[i]->m_id == id)
{
m_widgets[i]->Enable(mode);
return;
}
}
}
void CPerfHUD::DisableWidget(ICryPerfHUDWidget::EWidgetID id)
{
const int nWidgets = m_widgets.size();
for (int i = 0; i < nWidgets; i++)
{
if (m_widgets[i]->m_id == id)
{
m_widgets[i]->Disable();
return;
}
}
}
//////////////////////////////////////////////////////////////////////////
// Widget Specific Interface
//////////////////////////////////////////////////////////////////////////
void CPerfHUD::AddWarning(float duration, const char* fmt, va_list argList)
{
if (m_hudState != eHudOff)
{
//could cache warnings window ptr for efficiency
const int nWidgets = m_widgets.size();
for (int i = 0; i < nWidgets; i++)
{
if (m_widgets[i]->m_id == ICryPerfHUDWidget::eWidget_Warnings)
{
CWarningsWidget* warnings = (CWarningsWidget*)m_widgets[i].get();
if (warnings->ShouldUpdate())
{
warnings->AddWarningV(duration, fmt, argList);
break;
}
}
}
}
}
bool CPerfHUD::WarningsWindowEnabled() const
{
const int nWidgets = m_widgets.size();
for (int i = 0; i < nWidgets; i++)
{
if (m_widgets[i]->m_id == ICryPerfHUDWidget::eWidget_Warnings)
{
return m_widgets[i]->ShouldUpdate();
}
}
return false;
}
const std::vector<ICryPerfHUD::PerfBucket>* CPerfHUD::GetFpsBuckets(float& totalTime) const
{
const int nWidgets = m_widgets.size();
for (int i = 0; i < nWidgets; i++)
{
if (m_widgets[i]->m_id == ICryPerfHUDWidget::eWidget_FpsBuckets)
{
return ((CFpsWidget*)m_widgets[i].get())->GetFpsBuckets(totalTime);
}
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////
//FPS Buckets Widget
//////////////////////////////////////////////////////////////////////////
int CFpsWidget::m_cvarPerfHudFpsExclusive = 0;
CFpsWidget::CFpsWidget(IMiniCtrl* pParentMenu, ICryPerfHUD* pPerfHud)
: ICryPerfHUDWidget(eWidget_FpsBuckets)
{
m_fpsBucketSize = 5.f;
m_fpsBudget = 30.f;
m_dpBudget = 2500.f;
m_dpBucketSize = 250.f;
//FPS Buckets
IMiniCtrl* pFPSMenu = pPerfHud->CreateMenu("FPS", pParentMenu);
m_pInfoBox = pPerfHud->CreateInfoMenuItem(pFPSMenu, "FPS Buckets", NULL, Rect(850, 395, 860, 405));
pPerfHud->CreateCallbackMenuItem(pFPSMenu, "Reset Buckets", CFpsWidget::ResetCallback, this);
//Display framerates buckets as inclusive or exclusive
REGISTER_CVAR2("sys_perfhud_fpsBucketsExclusive", &m_cvarPerfHudFpsExclusive, 0, VF_CHEAT, "Toggle FPS Buckets exclusive / inclusive");
Init();
}
void CFpsWidget::LoadBudgets(XmlNodeRef PerfXML)
{
if (PerfXML)
{
XmlNodeRef xmlNode;
//FPS / GPU - explicit bucket values
if ((xmlNode = PerfXML->findChild("fpsBucketValues")))
{
m_perfBuckets[BUCKET_FPS].buckets.clear();
m_perfBuckets[BUCKET_GPU].buckets.clear();
uint32 nBuckets = xmlNode->getChildCount();
for (uint32 i = 0; i < nBuckets; i++)
{
XmlNodeRef bucketVal = xmlNode->getChild(i);
float target;
bucketVal->getAttr("value", target);
ICryPerfHUD::PerfBucket bucket(target);
m_perfBuckets[BUCKET_FPS].buckets.push_back(bucket);
m_perfBuckets[BUCKET_GPU].buckets.push_back(bucket);
}
m_perfBuckets[BUCKET_FPS].totalTime = 0.f;
m_perfBuckets[BUCKET_GPU].totalTime = 0.f;
}
//Auto generated buckets based on max fps and bucket size
else if ((xmlNode = PerfXML->findChild("fpsBucketMax")))
{
xmlNode->getAttr("value", m_fpsBudget);
if ((xmlNode = PerfXML->findChild("fpsBucketSize")))
{
xmlNode->getAttr("value", m_fpsBucketSize);
}
else
{
m_fpsBucketSize = 5;
}
m_perfBuckets[BUCKET_FPS].buckets.clear();
m_perfBuckets[BUCKET_GPU].buckets.clear();
float targetFPS = m_fpsBudget;
for (uint32 i = 0; i < NUM_FPS_BUCKETS_DEFAULT; i++)
{
ICryPerfHUD::PerfBucket bucket(targetFPS);
m_perfBuckets[BUCKET_FPS].buckets.push_back(bucket);
m_perfBuckets[BUCKET_GPU].buckets.push_back(bucket);
targetFPS -= m_fpsBucketSize;
}
m_perfBuckets[BUCKET_FPS].totalTime = 0.f;
m_perfBuckets[BUCKET_GPU].totalTime = 0.f;
}
//DP buckets - explicit bucket values
if ((xmlNode = PerfXML->findChild("dpBucketValues")))
{
m_perfBuckets[BUCKET_DP].buckets.clear();
uint32 nBuckets = xmlNode->getChildCount();
for (uint32 i = 0; i < nBuckets; i++)
{
XmlNodeRef bucketVal = xmlNode->getChild(i);
float target;
bucketVal->getAttr("value", target);
ICryPerfHUD::PerfBucket bucket(target);
m_perfBuckets[BUCKET_DP].buckets.push_back(bucket);
}
m_perfBuckets[BUCKET_DP].totalTime = 0.f;
}
//DPs auto values
else if ((xmlNode = PerfXML->findChild("drawPrimBucketMax")))
{
xmlNode->getAttr("value", m_dpBudget);
//Auto generated buckets based on max dp and bucket size
if ((xmlNode = PerfXML->findChild("dpBucketSize")))
{
xmlNode->getAttr("value", m_dpBucketSize);
}
else
{
m_dpBucketSize = 250.f;
}
m_perfBuckets[BUCKET_DP].buckets.clear();
float nDPs = m_dpBudget - ((NUM_FPS_BUCKETS_DEFAULT - 1) * m_dpBucketSize);
for (uint32 i = 0; i < NUM_FPS_BUCKETS_DEFAULT; i++)
{
ICryPerfHUD::PerfBucket bucket(nDPs);
m_perfBuckets[BUCKET_DP].buckets.push_back(bucket);
nDPs += m_dpBucketSize;
}
m_perfBuckets[BUCKET_DP].totalTime = 0.f;
}
}
}
template <bool LESS_THAN>
void CFpsWidget::UpdateBuckets(PerfBucketsStat& bucketStat, float frameTime, const char* name, float stat)
{
char entryBuffer[CMiniInfoBox::MAX_TEXT_LENGTH] = {0};
const uint32 numBuckets = bucketStat.buckets.size();
if (frameTime > 0.f)
{
bucketStat.totalTime += frameTime;
for (uint32 i = 0; i < numBuckets; i++)
{
if (LESS_THAN)
{
if (stat <= bucketStat.buckets[i].target)
{
bucketStat.buckets[i].timeAtTarget += frameTime;
}
}
else
{
if (stat >= bucketStat.buckets[i].target)
{
bucketStat.buckets[i].timeAtTarget += frameTime;
}
}
}
}
if (bucketStat.totalTime > 0.f)
{
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "%s: %.2f", name, stat);
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
//render exclusive stats
if (m_cvarPerfHudFpsExclusive)
{
for (uint32 i = 0; i < numBuckets; i++)
{
//inclusive time
float timeAtTarget = bucketStat.buckets[i].timeAtTarget;
if (i > 0)
{
//exclusive time
timeAtTarget -= bucketStat.buckets[i - 1].timeAtTarget;
//Add info to gui
float percentAtTarget = 100.f * (timeAtTarget / bucketStat.totalTime);
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "%.2f%%%% of time %.1f -> %.1f FPS", percentAtTarget, bucketStat.buckets[i].target, bucketStat.buckets[i - 1].target);
}
else
{
float percentAtTarget = 100.f * (bucketStat.buckets[i].timeAtTarget / bucketStat.totalTime);
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "%.2f%%%% of time >= %.1f FPS", percentAtTarget, bucketStat.buckets[i].target);
}
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
}
}
else //render inclusive stats
{
for (uint32 i = 0; i < numBuckets; i++)
{
float percentAtTarget = 100.f * (bucketStat.buckets[i].timeAtTarget / bucketStat.totalTime);
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "%.2f%%%% of time %s %.1f", percentAtTarget, LESS_THAN ? "<=" : ">=", bucketStat.buckets[i].target);
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
}
}
}
}
void CFpsWidget::Update()
{
m_pInfoBox->ClearEntries();
//
// FPS
//
{
float frameTime = gEnv->pTimer->GetRealFrameTime();
UpdateBuckets<false>(m_perfBuckets[BUCKET_FPS], frameTime, "FPS", 1.f / frameTime);
}
//
// GPU FPS
//
{
float gpuFrameTime = gEnv->pRenderer->GetGPUFrameTime();
if (gpuFrameTime > 0.f)
{
m_pInfoBox->AddEntry("", CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
UpdateBuckets<false>(m_perfBuckets[BUCKET_GPU], gpuFrameTime, "GPU FPS", 1.f / gpuFrameTime);
}
}
//
// Draw Call
//
{
//ugly, but buckets are float only at the moment
float nDPs = (float)gEnv->pRenderer->GetCurrentNumberOfDrawCalls();
m_pInfoBox->AddEntry("", CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
UpdateBuckets<true>(m_perfBuckets[BUCKET_DP], 1.f, "DPs", nDPs);
}
}
//Init buckets with default values
void CFpsWidget::Init()
{
float targetFPS = m_fpsBudget;
float nDPs = m_dpBudget - ((NUM_FPS_BUCKETS_DEFAULT - 1) * m_dpBucketSize);
for (uint32 i = 0; i < NUM_FPS_BUCKETS_DEFAULT; i++)
{
ICryPerfHUD::PerfBucket bucket(targetFPS);
m_perfBuckets[BUCKET_FPS].buckets.push_back(bucket);
m_perfBuckets[BUCKET_GPU].buckets.push_back(bucket);
bucket.target = (float)nDPs;
m_perfBuckets[BUCKET_DP].buckets.push_back(bucket);
targetFPS -= m_fpsBucketSize;
nDPs += m_dpBucketSize;
}
for (uint32 i = 0; i < BUCKET_TYPE_NUM; i++)
{
m_perfBuckets[i].totalTime = 0.f;
}
}
//Clear bucket totals
void CFpsWidget::Reset()
{
for (uint32 i = 0; i < BUCKET_TYPE_NUM; i++)
{
PerfBucketsStat& stat = m_perfBuckets[i];
const uint32 nBuckets = stat.buckets.size();
//Init fps buckets
for (uint32 j = 0; j < nBuckets; j++)
{
stat.buckets[j].timeAtTarget = 0.f;
}
stat.totalTime = 0.f;
}
}
void CFpsWidget::ResetCallback(void* data, [[maybe_unused]] bool status)
{
assert(data);
((CFpsWidget*)data)->Reset();
}
bool CFpsWidget::ShouldUpdate()
{
return !m_pInfoBox->IsHidden();
}
//TODO
void CFpsWidget::SaveStats(XmlNodeRef statsXML)
{
if (statsXML)
{
const char* perfBucketTypeStr[] =
{
"BUCKET_FPS",
"BUCKET_GPU",
"BUCKET_DP",
};
XmlNodeRef fpsNode;
for (int i = 0; i < BUCKET_TYPE_NUM; i++)
{
PerfBucketsStat& perfBucket = m_perfBuckets[i];
if (perfBucket.totalTime > 0.f)
{
if ((fpsNode = statsXML->newChild(perfBucketTypeStr[i])))
{
XmlNodeRef child;
const uint32 numBuckets = perfBucket.buckets.size();
for (uint32 j = 0; j < numBuckets; j++)
{
float percentAtTarget = 100.f * (perfBucket.buckets[j].timeAtTarget / perfBucket.totalTime);
if ((child = fpsNode->newChild("bucket")))
{
child->setAttr("target", perfBucket.buckets[j].target);
}
if ((child = fpsNode->newChild("percentAtTime")))
{
child->setAttr("value", percentAtTarget);
}
}
}
}
}
}
}
const std::vector<ICryPerfHUD::PerfBucket>* CFpsWidget::GetFpsBuckets(float& totalTime) const
{
totalTime = m_perfBuckets[BUCKET_FPS].totalTime;
return &m_perfBuckets[BUCKET_FPS].buckets;
}
//////////////////////////////////////////////////////////////////////////
//Render Stats Widget
//////////////////////////////////////////////////////////////////////////
CRenderStatsWidget::CRenderStatsWidget(IMiniCtrl* pParentMenu, ICryPerfHUD* pPerfHud)
: ICryPerfHUDWidget(eWidget_RenderStats)
{
m_pPerfHUD = pPerfHud;
m_fpsBudget = 30.f;
m_dpBudget = 2000;
m_polyBudget = 500000;
m_postEffectBudget = 3;
m_shadowCastBudget = 2;
m_particlesBudget = 1000;
m_pInfoBox = NULL;
m_buildNum = 0;
ZeroStruct(m_runtimeData);
m_pInfoBox = pPerfHud->CreateInfoMenuItem(pParentMenu, "Scene Summary", NULL, Rect(45, 350, 100, 400) /*, true*/);
//Get build number from BuildName.txt
//FILE *buildFile = fxopen("BuildName.txt", "r");
CCryFile buildFile;
if (buildFile.Open("./BuildName.txt", "rb", AZ::IO::IArchive::FOPEN_ONDISK | AZ::IO::IArchive::FOPEN_HINT_QUIET))
{
size_t fileSize = buildFile.GetLength();
if (fileSize > 0 && fileSize < 64)
{
char buffer[64] = {0};
buildFile.ReadRaw(buffer, fileSize);
if (buffer[0])
{
const char* ptr = strchr(buffer, '(');
if (ptr)
{
ptr++;
m_buildNum = atoi(ptr);
}
}
}
}
}
void CRenderStatsWidget::Update()
{
IRenderer* pRenderer = gEnv->pRenderer;
char entryBuffer[CMiniInfoBox::MAX_TEXT_LENGTH] = {0};
//Clear old entries
m_pInfoBox->ClearEntries();
ZeroStruct(m_runtimeData);
//
// FPS
//
m_runtimeData.fps = min(9999.f, gEnv->pTimer->GetFrameRate());
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "FPS: %.2f (%.2f)", m_runtimeData.fps, m_fpsBudget);
if (m_runtimeData.fps >= m_fpsBudget)
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
}
else
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_NORM);
CryPerfHUDWarning(1.f, "FPS Too Low: %.2f", m_runtimeData.fps);
//PerfHud / AuxRenderer causes us to be RenderThread limited
//Need to investigate vertex buffer locks in AuxRenderer before enabling
/*
// Fran: please call me before re-enabling this code
SRenderTimes renderTimes;
pRenderer->GetRenderTimes(renderTimes);
//wait for main is never 0
if(renderTimes.fWaitForMain>renderTimes.fWaitForRender && renderTimes.fWaitForMain>renderTimes.fWaitForGPU)
{
m_pInfoBox->AddEntry("Main Thread Limited", CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_ERROR);
m_pPerfHud->AddWarning("Main Thread Limited",1.f);
}
else if(renderTimes.fWaitForRender>renderTimes.fWaitForGPU)
{
m_pInfoBox->AddEntry("Render Thread Limited", CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_ERROR);
m_pPerfHud->AddWarning("Render Thread Limited",1.f);
}
else
{
m_pInfoBox->AddEntry("GPU Limited", CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_ERROR);
m_pPerfHud->AddWarning("GPU Limited",1.f);
}*/
}
//
// GPU Time
//
float gpuTime = pRenderer->GetGPUFrameTime();
if (gpuTime > 0.f)
{
float gpuFPS = 1.f / gpuTime;
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "GPU FPS: %.2f (%.2f)", gpuFPS, m_fpsBudget);
if (gpuFPS >= m_fpsBudget)
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
}
else
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_NORM);
CryPerfHUDWarning(1.f, "GPU FPS Too Low: %.2f", gpuFPS);
}
}
//
// HDR
//
bool bHDRModeEnabled = false;
pRenderer->EF_Query(EFQ_HDRModeEnabled, bHDRModeEnabled);
if (bHDRModeEnabled)
{
m_runtimeData.hdrEnabled = true;
if (!m_runtimeData.hdrEnabled)
{
CryPerfHUDWarning(1.f, "HDR Disabled");
}
}
if (!gEnv->IsEditor())
{
//
// Render Thread
//
static ICVar* pMultiThreaded = gEnv->pConsole->GetCVar("r_MultiThreaded");
if (pMultiThreaded && pMultiThreaded->GetIVal() > 0)
{
//sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "Render Thread Enabled");
//m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
m_runtimeData.renderThreadEnabled = true;
}
else
{
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "Render Thread Disabled");
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_NORM);
m_runtimeData.renderThreadEnabled = false;
CryPerfHUDWarning(1.f, "Render Thread Disabled");
}
}
//
// Camera
//
Matrix33 m = Matrix33(pRenderer->GetCamera().GetMatrix());
m_runtimeData.cameraRot = RAD2DEG(Ang3::GetAnglesXYZ(m));
m_runtimeData.cameraPos = pRenderer->GetCamera().GetPosition();
//
// Polys / Draw Prims
//
int nShadowVolPolys;
int nPolys;
pRenderer->GetPolyCount(nPolys, nShadowVolPolys);
m_runtimeData.nPolys = nPolys;
m_runtimeData.nDrawPrims = pRenderer->GetCurrentNumberOfDrawCalls();
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "Draw Calls: %d (%d)", m_runtimeData.nDrawPrims, m_dpBudget);
if (m_runtimeData.nDrawPrims <= m_dpBudget)
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
}
else
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_NORM);
CryPerfHUDWarning(1.f, "Too Many Draw Calls: %d", m_runtimeData.nDrawPrims);
}
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "Num Tris: %d (%d)", m_runtimeData.nPolys, m_polyBudget);
if (m_runtimeData.nPolys <= m_polyBudget)
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
}
else
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_NORM);
CryPerfHUDWarning(1.f, "Too Many Tris: %d", m_runtimeData.nPolys);
}
//
// Post Effects
//
gEnv->pRenderer->EF_Query(EFQ_NumActivePostEffects, m_runtimeData.nPostEffects);
sprintf_s(entryBuffer, CMiniInfoBox::MAX_TEXT_LENGTH, "Num Post Effects: %d (%d)", m_runtimeData.nPostEffects, m_postEffectBudget);
if (m_runtimeData.nPostEffects <= m_postEffectBudget)
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
}
else
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_NORM);
CryPerfHUDWarning(1.f, "Too Many Post Effects: %d", m_runtimeData.nPostEffects);
}
m_runtimeData.nFwdLights = 0;
m_runtimeData.nFwdShadowLights = 0;
//////////////////////////////////////////////////////////////////////////
if (gEnv->pRenderer)
{
STextureStreamingStats textureStats(true);
gEnv->pRenderer->EF_Query(EFQ_GetTexStreamingInfo, textureStats);
float fTexRequiredMB = (float)(textureStats.nRequiredStreamedTexturesSize) / (1024 * 1024);
sprintf_s(entryBuffer, "Textures Required: %.2f (%dMB)", fTexRequiredMB, azlossy_cast<int>(textureStats.nMaxPoolSize / (1024 * 1024)));
if (fTexRequiredMB < textureStats.nMaxPoolSize)
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_NORM, CPerfHUD::TEXT_SIZE_NORM);
}
else
{
m_pInfoBox->AddEntry(entryBuffer, CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_NORM);
CryPerfHUDWarning(1.f, "Too Many Textures: %.2fMB", fTexRequiredMB);
}
}
}
bool CRenderStatsWidget::ShouldUpdate()
{
if (!m_pInfoBox->IsHidden() ||
m_pPerfHUD->WarningsWindowEnabled())
{
return true;
}
return false;
}
void CRenderStatsWidget::LoadBudgets(XmlNodeRef perfXML)
{
if (perfXML)
{
XmlNodeRef xmlNode;
if ((xmlNode = perfXML->findChild("fps")))
{
xmlNode->getAttr("value", m_fpsBudget);
}
if ((xmlNode = perfXML->findChild("drawPrim")))
{
xmlNode->getAttr("value", m_dpBudget);
}
if ((xmlNode = perfXML->findChild("tris")))
{
xmlNode->getAttr("value", m_polyBudget);
}
if ((xmlNode = perfXML->findChild("postEffects")))
{
xmlNode->getAttr("value", m_postEffectBudget);
}
if ((xmlNode = perfXML->findChild("shadowCastingLights")))
{
xmlNode->getAttr("value", m_shadowCastBudget);
}
if ((xmlNode = perfXML->findChild("particles")))
{
xmlNode->getAttr("value", m_particlesBudget);
}
}
}
void CRenderStatsWidget::SaveStats(XmlNodeRef statsXML)
{
if (!ShouldUpdate())
{
//Force update of stats, widget may not be currently enabled
Update();
}
if (statsXML)
{
XmlNodeRef renderNode;
if ((renderNode = statsXML->newChild("RenderStats")))
{
XmlNodeRef child;
if ((child = renderNode->newChild("fps")))
{
child->setAttr("value", m_runtimeData.fps);
}
if ((child = renderNode->newChild("hdr")))
{
child->setAttr("value", m_runtimeData.hdrEnabled);
}
if ((child = renderNode->newChild("renderThread")))
{
child->setAttr("value", m_runtimeData.renderThreadEnabled);
}
if ((child = renderNode->newChild("cameraPos")))
{
child->setAttr("x", m_runtimeData.cameraPos.x);
child->setAttr("y", m_runtimeData.cameraPos.y);
child->setAttr("z", m_runtimeData.cameraPos.z);
}
if ((child = renderNode->newChild("cameraRot")))
{
child->setAttr("x", m_runtimeData.cameraRot.x);
child->setAttr("y", m_runtimeData.cameraRot.y);
child->setAttr("z", m_runtimeData.cameraRot.z);
}
if ((child = renderNode->newChild("drawPrims")))
{
child->setAttr("value", m_runtimeData.nDrawPrims);
}
if ((child = renderNode->newChild("numPolys")))
{
child->setAttr("value", m_runtimeData.nPolys);
}
if ((child = renderNode->newChild("numPostEffects")))
{
child->setAttr("value", m_runtimeData.nPostEffects);
}
if ((child = renderNode->newChild("numFwdLights")))
{
child->setAttr("value", m_runtimeData.nFwdLights);
}
if ((child = renderNode->newChild("numFwdShadowLights")))
{
child->setAttr("value", m_runtimeData.nFwdShadowLights);
}
if ((child = renderNode->newChild("numDefLights")))
{
child->setAttr("value", m_runtimeData.nDefLights);
}
if ((child = renderNode->newChild("numDefShadowLights")))
{
child->setAttr("value", m_runtimeData.nDefShadowLights);
}
if ((child = renderNode->newChild("numDefCubeMaps")))
{
child->setAttr("value", m_runtimeData.nDefCubeMaps);
}
if ((child = renderNode->newChild("numParticles")))
{
child->setAttr("value", m_runtimeData.nParticles);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
//Streaming Stats Widget
//////////////////////////////////////////////////////////////////////////
CStreamingStatsWidget::CStreamingStatsWidget(IMiniCtrl* pParentMenu, ICryPerfHUD* pPerfHud)
: ICryPerfHUDWidget(eWidget_StreamingStats)
{
m_pPerfHUD = pPerfHud;
m_pInfoBox = pPerfHud->CreateInfoMenuItem(pParentMenu, "Streaming", NULL, Rect(45, 200, 100, 300), true);
//m_maxMeshSizeArroundMB = 0;
//m_maxTextureSizeArroundMB = 0;
}
//////////////////////////////////////////////////////////////////////////
void CStreamingStatsWidget::Update()
{
}
//////////////////////////////////////////////////////////////////////////
bool CStreamingStatsWidget::ShouldUpdate()
{
if (!m_pInfoBox->IsHidden() ||
m_pPerfHUD->WarningsWindowEnabled())
{
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
void CStreamingStatsWidget::LoadBudgets(XmlNodeRef perfXML)
{
/*
if(perfXML)
{
XmlNodeRef xmlNode;
if( (xmlNode=perfXML->findChild("MeshSizeArround")) )
{
xmlNode->getAttr("value", m_maxMeshSizeArroundMB);
}
if( (xmlNode=perfXML->findChild("TextureSizeArround")) )
{
xmlNode->getAttr("value", m_maxTextureSizeArroundMB);
}
}
*/
}
//////////////////////////////////////////////////////////////////////////
//Warnings Widget
//////////////////////////////////////////////////////////////////////////
CWarningsWidget::CWarningsWidget(IMiniCtrl* pParentMenu, ICryPerfHUD* pPerfHud)
: ICryPerfHUDWidget(eWidget_Warnings)
{
m_pInfoBox = pPerfHud->CreateInfoMenuItem(pParentMenu, "Warnings", NULL, Rect(890, 150, 900, 200) /*, true*/);
m_nMainThreadId = CryGetCurrentThreadId();
}
void CWarningsWidget::Reset()
{
m_warnings.clear();
}
void CWarningsWidget::Update()
{
//must be Main thread for MT warnings to work
assert(CryGetCurrentThreadId() == m_nMainThreadId);
//Update from multithreaded queue
while (!m_threadWarnings.empty())
{
SWarning warning;
if (m_threadWarnings.try_pop(warning))
{
AddWarning(warning.remainingDuration, warning.text);
}
}
float frameTime = gEnv->pTimer->GetRealFrameTime();
//delete old warnings
// [K01]: fixing Linux crash
TSWarnings::iterator iter = m_warnings.begin();
while (iter != m_warnings.end())
{
SWarning* pW = &(*iter);
pW->remainingDuration -= frameTime;
if (pW->remainingDuration <= 0.f)
{
iter = m_warnings.erase(iter);
}
else
{
++iter;
}
}
int nWarnings = m_warnings.size();
m_pInfoBox->ClearEntries();
//display warnings
for (int i = 0; i < nWarnings; i++)
{
m_pInfoBox->AddEntry(m_warnings[i].text, CPerfHUD::COL_ERROR, CPerfHUD::TEXT_SIZE_WARN);
}
}
bool CWarningsWidget::ShouldUpdate()
{
return !m_pInfoBox->IsHidden();
}
void CWarningsWidget::SaveStats(XmlNodeRef statsXML)
{
if (statsXML)
{
const int nWarnings = m_warnings.size();
if (nWarnings > 0)
{
XmlNodeRef warningNode;
if ((warningNode = statsXML->newChild("warnings")))
{
XmlNodeRef child;
for (int i = 0; i < nWarnings; i++)
{
if ((child = warningNode->newChild("warning")))
{
child->setAttr("value", m_warnings[i].text);
}
}
}
}
}
}
void CWarningsWidget::AddWarningV(float duration, const char* fmt, va_list argList)
{
char warningText[WARNING_LENGTH];
int written = vsnprintf_s(warningText, WARNING_LENGTH, WARNING_LENGTH - 1, fmt, argList);
if (written == -1)
{
warningText[WARNING_LENGTH - 1] = '\0';
}
AddWarning(duration, warningText);
}
void CWarningsWidget::AddWarning(float duration, const char* warning)
{
if (CryGetCurrentThreadId() == m_nMainThreadId)
{
const size_t nWarnings = m_warnings.size();
bool bNewWarning = true;
ptrdiff_t nCompareLen = strlen(warning);
const char* s = strchr(warning, ':');
if (s)
{
nCompareLen = s - warning;
}
for (size_t i = 0; i < nWarnings; i++)
{
if (strncmp(m_warnings[i].text, warning, nCompareLen) == 0)
{
//warning already exists, update duration
m_warnings[i].remainingDuration = duration;
cry_strcpy(m_warnings[i].text, warning);
bNewWarning = false;
break;
}
}
if (bNewWarning)
{
SWarning newWarning;
newWarning.remainingDuration = duration;
cry_strcpy(newWarning.text, warning);
m_warnings.push_back(newWarning);
}
}
else
{
//add to thread safe queue, warning will be added next update
SWarning newWarning;
newWarning.remainingDuration = duration;
cry_strcpy(newWarning.text, warning);
m_threadWarnings.push(newWarning);
}
}
//////////////////////////////////////////////////////////////////////////
//Render Batch Stats Widget
//////////////////////////////////////////////////////////////////////////
CRenderBatchWidget::CRenderBatchWidget(IMiniCtrl* pParentMenu, ICryPerfHUD* pPerfHud)
: ICryPerfHUDWidget(eWidget_RenderBatchStats)
{
m_pTable = pPerfHud->CreateTableMenuItem(pParentMenu, "Render Batch Stats");
m_pRStatsCVar = GetISystem()->GetIConsole()->GetCVar("r_stats");
m_displayMode = DISPLAY_MODE_BATCH_STATS;
m_pTable->RemoveColumns();
m_pTable->AddColumn("Name");
m_pTable->AddColumn("DPs");
m_pTable->AddColumn("Instances");
m_pTable->AddColumn("ZPass");
m_pTable->AddColumn("Shadows");
m_pTable->AddColumn("General");
m_pTable->AddColumn("Transparent");
m_pTable->AddColumn("Misc");
}
void CRenderBatchWidget::Reset()
{
}
void CRenderBatchWidget::SaveStats(XmlNodeRef statsXML)
{
}
void CRenderBatchWidget::Enable(int mode)
{
mode = min(mode, DISPLAY_MODE_NUM - 1);
EDisplayMode newMode = (EDisplayMode)mode;
if (m_displayMode != newMode)
{
//workaround for now,
//since we poke renderer in order to gather stats
switch (m_displayMode)
{
case DISPLAY_MODE_BATCH_STATS:
gEnv->pRenderer->CollectDrawCallsInfo(false);
break;
case DISPLAY_MODE_GPU_TIMES:
m_pRStatsCVar->Set(0);
break;
}
switch (newMode)
{
case DISPLAY_MODE_BATCH_STATS:
m_pTable->RemoveColumns();
m_pTable->AddColumn("Name");
m_pTable->AddColumn("DPs");
m_pTable->AddColumn("Instances");
m_pTable->AddColumn("ZPass");
m_pTable->AddColumn("Shadows");
m_pTable->AddColumn("General");
m_pTable->AddColumn("Transparent");
m_pTable->AddColumn("Misc");
m_displayMode = newMode;
break;
case DISPLAY_MODE_GPU_TIMES:
m_pTable->RemoveColumns();
m_pTable->AddColumn("Name");
m_pTable->AddColumn("Num Batches");
m_pTable->AddColumn("Num Verts");
m_pTable->AddColumn("Num Tris");
m_displayMode = newMode;
break;
default:
CryLogAlways("[Render Batch Stats] Attempting to set incorrect display mode set: %d", mode);
break;
}
}
m_pTable->Hide(false);
}
void CRenderBatchWidget::Disable()
{
//ensure renderer is not doing unnecessary work
m_pRStatsCVar->Set(0);
gEnv->pRenderer->CollectDrawCallsInfo(false);
m_pTable->Hide(true);
}
bool CRenderBatchWidget::ShouldUpdate()
{
return !m_pTable->IsHidden();
}
void CRenderBatchWidget::Update()
{
switch (m_displayMode)
{
case DISPLAY_MODE_BATCH_STATS:
Update_ModeBatchStats();
break;
case DISPLAY_MODE_GPU_TIMES:
Update_ModeGpuTimes();
break;
default:
CryLogAlways("[Render Batch Stats]Incorrect Display mode set: %d", m_displayMode);
break;
}
}
void CRenderBatchWidget::Update_ModeBatchStats()
{
#if !defined(_RELEASE)
IRenderer* pRenderer = gEnv->pRenderer;
pRenderer->CollectDrawCallsInfo(true);
typedef std::map<string, BatchInfoPerPass> BatchMap;
typedef std::map<string, BatchInfoPerPass>::iterator BatchMapItor;
//sorted Map elements
std::vector<BatchInfoPerPass*> sortedBatchList;
BatchMap batchMap;
//mesh totals
static const char* strMeshTotals = "TOTAL (Mesh)";
BatchInfoPerPass totalsMesh;
totalsMesh.col.set(255, 255, 255, 255);
totalsMesh.name = strMeshTotals;
int nDPs = gEnv->pRenderer->GetCurrentNumberOfDrawCalls();
//scene totals
static const char* strSceneTotals = "TOTAL (Scene)";
BatchInfoPerPass totalsScene;
totalsScene.col.set(0, 255, 255, 255);
totalsScene.name = strSceneTotals;
totalsScene.nBatches = nDPs;
int unknownDPs = nDPs;
m_pTable->ClearTable();
IRenderer::RNDrawcallsMapMesh& drawCallsInfo = gEnv->pRenderer->GetDrawCallsInfoPerMesh();
IRenderer::RNDrawcallsMapMeshItor pEnd = drawCallsInfo.end();
IRenderer::RNDrawcallsMapMeshItor pItor = drawCallsInfo.begin();
//Per RenderNode Stats
for (; pItor != pEnd; ++pItor)
{
IRenderer::SDrawCallCountInfo& drawInfo = pItor->second;
uint32 nDrawcalls = drawInfo.nShadows + drawInfo.nZpass + drawInfo.nGeneral + drawInfo.nTransparent + drawInfo.nMisc;
const char* pRenderNodeName = drawInfo.meshName;
const char* pNameShort = strrchr(pRenderNodeName, '/');
if (pNameShort)
{
pRenderNodeName = pNameShort + 1;
}
BatchInfoPerPass batch;
batch.name = pRenderNodeName;
batch.nBatches = nDrawcalls;
batch.nInstances = 1;
batch.nZpass = drawInfo.nZpass;
batch.nShadows = drawInfo.nShadows;
batch.nGeneral = drawInfo.nGeneral;
batch.nTransparent = drawInfo.nTransparent;
batch.nMisc = drawInfo.nMisc;
BatchMapItor batchIter = batchMap.find(string(pRenderNodeName));
//already an entry for this stat obj - append details
if (batchIter != batchMap.end())
{
BatchInfoPerPass& currentBatch = batchIter->second;
currentBatch += batch;
}
else
{
//insert new entry into map
std::pair<BatchMapItor, bool> newElem = batchMap.insert(BatchMapItor::value_type(string(pRenderNodeName), batch));
BatchInfoPerPass& newBatch = newElem.first->second;
sortedBatchList.push_back(&newBatch);
}
totalsMesh += batch;
}
unknownDPs -= totalsMesh.nBatches;
//
// Unknown counts (sceneDP - sum of batches)
//
//could be -ve or +ve (-ve for conditional rendering)
if (unknownDPs != 0)
{
static const char* s_strUnknown = "Unknown";
static BatchInfoPerPass s_unknownBatch;
s_unknownBatch.Reset();
s_unknownBatch.name = s_strUnknown;
s_unknownBatch.nBatches = max(0, unknownDPs);
s_unknownBatch.col.set(255, 255, 0, 255);
sortedBatchList.push_back(&s_unknownBatch);
}
//Scene Totals
m_pTable->AddData(0, totalsScene.col, totalsScene.name);
m_pTable->AddData(1, totalsScene.col, "%d", totalsScene.nBatches);
m_pTable->AddData(2, totalsScene.col, "%d", totalsScene.nInstances);
m_pTable->AddData(3, totalsScene.col, "%d", totalsScene.nZpass);
m_pTable->AddData(4, totalsScene.col, "%d", totalsScene.nShadows);
m_pTable->AddData(5, totalsScene.col, "%d", totalsScene.nGeneral);
m_pTable->AddData(6, totalsScene.col, "%d", totalsScene.nTransparent);
m_pTable->AddData(7, totalsScene.col, "%d", totalsScene.nMisc);
//Mesh Totals
m_pTable->AddData(0, totalsMesh.col, totalsMesh.name);
m_pTable->AddData(1, totalsMesh.col, "%d", totalsMesh.nBatches);
m_pTable->AddData(2, totalsMesh.col, "%d", totalsMesh.nInstances);
m_pTable->AddData(3, totalsMesh.col, "%d", totalsMesh.nZpass);
m_pTable->AddData(4, totalsMesh.col, "%d", totalsMesh.nShadows);
m_pTable->AddData(5, totalsMesh.col, "%d", totalsMesh.nGeneral);
m_pTable->AddData(6, totalsMesh.col, "%d", totalsMesh.nTransparent);
m_pTable->AddData(7, totalsMesh.col, "%d", totalsMesh.nMisc);
if (int nBatches = sortedBatchList.size())
{
std::sort(sortedBatchList.begin(), sortedBatchList.end(), BatchInfoSortPerPass());
for (int i = 0; i < nBatches; i++)
{
BatchInfoPerPass* batch = sortedBatchList[i];
int nInstances = max(1, (int)batch->nInstances);
m_pTable->AddData(0, batch->col, batch->name);
//Due to different render meshes with the same name, averaged
//stats are a bit confusing, disabling for now
if constexpr (0 && batch->nInstances > 1)
{
m_pTable->AddData(1, batch->col, "%d (%d)", batch->nBatches, batch->nBatches / nInstances);
m_pTable->AddData(2, batch->col, "%d", batch->nInstances);
m_pTable->AddData(3, batch->col, "%d (%d)", batch->nZpass, batch->nZpass / nInstances);
m_pTable->AddData(4, batch->col, "%d (%d)", batch->nShadows, batch->nShadows / nInstances);
m_pTable->AddData(5, batch->col, "%d (%d)", batch->nGeneral, batch->nGeneral / nInstances);
m_pTable->AddData(6, batch->col, "%d (%d)", batch->nTransparent, batch->nTransparent / nInstances);
m_pTable->AddData(7, batch->col, "%d (%d)", batch->nMisc, batch->nMisc / nInstances);
}
else
{
m_pTable->AddData(1, batch->col, "%d", batch->nBatches);
m_pTable->AddData(2, batch->col, "%d", batch->nInstances);
m_pTable->AddData(3, batch->col, "%d", batch->nZpass);
m_pTable->AddData(4, batch->col, "%d", batch->nShadows);
m_pTable->AddData(5, batch->col, "%d", batch->nGeneral);
m_pTable->AddData(6, batch->col, "%d", batch->nTransparent);
m_pTable->AddData(7, batch->col, "%d", batch->nMisc);
}
}
}
#else
m_pTable->ClearTable();
m_pTable->AddData(0, ColorB(255, 0, 0, 255), "Not supported in Release builds");
#endif//RELEASE (DO_RENDERSTATS - not available in CrySystem)
}
void CRenderBatchWidget::Update_ModeGpuTimes()
{
m_pTable->ClearTable();
m_pTable->AddData(0, ColorB(255, 0, 0, 255), "Not supported for this platform");
}
#endif //USE_PERFHUD