DX 10 post, part 3 – New hardware features

DX 10 post, part 1 – Infrastructure
DX 10 post, part 2 – API changes
DX 10 post, part 3 – New hardware features

Про DX11 уже рассказали на GameFest 2008, но презентации еще не выложили, и, будем считать, это дает мне минутку рассказать, что же нового добавили в DX10.
Вообще, если кто-то программирует графику на PC и всего этого давно не знает, то я напуган за родной DX. Кстати, расскажите, кто за последний год начал потихоньку переписывать рендер на DX10(Коля, только не ты!)? Я буду шибко признателен, мне всегда интересно поговорить и послушать вашу ненависть (или даже, тьфу-тьфу-тьфу, радость).


Конечно же, первым пунктом – Geometry Shaders. Geometry Shader – это дополнительный шейдер между Vertex Shader и Pixel Shader, который может генерировать примитивы. На вход ему подается примитив с информацией о соседях, на выход – можно сгенерировать несколько (не фиксированное число).
Основная идея – наконец генерировать геометрию на GPU. Мне иногда кажется, что мужики очень стремились к показательной задаче геометрического процессинга – построению shadow volumes на GPU. Мол, если уж строить shadow volume получается, то и много всего остального получится. Кхм.
Так вот, Geometry Shaders очень плохо подходят для тесселяции и нужны для всяческой локальной генерации геометрии по мелочи. Ну там, совсем банальное – можно патикл строить по одному вертексу. Менее банальное – использовать стрипы геометрии в post-processing, например в motion blur – из карты скоростей генерировать геометрические линии, по которым блурится картинка (так делает Lost Planet). Совсем из другой области – например, делать рендер в Cubemap за один проход, GS выясняет в какие стороны cubemap попадает треугольник, и разбивает его на несколько.
В первой части GPU Gems 3 обсуждается несколько техник про GS, от некоторых мне хочется рвать волосы на жопе (процедурная генерация ландшафтов – сначала сгенерировать перлином в трехмерной текстуре распределение, потом запустить на этом marching cubes, потом тремя проходами GS сделать из этого indexed geometry, чтобы ее кешировать), до некоторых забавных (мне понравилась идея генерировать силуэты на GS для деревьев, чтобы не было видно недостатка полигонов на границе. Помните древние мечтания Hugues Hoppe про Silhouette Clipping? Это оно на GPU).
Основное препятствие использования GS – их скорость на современном железе. Тормозят. Думаю, именно поэтому все статьи про GS в GPU Gems – от NV, а не от девелоперов.

Насколько я понимаю, основной причиной называют “анти-параллельность” GS, то есть так как шейдер может выдавать variable input и GPU обязана сохранять последовательность вывода треугольников, нужно вводить промежуточную стадию, пишушую в память, а потом делать финальный Gather, убивающий дырки. Я весь полон сомнений насчет этого аргумента. Казалось бы, ограничений на порядок следования не больше, чем в обычной последовательности
примитивов… Вот кстати, совершенно нетривиально найти кого-то, кто может ответить на такие вопросы.
Надеюсь, это все поправимые проблемы в железе.

Stream Out надо упомянуть рядом. Это возможность записывать результат работы Vertex Shader/Geometry Shader в память. Например, кешировать обработку геометрии (кто-то попробовал для анимаций, кстати?) или вообще геометрию, созданную GS. Можно считать итеративные эффекты, типа Cloth/Water. То есть теперь можно напрямую трансформить и записывать геометрию на GPU, не только рисовать пиксели в Render Target.
Близкая кульная фича – возможность читать в шейдере из буфера в памяти по индексу, то есть иметь достаточно большую read-only shared memory. NV например предлагает там константы анимации хранить для инстансинга.
Про перф ничо не знаю, кстати…

Эволюционные изменения в шейдерах – в четвертой шейдерной модели добавили integer instructions и битовые операции (наконец-то можно считать в честном fixed point и передавать булевые флажки!), убрали ограничение на количество инструкций (но, кстати, очень длинный шейдер может упереться в ограничение по времени выполнения пакета на GPU – не удивляйтесь).

Отдельная группа фич посвящена уменьшению количества draw calls и переключений состояний
Появились массивы текстур, то есть контейнер одинаковых по размеру и формату текстур, из которого шейдер может выбирать по индексу (в DX10.1 – можно и cubemap arrays). Это тот самый atlasing done right – раньше когда вы в одной текстуре хранили несколько разных, приходилось беспокоиться за мип-левелы, оставлять зазор между текстурами и т.д. Теперь не надо, теперь проще. Я, грешным делом, думал, что это помогает megatexture делать, авотхуй.
В шейдер приходят primitive/instance id, в зависимости от instance ID можно использовать другой набор текстур/координат/whatever.
Ожидается, что dynamic branch в шейдере быстрый (лучше, чем в DX9-hardware), поэтому можно передавать Material ID и бранчиться по материалам в шейдере.
То есть, в теории, можно за один вызов генерять большое количество геометрии с разными параметрами, текстурами и вообще материалами.
На практике, больше всего мешает таки стоимость dynamic branch и проблем, с ним связанных (вычисление градиентов текстурных координат). А остальное – вполне можно и нужно использовать.

Multi-sampling antialiasing features
Небольшая фича, ради одной которой можно конвертаться на DX10. Теперь в шейдере можно читать каждый MSAA-сэмпл отдельно, то есть писать свой собственный AA-фильтр, вменяемо сэмплить при процессинге и вообще использовать MSAA RT как текстуру. Еще и AlphaToCoverage вместе с этим теперь официально прикрутили. В D3D10.1 это можно делать и с depth textures.

И, наконец, last but not least… (я ждал, ждал!)
Официальная поддержка depth textures!
Не прошло и восьми лет (Geforce 3 был выпущен в 2001-м)! Не прошло и трех поколений API! Ай маладцы!
То есть, depth buffer можно оффициально использовать как текстуру. Можно сказать, чтобы при сэмплинге сравнивал со значением и делал фильтрацию соседей, можно достать чистый depth value. Можно даже stencil value достать.
Аж прослезился. Помнится, я их через NV hack еще в DX8 пользовал.

И, разумеется, есть еще куча других фич. Ну там, что есть рендер в volume texture или что в DX10.1 можно скопировать из обычной текстуры в compressed на GPU. Или что есть настоящий conditional render, то есть возможность выкидывать целый draw call по результатам работы GPU асинхронно – можно делать occlusion culling полноценно.

В общем – оно все стало более consistent и с меньшим количеством исключений из правил. Можно больше работы делать на GPU, не привлекая CPU, появились новые задачи, которые вообще можно решать на GPU, пофиксены многие старые проблемы, внесены в API и легализованы полезные хаки вендоров.
Что меня удивляет в DX10 – так это различие между теорией и практикой. Все очень стройно, вкусно и здорово, пока не сталкиваешься с реальным миром. В реальном мире тормозят GS, до такой степени, что их нельзя применять масштабно, в реальном мире не получается так просто уменьшить количество DIP calls, а сами они становятся дешевле не на порядок, а всего в разы (большинство времени проводя, очевидно, в драйвере). Ну и конечно пункт “Vista only” маячит над горизонтом, спускаясь за него медленно и неспешно.

Откуда такое несоответствие задуманного и существующего – мне еще предстоит выяснить, я не знаю ответа. То ли наши с IHV мало пива пьют вместе, то ли death by a thousand cuts…

Тем не менее, DX10 – ничо так. Не так бесконечно круто, как хотелось бы, но сильно лучше, чем раньше. Как Виста таки победит – переходите смело. У нас тут исчо DX11 не за горами, в нем тоже клево.