Поделиться через


Привязка ресурсов в HLSL

В этом разделе описываются некоторые особенности использования модели шейдера HLSL 5.1 с Direct3D 12. Все оборудование Direct3D 12 поддерживает модель шейдера 5.1, поэтому поддержка этой модели не зависит от уровня компонентов оборудования.

Типы ресурсов и массивы

Синтаксис ресурсов модели шейдера 5 (SM5.0) использует register ключевое слово для передачи важных сведений о ресурсе в компилятор HLSL. Например, следующая инструкция объявляет массив из четырех текстур, привязанных к слотам t3, t4, t5 и t6. t3 — это единственный слот регистра, который отображается в операторе , просто являясь первым в массиве из четырех.

Texture2D<float4> tex1[4] : register(t3)

Синтаксис ресурсов модели шейдера 5.1 (SM5.1) в HLSL основан на существующем синтаксисе ресурсов регистра, что упрощает перенос. Ресурсы Direct3D 12 в HLSL привязаны к виртуальным регистрам в логических пространствах регистров:

  • t — для представлений ресурсов шейдера (SRV)
  • s — для выборок
  • u — для неупорядоченных представлений доступа (UAV)
  • b — для представлений буфера констант (CBV)

Корневая подпись, ссылающаяся на шейдер, должна быть совместима с объявленными слотами регистра. Например, следующая часть корневой сигнатуры будет совместима с использованием слотов текстуры от t3 до t6, так как она описывает таблицу дескрипторов со слотами t0–t98.

DescriptorTable( CBV(b1), SRV(t0,numDescriptors=99), CBV(b2) )

Объявление ресурса может быть скалярным, 1D-массивом или многомерным массивом:

Texture2D<float4> tex1 : register(t3,  space0)
Texture2D<float4> tex2[4] : register(t10)
Texture2D<float4> tex3[7][5][3] : register(t20, space1)

SM5.1 использует те же типы ресурсов и типы элементов, что и SM5.0. Ограничения объявления SM5.1 являются более гибкими и ограничены только ограничениями среды выполнения и оборудования. Ключевое слово space указывает, к какому логическому регистру привязана объявленная переменная. space Если ключевое слово опущен, то диапазону неявно присваивается индекс пространства по умолчанию 0 (поэтому указанный tex2 выше диапазон находится в space0). register(t3, space0) никогда не конфликтует ни с register(t3, space1), ни с любым массивом в другом пространстве, которое может содержать t3.

Ресурс массива может иметь неограниченный размер, который объявляется путем указания первого измерения, которое должно быть пустым, или 0:

Texture2D<float4> tex1[] : register(t0)

Таблица соответствующих дескрипторов может быть следующей:

DescriptorTable( CBV(b1), UAV(u0, numDescriptors = 4), SRV(t0, numDescriptors=unbounded) )

Несвязанный массив в HLSL соответствует фиксированному числу, заданному в numDescriptors таблице дескриптора, а фиксированный размер в HLSL соответствует объявлению неограниченного размера в таблице дескриптора.

Многомерные массивы разрешены, в том числе неограниченного размера. Эти многомерные массивы выравнивается в пространстве регистра.

Texture2D<float4> tex2[3000][10] : register(t0, space0); // t0-t29999 in space0
Texture2D<float4> tex3[0][5][3] : register(t5, space1)

Псевдонимы диапазонов ресурсов запрещены. Иными словами, для каждого типа ресурса (t, s, u, b) объявленные диапазоны регистров не должны перекрываться. Сюда также входят неограниченные диапазоны. Диапазоны, объявленные в разных регистровых пространствах, никогда не перекрываются. Обратите внимание, что неограниченные tex2 (выше) находятся в space0, а unbounded tex3 — в space1, так что они не перекрываются.

Доступ к ресурсам, объявленным как массивы, так же прост, как и их индексирование.

Texture2D<float4> tex1[400] : register(t3);
sampler samp[7] : register(s0);
tex1[myMaterialID].Sample(samp[samplerID], texCoords);

Существует важное ограничение по умолчанию на использование индексов (myMaterialID и samplerID в приведенном выше коде) в том, что они не могут изменяться в пределах волны. Даже изменение индекса на основе инстансинга считается изменяющимся.

Если требуется изменить индекс, укажите NonUniformResourceIndex квалификатор в индексе, например:

tex1[NonUniformResourceIndex(myMaterialID)].Sample(samp[NonUniformResourceIndex(samplerID)], texCoords);

