<2021SC@SDUSC> 开源游戏引擎 Overload 代码模块分析 之 OvGame(五)—— Debug(下)FrameInfo & GameProfiler

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 部分

<2021SC@SDUSC> 开源游戏引擎 Overload 代码模块分析 之 OvGame(五)—— Debug(下)FrameInfo & GameProfiler

上一篇:Linux字符设备驱动学习(4)——内核调试


下一篇:NOIP 模拟赛 day5 T2 水 故事题解