Почему игры на MacBook лагают при 100+ fps и как добиться плавности

Прокомментировать Просмотры: 4

Интервалы между кадрами в CS2: стандартное поведение против «Ровных кадров»

Игровой процесс на MacBook нередко страдает от микрофризов, даже когда счётчик демонстрирует впечатляющие 100+ кадров в секунду. Именно с этим я столкнулся в Counter‑Strike 2 на MacBook Pro 14 (M3 Pro): стабильные 110–120 fps, однако при плавном панорамировании камера всё равно заметно подёргивается.

Детальный анализ реального времени вывода каждого кадра на дисплей показал, что проблема крыта вовсе не в дефиците производительности, а в том, как именно кадры синхронизируются с частотной сеткой матрицы ProMotion. Вот как это выглядит в цифровом выражении:

Серые столбцы отображают исходное состояние игры, синие — после стабилизации фреймрейта.

Ниже я подробно расскажу о причинах этого явления и методах их устранения.

Эта публикация служит логическим продолжением исследования периферии, где я выяснял, почему курсор «плывёт» из‑за опроса мыши операционной системой аккурат под каждое обновление экрана. Проблему с опросом мыши удалось решить прямым чтением, однако визуальные рывки никуда не делись.

Небольшая ремарка: CS2 здесь используется исключительно как удобный тестовый полигон с возможностью воспроизвести идентичную игровую сцену. Разработанный мной режим вряд ли пригодится в динамичных соревновательных матчах, где критически важен каждый миллисекундный отклик и максимальная свежесть картинки. Данное решение создано для проектов с одиночной кампанией.

Методика измерений

В API Metal у каждого drawable-объекта имеется метка presentedTime, фиксирующая точный момент отображения кадра на физическом дисплее. Фиксация этого параметра для каждой итерации позволила оценить временные интервалы между смежными кадрами.

Идеально равномерные интервалы гарантируют абсолютную плавность движения. Если же они варьируются, человеческий глаз неминуемо фиксирует рывки вне зависимости от высоких средних показателей fps.

Условия тестирования: встроенный ProMotion-экран с частотой 120 Гц, автономная работа от аккумулятора, карта de_dust2 с ботами в CS2, ограничение fps_max 120, деактивированная вертикальная синхронизация и полноэкранный режим. Эталонным («ровным») считался интервал, отклонение которого от среднего значения не превышало одну миллисекунду.

кадров в секунду

стабильных интервалов

латентность дисплея: норма / худшие 5%

исходно

118

18%

20,8 / 32 мс

«Ровные кадры»

80

87%

19,2 / 24,4 мс

При показателе в 118 fps идеальный интервал должен составлять около 8,5 мс. На практике же наблюдается хаотичный микс из 4,17, 8,33 и 12,5 мс, что прекрасно иллюстрирует график.

Корни фризов при высоком фреймрейте

Дисплеи с технологией ProMotion обновляют изображение не произвольно, а строго по тайминговой сетке с шагом в 4,17 мс. Возможные интервалы равны 8,33, 12,5 или 16,67 мс, но промежуточные значения вроде 8,5 или 10 мс технически невозможны.

В результате идеальную плавность демонстрируют лишь те частоты, которые органично вписываются в эту сетку: 120, 80, 60, 48 и 40.

Нестабильные 118 fps (или просадки до 100 fps в тяжелых локациях) не попадают в ячейки сетки. Каждый отдельный кадр вынужден смещаться к ближайшему доступному таймингу, из‑за чего картинка выводится неравномерно. Как следствие, плавающие 118 fps воспринимаются глазом значительно хуже, чем стабильные 80 fps.

Тупиковые ветви экспериментов

Принудительная буферизация готовых кадров

Первое, что приходит на ум — раз уж кадры рендерятся слишком рано, придерживать их до наступления целевого тайминга. Для этой цели CAMetalLayer предоставляет метод presentAfterMinimumDuration:.

Тестовый стенд действительно показал рост стабильности до 89%, однако цена оказалась высока: кадровая частота рухнула до 60 вместо 80, а общая задержка увеличилась на 47 мс. Рендеринг опережал физический вывод, из‑за чего кадры скапливались в очереди.

Вывод кадров по расписанию

Следующая попытка задействовать жесткое планирование через presentAtTime: завела в тупик.

Стоит лишь приложению переключиться на детерминированный вывод кадров (через presentAtTime: или presentAfterMinimumDuration:), как ProMotion-дисплей принудительно фиксируется на максимальных 120 Гц.

