Недостаток знаний иногда может оказаться достоинством, потому что вы наивно говорите себе: «Пфф… разве это сложно?» и просто погружаетесь в проблему с головой. Я начал эту статью с размышления: «Хм… Что же такое Draw Call?». За время «5-ти минутного» исследования я так и не нашел удовлетворяющего меня объяснения. Я проверил часы и, так как до сна оставалось еще 30 минут, сказал…
Пфф… Разве это сложно написать самостоятельно?
… и просто начал. Это было два месяца назад и с тех пор я непрерывно читал, писал и задавал много вопросов.
Это было самое сложное и низкоуровневое исследование, которое я когда-либо делал, и для меня непрограммиста это был кошмар состоящий из «да, но в этом особом случае...» и «зависит от реализации API...». Это был мой личный ад визуализации, но я прошел через него и принес нечто с собой: Четыре книги, каждая из которых представляет собой попытку объяснить одну из частей визуализации с точки зрения художника. Я надеюсь, что вам понравится.
Сегодня художники должны быть хорошо подкованными: С точки зрения компьютера ваши игровые ресурсы — это всего лишь наборы вершин и данных текстур. Преобразование этих сырых данных в изображение уровня «следующего поколения» преимущественно делается вашим центральным процессором (CPU) и графическим процессором (GPU).
1. Копирование данных в оперативную память для быстрого доступа
Сперва все необходимые данные загружаются с вашего жесткого диска (HDD) в оперативную память (RAM) для быстрого доступа. После этого нужные полигональные сетки и текстуры загружаются в память графической карты (VRAM). Это делается потому, что графическая карта имеет значительно более быстрый доступ к VRAM и в большинстве случаев не имеет доступа к RAM.
До того как сторона визуализации может приступить к работе, CPU задает некоторые глобальные значения, которые описывают то, как полигональная сетка должна быть отображена. Этот набор значений называется Render State.
2. Установка Render State значений
Render State — это некоторое глобальное определение того, как полигональная сетка должна быть визуализирована. Она содержит в себе информацию о:
«вершинных и пиксельных шейдерах, текстурах, материалах, освещении, прозрачности итп. […]» [Real-Time Rendering: стр. 711]
Важно, что каждая полигональная сетка, которую CPU передает в GPU для отрисовки, будет отображена с этими условиями! Вы можете рисовать камень, стул или меч — все они будут использовать одинаковые параметры визуализации (такие как материал), если вы не измените Render State до отображения следующей сетки.
После того, как подготовка будет окончена, CPU наконец-то может обратиться к GPU и сказать, что следует рисовать. Эта команда известна как Draw Call.
3. Draw Call
Draw Call — это команда отображения одной полигональной сетки. Она отдается центральным процессором. Ее получает графический процессор. Команда указывает только на полигональную сетку, которую надо отобразить, и не содержит никакой информации о материале, так как она уже определена через установку Render State. В этот момент полигональная сетка находится в памяти графической карты (VRAM).
После того, как команда отдана, GPU берет параметры Render State'а (материал, текстуру, шейдер, ...) и все данные о вершинах и преобразует эту информацию с помощью некоторой магии в прекрасные (надеюсь) пиксели на вашем экране. Этот процесс преобразования называется Pipeline'ом.
4. Pipeline
Как я говорил выше, игровые ресурсы — это, в некотором роде, просто наборы вершин и данных текстур. Чтобы преобразовать их в сногсшибательную картинку, графической карте надо создать треугольники из вершин, вычислить их освещенность, отобразить на них пиксели текстуры и много чего еще. Эти действия называются состояниями. Состояниями Pipeline'а.
В зависимости от того, какие материалы вы читаете, вы обнаружите, что практически все делается графическим процессором. Но иногда говорится, что, например, создание треугольников и фрагментов делается другими частями графической карты.
Данный пример Pipeline'а крайне упрощен и должен трактоваться только как дающий общее представление. Я попытался изобразить его настолько хорошо, насколько смог, но, как у непрограммиста, у меня есть проблемы с пониманием, когда пример становится таким простым, что уводит вас в неправильном направлении. Поэтому, пожалуйста, не принимайте его всерьез и посмотрите все другие замечательные источники, которые я привел в конце статьи. Или не стесняйтесь написать мне письмо, твит или сообщение в фейсбуке, чтобы улучшить анимацию/объяснение. :)
Вот пример с единственным ядром GPU:
Визуализация — это просто выполнение огромного числа маленьких задач, таких как вычисление чего-либо для тысяч вершин или рисование миллионов пикселей на экране. Как минимум (хорошо бы) в 30 кадрах в секунду.
Важно иметь возможность вычислять эти вещи одновременно, а не каждую вершину/пиксель одну за другой. В старые добрые времена, процессоры имели только одно ядро и не было графического ускорения — они могли делать только одну вещь в один момент времени. Игры выглядели как… ретро. Современные CPU имеют 6-8 ядер, тогда как GPU содержат несколько тысяч (они не настолько сложны как CPU-ядра, но они идеальны для пропускания через себя огромного кол-ва вершинных и пиксельных данных).
Когда данные (например куча вершин) попадают на этап Pipeline'а, работа по трансформации точек/пикселей разделяется между несколькими ядрами так, чтобы большинство этих маленьких элементов формировалось параллельно в большую картину:
Теперь мы знаем о том, что GPU может обрабатывать все параллельно. Но как насчет коммуникации между CPU и GPU? Ждет ли CPU того момента, когда GPU закончит свою работу и сможет получать новые команды?
Нет!
К счастью, нет! Причина заключается в том, что такая коммуникация будет образовывать «бутылочные горлышки» (например, когда CPU не может доставлять команды достаточно быстро) и сделает параллельную работу невозможной. Решение — это список, в который команды могут добавляться CPU и читаться GPU независимо друг от друга! Такой список называется буфером команд.
5. Буфер команд
Буфер команд позволяет CPU и GPU работать независимо друг от друга. Если CPU хочет что-либо отобразить, он помещает команду в очередь, а когда у GPU появляются освободившиеся ресурсы, он берет команду из списка и выполняет ее (но, так как список устроен как FIFO, GPU может взять только самую старую команду из списка (которая была добавлена первой) и выполнить ее).
Тем не менее команды могут быть разными. Один из примеров — Draw Call, другим может быть изменение Render State'а.
На этом первая книга заканчивается. Теперь вы должны иметь общее представление о данных игровых ресурсов, Draw Call'ах, Render State'ах и взаимодействии между CPU и GPU.
Конец
Автор: AndreyMI