Interoperabilidad de DirectX y XAML

Nota

Este tema se aplica a los juegos y aplicaciones de Plataforma universal de Windows (UWP) y a los tipos de los espacios de nombres Windows.UI.Xaml.Xxx (no Microsoft.UI.Xaml.Xxx).

Puedes usar el lenguaje xaml (Extensible Application Markup Language) junto con Microsoft DirectX en tu juego o aplicación de Plataforma universal de Windows (UWP). La combinación de XAML y DirectX te permite crear marcos de interfaz de usuario flexibles que interoperan con el contenido representado por DirectX; que es especialmente útil para las aplicaciones de uso intensivo de gráficos. En este tema se explica la estructura de una aplicación para UWP que usa DirectX e identifica los tipos importantes que se usarán al compilar la aplicación para UWP para trabajar con DirectX.

Si la aplicación se centra principalmente en la representación 2D, es posible que quieras usar la biblioteca de Windows Runtime Win2D. Microsoft mantiene esa biblioteca y se basa en la tecnología principal de Direct2D . Win2D simplifica enormemente el patrón de uso para implementar gráficos 2D e incluye abstracciones útiles para algunas de las técnicas descritas en este documento. Consulta la página del proyecto para obtener más detalles. Este documento ofrece orientación para aquellos desarrolladores de aplicaciones que decidan no utilizar Win2D.

Nota

