Сколько времени требуется OpenGL для обновления экрана?

9

У меня есть простое тестовое приложение OpenGL на C, которое рисует разные вещи в ответ на ввод ключа. (Mesa 8.0.4, пробовал с Mesa-EGL и с GLFW, Ubuntu 12.04LTS на ПК с NVIDIA GTX650). Ничья довольно простая / быстрая (вращающийся треугольник). Мой тестовый код никоим образом не ограничивает частоту кадров, он просто выглядит так:

while (true)
{
    draw();
    swap_buffers();
}

Я очень тщательно рассчитал время и обнаружил, что время от одного eglSwapBuffers()(или того glfwSwapBuffersже) вызова до следующего составляет ~ 16,6 миллисекунды. Время от после звонка eglSwapBuffers()до следующего звонка лишь немного меньше этого, хотя то, что нарисовано, очень просто. Время, которое занимает вызов буферов подкачки, значительно меньше 1 мс.

Тем не менее, время от приложения, изменяющего то, что оно рисует в ответ на нажатие клавиши, до изменения, фактически отображаемого на экране, составляет> 150 мс (приблизительно 8-9 кадров). Это измеряется записью экрана и клавиатуры с частотой 60 кадров в секунду.

Поэтому вопросы:

  1. Где находятся буферизованные отрисовки между вызовом для замены буферов и фактическим отображением на экране? Почему задержка? Похоже, приложение всегда рисует много кадров впереди экрана.

  2. Что может сделать приложение OpenGL, чтобы вызвать немедленный вывод на экран? (т.е.: нет буферизации, просто блокируйте, пока не завершится отрисовка; мне не нужна высокая пропускная способность, мне нужна низкая задержка)

  3. Что может сделать приложение, чтобы вышеуказанная немедленная ничья произошла как можно быстрее?

  4. Как приложение может узнать, что на самом деле сейчас на экране? (Или как долго / сколько кадров составляет текущая задержка буферизации?)

Алекс я
источник
Можете ли вы отделить время, необходимое клавиатуре для уведомления вашего приложения, от времени, которое требуется для его рендеринга?
ThorinII
@ThorinII: Честная точка зрения. Вероятно, я могу настроить что-нибудь хакерское, используя светодиод на параллельном порту и т. Д., Чтобы получить представление о том, когда приложение действительно нажимает клавишу. Тем не менее, это просто fgetc (stdin), как медленно это может быть? : /
Алексей I
У меня сложилось впечатление, что glFlush был использован для немедленного сброса всех команд.
Programmdude
1
@AlexI проверить это gamedev.stackexchange.com/questions/66543/… может помочь вам
concept3d
1
@ concept3d: Да, на это, в основном, ответили, просто подумал, что вам нужен какой-нибудь дополнительный представитель :) Очевидно, мне все же придется подождать день, чтобы наградить его.
Алекс I

Ответы:

6

Любая функция API рисования, вызываемая из ЦП, будет передана в буфер кольца команд графического процессора для последующего выполнения графическим процессором. Это означает, что функции OpenGL являются в основном неблокирующими функциями. Таким образом, процессор и графический процессор будут работать параллельно.

Самая важная вещь, на которую стоит обратить внимание, это то, что ваше приложение может быть связано с процессором или графическим процессором как только вы вызовете glFinish, процессор должен будет ждать, пока графический процессор завершит выполнение команд рисования, если графический процессор отнимает больше времени и может / вызывает остановку центрального процессора, тогда ваши приложения привязаны к графическому процессору. Если графический процессор завершает свои команды рисования, а процессору требуется слишком много времени для glFinish, тогда ваше приложение привязано к процессору.

И обратите внимание, что есть разница между glFlushи glFinish.

glFlush: указывает, что все команды, которые ранее были отправлены в GL, должны завершиться за конечное время.

glFinish: принудительно завершает все предыдущие команды GL. Finish не возвращается до тех пор, пока не будут полностью реализованы все эффекты от ранее выполненных команд для GL-клиента и состояния сервера и кадрового буфера. "

glXSwapBuffers выполняет неявный glFlush перед его возвратом. Последующие команды OpenGL могут быть выполнены сразу после вызова glXSwapBuffers, но не выполняются до тех пор, пока не будет завершен обмен буферами.

Фактическое время кадра, скорее всего, будет определяться тем, какой из двух CPU / GPU тратит больше времени для завершения своей работы.

