2021SC@SDUSC
开源游戏引擎 Overload 代码模块分析 之 OvGame(五)—— Debug(下)FrameInfo & GameProfiler
目录
前言
本篇是 OvGame 的 Debug 的下篇,将探究其最后两个部分 FrameInfo & GameProfiler。想了解 Debug 的另一个部分请前往 Debug(上) 阅读。
另外,若想先大致了解该引擎各个大模块,可前往笔者这篇相关文章查看。
若想了解 OvGame 大纲,可前往笔者这篇文章。
分析
1、FrameInfo
1.1 FrameInfo.h
1.1.1 头文件
#include <OvRendering/Core/Renderer.h>
#include <OvWindowing/Window.h>
#include <OvUI/Panels/PanelUndecorated.h>
#include <OvUI/Widgets/Texts/TextColored.h>
该文件的头文件都来自 Overload 的其它模块,暂不详述。
1.1.2 主要代码
该文件主要代码是 FrameInfo 类,可以生成 Frame Information,的展示面板。定义如下:
class FrameInfo : public OvUI::Panels::PanelUndecorated
{
public:
/**
* Constructor
* @param p_renderer
* @param p_window
*/
FrameInfo(OvRendering::Core::Renderer& p_renderer, OvWindowing::Window& p_window);
/**
* Update the data
* @parma p_deltaTime
*/
void Update(float p_deltaTime);
private:
OvRendering::Core::Renderer& m_renderer;
OvWindowing::Window& m_window;
OvUI::Widgets::Texts::TextColored* m_frameInfo[3];
};
显然,该类同上篇文章探究的类一样,继承了 OvUI 的 PanelUndecorated 类,详情请读者前往 Utils(终)大纲及 FPSCounter & Debug(上)大纲及 DriverInfo 查看。另外,该类定义的函数已有注释,不多说;定义的变量分别是渲染器类、窗口类、有色文字类类数组。
1.2 FrameInfo.cpp
该文件仅包含上文的 FrameInfo.h 头文件,所以直接看函数:
FrameInfo() 函数
OvGame::Debug::FrameInfo::FrameInfo(OvRendering::Core::Renderer& p_renderer, OvWindowing::Window& p_window) :
m_renderer(p_renderer),
m_window(p_window)
{
m_defaultHorizontalAlignment = OvUI::Settings::EHorizontalAlignment::LEFT;
m_defaultVerticalAlignment = OvUI::Settings::EVerticalAlignment::BOTTOM;
m_defaultPosition.x = static_cast<float>(p_window.GetSize().first) - 10.f;
m_defaultPosition.y = static_cast<float>(p_window.GetSize().second) - 10.f;
m_frameInfo[0] = &CreateWidget<OvUI::Widgets::Texts::TextColored>("", OvUI::Types::Color::Yellow);
m_frameInfo[1] = &CreateWidget<OvUI::Widgets::Texts::TextColored>("", OvUI::Types::Color::Yellow);
m_frameInfo[2] = &CreateWidget<OvUI::Widgets::Texts::TextColored>("", OvUI::Types::Color::Yellow);
}
这是类的构造函数,首先,使用参数初始化表为渲染器和窗口赋值;其次,与上篇文章也一样,使用格式对齐枚举类,为父类 APanelTransformable 类的变量 m_defaultHorizontalAlignment 与 m_defaultVerticalAlignment 赋值,实现左对齐与底对齐;接着,同样是其父类 APanelTransformable 的变量 m_defaultPosition,用 m_window 的 GetSize() 获得窗口宽度、高度,设置面板的默认位置。
最后,为类数组 m_frameInfo 的三个对象都进行赋值,使用的是 CreateWidget() 函数,该函数在 Utils(终)大纲及 FPSCounter & Debug(上)大纲及 DriverInfo 也已经解析过,请自行前往查看。
Update() 函数
void OvGame::Debug::FrameInfo::Update(float p_deltaTime)
{
auto& frameInfo = m_renderer.GetFrameInfo();
m_frameInfo[0]->content = "Triangles: " + std::to_string(frameInfo.polyCount);
m_frameInfo[1]->content = "Batches: " + std::to_string(frameInfo.batchCount);
m_frameInfo[2]->content = "Instances: " + std::to_string(frameInfo.instanceCount);
SetPosition({ 10.0f , static_cast<float>(m_window.GetSize().second) - 10.f });
SetAlignment(OvUI::Settings::EHorizontalAlignment::LEFT, OvUI::Settings::EVerticalAlignment::BOTTOM);
}
首先,GetFrameInfo() 返回 OvRendering::Core 的 Renderer 类的结构体 FrameInfo,赋值 auto&(自判定类型)的 frameInfo;接着,m_frameInfo 的字符串变量 content(内容)依次分别赋值三角形个数、批次数、实例数,其中数据是直接读取结构体 FrameInfo 中的数据;然后,调用 APanelTransformable 父类的 SetPosition() 函数,设置面板的初始位置;最后,将左对齐方式和底对齐方式传入 APanelTransformable 类的函数 SetAlignment() 设置对齐格式。
2、GameProfiler
2.1 GameProfiler.h
2.1.1 头文件
#include <OvRendering/Core/Renderer.h>
#include <OvWindowing/Window.h>
#include <OvAnalytics/Profiling/Profiler.h>
#include <OvUI/Panels/PanelUndecorated.h>
#include <OvUI/Widgets/Texts/TextColored.h>
#include <OvUI/Widgets/Layout/Group.h>
#include <OvUI/Widgets/Buttons/Button.h>
该文件的头文件都来自 Overload 的其它模块,暂不详述。
2.1.2 主要代码
主要代码是 GameProfiler 类,可以生成 profiling information,配置文件信息,的面板。定义比较长,先看其中函数的定义,如下:
class GameProfiler : public OvUI::Panels::PanelUndecorated
{
public:
/**
* Constructor
* @param p_window
* @param p_frequency
*/
GameProfiler(OvWindowing::Window& p_window, float p_frequency);
/**
* Update the data
* @param p_deltaTime
*/
void Update(float p_deltaTime);
private:
OvUI::Types::Color CalculateActionColor(double p_percentage) const;
std::string GenerateActionString(OvAnalytics::Profiling::ProfilerReport::Action& p_action);
该类包括了两个公有函数和两个私有函数,公有函数有注释,不多说;私有函数具体定义在 GameProfiler.cpp 中,暂不叙述。
private:
float m_frequency;
float m_timer = 0.f;
OvAnalytics::Profiling::Profiler m_profiler;
OvWindowing::Window& m_window;
OvUI::Widgets::AWidget* m_separator;
OvUI::Widgets::Texts::TextColored* m_elapsedFramesText;
OvUI::Widgets::Texts::TextColored* m_elapsedTimeText;
OvUI::Widgets::Layout::Group* m_actionList;
};
该类声明了多个私有变量,大多数之前的文章都有了解过。其中,m_frequency 用于记录时间周期;m_timer 用于记录累计经过的时间;Profiler 是一个配置文件类,收集了正在运行程序的相关数据;Window 是窗口类;AWidget 是部件类;TextColored 是有色文本类;Group 是可以包含其他小部件的小部件类。
2.2 GameProfiler.cpp
2.2.1 头文件
该文件中又引入了更多的 Overload 其它模块的头文件,也暂不详述:
#include "OvGame/Debug/GameProfiler.h"
#include <OvDebug/Utils/Logger.h>
#include <OvUI/Widgets/Visual/Separator.h>
#include <OvAnalytics/Profiling/ProfilerSpy.h>
2.2.2 主要代码
在函数之前,还加入了下列的命名空间从而使代码简洁:
using namespace OvUI::Panels;
using namespace OvUI::Widgets;
using namespace OvUI::Types;
现在,先了解两个私有函数,都很简单,不作过多叙述:
CalculateActionColor() 函数
OvUI::Types::Color OvGame::Debug::GameProfiler::CalculateActionColor(double p_percentage) const
{
if (p_percentage <= 25.0f) return { 0.0f, 1.0f, 0.0f, 1.0f };
else if (p_percentage <= 50.0f) return { 1.0f, 1.0f, 0.0f, 1.0f };
else if (p_percentage <= 75.0f) return { 1.0f, 0.6f, 0.0f, 1.0f };
else return { 1.0f, 0.0f, 0.0f, 1.0f };
}
该函数负责计算操作的颜色,类型是颜色结构体 Color。显然,传入的参数,含义是操作完成度百分比,它将影响返回的颜色数据.。
GenerateActionString() 函数
std::string OvGame::Debug::GameProfiler::GenerateActionString(OvAnalytics::Profiling::ProfilerReport::Action & p_action)
{
std::string result;
result += "[" + p_action.name + "]";
result += std::to_string(p_action.duration) + "s (total) | ";
result += std::to_string(p_action.duration / p_action.calls) + "s (per call) | ";
result += std::to_string(p_action.percentage) + "%% | ";
result += std::to_string(p_action.calls) + " calls";
return result;
}
该函数的类型是 string,传入的是结构体 ProfilerReport,含义为被调用方法的操作;返回的是结构体内的名字、持续时间等信息组合成的字符串信息。
简单一提,在 OvEditor::Panels::Profiler 类中,也有与上述两个函数同名同代码的函数,不可混淆。
接着,继续了解公有函数:
GameProfiler() 函数
OvGame::Debug::GameProfiler::GameProfiler(OvWindowing::Window& p_window, float p_frequency) : m_frequency(p_frequency), m_window(p_window)
{
m_defaultHorizontalAlignment = OvUI::Settings::EHorizontalAlignment::LEFT;
m_defaultPosition = { 10.0f, 10.0f };
CreateWidget<Texts::Text>("Profiler state: ").lineBreak = true;
m_elapsedFramesText = &CreateWidget<Texts::TextColored>("", Color(1.f, 0.8f, 0.01f, 1));
m_elapsedTimeText = &CreateWidget<Texts::TextColored>("", Color(1.f, 0.8f, 0.01f, 1));
m_separator = &CreateWidget<OvUI::Widgets::Visual::Separator>();
m_actionList = &CreateWidget<Layout::Group>();
m_actionList->CreateWidget<Texts::Text>("Action | Total duration | Frame Duration | Frame load | Total calls");
m_profiler.Enable();
m_elapsedFramesText->enabled = true;
m_elapsedTimeText->enabled = true;
m_separator->enabled = true;
}
该函数是构造函数,首先,参数初始化表初始化频率、窗口;接着,同上文一样,设置左对齐和面板默认位置,CreateWidget() 函数创建文本面板来显示文件数据,并设置 lineBreak = true,即允许换行;然后,依次用 CreateWidget() 设置四个私有变量;而后,m_actionList 在赋值后又调用了 CreateWidget() 设置其中的文字,含义与上文提及 ProfilerReport 结构体对应;最后,依次设置上列设置好的部件激活,这样才能被绘制。
Update() 函数
该函数比较长,我们一段一段探究:
void OvGame::Debug::GameProfiler::Update(float p_deltaTime)
{
PROFILER_SPY("Game Profiler Update");
m_position = { 10.0f, static_cast<float>(m_window.GetSize().second / 2) };
m_timer += p_deltaTime;
首先,使用 PROFILER_SPY,笔者之前的文章已经提及过,此处是配置文件更新;然后设置面板位置与时间。
接下来,判断 m_profiler 是否激活(正如构造函数中提及的):
if (m_profiler.IsEnabled())
{
m_profiler.Update(p_deltaTime);
通过后,先调用 OvEditor::Core::Editor::Update() 进行一系列的更新,包括全局快捷键、当前编辑器模式等等;然后,判断如果记录的时间超过设定的周期时间,那么进入 while 循环:
while (m_timer >= m_frequency)
{
OvAnalytics::Profiling::ProfilerReport report = m_profiler.GenerateReport();
m_profiler.ClearHistory();
m_actionList->RemoveAllWidgets();
m_elapsedFramesText->content = "Elapsed frames: " + std::to_string(report.elapsedFrames);
m_elapsedTimeText->content = "Elapsed time: " + std::to_string(report.elaspedTime);
m_actionList->CreateWidget<Texts::Text>("Action | Total duration | Frame Duration | Frame load | Total calls");
首先,m_profiler 调用 GenerateReport() 返回包含硬件信息的 HardwareReport 结构体,而后 m_profiler 调用 ClearHistory() 清空所有收集数据的记录,操作列表 m_actionList 调用 RemoveAllWidgets() 移除所有部件;接着,和构造函数一样设置两个文本和操作列表的文本;然后还没有退出 while 循环,继续进入下面的 for 循环:
for (auto& action : report.actions)
{
auto color = CalculateActionColor(action.percentage);
m_actionList->CreateWidget<Texts::TextColored>(GenerateActionString(action), color);
}
m_timer -= m_frequency;
}
}
}
该循环的长度取决于 report 的 vector 容器 actions,循环内依次对 actions 的成员操作:先调用上文的 CalculateActionColor() 得到该操作的进度对应的颜色,再用 CreateWidget() 创造有色文本,内容即上文的 GenerateActionString() 转换后的该操作信息的字符串。
操作完所有的 action 后退出 for 循环,将 m_timer 扣除这一个周期的时间;如果扣除后 m_timer 小于单位周期时间了,才退出 while 循环,结束函数。
总结
本篇解析了 OvGame 的 Debug 的最后两部分,Debug 文件的所有内容就解析完毕了。总体看来,Debug 就是一些信息的展示操作,并不复杂。
现在,我们已经了解完了 OvGame 的 Utils 和 Debug 两块文件,就可以继续停更的 Core 文件的后续内容了。因此下一篇,我们将可以继续了解 OvGame 之 Core 文件的 Game 部分。