Введение в привязку ресурсов в Microsoft DirectX* 12

ЗагрузитьIntroduction-to-Resource-Binding-in-Microsoft-DirectX12.pdf

By Wolfgang Engel, CEO of Confetti

20 марта 2014 года корпорация Майкрософт объявила на конференции Game Developers Conference о выпуске DirectX* 12. За счет сокращения избыточной обработки ресурсов DirectX 12 будет способствовать более эффективной работе приложений и снижению потребления электроэнергии, благодаря чему можно будет дольше играть на мобильных устройствах без подзарядки.

На конференции SIGGRAPH 2014 специалисты Intel измерили потребляемую мощность ЦП при запуске простого демо с астероидами на планшете Microsoft Surface* Pro 3. Демонстрационное приложение можно переключать с API DirectX 11 на API DirectX 12 нажатием кнопки. Это демонстрационное приложение рисует огромное количество астероидов в космосе при фиксированной кадровой скорости (https://software.intel.com/ru-ru/blogs/2014/08/11/siggraph-2014-directx-12-on-intel). При использовании API DirectX 12 API потребляемая мощность ЦП снижается более чем вдвое по сравнению с DirectX 11**. Устройство работает в менее интенсивном тепловом режиме и способно дольше проработать от аккумулятора. В типичных игровых сценариях всю незадействованную мощность ЦП можно израсходовать на улучшение физики, искусственного интеллекта, алгоритмов поиска путей или других задач с интенсивной нагрузкой на ЦП. Таким образом, игра становится более мощной по функциональности или более экономичной с точки зрения потребления электричества.

Инструменты

Для разработки игр на основе DirectX 12 требуется следующее.

  • Windows* 10
  • Пакет DirectX 12 SDK
  • Visual Studio* 2013
  • Драйверы ГП, поддерживающие DirectX 12

Если вы являетесь разработчиком игр, попробуйте принять участие в программе Microsoft DirectX Early Access Program по адресу https://onedrive.live.com/survey?resid=A4B88088C01D9E9A!107&authkey=!AFgbVA2sYbeoepQ.

После принятия условий программы DirectX Early Access Program вы получите инструкции по установке SDK и драйверы для ГП.

Обзор

С высокоуровневой точки зрения, по сравнению с DirectX 10 и DirectX 11, архитектура DirectX 12 отличается в области управления состояниями, отслеживания и управления ресурсами в памяти.

В DirectX 10 появились объекты состояний для настройки группы состояний во время выполнения. В DirectX 12 появились объекты конвейера состояния (PSO), которые служат еще более крупными объектами состояний вместе с шейдерами. В этой статье рассматриваются изменения в работе с ресурсами; группировка состояний в PSO будет описана в дальнейших статьях.

В DirectX 11 система отвечала за предсказание и отслеживание использования ресурсов, что ограничивало возможности создания приложений при широкомасштабном использовании DirectX 11. В DirectX 12 именно программист (а не система и не драйвер) отвечает за обработку трех следующих моделей использования.

  1. Привязка ресурсов
    DirectX 10 и 11 отслеживали привязку ресурсов к графическому конвейеру, чтобы поддерживать в рабочем состоянии ресурсы, уже высвобожденные приложением, поскольку незавершенные операции ГП могли ссылаться на эти ресурсы. В DirectX 12 система не отслеживает привязку ресурсов. Заниматься управлением жизненным циклом объектов должно приложение, то есть программист.
  2. Анализ привязки ресурсов
    DirectX 12 не отслеживает привязку ресурсов, чтобы определить, произошло ли переключение ресурсов. Например, приложение может записывать в цель рендеринга с помощью представления цели рендеринга (RTV), а затем прочитать эту цель рендеринга в качестве текстуры с помощью представления ресурсов шейдера (SRV). В API DirectX 11 драйвер ГП должен был «знать», когда происходит такое переключение ресурсов, чтобы не допускать конфликтов при чтении, изменении и записи данных в памяти. В DirectX 12 вы должны идентифицировать и отслеживать все переключения ресурсов с помощью отдельных вызовов API.
  3. Синхронизация сопоставленной памяти
    В DirectX 11 драйвер обрабатывает синхронизацию сопоставленной памяти между ЦП и ГП. Система анализировала привязки ресурсов, чтобы понять, требуется ли задержка рендеринга, поскольку еще не отменено сопоставление ресурса, который был сопоставлен для доступа ЦП. В DirectX 12 приложение должно обрабатывать синхронизацию доступа ЦП и ГП к ресурсам. Единый механизм для синхронизации доступа к памяти запрашивает событие для пробуждения потока по завершении обработки в ГП.

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

Далее в этой статье описываются новые механизмы привязки ресурсов, первым из которых являются дескрипторы.

Дескрипторы

Дескрипторы описывают ресурсы, хранящиеся в памяти. Дескриптор — это блок данных, описывающий объект для ГП, в «непрозрачном» формате, предназначенном для ГП. Дескрипторы с некоторой натяжкой можно рассматривать как замену прежней системы «представлений» в DirectX 11. В дополнение к различным типам дескрипторов DirectX 11, таких как представление ресурсов шейдера (SRV) и представление неупорядоченного доступа (UAV), в DirectX 12 появились другие типы дескрипторов, например семплеры и представление постоянного буфера (CBV).

Например, SRV выбирает, какой нужно использовать базовый ресурс, какой набор рельефных карт и срезов массива и в каком формате интерпретировать память. Дескриптор SRV должен содержать виртуальный адрес ресурса Direct3D* (который может быть текстурой) в ГП. Приложение должно убедиться в том, что базовый ресурс не уничтожен и не является недоступным из-за нерезидентности.

На рис. 1 показан дескриптор «представления» текстуры.


Рисунок 1. Представление ресурса шейдера в дескрипторе [использовано с разрешения © Корпорация Майкрософт]

Для создания представления ресурсов шейдера в DirectX 12 используйте следующую структуру и метод устройства Direct3D.

typedef struct D3D12_SHADER_RESOURCE_VIEW_DESC
{
    DXGI_FORMAT Format;
    D3D12_SRV_DIMENSION ViewDimension;

    union
    {
        D3D12_BUFFER_SRV Buffer;
        D3D12_TEX1D_SRV Texture1D;
        D3D12_TEX1D_ARRAY_SRV Texture1DArray;
        D3D12_TEX2D_SRV Texture2D;
        D3D12_TEX2D_ARRAY_SRV Texture2DArray;
        D3D12_TEX2DMS_SRV Texture2DMS;
        D3D12_TEX2DMS_ARRAY_SRV Texture2DMSArray;
        D3D12_TEX3D_SRV Texture3D;
        D3D12_TEXCUBE_SRV TextureCube;
        D3D12_TEXCUBE_ARRAY_SRV TextureCubeArray;
        D3D12_BUFFEREX_SRV BufferEx;
    };
} D3D12_SHADER_RESOURCE_VIEW_DESC;

interface ID3D12Device
{
...
    void CreateShaderResourceView (
        _In_opt_ ID3D12Resource* pResource,
        _In_opt_ const D3D12_SHADER_RESOURCE_VIEW_DESC* pDesc,
        _In_ D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor);
};

Пример кода SRV может выглядеть примерно так.

// create SRV
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(D3D12_SHADER_RESOURCE_VIEW_DESC));
srvDesc.Format = mTexture->Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;

