Как же эти проблемы решаются в GPU? При описании графического конвейера неоднократно подчеркивалось, что внутри каждого блока конвейера выполняются независимые действия: вершины обрабатываются независимо одна от другой, аналогичное утверждение справедливо для треугольников и т. д. Поэтому не только отдельные этапы конвейера функционируют одновременно, но и на каждом этапе идет параллельная обработка. В этом смысле внутри GPU выделяются наборы вершинных и пиксельных процессоров (рис. 2). Для обеспечения произвольного порядка обработки фрагментов текстура, в которую выполняется рисование, не может в то же самое время использоваться и для выборки, то есть видеопамять делится на непересекающиеся участки только-для-чтения и только-для-записи. Также не могут совпадать обновляемые точки в целевой структуре, поскольку итоговое положение каждого фрагмента фиксируется еще на этапе растеризации. Этими ограничениями достигается достаточное свойство параллельности пиксельных процессоров. Теперь, несмотря на то что каждый шейдер – это последовательная программа, при задержке обращения к памяти при обработке одного фрагмента GPU может не простаивать, а переключиться на другой фрагмент – кандидатов всегда достаточно. Имеются и элементы явного параллелизма в шейдерах: каждая ассемблерная инструкция может выполнять операцию не со скалярами, а сразу с четырехэлементными векторами[Число четыре возникло не случайно – именно такова размерность гомогенного пространства, и таково число компонентов в полупрозрачной цветной текстуре. Векторными операциями можно не пользоваться, но тогда эффективность GPU резко снижается], есть комбинированная инструкция умножь-затем-прибавь.
Без ячеек памяти, которые можно и считывать, и записывать, совсем обойтись, конечно, нельзя. Каждому шейдеру для этой цели предоставляются регистры, их мало (табл. 1), приходится экономить, но благодаря этому все промежуточные вычисления ведутся без обращения к внешней памяти, куда попадает лишь финальный результат. Малый размер шейдера и его общность для всех фрагментов преследуют ту же цель – хранить код программы не в памяти, а внутри процессора. Ведется последовательная политика, включающая разъяснительные мероприятия среди разработчиков, увеличения количества арифметических операций между последовательными обращениями к памяти.
Во всем конвейере главными претендентами для выполнения неграфических расчетов, безусловно, являются вершинные и пиксельные процессоры. Рассмотрим для примера плату GeForce 6800 Ultra. В ней имеется шесть вершинных процессоров, каждый из которых способен за такт выполнять максимум две арифметические операции над четырехэлементными векторами, а также шестнадцать пиксельных процессоров, способных на три векторные операции за такт. Умножая на частоту чипа 425 МГц, получаем верхнюю оценку производительности в 100 Гфлопс. Проделав те же выкладки для новейшей GeForce 7900 GTX, имеющей уже восемь вершинных и двадцать четыре пиксельных процессора и функционирующей на частоте 650 МГц, получаем почти 230 Гфлопс.
Можно задействовать и другие участки конвейера. Если в пиксельном шейдере нужно вычислять некоторую линейную[Есть еще сферические и кубические текстуры, но их ценность для целей данной статьи сомнительна] функцию координаты, то можно перенести эту работу на этап растеризации, задав значения функции только в углах треугольника.
На этапе композиции можно выполнять условные присваивания, вычислять линейную комбинацию векторов, так же как и на этапе фильтрации. Однако эти действия не поддерживаются всеми современными платами для интересующих нас чисел высокой точности. Но если вас устроит «половинная» точность (16 бит), то оценка производительности может быть поднята еще выше.
Вы должны понимать, что это крайне оптимистичные оценки, реально достижимая скорость счета заметно ниже. На практике не всегда удается задействовать даже вершинные процессоры. Дело в том, что до Shader Model 3.0 они были лишены доступа к текстурам, то есть к памяти. К тому же их вывод не записывает в память непосредственно, а лишь определяет области, которые будут обсчитаны пиксельными процессорами. Конечно, и этим можно умело пользоваться, чтобы уменьшить размер и повысить скорость пиксельных шейдеров, но трудоемкость разработки всей программы для GPU сильно возрастает. Впрочем, здесь тоже ожидается скорый прогресс – уже вовсю говорят об унифицированных шейдерных процессорах, способных выполнять обработку как вершин, так и пикселов и не простаивать при любом виде нагрузки.
Для совершения первых шагов в освоении сопроцессора GPU начинающий разработчик должен научиться переводить графические термины на более привычный ему компьютерный язык.