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 應用程式。
- 如果您要構成靜態影像,或以事件驅動的間隔繪製複雜影像,則使用 Windows::UI::Xaml::Media::Imaging::SurfaceImageSource 繪製共用曲面。 該型別處理尺寸為 DirectX 的工程圖曲面。 通常,在構成影像或紋理作為點陣圖以在檔案或 UI 元素中顯示時,會使用此型別。 對於即時互動 (例如高效能遊戲),它不是很有效。 這是因為對SurfaceImageSource 對象的更新會與 XAML 使用者介面更新同步,這會在您提供給使用者的視覺回饋中引入延遲,例如波動的畫面速率,或感覺對即時輸入反應不佳。 不過,對於動態控制或資料模擬,更新仍足夠快。
- 如果影像大於提供的螢幕區域,且使用者可以縮放影像,則使用 Windows::UI::Xaml::Media::Imaging::VirtualSurfaceImageSource。 該型別處理尺寸大於螢幕的 DirectX 繪圖曲面。 與 SurfaceImageSource,您可在動態構成複雜影像或控制項時使用此選項。 而且,與 SurfaceImageSource,它在高效能遊戲中效果不佳。 可以使用 VirtualSurfaceImageSource XAML 元素的一些範例是地圖控制項,或大型的影像密集檔案檢視器。
- 如果您正在使用 DirectX 顯示即時更新的圖形,或者更新必須按正常低延遲間隔進行,請使用 SwapChainPanel 類別,以便您可以重新整理圖形,而不必同步到 XAML Framework 重新整理計時器。 使用SwapChainPanel,您可以直接存取圖形裝置的交換鏈 (IDXGI SwapChain1),並在彩現目標上方圖層 XAML。 SwapChainPanel,適用於需要基於 XAML 的使用者介面的遊戲和全螢幕 DirectX 應用程式。 您必須熟悉 DirectX 才能使用此方法,包括 Microsoft DirectX Graphics Infrastructure (DXGI)、Direct2D 和 Direct3D 技術。 如需詳細資訊,請參閱 Programming Guide for Direct3D 11。
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 中顯示它。
下面是對這些步驟進行深入的深入分析並提供原始碼範例。
您可以在 Microsoft Visual Studio 中建立新專案,以配合下面所示的程式碼執行。 建立空白應用程式 (C++/WinRT)。 以 Windows SDK 最新的正式推出版本 (即非預覽版本) 為目標。
開啟
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>
將下面顯示的指令
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() ) ); }
接下來,加入程式碼以建立 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);
呼叫 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());
使用 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(); }
呼叫 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 將會失敗。 處理視窗可視性相關事件以及應用程式暫停以完成此作業。
最後,在中的現有 XAML 標示內加入下列 Image
MainPage.xaml
元素。<!-- MainPage.xaml --> ... <Image x:Name="theImage" Width="500" Height="500" /> ...
您現在可以建置並執行應用程式。 按一下按鈕以檢視 SurfaceImageSource (顯示在圖像中) 的內容。
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 中顯示它。
下面是對這些步驟進行深入的深入分析並提供原始碼範例。
您可以在 Microsoft Visual Studio 中建立新專案,以配合下面所示的程式碼執行。 建立空白應用程式 (C++/WinRT),並將其命名為 VSIDemo (如果您要在下面列出的程式碼清單中複製貼上,請務必將這個名稱指定給專案)。 以 Windows SDK 最新的正式推出版本 (即非預覽版本) 為目標。
開啟
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>
在此步驟中,您將提供 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 將會失敗。 處理視窗可視性相關事件以及應用程式暫停以完成此作業。
在 MainPage 類別中,我們將新增 CallbackImplementation 型別的成員。 我們還將建立 Direct 3D 裝置、Direct 2D 裝置和 Direct 2D 裝置內容。 為此,我們將呼叫 D3D11CreateDevice、D2D1CreateDevice,以及 ID2D1Device::CreateDeviceContext。
MainPage.idl
將、MainPage.h
和MainPage.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() ) ); } }
接下來,加入程式碼以建立具有您想要大小的 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);
最後,在中的現有 XAML 標示內加入下列 Image
MainPage.xaml
元素。<!-- MainPage.xaml --> ... <Image x:Name="theImage" Width="500" Height="500" /> ...
您現在可以建置並執行應用程式。 按一下按鈕以檢視 Image 中顯示的內容 VirtualSurfaceImageSource。
SwapChainPanel 與遊戲
SwapChainPanel Windows Runtime 型別旨在支援高效能圖形和遊戲,讓您直接管理交換鏈。 在這種情況下,您可以建立自己的 DirectX 交換鏈結,並管理呈現內容的呈現方式。 SwapChainPanel 的另一個功能是,您可以在其前面覆蓋其他 XAML 元素。
提示
下列範例應用程式示範 SurfaceImageSource:Direct2D 進階彩色影像呈現、Direct2D 相片調整、Direct2D SVG 影像呈現、低延遲輸入 (Windows 8.1)。
為確保良好的效能,SwapChainPanel 型別有某些限制。
- 每個應用程式最多只能有 4 個 SwapChainPanel。
- 您應該將 DirectX 交換鏈的高度與寬度 (在 DXGI _SWAP_CHAIN_DESC1) 設定為交換鏈元素的目前尺寸。 如果您不這樣做,則會縮放顯示內容以符合大小 (使用 DXGI _SCALING_STRETCH)。
- 您必須將 DirectX 交換鏈的縮放模式 (在 DXGI _SWAP_CHAIN_DESC1) 設定為 DXGI_SCALING_STRETCH。
- 您必須呼叫 IDXGI Factory2::CreateSwapChainForComposition 來建立 DirectX 交換鏈。
您會根據應用程式的需求更新 SwapChainPanel,而不會與 XAML 架構的更新同步。 如果您需要將 SwapChainPanel 的更新與 XAML 架構的更新同步,請註冊 Windows::UI::Xaml::Media::CompositionTarget::Rendering。 否則,如果您嘗試從更新 SwapChainPanel 的執行緒以外的執行緒更新 XAML 元素,則必須考慮任何跨執行緒問題。
如果您需要接收 SwapChainPanel 的低延遲指標輸入,請使用 SwapChainPanel::CreateCoreIndependentInputSource。 此方法會傳回 CoreIndependentInputSource,可用於以最小延遲在背景執行緒上接收輸入事件。 請備註,呼叫此方法後,不會針對 SwapChainPanel 引發一般 XAML 指標輸入事件,因為所有輸入都會重新導向至背景執行緒。
以下是建立和更新 SwapChainPanel 物件的程式。
您可以在 Microsoft Visual Studio 中建立新專案,以配合下面所示的程式碼執行。 建立空白應用程式 (C++/WinRT),並將其命名為 SCPDemo (如果您要在下面列出的程式碼清單中複製貼上,請務必將這個名稱指定給專案)。 以 Windows SDK 最新的正式推出版本 (即非預覽版本) 為目標。
開啟
pch.h
並新增下列內容,這些內容位於已經存在的內容之下。// pch.h ... #include <d3d11_4.h> #include <d2d1_1.h> #include <windows.ui.xaml.media.dxinterop.h>
在 MainPage 型別中,我們將首先建立 Direct 3D 裝置、Direct 2D 裝置和 Direct 2D 裝置內容。 為此,我們將呼叫 D3D11CreateDevice、D2D1CreateDevice,以及 ID2D1Device::CreateDeviceContext。
MainPage.idl
將、MainPage.h
和MainPage.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() ) ); } }
在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,如我們所見。
接著,呼叫 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());
從您命名為 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()) );
最後,繪製至 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 元素。
您現在可以建置並執行應用程式。 按一下按鈕,檢視在其他 XAML 元素後面顯示的 SwapChainPanel。
注意
一般而言,您的 DirectX 應用程式應該建立橫向交換鏈,並且等於顯示視窗的大小 (在大多數 Microsoft Store 遊戲中,通常是原生螢幕解析度)。 這可以確保您的應用程式在沒有任何可見的 XAML 覆蓋時,使用最佳的交換鏈實作。 如果應用程式已旋轉為縱向模式,則您的應用程式應呼叫現有交換鏈上的 IDXGISwapChain1::SetRotation,視需要套用轉換至內容,然後在相同的交換鏈上再次呼叫 SetSwapChain。 同樣地,只要呼叫 IDXGI SwapChain::ResizeBuffers 來調整交換鏈的大小,您的應用程式就應該在相同的交換鏈上再次呼叫 SetSwapChain。
相關主題
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應