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


Создание корневой подписи

Корневые сигнатуры — это сложная структура данных, содержащая вложенные структуры. Их можно определить программными средствами, используя приведенное ниже определение структуры данных (которое включает методы для инициализации элементов). Кроме того, их можно создать на языке высокого уровня заливки (HLSL), что дает преимущество в том, что компилятор заранее проверит совместимость макета с шейдером.

API для создания корневой сигнатуры принимает сериализованную (автономную, без указателя) версию описания макета, описанного ниже. Для создания этой сериализованной версии из структуры данных C++ предоставляется метод, но другим способом получения определения сериализованной корневой сигнатуры является получение его из шейдера, скомпилированного с корневой сигнатурой.

Если вы хотите воспользоваться преимуществами оптимизации драйвера для дескрипторов и данных корневой сигнатуры, см. раздел Корневая сигнатура версии 1.1.

Типы привязок таблицы дескриптора

D3D12_DESCRIPTOR_RANGE_TYPE перечисления определяет типы дескрипторов, на которые можно ссылаться как часть определения макета таблицы дескриптора.

Это диапазон, так что, например, если часть таблицы дескрипторов содержит 100 SRV, этот диапазон можно объявить в одной записи, а не в 100. Таким образом, определение таблицы дескриптора представляет собой коллекцию диапазонов.

typedef enum D3D12_DESCRIPTOR_RANGE_TYPE
{
  D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
  D3D12_DESCRIPTOR_RANGE_TYPE_UAV,
  D3D12_DESCRIPTOR_RANGE_TYPE_CBV,
  D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER
} D3D12_DESCRIPTOR_RANGE_TYPE;

Диапазон дескрипторов

Структура D3D12_DESCRIPTOR_RANGE определяет диапазон дескрипторов заданного типа (например, SRV) в таблице дескрипторов.

Макрос D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND обычно можно использовать для OffsetInDescriptorsFromTableStart параметра D3D12_DESCRIPTOR_RANGE. Это означает, что в таблицу дескрипторов добавляется диапазон дескрипторов, определенный после предыдущего. Если приложению требуется псевдоним дескрипторов или по какой-либо причине требуется пропустить слоты, оно может задать OffsetInDescriptorsFromTableStart любое требуемое смещение. Недопустимо определять перекрывающиеся диапазоны разных типов.

Набор регистров шейдеров, заданный RangeTypeсочетанием , NumDescriptors, BaseShaderRegisterи RegisterSpace , не может конфликтовать или перекрываться между объявлениями в корневой сигнатуре, имеющими общие D3D12_SHADER_VISIBILITY (см. раздел видимости шейдера ниже).

Макет таблицы дескриптора

Структура D3D12_ROOT_DESCRIPTOR_TABLE объявляет макет таблицы дескриптора как коллекцию диапазонов дескрипторов, которые начинаются с заданного смещения кучи дескриптора. Дискретизации не допускаются в той же таблице дескрипторов, что и CBV,UAV/SRV.

Эта структура используется, если для типа слота корневой сигнатуры задано значение D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE.

Чтобы задать таблицу дескрипторов графики (CBV, SRV, UAV, Sampler), используйте ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable.

Чтобы задать таблицу дескриптора вычислений, используйте ID3D12GraphicsCommandList::SetComputeRootDescriptorTable.

Корневые константы

Структура D3D12_ROOT_CONSTANTS объявляет константы, встроенные в корневой сигнатуре, которые отображаются в шейдерах в виде одного буфера констант.

Эта структура используется, если для типа слота корневой сигнатуры задано значение D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS.

Корневой дескриптор

Структура D3D12_ROOT_DESCRIPTOR объявляет дескрипторы (которые отображаются в шейдерах) в корневой сигнатуре.

Эта структура используется, если для типа слота корневой сигнатуры задано значение D3D12_ROOT_PARAMETER_TYPE_CBV, D3D12_ROOT_PARAMETER_TYPE_SRV или D3D12_ROOT_PARAMETER_TYPE_UAV.

Видимость шейдера

Элемент перечисления D3D12_SHADER_VISIBILITY , заданный в параметре видимости шейдера D3D12_ROOT_PARAMETER определяет, какие шейдеры видят содержимое заданного слота корневой сигнатуры. Вычисление всегда использует _ALL (так как существует только один активный этап). Графику можно выбрать, но если она использует _ALL, все этапы шейдера видят, что привязано в корневом слоте сигнатуры.

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

Texture2D foo : register(t0);

и шейдер пикселей также может объявлять:

Texture2D bar : register(t0);

Если приложение создает привязку корневой сигнатуры к t0 VISIBILITY_ALL, оба шейдера видят одну и ту же текстуру. Если шейдер действительно хочет, чтобы каждый шейдер видел разные текстуры, он может определить 2 корневых слота сигнатуры с VISIBILITY_VERTEX и _PIXEL. Независимо от того, какая видимость находится в корневом слоте сигнатуры, она всегда имеет одинаковые затраты (стоимость только в зависимости от того, что такое SlotType) для одного фиксированного максимального размера корневой сигнатуры.

