Введение от меня, как от переводчика
Статья взята отсюда и переведена на русский язык. В статье автор подробно разбирает принцип работы графического пайплайна в GPU
, используя простые и понятные иллюстрации.
Статья имеет необычную структуру: сначала идет блок оригинального текста, взятого из первоисточника, а далее идет текст с переводом на русский язык. Сделано с целью, чтобы читатель мог видеть термины на английском языке, а также самостоятельно сверять текст с оригиналом.
Эта статья является частью серий статей под общим названием "Ад Рендера 2.0". Со списком статей можно ознакомиться здесь.
Перевод делался с рамках самостоятельного изучения GameDev. В рамках разработки собственной игры возникла необходимость разобраться как работает рендеринг, в итоге получилась статья с переводом и много ссылок на уточняющие неочевидные трактовки и описание терминов.
Эта статья и ей подобные исследования сначала публикуются на моем сайте bekhan.org. Узнать первыми о публикациях можно в моих сообществах.
Pipeline in Detail (Пайплайн в Деталях)
Most of the constructive feedback I received about this article was “Nice explanation, but your pipeline is 6 years old!”. I wasn’t sure what that exactly meant until Christoph Kubisch joined my fight in the Render Hell
. He is a Developer Technology Engineer working for NVIDIA and whatever question I had, he answered it. And believe me, I had a lot! :)
Большинство конструктивных отзывов, которые я получил по этой статье, были в духе: "Хорошее объяснение, но ваш пайплайн устарел на 6 лет!". Я не был уверен, что это точно значит, пока Кристоф Кубиш не присоединился к моей книге
. Он инженер по разработке технологий, работающим на NVIDIA, и на любой вопрос, который у меня был, он отвечал. И поверьте мне, у меня было много вопросов! :)
Our explanation is mostly based on
NVIDIA
architecture due the fact that Christoph is working forNVIDIA
. I hope you don’t misunderstand this as an advertisement. We both just share the passion to explain stuff and I was really happy to have someone who could help me with that technical knowledge.Be aware that we omitted and simplified some details.
Наше объяснение в основном базируется на архитектуре
NVIDIA
из-за того, что Кристоф работает наNVIDIA
. Я надеюсь, вы не воспринимаете это как рекламу. Мы оба просто разделяем страсть к объяснению вещей, и я был действительно счастлив, что нашел кого-то, кто мог помочь мне с этими техническими знаниями.Имейте в виду, что мы опустили и упростили некоторые детали
Two major points of the pipeline will be explained below, which weren’t totally wrong explained here in book I, but might not be clear enough:
Два главных момента пайплайна будут объяснены ниже, которые не были полностью неправильно объяснены здесь в книге I, но могли быть недостаточно ясными:
Not everything is done by the “tiny”
GPU
cores!There can be several parallel running pipelines!
Не всё делается "маленькими" ядрами
GPU
!Может быть несколько параллельно работающих пайплайнов!
Below I’ll go into detail about it. Have fun!
Ниже я подробно расскажу об этом. Приятного чтения!
1. Not everything is done by the “tiny” GPU cores! (Не всё делается "маленькими" ядрами GPU!)
In the pipeline example above it might seem like every pipeline stage is done by the GPU
Cores – This is NOT the case! In fact, most of the stuff is not done by them. In the new section in “1. Copy the data into system memory for fast access” you already saw that several components are necessary to just bring the data to a core. So what work do the cores actually do?
В приведенном выше примере пайплайна может показаться, что каждый этап пайплайна выполняется ядрами GPU
- это НЕ так! На самом деле большинство задач не выполняется ими. В новом разделе “1. Копирование данных в системную память для быстрого доступа” вы уже видели, что для передачи данных ядру необходимо несколько компонентов. Так что же на самом деле делают ядра?
Let’s observe one:
Давайте рассмотрим одно из ядер:
A core can receive commands and data. Then it executes the command by calculating the data in a floating point
unit (FP
UNIT) or an integer
unit (INT
UNIT). So you could say: A core can calculate pixels
and vertices
(maybe also other calculations like physics but let’s focus on graphic rendering
).
Ядро может принимать команды (инструкции) и данные. Затем оно выполняет команду, обрабатывая данные в блоке с плавающей точкой
(FP
-блок) или в целочисленном
блоке (INT
-блок). Так что можно сказать: ядро может вычислять пиксели
и вершины
(возможно, также и другие вычисления, например, физику, но давайте сосредоточимся на графическом рендеринге
).
Other important stuff like distribute the render
-tasks, calculate tessellation
, culling and preparing the fragments for the pixel shader
, depth testing and writing pixels
into the frame buffer
are NOT done by the cores. This work is done by special, not programmable hardware blocks which are also placed in the GPU
.
Другие важные задачи, такие как распределение задач рендеринга
, вычисление тесселяции
, отсечение и подготовка фрагментов для пиксельного шейдера
, тестирование глубины и запись пикселей
в буфер кадра
, НЕ выполняются ядрами. Эта работа выполняется специальными не программируемыми аппаратными блоками, которые также находятся в GPU
.
Ok, after we know that now, let’s move on to the next major point I have to clear out:
Хорошо, теперь, когда мы знаем это, давайте перейдем к следующему важному моменту, который мне нужно прояснить:
2. There can be several parallel running pipelines! (Может быть несколько параллельно работающих пайплайнов!)
First, I’ll give you a short example what I mean with that headline. If you’re still thirsty after this, I’ll take you into an even more detailed explanation.
Сначала я приведу вам короткий пример того, что я имею в виду под этим заголовком. Если после этого у вас по-прежнему останется интерес, я предоставлю более детализированное объяснение.
But first, let’s recap:
Но сначала давайте вспомним:
If we would only have one GPU
Core, what could we calculate with it?
Если бы у нас было только одно ядро GPU
, что бы мы могли вычислить с его помощью?
Correct: Nothing! Because the core needs someone who assigns him some work. This is done by a Streaming Multiprocessor
(SM
) which can handle a stream of vertices
/pixels
which belong to one shader
. OK, with that SM
and one core we could calculate one vertex
/pixel
at a time:
Верно: Ничего! Потому что ядру нужен кто-то, кто поручит ему какую-то работу. Это делается с помощью потоковых мультипроцессоров
(SM
), который может обрабатывать поток вершин
/пикселей
, относящихся к одному шейдеру
. Хорошо, с этим SM
и одним ядром мы могли бы вычислять одну вершину
/пиксель
за раз:
Of course, if we increase the amount of cores, there can be more vertices
/pixels
calculated at the same time. But only if they belong to the same shader
!
Конечно, если мы увеличим количество ядер, можно будет вычислять больше вершин
/пикселей
одновременно. Но только если они относятся к одному и тому же шейдеру
!
This was already explained in my first attempt of explaining the pipeline
! But now it gets interesting: What if we would add another Streaming Multiprocessor
which cares about half of the cores?
Это уже было объяснено в моей первой попытке объяснить пайплайн
! Но теперь становится интересно: что, если мы добавим еще один потоковый многопроцессор
, который будет заниматься половиной ядер?
Now we can not only calculate vertices
/pixels
in parallel, we can also take care about 2 shader
streams at the same time! This means, we can for example run two different pixel shaders
at the same time or run a vertex shader
AND a pixel shader
at the same time!
Теперь мы можем не только параллельно вычислять вершины
/пиксели
, но и одновременно обрабатывать 2 потока шейдеров
! Это означает, что мы можем, например, одновременно запустить два разных пиксельных шейдера
или запустить вершинный шейдер
и пиксельный шейдер
одновременно!
This rough example shall just give you a glimpse about that several different hardware blocks are involved and they all work in parallel so the pipeline
is more flexible than I described in my first attempt.
Этот грубый пример должен просто дать вам представление о том, что задействовано несколько разных аппаратных блоков, и все они работают параллельно, так что пайплайн
более гибкий, чем я описывал в своей первой попытке.
Anyone still thirsty? Then let’s get into more detail!
Есть еще желающие узнать подробности? Тогда давайте углубимся в детали!
3. In-Depth look into the pipeline stages (Подробный взгляд на этапы пайплайна)
3.1 Overview (Обзор)
First of all: Why do we need a flexible/parallel pipeline
? The reason is, that you can’t foresee, what workload you’ll have. Especially with tessellation
it might be, that there are suddenly 100.000 more polygons
on the screen than one frame
before. Therefore you need a flexible pipeline
which handles totally different workloads.
Прежде всего: почему нам нужен гибкий/параллельный пайплайн
? Причина в том, что нельзя предсказать, какая у вас будет рабочая нагрузка. Особенно с тесселяцией
может случиться так, что вдруг на экране появится на 100 000 полигонов
больше, чем за один кадр
до этого. Поэтому вам нужен гибкий пайплайн
, который справляется с совершенно разными рабочими нагрузками.
DON’T WORRY! (НЕ БОЙТЕСЬ!)
Please don’t be afraid by looking at the two following images (like I am when i read a Wikipedia article and see formulas between the text)! Yes, this stuff isn’t easy and even complex charts only show what a programmer needs to know and hide a lot of the “real” complexity. I only show it to give you a rough understand how complicated this stuff is. :)
Пожалуйста, не бойтесь, глядя на два следующих изображения (как я, когда читаю статью в Википедии и вижу формулы между текстом)! Да, это не просто, и даже сложные диаграммы показывают только то, что должен знать программист, и скрывают много "реальной" сложности. Я показываю это только чтобы дать вам грубое представление о том, насколько сложной является эта область. :)
The following image shows a GPU
. I have no idea about what is what, but it’s kind of beautiful, isn’t it?
Следующее изображение показывает GPU
. Я понятия не имею, что есть что, но это выглядит довольно красиво, не правда ли?
And here’s an image from Christoph Kubisch’ Article “Life of a triangle”. It shows parts of the work which is happening on a GPU
in structured graphics.
И вот изображение из статьи Кристофа Кубиша "Жизнь треугольника". Оно показывает части работы, которая происходит на GPU
в структурированных графиках.
I hope you’ve a rough idea about the complexity now and will realize how much the explanations below are simplified. Let’s now have a detailed look on the whole pipeline
.
Я надеюсь, у вас теперь есть общее представление о сложности и вы поймете, насколько объяснения ниже упрощены. Теперь давайте подробно рассмотрим весь пайплайн
.
3.2 Application Stage (Этап Приложения)
It starts with the application or a game telling the driver
that it wants something rendered
.
Все начинается с приложения или игры, сообщающих драйверу
, что оно хочет что-то отрендерить
.
3.3 Driver Stage (Этап Драйвера)
The driver
receives the commands from the application and puts them into a command buffer
(this was already explained here in book I). After a while (or when the programmer forces it), the commands are send to the GPU
.
Драйвер
получает команды от приложения и помещает их в буфер команд
(это уже было объяснено здесь в книге I). Спустя некоторое время (или когда программист заставляет это делать), команды отправляются на GPU
.
IMPORTANT: The Driver
can be a bottle-neck. See Book III “1. Many Draw Calls” for details.
ВАЖНО: Драйвер
может стать узким местом. Смотрите Книгу III "1. Много Вызовов Отрисовки" для получения подробной информации.
3.4 Read commands (Чтение Команд)
Now we’re on the graphic card
! The Host Interface
reads commands to make them available for further use.
Теперь мы на графической карте
! Интерфейс хоста
читает команды, чтобы сделать их доступными для дальнейшего использования.
3.5 Data Fetch (Передача Данных)
Some of the commands sent to the GPU
can contain data or are instructions to copy data. The GPU
typically has a dedicated engine to deal with copying data from RAM
to VRAM
and vice versa. This data could be anything filling vertex buffers
, textures
or other shader
parameter buffers. Our frame would typically start with some camera matrices being sent.
Некоторые команды, отправленные на GPU
, могут содержать данные или являются инструкциями по копированию данных. Обычно на GPU
есть специализированный движок для копирования данных из RAM
в VRAM
и обратно. Эти данные могут быть чем угодно, наполняя буферы вершин
, текстуры
или другие буферы параметров шейдеров
. Обычно наш кадр начинается с отправки некоторых матриц камеры.
Important
I symbolize the data as geometries but in fact we’re talking just about
vertex
-lists (vertex buffer
). For a long time therendering
process doesn’t care about the finalmodel
. Instead, most of the time, only singlevertices
/pixels
are worked with.Textures
only get copied, if they’re not already in theVRAM
on thegraphic card
!If
vertex buffer
are used a lot, they can stay in theVRAM
liketextures
and don’t have to be copied with everydraw call
Vertex buffers
may stay in theRAM
(notVRAM
) if they change a lot. Then theGPU
can read the data directly fromRAM
to itsCaches
.
Важно
Я обозначаю данные как геометрические фигуры, но на самом деле мы говорим просто о списках
вершин
(буфер вершин
). На протяжении долгого времени процессрендеринга
не заботится о конечноймодели
. Вместо этого, большую часть времени работа ведется с отдельнымивершинами
/пикселями
.Текстуры
копируются только в том случае, если они еще не находятся вVRAM
награфической карте
!Если
буферы вершин
часто используются, они могут оставаться вVRAM
подобнотекстурам
и не должны копироваться с каждымвызовом рендеринга
.Буферы вершин
могут оставаться вRAM
(не вVRAM
), если они часто меняются. ТогдаGPU
может считывать данные напрямую изRAM
в своикэши
.
Now that all ingredients are ready the Gigathread Engine
comes into play, it creates a thread for every vertex
/pixel
and bundles them into a package. NVIDIA
calls such a package: Thread Block
. Additional threads may also be created for vertices
after tessellation or for geometry shaders
(will be explained later). Thread blocks
are then distributed to the Streaming Multiprocessors
.
Теперь, когда все ингредиенты готовы, в игру вступает Gigathread Engine
. Он создает поток для каждой вершины
/пикселя
и объединяет их в пакет. NVIDIA
называет такой пакет: Блок потоков
. Дополнительные потоки могут быть созданы также для вершин
после тесселяции
или для геометрических шейдеров
(будет объяснено позже). Блоки потоков
затем распределяются по потоковым многопроцессорам
.
3.6 Vertex Fetch (Получение Вершин)
A Streaming Multiprocessor
is a collection of different hardware units and one of them is the Polymorph Engine
. For the sake of simplicity I present them like separate guys. :) The Polymorph Engine
gets the needed data and copies it into caches
so cores can work faster. Why it is useful to copy data into caches
was explained here in book I.
Потоковый многопроцессор
представляет собой совокупность различных аппаратных блоков, и одним из них является Polymorph Engine
. Для простоты я представляю их как отдельных персонажей. :) Polymorph Engine
получает необходимые данные и копирует их в кэши
, чтобы ядра могли работать быстрее. Зачем полезно копировать данные в кэши
, объясняется здесь в книге I.
3.7 Shader Execution (Выполнение Шейдеров)
The main purpose of the Streaming Multiprocessor
is executing program code written by the application developer, also called shaders
. There is multiple types of shaders
but each kind can run on any of these Streaming Multiprocesors
and they all follow the same execution logic.
Основная цель потокового многопроцессора
- выполнение программного кода, написанного разработчиком приложения, также называемого шейдерами
. Существует несколько типов шейдеров
, но каждый из них может работать на любом из этих потоковых многопроцессоров
, и все они следуют одной и той же логике выполнения.
The Streaming Multiprocessor
now takes his big Thread Block
which he received from the Gigathread Engine
and separates it into smaller chunks of work. He splits the Thread Block
into heaps of 32 Threads. Such a smaller heap is called: Warp
. In total, a Maxwell Streaming Multiprocessor
(SMM) can “hold” 64 of such warps
. In my example and SMM there are 32 dedicated cores to work on the 32 Threads.
Теперь потоковый многопроцессор
берет свой большой блок потоков
, который он получил от Gigathread Engine
, и разделяет его на меньшие части работы. Он разделяет блок потоков
на кучи по 32 потока. Такая меньшая куча называется: Варп
. В общей сложности потоковый многопроцессор
(в архитектуре) Maxwell (SMM) может "содержать" 64 таких варпов
. В моем примере и в SMM есть 32 выделенных ядра для работы с 32 потоками.
One Warp
is then taken to be worked on. At this point the hardware should have all necessary data loaded into the registers so that the cores can work with it. We simplify the illustration here a bit: SMM for example has 4 warp schedulers
, which each would let one warp
work and manage a subset of the SMM’s warps
. (исправленная неточность)
Один варп
затем берется для работы. На этом этапе аппаратное обеспечение должно загрузить все необходимые данные в регистры, чтобы ядра могли с ними работать. Мы немного упрощаем иллюстрацию здесь: например, SMM имеет 4 планировщика варпов
, каждый из которых позволяет одному варпу
работать и управлять подмножеством варпов
(что "содержатся" в) SMM. (исправленная неточность)
The actual work begins now. The cores itself never see the whole shader
-code but only one command of it at the time. They do their work and then the next command is given to them by the Streaming Multiprocessor
. All cores execute the same command but on different data (vertices
/pixels
). It’s not possible that some cores work on command A and some on command B at the same time. This method is called lock-step
.
Теперь начинается настоящая работа. Сами ядра никогда не видят весь код шейдера
, а только одну команду за раз. Они выполняют свою работу, а затем следующая команда передается им потоковым многопроцессором
. Все ядра выполняют одну и ту же команду, но с разными данными (вершины
/пиксели
). Невозможно, чтобы некоторые ядра работали над командой A, а некоторые над командой B одновременно. Этот метод называется синхронный шаг
.
This lock-step
method gets important if you have an IF
-statement in your shader
code which executes either one code block or another.
Метод синхронный шаг
становится важным, если у вас в коде шейдера
есть инструкция IF
, которая выполняет либо один блок кода, либо другой.
This IF
-Statement makes some of our cores execute the left code and some the right code. It can’t be done at the same time (like explained above). First one code-side would be executed and some cores would be “sleeping” and then the other side is executed afterwards. Kayvon explains this here at 43:58 “What about conditional execution?”.
Эта инструкция IF
заставляет некоторые из наших ядер выполнять левый код, а некоторые - правый. Это невозможно сделать одновременно (как объяснено выше). Сначала будет выполнена одна сторона кода, и некоторые ядра будут "спать", а затем другая сторона будет выполнена позже. Кейвон объясняет это здесь на 43:58 "Что насчет условного выполнения?".
In my example 16 pixels
/vertices
would be worked on but it’s of course possible to have IF
-statements where only ONE pixel
/vertex
is calculated and the other 31 cores get masked out. The same applies to loops, if only one core has to stay in the loop long, all the others become idle. This phenomenon is also called divergent threads
and should be minimized. Ideally all threads in the warp
hit the same side of the IF
-condition, because then we can entirely jump over the non-active side.
В моем примере было бы обработано 16 пикселей
/вершин
, но, конечно, возможны инструкции IF
, где рассчитывается только ОДИН пиксель
/вершина
, и остальные 31 ядро маскируются. То же самое относится к циклам: если только одно ядро должно оставаться в цикле долго, все остальные становятся неактивными. Это явление также называется расходящимися потоками
и его следует минимизировать. В идеале все потоки в варпе
должны попадать на одну и ту же сторону условия IF
, потому что тогда мы можем полностью пропустить неактивную сторону.
But why can the SMM hold 64 warps
, when the cores can only work on very few at a time?? This is because sometimes you can’t progress because you have to wait for some data. For example we need to calculate the lighting with the normal from our normal map texture . Even if the normal was in the cache
it takes a while to access it, and if it wasn’t it can take a good while. Pro’s call this Memory Stall
and this is greatly explained by Kayvon. Instead of doing nothing, the current warp
will just be exchanged with one, where the necessary data is ready for use:
Но почему SMM может содержать 64 варпа
, когда ядра могут работать только с очень небольшим их количеством за раз?? Это потому, что иногда вы не можете продолжить, потому что должны ждать какие-то данные. Например, нам нужно рассчитать освещение с нормалью из нашей текстуры карты нормали. Даже если нормаль была в кэше
, доступ к ней занимает некоторое время, и если ее не было, это может занять довольно много времени. Профессионалы называют это задержкой памяти
, и это хорошо объяснено Кейвоном. Вместо того, чтобы ничего не делать, текущий варп
просто заменяется на тот, где необходимые данные уже готовы к использованию:
The explanation above was a bit simplified. Modern GPU
architecture doesn’t only allow a Streaming Multiprocessor
working at one Warp
at the time. Look for example at this image showing a Streaming Multiprocessor
based on Maxwell
architecture (SMM
): One Streaming Multiprocessor
has access to four Warp schedulers
, each controlling 32 Cores. This makes him able to work on four Warps
completely parallel. The book keeping for the work state of multiple threads is kept independently and is reflected by how many Threads the SM
can hold in parallel, as noted on top.
Вышеописанное объяснение было немного упрощено. Современная архитектура GPU
не только позволяет потоковому многопроцессору
работать с одним варпом
за раз. Посмотрите, например, на эту картинку, показывающую потоковый многопроцессор
на основе архитектуры Maxwell
(SMM
): Один потоковый многопроцессор
имеет доступ к четырем планировщикам варпов
, каждый из которых управляет 32 ядрами. Это позволяет ему работать с четырьмя варпами
полностью параллельно. Учет рабочего состояния нескольких потоков ведется независимо и отражается тем, сколько потоков SM
может содержать параллельно, как отмечено выше.
There is more than just waiting for memory that allows us to switch active scheduling as Guohui Wang noted:
Есть ещё нечто большее, чем просто ожидание памяти, что позволяет нам переключать активное планирование, как отметил Guohui Wang:
Since more than 4
Warps
can run in parallel inMaxwell
. Eachwarp scheduler
can perform dual-issuing instruction in one clock clock to awarp
, in this sense, every clock cycle we have up to 4warps
get their new instructions to work on (suppose at least 4 warps are ready by far). However, we also have instruction-level parallelism. That means when 4warps
are executing instructions (typically with 10-20 cycle latency), the next batch of 4 warps can accept new instructions in the next clock cycle. Therefore, it is possible to have more than 4warps
running in parallel if the resources are available. Actually, in one of the CUDA optimization video in GTC2013, more than 30 activewarps
are recommended to keep thepipeline
fully occupied in a general case. You may want to revise the wording here to indicate that there will be multiple (more than 4)warps
running in parallel, to avoid any potential confusion that people may think 4scheduler
could only hold 4 parallelwarps
.
– Guohui Wang
Поскольку в архитектуре
Maxwell
могут параллельно работать более четырехварпов
, каждыйпланировщик варпов
может выполнить двойную выдачу инструкций за один тактовый цикл дляварпа
. Исходя из этого, на каждом тактовом цикле до четырехварпов
могут получать новые инструкции для работы (при условии, что как минимум четыреварпа
готовы). Однако здесь также присутствует параллелизм на уровне инструкций. Это означает, что в то время как четыреварпа
выполняют инструкции (обычно с задержкой в 10-20 циклов), следующая группа из четырехварпов
может принимать новые инструкции в следующем тактовом цикле. Таким образом, при наличии доступных ресурсов, возможно, что будет работать более четырехварпов
параллельно. На самом деле, в одном из видео по оптимизации CUDA на конференции GTC 2013 рекомендуется, чтобы для полной загрузкиконвейера
в общем случае было активно более 30варпов
. Возможно, стоит пересмотреть формулировку, чтобы указать на наличие множественных (более четырех) параллельныхварпов
и избежать возможного недопонимания, что четырепланировщика
могут обслуживать только четыре параллельно работающих варпа.
– Guohui Wang
But what are our threads actually working on? For example a Vertex Shader
!
Но над чем на самом деле работают наши потоки? Например, над вершинным шейдером
!
3.8 Vertex Shader (Вершинный Шейдер)
The vertex shader
takes care of one vertex
and modifies it how the programmer wants. Unlike normal software (e.g. Mail-Program) where you run one instance of the program and hand over a lot data which get taken care of (e.g. handling all the Mails), you run one instance of a vertex shader
program for every vertex
which then runs in in one thread managed by the Streaming Multiprocessor
.
Вершинный шейдер
заботится об одной вершине
и модифицирует её так, как хочет программист. В отличие от обычного программного обеспечения (например, почтовой программы), где вы запускаете один экземпляр программы и передаете много данных для обработки (например, обработка всех писем), вы запускаете один экземпляр программы вершинного шейдера
для каждой вершины
, которая затем выполняется в одном потоке, управляемом потоковым многопроцессором
.
Our vertex shader
transforms vertices
or its parameters (pos, color, uv) like you want:
Наш вершинный шейдер
преобразует вершины
или их параметры (позиция, цвет, uv) так, как вы хотите:
Some stages are only executed when Tessellation
is used. Click the link below, if you want to see what happens when Tessellation
gets into the game.
Некоторые этапы выполняются только при использовании тесселяции
. Нажмите на ссылку ниже, если хотите увидеть, что происходит, когда тесселяция
включена в игру.
Click here for Tessellation (Нажмите здесь для тесселяции)
3.13 Primitive Assembly (Сборка Примитивов)
Towards the end of the geometry pipeline we gather the vertices
that assemble our primitive: a triangle
, line
or point
. The vertices
either came from the vertex shader
, or if tessellation
was active from the domain shader
.
Ближе к концу геометрического конвейера мы собираем вершины
, которые формируют наш примитив: треугольник
, линию
или точку
. Вершины
поступили либо из вершинного шейдера
, либо, если тесселяция
была активна, из доменного шейдера
.
What mode (triangle
, line
or point
) we are in was defined in the application for this drawcall
. Normally we would just pass the primitive for final processing and rasterzation
, but there is an optional stage that makes use of this information, the Geometry Shader
.
В каком режиме (треугольник, линия или точка) мы находимся, было определено в приложении для этого вызова отрисовки
. Обычно мы просто передаем (дальше по этапам) примитив для окончательной обработки и растеризации
, но есть необязательный этап, который использует эту информацию, - геометрический шейдер
.
3.14 Geometry Shader (Геометрический Шейдер)
The Geometry Shader
works on the final primitives
. Similar to the Hull Shader
it gets the primitive’s vertices
as input. It can modify those vertices
and even generate a few new ones. It can also change the actual primitive
mode. For example turn a point into two triangles
, or the three visible sides of a cube.
Геометрический шейдер
работает с окончательными примитивами
. Подобно шейдеру оболочки
, он получает вершины примитива
на вход. Он может изменять эти вершины
и даже создавать несколько новых. Также он может изменить текущий режим примитива
. Например, превратить точку в два треугольника
или три видимые стороны куба.
However geometry shader
is not really good for creating lots of new vertices
or triangles
, tessellation
is best left to the tessellation shaders
. The purpose of the geometry shader
is rather special given it’s the last stage prior preparing the primitive for rasterization
. For example geometry shader
plays a key-role in current voxelization techniques.
Тем не менее, геометрический шейдер
не очень хорошо подходит для создания большого количества новых вершин
или треугольников
, тесселяцию
лучше всего поручить тесселяционным шейдерам
. Предназначение геометрического шейдера
довольно специфическое, поскольку это последний этап перед подготовкой примитива к растеризации
. Например, геометрический шейдер
играет ключевую роль в текущих техниках вокселизации.
Here you can find good examples how to program & use geometry shaders
. And this is a great overview about the openGL pipeline
.
Здесь вы можете найти хорошие примеры программирования и использования геометрических шейдеров
. А это отличный обзор конвейера OpenGL
.
3.15 Viewport Transform & Clipping (Преобразование Вида и Отсечение)
Until here the programmers seem to use a quadratic space for all the operations (i guess it’s easier/faster that way) but now this needs to be fit to the actual resolution of your monitor (or window where the game is rendered into). More info about this you can find here in the “Viewport Transform / Screen Mapping” Section.
До этого момента программисты, кажется, использовали квадратное пространство для всех операций (я предполагаю, что так проще/быстрее), но теперь это нужно приспособить к фактическому разрешению вашего монитора (или окна, в котором отображается игра). Больше информации об этом можно найти здесь в разделе "Viewport Transform / Screen Mapping".
Also triangles
get cut if they overlap a certain security-border (Guard Band
) of the scene (this is called: Guard Band Clipping
and you find more infos here and here). The clipping is done because the Rastizer
can only deal with triangles
within the area it’s working on:
Также треугольники
обрезаются, если они пересекают определенную защитную границу (Guard Band
) сцены (это называется: отсечение по защитной границе
, вы найдете больше информации здесь и здесь). Отсечение выполняется, потому что растеризатор
может работать только с треугольниками
в пределах области, над которой он работает:
3.16 Triangles Journey (Путешествие Треугольников)
This is not really a separate step in the pipeline
but I found it very interesting so that it gets its own section.
Это не совсем отдельный этап в конвейере
, но мне это показалось настолько интересным, что он получает свой собственный раздел.
At this point we know the exact position, shading, etc. of several vertices
which form triangles
. These triangles
need to be “painted” and before we can do that, someone has to find out which pixels
of the screen are covered by the triangles. This is done by so called rasterizers
. It’s important to know, that there are several available AND several of them can work at the same triangle
if its big enough. Else it would mean that only one rasterizer
is working when you have big triangles
on screen and the others would have some vacation.
На данном этапе мы точно знаем позиции, затенение и другие характеристики нескольких вершин
, которые образуют треугольники
. Эти треугольники
нужно «закрасить», и для этого кто-то должен определить, какие пиксели
экрана закрыты треугольниками
. Это делают так называемые растеризаторы
. Важно знать, что (растеризаторов) доступно несколько и что несколько из них могут работать над одним и тем же треугольником
, если он достаточно большой. В противном случае это означало бы, что при наличии крупных треугольников
на экране работает только один растеризатор
, а остальные могли бы отдыхать.
Therefore every rasterizer
has the responsibility for certain parts of the screen. And if a triangle
belongs to such a responsibility area (the bounding box of the triangle
is taken to measure this), it is send to this rasterizer
so that he can work on it.
Поэтому каждый растеризатор
несет ответственность за определенные части экрана. И если треугольник
относится к такой зоне ответственности (для измерения этого берется bounding box треугольника
), он отправляется этому растеризатору
, чтобы тот мог поработать над ним.
3.17 Rasterizing (Растеризация)
The rasterizer
receives the triangles
he’s responsible for and first does a quick check if the triangle even faces forward. If not, it’s thrown away (backface culling). If the triangle
is “valid”, the rasterizer
creates pre-pixels
/fragments
by calculating the edges
which connect the vertices
(edge setup
) and so seeing which pixels
quads (2×2 pixels
) belong to the triangle
.
Растеризатор
получает треугольники
, за которые он отвечает, и сначала быстро проверяет, направлен ли треугольник вперед. Если нет, он отбрасывается (отсечение задних граней). Если треугольник
"правильный", растеризатор
создает предварительные пиксели
/фрагменты
, вычисляя грани
, соединяющие вершины
(настройка граней
), и таким образом видит, какие пиксельные
квадраты (2×2 пикселя
) принадлежат треугольнику
.
If you’re really into rasterizing
and micro-triangles
, you definitely should check out this presentation. And this article gives a good overview about it.
Если вам действительно интересна растеризация
и микротреугольники
, вам определенно стоит ознакомиться с этой презентацией. А эта статья дает хороший обзор по этой теме.
That the raster engine
always works with 2×2 pixels
which can make the render
process inefficient. See Book III for details.
Тот факт, что растерный движок
всегда работает с квадратами 2×2 пикселя
, может сделать процесс рендеринга
неэффективным. См. Книгу III для деталей.
After the pre-pixels
/fragments
are created, there’s a check if they would be even visible (or hidden by already rendered
stuff):
После создания предварительных пикселей
/фрагментов
проводится проверка, будут ли они вообще видимыми (или скрытыми уже отрендеренными
объектами):
Pixels
produced by therasterizer
are sent to theZ-cull unit
. TheZ-cull unit
takes apixel
tile and compares the depth ofpixels
in the tile with existingpixels
in theframebuffer
.Pixel
tiles that lie entirely behindframebuffer
pixels
are culled from thepipeline
, eliminating the need for furtherpixel
shading work.
– NVIDIA GF100 Whitepaper
Пиксели
, созданныерастеризатором
, отправляются вблок Z-отсечения
.Блок Z-отсечения
беретпиксельный
квадрат и сравнивает глубинупикселей
в квадрате с существующимипикселями
вкадровом буфере
.Пиксельные
квадраты, которые полностью находятся запикселями
кадрового буфера
, отсекаются отконвейера
, устраняя необходимость дальнейшей работы по оттенениюпикселей
.
– NVIDIA GF100 Whitepaper
3.18 Pixel Shader
After the pre-pixels
/fragments
are generated they can be “filled”. For every pre-pixel
/fragment
a new thread is generated and again distributed to all the available cores (like it was done with all the vertices
).
После создания предварительных пикселей
/фрагментов
они могут быть "заполнены". Для каждого предварительного пикселя
/фрагмента
создается новый поток и снова распределяется по всем доступным ядрам (как это было сделано со всеми вершинами
).
“Again we batch
up 32 pixel
threads, or better say 8 times 2×2 pixel
quads, which is the smallest unit we will always work with in pixel shaders
.”
"Снова мы группируем
32 потока пикселей
, или лучше сказать, 8 раз по 2×2 пиксельных
квадрата, которые являются наименьшей единицей, с которой мы всегда будем работать в пиксельных шейдерах
."
When the cores are done with their work, they write the results into the registers
from where they are taken and put into the caches
for the last step: Raster Output (ROP)
.
Когда ядра закончат свою работу, они записывают результаты в регистры
, откуда они берутся и помещаются в кэши
для последнего этапа: Растровый вывод
(ROP
).
3.19 Raster Output (Растровый вывод)
The final step is done by the so called “Raster Output
” Units which move the final pixel
data (just got from the pixel shader
) from L2 cache
into the framebuffer
which lays around in the VRAM
. The GF100 as an example has 48 such ROPs
and I interpret the dataflow (from L2 cache
to VRAM
) based on that they are placed really near to each other:
Заключительный этап выполняется так называемыми блоками "Растрового вывода
", которые перемещают окончательные данные пикселей
(только что полученные от пиксельного шейдера
) из кэша L2
в буфер кадра
, который находится в VRAM
. Например, GF100 имеет 48 таких ROP
, и я интерпретирую поток данных (от кэша L2
к VRAM
) на основе того, что они размещены очень близко друг к другу:
“[…]
L2 cache
, andROP
group are closely coupled […]”
– NVIDIA GF 100 Whitepaper
“[…]
кэш L2
и группаROP
тесно связаны […]”
– NVIDIA GF 100 Whitepaper
Besides of just moving pixel
data, the ROPs
also take care of pixel
blending, coverage information for anti aliasing
and “atomic operations
”.
Помимо простого перемещения данных пикселей
, ROP
(в множественном числе) также заботятся о пиксельном
смешивании, информации о покрытии для антиалиасинга
и атомарных операций
.
What a ride, it took a long time to bring all the information together so I hope you found this book useful.
Какое путешествие, потребовалось много времени, чтобы собрать всю информацию, поэтому я надеюсь, что эта книга была вам полезна.
The End.
Конец.
gdt
Интересный подход, облегчаете задачу критикам. Однако, оригинал и перевод ничем визуально не отличаются, это усложняет чтение. Было бы круто оригинал выделить визуально как-то (например, курсивом) или убрать под спойлер. То же самое касается выделения слов, сильно пестрит - я уверен есть способ, который меньше бросается в глаза.
И все же интересно, как правильно - мультипроцессор или многопроцессор? У вас встречаются оба термина. А перейдя по ссылке в начале статьи увидел "Драйвер уровень" вместо "Уровень драйвера". Думаю в целом с переводами есть еще над чем поработать.