1、点击面板上的图标,触发事件 on_timeDraws_clicked()
GPUCounter 这个结构体中会存储很多变量,其中的EventGPUDuration 变量就是存放的duration的值。
【定义】GPUCounter:: EventGPUDuration —— 用于表示:此事件在 GPU 上花费的时间,由两个 GPU 时间戳之间的增量衡量。
// EventBrowser.cpp
void EventBrowser::on_timeDraws_clicked()
{
ANALYTIC_SET(UIFeatures.DrawcallTimes, true);
ui->events->header()->showSection(COL_DURATION);
m_Ctx.Replay().AsyncInvoke([this](IReplayController *r) {
// 调用FetchCounters函数来获取Duration
m_Times = r->FetchCounters({GPUCounter::EventGPUDuration});
GUIInvoke::call(this, [this]() {
if(ui->events->topLevelItemCount() == 0)
return;
// 根据m_Times中存放的结果,以及该时间对应的event 分别设置其变量。树状结构,子节点的时间和为父节点的值。
SetDrawcallTimes(ui->events->topLevelItem(0), m_Times);
ui->events->update();
});
});
}
2、深入查看如何计算出m_Times的值。跳转到文件 replay_controller.cpp 中的FetchCounters
rdcarray<CounterResult> ReplayController::FetchCounters(const rdcarray<GPUCounter> &counters)
{
CHECK_REPLAY_THREAD();
RENDERDOC_PROFILEFUNCTION();
// 根据当前获取到的设备信息和状态,来调用不同的函数FetchCounters
// 此处用到了虚函数,动态多态。
return m_pDevice->FetchCounters(counters);
}
renderdoc\driver\
文件夹下有
- gl_counters.cpp
- d3d11_counters.cpp
- d3d12_counters.cpp
- vk_counters.cpp
手游用到的API主要是OpenGL ES,因此跳转到gl_counters.cpp
,重点查看其中的逻辑。
3、查看函数rdcarray<CounterResult> GLReplay::FetchCounters(const rdcarray<GPUCounter> &allCounters)
重点关注以下几个地方:
使用ctx存储结果。其类型为GLCounterContext
:
struct GPUQueries
{
GLuint obj[arraydim<GPUCounter>()];
uint32_t eventId;
};
struct GLCounterContext
{
uint32_t eventStart;
rdcarray<GPUQueries> queries;
};
函数FillTimers(ctx, m_pDriver->GetRootDraw(), counters);
作用
填充并返回ctx。
筛选出绘制类的api,以及其对应的eventId。
标记两个时间戳,并在这两个时间戳之间进行绘制重播。
// Reverse order so that Timer counter is queried the last.
for(int32_t q = uint32_t(GPUCounter::Count) - 1; q >= 0; q--){
if(queries->obj[q])
// 标记开始
m_pDriver->glBeginQuery(glCounters[q], queries->obj[q]);
}
// 根据eventId进行绘制,重播
m_pDriver->ReplayLog(ctx.eventStart, d.eventId, eReplay_OnlyDraw);
for(auto q : indices<GPUCounter>())
if(queries->obj[q])
// 标记结束
m_pDriver->glEndQuery(glCounters[q]);
函数m_pDriver->glGetQueryObjectui64v(ctx.queries[i].obj[(uint32_t)counters[c]], eGL_QUERY_RESULT, &data);
将所需要的值存放进变量rdcarray<CounterResult> ret
中,并返回该值。
渲染时间,或者说是调用API绘制所需的时间。
可以通过这些信息来确定渲染的元素的开销占比。对于开发时调试代码的性能很有帮助。通过这种方式找到代码中最耗时的部分,以便于能够知道在哪些地方需要进行优化。
举个例子,可以通过object_time来决定是否需要在你的场景中增加或者减少物体。可以基于应用程序运行时所使用的图形硬件来决定使用更复杂的shader还是更简单的shader来渲染物体。
大体分为两种方法:
1)glGetQueryObjectuiv (renderdoc使用的是这种方式)
调用glGetQueryObjectuiv获取 query object的结果的时候,返回值是从调用glBeginQuery开始到调用glEndQuery之间所有的OpenGL指令耗费的时间,单位是纳秒。
// Declare our variables
GLuint queries[3]; // Three query objects that we'll use
GLuint world_time; // Time taken to draw the world
GLuint objects_time; // Time taken to draw objects in the world
GLuint HUD_time; // Time to draw the HUD and other UI elements
// Create three query objects
glGenQueries(3, queries);
glBeginQuery(GL_TIME_ELAPSED, queries[0]);
RenderWorld();
glEndQuery(GL_TIME_ELAPSED);
glBeginQuery(GL_TIME_ELAPSED, queries[1]);
RenderObjects();
glEndQuery(GL_TIME_ELAPSED);
glBeginQuery(GL_TIME_ELAPSED, queries[2]);
RenderHUD();
glEndQuery(GL_TIME_ELAPSED);
// world_time,objects_time和HUD_time这三个变量就分别存储着渲染world,所有的objects和HUD所耗费的时间。
glGetQueryObjectuiv(queries[0], GL_QUERY_RESULT, &world_time);
glGetQueryObjectuiv(queries[1], GL_QUERY_RESULT, &objects_time);
glGetQueryObjectuiv(queries[2], GL_QUERY_RESULT, &HUD_time);
// Clean up after ourselves.
glDeleteQueries(3, queries);
2)void glQueryCounter(GLuint id, GLenum target);
需要设置id参数为GL_TIMESTAMP,然后把target参数设置成为你之前创建的query object。这个函数会直接把查询操作推送进OpenGL的流水线中去,当OpenGL执行到那个query object的结束位置的时候,OpenGL会把当前的 时间写到query object里面去。
// Declare our variables
GLuint queries[4]; // Now we need four query objects
GLuint start_time; // The start time of the application
GLuint world_time; // Time taken to draw the world
GLuint objects_time; // Time taken to draw objects in the world
GLuint HUD_time; // Time to draw the HUD and other UI elements
// Create four query objects
glGenQueries(4, queries);// Get the start time
glQueryCounter(GL_TIMESTAMP, queries[0]);
// Render the world
RenderWorld();
// Get the time after RenderWorld is done
glQueryCounter(GL_TIMESTAMP, queries[1]);
// Render the objects in the world
RenderObjects();
// Get the time after RenderObjects is done
glQueryCounter(GL_TIMESTAMP, queries[2]);
// Render the HUD
RenderHUD();
// Get the time after everything is done
glQueryCounter(GL_TIMESTAMP, queries[3]);
// Get the result from the three queries, and subtract them to find deltas
glGetQueryObjectuiv(queries[0], GL_QUERY_RESULT, &start_time);
glGetQueryObjectuiv(queries[1], GL_QUERY_RESULT, &world_time);
glGetQueryObjectuiv(queries[2], GL_QUERY_RESULT, &objects_time);
glGetQueryObjectuiv(queries[3], GL_QUERY_RESULT, &HUD_time);
// 区别:需要计算出来时间差
HUD_time -= objects_time;
objects_time -= world_time;
world_time -= start_time;
// Done. world_time, objects_time, and hud_time contain the values we want.
// Clean up after ourselves.
glDeleteQueries(4, queries);
不过这两个例子的结果并不完全等同。
使用GL_TIMESTAMP的方式进行查询:当OpenGL流水线执行到了query object结束位置的时候,time变量才会被写入数值。
使用GL_TIME_ELAPSED的方式进行查询:OpenGL会计算出来glEndQuery指令执行结束时候的时间戳与glBeginQuery执行指令开始时的时间戳的时间差。
很明显,结果不会是一样的。但无论采用哪种方式,拿到的时间数据都有意义。
3)优化API: void glGetQueryObjectui64v(GLuint id,GLenum pname,GLuint64 * params);
关于timer queries的一个值得注意的事就是,由于它们的计量单位是纳秒,所以它们的数值可能会非常大。如果用一个无符号的32位整数来表示纳秒数据的话,那么它顶多能玩转4秒。所以如果你的某个操作出乎预料的执行时间 超过了4秒,那么就完了。所以应该使用64位的整数来获取和存储query object的数据。
4)也可从OpenGL通过下述API来立即采用同步的方式获取一个时间戳。
GLint64 t;
// 当这代码执行完毕后,t将会保存OpenGL返回的一个当前的时间。
void glGetInteger64v(GL_TIMESTAMP, &t);
如果你先获取这个时间戳,然后立刻直接执行一个时间戳的查询,获得时间戳查询的结果后用它减掉t;这个结果将是OpenGL执行到查询操作结束位置时所花费的 时间。这也就是我们所知道的OpenGL流水线的延迟,这个延迟基本上等同于从你的应用程序调用某个指令开始,到OpenGL完全执行完毕这个指令的时间。
参考资料:https://zhuanlan.zhihu.com/p/165497334 (OpenGL)
https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nn-d3d11-id3d11query (D3D11)
如果我打断点:
连接手机调试,会报错,看不到duration的最终计算数值。调用的函数是repaly_proxy.cpp
中的FetchCounters
,不是我想的gl_counters.cpp
// ReplayProxy 这个类和远程连接有关系
rdcarray<CounterResult> ReplayProxy::FetchCounters(const rdcarray<GPUCounter> &counters)
{
PROXY_FUNCTION(FetchCounters, counters);
}
连接手机截帧完后,将截帧的文件进行保存。再用电脑打开保存到本地的.rdc文件进行调试,调用的函数是gl_counters.cpp
中的FetchCounters
。但调试结束后,所有的duration值归0。(明明调试之前是有值的)
rdcarray<CounterResult> GLReplay::FetchCounters(const rdcarray<GPUCounter> &allCounters)
{
// 函数逻辑比较复杂一点,可重点关注以下三个地方:
GLCounterContext ctx;
FillTimers(ctx, m_pDriver->GetRootDraw(), counters);
m_pDriver->glGetQueryObjectui64v(ctx.queries[i].obj[(uint32_t)counters[c]], eGL_QUERY_RESULT, &data);
}
解决方法:
直接用电脑端的OpenGL进行绘制、截帧、保存截帧文件、打开文件进行调试。不连接手机了,可以正常查看变量值,没有报错~