2021SC@SDUSC
开源游戏引擎 Overload 代码模块分析 之 OvGame(五)—— Debug(下)FrameInfo & GameProfiler
本篇是 OvGame 的 Debug 的下篇,将探究其最后两个部分 FrameInfo & GameProfiler。想了解 Debug 的另一个部分请前往 Debug(上) 阅读。
另外,若想先大致了解该引擎各个大模块,可前往笔者这篇相关文章查看。
若想了解 OvGame 大纲,可前往笔者这篇文章。
#include <OvRendering/Core/Renderer.h> #include <OvWindowing/Window.h> #include <OvUI/Panels/PanelUndecorated.h> #include <OvUI/Widgets/Texts/TextColored.h>
该文件的头文件都来自 Overload 的其它模块,暂不详述。
该文件主要代码是 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 查看。另外,该类定义的函数已有注释,不多说;定义的变量分别是渲染器类、窗口类、有色文字类类数组。
该文件仅包含上文的 FrameInfo.h 头文件,所以直接看函数:
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 也已经解析过,请自行前往查看。
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() 设置对齐格式。
#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 的其它模块,暂不详述。
主要代码是 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 是可以包含其他小部件的小部件类。
该文件中又引入了更多的 Overload 其它模块的头文件,也暂不详述:
#include "OvGame/Debug/GameProfiler.h" #include <OvDebug/Utils/Logger.h> #include <OvUI/Widgets/Visual/Separator.h> #include <OvAnalytics/Profiling/ProfilerSpy.h>
在函数之前,还加入了下列的命名空间从而使代码简洁:
using namespace OvUI::Panels; using namespace OvUI::Widgets; using namespace OvUI::Types;
现在,先了解两个私有函数,都很简单,不作过多叙述:
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。显然,传入的参数,含义是操作完成度百分比,它将影响返回的颜色数据.。
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 类中,也有与上述两个函数同名同代码的函数,不可混淆。
接着,继续了解公有函数:
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 结构体对应;最后,依次设置上列设置好的部件激活,这样才能被绘制。
该函数比较长,我们一段一段探究:
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 部分。