Compartilhar via


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 garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Este artigo descreve o recurso de envio de trabalho do modo de usuário (UM) que ainda está em desenvolvimento a partir do Windows 11, versão 24H2 (WDDM 3.2). O envio de trabalho da UM permite que os aplicativos enviem trabalho para a GPU diretamente do modo de usuário com latência muito baixa. A meta é melhorar o desempenho de aplicativos que enviam cargas de trabalho pequenas com frequência para a GPU. Além disso, espera-se que o envio do modo de usuário beneficie significativamente esses aplicativos se eles estiverem em execução dentro de um contêiner ou VM (máquina virtual). Esse benefício ocorre porque o UMD (driver de modo de usuário) em execução na VM pode enviar diretamente o trabalho para a GPU sem precisar enviar uma mensagem para o host.

Drivers de IHV e hardware que dão suporte ao envio de trabalho no modo de usuário devem continuar a dar suporte simultaneamente ao modelo tradicional de envio de trabalho no modo kernel. Esse suporte é necessário para cenários como um convidado mais antigo que suporta apenas filas KM tradicionais em execução em um host mais recente.

Este artigo não discute a interoperabilidade do envio de UM com Flip/FlipEx. O envio de UM descrito neste artigo é limitado à classe de renderização somente/computação de cenários. O pipeline de apresentação continua sendo baseado no envio no modo kernel por enquanto, pois ele tem uma dependência de cercas monitoradas nativas. O design e a implementação de apresentações baseadas em submissão UM podem ser considerados uma vez que as cercas monitoradas nativas e a submissão UM para computação/renderização estejam totalmente implementadas. Portanto, os drivers devem dar suporte à submissão em modo de usuário por fila.

Campainhas

A maioria das gerações atuais ou futuras de GPUs que dão suporte ao agendamento de hardware também dão suporte ao conceito de um doorbell de GPU. Uma campainha é um mecanismo para indicar a um motor de GPU que o novo trabalho está em sua fila de espera. As campainhas normalmente são registradas no PCIe BAR (barra de endereços base) ou na memória do sistema. Cada IHV de GPU tem sua própria arquitetura que determina o número de campainhas, onde elas 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 da UM.

De forma geral, há dois modelos diferentes de campainhas implementados por diferentes IHV e GPUs:

  • Campainhas de alcance global

    No modelo Global Doorbells, todas as filas de hardware entre contextos e processos compartilham uma única campainha global. O valor gravado na campainha informa o agendador de GPU sobre qual fila de hardware e motor específicos têm trabalho disponível. O hardware de GPU usa uma forma de um mecanismo de sondagem para buscar trabalho se várias filas de hardware estiverem enviando trabalho ativamente e tocando a mesma campainha global.

  • Campainhas dedicadas

    No modelo de campainha dedicado, cada fila de hardware recebe sua própria campainha, que é acionada sempre que há um novo trabalho a ser enviado para a GPU. Quando uma campainha é tocada, o agendador de GPU sabe exatamente qual fila de hardware enviou um novo trabalho. Há sinalizadores limitados que são compartilhados entre 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 recentemente usada e atribuir sua campainha a uma fila recém-criada, efetivamente "virtualizando" as campainhas.

Descobrir o suporte ao envio de trabalho no modo de usuário

DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported

Para nodos de GPU que dão suporte ao recurso de envio de trabalhos de UM, o DxgkDdiGetNodeMetadata do KMD define o sinalizador de metadados de nó UserModeSubmissionSupported como adicionado ao DXGK_NODEMETADATA_FLAGS. Em seguida, o sistema operacional permite que a UMD crie filas de hardware de envio no modo de usuário e "doorbells" apenas nos nós onde esse sinalizador está ativado.

DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS

Para consultar informações específicas do sinalizador, 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, a única limitação necessária é o tamanho da memória da campainha (em bytes). Dxgkrnl precisa do tamanho da memória da campainha por alguns motivos:

  • Durante a criação da campainha (D3DKMTCreateDoorbell), Dxgkrnl retorna um DoorbellCpuVirtualAddress para UMD. Antes de fazer isso, 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 de campainha (D3DKMTConnectDoorbell), Dxgkrnl precisa girar o DoorbellCpuVirtualAddress para um DoorbellPhysicalAddress fornecido pelo KMD. Novamente, Dxgkrnl precisa saber o tamanho da campainha.

D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission em D3DKMTCreateHwQueue

A UMD define o sinalizador UserModeSubmission adicionado à D3DDDI_CREATEHWQUEUEFLAGS para a criação de HWQueues que utilizam o modelo de envio do modo de usuário. Os HWQueues criados usando esse sinalizador não podem usar o caminho de envio de trabalho do modo kernel regular e devem contar com o mecanismo de campainha para envio de trabalho na fila.