На некотором оборудовании использование этого квалификатора создает дополнительный код для обеспечения правильности (в том числе в разных потоках), но с незначительными затратами на производительность. Если индекс изменяется без этого квалификатора и в пределах рисования или отправки, результаты не определены.

Массивы дескрипторов и массивы текстур

Массивы текстур были доступны с DirectX 10. Для массивов текстур требуется один дескриптор, однако все срезы массива должны иметь одинаковый формат, ширину, высоту и число MIP. Кроме того, массив должен занимать непрерывный диапазон в виртуальном адресном пространстве. В следующем коде показан пример доступа к массиву текстур из шейдера.

Texture2DArray<float4> myTex2DArray : register(t0); // t0
float3 myCoord(1.0f,1.4f,2.2f); // 2.2f is array index (rounded to int)
color = myTex2DArray.Sample(mySampler, myCoord);

В массиве текстур индекс можно свободно изменять, не требуя квалификаторов, таких как NonUniformResourceIndex.

Эквивалентный массив дескрипторов будет следующим:

Texture2D<float4> myArrayOfTex2D[] : register(t0); // t0+
float2 myCoord(1.0f, 1.4f);
color = myArrayOfTex2D[2].Sample(mySampler,myCoord); // 2 is index

Обратите внимание, что неловкое использование float для индекса массива заменяется myArrayOfTex2D[2]на . Кроме того, массивы дескрипторов обеспечивают большую гибкость с измерениями. Тип( Texture2D в этом примере) не может отличаться, но формат, ширина, высота и число MIP могут отличаться в зависимости от дескриптора.

Допустимо иметь массив дескрипторов из массивов текстур:

Texture2DArray<float4> myArrayOfTex2DArrays[2] : register(t0);

Недопустимо объявлять массив структур, каждая из которых содержит дескрипторы, например следующий код, не поддерживается.

struct myStruct {
    Texture2D                    a; 
    Texture2D                    b;
    ConstantBuffer<myConstants>  c;
};
myStruct foo[10000] : register(....);

Это позволило бы использовать макет памяти abcabcabc...., но является языковым ограничением и не поддерживается. Один из поддерживаемых способов сделать это будет следующим, хотя в этом случае макет памяти является aaa... Bbb... ccc....

Texture2D                     a[10000] : register(t0);
Texture2D                     b[10000] : register(t10000);
ConstantBuffer<myConstants>   c[10000] : register(b0);

Для реализации макета памяти abcabcabc.... используйте таблицу дескриптора myStruct без использования структуры .

Псевдонимы ресурсов

Диапазоны ресурсов, указанные в шейдерах HLSL, являются логическими диапазонами. Они привязаны к конкретным диапазонам кучи во время выполнения с помощью механизма корневой сигнатуры. Как правило, логический диапазон сопоставляется с диапазоном кучи, который не перекрывается с другими диапазонами кучи. Однако механизм корневой сигнатуры позволяет создавать псевдонимы (перекрывать) диапазоны кучи совместимых типов. Например, tex2 диапазоны и tex3 из приведенного выше примера могут быть сопоставлены с тем же (или перекрывающимся) диапазоном кучи, который имеет эффект создания псевдонимов текстур в программе HLSL. Если требуется такое псевдонимирование, шейдер должен быть скомпилирован с параметром D3D10_SHADER_RESOURCES_MAY_ALIAS, который задается с помощью параметра /res_may_alias для средства компилятора эффектов (FXC). Параметр заставляет компилятор создавать правильный код, предотвращая определенные оптимизации загрузки и хранения при условии, что ресурсы могут быть псевдонимами.

Дивергенция и производные

SM5.1 не накладывает ограничений на индекс ресурсов; т. е. tex2[idx].Sample(…) — idx индекса может быть литеральной константой, константой cbuffer или интерполированным значением. Хотя модель программирования обеспечивает такую большую гибкость, существуют проблемы, о которых следует помнить:

  • Если индекс расходится в четырехугольнике, производные и производные величины, такие как LOD, могут быть неопределенными. Компилятор HLSL делает все возможное, чтобы выдать предупреждение в этом случае, но не будет препятствовать компиляции шейдера. Это поведение аналогично вычислению производных в разнонаправленном потоке управления.
  • Если индекс ресурсов отличается, производительность снижается по сравнению с универсальным индексом, так как оборудование должно выполнять операции с несколькими ресурсами. Индексы ресурсов, которые могут отличаться, должны быть помечены функцией в коде NonUniformResourceIndex HLSL. В противном случае результаты будут неопределенными.

