2026 г.

GPU и графический конвейер: от треугольника до кадра

Проект ЦИТадель

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

Раздел 1 оценивает масштаб работы. Раздел 2 объясняет ориентацию GPU на пропускную способность, раздел 3 проводит геометрию через графический конвейер, а раздел 4 рассматривает программируемые шейдеры. Раздел 5 вводит арифметическую интенсивность и ограничения памяти. Раздел 6 разбирает способы не выполнять невидимую работу и сопоставляет растеризацию с трассировкой лучей. Раздел 7 связывает архитектуру GPU с машинным обучением, раздел 8 предлагает практикум в браузере.

1. Масштаб задачи

Кадр 4K содержит 3840 × 2160, то есть около 8,3 миллиона пикселей. При частоте 60 Гц экран получает почти 498 миллионов итоговых значений в секунду. Реальное число фрагментных вычислений не равно этому числу: часть геометрии отбрасывается заранее, один пиксель могут закрывать несколько примитивов, а сглаживание, масштабирование и постобработка добавляют работу.

Тем не менее во многих стадиях одна и та же программа применяется к большому числу вершин, фрагментов или элементов изображения. Их вычисления часто можно выполнять параллельно, хотя полной независимости нет: используются общие текстуры и буферы, смешение цветов зависит от уже записанного результата, а экранные производные связывают соседние вызовы. Центральный процессор также умеет параллельные и векторные вычисления, но значительную часть его ресурсов занимают механизмы, уменьшающие задержку одной или нескольких сложных нитей: крупные кэши, предсказание переходов и внеочередное исполнение. GPU отдаёт больше ресурсов массовому исполнению сходных операций. Это не замена CPU, а другое соотношение между задержкой, управлением и пропускной способностью.

2. Другая машина: пропускная способность вместо задержки

GPU рассчитан прежде всего на высокую пропускную способность множества параллельных вызовов. Из этого следуют две важные особенности:

  • Вызовы исполняются группами. В модели SIMT отдельные нити имеют собственные данные и путь управления, но оборудование планирует их группами — например, варпами в терминологии CUDA. Если нити одной группы расходятся по ветвям, аппаратура выполняет пути с разными наборами активных линий, и загрузка вычислителей может снизиться. Потеря возникает именно при расхождении внутри группы; её величина зависит от кода, компилятора и архитектуры.
  • Задержка скрывается другой готовой работой. Пока одна группа ждёт данные или результат операции, планировщик может выдать инструкцию другой. Для этого на вычислительном блоке должно находиться достаточно готовых групп, а потребление регистров и разделяемой памяти не должно слишком ограничивать их число. Кэши при этом не исчезают: иерархия памяти и локальность доступа остаются важными.

Полезное приближение таково: CPU оптимизирует задержку небольшого числа сложных потоков, GPU — суммарную пропускную способность большого числа сходных вызовов. Реальные процессоры сочетают оба набора приёмов, а приложение обычно оставляет последовательное управление и разнородную работу на CPU, передавая GPU достаточно крупные параллельные задачи. Связь этой модели с потоками и общей памятью подробнее разобрана в статье «Конкурентность в современных языках».

3. Конвейер: от треугольника до пикселя

Приложение передаёт GPU команды, буферы с геометрией, текстуры и параметры. Поверхности обычно представлены сетками треугольников: три не лежащие на одной прямой вершины задают плоскость, а сложную форму можно приблизить множеством таких простых примитивов. Графические API поддерживают также точки и линии, а современные конвейеры могут создавать примитивы дополнительными стадиями, но базовый путь удобно рассмотреть на треугольнике:

  1. Обработка вершин. Вершинный шейдер вычисляет положение вершины в однородных координатах отсечения и передаёт дальше атрибуты: нормаль, цвет, координаты текстуры. Матрицы модели, вида и проекции часто объединяют, но шейдер может выполнять и другие преобразования.
  2. Сборка и отсечение примитивов. Вершины объединяются в треугольники. Части примитивов вне объёма видимости отсекаются, затем выполняются перспективное деление и преобразование к области экрана.
  3. Растеризация. Растеризатор определяет, какие позиции выборок покрывает примитив, и интерполирует по нему атрибуты вершин. Так возникают фрагменты — кандидаты на запись в буферы кадра.
  4. Обработка фрагментов. Фрагментный шейдер использует материал, текстуры, освещение и интерполированные данные, чтобы вычислить выходные значения. Число его вызовов зависит от площади примитивов, перекрытия, сглаживания и раннего отбрасывания.
  5. Тесты и объединение результата. Проверки глубины и трафарета решают, проходит ли фрагмент; смешение определяет, как его цвет сочетается с уже записанным. Реализация может выполнить допустимую проверку глубины до шейдера или после него. Результат записывается в цветовые и глубинные вложения, а подготовленное изображение затем выводится на экран.