APIs de envio de trabalho no modo de usuário

As SEGUINTEs APIs de modo de usuário são adicionadas para dar suporte ao envio de trabalho no modo de usuário.

  • D3DKMTCreateDoorbell cria uma campainha para um HWQueue D3D para envio de trabalho no modo de usuário.

  • D3DKMTConnectDoorbell conecta uma campainha criada anteriormente a um HWQueue D3D para submissão de trabalho no modo de usuário.

  • D3DKMTDestroyDoorbell destrói uma campainha criada anteriormente.

  • D3DKMTNotifyWorkSubmission notifica a KMD de que um novo trabalho foi enviado em um HWQueue. O ponto desse recurso é um caminho de baixa latência para envio de trabalho. O KMD não está envolvido nem ciente quando o trabalho é enviado. Essa API é útil em cenários em que o KMD precisa ser notificado sempre que o trabalho é submetido em um HWQueue. Os drivers devem usar esse mecanismo em cenários específicos e pouco frequentes, pois ele envolve uma comunicação entre a UMD e a KMD a cada envio de trabalho, comprometendo assim a finalidade de um modelo de envio de modo de usuário de baixa latência.

Modelo de residência de alocações de buffer de toque e memória de campainha

  • A UMD é responsável por tornar as alocações de controle de buffer de anel e anel residentes antes de criar uma campainha.
  • O UMD gerencia o ciclo de vida do buffer de anel e das alocações de controle do buffer de anel. Dxgkrnl não destruirá essas alocações implicitamente mesmo que a campainha correspondente seja destruída. A UMD é responsável por alocar e destruir essas alocações. No entanto, para impedir que um programa mal-intencionado no modo de usuário destrua essas alocações enquanto o doorbell está ativo, o Dxgkrnl mantém uma referência a elas durante o tempo de vida do doorbell.
  • O único cenário em que Dxgkrnl destrói alocações de buffer de anel é durante o encerramento do dispositivo. O Dxgkrnl destrói todas as alocações de HWQueues, campainhas e buffer de anel associadas ao dispositivo.
  • Desde que as alocações de buffer de anel estejam ativas, o CPUVA do buffer de anel sempre será válido e estará disponível para acesso da UMD, independentemente do status das conexões de campainha. Ou seja, a residência do buffer de anéis não está ligada à campainha.
  • Quando o KMD aciona o mecanismo de retorno de chamada DXG para desconectar uma campainha (ou seja, chama DxgkCbDisconnectDoorbell com o status D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), o Dxgkrnl realoca a CPUVA da campainha para uma página fictícia. Ele não remove nem desmapear as alocações do buffer de anel.
  • No caso de cenários perdidos pelo dispositivo (TDR/GPU Stop/Page, etc.), Dxgkrnl desconecta a campainha e marca o status como D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. O modo de usuário é responsável por destruir o HWQueue, doorbell, ring buffer e por recriá-los. Esse requisito é semelhante à forma como outros recursos de 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 de alarme ativa e o buffer circular (fila de trabalho) residente. Dessa forma, a UMD pode continuar a enfileirar o trabalho no contexto; esse trabalho simplesmente não é programado enquanto o contexto está suspenso. Depois que o contexto é retomado e agendado, o CMP (processador de gerenciamento de contexto) da GPU observa o novo ponteiro de gravação e o envio de trabalho.

Essa lógica é semelhante à lógica de envio do modo kernel atual, em que a UMD pode chamar D3DKMTSubmitCommand com um contexto suspenso. Dxgkrnl enfileira este novo comando na HwQueue, mas ele simplesmente não é agendado até um momento posterior.

A sequência de eventos a seguir ocorre durante a suspensão e retomada do contexto de hardware.

  • Suspendendo um contexto de hardware:

    1. Dxgkrnl chama DxgkddiSuspendContext.
    2. O KMD remove todos os HWQueues do contexto da lista do agendador HW.
    3. As campainhas ainda estão conectadas e as alocações de controle de buffer de anel/anel ainda estão residentes. O UMD pode gravar novos comandos no HWQueue desse contexto, mas a GPU não os processará, o que é semelhante ao envio de comando do modo kernel de hoje para um contexto suspenso.
    4. Se o KMD escolher vitimar o sinalizador de um HWQueue suspenso, a 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 permitir que ele continue enviando trabalhos que o mecanismo HW possa eventualmente processar depois que o contexto for retomado.
  • Restaurando um contexto de hardware

    1. Dxgkrnl chama DxgkddiResumeContext.
    2. O KMD adiciona todos os HWQueues do contexto à lista do agendador HW.

Transições de estado F do motor

