Technologie interop DirectX et XAML

Notes

Cette rubrique s’applique aux jeux et applications plateforme Windows universelle (UWP), ainsi qu’aux types dans les espaces de noms Windows.UI.Xaml.Xxx (et non Microsoft.UI.Xaml.Xxx).

Vous pouvez utiliser XAML (Extensible Application Markup Language) avec Microsoft DirectX dans votre jeu ou application de plateforme Windows universelle (UWP). La combinaison de XAML et DirectX vous permet de créer des infrastructures d’interface utilisateur flexibles qui interagissent avec votre contenu rendu par DirectX . ce qui est particulièrement utile pour les applications gourmandes en graphismes. Cette rubrique explique la structure d’une application UWP qui utilise DirectX et identifie les types importants à utiliser lors de la création de votre application UWP pour fonctionner avec DirectX.

Si votre application se concentre principalement sur le rendu 2D, vous pouvez utiliser la bibliothèque de Windows Runtime Win2D. Cette bibliothèque est gérée par Microsoft et repose sur la technologie Direct2D de base. Win2D simplifie considérablement le modèle d’utilisation pour implémenter des graphiques 2D et inclut des abstractions utiles pour certaines des techniques décrites dans ce document. Pour en savoir plus, voir la page du projet. Ce document fournit des recommandations pour les développeurs d’applications qui choisissent de ne pas utiliser Win2D.

Notes

