Чим GearVR відрізняється від картонної коробки, або гонитва за latency

Чим GearVR відрізняється від картонної коробки, або гонитва за latency

Disclaimer: пост написаний на основі неабияк відредагованих логів чату closedcircles.com, звідси і стиль викладу, і наявність уточнюючих питань.

Головний термін який треба знати щодо VR - motion-to-photon latency.

Інакше кажучи, затримка між поворотом голови і останнім фотоном зображення (відмальованого з ракурсу нового положення голови) покинув екран.

Емпірично виведено що motion-to-photon latency 20 msec і нижче дозволяє досягати presence - тобто відчуття що рухаєш головою у віртуальному світі.

Важливі значення менше 20 ms чи ні - незрозуміло, але загалом мета - досягти 20.

GearVR правдами і неправдами досягає, і я розповім як.

Заздалегідь прошу вибачити за вільне використання англійських термінів упереміш з транслітерованими і перекладеними.

GearVR це якщо хто не знає headset в який втикається телефон. У самому хедеті екрану немає - там є лінзи і IMU.

IMU - це стандартний gyroscope + accelerometer. Чи є в GearVR магнетометр я точно не знаю, начебто ні - але в будь-якому випадку на практиці магнетометр не дуже важливий.

Є стандартні способи отримувати орієнтацію пристрою за допомогою gyro і accelerometer, я зупинятися на цьому не буду.

Важливий момент: У GearVR свій IMU, який дає нові дані про орієнтацію на частоті 1000 Hz з мінімальним latency (швидше за все близько 1 ms).

Це в рази краще вбудованих сенсорів в телефонах (в Samsung Note 5 gyro частоти 200 Hz і latency 10-15 ms).

А як воно харчується? Від телефону?

Наскільки я розумію, харчується від телефону через USB - там власне майже заліза всередині немає, так що напевно витрата мінімальна.

Значить, базова розстановка - у нас є IMU дані, ми їх прочитали на початку кадру і треба відрендерити два кадри (стерео картинку). У GearVR немає positional tracking - ми знаємо тільки орієнтацію голови.

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

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

Як вже сказано, в GearVR є дві лінзи - по одній на око. Лінзи потрібні з двох причин:

  1. Збільшення FOV. Якщо поміряти FOV екрану приліпленого до голови то виходить недостатньо високе значення - хочеться 90 + градусів на кожне око, щоб екран міг бути досить маленьким.
  2. Зміна фокусної оптичної відстані.

Якщо б не було лінзи, то різні пікселі були б у різних фокальних точках, тому що екран дуже близько і різниця у відстанях різних пікселів досить суттєва.

Лінза «» випрямляє «» промені йдуть з ока, виходить відчуття оптичного фокусу на нескінченності:

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

Тому ми візьмемо картинку отриману стандартним 3D рендером і її при виведенні на екран спотворимо, щоб після спотворення лінзи вийшло приблизно те ж саме.

Для цього зазвичай малюється distortion mesh який виглядає приблизно так:

Плюс ще якщо у вас досить багато зайвого часу на GPU, то можна в цьому ж проході спробувати скоригувати chromatic aberration

(який теж виходить через властивості лінзи).

На практиці на GearVR за замовчуванням цей фільтр відключений - щоб його скомпенсувати треба замість 1 текстурної вибірки в шейдері робити 3, а це мобільне залізо.

Отже, ввідна закінчена, давайте розбиратися з latency:)

У запропонованій схемі ніяких 20 ms не буде і взагалі все буде дуже погано.

Тому що...

  • На початку кадру ми прочитали IMU data - з скажімо 1 ms latency;
  • Потім ми зібрали матриці кадру, відрендерили сцену - це в цілому тривалий процес, особливо з урахуванням якості OpenGL драйверів на Android;

(це ще 16 мс наприклад якщо 60 FPS render...)

  • Потім ми віддали кадр на GPU - він довго і наполегливо цей самий кадр малює, і потім робить distortion щоб отримати фінальну картинку;

(це ще 16 ms)

  • Потім запускається display scan-out!

Дисплей зрозуміло зовсім не CRT, але при цьому поняття «» променя «» зчитуючого дані все ще залишилося - контролер дисплея зчитує дисплей сканлайн за сканлайном і запалює пікселі.

І щоб вважати весь дисплей - йде 16 мс.

