Objeto de aplicación y DirectX

Los juegos DirectX para Plataforma universal de Windows (UWP) no usan muchos elementos y objetos de la interfaz de usuario de Windows. Como se ejecutan a un nivel más bajo en la pila de Windows Runtime, deben interoperar con el marco de la interfaz de usuario de una manera más básica, mediante el acceso y la interoperación con el objeto de aplicación directamente. Aprende cuándo y cómo se realiza esta interoperación y de qué manera puedes usar eficazmente, como desarrollador de DirectX, este modelo en el desarrollo de una aplicación para UWP.

Consulte el glosario de gráficos de Direct3D para obtener información sobre los conceptos o términos de gráficos desconocidos que se encuentran durante la lectura.

Los espacios de nombres de interfaz de usuario principales

En primer lugar, veamos los espacios de nombres de Windows Runtime que debes incluir (con using) en la aplicación para UWP. Pronto los describiremos en detalle.

Nota

Si no estás desarrollando una aplicación para UWP, usa los componentes de la interfaz de usuario proporcionados en las bibliotecas y espacios de nombres específicos de JavaScript o XAML en lugar de los tipos proporcionados en estos espacios de nombres.

El objeto de aplicación de Windows Runtime

Es posible que desees que tu aplicación para UWP obtenga una ventana y un proveedor de vistas de los que se pueda obtener una vista y a los que se pueda conectar la cadena de intercambio (los búferes de presentación). También puedes enlazar esta vista a los eventos específicos de ventana para la aplicación en ejecución. Para obtener la ventana primaria del objeto de aplicación, definida por el tipo CoreWindow, cree un tipo que implemente IFrameworkViewSource. Para obtener un ejemplo de código de C++/WinRT que muestra cómo implementar IFrameworkViewSource,vea Interoperación nativa de Composition con DirectX y Direct2D.

Este es el conjunto básico de pasos para obtener una ventana mediante el marco de interfaz de usuario principal.

  1. Crea un tipo que implemente IFrameworkView. Esta será la vista.

    En este tipo, define:

    • Un método Initialize que use una instancia de CoreApplicationView como parámetro. Puedes obtener una instancia de este tipo si llamas a CoreApplication.CreateNewView. El objeto de aplicación lo llama cuando se inicia la aplicación.
    • Un método SetWindow que use una instancia de CoreWindow como parámetro. Puedes obtener una instancia de este tipo si accedes a la propiedad CoreWindow en la nueva instancia de CoreApplicationView.
    • Un método Load que use una cadena para un punto de entrada como parámetro único. El objeto aplicación proporciona la cadena de punto de entrada al llamar a este método. Ahora es cuando hay que configurar recursos. Aquí es donde crearás los recursos de dispositivo. El objeto de aplicación lo llama cuando se inicia la aplicación.
    • Un método Run que active el objeto CoreWindow e inicie el distribuidor de eventos de ventana. El objeto aplicación lo llama cuando se inicia el proceso de la aplicación.
    • Un método Uninitialize que limpie los recursos configurado en la llamada a Load. El objeto de aplicación llama a este método cuando se cierra la aplicación.
  2. Crea un tipo que implemente IFrameworkViewSource. Este será el proveedor de vistas.

    En este tipo, define:

    • Un método denominado CreateView que devuelva una instancia de la implementación de IFrameworkView, tal como la creaste en el paso 1.
  3. Pase una instancia del proveedor de vistas a CoreApplication.Run desde la base de datos principal.

Con estos conceptos básicos en mente, veamos otras opciones para ampliar este enfoque.

Tipos de interfaz de usuario principales

Estos son otros tipos de interfaz de usuario principales de Windows Runtime que podrían resultar útiles:

Puedes usar estos tipos para acceder a la vista de la aplicación, en concreto, a las partes que extraen el contenido de la ventana primaria de la aplicación y controlan los eventos desencadenados de esa ventana. El proceso de la ventana de la aplicación es un contenedor uniproceso de aplicación (ASTA) que está aislado y controla todas las devoluciones de llamadas.

