11 лет назад 28 декабря 2009 в 18:29 390

Например, о том, чем AGP отличается от PCI Express, вершинные шейдеры от пиксельных, а тесселяция от LOD – ох, аж самому страшно стало. Ну что ж, раз наобещал с три короба – придется выполнять. Итак, начать надо с самых простых вопросов: что такое видеокарта как устройство, где она обитает и чем питается.

Основа основ
По сути видеокарта – это набор микросхем, которые отвечают за создание изображения и выведение его на любое предназначенное для этого техническое средство: монитор, проектор, а в недалеком будущем – непосредственно в мозг. Современные видеоадаптеры используются в основном для игр. Следовательно, им нужна быстрая связь с процессором, что означает «высокоскоростной» доступ к оперативной памяти, если мы говорим обо всех недавно выпущенных камнях AMD и набирающих обороты Nehalem от Intel, – ведь в них обращение к RAM возможно только через контроллер, встроенный в сам CPU.

В свое время интерфейсом, предоставляющим такую связь, стал AGP (Accelerated / Advanced Graphics Port), переезд с PCI на который можно считать точкой отсчета активного развития видеоигр. AGP рос, совершенствовался и ускорялся – сначала 2х, потом 4х и наконец 8х, но он, как и любой параллельный интерфейс, был обречен на вымирание с самого момента проектировки – ведь одновременная передача сигналов с огромной скоростью по большому количеству линий приводит к сложностям с их синхронизацией и наводками.

В результате произошло неизбежное: пришел последовательный интерфейс PCI Express, который и вытеснил старичка. Правда, процесс вытеснения шел долго и мучительно – первые видеокарты PCIE не могли полностью раскрыть потенциал даже старого интерфейса, что уж говорить о новом. Времена те уже прошли, и даже появился PCI Express 2.0 – он вдвое быстрее и к тому же может спокойно прокормить видеокарты с потреблением до 150 Вт против 75 Вт у первой версии.
Кстати, о питании: на большинстве мощных видюх есть дополнительные разъемы питания, не подключив которые вы увидите на экране разве что шедевр Малевича, а сделано это неспроста: с БП меньше «честных» 400 Вт к средне-высокого класса 3D-акселератору (GeForce 260, Radeon 4850) можно даже и не соваться – потянет на дно всю систему, сжигая внутренности компа.

Возвращаясь к интерфейсам: на подходе уже PCIE 3.0, с ППС 32 Гбайт/с в случае использования 16 линий (против 16 и 8 Гбайт/с у второй и первой версий соответственно), но это, как всегда, перекрывает текущие потребности с лихвой, даже учитывая multi-GPU-системы. В настольных компьютерах видеокарта присутствует, как правило, в виде отдельного куска текстолита, но в ноутбуках бывает и так, что видюха, даже дискретная, может быть распаяна как элемент материнской платы – сродни аудио- или сетевой карте. А еще ведь есть экзотика в виде внешних видеокарт – интересующимся искать в «Гугле» по «AMILO graphic booster».

Параллельные муравьи
Перейдем к начинке. Состоит типовой образец из двух обязательных компонентов – графического ядра и графической же памяти. Последняя обычно располагается на самой карте, что позволяет передавать данные в GPU через очень быстрые интерфейсы, скорость обмена в которых может достигать 200 Гбайт/с, – если бы HDD работали с такой скоростью, то за время прочтения одного только этого предложения вы бы уже забили под завязку как минимум терабайтный хард.

Скорость обмена данными складывается из ширины шины памяти (это называется битностью – сколько бит за такт передается) и скорости работы самих чипов. Например, количество данных, переданных за одну секунду по одному каналу памяти (64 bit) со скоростью 200 МГц, равно тому же объему информации, переданному в двухканалке (128 bit) со скоростью 100 МГц, а таких каналов может быть и три, и пять. Технология весьма проста: для связи с каждым чипом (или группой чипов) памяти выделяется отдельный пучок проводов, в результате данные пересылаются одновременно по многим направлениям, на радость нам и на погибель монстрам.

Хитрость состоит в том, что производители частенько заостряют внимание именно на объеме видеопамяти (делая честные глаза и говоря, что больше – лучше), не афишируя тот факт, что даже самый мощный графический чип просто задохнется от нехватки информации, если ему в пару дать медленную память, сколько бы ее ни было. Поэтому при выборе игровой видеокарты обязательно смотрите, на какой частоте работает память и какая у нее битность: умножив одно на другое и сравнив показатели разных претендентов, легко можно отделить зерна от плевел уже на этом этапе. Бывают, правда, ситуации, когда отдельная память не нужна и используется простая оперативная (см. врезку).

Встроенная слабость
Не всем для жизни требуется ежедневный отстрел монстров – иногда достаточно и видеокарты, способной просто показывать 2D-картинку, не поднимая при этом температуру в комнате до среднетропической и, что особенно важно, например, для ноутбуков, не потребляя много энергии. Такие видюхи, как правило, встраиваются непосредственно в чипсет, отчего и называются встроенными, или интегрированными, в противоположность дискретным, которые представляют собой отдельное устройство, устанавливаемое в материнскую плату.

Встроенные видеокарты обычно используют системную оперативную память, что выливается в очень низкую производительность в играх, но сильно уменьшает конечную стоимость компьютера или ноутбука – зачем переплачивать за то, чем никогда не будешь пользоваться? Традиционно выпуском таких карт занимается Intel, но у нее выходят совсем слабые чипы – например, и AMD и NVIDIA интеграшки производят, но у их детищ ноги растут из дискретных видеоадаптеров, что позволяет худо-бедно даже поиграть. Раньше у интегрированных видюх Intel была слабая сторона – отсутствие столь популярного нынче аппаратного декодирования видео, но и эта трудность преодолена. Осталось только «подружить» их с GPGPU и можно будет даже назвать их полноценными.

Быстрый поднос снарядов к орудию еще не означает высокую скорострельность, и точно так же быстрая память – еще полдела: ведь поступившие данные должен кто-то обрабатывать. Занимается этим видеоядро. Описывать все современные архитектуры GPU у меня в этой статье не поднимется рука, поэтому затрону лишь моменты, являющиеся общими для большинства акселераторов.

В любой видеокарте самыми важными элементами являются графическое ядро и графическая память. Характеристики именно этих узлов в основном и влияют на ее производительность
В любой видеокарте самыми важными элементами являются графическое ядро и графическая память. Характеристики именно этих узлов в основном и влияют на ее производительность

У любого процессора есть вычислительные ядра, а особенность видюх в том, что их много и они простые. Обусловлено это тем, что их задача заключается в обработке весьма однотипных примитивных данных, что они делают параллельно и притом очень быстро. Этому уже найдено применение в GPGPU, где видеокарты используются в неигровых задачах, таких как конвертация видео, обсчет физики или поиск лекарств от всего подряд. Больше всего процесс, как и в случае с многоканальностью памяти, напоминает перенос муравьями кучи сахарного песка: чем больше муравьев одновременно возьмут по песчинке и чем быстрее принесут, тем быстрее будет выполнена задача, но скорость муравьев ограничена, поэтому приходится наращивать их количество.

Этому явлению (GPGPU, не размножению муравьев) была посвящена одна из моих статей (UP #19 (420)). Посему не буду распыляться и просто скажу, что до сравнительно недавнего времени (всего два года тому назад) для каждой из операций в ядре использовался свой тип камня, но теперь всем занимаются унифицированные процессоры, число и мощность которых разнятся от экземпляра к экземпляру.

О производительности можно сказать, что сравнивать напрямую количество таких конвейеров и частоту их работы у ATI и NVIDIA некорректно, так как у них разные архитектуры и структуры ядер, – тут лучше посмотреть тесты. Подробнее обо всех нюансах процесса формирования изображения и методы его улучшения (aka фильтрация, антиалиасинг) вы узнаете, если не заснете и дочитаете до конца, а пока расскажу про драйверы и API.

Статские советники
Был в свое время вирус, который предлагал сыграть в морской бой, отмечая места проигрыша бэдблоками, – байка о нем так страшна, что ее нельзя рассказывать сисадминам младше пяти лет. Страшилка потеряла актуальность примерно тогда же, когда ни к одному устройству в Windows стало нельзя обратиться иначе как через драйвер. Он выступает в роли и информационного столба, и милиционера, и посредника одновременно. Драйвер говорит, что может делать устройство, чего не может, запрещает опасные действия и подсказывает, как правильнее совершить нужные, поэтому смена драйвера приводит к изменениям производительности и функционала устройств.

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

Если попытаться наглядно представить себе процесс общения пользователя и устройства, то юзер командует: «Играть вот эту музыку», ОС обращается к драйверу файловой системы: «Выручи – прочитай такой-то файл, дай мне, что там написано». Тот стучится в дверь к «дровам» дискового контроллера: «Тут команда сверху пришла, прочитайте такие-то секторы», берет необходимую информацию и передает драйверу аудиокарты: «Выручай, тут надо срочно проиграть вот это вот, только не слишком громко».

Самое приятное в этом процессе то, что не надо вручную отписывать команды устройствам, что им делать: ОС сама подергает за нужные драйверы. В «Линуксе» есть забавная возможность проиграть запись через колонки, используя одни только «дрова». Теоретически это можно реализовать и в Windows, но для этого нужен драйвер, который умеет это делать, или программа, которая совершает такое преобразование и передает разжеванные данные драйверу, непосредственно пересылающему их на устройство.

В случае с видеокартой все сложнее: есть еще API (Application Programming Interface). В качестве самых распространенных API можно выделить OpenGl и DirectX, последний широко известен благодаря его использованию подавляющим большинством игр в последние лет эдак пять. Итак, перед нами еще одна прослойка между человеком и устройством, только эта отвечает за более общие вещи. Например, движок игры говорит: «Нарисовать дым, огонь, солнце и немцев», API передает драйверу уже: «Нарисовать эффект огня такого типа с вот такими отражениями вот таким способом, дым туда же и т. п.», а драйвер видеокарте: «Взять вот эти данные отсюда, проделать над ними вот такие математически операции».

Цепочка кажется достаточно мудреной (так оно и есть), и очевидно, что потери производительности неизбежны. Зато ее использование гарантирует, что каждый этап будет выполнен надлежащим образом (огонь на мониторе выйдет именно таким, каким его задумали разработчики игры). А самое главное – не надо переписывать целиком код каждой игры при выходе новой видеокарты: ведь такое количество умных посредников даже команду вида «Елочка зажгись» или «Сделайте мне красиво» поймут и переработают в то самое «красиво». А теперь посмотрим, как именно это происходит.

Обои на каркасе
Так как де-факто стандартом для создания 3D-игр сейчас является API DirectX, все примеры будут рассматриваться относительно него и при этом сразу относительно еще только на бумаге существующей редакции за номером 11. Но это не значит, что данные функции не реализованы или не могут быть реализованы в других API или в иных версиях DirectX, – просто это было бы сложно, примерно как выкопать яму, держа лопату вилкой и ножом. Припомнив школьный курс биологии, можно сказать, что графический процессор – последнее звено информационной пищевой цепочки. Ведь все, что появляется на экране, должно быть обсчитано ЦП: даже когда мы нажимаем на кнопку «Пуск», эта команда сначала обрабатывается центральным процессором и только потом данные о том, какие точки где нарисовать, отдаются видеоядру.

На данный момент, вытеснив всех конкурентов, API DirectX стала основным стандартом для создания современных 3D-игр
На данный момент, вытеснив всех конкурентов, API DirectX стала основным стандартом для создания современных 3D-игр

Любой объект имеет опорные точки – вершины (control points), и любая модель начинается именно с них. CPU выдает эти точки графической карте, после чего в игру вступает тяжелый процесс рендеринга, львиную долю которого составляют шейдерные вычисления. «Шейдеры» в буквальном переводе означает «затенители», так как первоначальной задачей таких программ было всего лишь затенение цвета объекта, чтобы, находясь в тени, он выглядел реалистичнее. Позднее функционал таких программ расширился, но название осталось старым.

Чтобы проще было представить что и как, возьмем в качестве примера модель представителя Homo sapiens. Процессор отдает видеокарте на обработку контрольные точки – например, близкий к кругу по форме пятнадцатиугольник как вместилище мозга, одну точку как нос и несколько угловатых длинных многогранников, похожих по форме на цилиндры, как тело и конечности, с указанием вокруг них точек, где начинаются и кончаются пальцы.

Первыми в обработке модели удар на себя принимают вершинные (vertex) шейдеры – они анализируют присланные данные на правильность (вся ли фигура будет попадать в кадр или только часть, лишние вершины отсекаются) и заодно вычисляют положение вершин в пространстве (стоит модель на земле, у которой тоже надо вершины прорисовать, или висит вниз головой, а может, пополам разломана), попутно высчитывая освещение. Как только эти вершины обработаны, они передаются дальше шейдерам поверхности (hull shader), которые управляют тесселяцией.

Раньше создание сложных объектов было весьма непростым процессом, но в DirectX 11 грядет замечательная вещь, которую скоро начнут поддерживать все новые видеокарты, – тесселяция. Справедливости ради надо уточнить, что на аппаратном уровне в GPU от ATI (начиная с 2900-го), она уже реализована, но там она выступает только в непрограммируемом качестве, что сильно снижает ее возможности.

Итак, зачем это надо. Представьте, что мы вдалеке видим фигуру человека (наличие рук, ног, головы и бронежилета – по вкусу), а потом он подходит, и вы различаете даже царапины на его очках. Было бы неразумно вот эти самые царапины рисовать, когда он был еще на горизонте, – ведь на это тратятся драгоценные вычислительные ресурсы, а мы все равно не заметим их, пока объект не приблизится.

Чтобы не загружать видеокарту ненужными расчетами, была разработана система LOD (Level of Detail), которая создает несколько упрощенных моделей объекта. В зависимости от расстояния API выбирает наиболее простую из них, но вместе с тем не снижающую качества ее отображения. В приведенном примере с человеком вдалеке это были бы его модели на расстоянии метра, двух метров (нет царапин на очках), четырех (нет шнурков, и общая угловатость больше), восьми (нет карманов, гранаты) и так далее вплоть до точки на горизонте.

Можно создать больше или меньше уровней (чтобы система была гибче), однако их приходится делать руками или автоматически, но с определенными потерями производительности и качества. Кроме того, LOD получаются только методом напильника – берется за основу самая качественная модель и приводится в соответствие с нужным уровнем ущербности (ладно, обобщенности).

Тут-то и вступает в игру тесселяция. Суть ее в том, что можно не убрать, а увеличить детализацию на основе определенной программы. Например, добавить модели поры и пупырышки на уровне геометрии и придать гладкой поверхности именно фактуру кожи – прожилки, царапины, шершавость, выбрав при этом возможные варианты возраста и пола объекта, от шелковистости для виртуальной девушки и кончая задубевшей «шкурой» для военного (цвет кожи при этом остается за кадром до наложения текстуры).

Кроме этого тесселяция, которая, строго говоря, разбивает данную ей область на множество мелких и изменяет их при помощи displacement maps, в виде результата своей деятельности выдает кучу новых вершин, которые требуется как-то обработать – зарегистрировать их, провести между ними виртуальные и реальные линии. Этим занимаются шейдеры областей (domain shaders), которые берут получившиеся вершины и превращают их в удобоваримую поверхность, с которой можно работать дальше.

Любой, даже самый сложный, трехмерный объект можно построить из треугольников и четырехугольников, сложенных определенным образом, – весь вопрос будет только в угловатости результата: если взять достаточное их количество (пару тысяч), то можно и шарик изобразить, и собачку, и «Мону Лизу». Такие треугольники по-научному называются полигонами, четырехугольники – квадами, или патчами. Нарисовав базовую модель, сделав к ней несколько LOD и написав программы тесселяций, мы получаем на выходе некую горстку полигонов и патчей в виде почти завершенной модели нашего человека. «Почти» – потому что жизнь жестока, и наряду с естественными углублениями в области глаз и пустотами в области головы у человека могут появиться и искусственно нанесенные улучшения в виде впадин (в том числе сквозных) или выпуклостей. А значит, во имя реализма следует приступить к следующей части обработки – с помощью геометрических шейдеров.

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

Быль и небыль
Все методы построения и изменения изображения делятся на две большие категории – попиксельные (в данном случае пиксель берется как единица 3D-сцены) и вершинные. Различия между ними достаточно принципиальные, хотя результат может и быть похожим. Методы вершинной обработки геометрии, например displacement maps, которые трансформируют реальный каркас трехмерной модели, оперируют именно параметрами объектов, заставляя их меняться. Попиксельные же (такие как bump mapping, parallax mapping и normal mapping) только создают видимость объемности. Принцип работы этих методов заключается в высчитывании виртуальной формы (высоты / глубины) текстуры, причем parallax mapping даже учитывает угол, под которым мы смотрим на нее, и не отображает некоторые ее элементы, которые не были бы видны, если бы это была геометрическая фигура, а не плоская поверхность.

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

Далее происходит выборка текстур, благо размеры областей уже понятны, а также их растеризация и натягивание на получившийся каркас. В этот момент может оказаться, что текстура, которая до геометрического шейдера должна была накладываться на почти ровную поверхность, теперь уже накладывается на область с дырой и рваными краями (физика в играх все совершеннее, да), а иногда и вовсе не дыра посреди человека получается, а человек посреди дыры (да, вооружение тоже постоянно совершенствуется). Значит, в угоду реализму срочно надо закрасить все вокруг в красный или голубой (если у нас модель аристократа) цвет. Но ведь у нас уже есть текстура, она определенного цвета и вполне себе аккуратно наложена, а жадный до мелочей мозг требует брызг. Тут помогают пиксельные шейдеры (мультитекстурирование, альфа-блендинг и прочие технологии).

Ну, с мультитекстурированием все понятно – одна текстура накладывается поверх другой, вот и весь сказ. А вот термин «альфа-блендинг» явно требует пояснений. Видеокарта использует для описания цвета точки четыре параметра: три цвета (RGB) и альфа. Последний параметр отвечает за прозрачность пикселя, а это важно, потому что полукрасный с полным альфа – это непрозрачный розовый, а полный красный с половинным альфа – уже полупрозрачный розовый. И если бы не параметр альфа, не видать бы нам в играх прозрачных цветных стекол, отражений в воде и объектов под ней. Также на этапе пиксельных шейдеров выполняются бамп-мэппинг и применение карт нормалей, но постепенно эти приемы будут становиться все менее популярными, так как были, по сути, придуманы за неимением тесселяции. Несмотря на то что они уходят в прошлое, все же вкратце их опишу.

Для начала надо пояснить понятие нормалей. Если объяснение типа «нормаль – это прямая, ортогональная (перпендикулярная) касательной прямой к некоторой кривой или касательной плоскости к некоторой поверхности», кажется вам слишком невразумительным, то просто представьте, что держите щит, в самый центр которого из лука выстреливает человек, находящийся прямо перед вами. Стрела и есть та самая нормаль.

С точки зрения 3D-графики, подделав значение нормали, можно заставить свет якобы отражаться под другим углом от геометрически ровной поверхности, а значит, подделав конечный результат, можно сэкономить ресурсы. Бамп-мэппинг позволяет вычислить, как бы отражался свет от объекта, если бы у него была другая (читай – более сложная) геометрия, что было актуально в те времена, когда за геометрию и растеризацию отвечали различные процессоры графического ядра: ведь, сэкономив на геометрии, можно было выиграть в производительности, хотя результат получался и хуже – фейк есть фейк.

Карты нормалей являются логическим продолжением бамп-мэппинга. Они полностью устраняют необходимость в большинстве расчетов этого рода, представляя собой особую текстуру с заранее высчитанными параметрами нормалей. Правда, такой подход помимо некоторых присущих ему недостатков (см. RSS) еще и увеличивает требования к пропускной полосе и объему памяти, ведь, по сути, это использование дополнительной текстуры. И наконец, после всех мытарств остается последний штрих в собственно создании кадра – накладываются эффекты постпроцессинга (зернистость, блюр, HDR, «кровавый туман»), и готовая картинка подается к столу.

Само собой, пока выполняется один этап работы конвейера, оставшиеся свободными ресурсы не простаивают, обрабатывая другие кадры, а унифицированность шейдерных процессоров позволяет при «обкатке» одного кадра (например, на котором белье сушится на веревке) использовать мало геометрических и много пиксельных шейдеров, а в следующем кадре (махровое полотенце крупным планом) наоборот. И это не прекращается ни на секунду.

На полпути
Неудивительно, что при таком объеме работы бедные девайсы греются не хуже утюгов. Тут бы и сказке конец, но солнце над плантациями 3D-рендеринга еще высоко, ведь, как я уже сказал, процесс формирования изображения на этом этапе завершается, а ведь есть еще процедура отображения картинки и передачи ее на экран. Когда-нибудь мы напишем и про это (смайл).

Z-буфер
В реальной жизни, когда мы смотрим вокруг, некоторые объекты находятся ближе и, соответственно, перекрывают те, что расположены дальше. В 3D-графике же объекты рисуются целиком, а какая каша получится, если вывести на экран сразу и передние, и задние поверхности всего и вся, да еще и не учитывая их заслоненность друг другом, объяснять не требуется. Как раз для отсекания лишних деталей и установки правильности глубины трехмерной сцены используется Z-буфер, в который пишется информация о том, насколько каждая точка объекта удалена от наблюдателя.


1. Простая трехмерная сцена
2. Представление в Z-буфере

Но обрезка видимых областей изображения вовсе не означает, что то, что мы не видим, не обрабатывается – иначе как бы мы могли по тени на полу понять, что за углом кто-то (что-то) есть, или как увидели бы что-либо через стекло, ведь стекло с точки зрения геометрии – поверхность. А значит, если бы на Z-этапе отсекались все текстуры из геометрически невидимых областей, то за ним мы бы обнаружили серые стены либо просто пустоту. Поэтому Z-буфер представляет собой текстуру, на которую в 2D-режиме накладываются изометрические проекции объектов, дальность которых помечена светлотой цвета.

Никто не прокомментировал материал. Есть мысли?