Interoperabilità DirectX e XAML

Nota

Questo argomento si applica a giochi e app per la piattaforma UWP (Universal Windows Platform) e ai tipi negli spazi dei nomi Windows.UI.Xaml.Xxx (non Microsoft.UI.Xaml.Xxx).

È possibile usare XAML (Extensible Application Markup Language) insieme a Microsoft DirectX nel gioco o app per la piattaforma UWP (Universal Windows Platform). La combinazione di XAML e DirectX consente di creare framework flessibili dell'interfaccia utente che interagiscono con il contenuto di cui è stato eseguito il rendering DirectX; particolarmente utile per le app a elevato utilizzo di grafica. Questo argomento illustra la struttura di un'app UWP che usa DirectX e identifica i tipi importanti da usare durante la compilazione dell'app UWP per l'uso con DirectX.

Se l'app si concentra principalmente sul rendering 2D, si potrebbe voler usare la libreria Windows Runtime Win2D. Tale libreria viene gestita da Microsoft ed è basata sulla tecnologia Direct2D principale. Win2D semplifica notevolmente il modello di utilizzo per implementare grafica 2D e include astrazioni utili per alcune delle tecniche descritte in questo documento. Per altri dettagli, vedere la pagina relativa al progetto. Questo documento illustra le linee guida per gli sviluppatori di app che scelgono di non usare Win2D.

Nota

Le API DirectX non sono definite come tipi di Windows Runtime, ma è possibile usare C++/WinRT per sviluppare app UWP XAML che interagiscono con DirectX. Se si prende in considerazione il codice che chiama DirectX nel proprio componente C++/WinRT Windows Runtime (WRC), è possibile usare tale WRC in un'app UWP (anche C#) che combinerà quindi XAML e DirectX.

XAML e DirectX

DirectX offre due potenti librerie per la grafica 2D e 3D, rispettivamente Direct2D e Direct3D. Anche se XAML fornisce supporto per primitive ed effetti 2D di base, molte app di modellazione e di gioco richiedono un supporto grafico più complesso. Per queste, è possibile usare Direct2D e Direct3D per eseguire il rendering della grafica più complessa e usare XAML per elementi dell'interfaccia utente più tradizionali.

Se si sta implementando l'interoperabilità XAML e DirectX personalizzata, è necessario conoscere questi due concetti.

  • Le superfici condivise sono aree di dimensioni dello schermo, definite da XAML, per le quali si può usare DirectX per disegnare indirettamente con i tipi Windows::UI::Xaml::Media::ImageSource. Per le superfici condivise, non è possibile controllare l'intervallo preciso di quando viene visualizzato il nuovo contenuto sullo schermo. Gli aggiornamenti alla superficie condivisa sono invece sincronizzati con gli aggiornamenti del framework XAML.
  • Una swapchain rappresenta una raccolta di buffer utilizzati per visualizzare grafica con latenza minima. In genere, una swapchain viene aggiornata a 60 fotogrammi al secondo separatamente dal thread dell'interfaccia utente. Tuttavia, una swapchain usa più risorse di memoria e CPU per supportare aggiornamenti rapidi ed è relativamente difficile da usare, poiché è necessario gestire più thread.

Prendere in considerazione gli elementi per cui si usa DirectX. Si userà per comporre o animare un singolo controllo che si adatta alle dimensioni della finestra di visualizzazione? Conterrà l'output che deve essere sottoposto a rendering e controllato in tempo reale, come in un gioco? In tal caso, sarà probabilmente necessario implementare una swapchain. In caso contrario, è consigliabile usare una superficie condivisa.