Les API DirectX ne sont pas définies comme des types Windows Runtime, mais vous pouvez utiliser C++/WinRT pour développer des applications XAML UWP qui interagissent avec DirectX. Si vous factoriser le code qui appelle DirectX dans son propre composant de Windows Runtime C++/WinRT (WRC), vous pouvez utiliser ce WRC dans une application UWP (même C#) qui combine ensuite XAML et DirectX.

XAML et DirectX

DirectX fournit deux bibliothèques puissantes pour les graphiques 2D et 3D, respectivement : Direct2D et Direct3D. Bien que XAML assure la prise en charge des primitives et des effets 2D de base, de nombreuses applications de modélisation et de jeu nécessitent une prise en charge graphique plus complexe. Pour ceux-ci, vous pouvez utiliser Direct2D et Direct3D pour afficher les graphiques plus complexes, et utiliser XAML pour les éléments d’interface utilisateur plus traditionnels.

Si vous implémentez une interopérabilité XAML et DirectX personnalisée, vous devez connaître ces deux concepts.

  • Les surfaces partagées sont des régions dimensionnées de l’affichage, définies par XAML, dans lesquelles vous pouvez utiliser DirectX pour dessiner indirectement à l’aide de types Windows::UI::Xaml::Media::ImageSource . Pour les surfaces partagées, vous ne contrôlez pas le minutage précis de l’affichage du nouveau contenu à l’écran. Au lieu de cela, les mises à jour de la surface partagée sont synchronisées avec les mises à jour de l’infrastructure XAML.
  • Une chaîne d’échange représente une collection de mémoires tampons utilisées pour afficher des graphiques avec une latence minimale. En règle générale, une chaîne d’échange est mise à jour à 60 images par seconde, séparément du thread d’interface utilisateur. Toutefois, une chaîne d’échange utilise davantage de mémoire et de ressources processeur pour prendre en charge les mises à jour rapides, et est relativement difficile à utiliser, car vous devez gérer plusieurs threads.

Tenez compte de ce pour quoi vous utilisez DirectX. Allez-vous l’utiliser pour composer ou animer un seul contrôle qui s’adapte aux dimensions de la fenêtre d’affichage ? Contiendra-t-elle une sortie qui doit être rendue et contrôlée en temps réel, comme dans un jeu ? Dans ce cas, vous devrez probablement implémenter une chaîne d’échange. Dans le cas contraire, vous devez utiliser une surface partagée.

Une fois que vous avez déterminé comment vous envisagez d’utiliser DirectX, vous utilisez l’un des types de Windows Runtime suivants pour incorporer le rendu DirectX dans votre application UWP.

  • Si vous souhaitez composer une image statique ou dessiner une image complexe à intervalles pilotés par les événements, dessinez sur une surface partagée avec Windows::UI::Xaml::Media::Imaging::SurfaceImageSource. Ce type gère une surface de dessin DirectX dimensionnée. En général, ce type est utilisé pour composer une image ou une texture sous forme d’image bitmap à afficher dans un document ou un élément d’interface. En revanche, il s’avère peu efficace dans un contexte d’interactivité en temps réel, comme les jeux élaborés. Cela est dû au fait que les mises à jour d’un objet SurfaceImageSource sont synchronisées avec les mises à jour de l’interface utilisateur XAML, ce qui peut introduire une latence dans les commentaires visuels que vous fournissez à l’utilisateur, par exemple une fréquence d’images fluctuante ou une mauvaise réponse perçue à l’entrée en temps réel. Mises à jour sont cependant suffisamment rapides pour les contrôles dynamiques ou les simulations de données.
  • Si l’image est plus grande que l’espace de l’écran fourni et que l’utilisateur peut effectuer un zoom ou une panne, utilisez Windows::UI::Xaml::Media::Imaging::VirtualSurfaceImageSource. Ce type gère une surface de dessin DirectX dimensionnée qui est plus grande que l’écran. Tout comme SurfaceImageSource, il s’avère utile pour composer dynamiquement une image ou un contrôle complexe. De même, à la manière de SurfaceImageSource, il se révèle peu efficace pour les jeux élaborés. Parmi les éléments XAML susceptibles d’utiliser un VirtualSurfaceImageSource, citons les contrôles de carte ou une visionneuse de documents volumineux et riches en images.
  • Si vous utilisez DirectX pour présenter des graphiques mis à jour en temps réel, ou dans une situation où les mises à jour doivent être effectuées à intervalles réguliers de faible latence, utilisez la classe SwapChainPanel , afin de pouvoir actualiser les graphiques sans synchroniser avec le minuteur d’actualisation de l’infrastructure XAML. Avec SwapChainPanel , vous pouvez accéder directement à la chaîne d’échange du périphérique graphique (IDXGISwapChain1) et à la couche XAML au-dessus de la cible de rendu. SwapChainPanel fonctionne parfaitement pour les jeux et les applications DirectX en plein écran qui nécessitent une interface utilisateur XAML. Vous devez bien connaître DirectX pour utiliser cette approche, notamment les technologies DXGI (Microsoft DirectX Graphics Infrastructure ), Direct2D et Direct3D. Pour plus d’informations, voir Guide de programmation pour Direct3D 11.

SurfaceImageSource

SurfaceImageSource vous fournit une surface partagée DirectX dans laquelle dessiner ; il compose ensuite les bits dans le contenu de l’application.

Conseil

Les exemples d’applications Interligne (DirectWrite) et Polices téléchargeables (DirectWrite) illustrent SurfaceImageSource.

À un très haut niveau, voici le processus de création et de mise à jour d’une SurfaceImageSource.

  • Créez un appareil 3D direct, un appareil Direct 2D et un contexte d’appareil Direct 2D.
  • Créez une SurfaceImageSource et définissez l’appareil Direct 2D (ou Direct 3D) sur celui-ci.
  • Commencez à dessiner sur surfaceImageSource afin d’obtenir une surface DXGI.
  • Dessinez sur la surface DXGI avec Direct2D (ou Direct3D).
  • Terminez le dessin sur surfaceImageSource lorsque vous avez terminé.
  • Définissez surfaceImageSource sur une image XAML ou Un objet ImageBrush afin de l’afficher dans votre interface utilisateur XAML.

Et voici une présentation plus approfondie de ces étapes, avec des exemples de code source.

  1. Vous pouvez suivre le code indiqué et décrit ci-dessous en créant un projet dans Microsoft Visual Studio. Créez un projet Application vide (C++/WinRT). Ciblez la dernière version en disponibilité générale (autrement dit, pas la préversion) du SDK Windows.

  2. Ouvrez pch.h, puis ajoutez les inclut ci-dessous ceux déjà présents.

    // 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. Ajoutez la using directive ci-dessous en haut de MainPage.cpp, en dessous de celles déjà présentes. Également dans MainPage.cpp, remplacez l’implémentation existante de MainPage::ClickHandler par la liste ci-dessous. Le code crée un appareil Direct 3D, un appareil Direct 2D et un contexte d’appareil Direct 2D. Pour ce faire, il appelle D3D11CreateDevice, D2D1CreateDevice et 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. Ensuite, ajoutez du code pour créer un SurfaceImageSource et définissez l’appareil Direct 2D (ou Direct 3D) sur celui-ci en appelant ISurfaceImageSourceNativeWithD2D::SetDevice.

    Notes

    Si vous souhaitez dessiner sur votre SurfaceImageSource à partir d’un thread d’arrière-plan, vous devez également vous assurer que l’accès multithread est activé sur l’appareil DXGI (comme indiqué dans le code ci-dessous). Pour des raisons de performances, vous ne devez le faire que si vous dessinez à partir d’un thread d’arrière-plan.

    Définissez la taille de la surface partagée en passant la hauteur et la largeur au constructeur SurfaceImageSource. Vous pouvez également indiquer si la surface a besoin d’une prise en charge alpha (opacité).

    Pour définir l’appareil et exécuter les opérations de dessin, nous avons besoin d’un pointeur vers ISurfaceImageSourceNativeWithD2D. Pour en obtenir un, interrogez l’objet SurfaceImageSource pour son interface ISurfaceImageSourceNativeWithD2D sous-jacente.

    // 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. Appelez ISurfaceImageSourceNativeWithD2D::BeginDraw pour récupérer une surface DXGI (interface IDXGISurface ). Vous pouvez appeler ISurfaceImageSourceNativeWithD2D::BeginDraw (et les commandes de dessin ultérieures) à partir d’un thread d’arrière-plan si vous avez activé l’accès multithread. Dans cette étape, vous créez également une bitmap à partir de la surface DXGI et définissez-la dans le contexte de l’appareil Direct 2D.

    Dans le paramètre offset , ISurfaceImageSourceNativeWithD2D::BeginDraw retourne le décalage de point (une valeur x,y) du rectangle cible mis à jour. Vous pouvez utiliser ce décalage pour déterminer où dessiner votre contenu mis à jour avec 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. Utilisez le contexte d’appareil Direct 2D pour dessiner le contenu de SurfaceImageSource. Seule la zone spécifiée pour la mise à jour à l’étape précédente dans le paramètre updateRect est dessinée.

    // 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. Appelez ISurfaceImageSourceNativeWithD2D::EndDraw pour terminer la bitmap (vous devez appeler ISurfaceImageSourceNativeWithD2D::EndDraw uniquement à partir du thread d’interface utilisateur). Ensuite, définissez surfaceImageSource sur une image XAML (ou ImageBrush) afin de l’afficher dans votre interface utilisateur 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);
    

    Notes

    L’appel de SurfaceImageSource::SetSource (hérité de IBitmapSource::SetSource) lève actuellement une exception. Ne l’appelez pas à partir de votre objet SurfaceImageSource .

    Notes

    Évitez de dessiner sur SurfaceImageSource pendant que votre fenêtre est masquée ou inactive, sinon les API ISurfaceImageSourceNativeWithD2D échouent. Pour ce faire, gérez les événements liés à la visibilité de la fenêtre et à la suspension de l’application.

  8. Enfin, ajoutez l’élément Image suivant dans le balisage XAML existant dans MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  9. Vous pouvez maintenant générer et exécuter l’application. Cliquez sur le bouton pour afficher le contenu de SurfaceImageSource affiché dans l’image.

    Un contour rectanglulaire orange foncé épais sur un arrière-plan orange clair

VirtualSurfaceImageSource

VirtualSurfaceImageSource étend SurfaceImageSource, et c’est pour les scénarios où le contenu est potentiellement trop volumineux pour tenir sur l’écran à la fois (et/ou trop grand pour tenir dans la mémoire vidéo sous la forme d’une texture unique), de sorte que le contenu doit être virtualisé afin de s’afficher de manière optimale. Par exemple, les applications de mappage ou les canevas de documents volumineux.

Conseil

L’exemple d’application d’entrée manuscrite Complexe illustre VirtualSurfaceImageSource.

VirtualSurfaceImageSource diffère de SurfaceImageSource en ce qu’il utilise un rappel ( IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded) que vous implémentez pour mettre à jour les régions de la surface à mesure qu’elles deviennent visibles à l’écran. Vous n’avez pas besoin d’effacer les régions masquées, car l’infrastructure XAML s’en charge pour vous.

Il est judicieux de se familiariser avec SurfaceImageSource (voir la section SurfaceImageSource ci-dessus) avant d’aborder VirtualSurfaceImageSource. Mais à un niveau très élevé, voici le processus de création et de mise à jour d’un VirtualSurfaceImageSource.

  • Implémentez l’interface IVirtualSurfaceImageSourceCallbackNative .
  • Créez un appareil 3D direct, un appareil Direct 2D et un contexte d’appareil Direct 2D.
  • Créez un VirtualSurfaceImageSource et définissez l’appareil Direct 2D (ou Direct 3D) sur celui-ci.
  • Appelez RegisterForUpdatesNeeded sur VirtualSurfaceImageSource.
  • Dans votre rappel UpdatesNeeded , appelez GetUpdateRectCount et GetUpdateRects.
  • Affichez les rectangles de mise à jour (à l’aide de BeginDraw/EndDraw comme pour SurfaceImageSource).
  • Définissez surfaceImageSource sur une image XAML ou Un objet ImageBrush afin de l’afficher dans votre interface utilisateur XAML.

Et voici une présentation plus approfondie de ces étapes, avec des exemples de code source.

  1. Vous pouvez suivre le code indiqué et décrit ci-dessous en créant un projet dans Microsoft Visual Studio. Créez un projet d’application vide (C++/WinRT) et nommez-le VSISDemo (il est important de donner ce nom au projet si vous voulez copier-coller dans les listes de codes ci-dessous). Ciblez la dernière version en disponibilité générale (autrement dit, pas la préversion) du SDK Windows.

  2. Ouvrez pch.h, puis ajoutez les inclut ci-dessous ceux déjà présents.

    // 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. Dans cette étape, vous allez fournir une implémentation de l’interface IVirtualSurfaceUpdatesCallbackNative . Ajoutez un nouvel élément fichier d’en-tête (.h) au projet et nommez-le CallbackImplementation.h. Remplacez le contenu de ce fichier par la liste ci-dessous. Le code est expliqué après la description.

    #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 };
        };
    }
    

    Chaque fois qu’une région de VirtualSurfaceImageSource doit être mise à jour, l’infrastructure appelle votre implémentation de IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded (illustrée ci-dessus).

    Cela peut se produire lorsque l’infrastructure détermine que la région doit être dessinée (lorsque l’utilisateur effectue un panoramique ou un zoom sur la vue de la surface, par exemple), ou après que votre application a appelé IVirtualSurfaceImageSourceNative::Invalidate sur cette région.

    Dans votre implémentation de IVirtualSurfaceImageSourceNative::UpdatesNeeded, utilisez les méthodes IVirtualSurfaceImageSourceNative::GetUpdateRectCount et IVirtualSurfaceImageSourceNative::GetUpdateRects pour déterminer la ou les régions de la surface qui doivent être dessinées.

    Pour chaque région qui doit être mise à jour, dessinez le contenu spécifique dans cette région, mais limitez votre dessin aux régions délimitées pour de meilleures performances. Les spécificités de l’appel des méthodes ISurfaceImageSourceNativeWithD2D sont les mêmes que pour SurfaceImageSource (voir la section SurfaceImageSource ci-dessus).

    Notes

    Évitez de dessiner vers VirtualSurfaceImageSource pendant que votre fenêtre est masquée ou inactive, sinon les API ISurfaceImageSourceNativeWithD2D échoueront. Pour ce faire, gérez les événements liés à la visibilité de la fenêtre et à la suspension de l’application.

  4. Dans la classe MainPage , nous allons ajouter un membre de type CallbackImplementation. Nous allons également créer un appareil Direct 3D, un appareil Direct 2D et un contexte d’appareil Direct 2D. Pour ce faire, nous allons appeler D3D11CreateDevice, D2D1CreateDevice et ID2D1Device::CreateDeviceContext.

    Remplacez le contenu de MainPage.idl, MainPage.het MainPage.cpp par le contenu des descriptions ci-dessous.

    // 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. Ensuite, ajoutez du code pour créer un VirtualSurfaceImageSource avec la taille souhaitée, puis définissez l’appareil Direct 2D (ou Direct 3D) sur celui-ci en appelant ISurfaceImageSourceNativeWithD2D::SetDevice.

    Notes

    Si vous devez dessiner vers votre VirtualSurfaceImageSource à partir d’un thread d’arrière-plan, vous devez également vous assurer que l’accès multithread est activé sur l’appareil DXGI (comme indiqué dans le code ci-dessous). Pour des raisons de performances, vous ne devez le faire que si vous dessinez à partir d’un thread d’arrière-plan.

    Pour définir l’appareil et exécuter les opérations de dessin, nous avons besoin d’un pointeur vers ISurfaceImageSourceNativeWithD2D. Pour en obtenir un, interrogez l’objet VirtualSurfaceImageSource pour son interface ISurfaceImageSourceNativeWithD2D sous-jacente.

    Interrogez également IVirtualSurfaceImageSourceNative et appelez IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded, en fournissant votre implémentation de IVirtualSurfaceUpdatesCallbackNative.

    Ensuite, définissez surfaceImageSource sur une image XAML (ou ImageBrush) afin de l’afficher dans votre interface utilisateur 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. Enfin, ajoutez l’élément Image suivant dans le balisage XAML existant dans MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  7. Vous pouvez maintenant générer et exécuter l’application. Cliquez sur le bouton pour afficher le contenu de VirtualSurfaceImageSource affiché dans l’image.

