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


Собственный объект ограждения GPU

В этой статье описывается объект синхронизации забора GPU, который можно использовать для истинной синхронизации GPU и GPU на аппаратном этапе планирования GPU 2. Эта функция поддерживается начиная с Windows 11 версии 24H2 (WDDM 3.2). Разработчики графических драйверов должны быть знакомы с WDDM 2.0 и аппаратным планированием GPU этапа 1.

Существующие и новые объекты синхронизации ограждения

Существующий отслеживаемый объект синхронизации ограждения

Отслеживаемый объект синхронизации ограждения WDDM 2.x поддерживает следующие операции:

  • ЦП ожидает отслеживаемого значения ограждения либо по:
    • Опрос с помощью виртуального адреса ЦП (VA).
    • Очередь ожидания блокировки внутри Dxgkrnl , которая получает сигнал, когда ЦП наблюдает новое отслеживаемое значение ограждения.
  • Сигнал ЦП отслеживаемого значения.
  • Сигнал GPU отслеживаемого значения путем записи в отслеживаемый забор GPU VA и повышение отслеживаемого забора сигнализирует прерывание, чтобы уведомить ЦП об обновлении значения.

То, что не поддерживается, является собственным в gpu ожидание отслеживаемого значения ограждения. Вместо этого ОС содержит работу GPU, которая зависит от ожидающего значения ЦП. Он освобождает эту работу только в GPU при сигнале значения.

Объект синхронизации собственного ограждения GPU

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

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

Разработка объекта синхронизации собственного ограждения GPU

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

:Схема, иллюстрирующая архитектуру объекта собственного ограждения GPU и состояние объекта синхронизации, совместно используемое между ЦП и GPU.

Схема включает два основных компонента:

  • Текущее значение (называемое CurrentValue в этой статье). Это расположение памяти содержит 64-разрядное значение ограждения. CurrentValue сопоставляется с ЦП (доступной для записи из режима ядра, доступной как для чтения из пользовательского, так и в режиме ядра) и GPU (доступно для чтения и записи с помощью виртуального адреса GPU). CurrentValue требует, чтобы 64-разрядные записи были атомарными как с ЦП, так и с точки зрения GPU. То есть обновления на 32-разрядные и высокие 32 бита не могут быть разорваны и должны быть видимы одновременно. Эта концепция уже присутствует в существующем отслеживаемом объекте ограждения.

  • Отслеживаемое значение (называемое MonitoredValue в этой статье). Это расположение памяти содержит наименьшее ожидание значения ЦП, вычитаемого на 1. MonitoredValue сопоставляется и доступен для ЦП (доступно для чтения и записи из режима ядра, без доступа к пользовательскому режиму) и GPU (доступно для чтения с помощью GPU VA, нет доступа на запись). ОС поддерживает список невыполненных официантов ЦП для заданного объекта ограждения, и он обновляет MonitoredValue , так как официанты добавляются и удаляются. Если выдающиеся официанты отсутствуют, то для этого значения задано значение UINT64_MAX. Эта концепция является новой для объекта синхронизации собственного ограждения GPU.

На следующей схеме показано, как Dxgkrnl отслеживает выдающиеся официанты ЦП в определенном отслеживаемом значении ограждения. В нем также показано заданное отслеживаемое значение ограждения в определенный момент времени. CurrentValue и MonitoredValue являются 41, что означает следующее:

  • GPU выполнил все задачи до значения забора 41.
  • ЦП не ожидает значения забора меньше или равно 41.

:Схема, иллюстрирующая объект забора *CurrentValue* (41) и *MonitoredValue* (41), когда наименьшее ожидание значения забора равно 42.

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

:Схема, иллюстрирующая CMP GPU, вызывающая прерывание ЦП, когда новое значение забора *CurrentValue*равно 42 и *MonitoredValue* равно 41.

При обработке этого прерывания ЦП Dxgkrnl выполняет следующие действия, как показано на следующей схеме:

  • Он разблокирует официантов ЦП, которые были удовлетворены недавно написанным забором.
  • Он перемещает отслеживаемое значение в соответствии с наименьшим невыполненным значением, вычитанным на 1.

:Схема, иллюстрирующая, что ожидание ограждения 42 удовлетворено, поэтому наименьшее ожидание значения (*MonitoredValue*) теперь равно 42.

Хранилище физической памяти для текущих и отслеживаемых значений ограждения

Для заданного объекта забора CurrentValue и MonitoredValue хранятся в отдельных расположениях.

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

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

Текущее значение

Текущее значение может находиться в системной памяти или локальной памяти GPU.

  • Для заборов системной памяти ОС выделяет текущее хранилище значений из внутреннего пула памяти системы.

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

Отслеживаемое значение

Отслеживаемое значение также может находиться в локальной памяти системы или GPU.

  • Для заборов системной памяти ОС выделяет отслеживаемое хранилище значений из внутреннего пула памяти системы.

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

При изменении условий ожидания ЦП ОПЕРАЦИОННОй системы вызывает обратный вызов KMD DxgkDdiUpdateMonitoredValues , чтобы обновить отслеживаемое значение до указанного значения.

Проблемы с синхронизацией

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

  • GPU может считывать устаревший MonitoredValue и не вызывать прерывание, как ожидалось ЦП.
  • Обработчик GPU может записать новое CurrentValue , пока CMP находится в середине принятия решения о состоянии прерывания. Это новое Значение CurrentValue может не вызывать прерывание, как ожидалось, или не отображается ЦП, так как оно извлекает текущее значение.