mDevice->CreateShaderResourceView(mTexture.Get(), &srvDesc, mCbvSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

Этот код создает SRV для двухмерной текстуры и указывает ее формат и виртуальный адрес ГП. Последним аргументом для CreateShaderResourceView является маркер кучи дескрипторов, которая была выделена перед вызовом этого метода. Дескрипторы обычно хранятся в кучах дескрипторов, которые подробнее описаны в следующем разделе.

Примечание. Также можно передавать некоторые типы дескрипторов в ГП с помощью так называемых корневых параметров (с учетом версий драйверов). Подробнее об этом см. ниже.

Кучи дескрипторов

Кучу дескрипторов можно рассматривать как один выделенный объем памяти для нескольких дескрипторов. Различные типы куч могут содержать один или несколько типов дескрипторов. В настоящее время поддерживаются следующие типы.

Typedef enum D3D12_DESCRIPTOR_HEAP_TYPE
{
 D3D12_CBV_SRV_UAV_DESCRIPTOR_HEAP	= 0,
 D3D12_SAMPLER_DESCRIPTOR_HEAP = (D3D12_CBV_SRV_UAV_DESCRIPTOR_HEAP + 1) ,
 D3D12_RTV_DESCRIPTOR_HEAP	= ( D3D12_SAMPLER_DESCRIPTOR_HEAP + 1 ) ,
 D3D12_DSV_DESCRIPTOR_HEAP	= ( D3D12_RTV_DESCRIPTOR_HEAP + 1 ) ,
 D3D12_NUM_DESCRIPTOR_HEAP_TYPES = ( D3D12_DSV_DESCRIPTOR_HEAP + 1 ) 
} 	D3D12_DESCRIPTOR_HEAP_TYPE;

Существует тип куч для дескрипторов CBV, SRV и UAV. Также существуют типы для работы с представлениями цели рендеринга (RTV) и представлением формата глубины (DSV).

Следующий код создает кучу дескрипторов для девяти дескрипторов, каждый из которых может относиться к типу CBV, SRV или UAV.

// create shader resource view and constant buffer view descriptor heap
D3D12_DESCRIPTOR_HEAP_DESC descHeapCbvSrv = {};
descHeapCbvSrv.NumDescriptors = 9;
descHeapCbvSrv.Type = D3D12_CBV_SRV_UAV_DESCRIPTOR_HEAP;
descHeapCbvSrv.Flags = D3D12_DESCRIPTOR_HEAP_SHADER_VISIBLE;
ThrowIfFailed(mDevice->CreateDescriptorHeap(&descHeapCbvSrv, __uuidof(ID3D12DescriptorHeap), (void**)&mCbvSrvDescriptorHeap));

Первые две записи в описании кучи — это количество дескрипторов и типы дескрипторов, которые могут быть в этой куче. Третий параметр D3D12_DESCRIPTOR_HEAP_SHADER_VISIBLE описывает эту кучу дескрипторов как видимую для шейдера. Можно использовать кучи дескрипторов, невидимые для шейдера, например, для промежуточного хранения дескрипторов в ЦП или для RTV, недоступных для выбора изнутри шейдеров.

Этот код устанавливает флаг, из-за которого куча дескрипторов становится видной для шейдера, но есть еще один уровень косвенной адресации. Шейдер может «увидеть» кучу дескрипторов посредством таблицы дескрипторов (существуют также корневые дескрипторы, не использующие таблицы; подробнее о них см. ниже).

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

Основная цель кучи дескрипторов состоит в выделении необходимого объема памяти для хранения всех дескрипторов для рендеринга в наибольшем возможном количестве, скажем для одного кадра или более

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

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

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


Рисунок 2. Различные шейдеры указывают на кучу дескрипторов с помощью нескольких таблиц дескрипторов

В следующем примере кода создаются таблицы дескрипторов для SRV и семплера, видимые для пиксельного шейдера.

// define descriptor tables for a SRV and a sampler for pixel shaders
D3D12_DESCRIPTOR_RANGE descRange[2];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV, 1, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER, 1, 0);