Сканлайни на телефоні зазвичай "короткі" "- коли ви використовуєте VR то телефон в ландшафтному режимі, і сканлайни це вертикальні смужечки, що йдуть зліва направо (ну або справа наліво якщо телефон перевернуть...)

Відповідно найлевіший сканлайн (у лівому оці) оновлюється на 16 мс раніше ніж найправіший (у правому оці).

  • І нарешті, є ще одна радість:

Описаний механізм - стандартний double buffering...

У доблесній операційній системі насправді triple buffering.

Тобто. коли GPU домальовує картинку то вона віддається не на дисплей а в композитор, який її можливо поєднує з іншими вікнами ітп.

І виходить ще один кадр (16 ms) затримки.

У результаті коротше вийшло щось типу 65 ms latency

Що неймовірно більше ніж 20.

Плюс є ще одна проблема...

Зазвичай дисплей працює в full persistence mode - а саме, сканлайн запалюється новими кольорами, і потім залишається в такому режимі фактично весь кадр - тобто 16 мс.

Виявляється, що за 16 мс можна досить далеко повернути голову, і в результаті цей сканлайн оком реєструється в різних точках retina з одним і тим же кольором.

І виходить smearing при повороті голови.

Значить якщо все це не перемагати - GearVR гри виглядали б як Google Cardboard.

Треба перемагати.

Ключовою технологією є технологія яку Oculus називає Time Warp.

Ще вона називається reprojection, і напевно як-небудь ще, але мені подобається слово timewarp.

Ідея в наступному - якщо взяти картинку відрендерену камерою С, потім взяти камеру C'яка є камерою C повернутою (але не зміщеною) на не дуже велику відстань (одиниці-десятки градусів), і відрендерити їй картинку...

Те виявляється, що другу картинку з першої можна отримати простим 2D перетворенням.

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

Якщо зафіксувати матриці і повертати голову вліво-вправо то на невеликих кутах повороту той факт що ти матриці зафіксував неочевидний.

Це власне головний тест того що time warp працює коректно

(зафіксував матриці свого рендера і дивишся чи можна плюс-мінус 5 градусів вертіти головою).

Можна і більше ніж 5 градусів але там вже багато інформації брати нізвідки тому більша частина екрану стає чорною.

Власне «» трясти девайс «» це стандартний stress test для хедсетів в яких є positional tracking - фіксуєш голову а далі руками високочастотно зрушуєш хедсет на голові вліво-вправо, і значить картинка повинна бути стабільною - для цього власне і треба sub-20 ms (ну і якісний трекінг).

Слухай, а якщо на Gear VR рендерити з фіксованою матрицею в'ю\проджекшен і включити репроджекшен, а потім почати трясти девайс, картинка на ньому труситься?

Ммм, «трясти» - погано т. к. немає positional tracking і що буде незрозуміло

Ну якщо немає positional tracking, то тряска - це буде просто зміна кута, нехай навіть маленька.

Ну так, але в реальному світі буде змінюватися позиція а в хедеті ні - тобто стабільної картинки не вийде.

Можна пробувати високочастотно крутити голову вліво-вправо якщо шия не відвалиться:)

Timewarp робиться просто - ми вже малюємо той самий distortion mesh.

Отже, ви можете просто повернути текстурні координати у верхньому шейдері (3D-просторі).

Піксельний шейдер не змінюється, timewarp виходить фактично безкоштовним.

На скільки повертати?

На різницю між поворотом голови зараз і поворотом голови з яким була відмальована вихідна картинка.

Чим більше поворот - тим більше буде порожнього простору, його можна заповнювати чорним кольором

(або можна робити clamp адресацію, на невеликих кутах повороту виходить трохи краще ніж чорне, але на більших виходить дивне).

Тепер, коли у нас є механізм "довороту" "картинки, давайте розберемося як би його можна використовувати щоб зменшити latency.

Спочатку розберемося з full persistence - це технічно не впливає на motion-to-photon, але таки треба прибирати.

Для телефонів які підтримує GearVR написаний спеціальний драйвер дисплея який включає low persistence mode:

Кожен сканлайн запалюється як раніше потрібними кольорами, а далі майже відразу - я точно не знаю через скільки, але скажімо 3-4 ms - тухнет (стає чорним).

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

Кажуть що кордон сприйняття варіюється від десь 50 Hz морганія до 85 Hz - що є однією з головних причин чому десктопні VR хедсети (Rift/Vive) працюють з частотою 90 Hz.

