分批处理的应用
批处理是不是真的有性能优化呢?
这章我们绘制更多的图元,对比批渲染和多次渲染的效果。
最后实现如下图所示的效果,在上一章的基础上更加了很多小矩形,并增加了统计数据的显示。
增加分批 & 统计
代码修改不多,最核心的修改是,根据MaxQuads自动刷新,超过上限就触发一次绘制。
Hazel/src/Hazel/Renderer/Renderer2D.h
c++
// Stats
struct Statistics
{
uint32_t DrawCalls = 0;
uint32_t QuadCount = 0;
uint32_t GetTotalVertexCount() { return QuadCount * 4; }
uint32_t GetTotalIndexCount() { return QuadCount * 6; }
};
// 每帧绘制结束重置统计数据
static void ResetStats();
static Statistics GetStats();
private:
static void FlushAndReset();
Sandbox/src/Sandbox2D.cpp
c++
struct Renderer2DData
{
static const uint32_t MaxQuads = 10000;
static const uint32_t MaxVertices = MaxQuads * 4;
static const uint32_t MaxIndices = MaxQuads * 6;
...
// 增加统计数据
Renderer2D::Statistics Stats;
}
void Renderer2D::Flush()
{
...
// 刷新结束增加DrawCall统计
s_Data->Stats.DrawCalls++;
}
// FlushAndReset实现,刷新后重置s_Data,新启一个批次
void Renderer2D::FlushAndReset() {
EndScene();
s_Data->QuadIndexCount = 0;
s_Data->QuadVertexBufferPtr = s_Data->QuadVertexBufferBase;
s_Data->TextureSlotIndex = 1;
}
// DrawQuad中累计数据超过上限则触发刷新和重置
// 其他DrawQuad均有同样的代码逻辑,此处仅展示一个DrawQuad的代码变化,完整代码参考文末github链接
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color) {
HZ_PROFILE_FUNCTION();
// s_Data中累计的数据超过单批绘制的上限,则触发刷新和重置
if (s_Data->QuadIndexCount >= Renderer2DData::MaxIndices) {
FlushAndReset();
}
...
// DrawQuad结束,累计QuadCount值
s_Data->Stats.QuadCount++;
}
// 重置统计数据
void Renderer2D::ResetStats() {
// 注意这里哟花姑娘memset的方式重置数据为0
memset(&s_Data->Stats, 0, sizeof(Statistics));
}
// 获取统计数据
Renderer2D::Statistics Renderer2D::GetStats() {
return s_Data->Stats;
}
Demo工程中的修改
Sandbox/src/Sandbox2D.cpp
c++
void Sandbox2D::OnUpdate(Hazel::Timestep ts) {
// Update
m_CameraController.OnUpdate(ts);
// 每帧重置统计数据
Hazel::Renderer2D::ResetStats();
...
// 增加测试数据
// 绘制10 * 10的矩形彩色棋盘
Hazel::Renderer2D::BeginScene(m_CameraController.GetCamera());
for(float y = -5.0f; y < 5.0f; y += 0.5f) {
for (float x = -5.0f; x < 5.0f; x += 0.5f) {
glm::vec4 color = {(x+5.0f)/10.0f, 0.4f, (y+5.0f)/10.0f, 0.7f};
Hazel::Renderer2D::DrawQuad({x, y}, {0.45f, 0.45f}, color);
}
}
Hazel::Renderer2D::EndScene();
}
// ImGui实现统计面板
void Sandbox2D::OnImGuiRender() {
HZ_PROFILE_FUNCTION();
ImGui::Begin("Settings");
auto stats = Hazel::Renderer2D::GetStats();
ImGui::Text("Renderer2D Stats:");
ImGui::Text("Draw Calls : %d", stats.DrawCalls);
ImGui::Text("Quads : %d", stats.QuadCount);
ImGui::Text("Vertices : %d", stats.GetTotalVertexCount());
ImGui::Text("Indices : %d", stats.GetTotalIndexCount());
ImGui::ColorEdit4("Square Color", glm::value_ptr(m_SquareColor));
ImGui::End();
}
代码运行正常的话,会看到文章开头的画面。接下来我们简单测试下性能
性能测试
修改MaxQuads值观察是否卡顿
- MaxQuads默认是10000,很流畅
c++
static const uint32_t MaxQuads = 10000;
- MaxQuads改成1,有明显的卡顿
c++
static const uint32_t MaxQuads = 1;
可见批渲染确实能优化性能。这里我们只是粗略的测试和统计。有兴趣的读者可以实现帧率的实时显示,增加滑动条动态调整网格密度以观察帧率的变化。
完整代码&总结
完整代码
总结
批处理在实际应用中是很常见的优化,作为基础属性,在常见的游戏引擎中都有很好的支持。