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 extensible Application Markup Language (XAML) junto con Microsoft DirectX juntos 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 tu contenido representado por DirectX; que es especialmente útil para aplicaciones que consumen muchos 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 de Win2D . Microsoft mantiene esa biblioteca y se basa en la tecnología principal de Direct2D . Win2D simplifica considerablemente el patrón de uso para implementar gráficos 2D e incluye abstracciones útiles para algunas de las técnicas descritas en este documento. Consulte la página del proyecto para obtener más detalles. En este documento se describen las instrucciones para los desarrolladores de aplicaciones que eligen no usar 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 Windows Runtime de C++/WinRT (CMR), puedes usar ese XAML en una aplicación para UWP (incluso una C#) que luego 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 tienen el tamaño de las regiones 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 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 en 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? ¿Contendrá la salida que debe representarse y controlarse en tiempo real, como en un juego? Si es así, probablemente deba 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 componer una imagen o textura como mapa de bits para mostrarlo en un documento o elemento de la interfaz de usuario. No funciona bien para la interactividad en tiempo real, como 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 proporcione al usuario, como una velocidad de fotogramas fluctuante o una respuesta deficiente percibida a la entrada en tiempo real. Sin embargo, las actualizaciones siguen siendo lo suficientemente rápidas para controles dinámicos o simulaciones de datos.
- Si la imagen es mayor que el espacio real de la pantalla proporcionada 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. Al igual que SurfaceImageSource, usas esto al componer una imagen o un control complejos dinámicamente. Además, como SurfaceImageSource, no funciona bien para juegos de alto rendimiento. Algunos ejemplos de elementos XAML que podrían usar virtualSurfaceImageSource son controles de mapa o un visor de documentos grande y denso de imágenes.
- 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. Debe conocer Bien DirectX para usar este enfoque, incluidas las tecnologías de infraestructura de gráficos de Microsoft DirectX (DXGI), Direct2D y Direct3D. Para obtener más información, consulta Guía de programación para Direct3D 11.
SurfaceImageSource
SurfaceImageSource proporciona una superficie compartida de DirectX en la que dibujar; a continuación, compone los bits en el contenido de la aplicación.
Sugerencia
Las aplicaciones de ejemplo De 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 directo.
- 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 de dibujar en SurfaceImageSource cuando haya terminado.
- Establezca SurfaceImageSource en una imagen XAML o ImageBrush para mostrarlo en la interfaz de usuario XAML.
Y este es un análisis más profundo de esos pasos, con ejemplos de código fuente.
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.
Abra
pch.h
y agregue lo siguiente que 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>
Agregue la
using
directiva que se muestra a continuación a la parte superior deMainPage.cpp
, debajo de las que ya están allí. Además, enMainPage.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 y 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() ) ); }
A continuación, agregue código para crear un SurfaceImageSource y establezca el dispositivo Direct 2D (o Direct 3D) en ese dispositivo llamando a ISurfaceImageSourceNativeWithD2D::SetDevice.
Nota:
Si vas a dibujar en SurfaceImageSource desde un subproceso en segundo plano, también tendrás que asegurarte de que el dispositivo DXGI tenga habilitado el acceso multiproceso (como se muestra en el código siguiente). Por motivos de rendimiento, debe hacerlo solo si va a dibujar a partir de un subproceso de fondo.
Defina el tamaño de la superficie compartida pasando el alto y el ancho al constructor SurfaceImageSource. También puede indicar si la superficie necesita compatibilidad con 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);
Llame 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 2D directo.
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());
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(); }
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, establezca SurfaceImageSource en una imagen XAML (o ImageBrush) para mostrarlo en la 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 de ISurfaceImageSourceNativeWithD2D . Controle los eventos relacionados con la visibilidad de ventana y la suspensión de la aplicación para lograrlo.
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" /> ...
Ya puede compilar y ejecutar la aplicación. Haga clic en el botón para ver el contenido de SurfaceImageSource mostrado en la imagen.
VirtualSurfaceImageSource
VirtualSurfaceImageSource amplía 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 ajustarse a 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.
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 directo.
- Cree un virtualSurfaceImageSource y establezca el dispositivo Direct 2D (o Direct 3D) en ese dispositivo.
- Llame a RegisterForUpdatesNeeded en VirtualSurfaceImageSource.
- En la devolución de llamada UpdatesNeeded, llame a GetUpdateRectCount y GetUpdateRects.
- Representa los rectángulos de actualización (usando BeginDraw/EndDraw igual que para SurfaceImageSource).
- Establezca SurfaceImageSource en una imagen XAML o ImageBrush para mostrarlo en la interfaz de usuario XAML.
Y este es un análisis más profundo de esos pasos, con ejemplos de código fuente.
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.
Abra
pch.h
y agregue lo siguiente que 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>
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 acerca 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 a 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 (consulte 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 de ISurfaceImageSourceNativeWithD2D. Controle los eventos relacionados con la visibilidad de ventana y la suspensión de la aplicación para lograrlo.
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 directo. Para ello, llamaremos a D3D11CreateDevice, D2D1CreateDevice e ID2D1Device::CreateDeviceContext.
Reemplace el contenido de
MainPage.idl
,MainPage.h
yMainPage.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() ) ); } }
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 llamando a ISurfaceImageSourceNativeWithD2D::SetDevice.
Nota:
Si va a dibujar en virtualSurfaceImageSource desde un subproceso en segundo plano, también tendrá que 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 a partir de un subproceso de fondo.
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 la implementación de IVirtualSurfaceUpdatesCallbackNative.
A continuación, establezca SurfaceImageSource en una imagen XAML (o ImageBrush) para mostrarlo en la 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);
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" /> ...
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 admitir gráficos y juegos de alto rendimiento, donde administras la cadena de intercambio directamente. En este caso, creará su 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.
Sugerencia
Las siguientes aplicaciones de ejemplo muestran SurfaceImageSource: representación avanzada de imágenes de color direct2D, ajuste de fotos direct2D, representación de imágenes SVG de Direct2D, entrada de baja latencia, juego DirectX y XAML, y interoperabilidad xaml SwapChainPanel DirectX (Windows 8.1).
Para garantizar un buen rendimiento, existen ciertas limitaciones para 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 (con 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.
- Debe 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. De lo contrario, debes tener en cuenta cualquier problema entre subprocesos si intentas actualizar los elementos XAML de un subproceso diferente al que actualiza 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. Tenga 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.
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 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.
Abra
pch.h
y agregue lo siguiente que 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>
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 e ID2D1Device::CreateDeviceContext.
Reemplace el contenido de
MainPage.idl
,MainPage.h
yMainPage.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() ) ); } }
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.
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());
Obtenga un ISwapChainPanelNative de SwapChainPanel al que llamó 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()) );
Por último, dibuje a la cadena de intercambio de DirectX y, a continuación, presente 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 la lógica de representación o diseño de Windows Runtime indica una actualización.
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 otros elementos XAML.
Nota:
En general, la aplicación DirectX debe crear cadenas de intercambio en orientación horizontal y igual que el 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 use la implementación óptima de la cadena de intercambio cuando no tenga 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.