Язык Cg: прямой доступ к 3D-графике

0
576 views

Казалось бы: существует OpenGL — свободно распространяемая спецификация для трехмерной графики. Нужно ли что-нибудь еще? Оказывается, да, поскольку зачастую не все и не всем довольны…

Недовольство чаще всего происходит по линии производительности: “честно” реализованные на OpenGL сцены часто не показывают нужной производительности. Конечно, скорость — это еще не все, но, тем не менее, пользователь часто бывает разочарован производительностью новых видеокарт под OpenGL.

Причина: OpenGL ничего не знает о новых возможностях этих карточек. Большинство игр и деловых приложений до сих пор используют “читинг”, то есть многие эффекты реализуются не вызовами API OpenGL или DirectX, непосредственно передаваемых в видеоакселератор, а с помощью “волшебных” эффектов двух-с-половиной-мерной графики — так же как это делалось в первом Doom’е и Duke Nukem 3D.

Еще один, косвенно связанный с предыдущим, и тоже важный момент — это уровень интерфейса и доступа к возможностям GPU. Уровень OpenGL можно назвать средним: вы можете оперировать готовыми примитивами, до уровня декомпозиции включительно, и программно (средствами CPU) реализовывать остальные операции. Вопрос: насколько линия разделения “софт-хард” является оптимальной в OpenGL или DirectX? Какова степень этой неоптимальности?

Третий вопрос: как утилизировать новейшие (и не очень) специфические возможности, такие как SLI, Shader Model 3 или HDR, которые не входят (и не могут входить) в спецификацию Open GL? Понятно, что спецификация OpenGL никогда не успеет, и даже не будет стараться успеть, за всеми возможностями, появляющимися на рынке. Особенно с учетом того, что два ведущих производителя не координируют свои разработки и независимо создают различные расширения и технологии. Это противоречит концепции платформенной независимости OpenGL, так что только “фирменные” реализации OGL могут использовать фирменные секреты производительности и расширения стандартов.

Наконец, “окончательный” вопрос: как упростить жизнь самим создателям Open GL и Direct X, освободив их от копания в регистрах процессора? В этом плане две основные проблемы. Во-первых, большая удельная цена в “человекогодах” для разработки нового софтвера промежуточного уровня, такого как модули DirectX или расширения OpenGL. ВО-вторых — высокая вероятность ошибок в длинных программах на ассемблере (как известно, количество ошибок прямо пропорционально количеству строк и не зависит от уровня абстракций языка).

Ответом на все эти вопросы и запросы является уровень SDK, сопровождающий современные видеокарточки (в дальнейшем мы не будем говорить “видеокарты”, а станем оперировать коротким профессиональным термином GPU — то есть графический процессор).

Рассмотрим только один из возможных инструментов этого уровня, а именно — nVidia Cg, язык высокого (относительно ассемблера) уровня для доступа ко всем возможностям GPU этого производителя. Сразу же хорошая новость: Cg доступен как на MS Windows, так и для Linux — причем компилятор для Linux доступен и в виде открытого кода.

Основная идея Cg (С for graphics) — с помощью C-подобного языка описывать трехмерные сцены и задействовать максимум из возможных функций данной графической карточки, не используя при этом низкоуровневых ассемблерных конструкций и обращений к портам ввода-вывода. Cg очень специализированный язык: вы не можете (или не захотите) написать на нем текстовый редактор или электронную таблицу. Что можно делать с помощью Cg, так это описывать форму, размер и положение объектов. Сами создатели называют этот язык shading language, что приблизительно можно перевести как “язык для ретуширования”. На самом деле, однако, помимо собственно ретуширования с помощью Cg, можно реализовать и физическое моделирование, композицию и ряд других функций.

Заметное отличие Cg от других языков — ориентированность на поток данных. В качестве данных выступают обычно вершины (vertex’ы) и фрагменты, которые последовательно проходят по конвейеру обработки.

Ваша программа может рассматриваться как функция обратного вызова: когда растеризатор отрисовывает очередной фрагмент, он вызывает ваш код для каждой вершины и фрагмента, чтобы узнать текущее положение и состояние последних. Мы говорим “функция обратного вызова” с тем условием, что после компиляции на CPU программа будет по мере необходимости загружаться (с помощью Cg runtime) в GPU и выполняться там непосредственно на графическом акселераторе. Так что это, конечно, callback, но в качестве вызывающей стороны выступает не операционная система, а firmware GPU. Современные GPU поддерживают истинное программирование, в отличие от “программирования” через передачу параметров в старых видеоакселераторах.

Программы Cg могут быть откомпилированы прямо во время загрузки или предварительно. Естественно, компиляция по требованию во время загрузки открывает широкие возможности для автоматической генерации таких программ.

Главным преимуществом Cg является то, что уровень абстракций и примитивы Cg полностью совпадают с таковыми для самого GPU: уровень трансляции из одной системы ценностей в другую минимален. Это является необходимым условием производительности: например, для обработки изображения типичным размером один миллион пикселей с частотой 100 fps в шести проходах (современные GPU обрабатывают каждый пиксель от двух до шести раз — в зависимости от необходимых эффектов, таких как даунсемплинг и сглаживание) потребуется 600 млн. примитивных операций, каждая из которых включает несколько машинных команд.

