Partager via


Interop DirectX et XAML

Remarque

Cette rubrique s’applique aux jeux et applications plateforme Windows universelle (UWP) et 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 ensemble dans votre jeu ou application 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 DirectX ; qui est particulièrement utile pour les applications gourmandes en graphiques. 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 travailler avec DirectX.

Si votre application se concentre principalement sur le rendu 2D, vous pouvez utiliser la bibliothèque Windows Runtime Win2D . Cette bibliothèque est gérée par Microsoft et repose sur la technologie Direct2D principale. 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 plus d’informations, consultez la page du projet. Ce document décrit les conseils pour les développeurs d’applications qui choisissent de ne pas utiliser Win2D.

Remarque

Les API DirectX ne sont pas définies en tant que types Windows Runtime, mais vous pouvez utiliser C++/WinRT pour développer des applications UWP XAML qui interagissent avec DirectX. Si vous factorisez le code qui appelle DirectX dans son propre composant Windows Runtime C++/WinRT (WRC), vous pouvez utiliser ce WRC dans une application UWP (même un C# un) 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 jeux ont besoin d’une prise en charge graphique plus complexe. Pour ce faire, vous pouvez utiliser Direct2D et Direct3D pour afficher les graphiques plus complexes et utiliser XAML pour des éléments d’interface utilisateur plus traditionnels.

Si vous implémentez du code XAML personnalisé et de l’interopérabilité DirectX, vous devez connaître ces deux concepts.

  • Les surfaces partagées sont des régions de dimensionnement de l’affichage, définies par XAML, que 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 du moment où le nouveau contenu apparaît à l’écran. Au lieu de cela, les mises à jour de la surface partagée sont synchronisées avec les mises à jour du framework 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 ressources de mémoire et d’UC pour prendre en charge les mises à jour rapides et est relativement difficile à utiliser, car vous devez gérer plusieurs threads.

Considérez ce que vous utilisez DirectX pour. Allez-vous l’utiliser pour composite ou animer un contrôle unique qui s’adapte aux dimensions de la fenêtre d’affichage ? Contiendra-t-il une sortie qui doit être rendue et contrôlée en temps réel, comme dans un jeu ? Si c’est le cas, vous devrez probablement implémenter une chaîne d’échange. Sinon, vous devez utiliser une surface partagée.

Une fois que vous avez déterminé comment utiliser DirectX, vous utilisez l’un des types 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 règle générale, vous utilisez ce type lors de la composition d’une image ou d’une texture en tant que bitmap pour l’affichage dans un document ou un élément d’interface utilisateur. Il ne fonctionne pas bien pour l’interactivité en temps réel, comme un jeu hautes performances. 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 et peuvent introduire une latence dans les commentaires visuels que vous fournissez à l’utilisateur, telles qu’une fréquence d’images fluctuante ou une réponse médiocre perçue à une entrée en temps réel. Toutefois, les mises à jour sont encore suffisamment rapides pour les contrôles dynamiques ou les simulations de données.
  • Si l’image est supérieure à l’immobilier d’écran fourni et peut être mise en panne ou zoomée par l’utilisateur, utilisez Windows ::UI ::Xaml ::Media ::Imaging ::VirtualSurfaceImageSource. Ce type gère une surface de dessin DirectX dimensionnée supérieure à l’écran. Comme SurfaceImageSource, vous l’utilisez lors de la composition dynamique d’une image ou d’un contrôle complexe. Et, comme SurfaceImageSource, il ne fonctionne pas bien pour les jeux hautes performances. Voici quelques exemples d’éléments XAML qui peuvent utiliser un VirtualSurfaceImageSource sont des contrôles cartographiques ou une visionneuse de documents volumineuse et dense 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 pour pouvoir actualiser les graphiques sans synchroniser avec le minuteur d’actualisation du framework XAML. Avec SwapChainPanel , vous pouvez accéder directement à la chaîne d’échange de l’appareil graphique (IDXGISwapChain1) et calquer le code XAML sur la cible de rendu. SwapChainPanel fonctionne parfaitement pour les jeux et les applications DirectX en plein écran qui nécessitent une interface utilisateur basée sur 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, consultez le Guide de programmation pour Direct3D 11.

SurfaceImageSource

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

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

  • Créez un appareil Direct 3D, 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 pour obtenir une surface DXGI.
  • Dessinez sur l’aire DXGI avec Direct2D (ou Direct3D).
  • Fin du dessin sur SurfaceImageSource lorsque vous avez terminé.
  • Définissez SurfaceImageSource sur une image XAML ou ImageBrush pour l’afficher dans votre interface utilisateur XAML.

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

  1. Vous pouvez suivre le code présenté et décrit ci-dessous en créant un projet dans Microsoft Visual Studio. Créez un projet d’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.het ajoutez les éléments suivants sous 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. MainPage.cppEn outre, 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 une SurfaceImageSource et définissez l’appareil Direct 2D (ou Direct 3D) sur celui-ci en appelant ISurfaceImageSourceNativeWithD2D ::SetDevice.

    Remarque

    Si vous dessinez sur votre SurfaceImageSource à partir d’un thread d’arrière-plan, vous devez également vous assurer que l’appareil DXGI dispose d’un accès multithread activé (comme indiqué dans le code ci-dessous). Pour des raisons de performances, vous devez le faire uniquement si vous dessinerez à 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 de la 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 l’aire DXGI et définissez-la dans le contexte d’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 l’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 du 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). Définissez ensuite SurfaceImageSource sur une image XAML (ou ImageBrush) pour 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);
    

    Remarque

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

    Remarque

    Évitez de dessiner sur SurfaceImageSource pendant que votre fenêtre est masquée ou inactive, sinon les API ISurfaceImageSourceNativeWithD2D échouent. Gérez les événements autour de la visibilité des fenêtres et de la suspension de l’application pour y parvenir.

  8. Enfin, ajoutez l’élément Image suivant à l’intérieur du 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 épais et orange foncé rectanglulaire contre un arrière-plan orange plus clair

