НОУ ІНТУІТ, Лекція, Візуалізація примітивів

2.3. Введення в HLSL

У цьому розділі ми познайомимося з мовою High Level Shader Language (мова високого рівня для програмування шейдерів) або скорочено HLSL . HLSL використовується для програмування вершинних та піксельних процесорів графічного прискорювача. Програма для вершинного процесора називається вершинним шейдером, а піксельного процесора - піксельним шейдером. Підтримка шейдерів вперше з'явилася у 8-й версії DirectX. Правда шейдери DirectX 8 мали багато обмежень і програмувалися низькорівневою ассемблеро-подібною мовою, проте в 9-й версії DirectX можливості шейдерів значно зросли, що призвело до появи потреби в мовах високого рівня. Було створено кілька мов високого рівня для написання шейдерів DirectX 5 Наприклад, NVIDIA Cg , проте стандартом де-факто стала мова HLSL , що входить до складу DirectX 9 . У XNA Framework шейдери так само пишуться мовою HLSL, а сам XNA Framework під час роботи з шейдерами на платформі Windows значною мірою спирається на функціональність DirectX.

Так мова HLSL тісно пов'язана з архітектурою графічного процесора, ми почнемо цей розділ із знайомства з основами архітектури сучасного графічного процесора.

2.3.1. Графічний конвеєр

У розділі 2.2 ви отримали уявлення про візуалізацію примітивів засобами XNA Framework. При цьому процес візуалізації зображення (метод GraphicsDevice.DrawUserPrimitives ) залишався для нас чорною скринькою. Настав час надолужити втрачене. Отже, при виклику методу GraphicsDevice.DrawUserPrimitives вершини з графічного буфера надходять на обробку в графічний конвеєр XNA Framework , що є послідовністю ступенів (простих операцій), що виконуютьсянад вершинами у порядку (рисунок 2.2). Розглянемо ці щаблі у порядку виконання:

примітивів

    На початку вершини обробляються вершинним процесором за програмою, яка називається вершинним шейдером. На виході з вершинного процесора виходять звані трансформовані (перетворені) вершини. До вершин можуть бути прив'язані різні параметри: колір вершини, текстурні координати 6 Текстурні координати будуть розглянуті в розділі 5.x. і так далі. Координати трансформованих вершин задаються в логічній системі однорідних координат , званої clip space . Однорідні координати вершини визначаються чотирма числами: (x, y, z, w). Переведення однорідних координат у звичайні геометричні здійснюється шляхом поділу перших трьох компонентів на четвертий компонент w: (x/w, y/w, z/w. Наприклад, вершині з однорідними координатами (1, 2, 3, 4) у тривимірному просторі відповідає точка з координатами (1/4, 2/4, 3/4) = (0.25,0.5,0.75) Використання четвертого компонента обумовлено рядом особливостей алгоритмів візуалізації тривимірних зображень, що використовуються в 3D графіку. В цьому випадку нижньому лівому куті клієнтської області форми відповідає точка з координатами (-1, -1, 0, 1), правому верхньому куті клієнтської області - (1, 1, 0, 1), а центру клієнтської області - відповідно (0, 0, 0, 1).

DirectX дозволяє програмісту задавати координати вершин у віконних координатах. У цьому випадку при виклику методу Device.DrawUserPrimitives вершини відразу надходять на третю стадію графічного конвеєра, минаючи першу і другу стадії. Managed DirectX та XNA Framework Beta 1 дозволяють задавати координати у віконній системі координат, проте починаючи з XNAFramework Beta 2 ця функціональність чомусь зникла. Очевидно, це зумовлено прагненням зробити XNA Framework якомога більш платформо-незалежним.

Наступний етап – обробка пікселів піксельним процесором з використанням програми, яка називається піксельним шейдером. На вхід піксельного процесора подаються параметри пікселя (колір, текстурні координати і т.д.), отримані шляхом інтерполяції відповідних вершинних параметрів уздовж поверхні примітиву. Після обробки вхідних параметрів піксельний процесор повертає колір пікселя.

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

