Можно задействовать и другие участки конвейера. Если в пиксельном шейдере нужно вычислять некоторую линейную[Есть еще сферические и кубические текстуры, но их ценность для целей данной статьи сомнительна] функцию координаты, то можно перенести эту работу на этап растеризации, задав значения функции только в углах треугольника.
На этапе композиции можно выполнять условные присваивания, вычислять линейную комбинацию векторов, так же как и на этапе фильтрации. Однако эти действия не поддерживаются всеми современными платами для интересующих нас чисел высокой точности. Но если вас устроит «половинная» точность (16 бит), то оценка производительности может быть поднята еще выше.
Вы должны понимать, что это крайне оптимистичные оценки, реально достижимая скорость счета заметно ниже. На практике не всегда удается задействовать даже вершинные процессоры. Дело в том, что до Shader Model 3.0 они были лишены доступа к текстурам, то есть к памяти. К тому же их вывод не записывает в память непосредственно, а лишь определяет области, которые будут обсчитаны пиксельными процессорами. Конечно, и этим можно умело пользоваться, чтобы уменьшить размер и повысить скорость пиксельных шейдеров, но трудоемкость разработки всей программы для GPU сильно возрастает. Впрочем, здесь тоже ожидается скорый прогресс — уже вовсю говорят об унифицированных шейдерных процессорах, способных выполнять обработку как вершин, так и пикселов и не простаивать при любом виде нагрузки.
Словарь-минимумДля совершения первых шагов в освоении сопроцессора GPU начинающий разработчик должен научиться переводить графические термины на более привычный ему компьютерный язык.
Самое главное — понимание организации памяти. Универсальной встроенной структурой данных в GPU является текстура. Текстуры бывают одномерные, двухмерные и трехмерные[Т. Кормен, Ч. Лейзерсон, Р. Ривест. Алгоритмы. Построение и анализ. — М.: МЦНМО, 2000]. Это прямой аналог многомерному массиву. Пиксел текстуры (тексел) -элемент массива. К сожалению, максимальные ширина, высота и глубина текстуры строго ограничены. Этот предел зависит от платы и от размерности и обычно равен 2048 или 4096. Поэтому одномерные текстуры становятся малоинтересными — в них вмещается слишком мало данных. Трехмерные текстуры отпадают по другой причине — они могут быть только считаны, но рисование в них невозможно. Остаются только двухмерные текстуры, в которые нужно научиться упаковывать все прочие структуры данных. Заметим, что эффективная размерность всех текстур на единицу больше, поскольку каждый тексел может содержать до четырех цветовых компонентов. Например, длинный одномерный массив можно упаковать таким образом: первые четыре элемента записываются в тексел на пересечении первой строки и первого столбца, следующие четыре — в тексел из второго столбца и так до исчерпания первой строки, затем всё продолжается со следующей строки и т. д.
Любопытна система адресации в текстуре, которая осуществляется заданием по каждой координате действительного числа из диапазона [0,1]. Расстояние (изменение индекса при переходе) между соседними текселами уже не 1, как в обычном массиве, а зависит от разрешения текстуры (рис. 3). Для получения точного значения тексела необходимо указать координаты центра его «квадратика», при запросе по другому адресу результат будет зависеть от текущего режима фильтрации.
Запись данных в текстуру достигается назначением ее в качестве цели рендеринга (render target). Последующее рисование какой-либо фигуры фактически выбирает обновляемые элементы текстуры (рис. 4). Обычно рисуют один большой треугольник, покрывающий цель рендеринга с запасом, либо прямоугольник точно совпадающих размеров. Неудобно то, что координаты углов фигур нужно задавать уже не в текстурных, а в отличающихся от них экранных (viewport) координатах. Итак, координаты вершины определяют рассчитываемые фрагменты. Входные аргументы пиксельному шейдеру передаются через ассоциированные с вершиной данные, в первую очередь через текстурные координаты.
Процедура обработки одинакова для всех пикселов. Поэтому о пиксельном шейдере можно думать как о теле некоторого цикла. Также можно, рисуя меньшие фигуры и «играя» с тестом глубины, применять различные шейдеры избирательно. Такая необходимость возникает, когда алгоритмы обработки внутренних и приграничных точек текстуры существенно отличаются и их невозможно или нецелесообразно совмещать в одном шейдере.
Простейшие программыСейчас мы уже знаем, что GPU способен применять одинаковую программу для вычисления значения каждого элемента одного массива, основываясь на данных других массивов. Есть ли алгоритмы, которые формулируются именно таким образом? Оказывается, есть. К этому классу относятся, например, методы фильтрации изображений и часть способов приближенного решения дифференциальных уравнений, отражающих динамические явления физики. Именно такие алгоритмы проще всего переносятся на GPU, и именно на них достигается наибольшее ускорение.
Давайте рассмотрим что-нибудь посложнее. Задача редукции массива заключается в нахождении какой-то скалярной функции его элементов. Это может быть сумма всех чисел массива, или величина максимума, или что-то в том же духе. Поскольку шейдер ограничен в количестве операций, за один проход рендеринга решить задачу решительно невозможно. Применяется следующий способ. Порождается вспомогательная текстура, размерами чаще всего вдвое меньше исходной по обеим осям. Используемый шейдер, заполняя ее, вычисляет функцию только от четырех величин. Затем вспомогательная текстура назначается на вход шейдера, а выходом служит еще вчетверо меньшая текстура. И так до получения текстуры из одного пиксела, которая содержит ответ (рис. 5). Число проходов составляет логарифм от начального размера массива.
Умножить матрицу на вектор при ограничениях Shader Model 2.0 тоже не так-то просто. Одно из определений гласит, что произведение является линейной комбинацией столбцов исходной матрицы, взятых с весами из второго сомножителя. Поступают таким образом. На первом шаге каждый элемент матрицы умножается на соответствующий ему вес — получается вторая матрица. Последующие шаги посвящаются комбинированию столбцов — редукции только по горизонтали.
Еще интереснее дела обстоят с сортировкой массива. Сразу понятно, что в случае с GPU понадобится второй вспомогательный массив из-за невозможности выполнять чтение и запись одновременно с одним массивом. Но на этом сложности не кончаются. Алгоритмы сортировки основываются на операции сравнить-и-обменять: для выбранных двух позиций в массиве производится сравнение элементов, и, если они нарушают порядок сортировки, происходит их обмен. В GPU, как мы помним, определение новых значений массива выполняется независимо, поэтому каждое сравнение приходится делать дважды. Но и это еще не все. Выбор позиций для очередной операции сравнить-и-обменять в быстрых последовательных алгоритмах зависит от результата сравнения в предыдущих операциях. К счастью, в теории параллельных вычислений уже разработана тема сортирующих сетей, которую можно адаптировать для GPU. Количество операций, выполняемых параллельным алгоритмом, больше, чем у лучших последовательных алгоритмов, и их отношение растет с ростом массива. Тем не менее, благодаря своей мощи, современные GPU не уступают в скорости сортировки, хотя о существенном ускорении речь пока не идет.
Остальные алгоритмы конвертируются как-то аналогично или строятся на основе приведенных. Труднее всего GPU даются сложные структуры данных: списки и деревья, особенно их модификация. Поверьте (а если не хотите верить — пройдитесь по ссылкам сами), что придумано множество других любопытных способов переноса, казалось бы, неподходящих расчетов на GPU, включая, например, работу с разреженными матрицами. Но они уже выходят за рамки популярной статьи.
Заметьте, что в последних трех примерах достижение результата на GPU потребовало использования большего объема памяти, чем понадобилось бы на машине классической архитектуры. Добавьте к этому более высокую производительность GPU, выливающуюся в потенциальную способность перемалывать бОльшие объемы данных, и вы поймете, почему размер видеопамяти так высоко ценится среди расчетчиков. В этом вопросе проступает болезненное расхождение с другими пользователями, которые твердо знают, что для компьютерных игр памяти на графической плате много не надо.
ПерспективыЕсли говорить о ближайшем будущем самих чипов, то здесь все замечательно. Год за годом GPU прибавляют в скорости работы куда быстрее, чем CPU, увеличивая и без того немалый отрыв. Поколения графических чипов тоже сменяются куда чаще. Даже для непрофессионального использования уже некоторое время предлагается устанавливать в компьютер две видеокарты (технологии SLI или Crossfire). Можно будет использовать вторую плату исключительно как сопроцессор для расчетов, полностью освободив ее от обработки графики. Радужные перспективы GPGPU способны поблекнуть только из-за недостаточной программной поддержки.
Если говорить о ближайшем будущем самих чипов, то здесь все замечательно. Год за годом GPU прибавляют в скорости работы куда быстрее, чем CPU, увеличивая и без того немалый отрыв. Поколения графических чипов тоже сменяются куда чаще. Даже для непрофессионального использования уже некоторое время предлагается устанавливать в компьютер две видеокарты (технологии SLI или Crossfire). Можно будет использовать вторую плату исключительно как сопроцессор для расчетов, полностью освободив ее от обработки графики. Радужные перспективы GPGPU способны поблекнуть только из-за недостаточной программной поддержки.
ЗаключениеНеспособность центрального процессора в одиночку справляться с возникающими вычислительными задачами — секрет Полишинеля. Раньше всего это стало очевидно в машинной графике. В результате за прошедшие годы сформировался стандартный современный графический сопроцессор GPU. Сейчас речь заходит о создании других аппаратных акселераторов, в том числе и для игровых целей [Например, скоро вы можете столкнуться с невозможностью запустить игру без еще одного, на сей раз физического процессора:www.ageia.com/products/physx.html]. Но под каждое приложение свою «железку» не выпустишь, куда как интереснее увидеть нераскрытые возможности GPU, находящегося внутри почти каждого персонального компьютера.
Самое чудное во всей этой истории то, что GPU — а по сути суперкомпьютер на чипе — стал массовым продуктом не благодаря целенаправленной и продуманной стратегии ученых мужей, а скорее случайно — как побочное явление индустрии развлечений.
Лабораторные опытыОдно дело — лицезреть графики ускорения на страницах чужих статей, и совсем другое — убедиться в этом самостоятельно. Не случайно ведь пользуются широкой популярностью такие тесты, как 3DMark и Doom 3. Ничего подобного им по удобству и авторитетности в области вычислений на GPU пока не существует. Можно упомянуть разве что пакет GPUBench из Стэнфордского университета, но он предназначен для сравнения GPU только между собой и содержит лишь синтетические тесты вроде скорости выполнения одной инструкции, повторенной многократно. Более того, далеко не каждую найденную программу для GPU вообще удается запустить из-за ориентации ее авторов на конкретного производителя[Если вы читали статью сначала, то легко угадаете какого. К сожалению, неполная совместимость GPU еще не изжита окончательно] графических чипов. Поэтому я решил подойти к проблеме творчески и предложить самодельный набор тестов [Исходные тексты и исполняемые файлы http://jorik.sourceforge.net], а заодно убедить вас, что работа с GPU не так уж и сложна, как может показаться.
Тест 1. Решается динамическая задача распространения звуковых волн в двухмерном пространстве (см. рис.) при помощи простейшей разностной схемы на сетке 1536x1536. Как уже было сказано, такого рода задачи идеально подходят для GPU. Результат неудивителен — GPU может справиться с заданием в десять с лишним раз быстрее (табл. 1).
Примечательно другое. Во-первых, расстановка плат по производительности в играх и в неграфических приложениях может сильно отличаться. Во-вторых, на бюджетной плате ATI Radeon 9600 установлена такая же обычная память, что и на многих системных платах: 200 МГц DDR SDRAM 128 бит (аналог двухканального режима). Оказывается, огромное ускорение может быть получено даже без быстрой и дорогой графической памяти топовых моделей!
Тест 2. Любопытно также рассмотреть другую крайность — «неудобную» для GPU задачу. Производится сортировка массива из 16 млн. действительных чисел. Здесь CPU выполняет алгоритм быстрой сортировки (STL quick sort), GPU — параллельной битонической сортировки (см. выше). В результате разного количества операций в алгоритмах быстрая видеокарта лишь приближается к центральному процессору (табл. 2).
Тест 3. Исследуется скорость копирования многомегабайтной текстуры, где каждый пиксел содержит четыре 32-битных действительных числа, из системной памяти в видеопамять и обратно. Эта скорость важна, потому что она определяет, какие задачи имеет смысл доверять GPU. Если задача проста, то преимущество GPU может быть нивелировано временем передачи данных к нему. Поэтому выгода получается только на крупных задачах, обсчет которых занимает гораздо больше времени, нежели пересылка данных. Результаты[Их достоверность перепроверялась пакетом GPUBench, скорость для платы nVidia близка к данным презентации "Interactive Geometric Computations using Graphics Processors"] теста оказались просто обескураживающими (табл. 3).
Эти скорости не только ниже 4 Гбит/с шины PCI-Express, но, пожалуй, и самых первых версий AGP. Хочется надеяться, результаты столь низки оттого, что современным играм не приходится часто выполнять копирование больших текстур, особенно описанного формата, и мы имеем дело всего лишь с временной недоработкой драйверов.
До проведения тестирования предполагалось, что использующие GPU программы практически не будут загружать центральный процессор. Действительность оказалась не такой радужной. Нагрузка была очень высокой, доходя до 100%, так что даже окошки по экрану перетаскивались с трудом. Эффект наблюдался и для DirectX-, и для OpenGL-приложений. И еще нельзя не отметить, что плата ATI при большей сетке в первом тесте и при некоторых других обстоятельствах приводила к синему экрану смерти — это уж совсем никуда не годится.
ТЕМА НОМЕРА: По ту сторону треугольников
Автор: Павел Воронин
Приблизительно так можно перевести с английского «Beyond Triangles» — название последней главы замечательной книги «GPU Gems». Она одной из первых рассказала массовому читателю о возможности использовать графический процессор для чего-то большего, нежели рендеринг составленных из треугольников моделей. Тогда это были главным образом научные расчеты: динамика жидкостей, визуализация результатов объемного сканирования, расчет стереограмм и т. д. Однако за прошедшие несколько лет ситуация радикально изменилась: на GPU были перенесены все классические алгоритмы и разработан целый ряд новых, максимально использующих преимущества, даваемые архитектурой современных видеокарт. Техника GPGPU[General-Purpose Computation on GPUs, вычисления общего назначения на графических процессорах] становится по-настоящему general-purpose, универсальной, подходящей для решения практически любых задач. Мне, как большому энтузиасту этой области компьютерных наук, хотелось бы рассказать об одном из самых многообещающих ее направлений: использовании GPU для расчета внутренней логики игр.
Отчего да почемуДело в том, что обычно ядро игры устроено так: сначала на центральном процессоре рассчитываются все перемещения и взаимодействия объектов, изменение их формы (анимация), параметры среды и пр. Затем, с учетом всех этих данных, сцена (то есть набор моделей и спецэффектов) переводится все тем же центральным процессором в стандартное представление: набор координат вершин треугольников и их атрибутов (нормалей, цветов, текстурных координат и прочего). Полученный массив данных, зачастую довольно большой, пересылается на видеокарту, которая и занимается их выводом на экран: расчетом освещения, наложением текстур и применением различных спецэффектов. И хотя пропускная способность шины процессор-видеокарта за последние годы подросла довольно сильно, она до сих пор зачастую является «узким местом» при программировании игр. Поэтому весьма соблазнительно избежать этой пересылки и реализовать выполнение как можно большей части алгоритмов прямо на графическом процессоре.
Уроки физикиЧего не хватает современным играм, чтобы их наконец-то можно было назвать по-настоящему реалистичными? Поиграв во второй HalfLife на максимальных настройках графики, я уже совсем не уверен, что главным препятствием на пути окончательного переселения человечества в компьютерные миры является несовершенство визуальной составляющей. Несколько лет назад мне довелось повозиться с хорошим шлемом виртуальной реальности, и я не сомневаюсь: подай на него картинку уровня детища Valve, и достоверности изображения для девяноста девяти процентов случаев хватит с лихвой.
Другое дело, взаимодействие объектов игрового мира друг с другом и отклик их на действия человека. Лично меня при игре даже в самые динамичные игры вроде Warcraft III, Need For Speed, Grand Theft Auto или ту же HalfLife не покидает ощущение, что я попал в галактику, вдруг остывшую до нуля градусов по Кельвину. Планеты здесь подобно Луне лишены атмосферы, и ветер на них отсутствует. Дым и осколки разорвавшихся снарядов во всем подражают рентгеновским лучам и проходят сквозь тела, не причинив им ни малейшего вреда. Сами тела при этом сделаны из сплошного куска металла, но, разрушаясь, тут же растворяются в воздухе. Жидкостей здесь не бывает. Как, впрочем, и резины, дерева, глины или ваты.