DX 10 post, part 2 – API changes

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

Я так считаю, что пока DX11 не вышел, посты про DX10 еще считаются актуальными. Посему радуйтесь, я буду вам рассказывать про изменения вышедшего год назад API.
Тем более, не спится.


В DX10 очень много изменений в API, наверное, это одни из самых больших изменений за историю D3D.
Большинство из них направлено на то, чтобы сделать интерфейс более общим и уменьшить количество работы драйверу и рантайму, некоторые необходимы для новых фич.

Например, буфер вершин, буфер индексов и буфер констант – это один и тот же ID3D10Buffer, так как это набор байтов в памяти, и API к ним один и тот же. При создании буфера все равно нужно указывать, как он будет использоваться, так что все это в некотором смысле остается type safe.

Для ресурсов введены отдельные объекты для биндинга к пайплайну, так называемые resource views. Т.е. сначала создаешь текстуру как объект в памяти, а потом ее resource view как инпут для шейдера или как render target, и уже с этим view зовешь PSSetShaderResources (вместо SetTexture) и OMSetRenderTargets (вместо SetRenderTarget). Разумеется, у одного ресурса view может быть несколько.
Это нужно для общности подхода с новыми фичами – typeless resources, то есть ресурс, который при создании не имеет четко определенного типа (например, DXGI_FORMAT_R32G32B32_TYPELESS), тип выбирается во время создания view (например, DXGI_FORMAT_R32G32B32_UINT или DXGI_FORMAT_R32G32B32_FLOAT), и выбора элемента/слайса из texture array/volume texture.

Все Set*State заменены на State Objects. Стейты разделены по нескольким группам – Rasterizer State (fill mode, cull mode, depth bias, multisample, scissor и т.д.), Blend State (alpha blend, color write mask, blend op и т.д.), Depth State (depth func, stencil func и все вокруг) и разумеется SamplerState (tex filtering, clamping и все такое). Так вот, стейты для каждой группы ставятся целиком, а не каждый по отдельности, как в D3D9. Для каждой группы можно создать State Object, которому при создании указывается полный набор стейтов этой группы, и “установить” можно только его. Создание State Object – дорогая и медленная операция, и должна вызываться редко, в идеале только при загрузке уровня. Мотивация, опять же, та же самая – такой API позволяет драйверу сгенерировать набор команд видеокарте заранее (при создании State Object) и не генерировать его каждый раз во время рендера при вызовах Set*State.

Set*ShaderConstant заменены на Constant Buffers – группы констант, устанавливающихся за раз. Т.е. создаешь буфер на n констант, который можно локать и записывать как и обычный буфер, и биндишь его к шейдеру начиная с какого-то слота. Помните, в D3D9 рекомендовали устанвливать константы не по одной, а блоками подряд для перформанса? Это более формализованный способ делать то же самое. То есть, разделяешь константы на несколько групп по частоте обновления – per-object, per-material, per-pass, per-scene, для каждой заводишь свой Constant Buffer, и обновляешь их по мере необходимости. Таким образом, разделение на блоки апдейтов констант, необходимое для перформанса, происходит автоматически, ну и вообще, дает драйверу высокоуровневую картину и больше возможностей для оптимизации.

Для полноты – с VertexDeclaration произошла в общем-то похожая фигня. Теперь Input Layout (бывшый Vertex Decl) требует при создании Shader Input Signature, т.е. списка input-параметров шейдера. Полученный объект можно использовать как Vertex Declaration с любым шейдером, имеющим такой же список input-параметров. В D3D9 Vertex Declaration устанавливался независимо от шейдера при рендере, и поэтому драйверам приходилось серьезно модифицировать сетап при смене vdecl. Сейчас vdecl жестко привязан к инпуту шейдера, что опять же позволяет предвычислять это все заранее.

Ну, основная идея понятна, да? Как можно больше выносить в создание-инициализацию, как можно больше предвычислять и заранее валидировать, чтобы при рендере делать как можно меньше, и таким образом уменьшать DIP cost.