Плюс low persistence в хедеті сприймається краще ніж просто моргаюча з частотою 60 Hz панель в реальному світі, тому що начебто моргає все що ти бачиш, і мозок адаптується...

Reference links і відео:

https://www.youtube.com/watch?v=_FlV6pgwlrk

https://en.wikipedia.org/wiki/Persistence_of_vision

https://en.wikipedia.org/wiki/Flicker_fusion_threshold

Ааа, тобто тому що ми вже «» прибрали «» зображення (погасили екран), то це краще сприймається? А проблема - це коли він горить всі 16 мс, а голова рухається?

Так точно.

Є штука, яка називається vestibulo ocular reflex:

Коли ти повертаєш голову у тебе автоматично око повертається в зворотний бік.

Тому при поворотах голови ти можеш фіксувати очі на об'єкті і наприклад читати при не дуже сильних поворотах без проблем.

І значить якщо у тебе пікселі горять тривалий час, то при проекції на retina одного пікселя виходить смужечка через поворот ока і виходить smearing/ghosting.

А якщо піксель відразу майже вимикати то smearing не з'являється, і зважаючи на якісь особливості зору високочастотне моргання як моргання не сприймається - я не знаю конкретно чому.

VOR це взагалі крута штука, там натурально безпосередньо з'єднаний вестибулярний апарат з очними м'язами.

Участі мозку практично не потрібно, і ultra low latency:)

Тепер при русі головою у нас більше немає smearing, але все ще є 65 ms latency...

Давайте тепер розберемося як насправді в GearVR працює рендер.

Те що я написав вище про те що на початку рендер кадру ми читаємо дані з IMU, потім генеруємо OpenGL команди і потім віддаємо їх GPU який малює кадр - залишається валідним.

Це ігровий тред і ігровий GL контекст.

Є ще один тред який створює GearVR SDK і у нього є свій GL context, що працює в high priority режимі

(на нових версіях Android такий контекст можна тепер створити самому за допомогою EGL розширення).

High priority режим означає, що команди, віддані GPU на цьому контексті, почнуть виконуватися GPU практично відразу.

Я не знаю деталей GPU scheduling на конкретно GPU які підтримують GearVR - припущу, що там preemption granularity це draw call - тобто якщо у вас немає величезних 10 msec draw calls то можна за одну-дві мілісекунди закінчити поточний і почати наступний.

На десктопному GPU у NVidia ця гранулярність зазвичай в draw call а на AMD якщо я не помиляюся 1 wave (для compute), ну і десктопні вендори працюють над поліпшенням т. к. ця фіча теж важлива на десктопному VR...

Друга особливість цього VR контексту в тому...

Що він малює відразу в front buffer - в той самий буфер з якого відбувається scan out - таким чином пропускаючи всі композитори та іншу дурницю.

Як ми вже розібралися, scan out займає 16 мс і йде по сканлайнах один за одним в суворому порядку.

Сканлайни - це вертикальні смужки, які йдуть зліва направо (в ландшафтному режимі), тому половина смужок містять картинку з лівого ока (скануються за 8 мс), а друга половина смужок містить картинку з правого ока (сканується за 8 мс).

А значить можна спробувати малювати в праве око поки дисплей сканує лівий і навпаки.

Зазначу про всяк випадок - це не означає що ми будемо малювати прямо трикутники з кадру гри!

Тут потрібен дуже акуратний таймінг і чіткі гарантії, тому гра там як вийде малює кадр, а VR контекст буде його коригувати за допомогою time warp.

Отже, цей самий VR тред прокидається двічі - коли vsync інтервал почався, і через 8 мс після цього (в середині scan out).

Читає свіжі IMU дані (з latency 1 ms), а далі генерує дві матриці коригувального повороту.

Чому дві?

Через scan out лівий сканлайн лівого ока і правий сканлайн лівого ока рознесені в часі на 8 мс (з точки зору photon emission time), тому в ідеалі треба їх коригувати по-різному.

Тому ми читаємо дані IMU (latency 1 ms), і генеруємо дві орієнтації голови використовуючи gyroscope дані для prediction вперед.

Першу ми передикнемо вперед на 8 мс, другу на 16 мс (зараз буде зрозуміло чому).

Потім самитим draw call в high priority context в якому половина того distortion mesh зверху для потрібного ока і сподіваємося що GPU його досить швидко підбере і відмалює.

Через 8 мс після того як ми згенерували орієнтації scan out дійде до тієї частини екрану в яку ми тільки що намалювали дані і почне її показувати.

