Проходы отрисовки Direct3D 12

Функция проходов отрисовки является новой для Windows 10, версия 1809 (10.0; Сборка 17763), в ней представлена концепция прохода отрисовки Direct3D 12. Проход отрисовки состоит из подмножества команд, записываемых в список команд.

Чтобы объявить, где начинается и заканчивается каждый проход отрисовки, необходимо вложить команды, принадлежащие к этой передаче, внутри вызовов ID3D12GraphicsCommandList4::BeginRenderPass и EndRenderPass. Следовательно, любой список команд содержит ноль, один или несколько проходов отрисовки.

Сценарии

Проходы отрисовки могут повысить производительность отрисовщика, если он основан на Tile-Based отложенной отрисовки (TBDR), помимо других методов. В частности, этот метод помогает вашему отрисовщику повысить эффективность GPU, уменьшая трафик памяти, поступающий из памяти вне микросхемы, позволяя приложению лучше определять требования к упорядочению отрисовки ресурсов и зависимости данных.

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

Это сценарии, в которых проходы отрисовки предназначены для предоставления значения.

Разрешите приложению избежать ненужных загрузок или хранения ресурсов из main памяти в архитектуре Tile-Based отложенной отрисовки (TBDR)

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

Разрешите архитектуре TBDR использовать постоянные ресурсы во встроенном кэше по всем проходам отрисовки (даже в отдельных списках команд)

Примечание

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

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

Обязанности приложения

Даже при использовании функции рендеринга ни среда выполнения Direct3D 12, ни драйвер дисплея не берут на себя ответственность за вывод возможностей для повторного заказа или предотвращения нагрузок и хранилищ. Чтобы правильно использовать функцию проходов отрисовки, приложение несет эти обязанности.

  • Правильно определите зависимости данных и упорядочения для своих операций.
  • Упорядочение его отправки таким образом, чтобы свести к минимуму объемы сбросов (чтобы свести к минимуму использование флагов _PRESERVE ).
  • Правильно используйте барьеры ресурсов и отслеживайте состояние ресурса.
  • Избегайте ненужных копий и очистки. Для их выявления можно использовать автоматические предупреждения о производительности из средства PIX в Windows.

Использование функции прохода отрисовки

Что такое проход отрисовки?

Проход отрисовки определяется этими элементами.

  • Набор выходных привязок, которые фиксируются на протяжении всего этапа отрисовки. Эти привязки относятся к одному или нескольким целевым представлениям отрисовки (RTV) и (или) к представлению элементов глубины (DSV).
  • Список операций GPU, предназначенных для набора выходных привязок.
  • Метаданные, описывающие зависимости загрузки и хранения для всех выходных привязок, предназначенных для прохода отрисовки.

Объявление выходных привязок

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

Эти привязки объявляются в вызове ID3D12GraphicsCommandList4::BeginRenderPass.