No envio de trabalho do modo kernel tradicional, o Dxgkrnl é responsável por enviar novos comandos para o HWQueue e monitorar interrupções de conclusão do KMD. Por esse motivo, dxgkrnl tem uma visão completa de quando um mecanismo está ativo e ocioso.

No envio de trabalho no modo de usuário, Dxgkrnl monitora se um mecanismo de GPU está progredindo usando o intervalo de tempo limite de TDR. Portanto, se for vantajoso iniciar uma transição para o estado F1 antes do tempo limite de dois segundos do TDR, o KMD pode solicitar que o sistema operacional execute essa ação.

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 Dxgkrnl sobre transições de estado de motor que exigem uma ação de energia do GPU ou recuperação por tempo limite, como em Active -> TransitionToF1 e Active -> 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 motor para EngineStateChange.

Quando o KMD gera uma interrupção DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE com EngineStateChange.NewState definido como DXGK_ENGINE_STATE_TRANSITION_TO_F1, o Dxgkrnl desconecta todas as "doorbells" dos HWQueues no mecanismo e, em seguida, inicia a transição do componente de energia de F0 para F1.

Quando o UMD tenta enviar um novo trabalho para o mecanismo de GPU no estado F1, ele precisa reconectar a campainha, o que, por sua vez, faz com que dxgkrnl inicie uma transição de volta para o estado de energia F0.

Transições de estado D do motor

Durante uma transição de estado de energia do dispositivo D0 para D3, dxgkrnl suspende o HWQueue, desconecta a campainha (girando a CPUVA da campainha para uma página fictícia) e atualiza o status da campainha DoorbellStatusCpuVirtualAddress para D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.

Se a UMD chamar o D3DKMTConnectDoorbell quando a GPU estiver em D3, forçará Dxgkrnl a despertar a GPU para D0. Dxgkrnl também é responsável por retomar o HWQueue e ajustar a CPUVA da campainha para um local físico.

A sequência de eventos a seguir ocorre.

  • Ocorre uma queda de energia da GPU de D0 a D3:

    1. Dxgkrnl chama DxgkddiSuspendContext para todos os contextos HW na GPU. O KMD remove esses contextos da lista de agendadores do HW.
    2. Dxgkrnl desconecta todas as campainhas.
    3. O Dxgkrnl possivelmente remove todas as alocações de Buffer de Anel/Controle de Buffer de Anel da VRAM, se necessário. Ele faz isso depois que 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.
  • A UMD grava um novo comando em um HWQueue quando a GPU está no estado D3:

    1. A UMD detecta que a campainha está desconectada e, portanto, chama D3DKMTConnectDoorbell.
    2. Dxgkrnl inicia uma transição D0.
    3. Dxgkrnl torna todas as alocações de Buffer de Anel e Controle de Buffer de Anel residentes caso tenham sido removidas.
    4. Dxgkrnl chama a função DxgkddiCreateDoorbell do KMD para que o KMD estabeleça uma conexão de campainha para este HWQueue.
    5. Dxgkrnl chama DxgkddiResumeContext para todos os HWContexts. O KMD adiciona as filas correspondentes à lista do agendador HW.

DDIs para submissão de trabalhos no modo de usuário

DDIs implementados por KMD

Os seguintes DDIs do modo kernel são adicionados ao KMD para implementar o suporte ao envio de trabalho no modo de usuário.

  • DxgkDdiCreateDoorbell. Quando a UMD chama D3DKMTCreateDoorbell para criar uma campainha para um HWQueue, dxgkrnl faz uma chamada correspondente para essa função para que o KMD possa inicializar suas estruturas de campainha.

  • DxgkDdiConnectDoorbell. Quando a UMD chama D3DKMTConnectDoorbell, dxgkrnl faz uma chamada correspondente para essa função para que o KMD possa fornecer uma CPUVA mapeada para o local da campainha física e também faça as conexões necessárias entre o objeto HWQueue, o objeto doorbell, o endereço físico da campainha, o agendador de GPU e assim por diante.

  • DxgkDdiDisconnectDoorbell. Quando o sistema operacional deseja desconectar uma campainha específica, ele chama KMD com essa DDI.

  • DxgkDdiDestroyDoorbell. Quando a UMD chama D3DKMTDestroyDoorbell, o Dxgkrnl faz uma chamada correspondente para essa função, permitindo que o KMD destrua suas estruturas de "doorbell".

  • DxgkDdiNotifyWorkSubmission. Quando a UMD chama D3DKMTNotifyWorkSubmission, dxgkrnl faz uma chamada correspondente para essa função para que o KMD possa ser notificado sobre novos envios de trabalho.

DDI implementada por DXgkrnl