Это упрощённая схема: в конкретном API присутствуют дополнительные стадии и оптимизации. Нормативное описание растеризации и порядка операций можно найти в спецификации Vulkan. На сайте уже рассмотрен другой уровень той же цепочки: статья «Как устроен браузер» объясняет, как отрисованные слои передаются композитору. Перенос анимации в композицию часто разгружает главный поток, но конкретное использование GPU остаётся решением браузера и драйвера.

4. Шейдеры: конвейер становится программируемым

Ранние графические ускорители предоставляли в основном фиксированный набор операций. Программируемые вершинные и фрагментные стадии превратили конвейер в платформу: разработчик пишет функцию одного вызова, а API запускает её для множества вершин или фрагментов. Вычислительные шейдеры распространили ту же модель на задачи, не связанные непосредственно с рисованием.

Полезная исходная модель — независимое применение одной функции к множеству элементов, но современный шейдер не обязан быть чистой функцией. Он может читать общие ресурсы, а разрешённые стадии — записывать буферы и текстуры или использовать атомарные операции. Фрагментные вызовы могут совместно участвовать в вычислении экранных производных. Такие возможности требуют правил памяти и синхронизации, изложенных, например, в спецификации WGSL. Ветвление тоже не запрещено: оно становится дорогим, когда нити одной аппаратной группы идут разными путями и обе ветви содержат существенную работу. Поэтому производительность шейдера проверяют профилировщиком, а не выводят только из наличия if.

5. Вычисления и движение данных

Производительность может быть ограничена вычислительными блоками, пропускной способностью памяти, задержкой, числом доступных параллельных вызовов или другой частью конвейера. Для первого приближения полезна арифметическая интенсивность — число операций на байт данных, перенесённых через рассматриваемый уровень памяти. Модель Roofline ограничивает достижимую производительность меньшей из двух величин: пиком вычислений и произведением пропускной способности памяти на арифметическую интенсивность. Следовательно, задача с низкой интенсивностью чаще ограничена движением данных, а с высокой — вычислениями, если она достаточно велика для загрузки устройства.

У GPU есть иерархия регистров, разделяемой памяти вычислительных групп, кэшей и внешней памяти. Повторное использование данных на близком уровне, согласованный доступ соседних нитей, компактные форматы вершин и сжатие текстур уменьшают внешний трафик. Но универсального правила «память всегда является пределом» нет: границу определяют конкретный алгоритм, устройство и уровень иерархии. Исходная модель подробно описана в работе о Roofline. Связанные вопросы представления данных рассматриваются в статье «Как современные языки управляют памятью».

6. Метод отрасли: не делать лишнего

Первый способ ускорить кадр — не обрабатывать то, что не повлияет на результат. Приложение или GPU отбрасывает объекты вне объёма видимости, невидимые стороны и геометрию, закрытую другими объектами. Уровни детализации заменяют далёкую модель более простой, а цепочка уменьшенных копий текстуры (mipmap) предоставляет представления для разных масштабов. Ранний тест глубины способен остановить часть фрагментов до дорогого шейдера, если его семантика это допускает. Эти приёмы уменьшают разную работу и применяются на разных стадиях, поэтому их эффект измеряют отдельно.

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

7. Пиксели и матрицы: почему ИИ считает на видеокартах

Во многих нейронных сетях значительная доля времени приходится на умножения матриц и свёртки. Такие операции можно разбить на множество параллельно вычисляемых блоков и повторно использовать загруженные данные. Поэтому они хорошо соответствуют ориентации GPU на пропускную способность. Универсальные вычислительные API избавили разработчиков от необходимости представлять неграфические данные как текстуры и проводить расчёт через графические стадии, а специализированные матричные блоки ускорили операции умножения с накоплением.

Совпадение не полное. Функции активации, нормализация, редукции, обмен между устройствами и небольшие матричные операции часто ограничены памятью или задержкой. Даже матричное умножение при малом размере пакета может не загрузить GPU. Поэтому скорость обучения или вывода определяется не словом «матрица», а размером задачи, повторным использованием данных, точностью чисел, обменами и долей операций, подходящих устройству. Эти различия показаны в официальном руководстве по производительности вычислений для глубокого обучения. Тематические материалы находятся в разделе «Искусственный интеллект»; здесь достаточно главного вывода: графика создала массовый рынок параллельных ускорителей, а программируемость и развитая экосистема сделали их доступными другим областям.

8. Практикум: конвейер в браузере

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

Задание 1. Функция одного фрагмента. Залейте экран одним цветом, затем постройте горизонтальный градиент из координаты фрагмента. Нарисуйте круг по расстоянию до центра и изменяйте его радиус со временем. Опишите, что задаёт ваша функция, а что делает среда: полноэкранный примитив, растеризацию и множество вызовов шейдера.