void render_passes(::ID3D12GraphicsCommandList4 * pIGCL4,
    D3D12_CPU_DESCRIPTOR_HANDLE const& rtvCPUDescriptorHandle,
    D3D12_CPU_DESCRIPTOR_HANDLE const& dsvCPUDescriptorHandle)
{
    const float clearColor4[]{ 0.f, 0.f, 0.f, 0.f };
    CD3DX12_CLEAR_VALUE clearValue{ DXGI_FORMAT_R32G32B32_FLOAT, clearColor4 };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessClear{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, { clearValue } };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessPreserve{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {} };
    D3D12_RENDER_PASS_RENDER_TARGET_DESC renderPassRenderTargetDesc{ rtvCPUDescriptorHandle, renderPassBeginningAccessClear, renderPassEndingAccessPreserve };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessNoAccess{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessNoAccess{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_DEPTH_STENCIL_DESC renderPassDepthStencilDesc{ dsvCPUDescriptorHandle, renderPassBeginningAccessNoAccess, renderPassBeginningAccessNoAccess, renderPassEndingAccessNoAccess, renderPassEndingAccessNoAccess };

    pIGCL4->BeginRenderPass(1, &renderPassRenderTargetDesc, &renderPassDepthStencilDesc, D3D12_RENDER_PASS_FLAG_NONE);
    // Record command list.
    pIGCL4->EndRenderPass();
    // Begin/End further render passes and then execute the command list(s).
}

Первое поле структуры D3D12_RENDER_PASS_RENDER_TARGET_DESC задается дескриптором дескриптора ЦП, соответствующим одному или нескольким целевым представлениям отрисовки (RTV). Аналогичным образом D3D12_RENDER_PASS_DEPTH_STENCIL_DESC содержит дескриптор ЦП дескриптор, соответствующий представлению трафарета глубины (DSV). Эти дескрипторные дескрипторы ЦП являются теми же дескрипторами, которые в противном случае можно было бы передать в ID3D12GraphicsCommandList::OMSetRenderTargets. И, как и в случае с OMSetRenderTargets, дескрипторы ЦП прикрепляются из соответствующих куч (дескриптор ЦП) во время вызова BeginRenderPass.

RtV и DSV не наследуются в передаче отрисовки. Скорее, они должны быть заданы. Кроме того, rtv и DSV, объявленные в BeginRenderPass , не распространяются в список команд. Вместо этого они находятся в неопределенном состоянии после прохождения отрисовки.

Отрисовка проходов и рабочих нагрузок

Вы не можете вложить проходы отрисовки, и вы не можете иметь ряд проходов отрисовки более одного списка команд (они должны начинаться и заканчиваться при записи в один список команд). Оптимизации, предназначенные для обеспечения эффективного многопотокового создания проходов отрисовки, рассматриваются в разделе Флаги прохода отрисовки ниже.

Запись, которая выполняется в рамках прохода отрисовки, недопустим для чтения до последующего прохода отрисовки. Это исключает некоторые типы барьеров в пределах прохода отрисовки, например, ограничение от RENDER_TARGET до SHADER_RESOURCE на целевом объекте отрисовки, привязанном в данный момент. Дополнительные сведения см. в разделе Отрисовка проходов и барьеров ресурсов ниже.

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

Проходы отрисовки и барьеры ресурсов

Вы не можете считывать или использовать запись, которая произошла в рамках того же прохода отрисовки. Некоторые барьеры не соответствуют этому ограничению, например от D3D12_RESOURCE_STATE_RENDER_TARGET до *_SHADER_RESOURCE на целевом объекте отрисовки, привязанном к текущему моменту (и уровень отладки приведет к ошибке). Но этот же барьер для целевого объекта отрисовки, написанный за пределами текущего прохода отрисовки, соответствует требованиям, так как операции записи завершатся раньше, чем начнется текущий проход отрисовки. Вам может быть полезно знать о некоторых оптимизациях, которые может сделать драйвер дисплея в этом отношении. Учитывая соответствующую рабочую нагрузку, драйвер дисплея может переместить все барьеры, возникшие в вашем проходе отрисовки, в начало прохода отрисовки. Там они могут быть объединены (и не мешать каким-либо операциям укладки/бинирования). Это допустимая оптимизация при условии, что все операции записи завершены до начала текущего прохода отрисовки.

Ниже приведен более полный пример оптимизации драйвера, в котором предполагается, что у вас есть подсистема отрисовки с предварительной привязкой ресурсов в 12-стиле Direct3D, выполняющая барьеры по требованию в зависимости от того, как связаны ресурсы. При записи в неупорядоченное представление доступа (UAV) в конце кадра (для использования в следующем кадре) подсистема может оставить ресурс в состоянии D3D12_RESOURCE_STATE_UNORDERED_ACCESS по завершении кадра. В следующем кадре, когда подсистема переходит к привязке ресурса в качестве представления ресурсов шейдера (SRV), он обнаружит, что ресурс находится в неправильном состоянии, и он создаст барьер от D3D12_RESOURCE_STATE_UNORDERED_ACCESS до D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE. Если этот барьер возникает в пределах прохода отрисовки, драйвер дисплея имеет право предположить, что все операции записи уже произошли за пределами текущего прохода отрисовки, и, следовательно, (и вот где происходит оптимизация) драйвер дисплея может переместить барьер до начала прохода отрисовки. Опять же, это допустимо при условии, что код соответствует ограничению записи и чтения, описанному в этом разделе и последнем.

Это примеры соответствующих барьеров.

  • D3D12_RESOURCE_STATE_UNORDERED_ACCESSD3D12_RESOURCE_STATE_INDIRECT_ARGUMENT.
  • D3D12_RESOURCE_STATE_COPY_DEST в *_SHADER_RESOURCE.

И это примеры несоответствующие барьеры.

  • D3D12_RESOURCE_STATE_RENDER_TARGET в любое состояние чтения в текущих привязанных RTV/DSV.
  • D3D12_RESOURCE_STATE_DEPTH_WRITE к любому состоянию чтения на текущий момент привязанных RTV/DSV.
  • Любой барьер псевдонима.
  • Барьеры неупорядоченного представления доступа (UAV). 

Объявление доступа к ресурсам

Во время BeginRenderPass , а также при объявлении всех ресурсов, работающих в качестве RTV и (или) DSV в рамках этого прохода, необходимо также указать характеристики их начального и конечного доступа . Как видно из примера кода в разделе Объявление выходных привязок выше, это можно сделать с помощью структур D3D12_RENDER_PASS_RENDER_TARGET_DESC и D3D12_RENDER_PASS_DEPTH_STENCIL_DESC .

Дополнительные сведения см . в D3D12_RENDER_PASS_BEGINNING_ACCESS и D3D12_RENDER_PASS_ENDING_ACCESS структурах, а также в перечислениях D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE и D3D12_RENDER_PASS_ENDING_ACCESS_TYPE .

Отрисовка флагов передачи

Последний параметр, передаваемый в BeginRenderPass , является флагом передачи отрисовки (значение из перечисления D3D12_RENDER_PASS_FLAGS ).

enum D3D12_RENDER_PASS_FLAGS
{
    D3D12_RENDER_PASS_FLAG_NONE = 0,
    D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES = 0x1,
    D3D12_RENDER_PASS_FLAG_SUSPENDING_PASS = 0x2,
    D3D12_RENDER_PASS_FLAG_RESUMING_PASS = 0x4
};

UAV записывает данные в пределах прохода отрисовки

Запись в неупорядоченном режиме доступа (UAV) разрешена в пределах прохода отрисовки, но необходимо указать, что вы будете выдавать записи UAV в рамках прохода отрисовки, указав D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES, чтобы драйвер дисплея при необходимости может отказаться от ввода.

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

Привязки UAV (через корневые таблицы или корневые дескрипторы) наследуются в проходах отрисовки и распространяются из проходов отрисовки.

Приостановка проходов и возобновление проходов

Можно указать, что весь проход отрисовки является приостановленным и/или возобновляющим проходом. Пара приостановки с последующим возобновлением должна иметь одинаковые флаги представлений и доступа между проходами и может не иметь промежуточных операций GPU (например, рисование, отправку, отмену, очистку, копирование, сопоставление с обновлением плитки, запись буфера, запросы, разрешения запросов) между приостановленным проходом отрисовки и возобновлением прохода отрисовки.

Предполагаемый вариант использования — многопоточная отрисовка, где, скажем, четыре списка команд (каждый с собственными проходами отрисовки) могут быть нацелены на одни и те же целевые объекты отрисовки. Когда проходы отрисовки приостанавливаются или возобновляются в списках команд, списки команд должны выполняться в том же вызове ID3D12CommandQueue::ExecuteCommandLists.

Проход отрисовки может быть как возобновлением, так и приостановкой. В приведенном многопотоковом примере списки команд 2 и 3 будут возобновляться с 1 и 2 соответственно. И в то же время 2 и 3 будут приостановлены до 3 и 4 соответственно.

Запрос на отрисовку проходит поддержку функций

Вы можете вызвать ID3D12Device::CheckFeatureSupport , чтобы запросить степень, в которой драйвер устройства и (или) оборудование эффективно поддерживают проходы отрисовки.

D3D12_RENDER_PASS_TIER get_render_passes_tier(::ID3D12Device * pIDevice)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS5 featureSupport{};
    winrt::check_hresult(
        pIDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &featureSupport, sizeof(featureSupport))
    );
    return featureSupport.RenderPassesTier;
}
...
    D3D12_RENDER_PASS_TIER renderPassesTier{ get_render_passes_tier(pIDevice) };

Из-за логики сопоставления среды выполнения отрисовка передает функцию always. Но в зависимости от поддержки функций они не всегда предоставляют преимущества. Вы можете использовать код, аналогичный приведенному выше, чтобы определить, стоит ли выполнять команды в качестве проходов отрисовки и когда это определенно не является преимуществом (то есть, когда среда выполнения просто сопоставляется с существующей поверхностью API). Выполнение этого проверка особенно важно, если вы используете D3D11On12).

Описание трех уровней поддержки см. в перечислении D3D12_RENDER_PASS_TIER .