concept3d
источник
Это очень полезно. Некоторые возможные исправления: opengl.org/sdk/docs/man2/xhtml/glXSwapBuffers.xml «glXSwapBuffers выполняет неявный glFlush, прежде чем он возвращает», похоже, что на самом деле это не делает glFinish, поэтому буфер команд может иметь довольно много вещи в нем, когда буферы подкачки возвращаются.
Алекс I
... Кроме того, я думаю, что наиболее интересным моментом является то, что мое очень простое тестовое приложение не ограничено ни CPU, ни GPU - и здесь не нужно много работы - но что-то еще, оно как-то заканчивается как с низкой частотой кадров (точно так же, как у монитора). частота обновления ??) и высокая задержка (из-за буфера команд, на самом деле вы объяснили эту часть довольно хорошо).
Алекс I
@ Алекс both low framerate (exactly same as monitor refresh rate??Нет, если вы явно не используете VSync.
concept3d
@AlexI Я также хочу отметить, что время кадра отличается от FPS, используйте время кадра для профилирования вашего приложения, потому что оно линейное. FPS, с другой стороны, является плохим измерением, которое не является линейным.
concept3d
Я не использую явно vsync. Я ничего не делаю, чтобы изменить настройки по умолчанию vsync, я даже не знаю, где их изменить в OpenGL. Я просто получаю ровно 60 кадров в секунду (с точностью до процента).
Алекс, 1
4

Технически, OpenGL никогда не обновляет экран.

Для этого существует API оконной системы, отдельный от GL (например, GLX, WGL, CGL, EGL). Перестановки в буфере с использованием этих API обычно неявно вызывают, glFlush (...)но в некоторых реализациях (например, растеризатор GDI в Windows) он выполняет полный цикл glFinish (...):

 

    * С левой стороны, ICD (аппаратный) путь через SwapBuffers (...). Справа - путь к GDI (программному обеспечению).

Если у вас включен VSYNC и двойная буферизация, то любая команда, которая изменит задний буфер до того, как произойдет отложенный обмен, должна прерваться до обмена. Глубина очереди команд ограничена, поэтому эта остановленная команда в конечном итоге приведет к пробке в конвейере. Это может означать, что вместо блокировки SwapBuffers (...)вашего приложения фактически блокируется какая-то не связанная команда GL, пока VBLANK не развернется. На самом деле все сводится к тому, сколько обратных буферов у вас есть в цепочке обмена.

Пока все задние буферы заполнены законченными кадрами, которые еще предстоит перенести на передний план, буферы подкачки будут неявно вызывать блокировку. К сожалению, нет способа явно контролировать количество обратных буферов, используемых большинством API оконных систем GL (кроме 0 с одиночной буферизацией или 1 с двойной буферизацией). Драйвер может свободно использовать 2 обратных буфера, если он хочет (тройная буферизация), но вы не можете запросить это на уровне приложения, используя что-то вроде GLX или WGL.

Андон М. Коулман
источник
Andon: Хорошая информация, спасибо. Я думаю, что то, что я вижу, это частично двойная буферизация, но: «попытка изменить задний буфер до того, как произойдет своп, должна блокироваться» - почему? похоже, что все может быть отправлено в буфер команд, включая своп :)
Alex I
«явно контролировать количество обратных буферов, используемых большинством API оконных систем GL (кроме 0 с одинарной буферизацией или 1 с двойной буферизацией)» - как можно контролировать один / двойной буферизацию?
Алекс I
@AlexI: Я разъяснил язык относительно того, почему команда может привести к блокировке. В других API есть концепция, известная как рендеринг, которая фактически представляет собой глубину очереди команд. Что касается управления одиночной / двойной буферизацией, то это зависит от формата пикселя, который вы выбираете при создании контекста рендеринга. В WGL вы можете выбрать одинарную или двойную буферизацию, но если вы выберете двойную буферизацию, драйвер может фактически создать 2 обратных буфера (таким образом, двойная буферизация становится тройной буферизацией), если пользователь установит свой драйвер для этого.
Андон М. Коулман
На самом деле вы не хотите рендерить слишком много кадров вперед, потому что, хотя это уменьшит блокировку, но также увеличит задержку. Лучший способ минимизировать задержку - это запускать glFinish (...)сразу после замены буферов. Это очистит очередь команд, но это также означает, что графический процессор и процессор будут синхронизированы (что не очень хорошо, если вы хотите, чтобы графический процессор всегда работал).
Андон М. Коулман
1

Я полагаю, вы знакомы с этим экспериментом ?

По сути, Джон Кармак делал что-то подобное, записывая экран и синхронизируя пиксели, отправленные на экран. Он обнаружил, что большая часть задержки пришла с экрана. Другими факторами были задержка ввода с клавиатуры, видеодрайверов и / или ход выполнения самой программы.

MichaelHouse
источник