Це цікаво

Невловима проблема таймінгу кадрів


Технічний директор Croteam Ален Ладавач, брав участь в розробці Serious Sam і Talos Principle, розповідає, як йому вдалося знайти причину гальмування графіки навіть на найпотужніших машинах.

Нарешті з’явилося пояснення того, чому деякі ігри гальмують на вашому PC (і промінь надії на те, що в найближчому майбутньому вони гальмувати перестануть).

Т-т-гальма

Ви з нетерпінням чекали наступної частини вашої улюбленої серії відеоігор для PC і вона нарешті вийшла. На цей раз ви хочете насолодитися нею у всій повноті, тому витратили гроші і час на ретельну підготовку. Ви замінили процесор, поставили надсучасну відеокарту, додали ще ОЗП — чорт візьми, навіть купили RAID на SSD. Гра повинна бути плавною з самої заставки.

Передзамовлення нарешті розблоковано і ви тільки що завершили установку. У нервовому очікуванні ви вперше запускаєте гру. Поки все добре — вона працює з частотою 60 кадрів в секунду. Або, принаймні, так повідомляє лічильник кадрів тюнера GPU. Але щось не так. Ви робите мишею різкі, хаотичні рухи. Стрейфитесь вліво-вправо, і тут гра… починає гальмувати! Блін, так як таке можливо? Як вона може гальмувати при 60 кадрах в секунду?

Якщо таке з вами ніколи не траплялося, то це може здатися смішним. Але якщо ви відчули, то швидше за все ненавидите гальма всією душею. Гальма в іграх. Це не старий добрий «лад». Не низька частота кадрів. Це просто «гальма», які відбуваються при високих частотах кадрів на ідеальних, супербыстрых машинах. Що це, звідки вони взялися і як від них позбутися? Дозвольте мені розповісти вам історію…

Гальма, плавність, швидкість… адже це одне і те ж?

Відеоігри працювали з частотою 60 fps ще з часів перших аркадних автоматів в 70-х роках. Зазвичай очікується, що гра працює з тією ж частотою, яка використовується дисплеєм. Так було до популяризації 3D-ігор, в яких вперше стала допустимої знижена частота кадрів. У 90-х, коли “3D-карти” (так ми називали їх до того, як вони стали “GPU”) почали замінювати програмний рендерінг, люди грали в ігри при 20 fps, а 35 fps вважалися пристойним значенням для серйозних мережних боїв. Я не жартую.

Сьогодні у нас є супербыстрые машини “зрозуміло, вони можуть працювати при 60 fps”. Однак кількість розчарованих швидкістю ігор користувачів як ніколи велика. Як таке можливо? Проблема виявляється не в тому, що ігри не можуть працювати досить швидко, а в тому, що вони гальмують, навіть коли можуть працювати швидко!

Якщо почитаєте різні ігрові форуми, то напевно знайдете подібні повідомлення:


Можна подумати, що це поодинокі проблеми, але подивіться на статистику пошукових запитів Google:


За останні п’ять років гальма (stutter) стали (відносно) більш серйозною проблемою, ніж швидкість!

(Врахуйте, що це відносні значення. Вони не означають, що в цілому люди запитують про гальмуваннях більше, ніж про частоту кадрів. Вони означають, що запити про частоту кадрів (frame rate) залишаються на тому ж рівні, а кількість запитів про гальмах зростає, особливо останнім часом.)

Десяток років у пошуках причини непояснених гальм


Пацієнт швидше живий, ніж мертвий, просто гальмує трохи більше, ніж потрібно.

Вперше я зіткнувся з цією проблемою ще приблизно в 2003 році. Ми працювали над Serious Sam 2, і користувачі почали відправляти нам звіти про те, що вони тестували щось на порожньому рівні, і при переміщеннях миші руху не були плавними. Це супроводжувалося дуже характерним паттерном на графіку частоти кадрів, який ми прозвали «кардиограммой».

Ми думали, що десь в коді є баг, але не могли його знайти. Здавалося, що проблема з’являється і зникає випадковим чином — після перезапуску програми, перезавантаження машини… а потім гравець міняв якусь опцію швидкості і вона зникала. Потім гравець знову включав цю опцію, але проблема не поверталася. Вона була схожа на привида.

Очевидно, що ця проблема виникала не тільки у нас. Спостерігаючи за тими ж проблемами в інших іграх, ми почали думати, що винні драйвери. Але це відбувалося на відеокартах різних виробників. Навіть на різних API (OpenGL, DirectX 9, DirectX 11…) — єдиним спільним у них було те, що вони з’являлися на різних машинах, в деяких сценах… іноді.


