Предисловие
В этой статье я расскажу вам о том, как кадры, которые рендерит ваша видеокарта, попадают на экран; откуда берутся разрывы изображения; как работает V-Sync и каким образом он борется с разрывами; почему в результате этого изображение выводится на экран с задержкой; а также сравним обычный V-Sync и Fast V-Sync.
Хоть в этой статье я и объясню работу V-Sync'а относительно подробно, тем не менее мы не будем рассматривать абсолютно все его аспекты, а по большей части те, что касаются задержки. То, как он влияет на плавность выводимого потока кадров, на использование ресурсов GPU и т.д. — мы в данном случае рассматривать не будем.
Также в этой статье мы не будем рассматривать способы борьбы с разрывами, работающими на стороне монитора, а не на стороне GPU (G-Sync, FreeSync, и прочие).
Tearing
Для начала разберём, что вообще такое tearing и как он появляется.
Tearing — это разрыв изображения на экране, возникающий в следствии попытки отображения на дисплее информации одновременно из нескольких кадров.
Почему это происходит:
Дисплеи устроены так, что каждый кадр на дисплее отрисовывается, грубо говоря, построчно сверху вниз (относительно своей "правильной" ориентации). Как итог, если частота кадров видеосигнала, поступающего на дисплей, не синхронизирована с частотой обновления дисплея — то в какой-то момент в процессе отрисовки кадра мы получаем горизонтальный разрыв кадра — полосу в каком-то месте экрана.
Разбираем процесс подробно со стороны видеокарты:
Рендер и вывод кадров на экран происходит посредством двойной буферизации: видеокарта имеет 2 буфера кадров, условно называемые «передним» и «задним». Процесс рендера кадров происходит только в заднем буфере. В то время как монитор получает изображение только из переднего буфера. Когда кадр в заднем буфере полностью отрендерен — он отправляется из заднего буфера в передний.
Уточнение:
Если не использовать двойную буферизацию, а иметь только один буфер — весь процесс рендера кадра будет в риалтайме поступать на монитор, соответственно, в случаях современного комплексного процесса рендера кадра изображение на экране будет состоять по большей части из "мусора". Двойная буферизация, работая ранее описанным образом, позволяет избежать этой проблемы, поскольку отправляет в экранный буфер только полностью готовые кадры.
Также стоит отметить, что копирование кадров между буферами отнимает время, поэтому есть более простой способ смены буферов: page flipping. По сути это "подмена" переднего буфера задним путём простого переименования буферов. Это позволяет сменять кадр в переднем буфере разительно быстрее, чем копирование данных между буферами (по аналогии с тем как перевернуть страничку — гораздо быстрее, чем переписывать всё из одной странички в другую). И, как я понимаю, в современной реальности используется в основном как раз этот метод (поправьте меня, если я не прав).
Соответственно, если замена кадра в переднем буфере происходит не между циклами обновления дисплея, а прямо в процессе его обновления — то верхняя часть изображения на экране остаётся ещё от старого кадра, а нижняя — уже от нового.
Решение проблемы
Теоретически есть 2 способа решения этой проблемы: подгонка частоты кадров видеокарты под частоту обновления монитора; и наоборот — частоты обновления монитора под частоту кадров видеокарты.
Поскольку GPU по умолчанию рендерит кадры "как получится", т.е. её частота кадров — динамическая, а способов динамического управления частотой обновления монитора изначально не существовало — то первым пришло решение со стороны GPU, а именно V-Sync.
V-Sync
V-Sync / Vertical Synchronization (вертикальная синхронизация) — это самый простой метод согласования частоты кадров GPU с частотой обновления монитора.
Он запрещает видеокарте выполнять какие-либо видимые действия в буфере кадров, выводимом на дисплей (переднем буфере) до тех пор, пока монитор не завершит текущий цикл обновления. Обновление данных в переднем буфере происходит исключительно во время VBLANK'ов.
Уточнение:
VBLANK / VBI / vertical blanking interval (кадровый гасящий импульс) — короткий временной интервал (предположительно, <1 мс) между концом отрисовки последней видимой строки текущего кадра и началом отрисовки первой видимой строки следующего кадра. Иначе говоря, это момент, когда монитор уже закончил отрисовку (вывод) предыдущего кадра и ещё не начал вывод следующего. В ЭЛТ-мониторах это явление было необходимо для того, чтобы гасить луч кинескопа во время обратного хода (пока тот возвращается из конечной позиции в начальную). В цифровых LCD мониторах используется для реализации синхронизации частоты обновления GPU и монитора.
Соответственно, начало рендера нового кадра происходит только после окончания текущего цикла обновления, поскольку задний буфер сможет освободиться только после того, как текущий кадр будет отправлен в передний буфер (а это происходит только во время VBLANK'ов).
Соответственно, начало рендера нового кадра происходит только после окончания текущего цикла обновления, поскольку задний буфер сможет освободиться только после того, как текущий кадр будет отправлен в передний буфер (а это происходит только во время VBLANK'ов).
Задержка от V-Sync
Как следствие такого резервирования полного кадра, V-Sync добавляет к выводу кадров некоторую задержку:
- В начале каждого нового цикла обновления дисплея задержка самая низкая: равная длительности по меньшей мере одного цикла обновления.
- На протяжении цикла обновления она постепенно увеличивается (т.е. выводится всё тот же самый кадр, который с течением времени устаревает). В конце каждого цикла задержка самая высокая: равная длительности по меньшей мере двух циклов обновления.
Для примера, в случае 60 герцового монитора она будет прогрессировать в течении каждого цикла обновления от 16.7 мс до 33.3 мс соответственно.
Задержка без V-Sync
Разумеется, без V-Sync вывод изображения тоже нельзя назвать в полной мере мгновенным, ведь в этом случае обновляется только часть кадра. На протяжении каждого цикла обновления монитора, чем ближе к концу цикла видеокарта закончила рендер нового (более актуального) кадра — тем меньшая часть дисплея будет заполнена этим новым кадром, и большая часть дисплея будет оставаться заполненной уже устаревшим кадром. Тем не менее, задержка всё-равно гораздо меньше, чем с V-Sync:
- Какую-то часть кадра (рандомную в случае непредсказуемого фреймрейта) всегда можно увидеть вообще без задержки вывода, а только с задержкой от времени рендера кадра (frametime).
- Отсюда также понятно, что чем сильнее фреймрейт GPU превышает частоту обновления дисплея, тем актуальнее будет обновляемая часть кадра, т.е. здесь время рендера кадра (frametime) влияет на финальную задержку изображения на дисплее. В отличии от V-Sync, где разницы в задержке не будет вне зависимости от того, с какой скоростью GPU рендерит один кадр.
- Самая устаревшая часть кадра будет устаревшей не более чем на: один цикл обновления + frametime.
Таком образом, если предположить, что GPU рендерит 200 FPS, т.е. frametime равен всего 5 мс, то задержка в разных частях кадра на том же 60 герцовом мониторе будет варьироваться от 5 мс до 21.7 мс.
Fast V-Sync и Enhanced Sync
Режимы Fast V-Sync у Nvidia и Enhanced Sync у AMD позволяют видеокарте рендерить кадры подряд без каких-либо ожиданий, а на экран всегда поступает лишь последний полностью отрендеренный кадр. Тем самым они сильно уменьшают задержку по сравнению со стандартным V-Sync (хоть она всё ещё и остаётся выше, чем при полном отсутствии синхронизации, т.к. неполные кадры не отправляются на экран, насколько бы актуальными они ни были), и при этом продолжают выполнять свою первичную функцию: избавление от разрывов.
Для достижения такого результата Fast V-Sync использует не двойную, а тройную буферизацию. В таком режиме у видеокарты есть 3 буфера: передний, задний, а также буфер последнего готового кадра (Last Rendered Buffer). Как только видеокарта закончила рендер кадра в заднем буфере — он отправляется в Last Rendered Buffer, где будет ожидать нового цикла обновления дисплея. А GPU, тем временем, приступает к рендеру нового кадра в заднем буфере. И если до окончания текущего цикла обновления (то-бишь до ближайшего VBLANK'а) GPU успеет отрендерить ещё один новый кадр, то он тоже отправится в Last Rendered Buffer, заменив собой предыдущий кадр, находившийся там до сих пор. И этот процесс будет повторяться до тех пор, пока не закончится текущий цикл обновления. А как только наступает новый VBLANK — в передний буфер сразу же отправится кадр из Last Rendered Buffer'а (который, как понятно из вышеописанного объяснения, всегда является наиболее актуальным кадром) и начнёт выводиться на дисплей. А GPU, тем временем, будет продолжать рендерить кадры в заднем буфере и отправлять их в Last Rendered Buffer. И так по кругу.
Таким образом в отличии от стандартного V-Sync, Fast V-Sync не добавляет статичную задержку длительностью в один цикл обновления, тем самым позволяя задержке вывода в идеальных случаях сводиться к нулю; но в то же время Fast V-Sync не предполагает возможности обновления лишь части дисплея, как это происходит без синхронизации. Пока не наступит новый VBLANK — мы не увидим никаких частей нового кадра. Это позволяет избавиться от разрывов, однако это также означает, что несмотря на примерно одинаковый теоретический диапазон задержек (то бишь их максимальное и минимальное значения) при Fast V-Sync и при отсутствия синхронизации, фактическое распределение этих задержек у Fast V-Sync немного смещено в сторону более высоких значений по сравнению с отсутствием синхронизации.
Говоря проще: при Fast V-Sync'е мы часто будем видеть чуть более устаревшие кадры, чем при отсутствии синхронизации, хотя максимально возможное устаревание кадра будут примерно одинаковым. Как итог ощущаемая задержка чаще всего будет немного выше, чем без синхронизации. Причём, чем сильнее фреймрейт GPU превышает частоту обновления дисплея — тем больше будет ощущение этой задержки. Например, если частота GPU будет настолько высокой, что задержкой от её frametime'а можно будет пренебречь в рамках 60 Гц (например, 1000 FPS), то при отсутствии синхронизации устаревать будет ТОЛЬКО УЖЕ ОТРИСОВАННАЯ часть дисплея, а та, что отрисовывается в текущий момент — будет всегда актуальной.
Вот пример такого случая:
Здесь на очень быстро сужающихся кольцах мы видим по меньшей мере 13 разрывов, благодаря тому что фреймрейт в игре здесь ~1200 FPS. А каждый разрыв — это разделение кадра на устаревшую часть сверху и более актуальную — снизу.
В то время как при Fast V-Sync'е устаревает ОДИНАКОВО ВЕСЬ КАДР на протяжении всего цикла обновления.
Но очевидно, что с фреймрейтом, более близким к частоте обновления дисплея разница между Fast V-Sync и отсутствием синхронизации будет гораздо менее заметна. Допустим, если GPU рендерит 60-80 FPS (т.е. всего на 0-30% больше кадров, чем монитор может вывести), то ощутить разницу в задержке будет практически невозможно, зато что будет отчётливо ощутимо — так это отсутствие разрывов.
Итог
Не знаю, какой тут итог написать и, главное, зачем… Лучше вот держите скриншот с объяснением, как включается Fast V-Sync:
Статью для вас написал Dr.Sly, спасибо всем за внимание :)