Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Это важно
Некоторые сведения относятся к предварительному продукту, который может быть существенно изменен до его коммерческого выпуска. Корпорация Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых в отношении информации, предоставленной здесь.
В этой статье описывается функция отправки в пользовательском режиме (UM), которая всё ещё находится в стадии разработки в Windows 11, версии 24H2 (WDDM 3.2). Представление задач в пользовательском режиме позволяет приложениям отправлять задачи на GPU непосредственно с очень низкой задержкой. Цель заключается в повышении производительности приложений, которые часто передают небольшие рабочие нагрузки в GPU. Кроме того, ожидается, что отправка в режиме пользователя значительно повышает преимущества таких приложений, если они выполняются в контейнере или виртуальной машине. Это преимущество связано с тем, что драйвер пользовательского режима (UMD), работающий на виртуальной машине, может напрямую передавать задания в GPU, не отправляя сообщение на хост.
Драйверы и оборудование IHV, поддерживающие отправку задач в пользовательском режиме (UM), должны продолжать одновременно поддерживать традиционную модель отправки задач в режиме ядра. Эта поддержка необходима для таких сценариев, как старая гостевая система, которая поддерживает только традиционные очереди KM, работающие на новейшем узле.
В этой статье не рассматривается совместимость отправки UM с Flip/FlipEx. Отправка UM, описанная в этой статье, ограничена только сценарием визуализации и вычислительными классами сценариев. Конвейер презентации по-прежнему основан на отправке в режиме ядра, так как он зависит от собственных контролируемых барьеров. Проектирование и внедрение презентации на основе отправки UM (Unified Memory) можно рассматривать после полной реализации собственных контролируемых барьеров и отправки UM исключительно для вычислительных и графических задач. Таким образом, драйверы должны поддерживать отправку из пользовательского режима для каждой очереди отдельно.
Дверные звонки
Большинство текущих или предстоящих поколений GPU, поддерживающих аппаратное планирование, также поддерживают концепцию сигнальной системы GPU doorbell. Doorbell — это механизм, указывающий графическому процессору, что новая работа поступила в её рабочую очередь. Doorbells обычно регистрируются в PCIe BAR (базовой адресной строке) или системной памяти. Каждый GPU IHV имеет собственную архитектуру, которая определяет количество дверей, где они находятся в системе, и т. д. ОС Windows использует дверные звонки как часть своего дизайна для реализации отправки рабочей загрузки UM.
На высоком уровне существуют две разные модели дверных звонков, реализованные различными IHV и GPU.
Глобальные дверные звонки
В модели Global Doorbells все аппаратные очереди в контекстах и процессах совместно используют единый глобальный дверной сигнал. Значение, записанное в doorbell, сообщает планировщику GPU о том, какая конкретная очередь оборудования и движок имеют новую работу. Оборудование GPU использует механизм опроса для получения заданий, если несколько аппаратных очередей активно отправляют задания и сигнализируют на тот же глобальный сигнал.
Специализированные звонки
В выделенной модели сигнала каждая очередь аппаратного обеспечения назначается своим собственным сигналом, который активируется при добавлении новой задачи в GPU. Когда звонит дверной звонок, планировщик GPU точно определяет, какая очередь оборудования отправила новую работу. Существуют ограниченные двери, которые совместно используются во всех аппаратных очередях, созданных на GPU. Если количество созданных аппаратных очередей превышает количество доступных дверных звонков, драйвер должен отключить дверной звонок более старой или наименее недавно использованной аппаратной очереди и назначить его только что созданной очереди, эффективно "виртуализируя" дверные звонки.
Выявление поддержки отправки на уровне пользователя
DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported
Для GPU-узлов, поддерживающих функцию отправки задач в пользовательском режиме (UM), параметр KMD DxgkDdiGetNodeMetadata устанавливает флаг метаданных узла UserModeSubmissionSupported, добавляемый в DXGK_NODEMETADATA_FLAGS. Затем ОС позволяет UMD создавать очереди HWQueues и дверные звонки режима пользователя только на узлах, для которых установлен этот флаг.
DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS
Чтобы запросить сведения, специфичные для дверного звонка, ОС вызывает функцию DxgkDdiQueryAdapterInfo с запросом типа сведений об адаптере DXGKQAITYPE_USERMODESUBMISSION_CAPS. KMD отвечает, заполняя структуру DXGK_USERMODESUBMISSION_CAPS сведениями о поддержке отправки работы в пользовательском режиме.
В настоящее время единственным требуемым ограничением является размер памяти дверного сигнала (в байтах). Dxgkrnl требуется знать размер памяти doorbell по нескольким причинам:
- Во время создания doorbell (D3DKMTCreateDoorbell), Dxgkrnl возвращает адрес DoorbellCpuVirtualAddress на UMD. Прежде чем это сделать, Dxgkrnl сначала необходимо сопоставить с фиктивной страницей, потому что дверной звонок пока не назначен и не соединен. Размер дверного звонка необходим для выделения фиктивной страницы.
- Во время подключения doorbell (D3DKMTConnectDoorbell), Dxgkrnl необходимо повернуть DoorbellCpuVirtualAddress на DoorbellPhysicalAddress , предоставляемый KMD. Опять же, Dxgkrnl необходимо знать размер дверного звонка.
D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission в D3DKMTCreateHwQueue
UMD задает флаг UserModeSubmission, добавленный в D3DDDI_CREATEHWQUEUEFLAGS для создания HWQueues, использующих модель отправки в пользовательском режиме. HWQueues, созданные с помощью этого флага, не могут использовать стандартный путь отправки в режиме ядра и должны полагаться на механизм вызова для подачи работы в очереди.
API отправки задач в режиме пользователя
Следующие API пользовательского режима добавлены для поддержки работы в пользовательском режиме.
D3DKMTCreateDoorbell создает сигнализатор для D3D HWQueue для отправки заданий в пользовательском режиме.
D3DKMTConnectDoorbell подключает ранее созданный дверной звонок к D3D HWQueue для выполнения задач в пользовательском режиме.
D3DKMTDestroyDoorbell уничтожает ранее созданный сигнал вызова.
D3DKMTNotifyWorkSubmission уведомляет KMD о том, что новая работа была отправлена на HWQueue. Суть этой функции заключается в том, что она обеспечивает путь отправки заданий с низкой задержкой, где KMD не участвует и не осведомлен об отправке. Этот API полезен в сценариях, когда KMD необходимо получать уведомления при отправке работы на HWQueue. Драйверы должны использовать этот механизм в специфичных и редких сценариях, так как он включает в себя обмен между UMD и KMD при каждом запросе на выполнение, таким образом, устраняя преимущество модели отправки в режиме пользователя с низкой задержкой.
Модель расположения памяти для звонкового регистра и выделения кольцевого буфера
- UMD отвечает за обеспечение резидентности выделений кольцевого буфера и управления кольцевым буфером перед созданием сигнала.
- UMD управляет жизненным циклом кольцевого буфера и распределением контролей кольцевого буфера. Dxgkrnl не будет уничтожать эти выделения неявно, даже если соответствующий звонок будет разрушен. UMD отвечает за выделение и уничтожение этих выделений. Тем не менее, чтобы предотвратить уничтожение этих выделений вредоносной пользовательской программой, пока дверной звонок активен, Dxgkrnl держит ссылку на них в течение всего времени функционирования дверного звонка.
- Единственный сценарий, в котором Dxgkrnl удаляет выделенные буферы кольцевой очереди, — это завершение работы устройства. Dxgkrnl уничтожает все HWQueues, doorbells и выделенные кольцевые буферы, связанные с устройством.
- До тех пор, пока выделения кольцевого буфера существуют, CPUVA кольцевого буфера всегда действителен и доступен для UMD независимо от состояния соединений с сигнальным устройством. То есть, место расположения буфера кольца не привязано к двери.
- Когда KMD делает обратный вызов DXG, чтобы отключить doorbell (т. е. вызывает DxgkCbDisconnectDoorbell с состоянием D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), Dxgkrnl перенаправляет CPUVA doorbell на фиктивную страницу. Он не вытесняет или не отображает выделение кольцевого буфера.
- В случае любых сценариев потери устройства (TDR/GPU Stop/Page и т. д.), Dxgkrnl отключает звонок и обозначает состояние как D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. Пользовательский режим отвечает за уничтожение HWQueue, doorbell, кольцевого буфера и повторного их создания. Это требование аналогично уничтожению и повторному созданию других ресурсов устройства в этом сценарии.
Приостановка аппаратного контекста
При приостановке аппаратного контекста dxgkrnl сохраняет подключение с помощью механизма doorbell активным и область выделения кольцевого буфера (очереди задач). Таким образом, UMD может продолжать ставить задачи в очередь в контексте; просто эти задачи не выполняются, пока контекст приостановлен. После возобновления и планирования контекста процессор управления контекстом GPU (CMP) отслеживает новый указатель записи и выполнение задач.
Эта логика аналогична текущей логике отправки в режиме ядра, где UMD может вызывать D3DKMTSubmitCommand с приостановленным контекстом. Dxgkrnl ставит эту новую команду в очередь на HwQueue, но она просто не выполняется до более позднего времени.
Следующая последовательность событий возникает во время приостановки и возобновления работы контекста оборудования.
Приостановка аппаратного контекста:
- Dxgkrnl вызывает DxgkddiSuspendContext.
- KMD удаляет все аппаратные очереди контекста из списка аппаратного планировщика.
- Doorbells по-прежнему подключены, а выделение буфера или кольцевого буфера по-прежнему находится в резидентном режиме. UMD может записывать новые команды в HWQueue этого контекста, но GPU не обработает их, что аналогично сегодняшней отправке команд в режиме ядра в приостановленном контексте.
- Если KMD решит подвергнуть воздействию дверной звонок приостановленного HWQueue, то UMD теряет свое соединение. UMD может попытаться повторно подключить дверной звонок, и KMD назначит новый дверной звонок для этой очереди. Намерение заключается в том, чтобы не останавливать UMD, а наоборот, позволить ему продолжать отправлять работу, которую модуль HW сможет обработать в будущем после возобновления контекста.
Возобновление аппаратного контекста:
- Dxgkrnl вызывает DxgkddiResumeContext.
- KMD добавляет все HWQueues контекста в список планировщика HW.
Переходы состояний F-уровня процессора
В традиционной отправке в режиме ядра dxgkrnl отвечает за отправку новых команд в HWQueue и мониторинг прерываний от KMD. По этой причине Dxgkrnl имеет полное представление о том, когда движок активен и бездействует.
В режиме пользовательской отправки Dxgkrnl контролирует, делает ли двигатель GPU прогресс, используя интервалы ожидания TDR. Поэтому, если имеет смысл инициировать переход к состоянию F1 раньше, чем в пределах двухсекундного интервала ожидания TDR, KMD может запросить операционную систему о проведении этого.
Для упрощения этого подхода были внесены следующие изменения:
Тип прерывания DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE добавляется в DXGK_INTERRUPT_TYPE. KMD использует это прерывание для уведомления Dxgkrnl о переходе состояния двигателя, для которых требуется действие питания GPU или восстановление времени ожидания, например Active -> TransitionToF1 и Active -> Hung.
Структура данных прерывания EngineStateChange добавляется в DXGKARGCB_NOTIFY_INTERRUPT_DATA.
Перечисление DXGK_ENGINE_STATE было добавлено для представления переходов состояния движка для EngineStateChange.
Когда KMD вызывает прерывание DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE с помощью EngineStateChange.NewState , установлено значение DXGK_ENGINE_STATE_TRANSITION_TO_F1, Dxgkrnl отключает все двери HWQueues на этом двигателе, а затем инициирует переход компонента питания F0 на F1.
Когда UMD пытается отправить новую работу в движок GPU в состоянии F1, необходимо повторно подключить дверной звонок, что, в свою очередь, приводит к тому, что Dxgkrnl инициирует переход обратно в состояние питания F0.
Переходы состояния D-уровня двигателя
Во время перехода состояния питания устройства D0 на D3 Dxgkrnl приостанавливает HWQueue, отключает сигнал оповещения (перенаправляя doorbell CPUVA на фиктивную страницу) и обновляет состояние DoorbellStatusCpuVirtualAddress до D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
Если UMD вызывает D3DKMTConnectDoorbell, когда GPU находится в D3, это заставляет Dxgkrnl пробуждать GPU в D0. Dxgkrnl также отвечает за возобновление HWQueue и перенаправление ЦПВА дверного звонка на физическое местоположение этого звонка.
Происходит следующая последовательность событий.
Происходит отключение питания GPU из состояния D0 в состояние D3.
- Dxgkrnl вызывает DxgkddiSuspendContext для всех контекстов HW на GPU. KMD удаляет эти контексты из списка планировщика аппаратного обеспечения.
- Dxgkrnl отключает все дверные звонки.
- Dxgkrnl может вытеснять все выделения кольцевого буфера из VRAM при необходимости. Это делается после приостановки и удаления всех контекстов из списка планировщика оборудования, чтобы оборудование не ссылалось на вытесненную память.
UMD записывает новую команду в HWQueue, когда GPU находится в состоянии D3:
- UMD определяет, что дверной звонок отключен, поэтому вызывает D3DKMTConnectDoorbell.
- Dxgkrnl инициирует переход D0.
- Dxgkrnl делает все выделения кольцевого буфера/управления кольцевым буфером резидентными, если они были вытеснены.
- Dxgkrnl вызывает функцию DXGkddiCreateDoorbell KMD, чтобы KMD сделали соединение с сигнализацией для этой HWQueue.
- Dxgkrnl вызывает DxgkddiResumeContext для всех HWContexts. KMD добавляет соответствующие очереди в список планировщика аппаратного обеспечения.
DDIs для отправки заданий в пользовательском режиме
Реализованные KMD DDI-интерфейсы
Следующие DD в режиме ядра добавляются для KMD для реализации поддержки отправки в режиме пользователя.
DxgkDdiCreateDoorbell. Когда UMD вызывает D3DKMTCreateDoorbell для создания двери для HWQueue, Dxgkrnl делает соответствующий вызов этой функции, чтобы KMD мог инициализировать свои структуры двери.
DxgkDdiConnectDoorbell. Когда UMD вызывает D3DKMTConnectDoorbell, Dxgkrnl выполняет соответствующий вызов этой функции, чтобы KMD мог предоставить виртуальный адрес процессора (CPUVA), сопоставленный с физическим расположением дверного звонка, а также обеспечить необходимые подключения между объектом HWQueue, объектом дверного звонка, его физическим адресом, планировщиком GPU и т. д.
DxgkDdiDisconnectDoorbell. Когда ОС хочет отключить определенный звонок, она вызывает KMD с этим DDI.
DxgkDdiDestroyDoorbell. Когда UMD вызывает D3DKMTDestroyDoorbell, Dxgkrnl делает соответствующий вызов этой функции, чтобы KMD мог уничтожить свои структуры звонка.
DxgkDdiNotifyWorkSubmission. Когда UMD вызывает D3DKMTNotifyWorkSubmission, Dxgkrnl делает соответствующий вызов этой функции, чтобы KMD можно было получать уведомления о новых рабочих отправках.
Dxgkrnl-реализованный DDI
Обратный вызов DxgkCbDisconnectDoorbell реализуется Dxgkrnl. KMD может вызвать эту функцию, чтобы уведомить Dxgkrnl, что KMD нужно отключить определенный дверной звонок.
Изменения барьера прогресса очереди оборудования
Аппаратные очереди, работающие в модели отправки работы UM, по-прежнему имеют концепцию постоянно увеличивающегося значения ограждения прогресса, которое UMD создает и записывает при завершении буфера команд. Чтобы dxgkrnl знал, имеет ли определенная аппаратная очередь незавершенные задачи, UMD необходимо обновить значение ограждения выполнения очереди непосредственно перед добавлением нового командного буфера в кольцевой буфер и предоставлением его видимости для GPU. CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress — процесс сопоставления с поддержкой чтения и записи в режиме пользователя для последнего размещенного значения.
Важно убедиться, что значение очереди обновляется прямо перед тем, как новая отправка становится видимой для GPU. Ниже приведена рекомендуемая последовательность операций. Предполагается, что аппаратная очередь неактивна, а последний готовый буфер имел значение барьера хода выполнения N.
- Создайте новое значение барьера прогресса N+1.
- Заполните буфер команд. Последняя инструкция буфера команд — это запись значения прогрессирующего барьера в N+1.
- Сообщите ОС о новом значении очереди, установив *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) == N+1.
- Сделайте буфер команды видимым для GPU, добавив его в кольцевой буфер.
- Позвонить в дверь.
Нормальное и ненормальное завершение процесса
Следующая последовательность событий происходит во время нормального завершения процесса.
Для каждого HWQueue устройства или контекста:
- Dxgkrnl вызывает DxgkDdiDisconnectDoorbell, чтобы отключить звонок.
- Dxgkrnl ожидает завершения последней очереди HwQueueProgressFenceLastQueuedValueCPUVirtualAddress на GPU. Выделение кольцевого буфера или кольцевого буфера остается резидентным.
- Ожидание Dxgkrnl удовлетворено, и теперь он может уничтожить выделения для буфера кольца и контроля буфера кольца, а также объекты звонка и очереди HW.
Следующая последовательность событий происходит во время ненормального завершения процесса.
Dxgkrnl помечает устройство как ошибочное.
Для каждого контекста устройства Dxgkrnl вызывает DxgkddiSuspendContext для приостановки контекста. Выделения кольцевого буфера или кольцевых буферов управления все еще находятся в памяти. KMD перехватывает контекст и удаляет его из списка выполнения HW.
Для каждого HWQueue контекста Dxglrnl:
a. Вызывает DxgkDdiDisconnectDoorbell, чтобы отключить звонок.
б. Уничтожает выделения кольцевого буфера и управления кольцевым буфером, а также объекты doorbell и HWQueue.
Примеры псевдокода
Псевдокод подачи работы в UMD
Следующий псевдокод является базовым примером модели, которую UMD следует использовать для создания и отправки работы в HWQueues с помощью API-интерфейсов doorbell. Рассмотрим hHWqueue1, который является дескриптором HWQueue, созданного с использованием существующего API D3DKMTCreateHwQueue с флагом UserModeSubmission.
// Create a doorbell for the HWQueue
D3DKMT_CREATE_DOORBELL CreateDoorbell = {};
CreateDoorbell.hHwQueue = hHwQueue1;
CreateDoorbell.hRingBuffer = hRingBufferAlloc;
CreateDoorbell.hRingBufferControl = hRingBufferControlAlloc;
CreateDoorbell.Flags.Value = 0;
NTSTATUS ApiStatus = D3DKMTCreateDoorbell(&CreateDoorbell);
if(!NT_SUCCESS(ApiStatus))
goto cleanup;
assert(CreateDoorbell.DoorbellCPUVirtualAddress!=NULL &&
CreateDoorbell.DoorbellStatusCPUVirtualAddress!=NULL);
// Get a CPUVA of Ring buffer control alloc to obtain write pointer.
// Assume the write pointer is at offset 0 in this alloc
D3DKMT_LOCK2 Lock = {};
Lock.hAllocation = hRingBufferControlAlloc;
ApiStatus = D3DKMTLock2(&Lock);
if(!NT_SUCCESS(ApiStatus))
goto cleanup;
UINT64* WritePointerCPUVirtualAddress = (UINT64*)Lock.pData;
// Doorbell created successfully. Submit command to this HWQueue
UINT64 DoorbellStatus = 0;
do
{
// first connect the doorbell and read status
ApiStatus = D3DKMTConnectDoorbell(hHwQueue1);
D3DDDI_DOORBELL_STATUS DoorbellStatus = *(UINT64*(CreateDoorbell.DoorbellStatusCPUVirtualAddress));
if(!NT_SUCCESS(ApiStatus) || DoorbellStatus == D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT)
{
// fatal error in connecting doorbell, destroy this HWQueue and re-create using traditional kernel mode submission.
goto cleanup_fallback;
}
// update the last queue progress fence value
*(CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) = new_command_buffer_progress_fence_value;
// write command to ring buffer of this HWQueue
*(WritePointerCPUVirtualAddress) = address_location_of_command_buffer;
// Ring doorbell by writing the write pointer value into doorbell address.
*(CreateDoorbell.DoorbellCPUVirtualAddress) = *WritePointerCPUVirtualAddress;
// Check if submission succeeded by reading doorbell status
DoorbellStatus = *(UINT64*(CreateDoorbell.DoorbellStatusCPUVirtualAddress));
if(DoorbellStatus == D3DDDI_DOORBELL_STATUS_CONNECTED_NOTIFY)
{
D3DKMTNotifyWorkSubmission(CreateDoorbell.hDoorbell);
}
} while (DoorbellStatus == D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY);
Поражение псевдокода doorbell в KMD
В следующем примере показано, как KMD может потребоваться виртуализировать и совместно использовать доступные дверные звонки между HWQueues на GPU, использующих выделенные дверные звонки.
Псевдокод функции KMD VictimizeDoorbell() :
- KMD решает, что логический дверной звонок
hDoorbell1подключенный кPhysicalDoorbell1, нужно вывести из строя и отключить. - KMD вызывает Dxgkrnl
DxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue).- Dxgkrnl перенаправляет UMD-видимый CPUVA дверного колокольчика на фиктивную страницу и обновляет значение состояния до D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
- KMD восстанавливает контроль и выполняет фактическое отключение или нанесение вреда.
- KMD наносит вред
hDoorbell1и отключает его отPhysicalDoorbell1. -
PhysicalDoorbell1доступен для использования
- KMD наносит вред
Теперь рассмотрим следующий сценарий:
Существует один физический дверной звонок в PCI BAR с CPUVA в режиме ядра, равным
0xfeedfeee. Объект doorbell, созданный для HWQueue, назначается этим физическим значением doorbell.HWQueue KMD Handle: hHwQueue1 Doorbell KMD Handle: hDoorbell1 Doorbell CPU Virtual Address: CpuVirtualAddressDoorbell1 => 0xfeedfeee // hDoorbell1 is mapped to 0xfeedfeee Doorbell Status CPU Virtual Address: StatusCpuVirtualAddressDoorbell1 => D3DDDI_DOORBELL_STATUS_CONNECTEDОС вызывает
DxgkDdiCreateDoorbellдля другойHWQueue2:HWQueue KMD Handle: hHwQueue2 Doorbell KMD Handle: hDoorbell2 Doorbell CPU Virtual Address: CpuVirtualAddressDoorbell2 => 0 // this doorbell object isn't yet assigned to a physical doorbell Doorbell Status CPU Virtual Address: StatusCpuVirtualAddressDoorbell2 => D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY // In the create doorbell DDI, KMD doesn't need to assign a physical doorbell yet, // so the 0xfeedfeee doorbell is still connected to hDoorbell1ОС вызывает
DxgkDdiConnectDoorbellнаhDoorbell2// KMD needs to victimize hDoorbell1 and assign 0xfeedfeee to hDoorbell2. VictimizeDoorbell(hDoorbell1); // Physical doorbell 0xfeedfeee is now free and can be used vfor hDoorbell2. // KMD makes required connections for hDoorbell2 with HW ConnectPhysicalDoorbell(hDoorbell2, 0xfeedfeee) return 0xfeedfeee // On return from this DDI, *Dxgkrnl* maps 0xfeedfeee to process address space CPUVA i.e: // CpuVirtualAddressDoorbell2 => 0xfeedfeee // *Dxgkrnl* updates hDoorbell2 status to connected i.e: // StatusCpuVirtualAddressDoorbell2 => D3DDDI_DOORBELL_STATUS_CONNECTED ``
Этот механизм не требуется, если GPU использует глобальные дверные звонки. Вместо этого в этом примере и hDoorbell1, и hDoorbell2 будут назначены одному и тому же 0xfeedfeee физическому дверному звонку.