SwapChainPanel et jeux

SwapChainPanel est le type Windows Runtime conçu pour prendre en charge les graphismes et les jeux élaborés, où vous gérez directement la chaîne de permutation. Dans ce cas, vous devez créer votre propre chaîne d’échange DirectX et gérer la présentation du contenu rendu. Une autre fonctionnalité de SwapChainPanel est que vous pouvez superposer d’autres éléments XAML devant lui.

Pour garantir de bonnes performances, il existe certaines limitations au type SwapChainPanel .

  • Il ne doit pas y avoir plus de 4 instances SwapChainPanel par application.
  • Vous devez définir la hauteur et la largeur de la chaîne d’échange DirectX (dans DXGI_SWAP_CHAIN_DESC1) sur les dimensions actuelles de l’élément de chaîne d’échange. Si ce n’est pas le cas, le contenu d’affichage sera mis à l’échelle pour s’adapter (à l’aide de DXGI_SCALING_STRETCH).
  • Vous devez définir le mode de mise à l’échelle de la chaîne d’échange DirectX (dans DXGI_SWAP_CHAIN_DESC1) sur DXGI_SCALING_STRETCH.
  • Vous devez créer la chaîne de permutation DirectX en appelant IDXGIFactory2::CreateSwapChainForComposition.

Vous mettez à jour swapChainPanel en fonction des besoins de votre application, et vous ne vous synchronisez pas avec les mises à jour de l’infrastructure XAML. Si vous devez synchroniser les mises à jour de SwapChainPanel avec celles de l’infrastructure XAML, inscrivez-vous à l’événement Windows::UI::Xaml::Media::CompositionTarget::Rendering . Sinon, vous devez prévoir des problèmes inter-threads si vous essayez de mettre à jour les éléments XAML à partir d’un thread différent de celui qui met à jour SwapChainPanel.