Las API de DirectX no se definen como tipos de Windows Runtime, pero puedes usar C++/WinRT para desarrollar aplicaciones para UWP XAML que interoperan con DirectX. Si factorizas el código que llama a DirectX en su propio componente de C++/WinRT Windows Runtime (WRC), puedes usar ese CMR en una aplicación para UWP (incluso un C# uno) que, a continuación, combina XAML y DirectX.

XAML y DirectX

DirectX proporciona dos bibliotecas eficaces para gráficos 2D y 3D, respectivamente: Direct2D y Direct3D. Aunque XAML proporciona compatibilidad con primitivos y efectos 2D básicos, muchas aplicaciones de modelado y juegos necesitan compatibilidad con gráficos más complejas. Para estos, puedes usar Direct2D y Direct3D para representar los gráficos más complejos y usar XAML para elementos de interfaz de usuario (UI) más tradicionales.

Si vas a implementar la interoperabilidad personalizada de XAML y DirectX, debes conocer estos dos conceptos.

  • Las superficies compartidas son regiones de tamaño de la pantalla, definidas por XAML, que puedes usar DirectX para dibujar indirectamente mediante tipos Windows::UI::Xaml::Media::ImageSource . En el caso de las superficies compartidas, no controla el tiempo preciso de cuándo aparece el nuevo contenido en pantalla. En su lugar, las actualizaciones de la superficie compartida se sincronizan con las actualizaciones del marco XAML.
  • Una cadena de intercambio representa una colección de búferes que se usan para mostrar gráficos con una latencia mínima. Normalmente, una cadena de intercambio se actualiza a 60 fotogramas por segundo por separado del subproceso de la interfaz de usuario. Sin embargo, una cadena de intercambio usa más recursos de memoria y CPU para admitir actualizaciones rápidas y es relativamente difícil de usar, ya que tiene que administrar varios subprocesos.

Tenga en cuenta lo que usa DirectX para. ¿Se usará para componer o animar un único control que se ajuste a las dimensiones de la ventana de presentación? ¿Contiene salida que tenga que representarse y controlarse en tiempo real, como sucede en un juego? Si es así, es probable que tenga que implementar una cadena de intercambio. De lo contrario, debe estar bien usando una superficie compartida.

Una vez que hayas determinado cómo quieres usar DirectX, usas uno de los siguientes tipos de Windows Runtime para incorporar la representación de DirectX en tu aplicación para UWP.

  • Si quieres crear una imagen estática o dibujar una imagen compleja a intervalos controlados por eventos, dibuja en una superficie compartida con Windows::UI::Xaml::Media::Imaging::SurfaceImageSource. Ese tipo controla una superficie de dibujo directX de tamaño. Normalmente se usa este tipo al crear una imagen o textura, como un mapa de bits, que luego se mostrará en un documento o elemento de interfaz de usuario. Recuerda que no funciona bien con la interactividad en tiempo real como, por ejemplo, un juego de alto rendimiento. Esto se debe a que las actualizaciones de un objeto SurfaceImageSource se sincronizan con las actualizaciones de la interfaz de usuario XAML y pueden introducir latencia en los comentarios visuales que proporciones al usuario, como una velocidad de fotogramas fluctuante o una respuesta deficiente percibida a la entrada en tiempo real. sin embargo, Novedades son lo suficientemente rápidos para controles dinámicos o simulaciones de datos.
  • Si la imagen es mayor que el espacio en pantalla proporcionado y el usuario puede desplazarse o ampliarla, use Windows::UI::Xaml::Media::Imaging::VirtualSurfaceImageSource. Ese tipo controla una superficie de dibujo directX de tamaño mayor que la pantalla. Tal como sucede con SurfaceImageSource, usarás este tipo dinámicamente al componer una imagen o control complejos. Asimismo, al igual que SurfaceImageSource, este tipo no funciona bien en juegos de alto rendimiento. Algunos ejemplos de elementos XAML que pueden usar un tipo VirtualSurfaceImageSource, son los controles de mapa o un visor de documentos con imágenes grandes o de alta densidad.
  • Si usas DirectX para presentar gráficos actualizados en tiempo real o en una situación en la que las actualizaciones deben aparecer en intervalos regulares de baja latencia, usa la clase SwapChainPanel , para que puedas actualizar los gráficos sin sincronizar con el temporizador de actualización del marco XAML. Con SwapChainPanel puedes acceder directamente a la cadena de intercambio del dispositivo gráfico (IDXGISwapChain1) y colocar XAML sobre el destino de representación. SwapChainPanel funciona perfectamente para juegos y aplicaciones DirectX de pantalla completa que requieren una interfaz de usuario basada en XAML. Debes conocer bien DirectX para usar este enfoque, incluida la infraestructura de gráficos de Microsoft DirectX (DXGI), Direct2D y las tecnologías de Direct3D. Para obtener más información, consulta Guía de programación para Direct3D 11.

SurfaceImageSource

SurfaceImageSource te proporciona una superficie compartida de DirectX en la que dibujar; luego compone los bits en el contenido de la aplicación.

Sugerencia

Las aplicaciones de ejemplo Interlineado (DirectWrite) y Fuentes descargables (DirectWrite) muestran SurfaceImageSource.

En un nivel muy alto, este es el proceso para crear y actualizar un SurfaceImageSource.

  • Cree un dispositivo Direct 3D, un dispositivo Direct 2D y un contexto de dispositivo 2D de Direct.
  • Crea un SurfaceImageSource y establece el dispositivo Direct 2D (o Direct 3D) en ese dispositivo.
  • Comience a dibujar en SurfaceImageSource para obtener una superficie DXGI.
  • Dibuje a la superficie DXGI con Direct2D (o Direct3D).
  • Termine el dibujo en SurfaceImageSource cuando haya terminado.
  • Establece SurfaceImageSource en una imagen XAML o ImageBrush para mostrarlo en la interfaz de usuario xaml.

Y aquí se profundiza más en esos pasos, con ejemplos de código fuente.

  1. Puede seguir el código que se muestra y se describe a continuación mediante la creación de un nuevo proyecto en Microsoft Visual Studio. Cree un proyecto aplicación en blanco (C++/WinRT). Elija como destino la versión más reciente disponible de manera general (es decir, no en versión preliminar) de Windows SDK.

  2. Abra pch.hy agregue lo siguiente que se incluye debajo de los que ya están allí.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    #include <winrt/Windows.UI.Xaml.Media.Imaging.h>
    
  3. Agregue la using directiva que se muestra a continuación a la parte superior de MainPage.cpp, debajo de las que ya están allí. También en MainPage.cpp, reemplace la implementación existente de MainPage::ClickHandler por la lista que se muestra a continuación. El código crea un dispositivo Direct 3D, un dispositivo Direct 2D y un contexto de dispositivo 2D de Direct. Para ello, llama a D3D11CreateDevice, D2D1CreateDevice e ID2D1Device::CreateDeviceContext.

    // MainPage.cpp | paste this below the existing using directives
    using namespace Windows::UI::Xaml::Media::Imaging;
    
    // MainPage.cpp | paste this to replace the existing MainPage::ClickHandler
    void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
    {
        myButton().Content(box_value(L"Clicked"));
    
        uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
        D3D_FEATURE_LEVEL featureLevels[] =
        {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
            D3D_FEATURE_LEVEL_9_3,
            D3D_FEATURE_LEVEL_9_2,
            D3D_FEATURE_LEVEL_9_1
        };
    
        // Create the Direct3D device.
        winrt::com_ptr<::ID3D11Device> d3dDevice;
        D3D_FEATURE_LEVEL supportedFeatureLevel;
        winrt::check_hresult(::D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            d3dDevice.put(),
            &supportedFeatureLevel,
            nullptr)
        );
    
        // Get the DXGI device.
        winrt::com_ptr<::IDXGIDevice> dxgiDevice{
            d3dDevice.as<::IDXGIDevice>() };
    
        // Create the Direct2D device and a corresponding context.
        winrt::com_ptr<::ID2D1Device> d2dDevice;
        ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
        winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
        winrt::check_hresult(
            d2dDevice->CreateDeviceContext(
                D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                d2dDeviceContext.put()
            )
        );
    }
    
  4. A continuación, agregue código para crear un SurfaceImageSource y establezca el dispositivo Direct 2D (o Direct 3D) en ese dispositivo mediante una llamada a ISurfaceImageSourceNativeWithD2D::SetDevice.

    Nota

    Si vas a dibujar a SurfaceImageSource desde un subproceso en segundo plano, también tendrás que asegurarte de que el dispositivo DXGI tiene habilitado el acceso multiproceso (como se muestra en el código siguiente). Por motivos de rendimiento, debe hacerlo solo si va a dibujar desde un subproceso en segundo plano.

    Definir el tamaño de la superficie compartida pasando los valores de alto y ancho al constructor SurfaceImageSource. También puedes indicar si la superficie necesita compatibilidad alfa (opacidad).

    Para establecer el dispositivo y ejecutar las operaciones de dibujo, necesitaremos un puntero a ISurfaceImageSourceNativeWithD2D. Para obtener uno, consulte el objeto SurfaceImageSource para su interfaz ISurfaceImageSourceNativeWithD2D subyacente.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    SurfaceImageSource surfaceImageSource(500, 500);
    
    winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> sisNativeWithD2D{
        surfaceImageSource.as<::ISurfaceImageSourceNativeWithD2D>() };
    
    // Associate the Direct2D device with the SurfaceImageSource.
    sisNativeWithD2D->SetDevice(d2dDevice.get());
    
    // To enable multi-threaded access (optional)
    winrt::com_ptr<::ID3D11Multithread> d3dMultiThread{
        d3dDevice.as<::ID3D11Multithread>() };
    d3dMultiThread->SetMultithreadProtected(true);
    
  5. Llama a ISurfaceImageSourceNativeWithD2D::BeginDraw para recuperar una superficie DXGI (una interfaz IDXGISurface ). Puede llamar a ISurfaceImageSourceNativeWithD2D::BeginDraw (y los comandos de dibujo posteriores) desde un subproceso en segundo plano si ha habilitado el acceso multiproceso. En este paso también creará un mapa de bits a partir de la superficie DXGI y lo establecerá en el contexto del dispositivo Direct 2D.

    En el parámetro offset , ISurfaceImageSourceNativeWithD2D::BeginDraw devuelve el desplazamiento de punto (un valor x,y) del rectángulo de destino actualizado. Puede usar ese desplazamiento para determinar dónde dibujar el contenido actualizado con ID2D1DeviceContext.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    winrt::com_ptr<::IDXGISurface> dxgiSurface;
    
    RECT updateRect{ 0, 0, 500, 500 };
    POINT offset{ 0, 0 };
    HRESULT beginDrawHR = sisNativeWithD2D->BeginDraw(
        updateRect,
        __uuidof(::IDXGISurface),
        dxgiSurface.put_void(),
        &offset);
    
    // Create render target.
    winrt::com_ptr<::ID2D1Bitmap1> bitmap;
    winrt::check_hresult(
        d2dDeviceContext->CreateBitmapFromDxgiSurface(
            dxgiSurface.get(),
            nullptr,
            bitmap.put()
        )
    );
    
    // Set context's render target.
    d2dDeviceContext->SetTarget(bitmap.get());
    
  6. Usa el contexto del dispositivo Direct 2D para dibujar el contenido de SurfaceImageSource. Solo se dibuja el área especificada para la actualización en el paso anterior del parámetro updateRect .

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED ||
        beginDrawHR == DXGI_ERROR_DEVICE_RESET ||
        beginDrawHR == D2DERR_RECREATE_TARGET)
    {
        // The Direct3D and Direct2D devices were lost and need to be re-created.
        // Recovery steps are:
        // 1) Re-create the Direct3D and Direct2D devices
        // 2) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the new Direct2D
        //    device
        // 3) Redraw the contents of the SurfaceImageSource
    }
    else if (beginDrawHR == E_SURFACE_CONTENTS_LOST)
    {
        // The devices were not lost but the entire contents of the surface
        // were. Recovery steps are:
        // 1) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the Direct2D 
        //    device again
        // 2) Redraw the entire contents of the SurfaceImageSource
    }
    else
    {
        // Draw using Direct2D context.
        d2dDeviceContext->BeginDraw();
    
        d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
        winrt::com_ptr<::ID2D1SolidColorBrush> brush;
        winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(
            D2D1::ColorF(D2D1::ColorF::Chocolate),
            D2D1::BrushProperties(0.8f),
            brush.put()));
    
        D2D1_SIZE_F const size{ 500, 500 };
        D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
        d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
        d2dDeviceContext->EndDraw();
    }
    
  7. Llame a ISurfaceImageSourceNativeWithD2D::EndDraw para completar el mapa de bits (debe llamar a ISurfaceImageSourceNativeWithD2D::EndDraw solo desde el subproceso de la interfaz de usuario). A continuación, establece SurfaceImageSource en una imagen XAML (o ImageBrush) para mostrarlo en tu interfaz de usuario XAML.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    sisNativeWithD2D->EndDraw();
    
    // The SurfaceImageSource object's underlying 
    // ISurfaceImageSourceNativeWithD2D object will contain the completed bitmap.
    
    theImage().Source(surfaceImageSource);
    

    Nota

    La llamada a SurfaceImageSource::SetSource (heredada de IBitmapSource::SetSource) produce actualmente una excepción. No lo llames desde el objeto SurfaceImageSource .

    Nota

    Evite dibujar en SurfaceImageSource mientras la ventana está oculta o inactiva; de lo contrario, se producirá un error en las API ISurfaceImageSourceNativeWithD2D . Controle los eventos relacionados con la visibilidad de la ventana y la suspensión de la aplicación para lograrlo.

  8. Por último, agregue el siguiente elemento Image dentro del marcado XAML existente en MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  9. Ya puede compilar y ejecutar la aplicación. Haga clic en el botón para ver el contenido de SurfaceImageSource mostrado en la imagen.

    Contorno rectángulo de color naranja grueso y oscuro en un fondo naranja más claro

