Прежде программирование графики можно было отнести к декларативному типу. Трехмерное приложение перечисляло все объекты в сцене, свойства их поверхностей, говорило, где расположены источники освещения, куда смотрит камера и т. п. В завершение следовала команда графической плате: «А теперь возьми и отобрази, что видит камера!» Эта ситуация была бы всем хороша в спокойные времена, но не когда производители игр отчаянно борются за внимание пользователей, которое, по их мнению, можно привлечь только новыми и желательно уникальными эффектами. Поэтому наметился постепенный переход к императивной парадигме программирования графики. То есть вместо выбора одного из предопределенных типов обработки данных производители игр получили возможность самостоятельно писать малюсенькие программки, непосредственно выполняемые графическим процессором. В первую очередь это затронуло блок обработки вершин, а затем и фрагментов (обведены оранжевым цветом на рис. 1). Поскольку главным образом под эффектами понималась более точная передача игры света и тени, то программки эти стали называть шейдерами (от англ. shade – тень), соответственно разделяя их на вершинные и пиксельные. Появились специализированные низкоуровневые ассемблеры.
Поначалу (2001 год) шейдеры были сильно ограничены в функциональности: например, пиксельный шейдер мог считывать цвет точки текстуры только четыре раза и выполнять над этими цветами не больше восьми арифметических операций[Хотя малым это стало казаться только сейчас, а на момент появления впечатляло].
Переломным моментом можно считать самый конец 2002 года, когда в продаже появились платы семейства GeForce FX от nVidia и Radeon 9500 (и выше) от ATI. В них была заложена поддержка стандарта шейдеров Shader Model 2.0, который примечателен главным образом двумя аспектами.
Стандарт требовал от GPU умения выполнять гораздо более сложные программы и по количеству инструкций, и по числу обращений к текстурам.
Все промежуточные операции должны были выполняться с действительными числами высокой (в сравнении с предшествующими моделями GPU) точности. А производители сразу ввели поддержку текстур, в которых цвета хранятся также в виде действительных чисел.
Хотя позже появляются и другие модификации стандарта, включая последний на сегодняшний день Shader Model 3.0, шейдеры второй версии остаются по-прежнему актуальными, потому что платы, поддерживающие только их, присутствуют на рынке и сегодня. Особенности стандартов приведены в таблице 1.
Как только число инструкций в программе достигло десятков, пользоваться ассемблером стало не так удобно. Поэтому к тому же времени формируются специализированные C- подобные языки высокого уровня. Заслуживают упоминания как минимум три из них: cg от nVidia, HLSL из DirectX и GLSL из OpenGL. Все они очень похожи, но, к сожалению, отличаются лексическими и синтаксическими деталями. Их компиляторы к сегодняшнему дню стали довольно зрелыми, способны оптимизировать код, хотя небольшая вероятность наткнуться на неправильно скомпилированный шейдер еще остается. Отказываться от высокоуровневых языков сейчас приходится лишь в исключительных случаях, например, чтобы уложиться в лимит регистров или инструкций.
Достоинства графических чипов: высокая производительность, точность и достаточная простота программирования не могли не быть замеченными, в первую очередь университетами. В 2003 году наблюдается всплеск научных статей, посвященных алгоритмам и принципам расчетов общего назначения средствами графических плат (GPGPU[GPGPU – General-Purpose Computation on GPUs]). С 2004 года специально организуются научные конференции.
В этой связи любопытно поведение двух крупнейших игроков на рынке графических чипов. Если aTI, по-видимому, заняла выжидательную, консервативную позицию: «GPU нужен только для игр», то nVidia, наоборот, проявляет заметную активность. Она пропагандирует идею GPGPU, организует курсы, призывая исследователей пользоваться ее аппаратурой для неграфических вычислений[gpgpu.org/s2005], предоставляет им временную или постоянную работу. Под эгидой nVidia издано уже два бестселлера «GPU Gems»[developer.nvidia.com/object, developer.nvidia.com/object/gpu_gems_2_h ome.html], в которые вошли главы по вычислениям общего назначения. С 2002 года сотрудники фирмы ведут сайт gpgpu.org, пытающийся систематизировать все результаты в этой области. nVidia продает ряд продуктов для нужд киноиндустрии, на деле доказывая нешуточность идеи.
Каковы результаты этой активности? Судя по публикациям, GPU удается найти применение в самых различных областях высокопроизводительных вычислений, включая высококачественный рендеринг, трассировку лучей, обработку изображений и сигналов, машинное зрение, компрессию, поиск и сортировку, биоинформатику, решение систем линейных уравнений, моделирование физических эффектов. Достигаемое ускорение колеблется от случая к случаю, но типично составляет несколько крат по сравнению с расчетом на центральном процессоре. Вы спросите, отчего же CPU так катастрофически проигрывают, если они изготовляются на таких же, если не на лучших полупроводниковых фабриках, содержат сопоставимое число транзисторов[Буквально одно сравнение high-end-продуктов в подтверждение: 376 млн. транзисторов в двухъядерном Intel Pentium EE 955 против 384 млн. в ATI Radeon X1900XTX], а их рабочие частоты в разы выше, чем у GPU?
Одно арифметическое устройство, оперирующее числами с плавающей точкой, при современном технологическом процессе производства чипов занимает на кристалле меньше одного квадратного миллиметра[Эти и последующие числа раздела взяты из книги «GPU Gems 2»]. Поэтому во всем чипе их можно иметь сотни, но проблема не в количестве устройств, а в том, как их все загрузить работой. К сожалению, на этом пути есть препятствия.
В первую очередь – память. По закону Мура, каждый год количество транзисторов на чипе возрастает наполовину, возрастает (но медленнее) и скорость их работы, так что суммарно можно говорить примерно о семидесятипроцентном повышении теоретической производительности устройств. Почему теоретической? Да потому, что пропускная способность памяти ежегодно возрастает примерно на 25%, а ее латентность (задержка обращения к новому участку памяти) сокращается и того медленнее – всего на 5% в год. Поэтому если не предпринимать дополнительных усилий, то самое слабое звено и будет определять производительность всей системы.
Центральный процессор обеспечивает просто райские условия для разработчика: любая инструкция в программе может считать или записать произвольную ячейку большой оперативной памяти. На деле это выливается в совершенно нерегулярный набор обращений к памяти. И чтобы ее латентность не была столь критической, в процессор приходится встраивать быструю кэш-память. И встраивать много – кэш сейчас занимает не меньше половины площади кристалла, а значит, ее не занимают вычислительные блоки. Причем во многих сценариях большой кэш оказывается неэффективен, к примеру, если обращение к ячейке памяти происходит лишь единожды, как при обработке потоков.
Второй важной причиной является последовательный характер обычных программ – наборов инструкций, которые для получения желаемого результата должны выполняться друг за другом. Если одна инструкция задержится медленной памятью, то задержится исполнение и всех остальных. Конечно, не все инструкции являются зависимыми и поэтому могут выполняться одновременно. Но явно эта независимость в программе не отражена, так что на выявление скрытого параллелизма тратится другая заметная порция площади кристалла. В самом лучшем случае удается наскрести работу для считанных единиц исполнительных устройств.
Как же эти проблемы решаются в GPU? При описании графического конвейера неоднократно подчеркивалось, что внутри каждого блока конвейера выполняются независимые действия: вершины обрабатываются независимо одна от другой, аналогичное утверждение справедливо для треугольников и т. д. Поэтому не только отдельные этапы конвейера функционируют одновременно, но и на каждом этапе идет параллельная обработка. В этом смысле внутри GPU выделяются наборы вершинных и пиксельных процессоров (рис. 2). Для обеспечения произвольного порядка обработки фрагментов текстура, в которую выполняется рисование, не может в то же самое время использоваться и для выборки, то есть видеопамять делится на непересекающиеся участки только-для-чтения и только-для-записи. Также не могут