Задание 2. Расхождение ветвей. Подготовьте два варианта условного шейдера с одинаковой полезной работой. В первом условие должно быть одинаковым для больших соседних областей, во втором — чередоваться между соседними фрагментами. Убедитесь, что компилятор не удалил результат вычислений, прогрейте оба варианта и проведите несколько измерений. Если песочница показывает только частоту кадров, учитывайте её грубость и ограничение частотой экрана. Не делайте универсального вывода по одному GPU: объясните результат через расхождение внутри аппаратных групп и возможные оптимизации компилятора.

Задание 3. Преобразование вершины. Выберите соглашение о векторах и порядке умножения матриц. В однородных координатах поверните точку, перенесите её, примените матрицу вида и проекции, затем выполните перспективное деление. Перемножьте матрицы заранее и сравните результат. Отдельно объясните, почему перенос нельзя представить обычной матрицей 3 × 3 над трёхмерным вектором без однородной координаты.

Задание 4. Композиция в браузере. В инструментах разработчика включите подсветку перерисовок и, если доступно, отображение композиционных слоёв. Сравните анимацию transform или opacity с изменением свойства, вызывающего раскладку. Запишите, какие стадии повторились. Не считайте наличие отдельного слоя доказательством конкретной реализации на GPU: решение зависит от браузера, драйвера и ресурсов.

Задание 5. Оценка масштаба. Вычислите число экранных выборок в секунду для своего разрешения и частоты обновления. Затем составьте несколько сценариев: без перекрытия, с заданным коэффициентом перекрытия и с несколькими выборками сглаживания. Для каждого явно запишите допущения и оцените хотя бы объём чтения и записи цвета и глубины. Сравнивать этот результат следует с измеренными характеристиками устройства или данными профилировщика, а не с одной тактовой частотой CPU.

Задание 6*. Неграфические вычисления. В примере WebGPU сложите два больших массива. Отделите подготовку конвейера и передачу данных от повторных запусков, прогрейте реализацию и проверьте несколько размеров. Измерьте как время самой работы GPU, если доступна возможность timestamp-query, так и полное время с загрузкой и чтением результата; иначе дождитесь завершения очереди и явно назовите ограничение измерения. Сравните с циклом над типизированными массивами JavaScript. Объясните, почему сложение массивов ограничено в основном памятью и почему граница окупаемости передачи работы зависит от устройства и браузера.

Итоги

  • GPU ориентирован на пропускную способность множества сходных вызовов. Групповое исполнение и переключение между готовыми группами помогают использовать вычислители, но расхождение ветвей, нехватка параллельной работы и ограничения ресурсов снижают загрузку.
  • Упрощённый конвейер включает обработку вершин, сборку и отсечение примитивов, растеризацию, обработку фрагментов, тесты и объединение результата. Порядок отдельных оптимизаций, включая ранний тест глубины, зависит от семантики шейдера и API.
  • Шейдер описывает один вызов, а система запускает множество вызовов. Современные шейдеры могут обращаться к общей памяти, поэтому независимость является полезной моделью, но не всеобщим правилом.
  • Арифметическая интенсивность связывает объём вычислений с движением данных. Задача может упираться как в память, так и в вычисления, задержку или недостаточный параллелизм.
  • Отсечение, уровни детализации, mipmap-уровни и ранние тесты уменьшают ненужную работу. Растеризация и трассировка лучей могут дополнять друг друга; ни один метод сам по себе не гарантирует точности изображения.
  • Крупные матричные операции и свёртки хорошо соответствуют архитектуре GPU, но другие операции машинного обучения нередко ограничены памятью или задержкой. Итог зависит от всего вычислительного графа и обмена данными.

Литература

  1. T. Akenine-Möller et al., "Real-Time Rendering," 4th ed., CRC Press, 2018 — систематическое изложение рендеринга реального времени.
  2. CUDA Programming Guide: Writing CUDA SIMT Kernels — модель группового исполнения, расхождение ветвей и организация параллельной работы.
  3. Khronos Vulkan Tutorial: Graphics Pipeline Basics и Vulkan Guide: Depth — стадии конвейера и ранние или поздние проверки глубины.
  4. S. Williams et al., "Roofline: An Insightful Visual Performance Model for Multicore Architectures", 2008 — связь вычислительного предела, пропускной способности и интенсивности.
  5. спецификация WebGPU и WebGPU Fundamentals — графические и вычислительные конвейеры в браузере.
  6. Оглавление раздела «Компьютерная графика»; связанные материалы: «Как устроен браузер», «Веб-производительность», «Как современные языки управляют памятью», «Конкурентность в современных языках» и раздел «Искусственный интеллект».
404 Not Found

404 Not Found


nginx/1.24.0 (Ubuntu)

Связь с редакцией