VirtualSurfaceImageSource

VirtualSurfaceImageSource étend SurfaceImageSource, et il s’agit de scénarios où le contenu est potentiellement trop volumineux pour s’adapter à l’écran tout en même temps (et/ou trop grand pour s’adapter à la mémoire vidéo en tant que texture unique), et ainsi 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 dans le fait 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 sur l’écran. Vous n’avez pas besoin d’effacer les régions masquées, car l’infrastructure XAML s’occupe de cela pour vous.

Il est judicieux de vous 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’une VirtualSurfaceImageSource.

  • Implémentez l’interface IVirtualSurfaceImageSourceCallbackNative .
  • Créez un appareil Direct 3D, 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 RegisterForUpdatesNeededed 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 ImageBrush pour l’afficher dans votre interface utilisateur XAML.

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

  1. Vous pouvez suivre le code présenté 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 au projet ce nom si vous allez copier-coller dans les listes de code indiquées ci-dessous). Ciblez la dernière version en disponibilité générale (autrement dit, pas la préversion) du SDK Windows.

  2. Ouvrez pch.het ajoutez les éléments suivants sous 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 de 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 d’IVirtualSurfaceUpdatesCallbackNative ::UpdatesNeededed (illustré 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 zoome sur la vue de la surface, par exemple), ou une fois que votre application a appelé IVirtualSurfaceImageSourceNative ::Invalidate sur cette région.

    Dans votre implémentation d’IVirtualSurfaceImageSourceNative ::UpdatesNeeded, utilisez les méthodes IVirtualSurfaceImageSourceNative ::GetUpdateRectCount et IVirtualSurfaceImageSourceNative ::GetUpdateRects pour déterminer quelles régions de la surface 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 obtenir de meilleures performances. Les spécificités de l’appel des méthodes ISurfaceImageSourceNativeWithD2D sont identiques à celles de SurfaceImageSource (voir la section SurfaceImageSource ci-dessus).

    Remarque

    Évitez de dessiner sur VirtualSurfaceImageSource pendant que votre fenêtre est masquée ou inactive, sinon les API ISurfaceImageSourceNativeWithD2D échouent. Gérez les événements autour de la visibilité des fenêtres et de la suspension de l’application pour y parvenir.

  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 listes 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 une VirtualSurfaceImageSource avec la taille souhaitée et définissez l’appareil Direct 2D (ou Direct 3D) sur celui-ci en appelant ISurfaceImageSourceNativeWithD2D ::SetDevice.

    Remarque

    Si vous dessinez sur votre VirtualSurfaceImageSource à partir d’un thread d’arrière-plan, vous devez également vous assurer que l’appareil DXGI dispose d’un accès multithread activé (comme illustré dans le code ci-dessous). Pour des raisons de performances, vous devez le faire uniquement si vous dessinerez à 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 ::RegisterForUpdatesNeededed, en fournissant votre implémentation d’IVirtualSurfaceUpdatesCallbackNative.

    Définissez ensuite SurfaceImageSource sur une image XAML (ou ImageBrush) pour 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 à l’intérieur du 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 jeu

SwapChainPanel est le type Windows Runtime conçu pour prendre en charge des graphiques et des jeux hautes performances, où vous gérez directement la chaîne d’échange. Dans ce cas, vous créez votre propre chaîne d’échange DirectX et gérez la présentation de votre contenu rendu. Une autre fonctionnalité de SwapChainPanel est que vous pouvez superposer d’autres éléments XAML devant lui.

Conseil

Les exemples d’applications suivants illustrent SurfaceImageSource : rendu d’image de couleur avancée Direct2D, ajustement photo Direct2D, rendu d’image SVG Direct2D, entrée faible, jeu DirectX et XAML SwapChainPanel DirectX (Windows 8.1)

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 (en 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 d’échange DirectX en appelant IDXGIFactory2 ::CreateSwapChainForComposition.

Vous mettez à jour swapChainPanel en fonction des besoins de votre application et non de la synchronisation avec les mises à jour de l’infrastructure XAML. Si vous devez synchroniser les mises à jour du SwapChainPanel avec celles de l’infrastructure XAML, inscrivez-vous à l’événement Windows ::UI ::Xaml ::Media ::CompositionTarget ::Rendering . Sinon, vous devez prendre en compte tous les problèmes interthreads 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 retourne un objet CoreIndependentInputSource qui peut être utilisé pour recevoir des événements d’entrée à 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 seront pas déclenchés pour swapChainPanel, car toutes les entrées seront 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 présenté 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 allez copier-coller dans les listes de code indiquées ci-dessous). Ciblez la dernière version en disponibilité générale (autrement dit, pas la préversion) du SDK Windows.

  2. Ouvrez pch.het ajoutez les éléments suivants sous 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 listes 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. Appel de 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 de SwapChainPanel affiché derrière les autres éléments XAML.

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

Remarque

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 aucune 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 De nouveau SetSwapChain sur la même chaîne d’échange. De même, votre application doit appeler SetSwapChain à nouveau sur la même chaîne d’échange chaque fois que la chaîne d’échange est redimensionnée en appelant IDXGISwapChain ::ResizeBuffers.