Нессі, бігфут… майже настільки ж невловимі, як і проблема «кардиограммой».

Ми випустили ще кілька ігор, але це дивна поведінка раніше з’являлося і зникало. Деяких користувачів воно дратувало, і ми рекомендували їм змінити опції швидкості — іноді це допомагало, іноді ні. Таке життя, чи не правда?

Але одного разу, в чудовий зимовий день на початку 2013 року мій колега Дін покликав мене, щоб я побачив ще один приклад цієї проблеми, який він на той момент міг відносно стабільно відтворювати. На цей раз проблема виникала на рівні з Serious Sam 3. Ми експериментували з опціями в цій сцені, поки до мене раптово не дійшло. Я зрозумів, у чому була причина! І вона була дуже проста — не дивно, що вона вислизала від всіх протягом десятка років.

Змінивши всього одну дуже просту опцію ігрового движка, ми змогли змусити цю проблему з’являтися і зникати в цій конкретній сцені. Але нам стало відразу ж очевидно, що на її якісне рішення буде потрібно набагато більше зусиль. Зусиль не тільки з нашого боку, але і всієї ігрової екосистеми PC — програмістів драйверів GPU, розробників API, постачальників ОС — кожного.

Дозвольте пояснити.

У чому ж була причина все це час

Я хотів би показати вам її на прикладі сцени з Serious Sam 3, яку ми з Діном досліджували п’ять років тому. Або навіть ще краще — на прикладі тестовій сцени з Serious Sam 2, в якій ми вперше її побачили. Але, на жаль, після заміни «заліза» цей вислизаючий звір може переміститися в іншу сцену. У мене є сцена з The Talos Principle, в якій мені нещодавно вдалося відтворити цю проблему, і я зняв кілька відео, які дозволили проаналізувати її більш докладно.

Але перш ніж ми почнемо, переконайтеся, що ви насправді дивіться відео у 60 fps. Для перегляду зображень нижче прикладів перейдіть в 1080p60, як показано на картинці:


Щоб дивитися відео в 60 fps, перейдіть в YouTube на 1080p60.

Якщо ви все зробите правильно, а ваш комп’ютер і веб-браузер здатні показувати відео з частотою 60 fps, представлене нижче відео має відтворюватися плавно і без будь-яких гальм. Якщо це не так, то саме тому ми і говоримо про це — багато інших додатків теж демонструють таку поведінку, не тільки ігри. Поки я можу тільки рекомендувати вам спробувати подивитися відео на якій-небудь іншій машині, або просто читати текст.

Перевірка, перевірка, раз, два, три… Ви повинні бачити це відео в плавних 60 fps.

А тепер перейдемо до справи. Якщо ви стикаєтеся з гальмами, то найімовірніше це виглядає приблизно так:


Ось як виглядають «гальма при 60 fps». Ми називаємо цей симптом «кардиограммой».

Так, саме так виглядають «гальма», навіть коли гра працює з 60 fps. Ви могли стикатися з чимось подібним у будь-якій сучасній грі, і певно думали, що «гра не оптимізована». Так ось, вам варто переглянути свою теорію (про те, що такі гальма виникають із-за «повільного» візуалізації гри). Якщо гра «занадто повільна», то це означає, що в якісь моменти вона не зможе досить швидко отрендерить один кадр, а монітора доведеться заново показувати попередній кадр. Тому коли ми записуємо відео такої гри в 60 fps, то бачимо «пропущені кадри». (Це такі кадри, при яких наступний кадр не був отрендерен вчасно, тому поточний кадр показується двічі.)

Тепер знову запустіть попереднє відео з гальмами («кардиограммой»), поставте його на паузу і натискайте в плеєрі YouTube клавішу . (точка), щоб переміщатися від кадру до кадру. Спробуйте помітити, коли один і той же кадр показується двічі. Давайте, спробуйте, я почекаю…

Ну як, знайшли? Ні? Дивно, чи не правда?..

Відео виглядає не плавним, коли дивишся всю анімацію в цілому, але коли розглядаєш покадрово, розривів немає!

Як таке може бути?

Дозвольте мені пояснити більш докладно. Ось порівняння ідеально плавного відео і відео з гальмами у вигляді «кардіограми», на 1/20 від початкової швидкості, щоб ми могли бачити окремі кадри:

Зверху правильне відео з 60 fps, знизу «кардіограма». Сповільнене відтворення в 20 разів.