VirtualSurfaceImageSource

VirtualSurfaceImageSource extiende SurfaceImageSource y es para escenarios en los que el contenido es potencialmente demasiado grande para caber en la pantalla a la vez (o demasiado grande para caber en la memoria de vídeo como una sola textura), por lo que el contenido debe virtualizarse para que se represente de forma óptima. Por ejemplo, asignar aplicaciones o lienzos de documentos grandes.

Sugerencia

La aplicación de ejemplo de entrada manuscrita compleja muestra VirtualSurfaceImageSource.

VirtualSurfaceImageSource difiere de SurfaceImageSource en que usa una devolución de llamada( IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded), que se implementa para actualizar las regiones de la superficie a medida que se ven en la pantalla. No es necesario borrar las regiones que están ocultas, ya que el marco XAML se encarga de eso por ti.

Es una buena idea familiarizarse con SurfaceImageSource (consulta la sección SurfaceImageSource anterior) antes de abordar VirtualSurfaceImageSource. Pero en un nivel muy alto, este es el proceso para crear y actualizar un VirtualSurfaceImageSource.

  • Implemente la interfaz IVirtualSurfaceImageSourceCallbackNative .
  • Cree un dispositivo Direct 3D, un dispositivo Direct 2D y un contexto de dispositivo 2D de Direct.
  • Cree un elemento VirtualSurfaceImageSource y establezca el dispositivo Direct 2D (o Direct 3D) en él.
  • Llame a RegisterForUpdatesNeeded en VirtualSurfaceImageSource.
  • En la devolución de llamada UpdatesNeededed , llame a GetUpdateRectCount y GetUpdateRects.
  • Representa los rectángulos de actualización (con BeginDraw EndDraw/ igual que para SurfaceImageSource).
  • Establece SurfaceImageSource en una imagen XAML o ImageBrush para mostrarlo en la interfaz de usuario xaml.