Интервалы стабилизируются на отметках 8,33 и 16,67 мс, превращая целевые 80 ровных кадров в неуправляемую смесь (в меню игры MiSide доля стабильных интервалов составила всего 27%). Это, пожалуй, важнейший инсайт для всех разработчиков,ртирующих игры на macOS.

Лимитирование частоты в начале кадра

Именно так функционирует стандартная команда fps_max. Начало генерации кадров выравнивается, но экран продолжает рвать картинку: тестовая утилита зафиксировала лишь 28% стабильных интервалов.

Профилирование CS2 прояснило ситуацию. Поток, инициирующий вызов Present, отрабатывает примерно за 2 мс, тогда как основная нагрузка ложится на параллельные потоки. Кроме того, транслятор D3DMetal (прослойка Apple для перевода DirectX в Metal) затрачивает на финализацию кадра еще 8–12 мс. Основная десинхронизация зарождается именно там — после вызова Present.

Адаптация под фазу экрана

В условиях деактивированной вертикальной синхронизации ProMotion транслирует кадр на матрицу незамедлительно по мере его готовности. Подстраиваться здесь объективно не под что.

Рабочее решение

Успешная реализация потребовала синхронизации двух процессов непосредственно на этапе Present:

  1. Игровой поток задерживается до старта следующего интервала по строгой сетке (для отметки в 80 fps это ровно 12,5 мс).

  2. Трансляция кадра в Metal удерживается до фиксированной временной точки относительно запланированного начала кадра. В качестве опорной точки берется 98-й перцентиль времени подготовки кадра.

Второй пункт заработал не сразу. Изначальный отсчет от момента пробуждения потока приводил к тому, что микрозадержка единичного кадра транслировалась на следующий (успех составлял лишь 51%). Переход на отсчет от целевого старта по сетке позволил поднять показатель стабильности с 18% до впечатляющих 87%.

В CS2 это не привело к росту инпут-лага: средняя задержка осталась прежней (19,2 мс против 20,8), а в худших 5% сценариев она даже уменьшилась (24,4 против 32 мс) за счет ликвидации очередей.

Нюансы автоматизации

Первая итерация алгоритма опиралась на анализ длительности кадра, что в CS2 дало сбой: при вполне ровном времени рендеринга дисплей выдавал разрывы, заставляя алгоритм судорожно переключаться между 80 и 120 Гц по 41 разу за две минуты.

Текущая версия мониторит состояние самого экрана. Если доля ровных промежутков опускается ниже 45%, система понижает частотную ступень и спустя 2,5 секунды оценивает результат: если ситуация улучшилась — фиксирует значение, если нет — сбрасывает настройки для нового замера.

Интерфейс игры MiSide на 115 fps изначально демонстрирует высокую плавность (75–86%), поэтому искусственное ограничение до 80 fps лишь ухудшает показатели (до 52%). В подобных сценариях алгоритм не вмешивается.

Обратная сторона медали

Главная плата за плавность — снижение фреймрейта со 118 до 80 единиц. Для киберспортивных шутеров это неприемлемый компромисс, где важен каждый отдельный кадр. Однако в адвенчурах, гоночных симуляторах, экшенах и проектах с открытым миром микрофризы портят впечатление мгновенно, в то время как разницу в отклике между 118 и 80 fps заметить практически невозможно.

Возможно незначительное увеличение латентности. В CS2 этот показатель не изменился, тогда как на тестовом приложении добавилось около 2,5 мс.

Метод эффективен исключительно в полноэкранном режиме. Любое перекрытие окна или оконный режим заставляют macOS интерполировать кадры под базовую сетку 8,33 мс, разрушая стабильность 80 fps. Автоматика отслеживает такие моменты и отключает буферизацию.

Не стоит забывать и про аппаратные затыки, неподвластные софтуине: примерно раз в полсекунды доставка кадра на экран занимает 16–20 мс вместо привычных 6,5 мс.

Замеры производились на единственной конфигурации Mac в двух играх, поэтому на другом «железе» метрики могут отличаться.

Чек-лист для разработчиков под macOS

  • Замеряйте реальный момент отрисовки кадра на экране, а не виртуальное время рендеринга.

  • Избегайте принудительного шедулинга кадров: это переведет дисплей в режим жестких 120 Гц.

  • Ограничивайте кадровую частоту кратно возможностям матрицы (80, 60, 40) на этапе старта рендеринга, избегая искусственной буферизации уже готовых кадров.

  • Идеальная синхронизация достижима только в монопольном полноэкранном режиме.


Описанные механизмы интегрированы в мое приложение Uncork в виде тумблера «Ровные кадры» в настройках префикса.

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

 

Источник

Поделиться:

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов