Interoperabilidade do Direct3D 12
D3D12 pode ser usado para gravar aplicativos componentes.
- visão geral do interoperabilidade
- razões para usar de interoperabilidade
- APIs de interoperabilidade
- tópicos relacionados
Visão geral da interoperabilidade
O D3D12 pode ser muito poderoso e permitir que os aplicativos escrevam código gráfico com eficiência semelhante ao console, mas nem todo aplicativo precisa reinventar a roda e gravar a totalidade do mecanismo de renderização do zero. Em alguns casos, outro componente ou biblioteca já o fez melhor ou, em outros casos, o desempenho de uma parte do código não é tão crítico quanto sua correção e legibilidade.
Esta seção aborda as seguintes técnicas de interoperabilidade:
- D3D12 e D3D12, no mesmo dispositivo
- D3D12 e D3D12, em dispositivos diferentes
- D3D12 e qualquer combinação de D3D11, D3D10 ou D2D, no mesmo dispositivo
- D3D12 e qualquer combinação de D3D11, D3D10 ou D2D, em dispositivos diferentes
- D3D12 e GDI, ou D3D12 e D3D11 e GDI
Motivos para usar a interoperabilidade
Há vários motivos pelos quais um aplicativo deseja interoperabilidade D3D12 com outras APIs. Alguns exemplos:
- Portabilidade incremental: desejando portar um aplicativo inteiro de D3D10 ou D3D11 para D3D12, mantendo-o funcional em estágios intermediários do processo de portabilidade (para habilitar o teste e a depuração).
- Código da caixa preta: desejando deixar uma parte específica de um aplicativo as-is ao portar o restante do código. Por exemplo, pode não haver necessidade de portar elementos de interface do usuário de um jogo.
- Componentes imutáveis: precisando usar componentes que não pertencem ao aplicativo, que não são gravados no destino D3D12.
- Um novo componente: não querer portar todo o aplicativo, mas querer usar um novo componente escrito usando D3D12.
Há quatro técnicas principais para interoperabilidade em D3D12:
- Um aplicativo pode optar por fornecer uma lista de comandos aberta a um componente, que registra alguns comandos de renderização adicionais para um destino de renderização já associado. Isso é equivalente a fornecer um contexto de dispositivo preparado para outro componente em D3D11 e é ótimo para itens como adicionar interface do usuário/texto a um buffer back já associado.
- Um aplicativo pode optar por fornecer uma fila de comandos a um componente, juntamente com um recurso de destino desejado. Isso equivale a usar apIs do ClearState ou DeviceContextState em D3D11 para fornecer um contexto de dispositivo limpo para outro componente. É assim que os componentes como o D2D operam.
- Um componente pode optar por um modelo em que produz uma lista de comandos, potencialmente em paralelo, que o aplicativo é responsável pelo envio posteriormente. Pelo menos um recurso deve ser fornecido entre os limites do componente. Essa mesma técnica está disponível na D3D11 usando contextos adiados, embora o desempenho em D3D12 seja mais desejável.
- Cada componente tem suas próprias filas e/ou dispositivos, e o aplicativo e os componentes precisam compartilhar recursos e informações de sincronização entre os limites do componente. Isso é semelhante ao
ISurfaceQueue
herdado e aoIDXGIKeyedMutexmais moderno.
As diferenças entre esses cenários é exatamente o que é compartilhado entre os limites do componente. Supõe-se que o dispositivo seja compartilhado, mas como ele é basicamente sem estado, ele não é realmente relevante. Os objetos de chave são a lista de comandos, a fila de comandos, os objetos de sincronização e os recursos. Cada um deles tem suas próprias complicações ao compartilhá-las.
Compartilhando uma lista de comandos
O método mais simples de interoperabilidade requer o compartilhamento apenas de uma lista de comandos com uma parte do mecanismo. Depois que as operações de renderização forem concluídas, a propriedade da lista de comandos retornará ao chamador. A propriedade da lista de comandos pode ser rastreada por meio da pilha. Como as listas de comandos são threaded simples, não há como um aplicativo fazer algo exclusivo ou inovador usando essa técnica.
Compartilhando uma fila de comandos
Provavelmente a técnica mais comum para vários componentes compartilhando um dispositivo no mesmo processo.
Quando a fila de comandos é a unidade de compartilhamento, é necessário haver uma chamada para o componente para que ele saiba que todas as listas de comandos pendentes precisam ser enviadas para a fila de comandos imediatamente (e todas as filas de comando internas precisam ser sincronizadas). Isso é equivalente à D3D11 API do Flush e é a única maneira que o aplicativo pode enviar suas próprias listas de comandos ou sincronizar primitivas.
Compartilhando primitivos de sincronização
O padrão esperado para um componente que opera em seus próprios dispositivos e/ou filas de comando será aceitar um ID3D12Fence ou identificador compartilhado, e o par UINT64 ao iniciar seu trabalho, que ele aguardará e, em seguida, um segundo ID3D12Fence ou identificador compartilhado, e par UINT64 que sinalizará quando todo o trabalho for concluído. Esse padrão corresponde à implementação atual de IDXGIKeyedMutex e do design de sincronização de modelo de inversão DWM/DXGI.
Compartilhamento de recursos
De longe, a parte mais complicada da gravação de um aplicativo D3D12 que aproveita vários componentes é como lidar com os recursos que são compartilhados entre os limites dos componentes. Isso ocorre principalmente devido ao conceito de estados de recurso. Embora alguns aspectos do design do estado do recurso sejam destinados a lidar com a sincronização da lista de comandos, outros têm impacto entre listas de comandos, afetando o layout do recurso e conjuntos válidos de operações ou características de desempenho do acesso aos dados do recurso.
Há dois padrões de lidar com essa complicação, ambos envolvendo essencialmente um contrato entre componentes.
- O contrato pode ser definido pelo desenvolvedor do componente e documentado. Isso pode ser tão simples quanto "o recurso deve estar no estado padrão quando o trabalho é iniciado e será colocado novamente no estado padrão quando o trabalho for feito" ou pode ter regras mais complicadas para permitir coisas como compartilhar um buffer de profundidade sem forçar a resolução de profundidade intermediária.
- O contrato pode ser definido pelo aplicativo em runtime, no momento em que o recurso é compartilhado entre os limites do componente. Ele consiste nas mesmas duas informações: o estado em que o recurso estará quando o componente começar a usá-lo e o estado em que o componente deverá deixá-lo quando for concluído.
Escolhendo um modelo de interoperabilidade
Para a maioria dos aplicativos D3D12, o compartilhamento de uma fila de comandos provavelmente é o modelo ideal. Ele permite a propriedade completa da criação e do envio do trabalho, sem que a sobrecarga de memória adicional tenha filas redundantes e sem o impacto perf de lidar com os primitivos de sincronização de GPU.
O compartilhamento de primitivos de sincronização é necessário quando os componentes precisam lidar com propriedades de fila diferentes, como tipo ou prioridade, ou quando o compartilhamento precisa abranger os limites do processo.
O compartilhamento ou a produção de listas de comandos não são amplamente usados externamente por componentes de terceiros, mas podem ser amplamente usados em componentes internos para um mecanismo de jogo.
APIs de interoperabilidade
O tópico Direct3D 11 on 12 orienta você pelo uso de grande parte da superfície da API relacionada aos tipos de interoperação descritos neste tópico.
Consulte também o método ID3D12Device::CreateSharedHandle, que você pode usar para compartilhar superfícies entre APIs gráficas do Windows.