На низком оборудовании D3D11 SHADER_VISIBILITY также учитывается при проверке размеров таблиц дескрипторов в корневом макете, так как некоторые устройства D3D11 могут поддерживать только максимальное количество привязок на каждом этапе. Эти ограничения применяются только при работе на низкоуровневом оборудовании и вообще не ограничивают более современное оборудование.

Если в корневой сигнатуре определено несколько таблиц дескрипторов, которые перекрываются друг с другом в пространстве имен (привязки регистра к шейдеру), и любая из них указывает _ALL для видимости, макет недопустим (создание завершится ошибкой).

Определение корневой сигнатуры

Структура D3D12_ROOT_SIGNATURE_DESC может содержать таблицы дескрипторов и встроенные константы, каждый тип слота определяется структурой D3D12_ROOT_PARAMETER и D3D12_ROOT_PARAMETER_TYPE перечисления.

Чтобы инициировать корневой слот сигнатуры, ознакомьтесь с методами SetComputeRoot*** и SetGraphicsRoot***id3D12GraphicsCommandList.

Статические выборки описываются в корневой сигнатуре с использованием структуры D3D12_STATIC_SAMPLER .

Ряд флагов ограничивает доступ определенных шейдеров к корневой сигнатуре, см. D3D12_ROOT_SIGNATURE_FLAGS.

Сериализация и десериализация структуры данных корневой сигнатуры

Методы, описанные в этом разделе, экспортируются D3D12Core.dll и предоставляют методы для сериализации и десериализации структуры данных корневой сигнатуры.

Сериализованная форма передается в API при создании корневой сигнатуры. Если шейдер был создан с корневой сигнатурой (при добавлении этой возможности), скомпилированный шейдер уже будет содержать сериализованную корневую сигнатуру.

Если приложение процедурно создает структуру данных D3D12_ROOT_SIGNATURE_DESC , оно должно создать сериализованную форму с помощью D3D12SerializeRootSignature. Выходные данные могут быть переданы в ID3D12Device::CreateRootSignature.

Если приложение уже имеет сериализованную корневую сигнатуру или скомпилированный шейдер, который содержит корневую сигнатуру и хочет программно обнаружить определение макета (известное как отражение), можно вызвать D3D12CreateRootSignatureDeserializer . При этом создается интерфейс ID3D12RootSignatureDeserializer , содержащий метод для возврата десериализованной D3D12_ROOT_SIGNATURE_DESC структуры данных. Интерфейсу принадлежит время существования десериализованной структуры данных.

API создания корневой сигнатуры

API ID3D12Device::CreateRootSignature принимает сериализованную версию корневой сигнатуры.

Корневая сигнатура в объектах состояния конвейера

Методы для создания состояния конвейера (ID3D12Device::CreateGraphicsPipelineState и ID3D12Device::CreateComputePipelineState ) принимают необязательный интерфейс ID3D12RootSignature в качестве входного параметра (хранящегося в D3D12_GRAPHICS_PIPELINE_STATE_DESC структуре). Это переопределит все корневые сигнатуры, уже существующие в шейдерах.

Если корневая сигнатура передается в один из методов состояния конвейера создания, эта корневая сигнатура проверяется на соответствие всем шейдерам в PSO и передается драйверу для использования со всеми шейдерами. Если какой-либо из шейдеров имеет другую корневую сигнатуру, она заменяется корневой сигнатурой, переданной в API. Если корневая сигнатура не передается, все переданные шейдеры должны иметь корневую сигнатуру и должны соответствовать . Это будет передано драйверу. Задание pso в списке команд или пакете не изменяет корневую сигнатуру. Для этого используются методы SetGraphicsRootSignature и SetComputeRootSignature. К моменту вызова draw(graphics)/dispatch(compute) приложение должно убедиться, что текущий PSO соответствует текущей корневой сигнатуре; В противном случае поведение не определено.

Код для определения корневой подписи версии 1.1

В приведенном ниже примере показано, как создать корневую сигнатуру в следующем формате:

RootParameterIndex Содержимое Значения
[0] Корневые константы: { b2 } (1 CBV)
 [1] Таблица дескрипторов: { t2-t7, u0-u3 } (6 SPV + 4 БПЛА)
[2] Корневой CBV: { b0 } (1 CBV, статические данные)
[3] Таблица дескрипторов: { s0-s1 } (2 выборки)
[4] Таблица дескрипторов: { t8 - неограниченный } (неограниченное число srv, переменные дескрипторы)
[5] Таблица дескрипторов: { (t0, пробел1) — неограниченный } (неограниченное число srv, переменные дескрипторы)
[6] Таблица дескрипторов: { b1 } (1 CBV, статические данные)

 