Синхронизация в GPU между подсистемой и CMP

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

  • Модуль GPU, выполняющий поток буфера команд и условно вызывающий аппаратный сигнал к CMP.

  • Gpu CMP, который решает, следует ли вызывать прерывание ЦП.

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

  1. Запись нового MonitoredValue (теневое хранилище GPU)
  2. Выполнение барьера памяти для синхронизации доступа к памяти с обработчиком GPU
  3. Чтение CurrentValue:
    • Если CurrentValue>MonitoredValue, вызовет прерывание ЦП.
    • Если CurrentValue<= MonitoredValue, не создайте прерывание ЦП.

Для правильного разрешения этого состояния гонки крайне важно, чтобы барьер памяти в шаге 2 правильно функционировал. Не должно быть ожидающей операции записи памяти в CurrentValue на шаге 3, которая возникла из команды, которая не видела обновление MonitoredValue на шаге 1. Эта ситуация приведет к возникновению прерывания, если забор, написанный на шаге 3, был больше значения, обновленного на шаге 1.

Синхронизация между GPU и ЦП

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

  • Ос должна изменить MonitoredValue при добавлении нового официанта ЦП в систему или если существующий официант ЦП отключается.
  • ОС вызывает DxgkDdiUpdateMonitoredValues , чтобы уведомить GPU о новом отслеживаемом значении.
  • DxgkDdiUpdateMonitoredValue выполняется на уровне прерывания устройства и, таким образом, синхронизирован с отслеживаемой процедурой службы прерываний с сигнальным забором (ISR).
  • DxgkDdiUpdateMonitoredValue должен гарантировать, что после возврата CurrentValue, считываемый любым ядром процессора, был записан GPU CMP после наблюдения за новым MonitoredValue.
  • При возвращении из DxgkDdiUpdateMonitoredValue ОС перенаправляет CurrentValue и удовлетворяет всем официантам, которые разблокируются новым CurrentValue.

Это совершенно приемлемо для ЦП, чтобы наблюдать за новым CurrentValue , чем тот, который используется GPU, чтобы решить, следует ли вызывать прерывание. Эта ситуация иногда приводит к уведомлению о прерывании, которое не разблокирует никаких официантов. Что недопустимо для ЦП, чтобы не получать уведомление о прерывании для последнего отслеживаемого обновления CurrentValue (то есть CurrentValue> MonitoredValue).)

Отправка запросов к включению функций собственного ограждения в ОС

Для KMD вводятся следующие интерфейсы, чтобы запрашивать, включена ли ОС встроенная функция ограждения:

Как и аппаратный этап планирования 1 и функции очереди перевернутого оборудования, драйверы должны запрашивать, включена ли функция собственного ограждения в ОС во время инициализации драйвера. Однако начиная с WDDM 3.2 ОС использует добавленную поддержку функций WDDM и функцию включения, чтобы контролировать, включена ли эта функция . В результате драйверы должны реализовать этот интерфейс.

Прежде чем KMD объявляет поддержку собственного ограждения в DXGK_VIDSCHCAPS, KMD, как ожидается, реализует интерфейс DXGKDDI_FEATURE_INTERFACE и запрашивает, включена ли ОС функция DXGK_FEATURE_NATIVE_FENCE. Ос завершается сбоем инициализации адаптера, если KMD объявляет поддержку собственного ограждения, если ос не включена.

ОС реализует добавленную таблицу интерфейса DXGKCB_FEATURE_NATIVEFENCE_CAPS_1, выделенную для версии 1 DXGK_FEATURE_NATIVE_FENCE. KMD должен запрашивать эту таблицу интерфейса функций, чтобы определить возможности ОС. В будущих выпусках ОС операционная система может представить будущие версии этой таблицы интерфейса, подробные сведения о поддержке новых возможностей.

Пример кода драйвера для запроса поддержки


DXGK_FEATURE_INTERFACE      FeatureInterface;

struct FEATURE_RESULT
{
    bool Enabled;
    DXGK_FEATURE_VERSION Version;
};

// Driver internal cache for state & version of queried features
struct FEATURE_STATE
{
    struct
    {
        UINT NativeFenceEnabled             : 1;
    };

    DXGK_FEATURE_VERSION NativeFenceVersion = 0;

    // Interfaces
    DXGKCBINT_FEATURE_NATIVEFENCE_1 NativeFenceInterface = {};

    // Interface queried values
    DXGKARGCB_FEATURE_NATIVEFENCE_CAPS_1 NativeFenceOSCaps1 = {};
};

// Helper function to query OS's feature enabled interface
FEATURE_RESULT IsFeatureEnabled(
    DXGK_FEATURE_ID FeatureId
    )
{
    FEATURE_RESULT Result = {};

    //
    // If the feature interface functionality is available (e.g. supported by the OS)
    //
    DXGKARGCB_ISFEATUREENABLED2 Args = {};
    Args.FeatureId = FeatureId;

    if(NT_SUCCESS(FeatureInterface.IsFeatureEnabled(DxgkInterface.DeviceHandle, &Args)))
    {
        Result.Enabled = Args.Result.Enabled;
        Result.Version = Args.Result.Version;
    }

    return Result;
}

// Actual code to query whether OS has enabled Native Fence support and corresponding OS caps
FEATURE_RESULT FeatureResult = IsFeatureEnabled(DXGK_FEATURE_NATIVE_FENCE);
FEATURE_STATE FeatureState = {};
FeatureState.NativeFenceEnabled = !!FeatureResult.Enabled;

if (FeatureResult.Enabled)
{
    // Query OS caps for native fence feature, using the feature interface
    DXGKARGCB_QUERYFEATUREINTERFACE QFIArgs = {};
    QFIArgs.FeatureId = DXGK_FEATURE_NATIVE_FENCE;
    QFIArgs.Interface = &FeatureState.NativeFenceInterface;
    QFIArgs.InterfaceSize = sizeof(FeatureState.NativeFenceInterface);
    QFIArgs.Version = FeatureResult.Version;

    Status = FeatureInterface.QueryFeatureInterface(DxgkInterface.DeviceHandle, &QFIArgs);
    if(NT_SUCCESS(Status))
    {
        FeatureState.NativeFenceVersion = FeatureResult.Version;
        Status = FeatureState.NativeFenceInterface.GetOSCaps(&FeatureState.NativeFenceOSCaps1);
        NT_ASSERT(NT_SUCCESS(Status));
    }
    else
    {
        // We should always succeed getting an interface from a successfully
        // negotiated feature + version.
        NT_ASSERT(FALSE);
    }
}

Возможности собственного ограждения

Следующие интерфейсы обновляются или представлены для запроса собственных заборных заборов:

DDD-идентификаторы KMD для создания, открытия, закрытия и уничтожения собственного объекта ограждения

Следующие DD-интерфейсы, реализованные KMD, представлены для создания, открытия, закрытия и уничтожения собственного объекта ограждения. Dxgkrnl вызывает эти DDIs от имени компонентов пользовательского режима. Dxgkrnl вызывает их только в том случае, если ОС включила функцию DXGK_FEATURE_NATIVE_FENCE .

Следующие DDIs были обновлены для поддержки объектов собственного ограждения:

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

    • PDXGKDDI_CREATENATIVEFENCE DxgkDdiCreateNativeFence (добавлено в WDDM 3.1)
    • PDXGKDDI_DESTROYNATIVEFENCE DxgkDdiDewdNativeFence (добавлено в WDDM 3.1)
    • PDXGKDDI_OPENNATIVEFENCE DxgkDdiCreateNativeFence (добавлено в WDDM 3.2)
    • PDXGKDDI_CLOSENATIVEFENCE DxgkDdiCloseNativeFence (добавлено в WDDM 3.2)
    • PDXGKDDI_SETNATIVEFENCELOGBUFFER DxgkDdiSetNativeFenceLogBuffer (добавлено в WDDM 3.2)
    • PDXGKDDI_UPDATENATIVEFENCELOGS DxgkDdiUpdateNativeFenceLogs (добавлено в WDDM 3.2)

Глобальные и локальные дескрипторы для общих заборов

Представьте, что процесс A создает общий собственный забор и процесс B позже открывает этот забор.

  • При процессе A создает общий собственный забор, Dxgkrnl вызывает DxgkDdiCreateNativeFence с дескриптором драйвера адаптера, на котором создается этот забор. Дескриптор забора, созданный и возвращенный в hGlobalNativeFence, является глобальным дескриптором забора.

  • Затем Dxgkrnl следует вызову DxgkDdiOpenNativeFence, чтобы открыть локальный дескриптор A для конкретного процесса (hLocalNativeFenceA).

  • Когда процесс B открывает тот же общий собственный забор, Dxgkrnl вызывает DxgkDdiOpenNativeFence, чтобы открыть локальный дескриптор B для конкретного процесса (hLocalNativeFenceB).

  • Если процесс A уничтожает свой общий собственный экземпляр забора, Dxgkrnl видит, что есть еще ожидающая ссылка на этот глобальный забор, поэтому только вызывает dxgkDdiCloseNativeFence(hLocalNativeFenceA) для драйвера для очистки структур, относящихся к конкретному процессу. Дескриптор hGlobalNativeFence по-прежнему существует.

  • Когда процесс B уничтожает свой экземпляр забора, Dxgkrnl вызывает DxgkDdiCloseNativeFence(hLocalNativeFenceB), а затем DxgkDdiDe geometryNativeFence(hGlobalNativeFence), чтобы разрешить KMD уничтожить свои глобальные данные ограждения.

Сопоставления va gpu в адресном пространстве процесса разбиения по страницам для использования CMP

KMD задает ограничение DXGK_NATIVE_FENCE_CAPS::MapToGpuSystemProcess на оборудовании, требующее сопоставления виртуальных машин GPU собственного ограждения в адресном пространстве процесса gpu. Набор bit MapToGpuSystemProcess указывает ОС создать сопоставления VA GPU в адресном пространстве процесса разбиения на разбиение в адресное пространство собственного ограждения CurrentValue и MonitoredValue для использования CMP. Эти виртуальные машины GPU позже передаются в DxgkDdiCreateNativeFence как DXGKARG_CREATENATIVEFENCE::CurrentValueSystemProcessGpuVa и MonitoredValueSystemProcessGpuVa.

API ядра D3DKMT для собственных заборов

Следующие API-интерфейсы режима ядра D3DKMT представлены для создания и открытия собственного объекта ограждения.

Dxgkrnl вызывает существующую функцию D3DKMTDesynchronizationObject, чтобы закрыть и уничтожить (бесплатно) существующий собственный объект ограждения.

Поддерживаются структуры и перечисления, которые вводятся или обновляются:

Указание объекта забора забора в собственной очереди оборудования

В следующем обновлении показано, как указать объект забора в собственной очереди оборудования:

Собственный забор сигнализирует о прерывании

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

  • Перечисление DXGK_INTERRUPT_TYPE обновляется, чтобы иметь тип прерывания DXGK_INTERRUPT_NATIVE_FENCE_SIGNALED .
  • Структура DXGKARGCB_NOTIFY_INTERRUPT_DATA обновляется, чтобы включить структуру NativeFenceSignaled для обозначения собственного забора сигнального прерывания. NativeFenceSignaled используется для информирования ОС о том, что набор объектов GPU собственного ограждения, отслеживаемых ЦП, был сигнален на обработчике GPU. Если GPU может определить точное подмножество объектов с активными обработчиками ЦП, оно передает это подмножество через pSignaledNativeFenceArray. Дескриптор в этом массиве должен быть допустимым дескриптором hGlobalNativeFence, передаваемым в KMD в DxgkDdiCreateNativeFence. Передача дескриптора в разрушенный объект забора вызывает ошибку проверка.
  • Структура DXGKCB_NOTIFY_INTERRUPT_DATA_FLAGS обновляется, чтобы включить член EvaluateLegacyMonitoredFences .

GPU может передавать значение NULL pSignaledNativeFenceArray в следующих условиях:

  • Gpu не может определить точное подмножество объектов с активными официантами ЦП.
  • Несколько прерываний сигнала свернуты вместе, что затрудняет определение сигнального набора с активными официантами.

Значение NULL указывает ОС сканировать все выдающиеся собственные средства ожидания объектов ограждения GPU.

Контракт между ОС и драйвером: если у ОС есть активный официант ЦП (как выражено MonitoredValue), а обработчик GPU сигнализирует объекту значение, требующее прерывания ЦП, GPU должен выполнить одно из следующих действий:

  • Включите этот собственный дескриптор ограждения в pSignaledNativeFenceArray.
  • Вызов прерывания NativeFenceSignaled с помощью pSignaledNativeFenceArray.

По умолчанию, когда KMD вызывает это прерывание с помощью null pSignaledNativeFenceArray, Dxgkrnl проверяет только все ожидающие собственные официанты заборов и не сканирует устаревшие отслеживаемые официанты забора. На оборудовании, которое не может различать устаревшие DXGK_INTERRUPT_MONITORED_FENCE_SIGNALED и DXGK_INTERRUPT_NATIVE_FENCE_SIGNALED, KMD всегда может вызывать только введенные DXGK_INTERRUPT_NATIVE_FENCE_SIGNALED прерывания с помощью pSignaledNativeFenceArray = NULL и EvaluateLegacyMonitoredFences = 1, что указывает операционной системе для сканирования всех официантов (устаревшие отслеживаемые официанты забора и собственные официанты заборов).

Указание KMD обновлять пакеты значений

Ниже приведены инструкции KMD об обновлении пакета текущих или отслеживаемых значений:

Кроссплатформенный собственный забор

  • ОС должна поддерживать создание собственных заборов между адаптерами, так как существующие приложения DX12 создают и используют отслеживаемые заборы между адаптерами. Если базовые очереди и планирование для этих приложений переключаются на отправку в режиме пользователя, их отслеживаемые заборы также должны быть переключятся на собственные заборы (очереди пользовательского режима не могут поддерживать отслеживаемые заборы).

  • Межадаптерное ограждение должно быть создано с типом D3DDDI_NATIVEFENCE_TYPE_DEFAULT. В противном случае ошибка D3DKMTCreateNativeFence.

  • Все графические процессоры используют одну и ту же копию хранилища CurrentValue , которая всегда выделяется в системной памяти. Когда среда выполнения создает собственный забор между адаптерами на GPU1 и открывает его на GPU2, сопоставления va GPU на обоих GPU указывают на одно физическое хранилище CurrentValue .

  • Каждый GPU получает собственную копию MonitoredValue. Поэтому хранилище MonitoredValue может быть выделено в системной памяти или локальной памяти.

  • Межадаптерные собственные ограждения должны разрешать условие, в котором GPU1 ожидает собственного забора, что GPU2 сигнализирует. Сегодня нет концепции сигналов GPU к GPU; следовательно, ОПЕРАЦИОННая система явно разрешает это условие, сигналив GPU1 от ЦП. Этот сигнал выполняется путем установки MonitoredValue для межадаптерного забора значение 0 в течение всего времени существования. Затем, когда GPU2 сигнализирует о собственном заборе, он также вызывает прерывание ЦП, позволяя Dxgkrnl обновить CurrentValue на GPU1 (с помощью DxgkDdiUpdateCurrentValuesFromCpu с флагом NotificationOnly set TRUE) и разблокировать ожидающие обработчики ЦП/GPU этого GPU.

  • Хотя MonitoredValue всегда имеет значение 0 для собственных заборов между адаптерами, ожидание и сигналы, отправленные на том же GPU, по-прежнему пользуются более быстрыми преимуществами синхронизации GPU. Тем не менее, преимущество питания сокращения прерываний ЦП теряется, так как прерывания ЦП будут вызваны безоговорочно, даже если на другом GPU не было никаких официантов ЦП или официантов. Этот компромисс делается для поддержания стоимости проектирования и реализации кроссплатформенного собственного ограждения.

  • ОС поддерживает сценарий, в котором собственный объект забора создается на GPU1 и открыт на GPU2, где GPU1 поддерживает функцию и GPU2 не поддерживает. Объект забора открыт как обычный MonitoredFence на GPU2.

  • ОС поддерживает сценарий, в котором обычный отслеживаемый объект забора создается на GPU1 и открыт в качестве собственного забора на GPU2, который поддерживает эту функцию. Объект забора открыт как собственный забор на GPU2.

