DirectX 和 XAML Interop

注意

本主題適用於 Universal Windows Platform (UWP) 遊戲和應用程式,以及 Windows.UI.Xaml.Xxx 名稱空間 (而不是 Microsoft.UI.Xaml.Xxx) 中的型別。

您可以在 Universal Windows Platform (UWP) 遊戲或應用程式中,同時使用 Extensible Application Markup Language (XAML) 與 Microsoft DirectX。 XAML 與 DirectX 的組合可讓您建立彈性使用者介面框架,與您的 DirectX 呈現內容相互操作;這對於圖形密集型的應用程式特別有用。 本主題說明使用 DirectX 的 UWP 應用程式的結構,並識別在建置 UWP 應用程式以使用 DirectX 時要使用的重要型別。

如果您的應用程式主要專注於 2D 轉譯,則您可能想要使用 Win2D Windows 執行階段程式庫。 這個程式庫由微軟維護,是建立在核心 Direct2D 技術之上的。 Win2D 大幅簡化 2D 圖形的使用模式,並包含對本文中描述的一些技術的有用抽象。 如需詳細資訊,請參閱專案頁面。 本文件包含針對選擇 不使用 Win2D 的應用程式開發者的指南。

注意

DirectX API 未定義為 Windows 執行階段型別,但您可以使用 C++/WinRT,開發可與 DirectX 互動操作的 XAML UWP 應用程式。 如果您將呼叫 DirectX 的程式碼納入自己的 C++/WinRT Windows Runtime元件 (WRC),則您可以在結合 XAML 和 DirectX 的 UWP 應用程式 (甚至是 C# 應用程式) 中使用該 WRC。

XAML 和 DirectX

DirectX 提供兩個功能強大的 2D 和 3D 圖形庫:Direct2D 和 Direct3D。 雖然 XAML 支援基本的 2D 原稿和效果,但許多建模和遊戲應用程式需要更複雜的圖形支援。 對於這些專案,您可以使用 Direct2D 和 Direct3D 來呈現更複雜的圖形,並使用 XAML 來呈現更傳統的使用者介面 (UI) 元素。

如果要實作自訂 XAML 和 DirectX 互操作,則需要瞭解這兩個概念。

  • 共用曲面是由 XAML 所定義的顯示器大小區域,您可以使用 DirectX 透過 Windows::UI::Xaml::Media::ImageSource 型別間接將區域繪製到其中。 對於共用曲面,您無法控制新內容出現在螢幕上的精確時間。 相反,對共用曲面的更新將與 XAML 框架的更新同步。
  • 換鏈代表用來以最小延遲顯示圖形的緩衝區集合。 通常,交換鏈會與 UI 執行緒分開以每秒 60 個畫面更新。 但是,交換鏈會使用更多的記憶體和 CPU 資源來支援快速更新,而且由於必須管理多個執行緒,因此使用相對困難。

請考慮使用 DirectX 做什麼。 您會使用它來複合或動畫一個符合顯示視窗尺寸的控制? 它是否將包含需要即時呈現和控制的輸出,就像在遊戲中一樣? 如果是這樣,那麼您可能需要實施交換鏈。 否則,使用共用曲面應該沒問題。

當您決定要如何使用 DirectX 時,請使用下列其中一種 Windows 執行階段型別,將 DirectX 轉譯納入您的 UWP 應用程式。

SurfaceImageSource

SurfaceImageSource,可提供 DirectX 共用介面以擷取,然後將位元組合成應用程式內容。

提示

Line spacing (DirectWrite) Downloadable fonts (DirectWrite) sample 應用程式會顯示 SurfaceImageSource。

在非常高的層次上,以下是建立和更新 SurfaceImageSource 的過程。

  • 建立 Direct 3D 裝置、Direct 2D 裝置和 Direct 2D 裝置內容。
  • 建立 SurfaceImageSource,並在此設定 Direct 2D (或 Direct 3D) 裝置。
  • 開始在SurfaceImageSource 上繪製以獲得 DXGI 曲面。
  • 使用 Direct2D (或 Direct3D) 繪製至 DXGI 曲面。
  • 完成後停止在 SurfaceImageSource 繪圖。
  • 設定 XAML Image ImageBrush 上的 SurfaceImageSource,以便在 XAML UI 中顯示它。

下面是對這些步驟進行深入的深入分析並提供原始碼範例。

  1. 您可以在 Microsoft Visual Studio 中建立新專案,以配合下面所示的程式碼執行。 建立空白應用程式 (C++/WinRT)。 以 Windows SDK 最新的正式推出版本 (即非預覽版本) 為目標。

  2. 開啟pch.h並新增下列內容,這些內容位於已經存在的內容之下。

    // 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. 將下面顯示的指令using加到指令的上方,MainPage.cpp即已存在的指令的下方。 另外,MainPage.cpp在中,請用下列清單取代 MainPage::ClickHandler。 程式碼會建立 Direct 3D 裝置、Direct 2D 裝置和 Direct 2D 裝置內容。 此,D3D11CreateDevice、D2D1CreateDevice,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. 接下來,加入程式碼以建立 SurfaceImageSource,然後透過呼叫 ISurfaceImageSourceNativeWithD2D::SetDevice 來設定 Direct 2D (或Direct 3D) 裝置。

    注意

    如果您要從背景執行緒繪製到 SurfaceImageSource,則還需要確保 DXGI 裝置已啟用多執行緒存取 (如下面的程式碼所示)。 出於效能考慮,僅當您要從背景執行緒繪製時,才應執行此操作。

    將高度和寬度傳遞給 SurfaceImageSource 建構函式,定義共用曲面的大小。 您也可以指示曲面是否需要 alpha (不透明度) 支援。

    若要設定裝置,並執行繪製作業,我們需要指向 ISurfaceImageSourceNativeWithD2D 的指標。 若要取得一個,請查詢 SurfaceImageSource 物件的 ISurfaceImageSourceNativeWithD2D 介面。

    // 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. 呼叫 ISurfaceImageSourceNativeWithD2D::BeginDraw,DXGI 介面 (IDXGI Surface)。 如果已啟用多執行緒存取,則可以從背景執行緒呼叫 ISurfaceImageSourceNativeWithD2D::BeginDraw (以及後來的繪圖命令)。 在此步驟中,您還會從 DXGI 曲面建立點陣圖,並將它設定為 Direct 2D 裝置內容。

    offset 參數中,ISurfaceImageSourceNativeWithD2D::BeginDraw 會傳回更新的目標矩形的點位移 (x,y 值)。 您可以使用該位移來決定使用 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. 使用 Direct 2D 裝置前後關聯來繪製 SurfaceImageSource 的內容。 僅會繪製 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. 呼叫 ISurfaceImageSourceNativeWithD2D::EndDraw (您必須僅從 UI 執行緒呼叫 ISurfaceImageSourceNativeWithD2D::EndDraw) 以完成點陣圖。 然後設定 XAML Image (或 ImageBrush) 上的 SurfaceImageSource,以便在 XAML UI 中顯示它。

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

    注意

    呼叫 SurfaceImageSource::SetSource (繼承自 IBitmapSource::SetSource) 目前擲回例外狀況。 不要從 SurfaceImageSource 物件呼叫它。

    注意

    Window 隱藏或非使用中時,請避免繪製到 SurfaceImageSource ,否則 ISurfaceImageSourceNativeWithD2D API 將會失敗。 處理視窗可視性相關事件以及應用程式暫停以完成此作業。

  8. 最後,在中的現有 XAML 標示內加入下列 Image MainPage.xaml元素。

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  9. 您現在可以建置並執行應用程式。 按一下按鈕以檢視 SurfaceImageSource (顯示在圖像中) 的內容。

    A thick, dark orange rectanglular outline against a lighter orange background

VirtualSurfaceImageSource

VirtualSurfaceImageSource,是對 SurfaceImageSource 的擴展,適用於內容可能太大,無法一次全部放入螢幕的情況 (和/或太大而無法作為單一紋理放入影片記憶體的情況),因此必須對內容進行虛擬化,才能最優地呈現內容。 例如,對映應用程式或大型檔案畫布。

提示

複雜著色範例應用程式會顯示 VirtualSurfaceImageSource。

VirtualSurfaceImageSource 與 SurfaceImageSource 的不同之處在於,它使用回呼,IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded ,當曲面區域在螢幕上可見時,您實作該回呼來更新曲面區域。 您不需要清除隱藏的區域,因為 XAML 框架會為您處理該問題。

最好先熟悉 SurfaceImageSource (請參閱上面的 SurfaceImageSource 部分),然後再處理 VirtualSurfaceImageSource。 但是,從非常高的層次來看,這裡是一個建立和更新 VirtualSurfaceImageSource 的過程。

  • 實作 IVirtualSurfaceImageSourceCallbackNative 介面。
  • 建立 Direct 3D 裝置、Direct 2D 裝置和 Direct 2D 裝置內容。
  • 建立 VirtualSurfaceImageSource,並在此設定 Direct 2D (或 Direct 3D) 裝置。
  • 呼叫 VirtualSurfaceImageSource 上的 RegisterForUpdatesNeeded。
  • 在您的 UpdatesNeeded 回呼中,呼叫 GetUpdateRectCount GetUpdateRects。
  • 轉譯更/新的矩形 (使用 BeginDrawEndDraw ,就像 SurfaceImageSource 一樣)。
  • 設定 XAML Image ImageBrush 上的 SurfaceImageSource,以便在 XAML UI 中顯示它。

下面是對這些步驟進行深入的深入分析並提供原始碼範例。

  1. 您可以在 Microsoft Visual Studio 中建立新專案,以配合下面所示的程式碼執行。 建立空白應用程式 (C++/WinRT),並將其命名為 VSIDemo (如果您要在下面列出的程式碼清單中複製貼上,請務必將這個名稱指定給專案)。 以 Windows SDK 最新的正式推出版本 (即非預覽版本) 為目標。

  2. 開啟pch.h並新增下列內容,這些內容位於已經存在的內容之下。

    // 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. 此步驟中,您將提供 IVirtualSurfaceUpdatesCallbackNative 介面的實作。 將新的標頭檔案 (.h),新增至專案,並將其命名為 CallbackImplementation.h。 請用下列內容取代該檔案的內容。 該程式碼在上市後解釋。

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

    每當需要更新 VirtualSurfaceImageSource 的區域時,架構會呼叫您對 IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded (如上所示) 的實作。

    當框架確定需要繪製區域時 (例如,當使用者平移或縮放曲面檢視時),或當您的應用程式在該區域上呼叫 IVirtualSurfaceImageSourceNative::Invalidate,就可能發生這種情況。

    IVirtualSurfaceImageSourceNative::UpdatesNeeded 中,使IVirtualSurfaceImageSourceNative::GetUpdateRectCount 和 IVirtualSurfaceImageSourceNative::GetUpdateRects 方法來確定必須繪製曲面的區域。

    對於必須更新的每個區域,繪製特定內容到該區域,但將繪圖限制到限制區域以獲得更好的效能。 呼叫 ISurfaceImageSourceNativeWithD2D 方法的具體細節與呼叫 SurfaceImageSource 的方法相同 (請參閱上面的 SurfaceImageSource)。

    注意

    Window 隱藏或非使用中時,請避免繪製至 VirtualSurfaceImageSource,否則 ISurfaceImageSourceNativeWithD2D API 將會失敗。 處理視窗可視性相關事件以及應用程式暫停以完成此作業。

  4. MainPage 類別中,我們將新增 CallbackImplementation 型別的成員。 我們還將建立 Direct 3D 裝置、Direct 2D 裝置和 Direct 2D 裝置內容。 此,D3D11CreateDevice、D2D1CreateDevice,ID2D1Device::CreateDeviceContext。

    MainPage.idl將、MainPage.hMainPage.cpp的內容替換為以下清單的內容。

    // 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. 接下來,加入程式碼以建立具有您想要大小的 VirtualSurfaceImageSource,然後透過呼叫 ISurfaceImageSourceNativeWithD2D::SetDevice 來在此設定 Direct 2D (或 Direct 3D) 裝置。

    注意

    如果您要從背景執行緒繪製至 VirtualSurfaceImageSource,則還需要確保 DXGI 裝置已啟用多執行緒存取 (如下面的程式碼所示)。 出於效能考慮,僅當您要從背景執行緒繪製時,才應執行此操作。

    若要設定裝置,並執行繪製作業,我們需要指向 ISurfaceImageSourceNativeWithD2D 的指標。 若要取得一個,請查詢 VirtualSurfaceImageSource 物件的 ISurfaceImageSourceNativeWithD2D 介面。

    IVirtualSurfaceImageSourceNative,IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded,IVirtualSurfaceUpdatesCallbackNative 的實作。

    然後設定 XAML Image (或 ImageBrush) 上的 SurfaceImageSource,以便在 XAML UI 中顯示它。

    // 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. 最後,在中的現有 XAML 標示內加入下列 Image MainPage.xaml元素。

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  7. 您現在可以建置並執行應用程式。 按一下按鈕以檢視 Image 中顯示的內容 VirtualSurfaceImageSource。

SwapChainPanel 與遊戲

SwapChainPanel Windows Runtime 型別旨在支援高效能圖形和遊戲,讓您直接管理交換鏈。 在這種情況下,您可以建立自己的 DirectX 交換鏈結,並管理呈現內容的呈現方式。 SwapChainPanel 的另一個功能是,您可以在其前面覆蓋其他 XAML 元素。

提示

下列範例應用程式示範 SurfaceImageSource:Direct2D 進階彩色影像呈現、Direct2D 相片調整、Direct2D SVG 呈現、低延遲輸入 (Windows 8.1)。

為確保良好的效能,SwapChainPanel 型別有某些限制。

您會根據應用程式的需求更新 SwapChainPanel,而不會與 XAML 架構的更新同步。 如果您需要將 SwapChainPanel 的更新與 XAML 架構的更新同步,請註冊 Windows::UI::Xaml::Media::CompositionTarget::Rendering。 否則,如果您嘗試從更新 SwapChainPanel 的執行緒以外的執行緒更新 XAML 元素,則必須考慮任何跨執行緒問題。

如果您需要接收 SwapChainPanel 的低延遲指標輸入,請使用 SwapChainPanel::CreateCoreIndependentInputSource。 此方法會傳回 CoreIndependentInputSource,可用於以最小延遲在背景執行緒上接收輸入事件。 請備註,呼叫此方法後,不會針對 SwapChainPanel 引發一般 XAML 指標輸入事件,因為所有輸入都會重新導向至背景執行緒。

以下是建立和更新 SwapChainPanel 物件的程式。

  1. 您可以在 Microsoft Visual Studio 中建立新專案,以配合下面所示的程式碼執行。 建立空白應用程式 (C++/WinRT),並將其命名為 SCPDemo (如果您要在下面列出的程式碼清單中複製貼上,請務必將這個名稱指定給專案)。 以 Windows SDK 最新的正式推出版本 (即非預覽版本) 為目標。

  2. 開啟pch.h並新增下列內容,這些內容位於已經存在的內容之下。

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    
  3. MainPage 型別中,我們將首先建立 Direct 3D 裝置、Direct 2D 裝置和 Direct 2D 裝置內容。 此,D3D11CreateDevice、D2D1CreateDevice,ID2D1Device::CreateDeviceContext。

    MainPage.idl將、MainPage.hMainPage.cpp的內容替換為以下清單的內容。

    // 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. SwapChainPanel 元素中,用x:Name換行 XAML 標籤。 包裝的 XAML 元素將會在 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>
    

    然後,您可以透過存取子函式,以相同的名稱存取該 SwapChainPanel,如我們所見。

  5. 接著,呼叫 IDXGI Factory2::CreateSwapChainForComposition,以建立交換鏈。

    // 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. 名為 swapChainPanel 的 SwapChainPanel,取得 ISwapChainPanelNative。 呼ISwapChainPanelNative::SetSwapChain,設定 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. 最後,繪製至 DirectX 交換鏈結,然後顯示它以顯示內容。

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

    當 Windows 執行階段配置/轉譯邏輯發出更新訊號時,會重新整理 XAML 元素。

  8. 您現在可以建置並執行應用程式。 按一下按鈕,檢視在其他 XAML 元素後面顯示的 SwapChainPanel。

    A Direct2D-rendered rectangle behind a XAML button element

注意

一般而言,您的 DirectX 應用程式應該建立橫向交換鏈,並且等於顯示視窗的大小 (在大多數 Microsoft Store 遊戲中,通常是原生螢幕解析度)。 這可以確保您的應用程式在沒有任何可見的 XAML 覆蓋時,使用最佳的交換鏈實作。 如果應用程式已旋轉為縱向模式,則您的應用程式應呼叫現有交換鏈上的 IDXGISwapChain1::SetRotation,視需要套用轉換至內容,然後在相同的交換鏈上再次呼叫 SetSwapChain。 同樣地,只要呼叫 IDXGI SwapChain::ResizeBuffers 來調整交換鏈的大小,您的應用程式就應該在相同的交換鏈上再次呼叫 SetSwapChain。