В моей реализации потока рендеринга OpenGL я реализовал инфраструктуру измерения времени выполнения на основе запросов по таймеру. Я поделюсь частями запроса таймера ниже:
Пусть
enqueue
запускает функцию в потоке рендеринга
limiter.frame60
равен 0 раз каждые 60 кадров
Код:
struct TimerQuery
{
std::string description;
GLuint timer;
};
typedef std::deque<TimerQuery> TimerQueryQueue;
...
TimerQueryQueue timerQueryQueue;
...
void GlfwThread::beginTimerQuery(std::string description)
{
if (limiter.frame60 != 0)
return;
enqueue([this](std::string const& description) {
GLuint id;
glGenQueries(1, &id);
timerQueryQueue.push_back({ description, id });
glBeginQuery(GL_TIME_ELAPSED, id);
}, std::move(description));
}
void GlfwThread::endTimerQuery()
{
if (limiter.frame60 != 0)
return;
enqueue([this]{
glEndQuery(GL_TIME_ELAPSED);
});
}
void GlfwThread::dumpTimerQueries()
{
while (!timerQueryQueue.empty())
{
TimerQuery& next = timerQueryQueue.front();
int isAvailable = GL_FALSE;
glGetQueryObjectiv(next.timer,
GL_QUERY_RESULT_AVAILABLE,
&isAvailable);
if (!isAvailable)
return;
GLuint64 ns;
glGetQueryObjectui64v(next.timer, GL_QUERY_RESULT, &ns);
DebugMessage("timer: ",
next.description, " ",
std::fixed,
std::setprecision(3), std::setw(8),
ns / 1000.0, Stopwatch::microsecText);
glDeleteQueries(1, &next.timer);
timerQueryQueue.pop_front();
}
}
Вот пример вывода:
Framerate t=5.14 fps=59.94 fps_err=-0.00 aet=2850.67μs adt=13832.33μs alt=0.00μs cpu_usage=17%
instanceCount=20301 parallel_μs=2809
timer: text upload range 0.000μs
timer: clear and bind 95.200μs
timer: upload 1.056μs
timer: draw setup 1.056μs
timer: draw 281.568μs
timer: draw cleanup 1.024μs
timer: renderGlyphs 1.056μs
Framerate t=6.14 fps=59.94 fps_err=0.00 aet=2984.55μs adt=13698.45μs alt=0.00μs cpu_usage=17%
instanceCount=20361 parallel_μs=2731
timer: text upload range 0.000μs
timer: clear and bind 95.232μs
timer: upload 1.056μs
timer: draw setup 1.024μs
timer: draw 277.536μs
timer: draw cleanup 1.056μs
timer: renderGlyphs 1.024μs
Framerate t=7.14 fps=59.94 fps_err=-0.00 aet=3007.05μs adt=13675.95μs alt=0.00μs cpu_usage=18%
instanceCount=20421 parallel_μs=2800
timer: text upload range 0.000μs
timer: clear and bind 95.232μs
timer: upload 1.056μs
timer: draw setup 1.056μs
timer: draw 281.632μs
timer: draw cleanup 1.024μs
timer: renderGlyphs 1.056μs
Это позволяет мне вызывать renderThread->beginTimerQuery("draw some text");
перед моими вызовами opengl draw или чем-то еще, и renderThread->endTimerQuery();
сразу после него, чтобы измерить истекшее время выполнения GPU.
Идея здесь в том, что она выдает команду в очередь команд графического процессора непосредственно перед измеряемой секцией, поэтому glBeginQuery
TIME_ELAPSED
записывает значение некоторого счетчика, определенного реализацией. glEndQuery
выдает команду GPU для сохранения разницы между текущим счетчиком и счетчиком, сохраненным в начале запроса TIME_ELAPSED
. Этот результат хранится в графическом процессоре в объекте запроса и становится «доступным» в будущем. Мой код хранит очередь отправленных запросов таймера и проверяет один раз в секунду для завершенных измерений. Мой dumpTimerQueue
продолжает печатать измерения до тех пор, пока все еще доступен запрос таймера в начале очереди. В конце концов он срабатывает по таймеру, который еще не доступен, и останавливает печать сообщений.
Я добавил дополнительную функцию, которая исключает 59 из 60 вызовов функций измерения, поэтому он измеряет только один раз в секунду для всех инструментов в моей программе. Это предотвращает слишком много спама и делает его пригодным для передачи в стандартный вывод для разработки, а также предотвращает слишком большое влияние на производительность, вызванное измерениями. Это то, что является функцией limiter.frame60, frame60 гарантированно будет <60. Это обертка. </p>
Хотя это не совсем отвечает на вопрос, вы можете сделать вывод об использовании графического процессора, отметив истекшее время для всех вызовов отрисовки по сравнению с истекшим временем настенных часов. Если время кадра составляло 16 мс, а время запроса таймера TIME_ELAPSED составляло 8 мс, можно сделать вывод, что использование графического процессора составляет приблизительно 50%.
Еще одно примечание: измерение измеряется временем выполнения графического процессора путем помещения команд графического процессора в очередь графического процессора. Поток не имеет к этому никакого отношения, если операции внутри этих enqueue
были выполнены в одном потоке, это было бы эквивалентно.