Punch2027: Obidients reject INEC’s plan to use AIThe Jerusalem PostSome 18 injured in eight-car pileup on Highway 6 in central Israel, halting trafficDaily MaverickWORLD HEART DAY: Silent killer, simple fix? How to save SA from hypertensionRTP DesportoJaime Faria falha acesso ao quadro principal do torneio de TóquioInquirerTulfo flags ‘palakasan system’ in NHA housing beneficiary selectionVanguardOtti to Ndigbo: Don’t fight Chinese traders; beat them with technologyColliderArthur Morgan Actor Officially Addresses One of 'Red Dead Redemption 2's Biggest Fan Theories [Exclusive]20 Minuten«Überall liegt Staub»: Das Schächental nach dem FelssturzThe South AfricanDay 15 of 24: Festive Quiz – Test your Local Government Elections 2026 knowledge and win R250NMEVictoria Beckham “back in the studio” as she records vocals with son CruzIl Fatto QuotidianoNations League, la situazione dell’Italia: la nuova classifica, la sfida alla Francia, Montella a rischio | Cosa c’è in balloSportstarIndia in Athletics LIVE Updates, Asian Games 2026: Dev Meena, Kuldeep Kumar clear 5.35m in men's pole vault final; Pooja, Supriya in action in women's high jump
The Daily Newsstand · Free, Always
Tuesday, September 29, 2026

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

Translate

Игры на MacBook часто дёргаются даже тогда, когда счётчик показывает 100 кадров в секунду и больше. У меня так было с Counter-Strike 2 на MacBook Pro 14 с M3 Pro: 110–120 fps, а на медленном повороте картинка всё равно идёт рывками.

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

Промежутки между кадрами на экране в CS2: как есть и с «Ровными кадрами»

Промежутки между кадрами на экране в CS2: как есть и с «Ровными кадрами»

Серые столбики — как игра идёт сама по себе, синие — после того, как я выровнял кадры.

Дальше о том, почему так выходит и как я к этому пришёл.

Это продолжение истории про мышь: там я разбирался, почему прицел «плывёт» из-за того, что macOS отдаёт движение мыши раз в кадр экрана. Мышь с тех пор читается напрямую, а рывки остались.

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

Как я мерил

У каждого кадра в Metal есть момент, когда он на самом деле появился на экране: это presentedTime у drawable. Я записывал его для каждого кадра и смотрел на промежутки между соседними кадрами.

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

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

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

ровных промежутков

задержка до экрана: обычно / в худших 5 %

как есть

118

18 %

20,8 / 32 мс

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

80

87 %

19,2 / 24,4 мс

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

Откуда рывки при высоком fps

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

Поэтому ровно держатся только частоты, которые ложатся на эту сетку: 120, 80, 60, 48, 40.

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

Что я пробовал

Придержать готовый кадр

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

На тестовой программе ровность и правда выросла до 89 %, но частота упала до 60 вместо 80, а задержка выросла на 47 мс. Игра рисовала быстрее, чем кадры выпускались на экран, и они копились в очереди.

Показывать кадры по расписанию

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

Как только приложение начинает выводить кадры по расписанию (presentAtTime: или presentAfterMinimumDuration:), экран ProMotion переходит на жёсткие 120 Гц.

Промежутки становятся ровно 8,33 и 16,67 мс, и вместо 80 ровных кадров получается их смесь: в меню MiSide вышло 27 % ровных. По-моему, это самое полезное, что стоит знать, если вы пишете свою игру под Mac.

Ограничить частоту в начале кадра

Так работает fps_max. Начало кадров стало ровным, а экран остался рваным: на тестовой программе 28 % ровных промежутков.

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

Подстроиться под фазу экрана

Без вертикальной синхронизации ProMotion выводит кадр сразу, как только тот готов. Подстраиваться там не к чему.

Что в итоге заработало

Понадобились две вещи, обе внутри Present:

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

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

Второй пункт заработал не сразу. Сначала я отсчитывал точку от момента, когда поток просыпался, и опоздание одного кадра переезжало на следующий: ровных выходило 51 %. Когда стал считать от задуманного начала по сетке, стало 87 % против 18 % без режима.

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

Когда это не нужно

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

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

Меню MiSide на 115 кадрах и так ровное (75–86 %), а ограничение до 80 делает его хуже (52 %). Такие игры режим не трогает.

Цена

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

Задержка тоже может немного вырасти. В CS2 она не выросла, а на тестовой программе прибавилось около 2,5 мс.

Работает это только на весь экран. Если окно не развёрнуто или поверх него лежит другое, macOS сводит кадры с остальным экраном по сетке 8,33 мс, и ровные 80 снова рассыпаются. Режим это замечает и ничего не держит.

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

Мерил я на одном маке и в двух играх, так что на других машинах цифры могут отличаться.

Если вы пишете игру под Mac

  • Мерьте момент появления кадра на экране, а не время кадра.

  • Не выводите кадры по расписанию: экран уйдёт в жёсткие 120 Гц.

  • Ограничивайте частоту делителем частоты экрана (80, 60, 40) и в начале кадра, а не придерживайте готовый.

  • Ровный вывод бывает только на весь экран.

Всё описанное я встроил в своё приложение Uncork, это переключатель «Ровные кадры» в настройках бутылки, с тем самым автоматическим режимом.

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

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.