БПЛА в пиксельных шейдерах

SM5.1 не накладывает ограничений на диапазоны UAV в пиксельных шейдерах, как в случае с SM5.0.

Буферы констант

Синтаксис буферов констант SM5.1 (cbuffer) изменился с SM5.0, чтобы разработчики могли индексировать буферы констант. Чтобы включить индексируемые буферы констант, в SM5.1 представлена ConstantBuffer конструкция шаблона:

struct Foo
{
    float4 a;
    int2 b;
};
ConstantBuffer<Foo> myCB1[2][3] : register(b2, space1);
ConstantBuffer<Foo> myCB2 : register(b0, space1);

В приведенном выше коде объявляется буферная переменная myCB1 типа Foo и размера 6, а также скалярная константная буферная переменная myCB2. Теперь переменную буфера констант можно индексировать в шейдере следующим образом:

myCB1[i][j].a.xyzw
myCB2.b.yy

Поля "a" и "b" не становятся глобальными переменными, а должны рассматриваться как поля. Для обеспечения обратной совместимости SM5.1 поддерживает старую концепцию cbuffer для скалярных cbuffers. Следующая инструкция создает глобальные переменные a и b, доступные только для чтения, как в SM5.0. Тем не менее, такой старый стиль cbuffer не может быть индексируемым.

cbuffer : register(b1)
{
    float4 a;
    int2 b;
};

В настоящее время компилятор шейдера поддерживает ConstantBuffer шаблон только для определяемых пользователем структур.

В целях совместимости компилятор HLSL может автоматически назначать регистры ресурсов для диапазонов, объявленных в space0. Если в предложении register опущен пробел, используется значение по умолчанию space0 . Компилятор использует эвристические методы first-hole-fits для назначения регистров. Назначение можно получить с помощью API отражения, который был расширен для добавления поля Пробел для пространства, в то время как поле BindPoint указывает нижнюю границу диапазона регистра ресурсов.

Изменения байт-кода в SM5.1

SM5.1 изменяет способ объявления регистров ресурсов и ссылки на них в инструкциях. Синтаксис включает объявление регистра "переменная", аналогично тому, как это делается для групповых регистров общей памяти:

Texture2D<float4> tex0          : register(t5,  space0);
Texture2D<float4> tex1[][5][3]  : register(t10, space0);
Texture2D<float4> tex2[8]       : register(t0,  space1);
SamplerState samp0              : register(s5, space0);

float4 main(float4 coord : COORD) : SV_TARGET
{
    float4 r = coord;
    r += tex0.Sample(samp0, r.xy);
    r += tex2[r.x].Sample(samp0, r.xy);
    r += tex1[r.x][r.y][r.z].Sample(samp0, r.xy);
    return r;
}

Это приведет к разборке:

// Resource Bindings:
//
// Name                                 Type  Format         Dim    ID   HLSL Bind     Count
// ------------------------------ ---------- ------- ----------- -----   --------- ---------
// samp0                             sampler      NA          NA     S0    a5            1
// tex0                              texture  float4          2d     T0    t5            1
// tex1[0][5][3]                     texture  float4          2d     T1   t10        unbounded
// tex2[8]                           texture  float4          2d     T2    t0.space1     8
//
//
//
// Input signature:
//
// Name                 Index   Mask Register SysValue  Format   Used
// -------------------- ----- ------ -------- -------- ------- ------
// COORD                    0   xyzw        0     NONE   float   xyzw
//
//
// Output signature:
//
// Name                 Index   Mask Register SysValue  Format   Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_TARGET                0   xyzw        0   TARGET   float   xyzw
//
ps_5_1
dcl_globalFlags refactoringAllowed
dcl_sampler s0[5:5], mode_default, space=0
dcl_resource_texture2d (float,float,float,float) t0[5:5], space=0
dcl_resource_texture2d (float,float,float,float) t1[10:*], space=0
dcl_resource_texture2d (float,float,float,float) t2[0:7], space=1
dcl_input_ps linear v0.xyzw
dcl_output o0.xyzw
dcl_temps 2
sample r0.xyzw, v0.xyxx, t0[0].xyzw, s0[5]
add r0.xyzw, r0.xyzw, v0.xyzw
ftou r1.x, r0.x
sample r1.xyzw, r0.xyxx, t2[r1.x + 0].xyzw, s0[5]
add r0.xyzw, r0.xyzw, r1.xyzw
ftou r1.xyz, r0.zyxz
imul null, r1.yz, r1.zzyz, l(0, 15, 3, 0)
iadd r1.y, r1.z, r1.y
iadd r1.x, r1.x, r1.y
sample r1.xyzw, r0.xyxx, t1[r1.x + 10].xyzw, s0[5]
add o0.xyzw, r0.xyzw, r1.xyzw
ret
// Approximately 12 instruction slots are used.

