Отсебятина
Наткнулся год назад на ряд очень интересных статьей господина Simon Schreibt. Саймон очень любит разбирать то, как создаются игры, а именно графические решения того или иного элемента в игре. Начиная от сколов на гранях плит, заканчивая тем, как реализовано отрезание кусков от объектов. Но особенно интересным представляется его ряд статей под общим названием «Ад рендера» (Render Hell), в котором он подробно разбирает, как на уровне железа (да и программно тоже) происходит рендер 3D-объектов.
Перевод вольный. Его я делал для себя, чтобы в какой-то момент я мог вернуться и прочитать то, что мог не уловить с первого раза или просто забыть.
Ну что, начнем?
Книга первая. Обзор
(оригинал книги здесь)
Ребятушки, держитесь: с точки зрения ПК, ваши работы в 3D — ничто иное, как просто список вертексов и текстур. Все эти данные конвертируются в картинку Некст-гена, и в основном это делается с помощью процессора системы (CPU) и графического процессора (GPU).
Сначала данные загружаются с вашего жесткого диска (HDD) в оперативную память (RAM) для быстрого доступа к ним. После этого нужные для отображения (рендера) Объекты (Meshes/Меши) и текстуры загружаются в оперативную память видеокарты (VRAM). Это связано с тем, что доступ к VRAM у видеокарты намного быстрее.
Если текстура больше не нужна (после выгрузки в VRAM), она может быть удалена из оперативной памяти RAM (Но вы должны быть уверены, что она вам больше не понадобится в ближайшее время, потому что выгрузка с HDD занимает очень большое время).
Меши должны оставаться в RAM, потому что скорей всего процессор захочет иметь доступ к ним, например, для определения столкновения.
Следовательно, инженеры поместили маленький объем памяти прям в сам видеопроцессор (GPU) и назвали эту память кэш (Cache). Это небольшой объем памяти, потому что это невероятно дорого — помещать большой объем памяти напрямую в процессор. GPU копирует в кеш только то, что ему сейчас необходимо и малыми порциями.
Наша скопированная информация теперь лежит в кэш 2-го уровня (L2 Cache). В основном, это маленький объем памяти (например, на NVDIA GM204 объем составляет 2048 кбайт), который установлен в GPU и доступен для чтения намного быстрее, чем VRAM.
Но даже этого не хватает, чтобы работать эффективно! Поэтому есть еще маленький кеш 1-го уровня (L1 Cache). На NVIDIA GM204 Он составляет 384Кбайт, который доступен не только для GPU, но и ближайших сопроцессоров.
Кроме того, есть еще одна память, которая предназначена для входных и выходных данных для GPU ядер: для регистрации файлов и записи. Отсюда GPU берет, например, два типа значений, считает их и фиксирует результаты в регистр:
После чего эти результаты помещаются обратно в L1/L2/VRAM, чтобы освободить место для новых расчетов. Вы, как программист, обычно не должны беспокоиться на счет их расчетов.
Почему это все работает без проблем? Как сказано выше, это все о времени доступа. И если мы будем сравнивать время доступа, например, HDD и L1 Cache, то между ними черная дыра — такая вот разница. Можно так же почитать о точных цифрах задержки по этой ссылке: gist.github.com/hellerbarde/2843375
Прежде, чем рендер начнет зажигать, CPU устанавливает некоторые глобальные значения, которые описывают, как меши должны рендериться. Эти значения называют Render State.
Render State
Это своего рода параметры того, как меши должны рендериться. Параметры содержат в себе инфу о том, какая текстура должна быть, какие вертексные и пиксельные шейдеры должны использоваться для отрисовки последующих мешей, свет, прозрачность и прочее.
И ВАЖНО ПОНИМАТЬ: Каждый меш, который CPU отправляет в GPU для отрисовки, будет рендериться под теми параметрами (Render State), которые были указаны до него. То есть, вы можете отрендерить меч, камень, стул и машину — все они будут рендериться под одной текстурой, если перед каждым из этих объектов не указывать параметры отрисовки RenderState.
Когда все подготовки завершены, CPU может наконец-то позвать GPU и сказать, что нужно рисовать. Эту команду называют Draw Call.
DrawCall
Это команда CPU для GPU отрендерить один меш. Команда указывает конкретный меш для рендера и не содержит в себе никакой информации о материалах и прочего — это все указывается в Render State.
Meш уже загружен в память VRAM.
После того, как команда отправлена, GPU берет RenderState-данные (материал, текстуры, шейдеры), а так же всю информацию о вершинах объекта, и конвертирует эти данные в (нам хочется верить) красивые пиксели на вашем экране. Этот процесс конвертации называется Pipeline (гугл любит переводить это слово, как «трубопровод»).
Pipeline
Как говорилось ранее, любые объекты — это не больше, чем набор вертексов и текстурной информации. Чтобы сконвертировать это в мозговыносящую картинку, видеокарта создает треугольники из вертексов, рассчитывает, как они должны быть освещены, рисует текстуры на них и так далее.
Эти действия называются Pipeline States.
Чаще всего большая часть этой работы осуществляется с помощью GPU видеокарты. Но иногда, например, создание треугольников осуществляется с помощью других со-процессоров видеокарты.
Вот пример шагов, которые железо делает для одного треугольника:
Рендер картинки происходит путем решения десятков, сотен тысяч подобных задач, отрисовывания миллионов пикселей на экране. И все это должно (я надеюсь) укладываться как минимум в 30 кадров в секунду.
Современные процессоры имеют по 6-8 ядер, когда как видеопроцессоры имеют несколько тысяч ядер (пусть и не таких мощных, как CPU, но достаточно мощных. чтобы обрабатывать кучку вертексов и прочих данных).
Книга №2 посвящена деталям организации высокого и низкого уровня в графическом процессоре.
Когда информация (например, кучка вертексов) попадает на pipeline, то работу по трансформации из вертексов в полноценное изображение выполняют несколько ядер, поэтому кучка этих элементов формируется в изображение одновременно (параллельно).
Теперь нам известно, что GPU может обрабатывать информацию параллельно. Но что на счет коммуникации между CPU и GPU? Неужели CPU ждет, пока GPU не закончит работу прежде, чем отправить ему новые задачи?
NO!
К счастью, нет! Причиной тому является слабое звено, которое образуется, как горлышко в бутылке, когда CPU не способен отправить следующие задачи достаточно быстро. Решением является лист команд, в которой CPU добавляет команды для GPU, пока тот обрабатывает предыдущую команду. Этот лист называется — Command Buffer.
Command Buffer
Буффер команд делает возможным то, чтобы работы CPU и GPU были независимы от друг друга. Когда CPU хочет что-то рендерить, то запихивает объекты в очередь команд, и когда GPU освобождается — он забирает их из листа (буфера) и начинает выполнять команду. Принцип забора команды — очередность выполнения. Первая команда пришла, первой она и будет выполнена.
Кстати, есть разные команды. Например, одна команда может быть DrawCall, вторая — смена RenderState на новые параметры.
Ну, в общем, это первая книга. Теперь у вас есть представление о том, как информация рендерится, вызываются Draw Calls, Render State, и взаимодействуют между собой CPU и GPU.
The End.
Автор: MrBrooks