D3D12_ROOT_PARAMETER rootParameters[2];
rootParameters[0].InitAsDescriptorTable(1, &descRange[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &descRange[1], D3D12_SHADER_VISIBILITY_PIXEL);

При этом таблица дескрипторов видна только пиксельному шейдеру; это ограничение устанавливается с помощью флага D3D12_SHADER_VISIBILITY_PIXEL. Следующее перечисление определяет различные уровни видимости таблицы дескрипторов.

typedef enum D3D12_SHADER_VISIBILITY
{
 D3D12_SHADER_VISIBILITY_ALL	= 0,
 D3D12_SHADER_VISIBILITY_VERTEX	= 1,
 D3D12_SHADER_VISIBILITY_HULL	= 2,
 D3D12_SHADER_VISIBILITY_DOMAIN	= 3,
 D3D12_SHADER_VISIBILITY_GEOMETRY	= 4,
 D3D12_SHADER_VISIBILITY_PIXEL	= 5
} D3D12_SHADER_VISIBILITY;

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

Шейдер может обнаруживать ресурсы с помощью таблиц дескрипторов, но сначала шейдер должен «узнать» об этих таблицах дескрипторов с помощью корневого параметра в корневой подписи.

Корневая подпись и параметры

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

Корневые аргументы могут быть следующими.

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

Для достижения оптимальной производительности приложения обычно сортируют корневые параметры по убыванию частоты изменений.

Все корневые параметры, такие как таблицы дескрипторов, корневые дескрипторы и корневые константы, объединяются в список команд, а драйвер будет управлять их версиями от имени приложения. Другими словами, всякий раз, когда любой из корневых параметров будет изменяться в промежутке между вызовами рендеринга или отправки, оборудование будет обновлять номер версии корневой подписи. При изменении любого аргумента каждый вызов рендеринга или отправки получает уникальный полный набор состояний корневых параметров.

Корневые дескрипторы и корневые константы снижают уровень косвенной адресации ГП при доступе; таблицы дескрипторов позволяют получить доступ к более крупным объемам данных, но при этом уровень косвенной адресации повышается. Из-за более высокого уровня косвенной адресации при использовании таблиц дескрипторов приложение может инициализировать содержимое до момента отправки списка команд на выполнение. Кроме того, модель шейдеров 5.1, поддерживаемая всем оборудованием DirectX 12, позволяет шейдерам динамически индексировать все заданные таблицы дескрипторов. Поэтому шейдер может выбрать нужный дескриптор из таблицы дескрипторов во время выполнения шейдера. Приложение может просто создать одну большую таблицу дескрипторов и всегда использовать индексирование (например, с помощью идентификатора материала) для получения нужного дескриптора.

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

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

// define descriptor tables for a SRV and a sampler for pixel shaders
D3D12_DESCRIPTOR_RANGE descRange[2];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV, 1, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER, 1, 0);

