Envio de trabalho no modo de usuário
Importante
Algumas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Este artigo descreve o recurso de envio de trabalho de modo de usuário (UM) que está em desenvolvimento desde o Windows 11, versão 24H2 (WDDM 3.2). O envio de trabalho no modo de usuário permite que os aplicativos enviem trabalhos para a GPU diretamente do modo de usuário com latência muito baixa. O objetivo é melhorar o desempenho de aplicativos que frequentemente enviam pequenas cargas de trabalho para a GPU. Além disso, espera-se que o envio no modo de usuário beneficie significativamente esses aplicativos se estiverem sendo executados dentro de um contêiner ou de uma máquina virtual (VM). Esse benefício ocorre porque o driver de modo de usuário (UMD) em execução na VM pode enviar trabalho diretamente para a GPU, sem precisar enviar uma mensagem para o host.
Os drivers IHV e o hardware que suportam o envio de trabalho no modo de usuário devem continuar suportando o modelo tradicional de envio de trabalho no modo kernel simultaneamente. Esse suporte é necessário em situações como, por exemplo, um convidado mais velho que oferece suporte apenas a filas KM tradicionais em execução em um host mais recente.
Este artigo não discute a interoperabilidade de envio no modo de usuário com o Flip/FlipEx. O envio no modo de usuário descrito neste artigo é limitado à classe de cenários de renderização/computação. O pipeline de apresentação continua a ser baseado no envio em modo kernel por enquanto, pois tem uma dependência de limites monitorados nativos. O projeto e a implementação da apresentação baseada no envio no modo de usuário podem ser considerados uma vez que os limites monitorados nativos e o envio no modo de usuário apenas para computação/renderização estejam totalmente implementados. Portanto, os drivers devem dar suporte ao envio do modo de usuário por fila.
Campainhas
A maioria das gerações atuais ou futuras de GPUs que suportam agendamento de hardware também suportam o conceito de uma campainha de GPU. Uma campainha é um mecanismo para indicar a um mecanismo de GPU que um novo trabalho está na fila de trabalho. As campainhas são normalmente registradas na barra PCIe (barra de endereços base) ou na memória do sistema. Cada GPU IHV tem sua própria arquitetura que determina o número de campainhas, onde estão localizadas no sistema e assim por diante. O sistema operacional Windows usa campainhas como parte de seu design para implementar o envio de trabalho no modo de usuário.
Em alto nível, existem dois modelos de campainhas implementadas por diferentes IHV e GPUs:
Campainhas globais
No modelo Campainhas globais, todas as filas de hardware em contextos e processos compartilham uma única campainha global. O valor gravado na campainha informa ao agendador da GPU qual fila e mecanismo de hardware específico tem um novo trabalho. O hardware da GPU usa a forma de um mecanismo de sondagem para buscar trabalho caso várias filas de hardware estejam enviando trabalho ativamente e tocando a mesma campainha global.
Campainhas dedicadas
No modelo de campainha dedicada, a cada fila de hardware é atribuída sua própria campainha, a qual é tocada sempre que há um novo trabalho para submeter à GPU. Quando uma campainha é tocada, o agendador da GPU sabe exatamente qual fila de hardware enviou o novo trabalho. Há campainhas limitadas que são compartilhadas em todas as filas de hardware criadas na GPU. Se o número de filas de hardware criadas exceder o número de campainhas disponíveis, o driver precisará desconectar a campainha de uma fila de hardware mais antiga ou menos usada recentemente e atribuir sua campainha a uma fila recém-criada, efetivamente "virtualizando" campainhas.
Descobrindo o suporte ao envio de trabalho no modo de usuário
DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported
Para nós da GPU que oferecem suporte ao recurso de envio de trabalho no modo de usuário, o DxgkDdiGetNodeMetadata do KMD define o sinalizador de metadados do nó UserModeSubmissionSupported que é adicionado ao DXGK_NODEMETADATA_FLAGS. Em seguida, o sistema operacional permite que o UMD crie HWQueues de envio no modo de usuário e campainhas apenas em nós para os quais esse sinalizador está definido.
DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS
Para consultar informações específicas da campainha, o sistema operacional chama a função DxgkDdiQueryAdapterInfo do KMD com o tipo de informações do adaptador de consulta DXGKQAITYPE_USERMODESUBMISSION_CAPS. O KMD responde preenchendo uma estrutura DXGK_USERMODESUBMISSION_CAPS com seus detalhes de suporte para envio de trabalho no modo de usuário.
Atualmente, o único limite necessário é o tamanho da memória da campainha (em bytes). O Dxgkrnl precisa do tamanho da memória da campainha por alguns motivos:
- Durante a criação da campainha (D3DKMTCreateDoorbell), o Dxgkrnl retorna um DoorbellCpuVirtualAddress para o UMD. Antes de fazer isso, o Dxgkrnl primeiro precisa mapear internamente para uma página fictícia porque a campainha ainda não está atribuída e conectada. O tamanho da campainha é necessário para alocar a página fictícia.
- Durante a conexão da campainha (D3DKMTConnectDoorbell), o Dxgkrnl precisa girar o DoorbellCpuVirtualAddress para um DoorbellPhysicalAddress fornecido pelo KMD. Novamente, o Dxgkrnl precisa saber o tamanho da campainha.
D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission em D3DKMTCreateHwQueue
O UMD define o sinalizador UserModeSubmission adicionado ao D3DDDI_CREATEHWQUEUEFLAGS para criar HWQueues que usam o modelo de envio no modo de usuário. Os HWQueues criados usando esse sinalizador não podem usar o caminho de envio de trabalho no modo kernel regular e necessitam do mecanismo de campainha para envio de trabalho na fila.
APIs de envio de trabalho no modo de usuário
As APIs de modo de usuário a seguir são adicionadas para dar suporte ao envio de trabalho no modo de usuário.
A D3DKMTCreateDoorbell cria uma campainha para um D3D HWQueue para envio de trabalho no modo de usuário.
A D3DKMTConnectDoorbell conecta uma campainha criada anteriormente a um D3D HWQueue para envio de trabalho no modo de usuário.
A D3DKMTDestroyDoorbell destrói uma campainha criada anteriormente.
A D3DKMTNotifyWorkSubmission notifica o KMD de que um novo trabalho foi enviado em um HWQueue. O objetivo desse recurso é um caminho de submissão de trabalho de baixa latência, onde o KMD não está envolvido ou ciente sobre quando o trabalho é enviado. Essa API é útil nos cenários em que o KMD precisa ser notificado sempre que um trabalho é enviado em um HWQueue. Os drivers devem usar esse mecanismo em cenários específicos e pouco frequentes, pois envolve uma viagem de ida e volta do UMD ao KMD em cada envio de trabalho, acabando com o propósito de um modelo de envio no modo de usuário de baixa latência.
Modelo de residência de alocações de memória de campainha e buffer de anel
- O UMD é responsável por tornar as alocações de controle de buffer de anel e de toque residentes antes de criar uma campainha.
- O UMD gerencia a vida útil e as alocações de controle do buffer de anel. O Dxgkrnl não destruirá essas alocações implicitamente, mesmo que a campainha correspondente seja destruída. O UMD é responsável por alocar e destruir essas alocações. No entanto, para evitar que um programa malicioso no modo de usuário destrua essas alocações enquanto a campainha está ativa, o Dxgkrnl faz uma referência a eles durante a vida útil da campainha.
- O único cenário em que o Dxgkrnl destrói alocações de buffer de anel é durante a terminação do dispositivo. O Dxgkrnl destrói todos os HWQueues, campainhas e alocações de buffer de anel associadas ao dispositivo.
- Enquanto as alocações de buffer de anel estiverem ativas, o buffer de anel CPUVA é sempre válido e disponível para que o UMD acesse, independentemente do status das conexões da campainha. Ou seja, a residência do buffer de anel não está atrelada à campainha.
- Quando o KMD faz o retorno de chamada DXG para desconectar uma campainha (ou seja, chama DxgkCbDisconnectDoorbell com status D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), o Dxgkrnl gira a campainha CPUVA para uma página fictícia. Ele não remove ou desmapeia as alocações de buffer de anel.
- No caso de qualquer cenário de perda de dispositivo (Parada/Página de TDR/GPU etc.), o Dxgkrnl desconecta a campainha e marca seu status como D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. O modo de usuário é responsável por destruir o HWQueue, a campainha e o buffer de anel e por recriá-los. Esse requisito é semelhante a como outros recursos do dispositivo são destruídos e recriados nesse cenário.
Suspensão de contexto de hardware
Quando o sistema operacional suspende um contexto de hardware, o Dxgkrnl mantém a conexão da campainha ativa e a alocação do buffer de anel (fila de trabalho) residente. Desta forma, a UMD pode continuar a trabalhar na fila para o contexto, esse trabalho não é agendado enquanto o contexto está suspenso. Depois que o contexto é retomado e agendado, o processador de gerenciamento de contexto (CMP) da GPU observa o novo ponteiro de gravação e o envio do trabalho.
Essa lógica é semelhante à de envio no modo kernel atual, onde o UMD pode chamar o D3DKMTSubmitCommand com um contexto suspenso. O Dxgkrnl enfileira esse novo comando para o HwQueue, mas ele não é agendado até mais tarde.
A sequência de eventos a seguir ocorre durante a suspensão e retomada do contexto de hardware.
Suspender um contexto de hardware:
- O Dxgkrnl chama o DxgkddiSuspendContext.
- O KMD remove todos os HWQueues do contexto da lista do agendador de HW.
- As campainhas ainda estão conectadas e as alocações do buffer de anel/de controle do buffer de anel ainda são residentes. O UMD pode escrever novos comandos para o HWQueue desse contexto, mas a GPU não os processará, o que é semelhante ao envio de comandos no modo kernel atual para um contexto suspenso.
- Se o KMD optar por vitimizar a campainha de um HWQueu suspenso, o UMD perderá sua conexão. O UMD pode tentar reconectar a campainha e o KMD atribuirá uma nova campainha a essa fila. A intenção é não paralisar o UMD, mas sim permitir que ele continue enviando trabalhos que o mecanismo HW possa eventualmente processar assim que o contexto for retomado.
Retomar um contexto de hardware:
- O Dxgkrnl chama o DxgkddiResumeContext.
- O KMD adiciona todos os HWQueues do contexto à lista do agendador de HW.
Transições de estado F do mecanismo
No envio de trabalho no modo kernel tradicional, o Dxgkrnl é responsável por enviar novos comandos para o HWQueue e monitorar as interrupções de conclusão do KMD. Por esta razão, o Dxgkrnl possui uma visão completa de quando um mecanismo está ativo e ocioso.
No envio de trabalho no modo de usuário, o Dxgkrnl monitora se um mecanismo da GPU está progredindo usando a cadência de tempo limite do TDR, portanto, se vale a pena iniciar uma transição para o estado F1 mais rápido do que o tempo limite do TDR de dois segundos, o KMD pode solicitar que o sistema operacional faça isso.
As seguintes alterações foram feitas para facilitar essa abordagem:
O tipo de interrupção DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE é adicionado ao DXGK_INTERRUPT_TYPE. O KMD usa essa interrupção para notificar o Dxgkrnl sobre transições de estado do mecanismo que exigem uma ação de energia da GPU ou uma recuperação de tempo limite, como Ativo -> TransitionToF1 e Ativo -> Hung.
A estrutura de dados de interrupção EngineStateChange é adicionada ao DXGKARGCB_NOTIFY_INTERRUPT_DATA.
A enumeração DXGK_ENGINE_STATE é adicionada para representar as transições de estado do mecanismo para EngineStateChange.
Quando o KMD gera uma interrupção DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE com o EngineStateChange.NewState definido como DXGK_ENGINE_STATE_TRANSITION_TO_F1, o Dxgkrnl desconecta todas as campainhas de HWQueues neste mecanismo e, em seguida, inicia uma transição de componente de potência F0 para F1.
Quando o UMD tenta enviar um novo trabalho para o mecanismo da GPU no estado F1, ele precisa reconectar a campainha, o que faz com que o Dxgkrnl inicie uma transição de volta para o estado de potência F0.
Transições de estado D do mecanismo
Durante uma transição de estado de energia do dispositivo D0 para D3, o Dxgkrnl suspende o HWQueue, desconecta a campainha (girando a campainha CPUVA para uma página fictícia) e atualiza o status da campainha DoorbellStatusCpuVirtualAddress para D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
Se o UMD chamar o D3DKMTConnectDoorbell quando a GPU estiver em D3, ele forçará o Dxgkrnl a ativar a GPU para D0. O Dxgkrnl também é responsável por retomar o HWQueue e girar a campainha CPUVA para um local físico de campainha.
A sequência de eventos a seguir ocorre.
Ocorre uma desativação da GPU D0 a D3:
- O Dxgkrnl chama o DxgkddiSuspendContext para todos os contextos de HW na GPU. O KMD remove esses contextos da lista do agendador de HW.
- O Dxgkrnl desconecta todas as campainhas.
- O Dxgkrnl possivelmente remove todas as alocações do buffer de anel/de controle do buffer de anel da VRAM, se necessário. Ele faz isso quando todos os contextos são suspensos e removidos da lista do agendador de hardware para que o hardware não faça referência a nenhuma memória removida.
O UMD grava um novo comando para um HWQueue quando a GPU está no estado D3:
- O UMD vê que a campainha está desconectada e chama o D3DKMTConnectDoorbell.
- O Dxgkrnl inicia uma transição D0.
- O Dxgkrnl torna residentes todas as alocações do buffer de anel/de controle do buffer de anel se elas forem removidas.
- O Dxgkrnl chama a função DxgkddiCreateDoorbell do KMD para solicitar que o KMD faça uma conexão de campainha para este HWQueue.
- O Dxgkrnl chama a DxgkddiResumeContext para todos os HWContexts. O KMD adiciona as filas correspondentes à lista do agendador de HW.
DDIs para envio de trabalho no modo de usuário
DDIs implementadas pelo KMD
As seguintes DDIs de modo kernel são adicionadas para que o KMD implemente o suporte de envio de trabalho no modo de usuário.
DxgkDdiCreateDoorbell. Quando o UMD chama a D3DKMTCreateDoorbell para criar uma campainha para um HWQueue, o Dxgkrnl faz uma chamada correspondente para essa função, assim o KMD pode inicializar suas estruturas de campainha.
DxgkDdiConnectDoorbell. Quando o UMD chama a D3DKMTConnectDoorbell, o Dxgkrnl faz uma chamada correspondente a essa função para que o KMD forneça uma CPUVA mapeada para o local físico da campainha e também faça as conexões necessárias entre o objeto HWQueue, objeto da campainha, endereço físico da campainha, agendador da GPU e assim por diante.
DxgkDdiDisconnectDoorbell. Quando o sistema operacional deseja desconectar uma campainha específica, ele chama o KMD com essa DDI.
DxgkDdiDestroyDoorbell. Quando o UMD chama a D3DKMTDestroyDoorbell, o Dxgkrnl faz uma chamada correspondente a essa função para que o KMD destrua suas estruturas de campainha.
DxgkDdiNotifyWorkSubmission. Quando o UMD chama a D3DKMTNotifyWorkSubmission, o Dxgkrnl faz uma chamada correspondente a essa função para que o KMD seja notificado sobre novos envios de trabalho.
DDI implementada pelo Dxgkrnl
O retorno de chamada DxgkCbDisconnectDoorbell é implementado pelo Dxgkrnl. O KMD pode chamar essa função para notificar o Dxgkrnl de que o KMD precisa desconectar uma campainha específica.
Alterações no limite de progresso da fila de HW
As filas de hardware em execução no modelo de envio de trabalho no modo de usuário ainda têm um conceito de um valor de limite de progresso monotonicamente crescente que o UMD gera e grava quando um buffer de comando é concluído. Para que o Dxgkrnl saiba se uma fila de hardware específica possui trabalho pendente, o UMD precisa atualizar o valor do limite de progresso da fila antes de anexar um novo buffer de comando ao buffer de anel e torná-lo visível para a GPU. CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress é um mapeamento de processo de leitura/gravação no modo de usuário do valor da fila mais recente.
É essencial que o UMD garanta que o valor enfileirado seja atualizado antes que o novo envio fique visível para a GPU. As etapas a seguir são a sequência recomendada de operações. Assumem que a fila de HW está ociosa e o último buffer concluído tinha um valor de limite de progresso de N.
- Gerar um novo valor N+1 de valor de limite de progresso.
- Preencha o buffer de comandos. A última instrução do buffer de comando é um valor de limite de progresso escrito como N+1.
- Informe ao sistema operacional o valor recém-enfileirado definindo *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) igual a N+1.
- Torne o buffer de comando visível para a GPU adicionando-o ao buffer de anel.
- Toque a campainha.
Terminação normal e anormal do processo
A sequência de eventos a seguir ocorre durante a terminação normal do processo.
Para cada HWQueue do dispositivo/contexto:
- O Dxgkrnl chama DxgkDdiDisconnectDoorbell para desconectar a campainha.
- O Dxgkrnl aguarda que o último HwQueueProgressFenceLastQueuedValueCPUVirtualAddress enfileirado seja concluído na GPU. As alocações de buffer de anel/controle de buffer de anel permanecem residentes.
- A espera do Dxgkrnlterminou e agora ele pode destruir as alocações do buffer de anel/controle de buffer de anel e os objetos de campainha e HWQueue.
A sequência de eventos a seguir ocorre durante a terminação anormal do processo.
O Dxgkrnl marca o dispositivo com erro.
Para cada contexto de dispositivo, o Dxgkrnl chama DxgkddiSuspendContext para suspender o contexto. As alocações de buffer de anel/controle de buffer de anel ainda estão residentes. O KMD antecipa o contexto e o remove de sua lista de execução de HW.
Para cada HWQueue de contexto, i Dxglrnl:
a. Chama DxgkDdiDisconnectDoorbell para desconectar a campainha.
b. Destrói as alocações do buffer de anel/controle do buffer de anel e os objetos de HWQueue e de campainha.
Exemplos de pseudocódigo
Pseudocódigo de envio de trabalho no UMD
O pseudocódigo a seguir é um exemplo básico do modelo que o UMD deve usar para criar e enviar trabalhos para HWQueues usando as APIs de campainha. Considere que hHWqueue1
é o identificador para um HWQueue criado com o sinalizador UserModeSubmission
usando a API D3DKMTCreateHwQueue existente.
// 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);
Vitimizando o pseudocódigo de campainha no KMD
O exemplo a seguir ilustra como o KMD pode precisar "virtualizar" e compartilhar as campainhas disponíveis entre os HWQueues nas GPUs que usam campainhas dedicadas.
Pseudocódigo da função VictimizeDoorbell()
do KMD:
- O KMD decide que a campainha lógica
hDoorbell1
conectada aPhysicalDoorbell1
precisa ser vitimada e desconectada. - O KMD chama o
DxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue)
do Dxgkrnl.- O Dxgkrnl gira a CPUVA visível do UMD desta campainha para uma página fictícia e atualiza o valor de status para D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
- O KMD recupera o controle e faz a vitimização/desconexão real.
- O KMD vitimiza o
hDoorbell1
e o desconecta doPhysicalDoorbell1
. PhysicalDoorbell1
está disponível para uso
- O KMD vitimiza o
Agora, considere a situação a seguir:
Há uma única campainha física na PCI BAR com uma CPUVA de modo kernel igual a
0xfeedfeee
. Um objeto de campainha criado para um HWQueue recebe esse valor de campainha física.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
O sistema operacional chama
DxgkDdiCreateDoorbell
para umHWQueue2
diferente: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
O sistema operacional chama
DxgkDdiConnectDoorbell
nohDoorbell2
:// 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 ``
Esse mecanismo não é necessário se uma GPU usar campainhas globais. Em vez disso, neste exemplo, ambos hDoorbell1
e hDoorbell2
seriam atribuídos a mesma 0xfeedfeee
campainha física.