Усі сучасні графічні підсистеми побудовані за принципом конвеєра. Ідея конвеєра, вперше реалізована Генрі Фордом, полягає в наступному: якщо складний процес розбити на послідовність простих операцій (конвеєр), то на виході конвеєра ми отримаємо продуктивність рівну продуктивності найповільнішої операції в цьому ланцюжку. Як приклад конвеєра розглянемо процес виробництва найпопулярніших процесорів AMD Athlon. Створення одного процесора триває близько двох місяців. Відповідно, за умови класичної організації виробничого процесу один підрозділ може випускати близько шести процесорів на рік. Проте, на сучасних фабриках AMD виробничий процес розбивається на 400 стадій. У процесі виробництва кожен майбутній процесор проходить через ці 400 стадій. Щойно процесор проходить поточну стадію виробництва, його місце приходить наступний. У результаті на різних стадіях виробництва одночасно можеперебувати до 400 процесорів. У результаті, на виході конвеєра виходить продуктивність праці близько 400 процесорів на два місяці або 2400 процесорів на рік. Іншими словами, продуктивність праці зростає приблизно в 400 разів.

Сторонньому спостерігачеві може здатися, що за день один конвеєр виробляє близько 7 процесорів (400/60). Але насправді, між надходженням заготовки процесора і виходом готового процесора, як і раніше, минає два місяці. Це явище отримало назву латентність конвеєра. При нормальному функціонуванні конвеєра на цю обставину можна не зважати; проте у разі неполадок латентність конвеєра не забариться. Припустимо, що було виявлено та виправлено дуже небезпечну помилку в архітектурі процесора, після чого виправлена ​​версія процесора негайно надійшла у виробництво. Але, незважаючи на всю оперативність виправлення помилки, перші виправлені зразки процесорів вийдуть з конвеєра лише через два місяці. Адже подібна затримка може завдати фірмі помітних збитків…

Інше наслідок латентності – низька ефективність конвеєра під час виробництва невеликих партій процесорів. Наприклад, при виробництві одного процесора темп виробництва дорівнюватиме 0.017 процесорів на день (один процесор за 60 днів), при виробництві 28 процесорів - 0.44 процесора на день, при 100 процесорах - вже 1.33 процесорів на день і т.д. Більш-менш нормальний темп буде досягнутий лише при виробництві партії з кількох тисяч процесорів (рисунок 2.3).

До речі, графічний конвеєр не є винятком із правил. Він також малоефективний при візуалізації невеликої кількості примітивів. Тому для ефективного використання графічного конвеєра програміст повинен намагатися мінімізуватикількість викликів методу GraphicsDevice.DrawUserPrimitives , візуалізуючи за один присід якнайбільше примітивів.

лекція

2.3.2. Мова HLSL

На початку XXI століття корпорація 3dfx працювала над революційним GPU Rampage, що має на борту масив вершинних та піксельних процесорів. Для програмування цих процесорів Microsoft у тісній співпраці з 3dfx розробила дві схожі ассемблеро-подібні мови, які були включені в DirectX 8 . Мова для програмування вершинних процесорів отримала назву Vertex Shader 1.0 (VS 1.0), а мова для програмування піксельних процесорів – Pixel Shader 1.0 (PS 1.0). Відповідно, програми, написані цими мовами, стали називатися вершинними і піксельними шейдерами 7 Назва шейдер ( shader ) обумовлено застосуванням перших вершинних і піксельних процесорів переважно більш точної передачі гри світла і тіні ( shade ) поверхні об'єктів . На жаль, графічний процесор Rampage так і не надійшов у масове виробництво з фінансових причин: компанія 3dfx була оголошена банкрутом та незабаром куплена NVIDIA, а проект Rampage закритий.

8 Ці шейдери отримали неофіційне позначення Pixel Shader 0.5 вперше з'явилися в GPU NV10 (1999 рік). Проте з низки причин Microsoft не захотіла включити підтримку цих шейдерів у DirectX. В результаті, з точки зору DirectX-програміста, у NV10 відсутня будь-яка підтримка шейдерів. Єдина можливість задіяти шейдери NV10 – скористатися API OpenGL.