O callback DxgkCbDisconnectDoorbell é implementado por Dxgkrnl. O KMD pode chamar essa função para notificar Dxgkrnl que o KMD precisa desconectar um sinalizador específico.

Alterações na barreira de progresso da fila de hardware

As filas de hardware em execução no modelo de submissão de trabalho da UM ainda têm o conceito de um valor de progresso monotonicamente crescente, que o UMD gera e escreve quando um buffer de comando é concluído. Para que o Dxgkrnl saiba se uma fila de hardware específica tem trabalho pendente, o UMD precisa atualizar o valor da fence de progresso enfileirado pouco antes de acrescentar um novo buffer de comandos ao buffer circular e torná-lo visível para a GPU. CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress é um mapeamento de leitura/gravação no modo de usuário para o valor mais recente enfileirado.

É essencial para o UMD garantir que o valor enfileirado seja atualizado exatamente antes que a nova submissão seja visível para a GPU. As etapas a seguir são a sequência recomendada de operações. Eles pressupõem que a fila de hardware está ociosa e o último buffer concluído tinha um valor de barreira de progresso de N.

  • Gere um novo valor de barreira de progresso N+1.
  • Preencha o buffer de comando. A última instrução do buffer de comando é uma gravação de valor de barreira de progresso em N+1.
  • Informe o sistema operacional do valor recém-enfileirado definindo *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) igual a N+1.
  • Torne o buffer de comando visível à GPU adicionando-o ao buffer de anel.
  • Toque a campainha.

Término de processo normal e anormal

A sequência de eventos a seguir ocorre durante o encerramento normal do processo.

Para cada HWQueue do dispositivo/contexto:

  1. Dxgkrnl chama DxgkDdiDisconnectDoorbell para desconectar a campainha.
  2. Dxgkrnl aguarda a última fila HwQueueProgressFenceLastQueuedValueCPUVirtualAddress ser concluída na GPU. As alocações do Controle do Ring Buffer/Buffer de Anel permanecem residentes.
  3. A espera de Dxgkrnl foi satisfeita, e agora ele pode destruir as alocações do Buffer de Anel/Controle de Buffer de Anel, bem como os objetos doorbell e HWQueue.

A sequência de eventos a seguir ocorre durante o encerramento anormal do processo.

  1. Dxgkrnl marca o dispositivo em erro.

  2. Para cada contexto de dispositivo, Dxgkrnl chama DxgkddiSuspendContext para suspender o contexto. As alocações de controle de buffer de anel/buffer de anel ainda estão residentes. O KMD preempõe o contexto e o remove de sua lista de execuções do HW.

  3. Para cada HWQueue de contexto, Dxglrnl:

    a. Chama DxgkDdiDisconnectDoorbell para desconectar a campainha.

    b. Destrói as alocações do Buffer de Anel e do Controle do Buffer de Anel, além dos objetos doorbell e HWQueue.

Exemplos de pseudocódigo

Pseudocódigo de envio de trabalho no UMD

O pseudocódigo a seguir é um exemplo básico do modelo que UMD deve usar para criar e submeter tarefas para HWQueues usando as doorbell APIs. Considere hHWqueue1 como o handle de um HWQueue criado com o sinalizador UserModeSubmission utilizando 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);

Pseudocódigo de vitimização de campainha no KMD

O exemplo a seguir ilustra como o KMD pode precisar "virtualizar" e compartilhar as campainhas disponíveis entre os HWQueues em GPUs que usam campainhas dedicadas.

Pseudocódigo da função kmd VictimizeDoorbell() :

  • O KMD decide que a PhysicalDoorbell1 campainha hDoorbell1 lógica conectada precisa ser vitimada e desconectada.
  • KMD chama Dxgkrnl'sDxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue).
    • Dxgkrnl altera a CPUVA visível para UMD dessa campainha para uma página fictícia e atualiza o valor de status para D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
  • O KMD obtém o controle de volta e faz a vitimização/desconexão real.
    • O KMD vitimiza hDoorbell1 e desconecta-o de PhysicalDoorbell1.
    • PhysicalDoorbell1 está disponível para uso

Agora, considere o seguinte cenário:

  1. Há uma única campainha física na PCI BAR com uma CPUVA em modo kernel igual a 0xfeedfeee. Um objeto doorbell criado para um HWQueue recebe esse valor da doorbell 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
    
  2. O sistema operacional chama DxgkDdiCreateDoorbell para um diferente 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
    
  3. O sistema operacional chama DxgkDdiConnectDoorbell em 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
    ``
    
    

Esse mecanismo não será necessário se uma GPU usar sinalizadores globais. Em vez disso, neste exemplo, ambos hDoorbell1 e hDoorbell2 receberiam a mesma 0xfeedfeee campainha física.