Кроме этого, из изменений стоит отметить, что шейдеры больше нельзя писать на асме, нужно пользоваться HLSL. Хотя ассемблер для shader model 4.x есть и можно смотреть результат компиляции шейдеров в него, больше нет возможности получить бинарный код шейдера из текста асма (то что делали psa.exe/vsa.exe). Отреверсинженирить бинарный код, впрочем, никто не мешает :)

Чтобы было легче портировать код шейдеров, компилятор умеет компилять HLSL-шейдеры старых версий (SM2.0, SM 3.0) в SM4.0. В новом HLSL, кстати, добавили атрибуты для хинтов компилятору – unroll для лупов и выбор dynamic vs static branching для условных переходов.
И, конечно, мы всегда радуемся компиляторным багам, присылайте нам их всегда.

По-большому счету, пожалуй, и все. Осталось рассказать про самое вкусное – сами новые хардверные фичи, в следующий раз дойдем и до них. Глядишь к DX11 успею.

  • http://zeux.livejournal.com/ Zeux

    > Посему радуйтесь, я буду вам рассказывать про изменения вышедшего год назад API.
    На самом деле, API уже 2.5 года, если считать с CTP… ;)

    > т.е. не надо уже проверять валидность текстуры как RT каждый раз при вызове SetRenderTarget, можно при создании view
    Эм, Саймон, это какой-то очень плохой пример. Т.е., проверка в D3D9 по идее должна сводиться к surface->usage & D3DUSAGE_RENDERTARGET. Это как бы не то, что следует экономить. Может быть есть другие примеры?

    > Сейчас vdecl жестко привязан к инпуту шейдера, что опять же позволяет предвычислять это все заранее.
    Вот, я конечно не знаю, как устроено DX10 железо. Но глядя на железо консоли, могу сказать, что… на одной из приставок – существование IL бессмысленно, т.к. железо умеет произвольный vdecl биндить к произвольному шейдеру, единственное, что требуется от драйвера – подкорректировать vdecl в зависимости от input-ов шейдера, но (!) только чтобы сэкономить на стоимости vfetch. Это – дешевая операция. Очень.
    На другой из приставок существование IL бессмысленно, т.к. не vdecl зависит от shader, а shader зависит от vdecl – т.е. драйверу придется в зависимости от vdecl модифицировать код VS, и никакой IL его не спасет.

    В итоге, с моей колокольни тот IL выглядит странно. Как штука, которая никому не помогает, и в итоге является сильным неудобством с т.з. устройства кода. Ну или не сильным, как сделаешь – у меня вот теперь constraint, что для любого эффекта (аналог id3d10effect) все техники имеют compatible shader input signature, т.е. являются префиксами друг друга – в итоге хотя бы на одну пару mesh-effect нужен один IL.

    > HLSL и эффекты поэтому теперь не в D3DX, а в самом D3D10, чтобы были всегда.
    А правда, что если используешь D3DX10Compile*, то ты получаешь новый компилятор из D3DCompiler_*.dll, который обновляется с изменением SDK, а если D3DCompile*, то из d3d10.dll, который обновляется только с major OS update типа SP1?

  • CEMEH

    Ай, хорошие вопросы, дойду до работы – буду отвечать.

  • CEMEH

    Посмотрел. Performance benefit от Resource views – это я неправ, поправил пост. Они только для новых фич API.

    Про Input Layout – я вот вообще не знаю, как какое бы то ни было железо устроено. Я только знаю, что SetVDecl самая дорогая на CPU операция, утверждается, что потому что нужно биндить аутпут vdecl’a и input шейдера фактически сверяя ID и цифирки at draw time. Это и старались поправить.
    Если ты знаешь какие-то другие причины, почему SetVDecl тормозит – расскажи, очень интересно.

    Вроде как правда, надо всегда использовать D3DX10Compile*. Пожалуй, да, то что в рантайме есть компилятор знать не надо.

  • Pingback: highly professional scums » Blog Archive » DX10 post, part 1 - Infrastructure()

  • Pingback: highly professional scums » Blog Archive » DX 10 post, part 3 - New hardware features()