Как правильно рассчитать FPS, учитывая, что графические процессоры имеют очередь задач и являются асинхронными? - PullRequest
7 голосов
/ 08 января 2012

Я всегда предполагал, что правильным способом для вычисления FPS было бы просто указать время, которое потребовалось для выполнения итерации цикла отрисовки. И большая часть Интернета, кажется, соответствует.

Но!

Современные графические карты обрабатываются как асинхронные серверы, поэтому цикл рисования отправляет инструкции рисования для данных вершин / текстур / и т. Д., Уже находящихся в графическом процессоре. Эти вызовы не блокируют вызывающий поток до тех пор, пока запрос на графическом процессоре не завершится, они просто добавляются в очередь задач графического процессора. То есть, безусловно, «традиционный» (и довольно вездесущий) метод - это просто измерение времени отправки вызова?

Что побудило меня спросить, так это то, что я реализовал традиционный метод, и он давал неизменно нелепо высокие частоты кадров, даже если то, что воспроизводилось, вызывало прерывистую анимацию. Перечитывание моей OpenGL SuperBible привело меня к glGenQueries, который позволяет мне синхронизировать разделы конвейера рендеринга.

Подводя итог, можно сказать, является ли «традиционный» способ вычисления FPS полностью несуществующим с (едва ли) современными видеокартами? Если да, то почему методы профилирования GPU относительно неизвестны?

Ответы [ 2 ]

13 голосов
/ 09 января 2012

Измерять fps сложно.Это усложняется тем фактом, что различные люди, которые хотят измерять fps, не обязательно хотят измерять одно и то же.Так что спросите себя об этом.Почему вы хотите число fps?

Прежде чем я продолжу и углублюсь во все подводные камни и потенциальные решения, я хочу отметить, что это ни в коем случае не является проблемой, специфичной для «современных видеокарт».Во всяком случае, это было намного хуже, с машинами типа SGI, где рендеринг действительно происходил на графической подсистеме, которая могла быть удалена клиенту (например, физически удалена).GL1.0 фактически был определен в терминах клиент-сервер.

В любом случае.Возвращаясь к рассматриваемой проблеме.

fps, то есть количество кадров в секунду, на самом деле пытается передать в одном числе грубое представление о производительности вашего приложения в количестве, которое может быть непосредственно связано стакие вещи, как частота обновления экрана.для 1-го уровня приближения производительности, это делает хорошо работу.Он полностью разрушается, как только вы захотите углубиться в более детальный анализ.

Проблема в том, что на самом деле самое важное, что касается "ощущения гладкости" приложения, это когда выДрю заканчивается на экране.Второстепенная вещь, которая также имеет большое значение, это то, сколько времени прошло между моментом запуска действия и моментом, когда его эффект отображается на экране (общая задержка).

Когда приложение рисует серию кадров,он передает их в моменты времени s0, s1, s2, s3, ... и они в конечном итоге отображаются на экране в моменты времени t0, t1, t2, t3, ...

Чтобы чувствовать себя гладко, вам нужны все следующие вещи:

  1. tn-sn не слишком высокий (задержка)
  2. t (n + 1) -t (n) мал (меньше 30 мс)
  3. тамЭто также жесткое ограничение на время дельты симуляции, о котором я расскажу позже.

Когда вы измеряете процессорное время для вашего рендеринга, вы заканчиваете тем, что измеряете s1-s0 до приблизительного значения t1-t0,Как выясняется, это в среднем не так уж далеко от истины, поскольку клиентский код никогда не пойдет "слишком далеко вперед" (хотя это предполагает, что вы все время рендерите кадры. См. Нижедля других случаев).На самом деле происходит то, что GL блокирует ЦП (обычно во время SwapBuffer), когда он пытается зайти слишком далеко.Это время блокировки - примерно дополнительное время, затрачиваемое графическим процессором по сравнению с процессором в одном кадре.