У підсумку повна latency до photon emission крайнього зліва сканлайну кожного ока виходить приблизно 9 ms, а крайнього праворуч - приблизно 17 ms.

(ті хто 30 років тому на Amiga писали race-the-beam rasterizers - прослізіться)

Власне якщо порізати distortion mesh на вертикальні смужечки і мати більш чіткі timing гарантії - як я розумію, з поточним GPU це неможливо - то можна було б зменшувати motion-to-photon до < 10 ms.

Як видно, половина (8 ms) поточної latency це типу safety буфер щоб вкластися і засабмитить draw call на GPU і щоб він його відмалював, і половина - це найгірший scanout latency для сканлайну.

Якщо різати на смужечки і мати більш чіткий таймінг то можна це спробувати вирішувати.

Або прямо інтегрувати time warp в scanout контролер, операція warp - це просто білінійний семплінг текстури, не дуже складно.

Але за великим рахунком поточний latency - good enough. Якщо збільшити частоту дисплея до 90 Hz щоб прибрати проблеми з морганням для більш чутливих людей, то вийде на тій же технології відразу приблизно 10 ms.

Так, 8 ms це взагалі кажучи довільний буфер

Можна теоретично і менше

Теоретично можна менше, практично помітно менше напевно стає небезпечно.

Якщо preemption на draw call basis то треба враховувати це теж

Особливо коротше в VR на mobile є тенденція запихати всю сцену в 1-2 draw calls

Тому що OpenGL повільний, Unity повільний, ітп.

Отже, прокинулися відразу після vsync - в цей час scan out почав процесити ліве око - і намалювали праве око в front buffer.

Потім заснули до середини vsync interval, прокинулися - в цей час scan out почав процесити праве око - і намалювали ліве око в front buffer.

Ad infinitum.

Зазначу, time warp має кумедну проблему...

Він вміє тільки довірнути картинку.

Не вміє її зміщувати - при зміщенні починаються проблеми disocclusion, потрібен depth buffer, більше ресурсів ітп.

Але насправді очі два, і коли ви повертаєте голову - очі зміщуються і повертаються одночасно.

Тому чим більше timewarp корекція тим більш неправильною виходить stereo картинка і тим складніше фокусуватися.

На практиці виходить що time warp на 20-30 msec до видимих проблем не призводить, крім як при величезних швидкостях повороту де convergence перестає працювати, напевно.

Ще пара моментів які GearVR вміє робити, і які в звичайному Андроїді неможливі...

По-перше, можна включити realtime пріоритет своєму рендер потоку (ну і він включається VR потоку)

Щоб всякі там потоки garbage collection не дуже заважали.

По-друге, можна включити (точніше, потрібно) fixed clock frequency.

За замовчуванням частота CPU і GPU варіюється залежно від навантаження та інших факторів.

На практиці в Андроїді ці механізми працюють дуже дивно, і можуть призвести до того, що навантаження яка повинна укладатися в 16 ms на кадр кілька секунд поспіль не вкладається.

Тому SDK вимикає динамічний adjustment і дозволяє вибрати програмі фіксовані frequency scales для CPU/GPU окремо.

Ось, я значить уточню. Що весь цей цикл - він для цього спеціального треду про time warp

А власне рендер програми йому паралелений

Так, я десь там вище це писав. Рендер тред взаємодіє з цим time warp тредом через функцію «» я закінчив саблітити на GPU команди для наступного кадру, тримай його «».

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

І в додатку все ще може бути і 16 msec на CPU в драйвері, і 16 msec на відмальовку

В принципі може. Може бути і 40 ms на CPU.:)

Ось до яких чисел потрібно це зменшувати, щоб решту вивозив time warp?

Тут сказати складно однозначно. Дуже залежить від контенту. Загалом Oculus рекомендує тримати стабільні 60 fps (16 ms cpu/gpu) і розраховувати на time warp для прибирання цих зайвих 20-30 ms latency, і для згладжування раптових frame spikes.

Але якщо у тебе контент плюс-мінус статичний - наприклад, уяви собі adventure game - то в SDK є режим в якому вони очікують що ти віддаєш їм кадри раз в 33 ms а не в 16.

І time warp працює майже ідеально (майже - так як див. вище про рух очей при поворотах)

60 FPS, але 32 msec latency, так?

20-30, там залежить від того як конкретно працює драйвер для GPU який зазвичай tiled, наскільки швидко ти можеш засабмитити всі draw calls для першого frame buffer тощо.

