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


Передачи отрисовки Direct3D 12

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

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

Сценарии

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

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

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

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

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

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

Заметка

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

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

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

Даже если функция отрисовки передается, ни среда выполнения 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 задает дескриптор ЦП, соответствующий одному или нескольким целевым представлениям отрисовки (RTVs). Аналогичным образом D3D12_RENDER_PASS_DEPTH_STENCIL_DESC содержит дескриптор ЦП, соответствующий представлению элементов глубины (DSV). Эти дескрипторы ЦП являются теми же дескрипторами, которые можно передать в ID3D12GraphicsCommandList::OMSetRenderTargets. И так же, как и с OMSetRenderTargets, дескрипторы ЦП прикреплены из их соответствующих кучи (дескриптор ЦП) во время вызова BeginRenderPass.

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

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

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

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

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

Отрисовка проходов и барьеров ресурсов

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

Ниже приведен более полный пример оптимизации драйверов, который предполагает, что у вас есть модуль отрисовки с предварительной привязкой ресурсов Direct3D 12, выполняющий барьеры по запросу на основе того, как связаны ресурсы. При записи в неупорядоченное представление доступа (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 в любое состояние чтения на виртуальных машинах RTVs/DSVs, привязанных к текущим данным.
  • D3D12_RESOURCE_STATE_DEPTH_WRITE в любое состояние чтения на виртуальных машинах RTVs/DSVs, привязанных к текущим данным.
  • Любой барьер псевдонима.
  • Неупорядоченные барьеры представления доступа (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) };

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

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