前言
性能耗时分析是工程中非常重要的一块,所有的技术深入到后面都是在扣性能。
这节我们先暂停引擎核心功能的开发,做一个简单的耗时统计。用到了c++11的chrono库。
耗时统计(Profile)
删掉无用的代码
暂时我们不需要显示imGui的Demo,影响美观,先删掉。
Sandbox/Hazel/src/Hazel/ImGui/ImGuiLayer.h
c++
...
virtual void OnAttach() override;
virtual void OnDetach() override;
//virtual void OnImGuiRender() override;
void Begin();
void End();
Sandbox/Hazel/src/Hazel/ImGui/ImGuiLayer.cpp
c++
...
// void ImGuiLayer::OnImGuiRender() {
// static bool show = true;
// ImGui::ShowDemoWindow(&show);
// }
ImGuiLayer::ImGuiLayer() : Layer("ImGuiLayer"){}
}
性能统计实现
Sandbox2D中增加ProfileResult,记录耗时,用std::vector存储。
Sandbox/src/Sandbox2D.h
c++
class Sandbox2D : public Hazel::Layer {
public:
...
struct ProfileResult
{
const char* Name;
float Time;
};
std::vector<ProfileResult> m_ProfileResults;
glm::vec4 m_SquareColor = {0.2f, 0.3f, 0.8f, 1.0f};
};
Sandbox/src/Sandbox2D.cpp 增加一个自动记录起始时间的工具类Timer,构造时作为起始时间,释放时在虚函数中记录end时间。
c++
template<typename Fn>
class Timer
{
public:
Timer(const char* name, Fn&& func) : m_Name(name), m_Func(func), m_Stopped(false)
{
// 构造时,记录start时间
m_StartTimepoint = std::chrono::high_resolution_clock::now();
}
~Timer()
{
// 释放时,记录end时间
if(!m_Stopped) {
Stop();
}
}
void Stop() {
// 获取end时间
auto endTimPoint = std::chrono::high_resolution_clock::now();
// start时间转成低精度的微妙microseconds
// time_since_epoch从相对的开始时间到现在经过了多少时间(chrono底层用的可能是system时间,或者是某个其他的时钟)
// .count转成数量值,计算机最小时间刻度微秒,过了几个微秒就是count返回的值
long long start = std::chrono::time_point_cast<std::chrono::microseconds>(m_StartTimepoint).time_since_epoch().count();
// end时间返回的count值
long long end = std::chrono::time_point_cast<std::chrono::microseconds>(endTimPoint).time_since_epoch().count();
m_Stopped = true;
// *0.001转成毫秒值
float duration = (end - start) * 0.001f;
// 将统计名称、时长回调回去
m_Func({m_Name, duration});
}
private:
const char* m_Name;
Fn m_Func;
std::chrono::time_point<std::chrono::steady_clock> m_StartTimepoint;
bool m_Stopped;
};
分别记录Update和Render阶段的耗时,这两步也是引擎管线中最重要的两步,一个更新场景状态,一个渲染。
c++
#include <chrono>
...
// 设计一个宏定义来构造Timer,代码更整洁
// __LINE__表示行号
#define PROFILE_SCOPE(name) Timer timer##__LINE__(name, [&](ProfileResult profileResult) { \
m_ProfileResults.push_back(profileResult); })
...
void Sandbox2D::OnUpdate(Hazel::Timestep ts) {
// Update 第一个耗时统计
{
PROFILE_SCOPE("Sandbox2D::OnUpdate");
m_CameraController.OnUpdate(ts);
}
// Render 第二个耗时统计
{
PROFILE_SCOPE("Renderer Draw");
Hazel::RenderCommand::SetClearColor({0.1f, 0.1f, 0.1f, 1.0});
Hazel::RenderCommand::Clear();
...
}
}
// OnImGuiRender中将update中记录的耗时数据绘制出来
void Sandbox2D::OnImGuiRender() {
ImGui::Begin("Settings");
ImGui::ColorEdit4("Square Color", glm::value_ptr(m_SquareColor));
// 遍历统计的数据,每一条绘制成一个ImGui::Text
for (auto& result : m_ProfileResults) {
char label[50];
strcpy(label, "%.3fms "); // %.3f表示长度为3的float型
strcat(label, result.Name);
ImGui::Text(label, result.Time);
}
// 每次都需要清理
m_ProfileResults.clear();
ImGui::End();
}
代码运行正常的话,能看到界面中出现统计的数据,实时更新
代码 & 总结
总结:
本章节没有任何游戏、图形的知识,涉及c++开发中很常用的两种编程思路。
- 将Timer设计成通用的工具。
通过回调,把不同的逻辑抽离出去,这个Timer就是通用的。
- 自动计时。
将起止时间的记录操作放到构造函数和虚函数中,然后将调用的逻辑放到代码块中,充分利用构造函数和虚函数。这种思路在C++中很常见,简单高效。
另外,用到了std::chrono库,不熟悉的同学可以查查API熟悉下。