Пекло візуалізації 1.1:
- Книга 1: Огляд
- Книга 2: Проблеми
- Книга 3: Рішення
- Книга 4: Ув'язнення
Ласкаво просимо до другої книги! Тут ми вивчимо деякі проблеми, які можуть виникнути під час процесу візуалізації. Але, для початку, трохи практики:
Знати про проблему - корисно. Але дійсно відчути проблему набагато краще для розуміння. Давайте спробуємо поставити себе на місце CPU/GPU.
Експеримент
Будь ласка, створіть 10000 маленьких файлів (наприклад, розміром 1 КБ кожен) і скопіюйте їх з одного жорсткого диска на інший. Ця операція займе тривалий час, хоча розмір даних становить всього 9.7 МБ.
Тепер створимо один файл розміром 9.7 МБ і скопіюємо його тим же чином. Ця операція здійсниться значно швидше!
Чому? Адже розмір даних однаковий!
Це правда, але кожна операція копіювання складається з безлічі речей, які треба зробити, наприклад: підготувати файл до переміщення, виділити пам'ять, переміщувати головки читання/записи диска вперед-назад... Все це - накладні витрати для кожної операції запису. Як ви могли випробувати на своїй шкурі, ці накладні витрати величезні, якщо ви копіюєте безліч маленьких файлів. Візуалізація безлічі полігональних сіток (тобто, виконання багатьох команд) значно складніша, але відчувається схоже.
Тепер давайте розглянемо найгірший випадок, який може виникнути в процесі візуалізації.
Найгірший випадок
Мати безліч маленьких полігональних сіток - погано. Якщо вони використовують різні параметри матеріалів, то все стає ще гірше. Але чому?
1. Багато полігональних сіток
Графічний процесор може малювати швидше, ніж центральний процесор відправляти команди.
Основна причина зменшення кількості Draw Call'ів полягає в тому, що графічне обладнання може змінювати і візуалізувати трикутники значно швидше, ніж ви передавати їх. Якщо надсилати невелику кількість трикутників на кожен виклик, то ви виявитеся повністю пов'язаними продуктивністю CPU, а GPU здебільшого буде знаходитися в режимі очікування. CPU не буде в змозі «годувати» GPU досить швидко. [f05]
До всього іншого, кожен Draw Call виробляє деякі накладні витрати (як було сказано вище):
Існують накладні витрати на рівні драйвера щоразу, коли ви робите виклик API, і найкращий спосіб зменшити їх - викликати API якомога рідше. [a02]
2. Безліч Draw Call'ів
Один з прикладів таких додаткових витрат - це буфер команд. Ви пам'ятаєте, що CPU наповнює буфер команд, а GPU читає його? Так, їм доводиться повідомляти про зміни і це також створює накладні витрати (відбувається зміна покажчиків читання/запису, прочитати докладніше ви можете тут)! З цієї причини, може виявитися краще не передавати команди по одній, а спочатку заповнити буфер і передати цілий блок команд графічному процесору. Це збільшує ризик того, що GPU доведеться чекати поки CPU закінчить будувати блок команд, але при цьому зменшує витрати на комунікацію.
У GPU (на щастя) є багато речей, які треба робити, поки CPU складає новий буфер команд (наприклад, обробка попереднього блоку). Сучасні процесори можуть заповнювати відразу кілька буферів команд незалежно один від одного, а після послідовно передавати їх GPU.
Вище було описано лише один приклад. У реальному світі не тільки CPU, GPU і буфери команд перемовляються між собою. API (DirectX, OpenGL), драйвери і багато інших елементів включені в цей процес, що не робить його простішим.
Ми обговорили тільки випадок з багатьма полігональними сітками, які використовують один і той же матеріал (Render State). Але що станеться, коли ми захочемо візуалізувати об'єкти з різними матеріалами?
3. Багато полігональних сіток і матеріалів
Скидання конвеєра.
Змінюючи стан, іноді доводиться частково або повністю скидати конвеєр. З цієї причини зміна шейдера або параметрів матеріалу може виявитися дуже дорогою операцією [...] [b01]
Ви думали, гірше вже не буде? Отже... якщо ви використовуєте різні матеріали з різними полігональними сітками, ви не можете згрупувати команди візуалізації. Ви вказуєте Render State для першої сітки, командуєте відобразити її, потім задаєте новий Render State, відправляєте наступну команду візуалізації і так далі.
Я пофарбував команду «Change State» в червоний, так як а) вона дорога і б) для читабельності.
Встановлення значень Render State'a іноді (не завжди, залежить від параметрів, які ви хочете змінити) тягне за собою скидання всього конвеєра. Це означає, що кожна полігональна сітка, яка обробляється в даний момент (з поточним Render State'ом), повинна бути відображена до того, як можна буде приступити до візуалізації наступної (з новим Render State'ом). Це виглядає як на відео вище.
Замість того, щоб брати величезне число вершин (наприклад, комбінуючи кілька сіток з однаковим Render State'ом. Цю оптимізацію я поясню пізніше), відображається невелика кількість перед операцією зміни Render State'a, що очевидно погано.
Між іншим: Оскільки CPU потребує певного мінімального часу для встановлення параметрів Draw Call'a (незалежно від розміру полігональної сітки), можна вважати, що немає різниці у відображенні 2 або 200 трикутників. GPU - до біса швидкий і поки CPU підготує новий Draw Call, трикутники вже стануть новоспеченими пікселями на екрані. Звичайно, це «правило» зміниться, коли ми будемо говорити про комбінування декількох маленьких полігональних сіток в одну велику (ми розглянемо це пізніше).
Мені не вдалося знайти свіжі дані про кількість полігонів, які можна візуалізувати «безкоштовно» на сучасних графічних картах. Якщо ви знаєте що-небудь про це або робили недавно які-небудь вимірювання, будь ласка повідомте мені!
4. Полігональні сітки та мультиматеріали
Що якщо полігональній сітці призначено не один матеріал, а два або більше? В основному, сітка рветься на кілька шматків, а потім по частинах «згодовується» буферу команд.
Звичайно ж, це тягне додаткові Draw Call'и на кожен елемент сітки.
Сподіваюся, мені вдалося дати вам побіжне уявлення про те, що поганого у великій кількості полігональних сіток і матеріалів. У наступній книзі ми розглянемо деякі рішення, нехай все це і виглядає жахливо. Але існують прекрасні ігри, які доводять, що описані вище проблеми якось вдалося подолати.
Кінець
[a02] GPU Programming Guide GeForce 8 and 9 Series
[b01] Real-Time Rendering: Стр. 711/712
[f05] Why are draw calls expensive