Але загалом 32.

Ти це будеш в основному бачити на позиціях інших об'єктів, якщо будеш.

Тобто. це не відрізняється від стандартних питань latency в звичайних іграх - реакція від натискання на яку-небудь клавішу до руху і т. п., це все не так критично як head tracking.

А, ще забув!

Про Cardboard.

Ось як видно з перерахованого вище - є рівно ОДИН момент в hardware у GearVR.

Хороші сенсори з маленьким latency і великим refresh rate.

Все інше - software.

(є ще лінзи але в GearVR лінзи наскільки я розумію - нічим не виділяються, в cardboard девайсах такі ж рахуй)

А про це можна детально десь почитати? Про лінзу саму. Мені цікаво, чому з об'єктами, які зовсім близько виходить фігня. Там ліва-права картинки розходяться так сильно, що я не можу сфокусуватися. А на реальному об'єкті - можу. Легко тестувати на HTC Vive - контролер до шолома підносиш, потім шолом знімаєш і на нього ж дивишся в реалі.

Там є проблема яка називається vergence-accomodation conflict, може бути через це...

Vergence - це феномен при якому очі повертаються так щоб дивитися на об'єкт:

Якщо об'єкт далеко то очі дивляться прямо;

Якщо об'єкт зовсім близько, вони повертаються до носа, наприклад, якщо об'єкт перед носом.

Accomodation - це те саме фокусування зору, там змінюються оптичні властивості ока щоб фокусуватися (https://en.wikipedia.org/wiki/Accommodation_(eye) ).

І ось значить у VR відбувається наступна штука:

Vergence працює коректно тому що кожному оку дається своя картинка, і якщо об'єкт близько то в лівому і правому очах різні зображення - об'єкт сильно зміщений і очі повертаються як треба.

А accomodation не працює так як лінза одна і вона не налаштовується ніяк тому всі об'єкти на оптичній нескінченності.

Цей конфлікт різними людьми розмовляє по-різному, ну і чим ближче об'єкт тим більше конфлікт.

Судячи з моїх експериментів з сенсорами на різних телефонах з т. зв. latency…

У Samsung і Apple трекінг помітно гірший, але у Nexus 6 трекінг гірше але вже в межах розумного.

(ЕМНИП 4ms refresh interval, avg 3 ms latency)

Тобто. на Nexus 6 - можна робити все що я написав вище і мати experience натурально порівняний з GearVR, але в картонній коробці.

Проблема в тому що практично все що я написав вище не зробити з user mode - потрібно патчити ядро і драйвера.

Тож автори додатків цього робити не можуть, але ось Гугл - цілком.

грав я вчора якраз на Gear VR - на знаю кому як, але мене каламутить, хоча на усвідомленому рівні я лага не помічаю

ну і пікселі величезні!

Головна проблема GearVR з т. зв. motion sickness - відсутність positional tracking.

Коли ти рухаєш голову, картинка цей рух не відображає - це породжує конфлікт між вестибулярною і оптичною системою

Були чутки, що напрямки ресеча на майбутнє в Окулусі - це positional tracking для mobile і AR

Ну я не здивований, positional tracking це missing feature # 1, # 2 і # 3 в gear.

# 4, напевно, full RGB display

Поки всі успішні підходи до positional tracking вимагають зовнішньої камери.

Або купи камер на хедеті як в HoloLens...

ось я пробував обидва що rift dk2 що gear vr - однаково здалися марною іграшкою

ну у меня еще косоглазие.

У мене теж, це особисто мені не дуже заважає.

Це те що я там вище писав про convergence

Особисто мені плюс-мінус пофіг який там IPD в камерах:)

У мене велика частина сприйняття через одне з очей, не через обидва - дивлячись на якому "" сфокусуєшся "

ага - або я 3д погано бачу

у мене було відчуття ніби я всередині проекційної сфери

а не всередині 3д світу

До речі можливо що для людей з косоглазієм positional tracking більш критичний за замовчуванням - коли у тебе є стерео зір то ти відстань до об'єктів сприймаєш власне їм, а коли воно не дуже хороше то головний visual cue це паралакс

А паралакс виходить в основному від руху голови

Особливо в gunjack де туррель навколо тебе

Але тут я злегка out of my depth, не дуже добре уявляю значимість різних факторів сприйняття, так що точно не впевнений.

Я в цілому згоден що мене особисто GearVR не дуже вставляв, на відміну від десктопних experiences.