Сочетания ожидания и сигнала между адаптерами

В таблицах в следующих подразделах приведен пример системы iGPU и dGPU, а также перечислены различные конфигурации, которые могут быть доступны для собственного ожидания или сигнала от ЦП/GPU. Рассматриваются следующие два случая:

  • Оба GPU поддерживают собственные ограждения.
  • IGPU не поддерживает собственные ограждения, но dGPU поддерживает собственные заборы.

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

Таблицы следует считывать, выбрав пару ожиданий и сигналов из столбцов, например WaitFromGPU - SignalFromGPU или WaitFromGPU - SignalFromCPU, et cetera.

Сценарий 1

В сценарии 1 dGPU и iGPU поддерживают собственные ограждения.

iGPU WaitFromGPU (hFence, 10) iGPU WaitFromCPU (hFence, 10) dGPU SignalFromGpu (hFence, 10) dGPU SignalFromCpu(hFence, 10)
UMD вставляет инструкцию hfence CurrentValue == 10 в буфер команд Среда выполнения вызывает D3DKMTWaitForSynchronizationObjectFromCpu
VidSch отслеживает этот объект синхронизации в собственном списке официанта ЦП ЦП
UMD вставляет инструкцию сигнала записи hFence CurrentValue = 10 сигналов в буфер команд Среда выполнения вызывает D3DKMTSignalSynchronizationObjectFromCpu
VidSch получает собственный забор сигналов ISR при записи CurrentValue (так как MonitoredValue == 0 всегда) VidSch вызывает DxgkDdiUpdateCurrentValuesFromCpu(hFence, 10)
VidSch распространяет сигнал (hFence, 10) на iGPU VidSch распространяет сигнал (hFence, 10) на iGPU
VidSch получает распространяемый сигнал и вызывает DxgkDdiUpdateCurrentValuesFromCpu(hFence, NotificationOnly=TRUE) VidSch получает распространяемый сигнал и вызывает DxgkDdiUpdateCurrentValuesFromCpu(hFence, NotificationOnly=TRUE)
KMD повторно сканирует список выполнения для разблокировки канала HW, ожидающего hFence VidSch разблокирует условие ожидания ЦП, сигналив KEVENT

Сценарий 2a

В сценарии 2a iGPU не поддерживает собственные ограждения, но dGPU делает. Ожидание отправляется на iGPU и сигнал передается на dGPU.

iGPU WaitFromGPU (hFence, 10) iGPU WaitFromCPU (hFence, 10) dGPU SignalFromGpu (hFence, 10) dGPU SignalFromCpu(hFence, 10)
Среда выполнения вызывает D3DKMTWaitForSynchronizationObjectFromGpu Среда выполнения вызывает D3DKMTWaitForSynchronizationObjectFromCpu
VidSch отслеживает этот объект синхронизации в отслеживаемом списке ожидания забора VidSch отслеживает этот объект синхронизации в отслеживаемом заборе головы списка официантов ЦП
UMD вставляет инструкцию сигнала записи hFence CurrentValue = 10 сигналов в буфере команд Среда выполнения вызывает D3DKMTSignalSynchronizationObjectFromCpu
VidSch получает NativeFenceSignaledISR при записи CurrentValue (так как MV == 0 всегда) VidSch вызывает DxgkDdiUpdateCurrentValuesFromCpu(hFence, 10)
VidSch распространяет сигнал (hFence, 10) на iGPU VidSch распространяет сигнал (hFence, 10) на iGPU
VidSch получает распространяемый сигнал и наблюдает новое значение ограждения VidSch получает распространяемый сигнал и наблюдает новое значение ограждения
VidSch сканирует отслеживаемый список ожидания забора и разблокирует контексты программного обеспечения VidSch сканирует отслеживаемую заборную головку списка официантов ЦП и разблокирует ожидание ЦП, сигналив KEVENT

Сценарий 2b

В сценарии 2b поддержка собственного ограждения остается той же (iGPU не поддерживает dGPU). На этот раз сигнал отправляется на iGPU, а ожидание отправляется на dGPU.