Si vous devez recevoir une entrée de pointeur à faible latence vers votre SwapChainPanel, utilisez SwapChainPanel::CreateCoreIndependentInputSource. Cette méthode renvoie un objet CoreIndependentInputSource qui peut être utilisé pour recevoir des événements d’entrée avec une latence minimale sur un thread d’arrière-plan. Notez qu’une fois cette méthode appelée, les événements d’entrée de pointeur XAML normaux ne sont pas déclenchés pour swapChainPanel, car toutes les entrées sont redirigées vers le thread d’arrière-plan.

Voici le processus de création et de mise à jour d’un objet SwapChainPanel .

  1. Vous pouvez suivre le code indiqué et décrit ci-dessous en créant un projet dans Microsoft Visual Studio. Créez un projet d’application vide (C++/WinRT) et nommez-le SCPDemo (il est important de donner ce nom au projet si vous voulez copier-coller dans les listes de codes ci-dessous). Ciblez la dernière version en disponibilité générale (autrement dit, pas la préversion) du SDK Windows.

  2. Ouvrez pch.h, puis ajoutez les inclut ci-dessous ceux déjà présents.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    
  3. Dans la classe MainPage , nous allons d’abord créer un appareil Direct 3D, un appareil Direct 2D et un contexte d’appareil Direct 2D. Pour ce faire, nous allons appeler D3D11CreateDevice, D2D1CreateDevice et ID2D1Device::CreateDeviceContext.

    Remplacez le contenu de MainPage.idl, MainPage.het MainPage.cpp par le contenu des descriptions ci-dessous.

    // 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. Encapsulez votre balisage XAML dans un élément SwapChainPanel avec un x:Name. Les éléments XAML encapsulés s’affichent devant 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>
    

    Vous pouvez ensuite accéder à cet objet SwapChainPanel via la fonction d’accesseur portant le même nom, comme nous le verrons.

  5. Ensuite, appelez IDXGIFactory2::CreateSwapChainForComposition pour créer une chaîne d’échange.

    // 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. Obtenez un ISwapChainPanelNative à partir du SwapChainPanel que vous avez nommé swapChainPanel. Appelez ISwapChainPanelNative::SetSwapChain pour définir la chaîne d’échange sur 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. Enfin, dessinez sur la chaîne d’échange DirectX, puis présentez-la pour afficher le contenu.

    // 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);
    

    Les éléments XAML sont actualisés lorsque la logique de disposition/rendu de Windows Runtime signale une mise à jour.

  8. Vous pouvez maintenant générer et exécuter l’application. Cliquez sur le bouton pour afficher le contenu du SwapChainPanel affiché derrière les autres éléments XAML.

    Rectangle rendu direct2D derrière un élément de bouton XAML

Notes

En général, votre application DirectX doit créer des chaînes d’échange en orientation paysage et égale à la taille de la fenêtre d’affichage (qui est généralement la résolution d’écran native dans la plupart des jeux du Microsoft Store). Cela garantit que votre application utilise l’implémentation optimale de la chaîne d’échange lorsqu’elle n’a pas de superposition XAML visible. Si l’application est pivotée en mode portrait, votre application doit appeler IDXGISwapChain1::SetRotation sur la chaîne d’échange existante, appliquer une transformation au contenu si nécessaire, puis appeler à nouveau SetSwapChain sur la même chaîne d’échange. De même, votre application doit appeler de nouveau SetSwapChain sur la même chaîne de permutation chaque fois que celle-ci est redimensionnée à la suite d’un appel de IDXGISwapChain::ResizeBuffers.