Y aquí se profundiza más en esos pasos, con ejemplos de código fuente.

  1. Puede seguir el código que se muestra y se describe a continuación mediante la creación de un nuevo proyecto en Microsoft Visual Studio. Cree un proyecto aplicación en blanco (C++/WinRT) y asígnele el nombre VSISDemo (es importante asignarle este nombre al proyecto si va a copiar y pegar en las listas de código que se indican a continuación). Elija como destino la versión más reciente disponible de manera general (es decir, no en versión preliminar) de Windows SDK.

  2. Abra pch.hy agregue lo siguiente que se incluye debajo de los que ya están allí.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    #include <winrt/Windows.UI.Xaml.Media.Imaging.h>
    
  3. En este paso, proporcionará una implementación de la interfaz IVirtualSurfaceUpdatesCallbackNative . Agregue un nuevo elemento Archivo de encabezado (.h) al proyecto y asígnele el nombre CallbackImplementation.h. Reemplace el contenido de ese archivo por la lista siguiente. El código se explica después de la lista.

    #include "pch.h"
    
    namespace winrt::VSISDemo::implementation
    {
        struct CallbackImplementation : winrt::implements<CallbackImplementation, ::IVirtualSurfaceUpdatesCallbackNative>
        {
            CallbackImplementation(
                winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> sisNativeWithD2D,
                winrt::com_ptr<::IVirtualSurfaceImageSourceNative> const& vsisNative,
                winrt::com_ptr<::ID2D1DeviceContext> const& d2dDeviceContext) :
                m_sisNativeWithD2D(sisNativeWithD2D),
                m_vsisNative(vsisNative),
                m_d2dDeviceContext(d2dDeviceContext)
            {}
    
            IFACEMETHOD(UpdatesNeeded)()
            {
                HRESULT hr = S_OK;
    
                ULONG drawingBoundsCount = 0;
                m_vsisNative->GetUpdateRectCount(&drawingBoundsCount);
    
                std::unique_ptr<RECT[]> drawingBounds(
                    new RECT[drawingBoundsCount]);
    
                m_vsisNative->GetUpdateRects(
                    drawingBounds.get(),
                    drawingBoundsCount);
    
                for (ULONG i = 0; i < drawingBoundsCount; ++i)
                {
                    winrt::com_ptr<::IDXGISurface> dxgiSurface;
    
                    POINT offset{ 0, 0 };
                    HRESULT beginDrawHR = m_sisNativeWithD2D->BeginDraw(
                        drawingBounds[i],
                        __uuidof(::IDXGISurface),
                        dxgiSurface.put_void(),
                        &offset);
    
                    // Create render target.
                    winrt::com_ptr<::ID2D1Bitmap1> bitmap;
                    winrt::check_hresult(
                        m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
                            dxgiSurface.get(),
                            nullptr,
                            bitmap.put()
                        )
                    );
    
                    // Set context's render target.
                    m_d2dDeviceContext->SetTarget(bitmap.get());
    
                    if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED ||
                        beginDrawHR == DXGI_ERROR_DEVICE_RESET ||
                        beginDrawHR == D2DERR_RECREATE_TARGET)
                    {
                        // The Direct3D and Direct2D devices were lost and need to be re-created.
                        // Recovery steps are:
                        // 1) Re-create the Direct3D and Direct2D devices
                        // 2) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the new Direct2D
                        //    device
                        // 3) Redraw the contents of the SurfaceImageSource
                    }
                    else if (beginDrawHR == E_SURFACE_CONTENTS_LOST)
                    {
                        // The devices were not lost but the entire contents of the surface
                        // were. Recovery steps are:
                        // 1) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the Direct2D 
                        //    device again
                        // 2) Redraw the entire contents of the SurfaceImageSource
                    }
                    else
                    {
                        // Draw using Direct2D context.
                        m_d2dDeviceContext->BeginDraw();
    
                        m_d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
                        winrt::com_ptr<::ID2D1SolidColorBrush> brush;
                        winrt::check_hresult(m_d2dDeviceContext->CreateSolidColorBrush(
                            D2D1::ColorF(D2D1::ColorF::Chocolate),
                            D2D1::BrushProperties(0.8f),
                            brush.put()));
    
                        D2D1_SIZE_F const size{ drawingBounds[i].right - drawingBounds[i].left, drawingBounds[i].bottom - drawingBounds[i].top };
                        D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
                        m_d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
                        m_d2dDeviceContext->EndDraw();
                    }
    
                    m_sisNativeWithD2D->EndDraw();
                }
    
                return hr;
            }
    
        private:
            winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> m_sisNativeWithD2D{ nullptr };
            winrt::com_ptr<::IVirtualSurfaceImageSourceNative> m_vsisNative{ nullptr };
            winrt::com_ptr<::ID2D1DeviceContext> m_d2dDeviceContext{ nullptr };
        };
    }
    

    Cada vez que es necesario actualizar una región de VirtualSurfaceImageSource , el marco llama a la implementación de IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded (mostrado anteriormente).

    Esto puede ocurrir cuando el marco determina que la región debe dibujarse (cuando el usuario realiza un movimiento panorámico o amplía la vista de la superficie, por ejemplo), o después de que la aplicación haya llamado A IVirtualSurfaceImageSourceNative::Invalidate en esa región.

    En la implementación de IVirtualSurfaceImageSourceNative::UpdatesNeeded, use los métodos IVirtualSurfaceImageSourceNative::GetUpdateRectCount e IVirtualSurfaceImageSourceNative::GetUpdateRects para determinar qué regiones de la superficie deben dibujarse.

    Para cada región que se debe actualizar, dibuje el contenido específico de esa región, pero restrinja el dibujo a las regiones limitadas para mejorar el rendimiento. Los detalles de llamar a los métodos ISurfaceImageSourceNativeWithD2D son los mismos que para SurfaceImageSource (consulta la sección SurfaceImageSource anterior).

    Nota

    Evite dibujar en VirtualSurfaceImageSource mientras la ventana está oculta o inactiva; de lo contrario, se producirá un error en las API ISurfaceImageSourceNativeWithD2D . Controle los eventos relacionados con la visibilidad de la ventana y la suspensión de la aplicación para lograrlo.

  4. En la clase MainPage , agregaremos un miembro de tipo CallbackImplementation. También crearemos un dispositivo Direct 3D, un dispositivo Direct 2D y un contexto de dispositivo 2D de Direct. Para ello, llamaremos a D3D11CreateDevice, D2D1CreateDevice y ID2D1Device::CreateDeviceContext.

    Reemplace el contenido de MainPage.idl, MainPage.hy MainPage.cpp por el contenido de las listas siguientes.

    // MainPage.idl
    namespace VSISDemo
    {
        [default_interface]
        runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
        {
            MainPage();
        }
    }
    
    // MainPage.h
    #pragma once
    
    #include "MainPage.g.h"
    #include "CallbackImplementation.h"
    
    namespace winrt::VSISDemo::implementation
    {
        struct MainPage : MainPageT<MainPage>
        {
            MainPage();
            void ClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args);
    
        private:
            winrt::com_ptr<::IVirtualSurfaceUpdatesCallbackNative> m_cbi{ nullptr };
        };
    }
    
    namespace winrt::VSISDemo::factory_implementation
    {
        struct MainPage : MainPageT<MainPage, implementation::MainPage>
        {
        };
    }
    
    // MainPage.cpp
    #include "pch.h"
    #include "MainPage.h"
    #include "MainPage.g.cpp"
    
    using namespace winrt;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Media::Imaging;
    
    namespace winrt::VSISDemo::implementation
    {
        MainPage::MainPage()
        {
            InitializeComponent();
        }
    
        void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
        {
            myButton().Content(box_value(L"Clicked"));
    
            uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
            D3D_FEATURE_LEVEL featureLevels[] =
            {
                D3D_FEATURE_LEVEL_11_1,
                D3D_FEATURE_LEVEL_11_0,
                D3D_FEATURE_LEVEL_10_1,
                D3D_FEATURE_LEVEL_10_0,
                D3D_FEATURE_LEVEL_9_3,
                D3D_FEATURE_LEVEL_9_2,
                D3D_FEATURE_LEVEL_9_1
            };
    
            // Create the Direct3D device.
            winrt::com_ptr<::ID3D11Device> d3dDevice;
            D3D_FEATURE_LEVEL supportedFeatureLevel;
            winrt::check_hresult(::D3D11CreateDevice(
                nullptr,
                D3D_DRIVER_TYPE_HARDWARE,
                0,
                creationFlags,
                featureLevels,
                ARRAYSIZE(featureLevels),
                D3D11_SDK_VERSION,
                d3dDevice.put(),
                &supportedFeatureLevel,
                nullptr)
            );
    
            // Get the Direct3D device.
            winrt::com_ptr<::IDXGIDevice> dxgiDevice{
                d3dDevice.as<::IDXGIDevice>() };
    
            // Create the Direct2D device and a corresponding context.
            winrt::com_ptr<::ID2D1Device> d2dDevice;
            ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
            winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
            winrt::check_hresult(
                d2dDevice->CreateDeviceContext(
                    D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                    d2dDeviceContext.put()
                )
            );
        }
    }
    
  5. A continuación, agregue código para crear un elemento VirtualSurfaceImageSource con el tamaño que desee y establezca el dispositivo Direct 2D (o Direct 3D) en ese dispositivo mediante una llamada a ISurfaceImageSourceNativeWithD2D::SetDevice.

    Nota

    Si va a dibujar a virtualSurfaceImageSource desde un subproceso en segundo plano, también deberá asegurarse de que el dispositivo DXGI tiene habilitado el acceso multiproceso (como se muestra en el código siguiente). Por motivos de rendimiento, debe hacerlo solo si va a dibujar desde un subproceso en segundo plano.

    Para establecer el dispositivo y ejecutar las operaciones de dibujo, necesitaremos un puntero a ISurfaceImageSourceNativeWithD2D. Para obtener uno, consulte el objeto VirtualSurfaceImageSource para su interfaz ISurfaceImageSourceNativeWithD2D subyacente.

    Consulte también IVirtualSurfaceImageSourceNative y llame a IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded, proporcionando su implementación de IVirtualSurfaceUpdatesCallbackNative.

    A continuación, establece SurfaceImageSource en una imagen XAML (o ImageBrush) para mostrarlo en tu interfaz de usuario XAML.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    VirtualSurfaceImageSource virtualSurfaceImageSource(2000, 2000);
    
    winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> sisNativeWithD2D{
        virtualSurfaceImageSource.as<::ISurfaceImageSourceNativeWithD2D>() };
    
    // Associate the Direct2D device with the SurfaceImageSource.
    sisNativeWithD2D->SetDevice(d2dDevice.get());
    
    // To enable multi-threaded access (optional)
    winrt::com_ptr<::ID3D11Multithread> d3dMultiThread{
        d3dDevice.as<::ID3D11Multithread>() };
    d3dMultiThread->SetMultithreadProtected(true);
    
    winrt::com_ptr<::IVirtualSurfaceImageSourceNative> vsisNative{
        virtualSurfaceImageSource.as<::IVirtualSurfaceImageSourceNative>() };
    
    m_cbi = winrt::make<CallbackImplementation>(sisNativeWithD2D, vsisNative, d2dDeviceContext);
    vsisNative->RegisterForUpdatesNeeded(m_cbi.as<::IVirtualSurfaceUpdatesCallbackNative>().get());
    
    // The SurfaceImageSource object's underlying 
    // ISurfaceImageSourceNativeWithD2D object will contain the completed bitmap.
    
    theImage().Source(virtualSurfaceImageSource);
    
  6. Por último, agregue el siguiente elemento Image dentro del marcado XAML existente en MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  7. Ya puede compilar y ejecutar la aplicación. Haga clic en el botón para ver el contenido de VirtualSurfaceImageSource mostrado en la imagen.