La vista de la aplicación la genera el proveedor de vistas para la ventana de aplicación y, en la mayoría de los casos, la implementará un paquete de marco específico o el mismo sistema, por lo que no tendrás que implementarla. Para DirectX debes implementar un proveedor de vistas ligero, como se indicó previamente. Hay una relación uno a uno específica entre los siguientes componentes y comportamientos:

  • Una vista de la aplicación, que se representa mediante el tipo CoreApplicationView, y que define los métodos de actualización de la ventana.
  • Un ASTA, cuya atribución define el comportamiento de subprocesos para la aplicación. No puedes crear instancias de tipos con atributos STA de COM en un ASTA.
  • Un proveedor de vistas, que la aplicación obtiene del sistema o que puedes implementar.
  • Una ventana primaria, que se representa mediante el tipo CoreWindow.
  • Origen para todos los eventos de activación. Las vistas y las ventanas tienen eventos de activación independientes.

En resumen, el objeto aplicación proporciona una fábrica de proveedores de vistas. Crea un proveedor de vistas y una instancia de ventana primaria para la aplicación. El proveedor de vistas define la vista de la aplicación para la ventana primaria de la aplicación. Veamos ahora los detalles de la vista y la ventana primaria.

Comportamientos y propiedades de CoreApplicationView

CoreApplicationView representa la vista actual de la aplicación. El singleton de aplicación crea la vista de la aplicación durante la inicialización, pero la vista permanece inactiva hasta que se activa. Puedes obtener la clase CoreWindow que muestra la vista al accediendo a la propiedad CoreApplicationView.CoreWindow de esta y puedes controlar eventos de activación y desactivación para la vista registrando delegados con el evento CoreApplicationView.Activated.

Comportamientos y propiedades de CoreWindow

La ventana primaria, que es una instancia de CoreWindow, se crea y se pasa al proveedor de vistas cuando se inicializa el objeto de aplicación. Si la aplicación tiene una ventana para mostrar, la muestra; de lo contrario, simplemente inicializa la vista.

CoreWindow proporciona diversos eventos específicos de entrada y comportamientos básicos de ventana. Puedes controlar estos eventos registrando tus propios delegados con ellos.

También puede obtener el distribuidor de eventos de ventana para la ventana accediendo a la propiedad CoreWindow.Dispatcher, que proporciona una instancia de CoreDispatcher.

Comportamientos y propiedades de CoreDispatcher

Puedes determinar el comportamiento de los subprocesos de la distribución de eventos para una ventana con el tipo CoreDispatcher. En este tipo, hay un método especialmente importante: el método CoreDispatcher.ProcessEvents, que inicia el procesamiento de eventos de ventana. Llamar a este método con la opción incorrecta para la aplicación puede provocar comportamientos de procesamiento de eventos inesperados de todo tipo.

Opción CoreProcessEventsOption Descripción
CoreProcessEventsOption.ProcessOneAndAllPending Distribuye todos los eventos disponibles actualmente en la cola. Si no hay eventos pendientes, espera al siguiente evento nuevo.
CoreProcessEventsOption.ProcessOneIfPresent Distribuye un evento si está pendiente en la cola. Si no hay eventos pendientes, no espera a que se genere un evento nuevo, sino que se devuelve inmediatamente.
CoreProcessEventsOption.ProcessUntilQuit Espera eventos nuevos y distribuye todos los eventos disponibles. Continúa con este comportamiento hasta que la ventana se cierra o la aplicación llama al método Close en la instancia de CoreWindow.
CoreProcessEventsOption.ProcessAllIfPresent Distribuye todos los eventos disponibles actualmente en la cola. Si no hay eventos pendientes, se devuelve inmediatamente.