Аналогично, векторный процессор выполняет десятки миллионов операций, до того как вершины будут преобразованы в векторы, кривые и точки и вся сцена будет должным образом скомпонована. Ясно, что центральный процессор не может адекватно помочь в реализации каждого из таких вычислений: сам CPU во время отрисовки трехмерных сцен также загружен массой работы — от логического уровня перемещения моделей до расчета ходов от имени искусственного интеллекта.

Хотя Cg и говорит на языке GPU — тем не менее это функциональный язык, не зависящий от конкретных аппаратных средств. Вы указываете, что делать, но не как — так что программа может работать на большинстве современных GPU, равно как и на будущих моделях. Точно так же совершенно за кадром остаются детали реализации, такие как загрузка программы в GPU посредством портов ввода-вывода и т.д.

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

Возникает закономерный вопрос: как компилятор Cg на этапе компиляции может узнать возможности GPU? Ведь совсем не факт, что выполнение будет производиться на той же машине. Решение заключается в использовании профилей возможностей. Все аппаратно реализуемые возможности разделены на классы, называемые профилями: например, CG_PROFILE_VS_2_0 соответствует набору возможностей DirectX 9 vertex shaders 2. Аналогично, CG_PROFILE_ARBVP1 определяет набор OpenGL ARB vertex v.1 и т.д. Имя профиля указывается в виде параметра командной строки компилятора. Это налагает ограничения на возможности и длину программ: то, что позволительно для профиля NV30, может вызвать ошибку для NV2X. Обратите внимание, что профили относятся к встроенным в GPU блокам функций, а не к установленному на вашем компьютере софтверу: если ваша карточка не поддерживает возможности vertex shaders 2, то наличие установленного Direct X 9.0 не поможет выполнить Cg-код, скомпилированный с ключом -profile vs_2_0.

Хотя программы Cg выполняются параллельно с работой основного процессора, сами они по своей природе также являются параллельными. Причина — наличие в современных GPU двух отдельных процессоров для обработки векторной графики (вершин) и для обработки растра (фрагментов). У вас есть любая возможность для комбинирования этих двух конвейеров — вы можете загрузить обе части кода с помощью Cg или использовать код, написанный на ассемблере для любого из процессоров. Можно вообще отключить внешнее программирование и использовать аппаратные параметризованные фильтры в духе OpenGL.

С точки зрения обработки данных ваши программы будут вызываться каждый раз, когда GPU нужно будет обработать вершину или фрагмент. Это техника знакома программистам на Perl, где в качестве входного потока может выступать текст, а программа выполняется для каждой строки.

Программы на Cg воспринимают фиксированные, не зависящие от отдельных элементов изображения, данные, а также переменные, связанные с конкретным элементом. Переменные и параметры, кроме имени и типа данных, дополнительно имеют еще и привязку. Грубо говоря, это отдаленно соответствует порту ввода-вывода, но не всегда так однозначно. Привязки помогают унифицировать интерфейс между приложением и GPU в целом, а также между векторным и пиксельным процессором. Если вам удобно, можете воспринимать привязки как именованные переменные из пула памяти GPU.

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

Мы не имеем возможности рассмотреть здесь все тонкости строения современных GPU вообще и их отображения на язык Cg в частности — документация по Cg занимает около трехсот страниц. Тем не менее отметим наличие скалярных и векторных типов данных, а также специфических предопределенных структур. Многие арифметические и логические операции в Cg имеют такое же значение, как и в C, хотя отсутствие указателей как сущности в GPU налагает свой отпечаток, например, на доступ к массивам или передачу параметров по ссылке.

Помимо привычных операторов, существует также достаточно много специфических “ускорителей”: операторов, эффективно транслирующихся в машинные команды GPU или вообще реализующихся за нуль-время — например, выделение компонента из вектора или перестановка элементов вектора (команды GPU в любом случае содержат биты, указывающие, какие компоненты координат или цветности извлекать). В новых профилях доступны также такие сложные операции, как поиск текстур и т.д.

На этом завершаем небольшое вступление в Cg — мы не даем знаний, зато показываем возможности. На диске К+П находится текущая версия компилятора, снабженная несколькими примерами и документацией. Если у вас сравнительно новая карточка nVidia версии NV2x/3x и где-то неподалеку Visual Studio — можете сразу приступать к экспериментам. Однако для получения хоть сколько-нибудь приемлемых эффектов понадобятся, как минимум, знания в оптике и трехмерном моделировании. Для моделирования реалистической физики необходимо знание физических моделей, таких как упругие или коллоидные среды и т.п.

Лично я что-то написал, однако продемонстрировать это не решаюсь — в двух словах композиция называется “Дисперсный хаос в турбулентных потоках”J. Впрочем, синий тор из туториала блестел и отражался вполне приемлемо, как и было обещано. Короче, математику и физику придется вспомнить.

Есть и другой вариант. Правда, создатели языка всячески отказывают ему в универсальности — но настоящий хакер никогда ведь не слушает, что ему говорят, не так ли? Обидно, когда отличный процессор GPU за 200 баксов простаивает 90% времени. А не заставить ли GPU решать и другие задачи, кроме отрисовки графики? Архивирование и взлом паролей перебором — отличные кандидатуры для таких чисто вычислительных и “зацикленных” задач. Не забывайте задействовать оба процессора GPU!

(И никому не говорите, что я вам это говорил!)

Арсений Чеботарев