Dopo avere determinato come si intende usare DirectX, si usa uno dei tipi di Windows Runtime seguenti per incorporare il rendering DirectX nell'app UWP.

  • Se si desidera comporre un'immagine statica o disegnare un'immagine complessa a intervalli basati su eventi, disegnare in una superficie condivisa con Windows::UI::Xaml::Media::Imaging::SurfaceImageSource. Tale tipo gestisce una superficie di disegno DirectX ridimensionata. In genere, questo tipo viene usato quando si compone un'immagine o una texture come bitmap per la visualizzazione in un documento o in un elemento dell'interfaccia utente. Non funziona bene per l'interattività in tempo reale, ad esempio un gioco ad alte prestazioni. Ciò è dovuto al fatto che gli aggiornamenti a un oggetto SurfaceImageSource vengono sincronizzati con gli aggiornamenti dell'interfaccia utente XAML e che possono introdurre la latenza nel feedback visivo fornito all'utente, ad esempio una frequenza dei fotogrammi fluttuante o una risposta percepita come scarsa all'input in tempo reale. Gli aggiornamenti sono ancora abbastanza veloci per i controlli dinamici o le simulazioni di dati.
  • Se l'immagine è più grande dello schermo fornito e può essere zoomata dall'utente, usare Windows::UI::Xaml::Media::Imaging::VirtualSurfaceImageSource. Tale tipo gestisce una superficie di disegno DirectX ridimensionata più grande dello schermo. Come SurfaceImageSource, è possibile usarlo per comporre un'immagine o un controllo complesso in modo dinamico. E, come SurfaceImageSource, non funziona bene per i giochi ad alte prestazioni. Alcuni esempi di elementi XAML che potrebbero usare VirtualSurfaceImageSource sono controlli mappa o un visualizzatore di documenti con densità di immagini di grandi dimensioni.
  • Se si usa DirectX per presentare grafica aggiornata in tempo reale o in una situazione in cui gli aggiornamenti devono venire a intervalli regolari a bassa latenza, usare la classe SwapChainPanel, in modo da aggiornare la grafica senza sincronizzare il timer di aggiornamento del framework XAML. Con SwapChainPanel è possibile accedere direttamente alla swapchain del dispositivo grafico (IDXGISwapChain1) e al livello XAML sopra la destinazione di rendering. SwapChainPanel è ideale per giochi e app DirectX a schermo intero che richiedono un'interfaccia utente basata su XAML. Devi conoscere bene DirectX per usare questo approccio, tra cui le tecnologie Microsoft DirectX Graphics Infrastructure (DXGI), Direct2D e Direct3D. Per maggiori informazioni, vedere Guida alla programmazione per Direct3D 11.

SurfaceImageSource

SurfaceImageSource fornisce una superficie DirectX condivisa in cui disegnare, quindi compone i bit nel contenuto dell'app.

Suggerimento

Le applicazioni di esempio Spaziatura linea (DirectWrite) e Tipi di carattere scaricabili (DirectWrite) illustrano SurfaceImageSource.

A un livello molto elevato, ecco il processo di creazione e aggiornamento di SurfaceImageSource.

  • Creare un dispositivo Direct 3D, un dispositivo Direct 2D e un contesto di dispositivo Direct 2D.
  • Creare un dispositivo SurfaceImageSource e impostarvi il dispositivo Direct 2D (o Direct 3D).
  • Iniziare a disegnare su SurfaceImageSource per ottenere una superficie DXGI.
  • Disegnare sulla superficie DXGI con Direct2D (o Direct3D).
  • Terminare il disegno su SurfaceImageSource.
  • Impostare SurfaceImageSource su un'immagine XAML o ImageBrush per visualizzarla nell'interfaccia utente XAML.