iGPU SignalFromGPU (hFence, 10) iGPU SignalFromCPU (hFence, 10) dGPU WaitFromGpu (hFence, 10) dGPU WaitFromCpu(hFence, 10)
UMD вставляет инструкцию hfence CurrentValue == 10 в буфере команд Среда выполнения вызывает D3DKMTWaitForSynchronizationObjectFromCpu
VidSch отслеживает этот объект синхронизации в собственном списке официанта ЦП ЦП
UMD вызывает D3DKMTSignalSynchronizationObjectFromGpu UMD вызывает D3DKMTSignalSynchronizationObjectFromCpu
Если пакет находится в центре контекста программного обеспечения, VidSch обновляет значение забора непосредственно из ЦП VidSch обновляет значение ограждения непосредственно из ЦП
VidSch распространяет сигнал (hFence, 10) на dGPU VidSch распространяет сигнал (hFence, 10) на dGPU
VidSch получает распространяемый сигнал и вызывает DxgkDdiUpdateCurrentValuesFromCpu(hFence, NotificationOnly=TRUE) VidSch получает распространяемый сигнал и вызывает DxgkDdiUpdateCurrentValuesFromCpu(hFence, NotificationOnly=TRUE)
KMD повторно сканирует список выполнения для разблокировки канала HW, ожидающего hFence VidSch разблокирует условие ожидания ЦП, сигналив KEVENT

Будущий сигнал между АДАПТЕРами GPU и GPU

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

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

Задача этой работы заключается в разработке:

  • Общая память двери.
  • Интеллектуальная полезные данные или дескриптор, которые GPU может записывать в дверь, что позволяет другим GPU определить, какой забор был сигналирован, чтобы он смог сканировать только подмножество HWQueues.

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

Конструктор буфера журнала в собственном заборе

При использовании собственных заборов и отправки в пользовательском режиме Dxgkrnl не имеет видимости, когда собственный GPU ожидает и сигналов, вложенных из UMD, разблокируются на GPU для определенного HWQueue. С собственными заборами, отслеживаемый забор сигнализирует прерывание может быть подавлен для заданного забора.

:Схема, иллюстрирующая операции ограждения с полями для сигналов и ожиданий.

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

Таким образом, на высоком уровне условия HWQueue, необходимые для ведения журнала:

Condition Значение
FENCE_WAIT_QUEUED Метка времени ЦП, когда UMD вставляет инструкцию ожидания GPU в очередь команд
FENCE_SIGNAL_QUEUED Метка времени ЦП, когда UMD вставляет инструкцию сигнала GPU в очередь команд
FENCE_SIGNAL_EXECUTED Метка времени GPU при выполнении команды сигнала на GPU для HWQueue
FENCE_WAIT_UNBLOCKED Метка времени GPU о том, когда условие ожидания удовлетворено на GPU, и HWQueue разблокируется

Идентификаторы DD буфера журнала в собственном заборе

Для поддержки собственных буферов журналов забора представлены следующие DDI, структуры и перечисления:

Механизм буфера журнала

  1. Dxgkrnl выделяет два выделенных 4-КБ буферов журнала на HWQueue.

    • Один для ведения журнала ожидает.
    • Один для сигналов ведения журнала.

    Эти буферы журналов имеют сопоставления для ЦП в режиме ядра (LogBufferCpuVa), виртуальной машины GPU в адресном пространстве процесса (LogBufferGpuVa) и CMP VA (LogBufferSystemProcessGpuVa), чтобы они могли читать и записывать данные в KMD, обработчик GPU и CMP. Dxgkrnl вызывает DxgkDdiSetNativeFenceFenceLogBuffer дважды: один раз, чтобы задать буфер журнала для ожиданий ведения журнала и один раз задать буфер журнала для сигналов ведения журнала.

  2. Сразу после вставки собственной инструкции ожидания или сигнала забора в список команд также вставляется команда, в которой GPU будет записывать полезные данные при определенной записи в буфер журнала.

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

  4. Хотя UMD не может получить доступ к буферу журнала с поддержкой GPU, он управляет прогрессией буфера журнала. То есть UMD определяет следующую бесплатную запись для записи, если она есть, и программирует GPU с этой информацией. Когда GPU записывает в буфер журнала, он увеличивает значение FirstFreeEntryIndex в заголовке журнала. UMD должен гарантировать, что записи в записи журнала монотонно увеличиваются.

Рассмотрим следующий сценарий:

  1. Существует два HWQueues, HWQueueA и HWQueueB с соответствующими буферами журналов ограждения с gpu VAs of FenceLogA и FenceLogB. HWQueueA связан с буфером журнала для ожидания ведения журнала, а HWQueueB связан с буфером журнала для сигналов ведения журнала.
  2. Существует собственный объект ограждения с пользовательским режимом D3DKMT_HANDLE FenceF.
  3. Ожидание GPU в FenceF для Value V1 помещается в очередь В HWQueueA во время ЦПT1. При сборке буфера команд UMD вставляет команду, в которой GPU будет записывать полезные данные: LOG(FenceF, V1, DXGK_NATIVE_FENCE_LOG_OPERATION_WAIT_UNBLOCKED).
  4. Сигнал GPU к FenceF с значением V1 помещается в очередь в HWQueueB во время ЦП2. При сборке буфера команд UMD вставляет команду, в которой GPU будет записывать полезные данные: LOG(FenceF, V1, DXGK_NATIVE_FENCE_LOG_OPERATION_SIGNAL_EXECUTED).

После того как планировщик GPU выполнит сигнал GPU на HWQueueB во время GPU GPU GPUT1, он считывает полезные данные UMD и регистрирует событие в журнале забора ОС для HWQueueB:

DXGK_NATIVE_FENCE_LOG_ENTRY LogEntry = {};
LogEntry.hNativeFence = FenceF;
LogEntry.FenceValue = V1;
LogEntry.OperationType = DXGK_NATIVE_FENCE_LOG_OPERATION_SIGNAL_EXECUTED;
LogEntry.FenceEndGpuTimestamp = GPUT1;  // Time when UMD submits a command to the GPU

После того, как планировщик GPU наблюдает за разблокировками HWQueueA во время GPU GPU GPUT2, он считывает полезные данные UMD и регистрирует событие в журнале забора ОС для HWQueueA:

DXGK_NATIVE_FENCE_LOG_ENTRY LogEntry = {};
LogEntry.hNativeFence = FenceF;
LogEntry.FenceValue = V1;
LogEntry.OperationType = DXGK_NATIVE_FENCE_LOG_OPERATION_WAIT_UNBLOCKED;
LogEntry.FenceObservedGpuTimestamp = GPUTo;  // Time that GPU acknowledged UMD's submitted command and queued the fence wait on HW
LogEntry.FenceEndGpuTimestamp = GPUT2;

Dxgkrnl может уничтожить и воссоздать буфер журнала. Каждый раз, когда он делает, он вызывает DxgkDdiSetNativeFenceLogBuffer , чтобы сообщить KMD о новом расположении.

Метки времени ЦП для операций с очередями забора

Существует мало преимуществ для создания журнала UMD этих меток времени ЦП, учитывая следующее:

  • Список команд можно записать несколько минут до выполнения gpu буфера команд, включающего список команд.
  • Эти несколько минут могут быть неупорядоченными с другими объектами синхронизации, которые находятся в одном буфере команд.

Существует плата за включение меток времени ЦП в инструкции UMD к буферу журнала с записью GPU, поэтому метки времени ЦП не включаются в полезные данные записи журнала.

Вместо этого среда выполнения или UMD может выдавать событие трассировки времени ЦП во время записи списка команд. Таким образом, средства могут создавать временная шкала заборных и завершенных событий путем объединения метки времени ЦП из этого нового события и метки времени GPU из записи буфера журнала.

Порядок операций на GPU при сигнале или разблокировки забора

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

  1. Напишите новое значение забора, чтобы забора VA/CMP VA.
  2. Запишите полезные данные журнала в соответствующий буфер журнала GPU VA/CMP VA.
  3. При необходимости вызвать собственное ограждение, сигнальное прерывание.

Этот порядок операций гарантирует, что Dxgkrnl видит самые последние записи журнала при вызове прерывания в ОС.

Разрешено переполнение буфера журнала

GPU может перезаписать буфер журнала, перезаписав записи, которые еще не видели ОС. Это делается путем добавочного обходаCount.

Когда ОПЕРАЦИОННая система в конечном итоге считывает журнал, она может обнаружить, что превышено превышение, сравнивая новое значение WraparoundCount в заголовке журнала с его кэшируемым значением. Если произошло перерасход, ОС имеет следующие резервные параметры:

  • Чтобы разблокировать заборы при возникновении переполнения, ОС проверяет все заборы и определяет, какие официанты были разблокированы.
  • Если трассировка включена, ОС может вывести флаг в трассировке, чтобы уведомить пользователя о том, что события были потеряны. Кроме того, при включении трассировки ОС сначала увеличивает размер буфера журнала, чтобы предотвратить перезапуск в первую очередь.

Для реализации поддержки обратного давления при выполнении записей буфера журнала не требуется UMD.

Пустые или повторяющиеся метки времени буфера журнала

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

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

Синхронно обновление журнала собственного ограждения

Gpu записывает данные, чтобы обновить значение ограждения и соответствующий буфер журнала, должен обеспечить полное распространение операций записи перед чтением ЦП. Это требование требует использования барьеров памяти. Например:

  • Signal Fence(N): запись N в качестве нового текущего значения
  • Запись LOG, включая метку времени GPU
  • MemoryBarrier
  • Добавочное значение FirstFreeEntryIndex
  • MemoryBarrier
  • Отслеживаемое прерывание забора (N): чтение адреса "M" и сравнение значения с N для принятия решения о доставке прерывания ЦП

Слишком дорого вставлять два барьера для каждого сигнала GPU, особенно если вероятно, что условный прерываний проверка не удовлетворен, и не требуется прерывание ЦП. В результате дизайн перемещает стоимость вставки одного из барьеров памяти от GPU (производителя) к ЦП (потребителю). Dxgkrnl вызывает введенную функцию DxgkDdiUpdateNativeFenceLogs, чтобы kmD синхронно смыть ожидающие записи в журнале собственного ограждения по запросу (аналогично тому, как dxgkddiUpddiUpdateflipqueuelog был введен для журнала очереди HW flip queue flush).

Операции GPU:

  • Signal Fence(N): запись N в качестве нового текущего значения
  • Запись LOG, включая метку времени GPU
  • Добавочное значение FirstFreeEntryIndex
  • MemoryBarrier => Обеспечение полного распространения FirstFreeEntryIndex
  • Отслеживаемое прерывание забора (N): чтение адреса "M" и сравнение значения с N для принятия решения о доставке прерывания

Операции ЦП:

В собственном заборе Dxgkrnl сигнализирует обработчик прерываний (DISPATCH_IRQL):

  • Для каждого журнала HWQueue: чтение FirstFreeEntryIndex и определение, записываются ли новые записи.
  • Для каждого журнала HWQueue с новыми записями: вызов dxgkDdiUpdateNativeFenceLogs и укажите дескриптор ядра для этих HWQueues. В этом DDI KMD вставляет барьер памяти для каждого заданного HWQueue, что гарантирует фиксацию всех записей журнала.
  • Dxgkrnl считывает записи журнала для извлечения полезных данных метки времени.

Таким образом, если оборудование вставляет барьер памяти после записи в FirstFreeEntryIndex, Dxgkrnl всегда вызывает DDI KMD, позволяя KMD вставлять барьер памяти, прежде чем Dxgkrnl считывает все записи журнала.

Будущие требования к оборудованию

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

  • ОС выполняет чтение (потенциально через PCI) значения забора.
  • Зная, какой забор был сигнален и значение забора, ОС просыпается официантов ЦП, которые ожидают этого забора или значения.
  • Отдельно для родительского устройства этого забора ОС сканирует буферы журнала всех его HWQueues. Затем ОС считывает последние записи буфера журнала, чтобы определить, какой HWQueue сделал сигнал и извлекает соответствующие полезные данные метки времени. Этот подход может избыточно считывать некоторые значения забора по стандарту PCI.

На будущих платформах Dxgkrnl предпочитает получать массив дескрипторов ядра HwQueue в собственном заборе сигнального прерывания. Такой подход позволяет ос:

  • Прочитайте последние записи буфера журнала для этого HwQueue. Устройство пользователя не известно обработчику прерываний; поэтому этот дескриптор HwQueue должен быть дескриптором ядра.
  • Проверьте буфер журнала для записей журнала, указывающих, какие заборы были сигнализируют, а также какие значения. Чтение только буфера журнала обеспечивает одно чтение по PCI вместо избыточного чтения значений ограждения и буфера журнала. Эта оптимизация завершается успешно, пока буфер журнала не был перезагрущен (удаление записей, которые Dxgkrnl никогда не считывается).
  • Если ОС обнаруживает, что буфер журнала был переполнен, он возвращается к неоптимизированному пути, который считывает динамическое значение каждого ограждения, принадлежащее одному устройству. Производительность пропорциональна количеству заборов, принадлежащих устройству. Если значение забора находится в памяти видео, то эти операции чтения согласованы с кэшем в PCI.
  • Зная, какие заборы были сигналированы и значения забора, ОС просыпается официантов ЦП, которые ожидают этих заборов или значений.

Оптимизированное собственное ограждение сигнализировало прерывание

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

Если поддерживается оборудованием, то вместо заполнения массива дескрипторов ограждения, которые сигналировали, GPU должен упоминание маркер KMD HWQueue, который выполнялся при возникновении прерывания. Dxgkrnl сканирует буфер журнала забора для этого HWQueue и считывает все операции заборов, которые были завершены GPU с момента последнего обновления и разблокирует все соответствующие официанты ЦП. Если GPU не удалось определить, какой подмножество заборов было сигналировано, следует указать дескриптор NULL HWQueue. Когда Dxgkrnl видит дескриптор HWQueue NULL, он возвращается для повторного сканирования буфера журнала всех HWQueues на этом механизме, чтобы определить, какие заборы получили сигнал.

Поддержка этой оптимизации является необязательной; KMD должен задать ограничение DXGK_VIDSCHCAPS:OptimizedNativeFenceFenceSignaledInterrupt , если оно поддерживается оборудованием. Если не задана крышка OptimizedNativeFenceFenceSignaledInterrupt, то GPU/KMD должен следовать поведению, описанному в встроенном заборе, сигнализировав прерывание.

Пример оптимизированного собственного ограждения сигнализировал прерывание

  1. HWQueueA: GPU Signal to Fence F1, Value V1 —> Запись в запись буфера журнала E1 —> прерывание не требуется

  2. HWQueueA: GPU Signal to Fence F1, Value V2 —> Запись в запись буфера журнала E2 —> прерывание не требуется

  3. HWQueueA: GPU Signal to Fence F2, Value V3 —> Запись в запись буфера журнала E3 —> не требуется прерывание

  4. HWQueueA: GPU Signal to Fence F2, Value V3 —> Запись в запись буфера журнала E4 —> прерывание, возникающее

    DXGKARGCB_NOTIFY_INTERRUPT_DATA FenceSignalISR = {};
    FenceSignalISR.NodeOrdinal = 0;
    FenceSignalISR.EngineOrdinal = 0;
    FenceSignalISR.hHWQueue = A;
    
  5. Dxgkrnl считывает буфер журнала для HWQueueA. Он считывает записи буфера журнала E1, E2, E3 и E4 для наблюдения за сигнализованными ограждениями F1 @ Value V1, F1 @ Value V2, F2 @ Value V3 и F2 @ Value V3, и разблокирует все официанты, ожидающие этих заборов и значений

Необязательное и обязательное ведение журнала

Поддержка ведения журнала в собственном заборе для DXGK_NATIVE_FENCE_LOG_TYPE_WAITS и DXGK_NATIVE_FENCE_LOG_TYPE_SIGNALS является обязательной.

В будущем другие типы ведения журнала могут быть добавлены только в том случае, если такие средства, как GPUView , позволяют выполнять подробное ведение журнала ETW в ОС. ОС должна информировать UMD и KMD о том, когда подробное ведение журнала включено и отключено, чтобы ведение журнала этих подробных событий было выборочно включено.