D3D12_ROOT_PARAMETER rootParameters[2];
rootParameters[0].InitAsDescriptorTable(1, &descRange[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &descRange[1], D3D12_SHADER_VISIBILITY_PIXEL);

// store the descriptor tables int the root signature
D3D12_ROOT_SIGNATURE descRootSignature;
descRootSignature.Init(2, rootParameters, 0);

ComPtr<ID3DBlob> pOutBlob;
ComPtr<ID3DBlob> pErrorBlob;
ThrowIfFailed(D3D12SerializeRootSignature(&descRootSignature,   
              D3D_ROOT_SIGNATURE_V1, pOutBlob.GetAddressOf(),                    
              pErrorBlob.GetAddressOf()));

ThrowIfFailed(mDevice->CreateRootSignature(pOutBlob->GetBufferPointer(), 
              pOutBlob->GetBufferSize(), __uuidof(ID3D12RootSignature),                  
             (void**)&mRootSignature));

Все шейдеры в PSO должны быть совместимыми с корневой подписью, указанной с этим объектом PSO; в противном случае объект PSO не будет создан.

Корневую подпись необходимо задать для списка команд или пакета. Для этого мы вызываем: 

commandList->SetGraphicsRootSignature(mRootSignature);

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

// set the two descriptor tables to index into the descriptor heap 
// for the SRV and the sampler
commandList->SetGraphicsRootDescriptorTable(0, 
               mCbvSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
commandList->SetGraphicsRootDescriptorTable(1, 
               mSamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart());

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

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

Собираем компоненты вместе

В приведенном ниже крупном фрагменте кода показаны все механизмы, используемые для привязки ресурсов. Это приложение использует только одну текстуру, а этот код предоставляет семплер и SRV для этой текстуры.

// define descriptor tables for a SRV and a sampler for pixel shaders
D3D12_DESCRIPTOR_RANGE descRange[2];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV, 1, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER, 1, 0);

D3D12_ROOT_PARAMETER rootParameters[2];
rootParameters[0].InitAsDescriptorTable(1, &descRange[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &descRange[1], D3D12_SHADER_VISIBILITY_PIXEL);

// store the descriptor tables in the root signature
D3D12_ROOT_SIGNATURE descRootSignature;
descRootSignature.Init(2, rootParameters, 0);

ComPtr<ID3DBlob> pOutBlob;
ComPtr<ID3DBlob> pErrorBlob;
ThrowIfFailed(D3D12SerializeRootSignature(&descRootSignature,   
              D3D_ROOT_SIGNATURE_V1, pOutBlob.GetAddressOf(),                    
              pErrorBlob.GetAddressOf()));

ThrowIfFailed(mDevice->CreateRootSignature(pOutBlob->GetBufferPointer(), 
              pOutBlob->GetBufferSize(), __uuidof(ID3D12RootSignature),                  
             (void**)&mRootSignature));



// create descriptor heap for shader resource view
D3D12_DESCRIPTOR_HEAP_DESC descHeapCbvSrv = {};
descHeapCbvSrv.NumDescriptors = 1; // for SRV
descHeapCbvSrv.Type = D3D12_CBV_SRV_UAV_DESCRIPTOR_HEAP;
descHeapCbvSrv.Flags = D3D12_DESCRIPTOR_HEAP_SHADER_VISIBLE;
ThrowIfFailed(mDevice->CreateDescriptorHeap(&descHeapCbvSrv, __uuidof(ID3D12DescriptorHeap), (void**)&mCbvSrvDescriptorHeap));
		
// create sampler descriptor heap
D3D12_DESCRIPTOR_HEAP_DESC descHeapSampler = {};
descHeapSampler.NumDescriptors = 1;
descHeapSampler.Type = D3D12_SAMPLER_DESCRIPTOR_HEAP;
descHeapSampler.Flags = D3D12_DESCRIPTOR_HEAP_SHADER_VISIBLE;
ThrowIfFailed(mDevice->CreateDescriptorHeap(&descHeapSampler, __uuidof(ID3D12DescriptorHeap), (void**)&mSamplerDescriptorHeap));

// skip the code that uploads the texture data into heap

// create sampler descriptor in the sample descriptor heap
D3D12_SAMPLER_DESC samplerDesc;
ZeroMemory(&samplerDesc, sizeof(D3D12_SAMPLER_DESC));
samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_WRAP;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D12_COMPARISON_ALWAYS;
mDevice->CreateSampler(&samplerDesc, 
           mSamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

// create SRV descriptor in the SRV descriptor heap
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(D3D12_SHADER_RESOURCE_VIEW_DESC));
srvDesc.Format = SampleAssets::Textures->Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
mDevice->CreateShaderResourceView(mTexture.Get(), &srvDesc,            
            mCbvSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());


// writing into the command list
// set the root signature
commandList->SetGraphicsRootSignature(mRootSignature);

// other commands here ...

// set the two descriptor tables to index into the descriptor heap 
// for the SRV and the sampler
commandList->SetGraphicsRootDescriptorTable(0, 
               mCbvSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
commandList->SetGraphicsRootDescriptorTable(1, 
               mSamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart());

Статические семплеры

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

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

typedef struct D3D12_ROOT_SIGNATURE
{
    UINT NumParameters;
    const D3D12_ROOT_PARAMETER* pParameters;
    UINT NumStaticSamplers;
    const D3D12_STATIC_SAMPLER* pStaticSamplers;
    D3D12_ROOT_SIGNATURE_FLAGS Flags;

    // Initialize struct
    void Init(
        UINT numParameters,
        const D3D12_ROOT_PARAMETER* _pParameters,
        UINT numStaticSamplers = 0,
        const D3D12_STATIC_SAMPLER* _pStaticSamplers = NULL,
        D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_NONE)
    {
        NumParameters = numParameters;
        pParameters = _pParameters;
        NumStaticSamplers = numStaticSamplers;
        pStaticSamplers = _pStaticSamplers;
        Flags = flags;
    }

    D3D12_ROOT_SIGNATURE() { Init(0,NULL,0,NULL,D3D12_ROOT_SIGNATURE_NONE);}

    D3D12_ROOT_SIGNATURE(
        UINT numParameters,
        const D3D12_ROOT_PARAMETER* _pParameters,
        UINT numStaticSamplers = 0,
        const D3D12_STATIC_SAMPLER* _pStaticSamplers = NULL,
        D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_NONE)
    {
        Init(numParameters, _pParameters, numStaticSamplers, _pStaticSamplers, flags);
    }
} D3D12_ROOT_SIGNATURE;

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

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

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

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

Заключение

В DirectX 12 поддерживается полный контроль над моделями использования ресурсов. Разработчик приложения отвечает за выделение памяти в кучах дескрипторов, за описание ресурсов в дескрипторах и за адресацию шейдера по индексу к кучам дескрипторов посредством таблиц дескрипторов, которые, в свою очередь, «раскрываются» для шейдера с помощью корневых подписей.

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

  • корневые константы;
  • статические семплеры;
  • корневые дескрипторы;
  • таблицы дескрипторов.

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

Об авторе

Вольфганг Энгель — директор компании Confetti. Компания Confetti занимается исследованиями в области графики реального времени и является поставщиком услуг для кинематографии и отрасли компьютерных игр. До организации компании Confetti Вольфганг более 4 лет работал в должности ведущего графического программиста в студии RAGE — основной технологической группе компании Rockstar. Вольфганг — основатель и редактор серий книг ShaderX и GPU Pro, обладатель звания Microsoft MVP, автор нескольких книг и статей, посвященных отрисовке в реальном времени. Он регулярно публикует свои материалы на веб-сайтах и проводит выступления на конференциях GDC. Одна из книг, редактором которой он является (ShaderX4), получила в 2006 году премию Game developer Front line. Вольфганг входит в состав нескольких отраслевых консультативных советов, в частности в консультативный совет Майкрософт по графическим решениям DirectX 12. Вольфганг активно участвует в разработке ряда перспективных стандартов для игровой отрасли. Следите за его публикациями в Twitter: wolfgangengel.  Веб-сайт Confetti: www.conffx.com.

Благодарности

Автор выражает благодарность Чазу Бойду, Амару Пателу и Дэвиду Рэйнигу за отзывы и исправление ошибок в этой статье.

Ссылки и полезные материалы

 

** Программное обеспечение и нагрузки, использованные в тестах производительности, могли быть оптимизированы для достижения высокой производительности на микропроцессорах Intel. Тесты производительности, такие как SYSmark* и MobileMark*, проводятся на определенных компьютерных системах, компонентах, программах, операциях и функциях. Любые изменения любого из этих элементов могут привести к изменению результатов. При выборе приобретаемых продуктов следует обращаться к другой информации и тестам производительности, в том числе к тестам производи­тельности определенного продукта в сочетании с другими продуктами.

Быстрые ссылки

Для получения подробной информации о возможностях оптимизации компилятора обратитесь к нашему Уведомлению об оптимизации.
Возможность комментирования русскоязычного контента была отключена. Узнать подробнее.