UWP con DirectX debe usar la opción CoreProcessEventsOption.ProcessAllIfPresent para evitar comportamientos de bloqueo que puedan interrumpir las actualizaciones de elementos gráficos.

Consideraciones de ASTA para desarrolladores con DirectX

El objeto de aplicación que define la representación en tiempo de ejecución de tu aplicación para UWP y DirectX usa un modelo de subprocesos denominado contenedor uniproceso de aplicación (ASTA) para hospedar las vistas de la interfaz de usuario de tu aplicación. Si estás desarrollando una aplicación para UWP y DirectX, estarás familiarizado con las propiedades de ASTA, porque los subprocesos que envíes desde tu aplicación para UWP y DirectX deben usar las API de Windows::System::Threading o deben usar CoreWindow::CoreDispatcher. (Puedes obtener el objeto CoreWindow del ASTA llamando a CoreWindow::GetForCurrentThread desde la aplicación).

Lo más importante que debe tener en cuenta, como desarrollador de una aplicación DirectX para UWP, es que debe habilitar el subproceso de la aplicación para enviar subprocesos MTA estableciendo Platform::MTAThread en main().

[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
    auto myDXAppSource = ref new MyDXAppSource(); // your view provider factory 
    CoreApplication::Run(myDXAppSource);
    return 0;
}

Cuando se activa el objeto de aplicación de la aplicación DirectX para UWP, se crea el ASTA que se usará para la vista de la interfaz de usuario. El nuevo subproceso de ASTA llama a tu fábrica de proveedores de vistas para crear el proveedor de vistas de tu objeto de aplicación y, como resultado, el código de tu proveedor de vistas se ejecutará en ese subproceso de ASTA.

Además, cualquier subproceso que elabores a partir del ASTA debe estar en un MTA. Ten en cuenta que cualquier subproceso de MTA que elabores todavía puede crear problemas de reentrada y provocar un interbloqueo.

Si vas a portar el código existente para que se ejecute en el subproceso de ASTA, ten en cuenta estas consideraciones:

  • Los primitivos de espera, como CoWaitForMultipleObjects, se comportan de forma diferente en un ASTA que en un contenedor uniproceso (STA).

  • El bucle modal de llamada de COM opera de forma diferente en un ASTA. Ya no se pueden recibir llamadas no relacionadas mientras haya una llamada saliente en curso. Por ejemplo, el siguiente comportamiento creará un interbloqueo de un ASTA (y hará que la aplicación se bloquee inmediatamente después):

    1. El ASTA llama a un objeto MTA y pasa un puntero de interfaz P1.
    2. Después, el ASTA llama al mismo objeto MTA. El objeto MTA llama a P1 antes de volver al ASTA.
    3. P1 no puede entrar en el ASTA porque está bloqueado con una llamada no relacionada. Sin embargo, el subproceso de MTA está bloqueado mientras intenta realizar la llamada a P1.

    Para resolverlo:

    • Usa el patrón async definido en la Biblioteca de patrones de procesamiento paralelo (PPLTasks.h).
    • Llama a CoreDispatcher::ProcessEvents desde el ASTA de la aplicación (el subproceso principal de la aplicación) lo antes posible para permitir las llamadas arbitrarias.

    Dicho esto, no puedes depender del envío inmediato de llamadas no relacionadas al ASTA de tu aplicación. Para obtener más información sobre las llamadas asincrónicas, lea Programación asincrónica en C++.

En general, cuando diseñes tu aplicación para UWP, usa la clase CoreDispatcher para la clase CoreWindow y el método CoreDispatcher::ProcessEvents de tu aplicación para controlar todos los subprocesos de interfaz de usuario en vez de intentar crear y administrar tú mismo los subprocesos de MTA. Cuando necesites un subproceso independiente que no puedas controlar con CoreDispatcher, usa patrones asincrónicos y sigue las instrucciones mencionadas más arriba para evitar problemas de reentrada.