Першим дійсно масовим GPU з вершинними та піксельними процесорами став NV20 (NVIDIA GeForce3), що з'явився у 2001 році. Для програмування вершинних та піксельних процесорів NV20 корпорація Microsoftспільно з NVIDIA розробила мови Vertex Shader 1.1 та Pixel Shader 1.1, які є розширеними версіями Vertex Shader 1.0 та Pixel Shader 1.0. Незабаром після NV20 вийшов NV25 (GeForce4), функціональність піксельних процесорів якого була дещо розширена.

Відповідно мова Pixel Shader 1.1 була оновлена ​​до версії 1.328. Потім з'явився процесор GPU R200 (Radeon 8500) корпорації ATI та мова Pixel Shader 1.4, потім R300 (Radeon 9700 Pro) з Vertex Shader 2.0 та Pixel Shader 2.0 тощо.

У результаті до початку 2002-го року на ринку творилася повна плутанина серед мов програмування шейдерів. На щастя Microsoft передбачала подібний поворот, тому заздалегідь зробила мови Vertex Shader і Pixel Shader незалежними від системи команд графічного процесора. Фактично, кожна версія мови Vertex/Pixel Shader є мовою програмування для деякого віртуального процесора, наближеного до деякого реального прототипу. Компіляція шейдера в систему команд фізичного процесора відбувається безпосередньо перед завантаженням шейдера в GPU. Таким чином, мови Vertex Shader та Pixel Shader є аналогами мови IL в .NET.

Незалежність мов Vertex Shader та Pixel Shader від системи команд фізичного процесора теоретично дозволяє GPU виконувати будь-який асемблерний код незалежно від версії шейдера.

Наприклад, GPU R200 корпорації ATI поряд з рідними Pixel Shader 1.4 може виконувати Pixel Shader 1.0, Pixel Shader 1.1, Pixel Shader 1.2 та Pixel Shader 1.3. Це досягається шляхом перекомпіляції чужорідних шейдерів на рідний код. На жаль, зворотне перетворення не завжди можливе. Наприклад, R200 не може виконувати Pixel Shader 2.0, так як програма, що використовує просунуті можливості цієї версії шейдерів не може бутивтиснута в прокрустове ложе архітектури R200.

У міру зростання можливостей GPU програми для вершинних та піксельних процесорів ставали все складніші та складніші. Наприклад, якщо Pixel Shader 1.1 довжина програми не могла перевищувати 16 асемблерних команд, то Pixel Shader 2.0 максимально можливе число асемблерних інструкцій перевищило сотню. Відповідно зростала трудомісткість розробки та підтримки шейдерів з використанням асемблера-подібної мови. Таким чином, виникла реальна потреба переходу на мови програмування шейдерів високого рівня.

У 2002 році Microsoft випустила високорівневу мову програмування шейдерів High Level Shader Language (HLSL). HLSL – це мова програмування високого рівня, призначена для написання програм (шейдерів) для вершинних та піксельних процесорів. HLSL є C-подібною мовою програмування з численними запозиченнями із C++ та C#. У той же час у HLSL є ряд важливих розширень, корисних при програмуванні графічного процесора. Програма, написана на HLSL, компілюється в одну з ассемблеро-подібних мов DirectX. Таким чином, процес компіляції HLSL програми дуже нагадує компіляцію C# -програми спочатку проміжний мову ( IL ), а потім в машинний для конкретного центрального процесора (рисунок 2.4).

Найбільшою логічною одиницею HLSL є ефект (Effect), що зберігається в окремому текстовому файлі з розширенням .fx. В принципі, ефект можна вважати аналогом матеріалу в 3DS MAX. Кожен ефект складається з однієї або кількох технік (technique). Техніка – це метод візуалізації матеріалу. Наприклад, ефект візуалізації мармурового матеріалу може містити три техніки для різних графічних процесорів: техніку High для прискорювачів9 Для програмування піксельних процесорів NV25 (GeForce4) планувалося використовувати мову Pixel Shader 1.2 . Однак після виходу NV25 виявилося, що його функціональність дещо ширша, ніж передбачалося. Відповідно мова Pixel Shader 1.2 виявилася не долею, і незабаром була оновлена ​​до версії 1.3. . Кожній техніці зіставлено піксельний і вершинний шейдер, причому кілька технік можуть використовувати загальний шейдер.

Кількість технік та їх назви можуть бути довільними.