SwapChainPanel y juegos

SwapChainPanel es el tipo de Windows Runtime diseñado para elementos gráficos y juegos de alto rendimiento, en los que puedes administrar la cadena de intercambio directamente. En este caso, puedes crear tu propia cadena de intercambio de DirectX y administrar la presentación del contenido representado. Otra característica de SwapChainPanel es que puedes superponer otros elementos XAML delante de él.

Para garantizar un buen rendimiento, existen ciertas limitaciones en el tipo SwapChainPanel .

  • No debe haber más de 4 instancias swapChainPanel por aplicación.
  • Debe establecer el alto y el ancho de la cadena de intercambio de DirectX (en DXGI_SWAP_CHAIN_DESC1) en las dimensiones actuales del elemento de la cadena de intercambio. Si no lo hace, el contenido para mostrar se escalará para ajustarse (mediante DXGI_SCALING_STRETCH).
  • Debe establecer el modo de escalado de la cadena de intercambio de DirectX (en DXGI_SWAP_CHAIN_DESC1) en DXGI_SCALING_STRETCH.
  • Debes crear la cadena de intercambio de DirectX llamando a IDXGIFactory2::CreateSwapChainForComposition.

Actualizas SwapChainPanel en función de las necesidades de tu aplicación y no sincronizas con las actualizaciones del marco XAML. Si necesitas sincronizar las actualizaciones de SwapChainPanel con las del marco XAML, registra el evento Windows::UI::Xaml::Media::CompositionTarget::Rendering . En caso contrario, deberás tener en cuenta los posibles problemas entre subprocesos al intentar actualizar los elementos XAML de un subproceso distinto al que actualiza el objeto SwapChainPanel.