Если большинство частей корневой сигнатуры используются большую часть времени, это может быть лучше, чем слишком частое переключение корневой сигнатуры. Приложения должны отсортировать записи в корневой сигнатуре от наиболее часто изменяющихся до наименьших. Когда приложение изменяет привязки на любую часть корневой сигнатуры, драйверу может потребоваться создать копию некоторых или всех состояний корневой сигнатуры, что может стать нетривиальной стоимостью при умножении на многие изменения состояния.

Кроме того, корневая сигнатура определяет статический дискретизатор, выполняющий фильтрацию анизотропных текстур в регистре шейдера s3.

После привязки этой корневой сигнатуры таблицы дескрипторов, корневые CBV и константы можно назначить пространству параметров [0..6]. Например, таблицы дескрипторов (диапазоны в куче дескрипторов) могут быть привязаны к каждому из корневых параметров [1] и [3..6].

CD3DX12_DESCRIPTOR_RANGE1 DescRange[6];

DescRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV,6,2); // t2-t7
DescRange[1].Init(D3D12_DESCRIPTOR_RANGE_UAV,4,0); // u0-u3
DescRange[2].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER,2,0); // s0-s1
DescRange[3].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,8, 0,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); // t8-unbounded
DescRange[4].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,0,1,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); 
                                                            // (t0,space1)-unbounded
DescRange[5].Init(D3D12_DESCRIPTOR_RANGE_CBV,1,1,
                  D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC); // b1

CD3DX12_ROOT_PARAMETER1 RP[7];

RP[0].InitAsConstants(3,2); // 3 constants at b2
RP[1].InitAsDescriptorTable(2,&DescRange[0]); // 2 ranges t2-t7 and u0-u3
RP[2].InitAsConstantBufferView(0, 0, 
                               D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC); // b0
RP[3].InitAsDescriptorTable(1,&DescRange[2]); // s0-s1
RP[4].InitAsDescriptorTable(1,&DescRange[3]); // t8-unbounded
RP[5].InitAsDescriptorTable(1,&DescRange[4]); // (t0,space1)-unbounded
RP[6].InitAsDescriptorTable(1,&DescRange[5]); // b1

CD3DX12_STATIC_SAMPLER StaticSamplers[1];
StaticSamplers[0].Init(3, D3D12_FILTER_ANISOTROPIC); // s3
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC RootSig(7,RP,1,StaticSamplers);
ID3DBlob* pSerializedRootSig;
CheckHR(D3D12SerializeVersionedRootSignature(&RootSig,pSerializedRootSig)); 

ID3D12RootSignature* pRootSignature;
hr = CheckHR(pDevice->CreateRootSignature(
    pSerializedRootSig->GetBufferPointer(),pSerializedRootSig->GetBufferSize(),
    __uuidof(ID3D12RootSignature),
    &pRootSignature));

В следующем коде показано, как указанная выше корневая сигнатура может использоваться в графическом списке команд.

InitializeMyDescriptorHeapContentsAheadOfTime(); // for simplicity of the 
                                                 // example
CreatePipelineStatesAhreadOfTime(pRootSignature); // The root signature is passed into 
                                     // shader / pipeline state creation
...

ID3D12DescriptorHeap* pHeaps[2] = {pCommonHeap, pSamplerHeap};
pGraphicsCommandList->SetDescriptorHeaps(2,pHeaps);
pGraphicsCommandList->SetGraphicsRootSignature(pRootSignature);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                        6,heapOffsetForMoreData,DescRange[5].NumDescriptors);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(5,heapOffsetForMisc,5000); 
pGraphicsCommandList->SetGraphicsRootDescriptorTable(4,heapOffsetForTerrain,20000);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                        3,heapOffsetForSamplers,DescRange[2].NumDescriptors);
pGraphicsCommandList->SetComputeRootConstantBufferView(2,pDynamicCBHeap,&CBVDesc);

MY_PER_DRAW_STUFF stuff;
InitMyPerDrawStuff(&stuff);
pGraphicsCommandList->SetGraphicsRoot32BitConstants(
                        0,RTSlot[0].Constants.Num32BitValues,&stuff,0);

SetMyRTVAndOtherMiscBindings();

for(UINT i = 0; i < numObjects; i++)
{
    pGraphicsCommandList->SetPipelineState(PSO[i]);
    pGraphicsCommandList->SetGraphicsRootDescriptorTable(
                    1,heapOffsetForFooAndBar[i],DescRange[1].NumDescriptors);
    pGraphicsCommandList->SetGraphicsRoot32BitConstant(0,i,drawIDOffset);
    SetMyIndexBuffers(i);
    pGraphicsCommandList->DrawIndexedInstanced(...);
}

Корневые подписи

Определение корневых подписей в HLSL

Использование корневой подписи