Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo descreve o objeto de barreira de sincronização de GPU que pode ser usado para sincronização efetiva de GPU para GPU na fase 2 do agendamento de hardware da GPU. Este recurso é suportado a partir do Windows 11, versão 24H2 (WDDM 3.2). Os desenvolvedores de drivers gráficos devem estar familiarizados com o WDDM 2.0 e o estágio 1 de agendamento de hardware da GPU.
Objeto de sincronização de barreira monitorada do WDDM 2.x
O objeto de sincronização de cerca monitorado do WDDM 2.x suporta as seguintes operações:
- A CPU aguarda em um valor de barreira monitorado, seja ele:
- Sondagem usando um endereço virtual (VA) da CPU.
- Enfileirar uma espera bloqueante dentro de Dxgkrnl, que é sinalizada assim que a CPU observa o novo valor da cerca monitorada.
- Sinal da CPU de um valor monitorado.
- Sinalização da GPU de um valor monitorado mediante a escrita na barreira monitorada GPU VA e o levantamento de uma interrupção sinalizada da barreira monitorada para notificar a CPU da atualização do valor.
O que não era suportado era uma espera nativa diretamente na GPU por um valor de barreira monitorado. Em vez disso, o sistema operacional manteve o trabalho da GPU que depende do valor esperado na CPU. Ele apenas liberou este trabalho para a GPU quando o valor foi sinalizado.
Adicionado objeto de sincronização de barreira nativo da GPU
A partir do WDDM 3.2, o objeto de cerca monitorado foi estendido para suportar os seguintes recursos adicionados:
- A GPU aguarda em um valor de cerca monitorado, o que permite a sincronização de alto desempenho entre mecanismos sem a necessidade de uma ida e volta da CPU.
- Notificação de interrupção condicional apenas para sinais de barreira de GPU que tenham esperas de CPU. Esse recurso permite economias substanciais de energia, permitindo que a CPU entre em um estado de baixo consumo de energia quando todo o trabalho da GPU está na fila.
- Armazenamento de valor de sincronização na memória local da GPU (em vez de na memória do sistema).
Design de objeto de sincronização de cerca nativo da GPU
O diagrama a seguir ilustra a arquitetura básica de um objeto de cerca nativo da GPU, com foco no estado do objeto de sincronização compartilhado entre a CPU e a GPU.
:
O diagrama inclui dois componentes principais:
Valor atual (referido como CurrentValue neste artigo). Este local de memória contém o valor de barreira de 64 bits atualmente sinalizado. CurrentValue é mapeado e acessível tanto pela CPU (gravável em modo kernel, legível em modo de utilizador e kernel) como pela GPU (legível e gravável usando o endereço virtual da GPU). CurrentValue requer gravações de 64 bits para serem atômicas do ponto de vista da CPU e da GPU. Ou seja, as atualizações para os 32 bits altos e baixos não podem ser rasgadas e devem ser visíveis ao mesmo tempo. Este conceito já está presente no objeto de cerca monitorado existente.
Valor monitorado (referido como MonitoredValue neste artigo). Este local de memória contém o valor menos aguardado atualmente pela CPU subtraído por um (1). MonitoredValue é mapeado e acessível tanto para a CPU (legível e gravável a partir do modo kernel, sem acesso ao modo de usuário) como para a GPU (legível usando o endereço virtual (VA) da GPU, sem acesso de gravação). O sistema operativo mantém a lista de processos de CPU em espera para um determinado objeto de cerca, e atualiza MonitoredValue à medida que os processos são adicionados e removidos. Quando não há esperas pendentes, o valor é atribuído a UINT64_MAX. Esse conceito é novo no objeto de sincronização de cerca nativo da GPU.
O diagrama seguinte ilustra como Dxgkrnl rastreia processos de CPU em espera em um valor de barreira monitorada específico. Mostra também o valor da cerca monitorada num determinado momento. CurrentValue e MonitoredValue são ambos 41, o que significa que:
- A GPU concluiu todas as tarefas até ao valor de barreira de 41.
- O processador não está esperando por nenhum valor de barreira menor ou igual a 41.
:
O diagrama a seguir ilustra que o processador de gerenciamento de contexto (CMP) da GPU gera condicionalmente uma interrupção da CPU somente se o novo valor de cerca for maior do que o valor monitorado. Uma tal interrupção significa que existem processos pendentes do CPU que podem ser atendidos com o novo valor escrito.
:
Quando a CPU processa essa interrupção, o Dxgkrnl executa as seguintes ações, conforme ilustrado no diagrama a seguir:
- Ele desbloqueia os esperadores da CPU que foram atendidos com a nova barreira escrita.
- Adianta o valor monitorado para corresponder ao menor valor pendente aguardado subtraído por 1.
:
Armazenamento de memória física para valores de cerca atuais e monitorados
Para um determinado objeto de cerca, CurrentValue e MonitoredValue são armazenados em locais separados.
Os objetos de barreira não compartilháveis têm armazenamento de valores de barreira para diferentes objetos de barreira dentro do mesmo processo, todos embalados na mesma página de memória. Os valores são embalados de acordo com os valores de passada especificados nas tampas KMD de cerca nativas descritas posteriormente neste artigo.
Os objetos de vedação que são partilháveis têm os seus valores atuais e monitorizados colocados em páginas de memória que não são partilhadas com outros objetos de vedação.
Valor atual
O valor atual pode residir na memória do sistema ou na memória local da GPU, dependendo do tipo de cerca especificado pelo D3DDDI_NATIVEFENCE_TYPE.
O valor atual para barreiras de adaptadores cruzados está sempre na memória do sistema.
Quando o valor atual é armazenado na memória do sistema, o armazenamento é alocado do pool de memória interno do sistema.
Quando o valor atual é armazenado na memória local, o armazenamento é alocado a partir de segmentos de memória que o driver especificou em D3DKMDT_FENCESTORAGESURFACEDATA.
Valor monitorado
O valor monitorado também pode residir na memória local do sistema ou da GPU, dependendo D3DDDI_NATIVEFENCE_TYPE.
Quando o valor monitorado é armazenado na memória do sistema, o sistema operacional aloca o armazenamento do pool de memória interno do sistema.
Quando o valor monitorado é armazenado na memória local, o sistema operacional aloca armazenamento a partir de segmentos de memória que o driver especificou em D3DKMDT_FENCESTORAGESURFACEDATA.
Quando as condições de espera da CPU do SO mudam, é feita uma chamada de retorno para o DxgkDdiUpdateMonitoredValues do KMD, instruindo-o a atualizar o valor monitorado para um valor especificado.
Problemas de sincronização
O mecanismo descrito anteriormente tem uma condição de corrida inerente entre a CPU e a GPU lê e grava o valor atual e o valor monitorado. Se não forem tomados cuidados especiais, poderão ocorrer os seguintes problemas:
- A GPU pode ler um MonitoredValue obsoleto e não gerar uma interrupção como esperado pela CPU.
- Um motor de GPU pode gravar um CurrentValue mais recente enquanto o CMP está durante o processo de decisão da condição de interrupção. Este CurrentValue mais recente pode não gerar a interrupção conforme esperado ou pode não ser percebido pela CPU enquanto obtém o valor atual.
Sincronização na GPU entre o mecanismo e o CMP
Para eficiência, muitas GPUs discretas implementam a semântica do sinal de cerca monitorado usando o estado de sombra que reside na memória local da GPU entre:
O mecanismo de GPU executando o fluxo de buffer de comando e elevando condicionalmente um sinal de hardware para o CMP.
A CMP da GPU que decide se uma interrupção da CPU deve ser gerada.
Nesse caso, o CMP precisa sincronizar o acesso à memória com o mecanismo de GPU que executa a operação de escrita no valor da barreira. Em particular, a operação de atualização de um MonitoredValue sombra deve ser ordenada do ponto de vista CMP:
- Escreva um novo MonitoredValue (armazenamento de GPU sombra).
- Execute uma barreira de memória para sincronizar o acesso à memória com o mecanismo da GPU.
- Leia CurrentValue:
- Se CurrentValue>MonitoredValue, gerar uma interrupção da CPU.
- Se CurrentValue<= MonitoredValue, não aumente a interrupção da CPU.
Para que esta condição de corrida se resolva corretamente, é imperativo que a barreira de memória no passo 2 funcione corretamente. Não deve haver uma operação de gravação de memória pendente para CurrentValue na etapa 3 que se originou de um comando que não viu a atualização MonitoredValue na etapa 1. Essa situação geraria uma interrupção se a cerca escrita na etapa 3 fosse maior do que o valor atualizado na etapa 1.
Sincronização entre a GPU e a CPU
A CPU tem que executar atualizações de MonitoredValue e leituras de CurrentValue de uma forma que não perca a notificação de interrupção para sinais em voo.
- O sistema operacional tem que modificar MonitoredValue quando um novo garçom de CPU é adicionado ao sistema, ou se um garçom de CPU existente é aposentado.
- O sistema operacional chama DxgkDdiUpdateMonitoredValues para notificar a GPU de um novo valor monitorado.
- DxgkDdiUpdateMonitoredValue é executado no nível de interrupção do dispositivo e, portanto, é sincronizado com a rotina de serviço de interrupção sinalizada (ISR) da cerca monitorada.
- DxgkDdiUpdateMonitoredValue deve garantir que, depois de retornar, o CurrentValue lido por qualquer núcleo de processador foi escrito pela GPU CMP depois de ter observado o novo MonitoredValue.
- Após o retorno de DxgkDdiUpdateMonitoredValue, o sistema operacional reavalia CurrentValue e satisfaz todos os processos em espera que são desbloqueados pelo novo CurrentValue.
É perfeitamente aceitável que a CPU observe um CurrentValue mais recente do que o usado pela GPU para decidir se aumenta a interrupção. Essa situação ocasionalmente resultaria em uma notificação de interrupção que não desbloqueia nenhum garçom. O que não é aceitável é que a CPU não receba uma notificação de interrupção para a atualização CurrentValue mais recente que foi monitorada (ou seja, CurrentValue>MonitoredValue).
Consultando a ativação de recurso de cerca nativa no sistema operacional
Os drivers devem consultar se o recurso de cerca nativo está habilitado no sistema operacional durante a inicialização do driver. A partir do WDDM 3.2, o sistema operacional usa a interface IsFeatureEnabled adicionada para controlar se determinados recursos estão habilitados, incluindo o recurso de cerca nativo.
Como resultado, o KMD deve implementar a interface IsFeatureEnabled . A implementação do KMD deve verificar se o sistema operativo habilitou o recurso DXGK_FEATURE_NATIVE_FENCE antes de anunciar o suporte de fence nativo no DXGK_VIDSCHCAPS. O sistema operacional não conseguirá inicializar o adaptador se o KMD anunciar suporte nativo a barreiras caso o sistema operacional não tenha ativado a funcionalidade.
Para obter mais informações sobre a interface de ativação de recursos, consulte Consultando o suporte e a habilitação de recursos WDDM.
DDIs para consultar a ativação de funcionalidades nativas de barreira
As interfaces a seguir são introduzidas para um KMD consultar se o sistema operacional habilitou o recurso de cerca nativo:
- DXGKCB_FEATURE_NATIVEFENCE_CAPS_1
- DXGKARGCB_FEATURE_NATIVEFENCE_CAPS_1
- DXGKCBINT_FEATURE_NATIVEFENCE_1
O SO implementa a tabela de interface adicionada DXGKCB_FEATURE_NATIVEFENCE_CAPS_1 dedicada à versão 1 do DXGK_FEATURE_NATIVE_FENCE. O KMD deve consultar esta tabela de interface de recursos para determinar os recursos do sistema operacional. Em versões futuras do sistema operacional, o sistema operacional pode introduzir versões futuras dessa tabela de interface, detalhando o suporte para novos recursos.
Exemplo de código de driver para consultar o suporte
O código de exemplo a seguir mostra como os drivers devem usar o recurso DXGK_FEATURE_NATIVE_FENCE na interface DXGK_FEATURE_INTERFACE para consultar o suporte.
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);
}
}
Capacidades nativas de barreira
As interfaces a seguir são atualizadas ou introduzidas para consultar limites de vedação nativos:
O campo NativeGpuFence é adicionado ao DXGK_VIDSCHCAPS. Se o sistema operativo tiver ativado a funcionalidade DXGK_FEATURE_NATIVE_FENCE, o driver pode declarar suporte para a funcionalidade nativa de cerca de GPU durante a inicialização do adaptador, definindo o bit DXGK_VIDSCHCAPS::NativeGpuFence como 1.
DXGKQAITYPE_NATIVE_FENCE_CAPS é adicionado ao DXGK_QUERYADAPTERINFOTYPE.
Dxgkrnl expõe esse recurso ao modo de usuário através da D3DKMT_WDDM_3_1_CAPS::NativeGpuFenceSupported structure/bit adicionada.
KMTQAITYPE_WDDM_3_1_CAPS é adicionado ao KMTQUERYADAPTERINFOTYPE.
As entidades a seguir são adicionadas para um KMD para indicar as suas capacidades de suporte para a funcionalidade de cerca nativa da GPU.
A estrutura DXGK_NATIVE_FENCE_CAPS descreve os recursos de cerca nativa da GPU. Quando o KMD define o bit MapToGpuSystemProcess dessa estrutura, ele está instruindo o sistema operacional a reservar um espaço de endereço virtual da GPU de processo do sistema para uso de CMP e a criar mapeamentos VA de GPU nesse espaço de endereçamento para a cerca nativa CurrentValue e MonitoredValue. Esses VAs de GPU são posteriormente passados para o retorno de chamada de criação de barreira do KMD como DXGKARG_CREATENATIVEFENCE::CurrentValueSystemProcessGpuVa e MonitoredValueSystemProcessGpuVa.
KMD retorna a sua estrutura DXGK_NATIVE_FENCE_CAPS preenchida quando a sua função DxgkDdiQueryAdapterInfo é chamada com o tipo de informação de adaptador de consulta DXGKQAITYPE_NATIVE_FENCE_CAPS adicionado.
DDIs KMD para criar, abrir, fechar e destruir um objeto de barreira nativo
As seguintes DDIs implementadas pelo KMD são usadas para criar, abrir, fechar e destruir um objeto de barreira nativo. Dxgkrnl chama estas DDIs em nome de componentes de modo utilizador. Dxgkrnl chama-os apenas se o SO ativou o recurso DXGK_FEATURE_NATIVE_FENCE .
- DxgkDdiCreateNativeFence/DXGKARG_CREATENATIVEFENCE
- DxgkDdiOpenNativeFence/DXGKARG_OPENNATIVEFENCE
- DxgkDdiCloseNativeFence/DXGKARG_CLOSENATIVEFENCE
- DxgkDdiDestroyNativeFence/DXGKARG_DESTROYNATIVEFENCE
Os seguintes DDIs foram atualizados para oferecer suporte a objetos de cerca nativos:
Os seguintes membros foram adicionados a DRIVER_INITIALIZATION_DATA. Os drivers que suportam objetos de cerca de GPU nativos devem implementar as funções e fornecer ao Dxgkrnl ponteiros para eles por meio dessa estrutura.
- PDXGKDDI_CREATENATIVEFENCE DxgkDdiCreateNativeFence (adicionado ao WDDM 3.1)
- PDXGKDDI_DESTROYNATIVEFENCE DxgkDdiDestroyNativeFence (adicionado ao WDDM 3.1)
- PDXGKDDI_OPENNATIVEFENCE DxgkDdiCreateNativeFence (adicionado ao WDDM 3.2)
- PDXGKDDI_CLOSENATIVEFENCE DxgkDdiCloseNativeFence (adicionado ao WDDM 3.2)
- PDXGKDDI_SETNATIVEFENCELOGBUFFER DxgkDdiSetNativeFenceLogBuffer (adicionado ao WDDM 3.2)
- PDXGKDDI_UPDATENATIVEFENCELOGS DxgkDdiUpdateNativeFenceLogs (adicionado no WDDM 3.2)
Alças globais e locais para cercas compartilhadas
Imagine que o processo A cria uma cerca nativa compartilhada e o processo B depois abre essa cerca.
Quando o processo A cria a cerca nativa compartilhada, Dxgkrnl chama DxgkDdiCreateNativeFence com o identificador de driver do adaptador no qual essa cerca é criada. O identificador de fence criado e devolvido em hGlobalNativeFence é o identificador de fence global.
Dxgkrnl subsequentemente segue com uma chamada para DxgkDdiOpenNativeFence para abrir um identificador local específico do processo A (hLocalNativeFenceA).
Quando o processo B abre a mesma cerca nativa compartilhada, o Dxgkrnl chama DxgkDdiOpenNativeFence para abrir um identificador local específico do processo B (hLocalNativeFenceB).
Se o processo A destrói a sua instância de cerca nativa compartilhada, Dxgkrnl vê que ainda há uma referência pendente a essa mesma cerca global, então chama apenas DxgkDdiCloseNativeFence(hLocalNativeFenceA) para o driver limpar as estruturas específicas do processo A. O identificador hGlobalNativeFence ainda existe.
Quando o processo B destrói sua instância de cerca, Dxgkrnl chama DxgkDdiCloseNativeFence(hLocalNativeFenceB) e, em seguida, DxgkDdiDestroyNativeFence(hGlobalNativeFence) para permitir que o KMD destrua seus dados de cerca global.
Mapeamentos GPU VA no espaço de endereçamento do processo de paginação para uso CMP
O KMD define o limite DXGK_NATIVE_FENCE_CAPS::MapToGpuSystemProcess no hardware que requer que os VAs de GPU de cerca nativos também sejam mapeados no espaço de endereçamento do processo de paginação da GPU. Um bit MapToGpuSystemProcess definido instrui o SO a criar mapeamentos VA GPU no espaço de endereçamento do processo de paginação para CurrentValue e MonitoredValue da cerca nativa para uso pelo CMP. Esses VAs de GPU são posteriormente passados para DxgkDdiCreateNativeFence como DXGKARG_CREATENATIVEFENCE::CurrentValueSystemProcessGpuVa e MonitoredValueSystemProcessGpuVa.
APIs do kernel D3DKMT para criar, abrir e destruir cercas nativas
As seguintes APIs de modo kernel D3DKMT são introduzidas para criar e abrir um objeto de cerca nativo.
- D3DKMTCreateNativeFence / D3DKMT_CREATENATIVEFENCE
- D3DKMTOpenNativeFenceFromNTHandle / D3DKMT_OPENNATIVEFENCEFROMNTHANDLE
Dxgkrnl chama a função D3DKMTDestroySynchronizationObject existente para fechar e destruir (liberar) um objeto de cerca nativo existente.
As estruturas de suporte e enumerações que são introduzidas ou atualizadas incluem:
- D3DDDI_NATIVEFENCEINFO
- D3DDDI_NATIVEFENCE_TYPE
- D3DDDI_SYNCHRONIZATIONOBJECT_FLAGS
- D3DDDI_NATIVEFENCE_MAPPING
DDI para suporte a colocação de valores de barreira nativos na memória local
Os seguintes DDIs foram adicionados ou alterados para suportar o posicionamento de valores de cerca nativos na memória local:
A estrutura D3DKMDT_FENCESTORAGESURFACEDATA é adicionada.
A cerca nativa MonitoredValue e CurrentValue do tipo de cerca nativa D3DDDI_NATIVEFENCE_TYPE_INTRA_GPU podem ser colocados na memória do dispositivo local. Para fazer isso, o sistema operacional solicitará ao driver especificar segmentos de memória nos quais o armazenamento de barreira deve ser colocado. DxgkDdiGetStandardAllocation é estendido para fornecer tais informações.
D3DKMDT_STANDARDALLOCATION_FENCESTORAGE é adicionado ao DXGKARG_GETSTANDARDALLOCATIONDRIVERDATA.
Indicando uma cerca de progresso nativa para filas de hardware
A seguinte atualização é introduzida para indicar um objeto de barreira de progresso de fila de hardware nativo:
Um sinalizador NativeProgressFence é adicionado para chamadas para DxgkDdiCreateHwQueue.
- Em sistemas suportados, o SO atualiza a barreira de progresso da fila de hardware para uma barreira nativa. Quando o sistema operacional define NativeProgressFence, indica ao KMD que o identificador DXGKARG_CREATEHWQUEUE::hHwQueueProgressFence aponta para o identificador de driver de um objeto de barreira nativo da GPU previamente criado utilizando DxgkDdiCreateNativeFence.
Barreira nativa sinalizada com interrupção
As seguintes alterações são feitas no mecanismo de interrupção para dar suporte a uma interrupção sinalizada de cerca nativa:
O enum DXGK_INTERRUPT_TYPE foi atualizado para incluir um tipo de interrupção DXGK_INTERRUPT_NATIVE_FENCE_SIGNALED.
A estrutura DXGKARGCB_NOTIFY_INTERRUPT_DATA é atualizada para incluir uma estrutura NativeFenceSignaled para denotar uma interrupção sinalizada de barreira nativa.
NativeFenceSignaled é usado para informar ao sistema operativo que um conjunto de objetos GPU "fence" nativos monitorizados pela CPU foram sinalizados num motor de GPU. Se a GPU for capaz de determinar o subconjunto exato de objetos com processos de CPU ativos, ela passa esse subconjunto via pSignaledNativeFenceArray. As alças nesta matriz devem ser alças hGlobalNativeFence válidas que o Dxgkrnl passou para o KMD no DxgkDdiCreateNativeFence. Passar uma alça para um objeto de cerca nativo destruído causa uma verificação de erro.
A estrutura DXGKCB_NOTIFY_INTERRUPT_DATA_FLAGS é atualizada e consiste em incluir um membro EvaluateLegacyMonitoredFences.
A GPU pode passar um pSignaledNativeFenceArray NULL nas seguintes condições:
- A GPU não consegue determinar o subconjunto exato de objetos com esperas de CPU ativas.
- Várias interrupções de sinais são combinadas, tornando difícil determinar o conjunto sinalizado com esperas ativas.
Um valor NULL instrui o SO a verificar todos os esperadores de objetos de fence de GPU nativos pendentes.
O contrato entre o SO e o driver é: se o SO tiver um garçom de CPU ativo (conforme expresso por MonitoredValue), e o mecanismo de GPU sinalizou o objeto para o valor que requer uma interrupção da CPU, a GPU deve executar uma das seguintes ações:
- Inclua essa alça de cerca nativa no pSignaledNativeFenceArray.
- Gere uma interrupção NativeFenceSignaled com um pSignaledNativeFenceArray nulo.
Por padrão, quando o KMD gera esta interrupção com um pSignaledNativeFenceArray NULL, o Dxgkrnl apenas verifica todos os esperadores de barreira nativas pendentes e não verifica os esperadores de barreira monitorados legados. pt-PT: Em hardware que não consegue distinguir entre DXGK_INTERRUPT_MONITORED_FENCE_SIGNALED e DXGK_INTERRUPT_NATIVE_FENCE_SIGNALED herdados, o KMD pode sempre gerar apenas a interrupção DXGK_INTERRUPT_NATIVE_FENCE_SIGNALED introduzida com pSignaledNativeFenceArray = NULL e EvaluateLegacyMonitoredFences = 1, o que indica ao sistema operacional para analisar todos os processos (processo de cerca monitorado legado e processos de cerca nativos).
Instruindo o KMD a atualizar lotes de valores
As seguintes interfaces são introduzidas para instruir o KMD a atualizar um lote de valores atuais ou monitorados:
DxgkDdiUpdateCurrentValuesFromCpu / DXGKARG_UPDATECURRENTVALUESFROMCPU
DxgkDdiUpdateMonitoredValues / DXGKARG_UPDATEMONITOREDVALUES
Barreiras nativas entre adaptadores
O sistema operacional deve suportar a criação de barreiras nativas entre adaptadores porque as aplicações DX12 existentes criam e usam barreiras monitorizadas entre adaptadores. Se as filas subjacentes e o agendamento para esses aplicativos forem mudados para envio no modo utilizador, então as suas barreiras monitoradas também terão de ser mudadas para barreiras nativas (as filas em modo utilizador não podem suportar barreiras monitoradas).
Uma barreira de adaptador cruzado deve ser criada com o tipo D3DDDI_NATIVEFENCE_TYPE_DEFAULT. Caso contrário, D3DKMTCreateNativeFence falhará.
Todas as GPUs compartilham a mesma cópia do armazenamento CurrentValue , que é sempre alocada na memória do sistema. Quando o tempo de execução cria uma barreira nativa de adaptador cruzado na GPU1 e a abre na GPU2, os mapeamentos de endereços virtuais (VA) da GPU em ambas as GPUs apontam para o mesmo armazenamento físico CurrentValue.
Cada GPU recebe sua própria cópia de MonitoredValue. Assim, o armazenamento MonitoredValue pode ser alocado na memória do sistema ou na memória local.
As barreiras nativas entre adaptadores devem resolver a condição na qual a GPU1 está aguardando uma barreira nativa sinalizada pela GPU2. Hoje, não existe o conceito de sinais GPU-to-GPU; assim, o sistema operativo resolve essa condição explicitamente ao sinalizar a GPU1 a partir da CPU. Essa sinalização é feita ao definir MonitoredValue da cerca do adaptador cruzado para 0 durante a sua vida útil. Em seguida, quando a GPU2 sinaliza a cerca nativa, ela também gera uma interrupção da CPU, permitindo que o Dxgkrnl atualize o CurrentValue na GPU1 (usando DxgkDdiUpdateCurrentValuesFromCpu com o sinalizador NotificationOnly definido TRUE) e desbloqueie quaisquer processos de espera pendentes de CPU/GPU dessa GPU.
Embora MonitoredValue seja sempre 0 para barreiras nativas entre adaptadores, a espera e os sinais submetidos na mesma GPU ainda se beneficiam de uma sincronização mais rápida na GPU. No entanto, o benefício de energia das interrupções reduzidas da CPU é perdido porque as interrupções da CPU serão geradas incondicionalmente, mesmo que não houvesse processos em espera da CPU ou processos em espera noutra GPU. Essa troca é feita para manter o custo de projeto e implementação da barreira nativa entre adaptadores simples.
O sistema operacional suporta o cenário em que um objeto de cerca nativo é criado na GPU1 e aberto na GPU2, onde a GPU1 suporta o recurso e a GPU2 não. O objeto fence é aberto como um MonitoredFence regular na GPU2.
O sistema operacional suporta o cenário em que um objeto de cerca monitorado regular é criado na GPU1 e aberto como uma cerca nativa na GPU2, que suporta o recurso. O objeto fence é aberto como uma cerca nativa na GPU2.
Combinações de espera/sinal entre diferentes adaptadores
As tabelas nas subseções a seguir apresentam um exemplo de um sistema iGPU e dGPU e apresentam as várias configurações que são possíveis para espera/sinalização de barreira nativa entre a CPU/GPU. São considerados os dois casos seguintes:
- Ambas as GPUs suportam cercas nativas.
- A iGPU não suporta cercas nativas, mas a dGPU suporta cercas nativas.
O segundo cenário também é semelhante ao caso em que ambas as GPUs suportam cercas nativas, mas a espera e o sinal de cerca nativa são enviados para uma fila de modo kernel na iGPU.
As tabelas devem ser lidas selecionando um par de espera e sinal das colunas, por exemplo, WaitFromGPU - SignalFromGPU ou WaitFromGPU - SignalFromCPU, et cetera.
Cenário 1
No Cenário 1, tanto a dGPU quanto a iGPU suportam cercas nativas.
| iGPU WaitFromGPU (hFence, 10) | iGPU WaitFromCPU (hFence, 10) | dGPU SignalFromGpu (hFence, 10) | dGPU SignalFromCpu(hFence, 10) |
|---|---|---|---|
| UMD insere uma espera pela instrução hfence CurrentValue == 10 no buffer de comando | O tempo de execução chama D3DKMTWaitForSynchronizationObjectFromCpu | ||
| O VidSch rastreia esse objeto de sincronização na sua lista de espera da CPU de fence nativa | |||
| UMD insere uma instrução de gravação hFence CurrentValue = 10 signal no buffer de comando | Runtime chama D3DKMTSignalSynchronizationObjectFromCpu | ||
| VidSch recebe uma cerca nativa sinalizada ISR quando CurrentValue é escrito (porque MonitoredValue == 0 sempre) | VidSch chama DxgkDdiUpdateCurrentValuesFromCpu(hFence, 10) | ||
| VidSch propaga o sinal (hFence, 10) para a iGPU | VidSch propaga o sinal (hFence, 10) para iGPU | ||
| VidSch recebe o sinal propagado e chama DxgkDdiUpdateCurrentValuesFromCpu(hFence, NotificationOnly=TRUE) | VidSch recebe o sinal propagado e chama DxgkDdiUpdateCurrentValuesFromCpu(hFence, NotificationOnly=TRUE) | ||
| KMD verifica novamente a lista de execução para desbloquear o canal HW que estava aguardando no hFence | VidSch desbloqueia a condição de espera da CPU sinalizando o KEVENT |
Cenário 2a
No Cenário 2a, a iGPU não suporta barreiras nativas, mas a dGPU suporta. Uma espera é submetida na iGPU e um sinal é enviado na dGPU.
| iGPU WaitFromGPU (hFence, 10) | iGPU WaitFromCPU (hFence, 10) | dGPU SignalFromGpu (hFence, 10) | dGPU SignalFromCpu(hFence, 10) |
|---|---|---|---|
| A execução invoca D3DKMTWaitForSynchronizationObjectFromGpu | O tempo de execução chama D3DKMTWaitForSynchronizationObjectFromCpu | ||
| O VidSch rastreia esse objeto de sincronização em sua lista de espera de cerca monitorada | O VidSch rastreia este objeto de sincronização na cabeça da lista de espera da CPU em cerca monitorada | ||
| UMD insere uma instrução de escrita hFence CurrentValue = 10 no buffer de comando | Runtime chama D3DKMTSignalSynchronizationObjectFromCpu | ||
| VidSch recebe NativeFenceSignaledISR quando CurrentValue é escrito (porque MV == 0 sempre) | VidSch chama DxgkDdiUpdateCurrentValuesFromCpu(hFence, 10) | ||
| VidSch propaga o sinal (hFence, 10) para iGPU | VidSch propaga o sinal (hFence, 10) para iGPU | ||
| VidSch recebe o sinal propagado e observa o novo valor de barreira | VidSch recebe o sinal propagado e observa novo valor de barreira | ||
| VidSch verifica sua lista de espera de cerca monitorada e desbloqueia contextos de software | O VidSch verifica a fila de espera da CPU monitorada e desbloqueia a espera do processador sinalizando o KEVENT |
Cenário 2b
No Cenário 2b, o suporte de cerca nativo permanece o mesmo (iGPU não suporta, dGPU suporta). Desta vez, um sinal é enviado na iGPU e uma espera é enviada na dGPU.
| iGPU SignalFromGPU (hFence, 10) | iGPU SignalFromCPU (hFence, 10) | dGPU WaitFromGpu (hFence, 10) | dGPU WaitFromCpu(hFence, 10) |
|---|---|---|---|
| UMD insere uma instrução de espera para hfence CurrentValue == 10 no buffer de comandos | O tempo de execução chama D3DKMTWaitForSynchronizationObjectFromCpu | ||
| O VidSch rastreia esse objeto de sincronização na sua lista de espera da CPU de fence nativa | |||
| UMD chama D3DKMTSignalSynchronizationObjectFromGpu | UMD chama D3DKMTSignalSynchronizationObjectFromCpu | ||
| Quando o pacote está no topo do contexto do software, o VidSch atualiza o valor da cerca diretamente da CPU | O VidSch atualiza o valor da cerca diretamente da CPU | ||
| VidSch propaga o sinal (hFence, 10) para dGPU | VidSch propaga o sinal (hFence, 10) para dGPU | ||
| VidSch recebe o sinal propagado e chama DxgkDdiUpdateCurrentValuesFromCpu(hFence, NotificationOnly=TRUE) | VidSch recebe o sinal propagado e chama DxgkDdiUpdateCurrentValuesFromCpu(hFence, NotificationOnly=TRUE) | ||
| KMD verifica novamente a lista de execução para desbloquear o canal HW que estava aguardando no hFence | VidSch desbloqueia a condição de espera da CPU sinalizando o KEVENT |
Futuro sinal de adaptador cruzado GPU para GPU
Conforme descrito em Problemas de sincronização, para barreiras nativas entre adaptadores, há uma perda de poupança de energia porque uma interrupção da CPU é ativada incondicionalmente.
Em uma versão futura, o sistema operativo desenvolverá uma infraestrutura para permitir que um sinal de GPU numa GPU interrompa outras GPUs escrevendo numa memória porta comum, permitindo que as outras GPUs acordem, processem a sua lista de execução e desbloqueiem as filas de hardware prontas.
O desafio para este trabalho é projetar:
- A memória comum da campainha.
- Uma carga útil ou alça inteligente que uma GPU pode gravar na campainha que permite que outras GPUs determinem qual cerca foi sinalizada para que ela só possa verificar um subconjunto de HWQueues.
Com este sinal entre adaptadores, pode até ser possível que as GPUs compartilhem a mesma cópia do armazenamento de barreira nativa (uma alocação de formato linear entre adaptadores, semelhante às alocações de armazenamento em cache entre adaptadores) a partir da qual todas as GPUs leem e gravam.
Design nativo de buffer de registo de limite
Com cercas nativas e envio de modo de usuário, o Dxgkrnl não tem visibilidade de quando a GPU nativa espera e os sinais enfileirados do UMD são desbloqueados na GPU para um HWQueue específico. Com cercas nativas, uma interrupção sinalizada mediante monitorização pode ser suprimida em uma cerca determinada.
:
É necessária uma maneira de recriar as operações de cerca, conforme mostrado nesta imagem GPUView . As caixas rosa escuro são sinais e as caixas rosa claro são esperas. Cada caixa começa quando a operação foi enviada na CPU para Dxgkrnl e termina quando Dxgkrnl conclui a operação na CPU. Desta forma, somos capazes de estudar toda a vida útil de um comando.
Assim, em um alto nível, as condições per HWQueue necessárias para serem registradas são:
| Condição | Significado |
|---|---|
| FENCE_WAIT_QUEUED | Carimbo de data/hora da CPU de quando o UMD insere uma instrução GPU Wait na fila de comandos |
| FENCE_SIGNAL_QUEUED | Carimbo de data/hora da CPU de quando o UMD insere uma instrução de sinal de GPU na fila de comandos |
| SINAL_DE_CERCA_EXECUTADO | Carimbo de data/hora da GPU de quando um comando de sinal é executado na GPU para um HWQueue |
| FENCE_WAIT_UNBLOCKED | Carimbo de data/hora da GPU de quando uma condição de espera é satisfeita na GPU e o HWQueue é desbloqueado |
Buffers de log de cerca nativos DDIs
Os DDI, estruturas e enums seguintes são introduzidos para oferecer suporte a buffers de registo de cerca nativos.
- DxgkDdiSetNativeFenceLogBuffer / DXGKARG_SETNATIVEFENCELOGBUFFER
- DxgkDdiUpdateNativeFenceLogs / DXGKARG_UPDATENATIVEFENCELOGS
- Um buffer de log que contém um cabeçalho e uma matriz de entradas de log. O cabeçalho identifica se as entradas são para uma espera ou sinal, e cada entrada identifica o tipo de operação (executada ou desbloqueada):
O design do buffer de log destina-se a barreiras nativas e filas de envio de modo de usuário onde a carga útil do buffer de log é gravada pelo mecanismo da GPU/CMP, sem envolvimento de Dxgkrnl ou KMD. Assim, UMD inserirá uma instrução ao gerar o buffer de comando wait/signal, programando a GPU para gravar a carga útil do buffer de log na entrada do buffer de log durante a execução. Para submissão sem modo de usuário (ou seja, filas de modo kernel), a espera e os sinais são comandos de software dentro do Dxgkrnl, então já sabemos os carimbos de data/hora e outros detalhes dessas operações e não precisamos de hardware/KMD para atualizar o buffer de log. Para essas filas de modo kernel, o Dxgkrnl não criará um buffer de log.
Mecanismo de buffer de log
Dxgkrnl aloca dois buffers de log dedicados de 4 KB por HWQueue.
- Um para registar esperas.
- Um para registrar sinais.
Esses buffers de log têm mapeamentos para o VA da CPU em modo kernel (LogBufferCpuVa), um VA da GPU no espaço de endereçamento do processo (LogBufferGpuVa) e o VA CMP (LogBufferSystemProcessGpuVa), para permitir leitura/escrita pelo KMD, pelo motor da GPU e pelo CMP. Dxgkrnl chama DxgkDdiSetNativeFenceLogBuffer duas vezes: uma para definir o buffer de log para registro de esperas e uma vez para definir o buffer de log para registro de sinais.
Imediatamente após o UMD inserir uma instrução de espera ou sinal de cerca nativa na lista de comandos, ele também insere um comando instruindo a GPU a gravar uma carga útil em uma entrada específica no buffer de log.
Depois que o mecanismo da GPU executa a operação de cerca, ele vê a instrução UMD para gravar uma carga útil em uma determinada entrada no buffer de log. Além disso, a GPU também grava o FenceEndGpuTimestamp atual nessa entrada de buffer de log.
Embora o UMD não possa acessar o buffer de log acessível pela GPU, ele controla a progressão do buffer de log. Ou seja, o UMD determina a próxima entrada livre para gravar, se houver, e programa a GPU com essas informações. Quando a GPU grava no buffer de log, ela incrementa o valor FirstFreeEntryIndex no cabeçalho do log. O UMD deve garantir que as gravações em entradas de log estejam aumentando monotonicamente.
Considere o seguinte cenário:
- Existem duas HWQueues, HWQueueA e HWQueueB, com buffers de log de fences correspondentes com VAs GPU de FenceLogA e FenceLogB. HWQueueA estão associados ao buffer de log para registo de esperas e HWQueueB estão associados ao buffer de log para registo de sinais.
- Há um objeto de cerca nativo com um manípulo D3DKMT em modo de utilizador FenceF.
- Uma espera da GPU em FenceF para o valor V1 é enfileirada para HWQueueA no momento CPUT1. Quando o UMD cria o buffer de comandos, ele insere um comando instruindo a GPU a registrar a carga útil: LOG(FenceF, V1, DXGK_NATIVE_FENCE_LOG_OPERATION_WAIT_UNBLOCKED).
- Um sinal da GPU para FenceF com valor V1 é colocado na fila para HWQueueB no momento CPUT2. Quando o UMD cria o buffer de comandos, ele insere um comando instruindo a GPU a registrar a carga útil: LOG(FenceF, V1, DXGK_NATIVE_FENCE_LOG_OPERATION_SIGNAL_EXECUTED).
Após o agendador da GPU executar o sinal da GPU no HWQueueB ao tempo GPUT1, ele lê a carga útil UMD e regista o evento no registo de barreira fornecido pelo sistema operativo para 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
Após o agendador da GPU observar o desbloqueio de HWQueueA no tempo de GPU GPUT2, lê a carga útil da UMD e regista o evento no registo de vedação fornecido pelo SO para 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 pode destruir e recriar um buffer de log. Sempre que isso acontece, ele chama DxgkDdiSetNativeFenceLogBuffer para informar o KMD sobre o novo local.
Carimbos de data/hora da CPU de operações em fila de vedação
Há pouco benefício em fazer o UMD registrar esses carimbos de data/hora da CPU, dado que:
- Uma lista de comandos pode ser gravada vários minutos antes da execução pela GPU de um buffer de comandos que inclui a lista de comandos.
- Esses vários minutos podem estar fora de ordem com outros objetos de sincronização que estão no mesmo buffer de comando.
Há um custo para incluir os carimbos de data/hora da CPU nas instruções do UMD no buffer de log gravado na GPU, portanto, os carimbos de data/hora da CPU não são incluídos na carga útil de entrada de log.
Em vez disso, o tempo de execução ou UMD pode emitir um evento ETW nativo enfileirado com o carimbo de data/hora da CPU no momento em que uma lista de comandos está sendo gravada. Assim, as ferramentas podem criar uma linha do tempo de eventos enfileirados e concluídos combinando o carimbo de data/hora da CPU desse novo evento com o carimbo de data/hora da GPU da entrada do buffer de log.
Ordem de operações na GPU ao sinalizar ou desbloquear uma cerca
O UMD deve garantir que mantém a seguinte ordem quando cria uma lista de comandos instruindo a GPU a sinalizar/desbloquear uma cerca:
- Escreva o novo valor de fence na GPU VA/CMP VA.
- Escreva a carga útil do log no buffer de log correspondente ao endereço virtual GPU (VA) ou CMP (VA).
- Levante uma barreira nativa sinalizada de interrupção, se necessário.
Essa ordem de operações garante que o Dxgkrnl veja as entradas de log mais recentes quando a interrupção é acionada para o sistema operacional.
O transbordamento do buffer de log é permitido
A GPU pode invadir o buffer de log ao sobrescrever entradas no buffer ainda não vistas pelo sistema operacional. Ele faz isso incrementando o WraparoundCount.
Quando o sistema operacional eventualmente lê o log, ele pode detetar que ocorreu uma saturação comparando o novo valor WraparoundCount no cabeçalho do log com seu valor armazenado em cache. Se ocorrer uma saturação, o SO tem as seguintes opções de fallback:
- Para desbloquear cercas quando ocorre uma saturação, o sistema operacional verifica todas as cercas e determina quais garçons foram desbloqueados.
- Se o rastreamento estiver habilitado, o sistema operacional poderá emitir um sinalizador no rastreamento para notificar o usuário de que os eventos foram perdidos. Além disso, quando o rastreamento está habilitado, o sistema operacional primeiro aumenta o tamanho do buffer de log para evitar saturações em primeiro lugar.
Não é necessário que o UMD implemente o suporte à pressão de retorno durante o progresso das entradas do buffer de log.
Carimbos de data/hora vazios ou repetidos nos registos do buffer de log
Em casos comuns, Dxgkrnl espera que os carimbos de data/hora nas entradas de log estejam aumentando monotonicamente. No entanto, há cenários em que os carimbos de data/hora das entradas de log subsequentes são zero ou iguais às entradas de log anteriores.
Por exemplo, em um cenário com adaptadores de vídeo vinculados, um dos adaptadores encadeados no LDA pode ignorar a operação de gravação de cerca. Nesse caso, sua entrada de buffer de log tem um carimbo de data/hora zero. Dxgkrnl lida com esse caso. Dito isto, o Dxgkrnl nunca espera que o carimbo de data/hora de uma determinada entrada de log seja menor do que o da entrada de log anterior; ou seja, os carimbos de data/hora nunca podem retroceder.
Atualização síncrona do log de vedação nativo
As gravações da GPU para atualizar o valor da barreira e o buffer de log correspondente devem garantir que essas gravações sejam totalmente propagadas antes que a CPU realize leituras. Este requisito requer o uso de barreiras de memória. Por exemplo:
- Signal Fence(N): escreva N como um novo valor atual
- Gravar entrada de LOG incluindo timestamp da GPU
- Barreira de Memória
- Incremento FirstFreeEntryIndex
- Barreira de Memória
- Interrupção de barreira monitorada (N): ler o endereço "M" e comparar o valor com N para decidir sobre a entrega da interrupção da CPU
É muito caro inserir duas barreiras em cada sinal de GPU, especialmente quando é provável que a verificação de interrupção condicional não esteja satisfeita e nenhuma interrupção da CPU seja necessária. Como resultado, o design move o custo de inserir uma das barreiras de memória da GPU (produtor) para a CPU (consumidor). O Dxgkrnl chama a função introduzida DxgkDdiUpdateNativeFenceLogs para fazer com que o KMD libere gravações de logs nativas de cercas pendentes de forma síncrona, sob demanda (semelhante a como DxgkddiUpdateflipqueuelog foi introduzido para HW flip queue log flush).
Para operações de GPU:
- Signal Fence(N): escreva N como um novo valor atual
- Registar uma entrada no LOG, incluindo o carimbo de data/hora da GPU
- Incremento FirstFreeEntryIndex
- MemoryBarrier => Garante que FirstFreeEntryIndex seja totalmente propagado
- Interrupção de cerca monitorada (N): leia o endereço "M" e compare o valor com N para decidir sobre a entrega da interrupção
Para operações de CPU:
No manipulador de interrupção sinalizado da barreira nativa do Dxgkrnl (DISPATCH_IRQL):
- Para cada log HWQueue: Leia FirstFreeEntryIndex e determine se novas entradas são gravadas.
- Para cada log HWQueue com novas entradas: Chame DxgkDdiUpdateNativeFenceLogs e forneça os identificadores do kernel para esses HWQueues. Neste DDI, o KMD insere uma barreira de memória para cada HWQueue fornecido, o que garante que todas as gravações de entrada de log sejam confirmadas.
- Dxgkrnl lê entradas de log para extrair a carga de marca temporal.
Assim, desde que o hardware insira uma barreira de memória depois de gravar em FirstFreeEntryIndex, o Dxgkrnl sempre chama o DDI do KMD, permitindo que o KMD insira uma barreira de memória antes que o Dxgkrnl leia quaisquer entradas de log.
Requisitos de hardware futuros
A maioria do hardware da geração atual poderá apenas suportar a escrita do identificador do kernel do objeto de barreira sinalizado na interrupção da barreira nativa sinalizada. Esse design é descrito anteriormente em Interrupção sinalizada de cerca nativa. Nesse caso, o Dxgkrnl lida com a carga útil de interrupção, da seguinte maneira:
- O sistema operativo executa uma leitura (possivelmente via PCI) do valor de barreira.
- Sabendo qual barreira foi sinalizada e o valor da barreira, o sistema operacional desperta os processos de espera na CPU que estão aguardando nessa barreira e valor.
- Separadamente, para o dispositivo pai dessa barreira, o sistema operativo verifica os buffers de log de todas as suas HWQueues. Em seguida, o sistema operativo lê as últimas entradas gravadas no buffer de log para determinar qual HWQueue sinalizou e extrai a correspondente carga útil do carimbo de data/hora. Essa abordagem pode ler redundantemente alguns valores de cerca no PCI.
Em plataformas futuras, o Dxgkrnl prefere obter uma matriz de identificadores HwQueue do kernel na cerca nativa sinalizada de interrupção. Essa abordagem permite que o sistema operacional:
- Leia as entradas mais recentes do buffer de log para esse HwQueue. O dispositivo do usuário não é conhecido pelo manipulador de interrupção; portanto, esse identificador HwQueue precisa ser um identificador de kernel.
- Examine o buffer de log em busca de entradas de log que indiquem quais barreiras foram sinalizadas e quais os valores associados. Ler apenas o buffer de log garante uma única leitura através do PCI, em vez de ter que ler redundantemente os valores de barreira e o buffer de log. Essa otimização é bem-sucedida desde que o buffer de log não tenha sido saturado (descartando entradas que o Dxgkrnl nunca leu).
- Se o sistema operativo detetar que o buffer de log foi saturado, voltará para o caminho não otimizado que lê o valor atual de cada barreira pertencente ao mesmo dispositivo. O desempenho é proporcional ao número de cercas de propriedade do dispositivo. Se o valor da barreira estiver na memória de vídeo, essas leituras serão coerentes com o cache através do PCI.
- Sabendo quais barreiras foram sinalizadas e os valores dessas barreiras, o sistema operacional desperta os processos de espera da CPU que estão em espera nessas barreiras/valores.
Interrupção sinalizada de cerca nativa otimizada
Além das alterações descritas em Interrupção sinalizada de cerca nativa, a seguinte alteração também é feita para dar suporte à abordagem otimizada:
- O limite OptimizedNativeFenceSignaledInterrupt é adicionado ao DXGK_VIDSCHCAPS.
Se suportado pelo hardware, em vez de preencher uma matriz de handles de barreira que foram sinalizadas, a GPU deve indicar apenas o handle KMD do HWQueue que estava em execução quando a interrupção foi gerada. O Dxgkrnl verifica o buffer de log de cercas para este HWQueue e lê todas as operações de cercas que foram concluídas pela GPU desde a última atualização e desbloqueia todas as esperas da CPU correspondentes. Se a GPU não conseguir determinar qual subconjunto de barramentos foi sinalizado, deverá especificar um identificador nulo para o HWQueue. Quando o Dxgkrnl vê um identificador NULL HWQueue, ele recorre a uma nova verificação do buffer de registo de todos os HWQueues nesse mecanismo para determinar quais barreiras foram sinalizadas.
O suporte para esta otimização é opcional; O KMD deve definir o limite DXGK_VIDSCHCAPS:OptimizedNativeFenceSignaledInterrupt se for suportado pelo hardware. Se a capacidade OptimizedNativeFenceSignaledInterrupt não estiver definida, a GPU/KMD deverá seguir o comportamento descrito em Interrupção sinalizada de barreira nativa.
Exemplo de interrupção sinalizada de cerca nativa otimizada
HWQueueA: Sinal de GPU para o Fence F1, Valor V1 -> Escrever na entrada do buffer de log E1 -> sem necessidade de interrupção
HWQueueA: GPU Signal to Fence F1, Value V2 -> Escrever para a entrada do buffer de registo E2 -> sem interrupção necessária
HWQueueA: Sinal do GPU para Fence F2, Valor V3 -> Escrever na entrada do buffer de log E3 -> sem necessidade de interrupção
HWQueueA: GPU Sinal para a Barreira F2, Valor V3 -> Escrever na entrada do buffer de log E4 -> interrupção levantada
DXGKARGCB_NOTIFY_INTERRUPT_DATA FenceSignalISR = {}; FenceSignalISR.NodeOrdinal = 0; FenceSignalISR.EngineOrdinal = 0; FenceSignalISR.hHWQueue = A;Dxgkrnl lê o buffer de log para HWQueueA. Ele lê as entradas de buffer de log E1, E2, E3 e E4 para observar as barreiras sinalizadas F1 @ Value V1, F1 @ Value V2, F2 @ Value V3 e F2 @ Value V3, e desbloqueia todos os processos em espera que aguardam nessas barreiras e valores.
Registo Opcional e Obrigatório
O suporte para o registo de barreiras nativas para DXGK_NATIVE_FENCE_LOG_TYPE_WAITS e DXGK_NATIVE_FENCE_LOG_TYPE_SIGNALS é obrigatório.
No futuro, outros tipos de log poderão ser adicionados somente quando ferramentas como GPUView habilitarem o registro detalhado de ETW no sistema operacional. O sistema operativo deve informar tanto o UMD como o KMD sobre quando o registo detalhado é ativado e quando é desativado, para que o registro desses eventos detalhados seja ativado de forma seletiva.