Каждый диапазон ресурсов шейдера теперь имеет идентификатор (имя), уникальный для байт-кода шейдера. Например, массив текстур tex1 (t10) становится T1 в байт-коде шейдера. Предоставление уникальных идентификаторов каждому диапазону ресурсов позволяет выполнять две задачи:

  • Однозначно определите, какой диапазон ресурсов (см. dcl_resource_texture2d) индексируется в инструкции (см. пример инструкции).
  • Присоединение к объявлению набора атрибутов, например типа элемента, размера шага, режима работы растра и т. д.

Обратите внимание, что идентификатор диапазона не связан с объявлением нижней границы HLSL.

Порядок привязок ресурсов отражения (перечисление вверху) и инструкций по объявлению шейдера (dcl_*) одинаков, чтобы помочь определить соответствие между переменными HLSL и идентификаторами байт-кода.

Каждая инструкция объявления в SM5.1 использует трехмерный операнд для определения идентификатора диапазона, нижней и верхней границ. Для указания регистрового пространства создается дополнительный маркер. Другие маркеры также могут быть выданы для передачи дополнительных свойств диапазона, например, инструкция cbuffer или структурированного объявления буфера выдает размер cbuffer или структуры. Точные сведения о кодировке можно найти в d3d12TokenizedProgramFormat.h и D3D10ShaderBinary::CShaderCodeParser.

Инструкции SM5.1 не будут выдавать дополнительные сведения об операнде ресурса в рамках инструкции (как в SM5.0). Эти сведения теперь содержатся в инструкциях объявления. В SM5.0 инструкции по индексации ресурсов требовали, чтобы атрибуты ресурсов описывались в расширенных маркерах opcode, так как индексирование заслоняет связь с объявлением. В SM5.1 каждый идентификатор (например, t1) однозначно связан с одним объявлением, описывающим необходимые сведения о ресурсе. Таким образом, расширенные маркеры opcode, используемые в инструкциях для описания сведений о ресурсах, больше не создаются.

В инструкциях, не являющихся объявлениями, операнд ресурсов для образцов, srV и БПЛА является двухмерным операндом. Первый индекс является литеральной константой, указывающей идентификатор диапазона. Второй индекс представляет линейное значение индекса. Значение вычисляется относительно начала соответствующего регистрового пространства (не относительно начала логического диапазона), чтобы лучше сопоставить с корневой сигнатурой и уменьшить нагрузку компилятора драйвера на корректировку индекса.

Операнд ресурса для CBV — это трехмерный операнд, содержащий: литеральный идентификатор диапазона, индекс буфера констант, смещение в конкретный экземпляр буфера констант.

Примеры объявлений HLSL

Программам HLSL не нужно ничего знать о корневых сигнатурах. Они могут назначать привязки виртуальному пространству привязки "регистра", t# для SRV, u# для БПЛА, b# для CBV, s# для выборки или полагаться на компилятор для выбора назначений (и последующего запроса результирующих сопоставлений с помощью отражения шейдера). Корневая сигнатура сопоставляет таблицы дескрипторов, корневые дескрипторы и корневые константы с этим виртуальным пространством регистров.

Ниже приведены примеры объявлений, которые может иметь шейдер HLSL. Обратите внимание, что нет ссылок на корневые подписи или таблицы дескрипторов.

Texture2D foo[5] : register(t2);
Buffer bar : register(t7);
RWBuffer dataLog : register(u1);

Sampler samp : register(s0);

struct Data
{
    UINT index;
    float4 color;
};
ConstantBuffer<Data> myData : register(b0);

Texture2D terrain[] : register(t8); // Unbounded array
Texture2D misc[] : register(t0,space1); // Another unbounded array 
                                        // space1 avoids overlap with above t#

struct MoreData
{
    float4x4 xform;
};
ConstantBuffer<MoreData> myMoreData : register(b1);

struct Stuff
{
    float2 factor;
    UINT drawID;
};
ConstantBuffer<Stuff> myStuff[][3][8]  : register(b2, space3)