Можна помітити дві речі: по-перше, вони дійсно відтворюються з однаковою частотою — коли зверху є новий кадр, знизу теж є новий кадр. По-друге, вони з якоїсь причини рухаються трохи по-різному — є помітний «розрив» в середині зображення, який стає то більш, то менш помітним.

Уважний глядач може помітити і ще одну цікаву деталь: нижнє — гальмуючий — зображення — яке рахується «повільним»… насправді «випереджає» правильне. Дивно, правда?

Якщо ми подивимося на два сусідніх кадру і їх таймінги (зауважте, що у всіх показаних мною відео були точні таймери (з точністю 1/10 000 секунди), то можемо помітити щось дуже цікаве: перші два кадру ідеально синхронізовані, але третій…


Шість йдуть один за одним кадрів з відео з точними таймінгами. Верхні — правильні, нижні — з «кардиограммой».

…на третьому кадрі ми бачимо, що дерево в «гальмує» відео значно випереджає свою копію з правильного відео (обведено червоним). Також можна помітити, що на цей кадр, схоже, пішло більше часу (обведено жовтим).

Постійте… якщо відео «повільніше», і на кадр «пішло більше часу», то як він може випереджати правильний?

Щоб розібратися в цьому, вам потрібно зрозуміти, як у наш час ігри та інші інтерактивні тривимірні додатки реалізують анімації і візуалізації. (Досвідчені розробники повинні пробачити мене за те, що я надокучаю їм з очевидними речами, але мені потрібно бути впевненим, що текст зможуть зрозуміти всі читають цей текст гравці.)

Коротка історія таймінгів кадру

Давним-давно, в далекій-далекій галактиці… Коли розробники створювали перші відеоігри, вони зазвичай підлаштовувалися під точну частоту кадрів, з якою працює дисплей. У регіонах NTSC, де телевізори працювали при 60 Гц, це означало 60 fps, в регіонах PAL/SECAM, де телевізори працювали при 50 Гц, це означало 50 fps. Вони навіть і думати не могли про якийсь можливості «пропуску кадру».

Більшість ігор було дуже вилизаними і спрощеними концептами, які працювали на конкретному обладнанні, зазвичай на аркадном комп’ютері, або на “домашньому мікрокомп’ютері”, на зразок ZX Spectrum, C64, Atari ST, Amstrad CPC 464, Amiga і т. д. По суті, розробник створював дизайн, реалізовував і тестував гру під конкретну машину і конкретну частоту кадрів, і був на 100% впевнений, що вона ніколи не пропустить жодного кадру.

Швидкості об’єктів теж зберігалися в одиницях «кадрів». Тому говорили, не на скільки пікселів в секунду повинен рухатися персонаж, а на скільки пікселів за кадр. Відомо, що в Sonic The Hedgehog для Sega Genesis швидкість обертання, наприклад, була дорівнює 16 пікселям за кадр. У багатьох ігор навіть були окремі версії для регіонів PAL і NTSC, у яких анімації вручну малювалися спеціально під 50 fps і 60 fps. По суті, запуск при будь-якій іншій частоті кадрів навіть не розглядалося.

Коли ігри почали працювати на більш різноманітних машинах — зокрема, на PC з розширюваним і замінним «залізом» — розробники більше не могли знати, з якою частотою кадрів тепер буде працювати гра. Доповніть це тим фактом, що ігри стали більш складними і непередбачуваними. Найвизначнішими в цьому плані стали 3D-ігри, які відрізнялися різноманітністю складності сцен, іноді навіть залежить від гравця. Наприклад, всім подобається стріляти в бочки з паливом — при цьому відбувається величезний вибух, красиві ефекти… і неминуча просадка кадрів. Але тут ніхто не проти пропуску кадрів, тому що це так цікаво.

Тому було дуже складно передбачити, скільки займе симуляція і рендеринг одного кадру. (Варто зауважити, що на сучасних консолях «залізо», як і раніше незмінно, але самі ігри все одно часто непередбачувані і складні.)

Якщо ми не можемо бути впевненими, при якій частоті кадрів буде працювати гра, нам доводиться вимірювати поточну частоту кадрів і постійно адаптувати фізику і швидкість анімації гри. Якщо один кадр займає 1/60 секунди (16,67 мс), а персонаж біжить зі швидкістю 10 м/с, то в кожному кадрі він переміщується на 1/6 метра. Але якщо кадр перестає займати 1/60 секунди, а замість цього раптово почав займати 1/30 секунди (33,33 мс), то треба почати переміщати персонажа на 1/3 метра (у два рази «швидше») за кадр, щоб на екрані він продовжував рухатися з уявною постійною швидкістю.

Як же гра це робить? По суті вона вимірює час на початку одного кадру, а потім на початку наступного і віднімає. Це досить простий спосіб, але він дуже добре працює. Тобто вибачте, він працював добре. В 90-х (згадайте фразу «35 fps вважалися пристойним значенням для серйозних мережних боїв» з початку статті), людей більш ніж влаштовував цей спосіб. Але в той час графічна карта (не забувайте, їх навіть ще не називали GPU) була дуже «тонким» елементом «заліза», і попаданням об’єктів на екран безпосередньо керував основний ЦП. Якщо в комп’ютері не було 3D-прискорювача, процесор навіть сам перетворювати ці об’єкти. Тому він точно знав, коли вони з’являться на екрані.

Що відбувається сьогодні

З часом у нас з’явилися більш складні GPU, і вони ставали все більш і більш “асинхронними”. Це означає, що коли ЦП віддає GPU команду промалювати щось на екрані, то GPU просто зберігає цю команду в буфер, щоб ЦП займався своїми справами, поки GPU виконує рендеринг. В результаті це призвело до того, що коли ЦП повідомляє GPU, що «це кінець кадру», GPU просто зберігає повідомлення як ще один фрагмент даних. Але він не відноситься до нього як чогось особливо строковим. Та і як він може — адже йому ще потрібно обробляти частина з раніше переданих команд. Він покаже кадр на екрані, коли закінчить всю роботу, яку йому дали раніше.

Тому коли гра намагається обчислити таймінги, віднімаючи мітки часу початку двох кадрів, то актуальність цього, грубо кажучи… вельми сумнівна. Давайте повернемося до нашого прикладу з коротких відео. У нас є ці кадри, де камера переміщається уздовж дерев:


Шість йдуть один за одним кадрів з відео з точними таймінгами. Верхні — правильні, нижні — з «кардиограммой».

Тепер згадайте те, що я говорив про таймінгах і переміщеннях. У перших двох кадрах таймінг кадру дорівнює 16,67 мс (тобто 1/60 секунди), і камера рухається на одну і ту ж величину і зверху, і знизу, тому дерева синхронізовані. На третьому кадрі (внизу) гра побачила, що час кадру одно 24,8 мс (що більше 1/60 секунди), тому вона думає, що частота кадрів знизилася і поспішає перемістити камеру трохи далі… тільки для того, щоб виявити, що таймінг четвертого кадру становить всього 10,7 мс, тому камера рухається тут трохи повільніше, і дерева знову більш-менш синхронізовані. (Синхронізація повністю відновиться тільки через два кадру.)

Тут відбувається наступне: гра вимірює те, що вона вважає початком кожного кадру, а ці таймінги кадрів іноді з різних причин коливаються, особливо на такий високонавантаженої багатозадачної системи, як PC. Тому в деякі моменти гра думає, що не встигає забезпечити 60 fps, і в якісь моменти генерує кадри анімації, розраховані на меншу частоту кадрів. Але з-за асинхронної природи роботи GPU він насправді встигає забезпечити 60 fps для кожного окремого кадру цієї послідовності.

Саме це ми сприймаємо як гальма — анімація, згенерована під змінну частоту кадрів («кардіограму») насправді відображається з правильною постійною частотою кадрів.
Тобто, по суті, тут немає ніякої проблеми — все дійсно працює плавно, просто гра про це не знає.

При цьому ми повертаємося до початку статті. Нарешті виявивши причину проблеми (насправді, це ілюзія проблеми — адже її насправді немає, так?), ось що ми зробили для перевірки:

Спочатку ми спостерігаємо «кардіограму», а потім використовуємо невеликий трюк, щоб позбутися від неї.

У першій частині відео ви з самого початку можете побачити проблему з «кардиограммой». Потім ми змінюємо «магічну» опцію, після чого все стає ідеально плавним!

Що ж це за чарівна опція? В Serious Engine ми називаємо її sim_fSyncRate=60. Якщо говорити простими словами, то це означає «повністю ігнорувати всі ці заморочки з таймінгами і прикинутися, що ми завжди заміряємо стабільні 60 fps». І завдяки цьому все працює плавно — просто тому, що всі і так працювало плавно! Єдина причина, по якій все виглядало гальмуючим — це помилкові таймінги, використовувані для анімації.

І на цьому все? Досить зробити так і все стане чудово?

Невже рішення настільки просте?

На жаль — ні. Це всього лише тест розробників. Якщо ми перестанемо заміряти частоту кадрів в реальних ситуаціях і просто будемо припускати, що вона завжди дорівнює 60, то коли вона впаде нижче 60 — а на PC вона рано чи пізно обов’язково впаде з тих чи інших причин: ОС запустить якийсь фоновий процес, включиться економія енергії або захист від перегріву GPU/ЦП… хто знає — то все сповільниться.

Отже, якщо ми змінюємо частоту кадрів, то виникають гальмування, якщо немає — то все в якісь моменти може сповільнюватися. Що ж нам робити?

Реальне рішення полягає в тому, щоб вимірювати не початок/завершення візуалізації кадру, а час відображення картинки на екрані.

Як же нам повідомити грі, що зображення кадру вже показується на екрані? Ви можете здивуватися, дізнавшись це, але на сьогоднішній день такого способу немає!
Це шокує, я розумію. Можна було очікувати, що це базова функція кожного графічного API. Але виявляється, в процесі поступових поліпшень тут і там всі, по суті, втратили сам сенс проблеми. Ми всі забули про тонких деталях своєї роботи, продовжуючи повторювати одне і те ж, а графічні API еволюціонували у всіх інших аспектах, крім цього: додаток ніяким чином не може точно дізнатися, що кадр насправді відображається на екрані. Ми можемо дізнатися, коли закінчиться його рендеринг, але не коли він буде показаний.

Що далі?

Але не хвилюйтеся, все не так похмуро. Багато людей у графічній екосистемі активно працюють над реалізацією підтримки правильного таймінгу кадрів для різних API. Для Vulkan API вже є розширення під назвою VK_GOOGLE_display_timing, яке продемонструвало свою корисність в реалізації proof of concept. Однак воно доступне тільки для обмеженого асортименту обладнання, в основному на Android і Linux.

Вже ведеться робота по створенню аналогічних і більш якісних систем для всіх основних графічних API. Коли вона закінчиться? Складно сказати, тому що проблема лежить досить глибоко всередині різних підсистем ОС.

Однак я запевняю вас, що Croteam невтомно працює над тим, щоб ця проблема була вирішена якомога швидше, і всі учасники екосистеми інтерактивної графіки розуміють і підтримують наші зусилля.

Ми прагнемо зробити це рішення доступним для широкої аудиторії, і коли це трапиться, ми підготуємо апдейт The Talos Principle з реалізацією цієї функції.

Різні перешкоди і інші подробиці

Останню фразу можна вважати кінцем основного тексту. Нижче представлені «бонусні розділи», практично не пов’язані один з одним і основним текстом. Ймовірно, я буду оновлювати їх, коли ситуація стане змінюватися, або в разі виникнення більш складних питань, що потребують вирішення у найближчому майбутньому.

«Композитор»


Ефект матового скла? Так, для нього нам безумовно потрібен композитор. Він просто-таки необхідний, правда?

За лаштунками з усім цим пов’язаний концепт під назвою «композитний менеджер вікон», або композитор. Це система, існуюча тепер у всіх ОС, яка дозволяє вікнам бути прозорими, мати розмитий фон, тіні, спливаючі зверху вікна Skype і т. д. Композитори навіть можуть відображати вікна в 3D. Для цього композитор перехоплює керування над останнім етапом створення картинки кадру і вирішує, що йому зробити з неї, перш ніж вона потрапить на екран монітора. Це ще більше все ускладнює.

В деяких ОС композитор можна відключити в повноекранному режимі. Але це не завжди можливо, а навіть якщо й можливо, то хіба можна заборонити запускати гру у віконному режимі?

Керування живленням і тепловиділенням проти складності візуалізації

Ми також повинні враховувати, що сучасні ЦП і GPU не працюють з фіксованою частотою, а мають системи, що змінюють їх швидкості у відповідності з навантаженням та температурою. Тому гра не може просто припустити, що GPU і ЦП будуть мати однакову швидкість в кожному кадрі. З іншого боку, ОС і драйверів не можуть очікувати, що у гри в кожному кадрі буде однаковий обсяг роботи. Щоб врахувати це, необхідно розробити складні системи для спілкування цих двох сторін.

А хіба ми не можемо просто…

Швидше за все немає. 🙂 Зазвичай вимірювання часу GPU вважається альтернативою таймінгів дисплея. Але воно не враховує наявність композитора і той факт, що жоден з таймерів візуалізації GPU насправді не синхронізований безпосередньо з оновленням дисплея. Для ідеальної анімації нам абсолютно точно треба знати час відображення зображення, а не час завершення рендеринга.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Close