Ecco un approfondimento su questi passaggi, con esempi di codice sorgente.

  1. È possibile seguire il codice illustrato e descritto di seguito creando un nuovo progetto in Microsoft Visual Studio. Creare un progetto App vuota (C++/WinRT). Specificare come destinazione la versione più recente disponibile a livello generale (ovvero non l'anteprima) di Windows SDK.

  2. Aprire pch.h e aggiungere le seguenti istruzioni include in quelli già presenti.

    // 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. Aggiungere la direttiva using illustrata di seguito all'inizio di MainPage.cpp, sotto quelle già presenti. In MainPage.cpp, sostituire anche l'implementazione esistente di MainPage::ClickHandler con l'elenco illustrato di seguito. Il codice crea un dispositivo Direct 3D, un dispositivo Direct 2D e un contesto di dispositivo Direct 2D. A tale scopo, chiamare D3D11CreateDevice, D2D1CreateDevice e ID2D1Device::CreateDeviceContext.

    // MainPage.cpp | paste this below the existing using directives
    using namespace Windows::UI::Xaml::Media::Imaging;
    
    // MainPage.cpp | paste this to replace the existing MainPage::ClickHandler
    void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
    {
        myButton().Content(box_value(L"Clicked"));
    
        uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
        D3D_FEATURE_LEVEL featureLevels[] =
        {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
            D3D_FEATURE_LEVEL_9_3,
            D3D_FEATURE_LEVEL_9_2,
            D3D_FEATURE_LEVEL_9_1
        };
    
        // Create the Direct3D device.
        winrt::com_ptr<::ID3D11Device> d3dDevice;
        D3D_FEATURE_LEVEL supportedFeatureLevel;
        winrt::check_hresult(::D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            d3dDevice.put(),
            &supportedFeatureLevel,
            nullptr)
        );
    
        // Get the DXGI device.
        winrt::com_ptr<::IDXGIDevice> dxgiDevice{
            d3dDevice.as<::IDXGIDevice>() };
    
        // Create the Direct2D device and a corresponding context.
        winrt::com_ptr<::ID2D1Device> d2dDevice;
        ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
        winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
        winrt::check_hresult(
            d2dDevice->CreateDeviceContext(
                D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                d2dDeviceContext.put()
            )
        );
    }
    
  4. Aggiungere quindi il codice per creare un oggetto SurfaceImageSource e impostare il dispositivo Direct 2D (o Direct 3D) in tale dispositivo chiamando ISurfaceImageSourceNativeWithD2D::SetDevice.

    Nota

    Se si sta disegnando su SurfaceImageSource da un thread in background, sarà necessario anche assicurarsi che il dispositivo DXGI abbia accesso multithread (come illustrato nel codice seguente). Per motivi di prestazioni, è consigliabile farlo solo se si sta disegnando da un thread di sfondo.

    Definire le dimensioni della superficie condivisa passando l'altezza e la larghezza al costruttore SurfaceImageSource. È anche possibile indicare se la superficie necessita del supporto alfa (opacità).

    Per impostare il dispositivo ed eseguire le operazioni di disegno, è necessario un puntatore a ISurfaceImageSourceNativeWithD2D. Per ottenerlo, eseguire una query sull'oggetto SurfaceImageSource per l'interfaccia ISurfaceImageSourceNativeWithD2D sottostante.

    // 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. Chiamare ISurfaceImageSourceNativeWithD2D::BeginDraw per recuperare una superficie DXGI (un'interfaccia IDXGISurface). È possibile chiamare ISurfaceImageSourceNativeWithD2D::BeginDraw (e i comandi di disegno successivi) da un thread in background se è stato attivato l'accesso multithread. In questo passaggio si crea anche una bitmap dalla superficie DXGI e la si imposta nel contesto del dispositivo Direct 2D.

    Nel parametro offset, ISurfaceImageSourceNativeWithD2D::BeginDraw restituisce l'offset del punto (un valore x,y) del rettangolo di destinazione aggiornato. È possibile usare tale offset per determinare dove disegnare il contenuto aggiornato con ID2D1DeviceContext.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    winrt::com_ptr<::IDXGISurface> dxgiSurface;
    
    RECT updateRect{ 0, 0, 500, 500 };
    POINT offset{ 0, 0 };
    HRESULT beginDrawHR = sisNativeWithD2D->BeginDraw(
        updateRect,
        __uuidof(::IDXGISurface),
        dxgiSurface.put_void(),
        &offset);
    
    // Create render target.
    winrt::com_ptr<::ID2D1Bitmap1> bitmap;
    winrt::check_hresult(
        d2dDeviceContext->CreateBitmapFromDxgiSurface(
            dxgiSurface.get(),
            nullptr,
            bitmap.put()
        )
    );
    
    // Set context's render target.
    d2dDeviceContext->SetTarget(bitmap.get());
    
  6. Usa il contesto del dispositivo Direct 2D per disegnare il contenuto di SurfaceImageSource. Viene disegnata solo l'area specificata per l'aggiornamento nel passaggio precedente nel parametro updateRect.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED ||
        beginDrawHR == DXGI_ERROR_DEVICE_RESET ||
        beginDrawHR == D2DERR_RECREATE_TARGET)
    {
        // The Direct3D and Direct2D devices were lost and need to be re-created.
        // Recovery steps are:
        // 1) Re-create the Direct3D and Direct2D devices
        // 2) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the new Direct2D
        //    device
        // 3) Redraw the contents of the SurfaceImageSource
    }
    else if (beginDrawHR == E_SURFACE_CONTENTS_LOST)
    {
        // The devices were not lost but the entire contents of the surface
        // were. Recovery steps are:
        // 1) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the Direct2D 
        //    device again
        // 2) Redraw the entire contents of the SurfaceImageSource
    }
    else
    {
        // Draw using Direct2D context.
        d2dDeviceContext->BeginDraw();
    
        d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
        winrt::com_ptr<::ID2D1SolidColorBrush> brush;
        winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(
            D2D1::ColorF(D2D1::ColorF::Chocolate),
            D2D1::BrushProperties(0.8f),
            brush.put()));
    
        D2D1_SIZE_F const size{ 500, 500 };
        D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
        d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
        d2dDeviceContext->EndDraw();
    }
    
  7. Chiamare ISurfaceImageSourceNativeWithD2D::EndDraw per completare la bitmap (devi chiamare ISurfaceImageSourceNativeWithD2D::EndDraw solo dal thread dell'interfaccia utente). Impostare quindi SurfaceImageSource su un'immagine XAML o ImageBrush per visualizzarla nell'interfaccia utente 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 chiamata a SurfaceImageSource::SetSource (ereditata da IBitmapSource::SetSource) genera attualmente un'eccezione. Non chiamarlo dall'oggetto SurfaceImageSource.

    Nota

    Evitare di disegnare in SurfaceImageSource mentre la finestra è nascosta o inattiva, altrimenti le API ISurfaceImageSourceNativeWithD2D avranno esito negativo. Gestire gli eventi relativi alla visibilità della finestra e alla sospensione dell'applicazione per eseguire questa operazione.

  8. Aggiungere infine l'elemento Image seguente all'interno del markup XAML esistente in MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  9. È ora possibile compilare ed eseguire l'app. Fare clic sul pulsante per visualizzare il contenuto di SurfaceImageSource visualizzato nell'immagine.

    Un contorno arancione spesso e scuro rectanglulare su uno sfondo arancione chiaro

VirtualSurfaceImageSource

VirtualSurfaceImageSource estende SurfaceImageSource ed è per scenari in cui il contenuto è potenzialmente troppo grande per adattarsi allo schermo contemporaneamente (e/o troppo grande per adattarsi alla memoria video come una singola texture) e quindi il contenuto deve essere virtualizzato in modo ottimale. Ad esempio, mapping di app o canvas di documenti di grandi dimensioni.

Suggerimento

L'applicazione di esempio di input penna complesso illustra VirtualSurfaceImageSource.

VirtualSurfaceImageSource differisce da SurfaceImageSource in quanto usa un callback, ovvero IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded, che si implementa per aggiornare le aree della superficie man mano che diventano visibili sullo schermo. Non è necessario cancellare le aree nascoste, perché il framework XAML si occupa automaticamente di tali aree.

È consigliabile acquisire familiarità con SurfaceImageSource (vedere la sezione SurfaceImageSource precedente) prima di affrontare VirtualSurfaceImageSource. A un livello molto elevato, ecco il processo di creazione e aggiornamento di SurfaceImageSource.

  • Implementare l'interfaccia IVirtualSurfaceImageSourceCallbackNative.
  • Creare un dispositivo Direct 3D, un dispositivo Direct 2D e un contesto di dispositivo Direct 2D.
  • Creare un dispositivo VirtualSurfaceImageSource e impostarne il dispositivo Direct 2D (o Direct 3D).
  • Chiamare RegisterForUpdatesNeeded in VirtualSurfaceImageSource.
  • Nel callback UpdatesNeeded chiamare GetUpdateRectCount e GetUpdateRects.
  • Eseguire il rendering dei rettangoli di aggiornamento (usando BeginDraw/EndDraw esattamente come per SurfaceImageSource).
  • Impostare SurfaceImageSource su un'immagine XAML o ImageBrush per visualizzarla nell'interfaccia utente XAML.

Ecco un approfondimento su questi passaggi, con esempi di codice sorgente.

  1. È possibile seguire il codice illustrato e descritto di seguito creando un nuovo progetto in Microsoft Visual Studio. Creare un progetto App vuota (C++/WinRT) e denominarlo VSISDemo (è importante assegnare al progetto questo nome se si copierà il codice indicato di seguito). Specificare come destinazione la versione più recente disponibile a livello generale (ovvero non l'anteprima) di Windows SDK.

  2. Aprire pch.h e aggiungere le seguenti istruzioni include in quelli già presenti.

    // 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. In questo passaggio si fornirà un'implementazione dell'interfaccia IVirtualSurface UpdatesCallbackNative. Aggiungere un nuovo elemento file di intestazione (.h) al progetto e denominarlo CallbackImplementation.h. Sostituire il contenuto di quel file con il listato di codice riportato di seguito. Il codice viene illustrato dopo il listato.

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

    Ogni volta che è necessario aggiornare un'area di VirtualSurfaceImageSource, il framework chiama l'implementazione di IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded (illustrato sopra).

    Ciò può verificarsi quando il framework determina che l'area deve essere disegnata (quando l'utente esegue la panoramica o esegue lo zoom della visualizzazione della superficie, ad esempio) o dopo che l'app ha chiamato IVirtualSurfaceImageSourceNative::Invalidate in tale area.

    Nell'implementazione di IVirtualSurfaceImageSourceNative::UpdatesNeeded, usare i metodi IVirtualSurfaceImageSourceNative::GetUpdateRectCount e IVirtualSurfaceImageSourceNative::GetUpdateRects per determinare quali aree della superficie devono essere disegnate.

    Per ogni area che deve essere aggiornata, disegnarne il contenuto specifico, ma vincolare il disegno alle aree delimitate al fine di ottenere prestazioni migliori. Le specifiche della chiamata ai metodi ISurfaceImageSourceNativeWithD2D sono le stesse di SurfaceImageSource (vedere la sezione SurfaceImageSource sopra).

    Nota

    Evitare di disegnare in VirtualSurfaceImageSource mentre la finestra è nascosta o inattiva, altrimenti le API ISurfaceImageSourceNativeWithD2D avranno esito negativo. Gestire gli eventi relativi alla visibilità della finestra e alla sospensione dell'applicazione per eseguire questa operazione.

  4. Nella classe MainPage verrà aggiunto un membro di tipo CallbackImplementation. Procedere con il creare un dispositivo Direct 3D, un dispositivo Direct 2D e un contesto di dispositivo Direct 2D. A tale scopo, chiamare D3D11CreateDevice, D2D1CreateDevice e ID2D1Device::CreateDeviceContext.

    Sostituire il contenuto di MainPage.idl, MainPage.h e MainPage.cpp con il contenuto delle voci riportate di seguito.

    // 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. Aggiungere quindi il codice per creare un oggetto VirtualSurfaceImageSource e impostare il dispositivo Direct 2D (o Direct 3D) in tale dispositivo chiamando ISurfaceImageSourceNativeWithD2D::SetDevice.

    Nota

    Se si sta disegnando su VirtualSurfaceImageSource da un thread in background, sarà necessario anche assicurarsi che il dispositivo DXGI abbia accesso multithread (come illustrato nel codice seguente). Per motivi di prestazioni, è consigliabile farlo solo se si sta disegnando da un thread di sfondo.

    Per impostare il dispositivo ed eseguire le operazioni di disegno, è necessario un puntatore a ISurfaceImageSourceNativeWithD2D. Per ottenerlo, eseguire una query sull'oggetto VirtualSurfaceImageSource per l'interfaccia ISurfaceImageSourceNativeWithD2D sottostante.

    Aprire una query anche per IVirtualSurfaceImageSourceNative e chiamare IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded, fornendo la propria implementazione di IVirtualSurfaceUpdatesCallbackNative.

    Impostare quindi SurfaceImageSource su un'immagine XAML o ImageBrush per visualizzarla nell'interfaccia utente 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. Aggiungere infine l'elemento Image seguente all'interno del markup XAML esistente in MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  7. È ora possibile compilare ed eseguire l'app. Fare clic sul pulsante per visualizzare il contenuto di VirtualSurfaceImageSource visualizzato nell'immagine.

SwapChainPanel e giochi

SwapChainPanel è il tipo Windows Runtime pensato per supportare grafica e giochi ad alte prestazioni, in cui si gestisce direttamente la swapchain. In questo caso, si crea una swapchain DirectX personalizzata e si gestisce la presentazione del contenuto sottoposto a rendering. Un'altra funzionalità di SwapChainPanel è che è possibile sovrapporvi altri elementi XAML.

Per garantire prestazioni ottimali, esistono alcune limitazioni per il tipo SwapChainPanel.

  • Non devono essere presenti più di 4 istanze di SwapChainPanel per ogni app.
  • È necessario impostare l'altezza e la larghezza della swapchain DirectX (in DXGI_SWAP_CHAIN_DESC1) sulle dimensioni correnti dell'elemento della swapchain. In caso contrario, il contenuto visualizzato verrà ridimensionato per adattarsi (usando DXGI_SCALING_STRETCH).
  • È necessario impostare la modalità di ridimensionamento della swapchain DirectX (in DXGI_SWAP_CHAIN_DESC1) su DXGI_SCALING_STRETCH.
  • È necessario creare la swapchain DirectX chiamando IDXGIFactory2::CreateSwapChainForComposition.

Aggiornare SwapChainPanel in base alle esigenze della tua app e non sincronizzare con gli aggiornamenti del framework XAML. Se è necessario sincronizzare gli aggiornamenti di SwapChainPanel con quelli del framework XAML, registrare l'evento Windows::UI::Xaml::Media::CompositionTarget::Rendering. In caso contrario, è necessario considerare eventuali problemi tra thread se si tenta di aggiornare gli elementi XAML da un thread diverso da quello che aggiorna SwapChainPanel.

Se è necessario ricevere l'input del puntatore a bassa latenza per SwapChainPanel, usare SwapChainPanel::CreateCoreIndependentInputSource. Questo metodo restituisce un oggetto CoreIndependentInputSource utilizzabile per ricevere eventi di input con latenza minima in un thread in background. Si noti che una volta chiamato questo metodo, i normali eventi di input del puntatore XAML non verranno generati per SwapChainPanel perché tutti gli input verranno reindirizzati al thread in background.

Ecco il processo di creazione e aggiornamento di un oggetto SwapChainPanel.

  1. È possibile seguire il codice illustrato e descritto di seguito creando un nuovo progetto in Microsoft Visual Studio. Creare un progetto App vuota (C++/WinRT) e denominarlo SCPDemo (è importante assegnare al progetto questo nome se si copierà il codice indicato di seguito). Specificare come destinazione la versione più recente disponibile a livello generale (ovvero non l'anteprima) di Windows SDK.

  2. Aprire pch.h e aggiungere le seguenti istruzioni include in quelli già presenti.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    
  3. Nella classe MainPage verrà prima creato un dispositivo Direct 3D, un dispositivo Direct 2D e un contesto di dispositivo Direct 2D. A tale scopo, chiamare D3D11CreateDevice, D2D1CreateDevice e ID2D1Device::CreateDeviceContext.

    Sostituire il contenuto di MainPage.idl, MainPage.h e MainPage.cpp con il contenuto delle voci riportate di seguito.

    // 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. Eseguire il wrapping del markup XAML in un elemento SwapChainPanel con un oggetto x:Name. Gli elementi XAML di cui è stato eseguito il wrapping verranno visualizzati davanti a 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>
    

    È quindi possibile accedere all'oggetto SwapChainPanel tramite la funzione di accesso con lo stesso nome, come vedremo.

  5. Chiamare quindi IDXGIFactory2::CreateSwapChainForComposition per creare una swapchain.

    // 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. Ottenere un oggetto ISwapChainPanelNative da SwapChainPanel chiamato swapChainPanel. Chiamare ISwapChainPanelNative::SetSwapChain per impostare la swapchain su 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. Infine, disegnare alla swapchain DirectX e quindi presentarla per visualizzare il contenuto.

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

    Gli elementi XAML vengono aggiornati quando il layout/rendering di Windows Runtime segnala un aggiornamento.

  8. È ora possibile compilare ed eseguire l'app. Fare clic sul pulsante per visualizzare il contenuto di SwapChainPanel visualizzato dietro gli altri elementi XAML.

    Rettangolo sottoposto a rendering Direct2D dietro un elemento del pulsante XAML

Nota

In generale, l'app DirectX deve creare swapchain con orientamento orizzontale e uguale alla dimensione della finestra di visualizzazione (che in genere è la risoluzione dello schermo nativa nella maggior parte dei giochi sul Microsoft Store). Ciò garantisce che l'app usi l'implementazione ottimale della swapchain quando non ha una sovrimpressione XAML visibile. Se l'app viene ruotata in modalità verticale, deve chiamare IDXGISwapChain1::SetRotation sulla swapchain esistente, applicare una trasformazione al contenuto, se necessario, quindi chiamare di nuovo SetSwapChain nella stessa swapchain. Analogamente, l'app deve chiamare di nuovo SetSwapChain nella stessa swapchain ogni volta che la swapchain viene ridimensionata chiamando IDXGISwapChain::ResizeBuffers.