Если вы действительно хотите измерить t1-t0, как вы упоминали в своем посте, запросы ближе к нему,Но ... Вещи никогда не бывают такими простыми.Первая проблема заключается в том, что если вы привязаны к процессору (то есть ваш процессор недостаточно быстр, чтобы всегда обеспечивать работу графического процессора), то часть времени t1-t0 фактически является временем простоя графического процессора.Это не будет захвачено Запросом.Следующая проблема, с которой вы столкнулись, заключается в том, что в зависимости от вашей среды (среды компоновки экрана, vsync) запросы могут фактически измерять только время, которое ваше приложение тратит на рендеринг в резервный буфер, что не является полным временем рендеринга (поскольку отображение не былообновлено в то время).Это дает вам приблизительное представление о том, сколько времени займет рендеринг, но также не будет точным.Также обратите внимание, что запросы также подвержены асинхронности графической части.Так что, если ваш графический процессор простаивает часть времени, запрос может пропустить эту часть.(Например, скажем, ваш процессор занимает очень много времени (100 мс) для отправки вашего кадра. Графический процессор выполняет полный кадр за 10 мс. Ваш запрос, скорее всего, выдаст 10 мс, даже если общее время обработки было ближе к 100 мс ...).

Теперь, что касается «рендеринга на основе событий», а не непрерывного рендеринга, который я обсуждал до сих пор. fps для этих типов рабочих нагрузок не имеет большого смысла, поскольку цель состоит не в том, чтобы набрать как можно больше f / s. Там естественным показателем производительности GPU является мс / ф. Тем не менее, это только небольшая часть картины. То, что действительно имеет значение, это время, которое потребовалось с момента, когда вы решили обновить экран, и время, когда это произошло. К сожалению, это число трудно найти: оно обычно начинается, когда вы получаете событие, которое запускает процесс, и заканчивается при обновлении экрана (то, что вы можете измерить только с помощью камеры, фиксирующей вывод экрана ...).

Проблема заключается в том, что между этими двумя значениями у вас есть потенциальное перекрытие между процессором и обработкой на графическом процессоре, или нет (или даже некоторая задержка между моментом, когда процессор перестает отправлять команды и графический процессор начинает их выполнять). И это полностью зависит от реализации, чтобы решить. Лучшее, что вы можете сделать, это вызвать glFinish в конце рендеринга, чтобы точно знать, что GPU завершил обработку отправленных вами команд, и измерить время на CPU. Это решение снижает общую производительность стороны ЦП и, возможно, стороны ГП, если вы собираетесь отправить следующее событие сразу после ...

Последнее обсуждение «жесткого ограничения времени дельты симуляции»:

Типичная анимация использует промежуток времени между кадрами для перемещения анимации вперед. Основная проблема заключается в том, что для полностью плавной анимации вы действительно хотите, чтобы дельта-время, которое вы используете при отправке вашего кадра на s1, равнялось t1-t0 (чтобы при отображении t1 время, которое фактически было потрачено с предыдущего кадра, было действительно t1 -t0). Проблема, конечно, в том, что вы не представляете, что такое t1-t0, когда вы отправляете s1 ... Поэтому вы обычно используете приближение. Многие просто используют s1-s0, но это может сломаться - например, Системы типа SLI могут иметь некоторые задержки при рендеринге AFR между различными графическими процессорами). Вы также можете попытаться использовать аппроксимацию t1-t0 (или, более вероятно, t0-t (-1)) через запросы. Результатом неправильного понимания этого является, скорее всего, микроблокировка в системах SLI.

Самое надежное решение - это сказать «привязка к 30 кадрам в секунду и всегда использовать 1/30 с». Это также тот, который обеспечивает наименьшую свободу действий для контента и оборудования, поскольку у вас есть , чтобы гарантировать, что ваш рендеринг действительно может быть выполнен в эти 33 мс ... Но это то, что некоторые разработчики консолей выбирают (фиксированное оборудование делает это несколько проще).

1 голос
/ 08 января 2012

«И большая часть Интернета, кажется, соответствует». не кажется мне правильным:

Большинство публикаций будет измерять, сколько времени потребуется для МНОГИХ итераций, а затем нормализуется. Таким образом, вы можете разумно предположить, что заполнение (и опорожнение) трубы - лишь малая часть общего времени.

...