Si necesita recibir una entrada de puntero de baja latencia a SwapChainPanel, use SwapChainPanel::CreateCoreIndependentInputSource. Ese método devuelve un objeto CoreIndependentInputSource que se puede usar para recibir eventos de entrada con una latencia mínima en un subproceso en segundo plano. Ten en cuenta que, una vez que se llama a este método, no se generarán eventos de entrada de puntero XAML normales para SwapChainPanel, ya que toda la entrada se redirigirá al subproceso en segundo plano.

Este es el proceso para crear y actualizar un objeto SwapChainPanel .

  1. Puede seguir el código que se muestra y se describe a continuación mediante la creación de un nuevo proyecto en Microsoft Visual Studio. Cree un proyecto aplicación en blanco (C++/WinRT) y asígnele el nombre SCPDemo (es importante asignarle el nombre a este proyecto si va a copiar y pegar en las listas de código que se indican a continuación). Elija como destino la versión más reciente disponible de manera general (es decir, no en versión preliminar) de Windows SDK.

  2. Abra pch.hy agregue lo siguiente que se incluye debajo de los que ya están allí.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    
  3. En la clase MainPage , primero crearemos un dispositivo Direct 3D, un dispositivo Direct 2D y un contexto de dispositivo 2D de Direct. Para ello, llamaremos a D3D11CreateDevice, D2D1CreateDevice y ID2D1Device::CreateDeviceContext.

    Reemplace el contenido de MainPage.idl, MainPage.hy MainPage.cpp por el contenido de las listas siguientes.

    // MainPage.idl
    namespace SCPDemo
    {
        [default_interface]
        runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
        {
            MainPage();
        }
    }
    
    // MainPage.h
    #pragma once
    
    #include "MainPage.g.h"
    
    namespace winrt::SCPDemo::implementation
    {
        struct MainPage : MainPageT<MainPage>
        {
            MainPage();
            void ClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args);
        };
    }
    
    namespace winrt::SCPDemo::factory_implementation
    {
        struct MainPage : MainPageT<MainPage, implementation::MainPage>
        {
        };
    }
    
    // MainPage.cpp
    #include "pch.h"
    #include "MainPage.h"
    #include "MainPage.g.cpp"
    
    using namespace winrt;
    using namespace Windows::UI::Xaml;
    
    namespace winrt::SCPDemo::implementation
    {
        MainPage::MainPage()
        {
            InitializeComponent();
        }
    
        void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
        {
            myButton().Content(box_value(L"Clicked"));
    
            uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
            D3D_FEATURE_LEVEL featureLevels[] =
            {
                D3D_FEATURE_LEVEL_11_1,
                D3D_FEATURE_LEVEL_11_0,
                D3D_FEATURE_LEVEL_10_1,
                D3D_FEATURE_LEVEL_10_0,
                D3D_FEATURE_LEVEL_9_3,
                D3D_FEATURE_LEVEL_9_2,
                D3D_FEATURE_LEVEL_9_1
            };
    
            // Create the Direct3D device.
            winrt::com_ptr<::ID3D11Device> d3dDevice;
            D3D_FEATURE_LEVEL supportedFeatureLevel;
            winrt::check_hresult(::D3D11CreateDevice(
                nullptr,
                D3D_DRIVER_TYPE_HARDWARE,
                0,
                creationFlags,
                featureLevels,
                ARRAYSIZE(featureLevels),
                D3D11_SDK_VERSION,
                d3dDevice.put(),
                &supportedFeatureLevel,
                nullptr)
            );
    
            // Get the Direct3D device.
            winrt::com_ptr<::IDXGIDevice> dxgiDevice{
                d3dDevice.as<::IDXGIDevice>() };
    
            // Create the Direct2D device and a corresponding context.
            winrt::com_ptr<::ID2D1Device> d2dDevice;
            ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
            winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
            winrt::check_hresult(
                d2dDevice->CreateDeviceContext(
                    D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                    d2dDeviceContext.put()
                )
            );
        }
    }
    
  4. Ajuste el marcado XAML en un elemento SwapChainPanel con un x:Name. Los elementos XAML ajustados se representarán delante de SwapChainPanel.

    <!-- MainPage.xaml -->
     <SwapChainPanel x:Name="swapChainPanel">
     	<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
     		<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
     	</StackPanel>
     </SwapChainPanel>
    

    A continuación, puede acceder a ese objeto SwapChainPanel a través de la función de descriptor de acceso con el mismo nombre, como veremos.

  5. A continuación, llame a IDXGIFactory2::CreateSwapChainForComposition para crear una cadena de intercambio.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    // Get the DXGI adapter.
    winrt::com_ptr< ::IDXGIAdapter > dxgiAdapter;
    dxgiDevice->GetAdapter(dxgiAdapter.put());
    
    // Get the DXGI factory.
    winrt::com_ptr< ::IDXGIFactory2 > dxgiFactory;
    dxgiFactory.capture(dxgiAdapter, &IDXGIAdapter::GetParent);
    
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc { 0 };
    swapChainDesc.Width = 500;
    swapChainDesc.Height = 500;
    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swapchain format.
    swapChainDesc.Stereo = false;
    swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = 2;
    swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // We recommend using this swap effect for all applications.
    swapChainDesc.Flags = 0;
    
    // Create a swap chain by calling IDXGIFactory2::CreateSwapChainForComposition.
    winrt::com_ptr< ::IDXGISwapChain1 > swapChain;
    dxgiFactory->CreateSwapChainForComposition(
        d3dDevice.get(),
        &swapChainDesc,
        nullptr,
        swapChain.put());
    
  6. Obtén un ISwapChainPanelNative del SwapChainPanel que llamaste swapChainPanel. La llamada a ISwapChainPanelNative::SetSwapChain para establecer la cadena de intercambio en SwapChainPanel.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    // Get native interface for SwapChainPanel
    auto panelNative{ swapChainPanel().as<ISwapChainPanelNative>() };
    
    winrt::check_hresult(
        panelNative->SetSwapChain(swapChain.get())
    );
    
  7. Por último, dibuje a la cadena de intercambio de DirectX y, a continuación, presentándola para mostrar el contenido.

    // Create a Direct2D target bitmap associated with the
    // swap chain back buffer, and set it as the current target.
    D2D1_BITMAP_PROPERTIES1 bitmapProperties =
        D2D1::BitmapProperties1(
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
            96.f,
            96.f
        );
    
    winrt::com_ptr<::IDXGISurface> dxgiBackBuffer;
    swapChain->GetBuffer(0, __uuidof(dxgiBackBuffer), dxgiBackBuffer.put_void());
    
    winrt::com_ptr< ::ID2D1Bitmap1 > targetBitmap;
    winrt::check_hresult(
        d2dDeviceContext->CreateBitmapFromDxgiSurface(
            dxgiBackBuffer.get(),
            &bitmapProperties,
            targetBitmap.put()
        )
    );
    
    d2dDeviceContext->SetTarget(targetBitmap.get());
    
    // Draw using Direct2D context.
    d2dDeviceContext->BeginDraw();
    
    d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
    winrt::com_ptr<::ID2D1SolidColorBrush> brush;
    winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(
        D2D1::ColorF(D2D1::ColorF::Chocolate),
        D2D1::BrushProperties(0.8f),
        brush.put()));
    
    D2D1_SIZE_F const size{ 500, 500 };
    D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
    d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
    d2dDeviceContext->EndDraw();
    
    swapChain->Present(1, 0);
    

    Los elementos XAML se actualizan cuando el diseño o la lógica de representación de Windows Runtime envían una señal de actualización.

  8. Ya puede compilar y ejecutar la aplicación. Haga clic en el botón para ver el contenido de SwapChainPanel mostrado detrás de los demás elementos XAML.

    Un rectángulo representado por Direct2D detrás de un elemento de botón XAML

Nota

En general, la aplicación DirectX debe crear cadenas de intercambio en orientación horizontal y igual al tamaño de la ventana de visualización (que suele ser la resolución de pantalla nativa en la mayoría de los juegos de Microsoft Store). Esto garantiza que la aplicación usa la implementación óptima de la cadena de intercambio cuando no tiene ninguna superposición XAML visible. Si la aplicación se gira al modo vertical, la aplicación debe llamar a IDXGISwapChain1::SetRotation en la cadena de intercambio existente, aplicar una transformación al contenido si es necesario y, a continuación, llamar a SetSwapChain de nuevo en la misma cadena de intercambio. Del mismo modo, la aplicación debe llamar a SetSwapChain de nuevo en la misma cadena de intercambio siempre que se cambie el tamaño de la cadena de intercambio llamando a IDXGISwapChain::ResizeBuffers.