O objeto do aplicativo e o DirectX
A Plataforma Universal do Windows (UWP) com jogos DirectX não usa muitos dos elementos e objetos da interface do usuário da interface do usuário do Windows. Em vez disso, como eles são executados em um nível inferior na pilha do Tempo de Execução do Windows, eles devem interoperar com a estrutura da interface do usuário de uma maneira mais fundamental: acessando e interoperando diretamente com o objeto do aplicativo. Saiba quando e como essa interoperação ocorre e como você, como desenvolvedor do DirectX, pode usar efetivamente esse modelo no desenvolvimento do seu aplicativo UWP.
Consulte o glossário de elementos gráficos do Direct3D para obter informações sobre termos ou conceitos gráficos desconhecidos que você encontra durante a leitura.
Os namespaces principais importantes da interface do usuário
Primeiro, vamos observar os namespaces do Tempo de Execução do Windows que você deve incluir (com using) em seu aplicativo UWP. Entramos nos detalhes daqui a pouco.
- Windows.ApplicationModel.Core
- Windows.ApplicationModel.Activation
- Windows.UI.Core
- Windows.System
- Windows.Foundation
Observação
Se você não estiver desenvolvendo um aplicativo UWP, use os componentes da interface do usuário fornecidos nas bibliotecas e namespaces específicos de JavaScript ou XAML em vez dos tipos fornecidos nesses namespaces.
O objeto de aplicativo do Tempo de Execução do Windows
Em seu aplicativo UWP, você deseja obter uma janela e um provedor de exibição do qual você pode obter uma exibição e ao qual você pode conectar sua cadeia de troca (seus buffers de exibição). Você também pode conectar essa exibição aos eventos específicos da janela para seu aplicativo em execução. Para obter a janela pai do objeto do aplicativo, definida pelo tipo CoreWindow, crie um tipo que implemente IFrameworkViewSource. Para obter um exemplo de código C++/WinRT mostrando como implementar IFrameworkViewSource, consulte Interoperação nativa de composição com DirectX e Direct2D.
Aqui está o conjunto básico de etapas para obter uma janela usando a estrutura de interface do usuário principal.
Crie um tipo que implemente IFrameworkView. Esta é a sua opinião.
Nesse tipo, defina:
- Um método Initialize que usa uma instância de CoreApplicationView como um parâmetro. Você pode obter uma instância desse tipo chamando CoreApplication.CreateNewView. O objeto app o chama quando o aplicativo é iniciado.
- Um método SetWindow que usa uma instância de CoreWindow como um parâmetro. Você pode obter uma instância desse tipo acessando a propriedade CoreWindow em sua nova instância CoreApplicationView.
- Um método Load que usa uma cadeia de caracteres para um ponto de entrada como o único parâmetro. O objeto app fornece a cadeia de caracteres do ponto de entrada quando você chama esse método. É aqui que você configura os recursos. Você cria os recursos do dispositivo aqui. O objeto app o chama quando o aplicativo é iniciado.
- Um método Run que ativa o objeto CoreWindow e inicia o dispatcher de eventos de janela. O objeto app o chama quando o processo do aplicativo é iniciado.
- Um método Uninitialize que limpa os recursos configurados na chamada para Load. O objeto app chama esse método quando o app é fechado.
Crie um tipo que implemente IFrameworkViewSource. Este é o seu provedor de exibição.
Nesse tipo, defina:
- Um método chamado CreateView que retorna uma instância da implementação de IFrameworkView, conforme criado na Etapa 1.
Passe uma instância do provedor de exibição para CoreApplication.Run de main.
Com esses princípios básicos em mente, vejamos mais opções que você tem para estender essa abordagem.
Principais tipos de interface do usuário
Aqui estão outros tipos principais de interface do usuário no Tempo de Execução do Windows que podem ser úteis:
- Windows.ApplicationModel.Core.CoreApplicationView
- Windows.UI.Core.CoreWindow
- Windows.UI.Core.CoreDispatcher
Você pode usar esses tipos para acessar a exibição do aplicativo, especificamente, os bits que desenham o conteúdo da janela pai do aplicativo e manipulam os eventos disparados para essa janela. O processo da janela do aplicativo é um ASTA (apartamento de thread único) de aplicativo isolado e que lida com todos os retornos de chamada.
A exibição do aplicativo é gerada pelo provedor de exibição da janela do aplicativo e, na maioria dos casos, será implementada por um pacote de estrutura específico ou pelo próprio sistema, portanto, você não precisa implementá-la por conta própria. Para o DirectX, você precisa implementar um provedor de exibição fina, conforme discutido anteriormente. Há uma relação específica de 1 para 1 entre os seguintes componentes e comportamentos:
- A exibição de um aplicativo, que é representada pelo tipo CoreApplicationView e que define os métodos para atualizar a janela.
- Um ASTA, cuja atribuição define o comportamento de encadeamento do aplicativo. Você não pode criar instâncias de tipos atribuídos a COM STA em um ASTA.
- Um provedor de exibição, que seu aplicativo obtém do sistema ou que você implementa.
- Uma janela pai, que é representada pelo tipo CoreWindow .
- Sourcing para todos os eventos de ativação. Exibições e janelas têm eventos de ativação separados.
Em resumo, o objeto app fornece uma fábrica de provedores de exibição. Ele cria um provedor de exibição e instancia uma janela pai para o aplicativo. O provedor de exibição define a exibição do aplicativo para a janela pai do aplicativo. Agora, vamos discutir as especificidades da exibição e da janela pai.
Comportamentos e propriedades do CoreApplicationView
CoreApplicationView representa a exibição atual do aplicativo. O singleton do aplicativo cria o modo de exibição do aplicativo durante a inicialização, mas o modo de exibição permanece inativo até que seja ativado. Você pode obter o CoreWindow que exibe o modo de exibição acessando a propriedade CoreApplicationView.CoreWindow nele e pode manipular eventos de ativação e desativação para o modo de exibição registrando delegados com o evento CoreApplicationView.Activated.
Comportamentos e propriedades do CoreWindow
A janela pai, que é uma instância CoreWindow , é criada e passada para o provedor de exibição quando o objeto do aplicativo é inicializado. Se o aplicativo tiver uma janela para exibir, ele a exibirá; caso contrário, ele simplesmente inicializa a exibição.
CoreWindow fornece vários eventos específicos para comportamentos de entrada e de janela básicos. Você pode manipular esses eventos registrando seus próprios delegados com eles.
Você também pode obter o dispatcher de eventos de janela para a janela acessando a propriedade CoreWindow.Dispatcher, que fornece uma instância de CoreDispatcher.
Comportamentos e propriedades do CoreDispatcher
Você pode determinar o comportamento de threading da expedição de eventos para uma janela com o tipo CoreDispatcher. Nesse tipo, há um método particularmente importante: o método CoreDispatcher.ProcessEvents, que inicia o processamento de eventos de janela. Chamar esse método com a opção errada para seu aplicativo pode levar a todos os tipos de comportamentos inesperados de processamento de eventos.
Opção CoreProcessEventsOption | Descrição |
---|---|
CoreProcessEventsOption.ProcessOneAndAllPending | Despache todos os eventos disponíveis no momento na fila. Se nenhum evento estiver pendente, aguarde o próximo novo evento. |
CoreProcessEventsOption.ProcessOneIfPresent | Despache um evento se ele estiver pendente na fila. Se nenhum evento estiver pendente, não espere que um novo evento seja gerado, mas retorne imediatamente. |
CoreProcessEventsOption.ProcessUntilQuit | Aguarde novos eventos e despache todos os eventos disponíveis. Continue esse comportamento até que a janela seja fechada ou o aplicativo chame o método Close na instância CoreWindow. |
CoreProcessEventsOption.ProcessAllIfPresent | Despache todos os eventos disponíveis no momento na fila. Se nenhum evento estiver pendente, retorne imediatamente. |
A UWP que usa DirectX deve usar a opção CoreProcessEventsOption.ProcessAllIfPresent para evitar comportamentos de bloqueio que possam interromper as atualizações gráficas.
Considerações do ASTA para desenvolvedores do DirectX
O objeto de aplicativo que define a representação em tempo de execução do aplicativo UWP e DirectX usa um modelo de threading chamado ASTA (Application Single-Threaded Apartment) para hospedar as exibições da interface do usuário do aplicativo. Se você estiver desenvolvendo um aplicativo UWP e DirectX, estará familiarizado com as propriedades de um ASTA, pois qualquer thread que você expedir de seu aplicativo UWP e DirectX deve usar as APIs Windows::System::Threading ou usar CoreWindow::CoreDispatcher. (Você pode obter o CoreWindow para o ASTA chamando CoreWindow::GetForCurrentThread do seu aplicativo.)
A coisa mais importante para você estar ciente, como desenvolvedor de um aplicativo UWP DirectX, é que você deve habilitar o thread do aplicativo para expedir threads MTA definindo Platform::MTAThread em main().
[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
auto myDXAppSource = ref new MyDXAppSource(); // your view provider factory
CoreApplication::Run(myDXAppSource);
return 0;
}
Quando o objeto de aplicativo para seu aplicativo DirectX UWP é ativado, ele cria o ASTA que será usado para o modo de exibição da interface do usuário. O novo thread ASTA chama sua fábrica de provedor de exibição para criar o provedor de exibição para seu objeto de aplicativo e, como resultado, o código do provedor de exibição será executado nesse thread ASTA.
Além disso, qualquer thread que você desmembrar do ASTA deve estar em um MTA. Lembre-se de que qualquer thread MTA que você desmembrar ainda pode criar problemas de reentrância e resultar em um deadlock.
Se você estiver portando o código existente para ser executado no thread ASTA, lembre-se destas considerações:
Primitivos de espera, como CoWaitForMultipleObjects, se comportam de maneira diferente em um ASTA do que em um STA.
O loop modal de chamada COM opera de forma diferente em um ASTA. Você não pode mais receber chamadas não relacionadas enquanto uma chamada de saída estiver em andamento. Por exemplo, o comportamento a seguir criará um deadlock de um ASTA (e travará imediatamente o aplicativo):
- O ASTA chama um objeto MTA e passa um ponteiro de interface P1.
- Posteriormente, o ASTA chama o mesmo objeto MTA. O objeto MTA chama P1 antes de retornar ao ASTA.
- P1 não pode entrar no ASTA, pois está bloqueado fazendo uma chamada não relacionada. No entanto, o thread MTA é bloqueado ao tentar fazer a chamada para P1.
Você pode resolver isso :
- Usando o padrão assíncrono definido na Biblioteca de Padrões Paralelos (PPLTasks.h)
- Chamar CoreDispatcher::P rocessEvents do ASTA do aplicativo (o thread principal do aplicativo) o mais rápido possível para permitir chamadas arbitrárias.
Dito isso, você não pode confiar na entrega imediata de chamadas não relacionadas ao ASTA do seu aplicativo. Para obter mais informações sobre chamadas assíncronas, leia Programação assíncrona em C++.
No geral, ao projetar seu aplicativo UWP, use o CoreDispatcher para CoreWindow e CoreDispatcher::P rocessEvents do aplicativo para lidar com todos os threads da interface do usuário em vez de tentar criar e gerenciar seus threads MTA por conta própria. Quando você precisar de um thread separado que não possa ser manipulado com o CoreDispatcher, use padrões assíncronos e siga as diretrizes mencionadas anteriormente para evitar problemas de reentrância.