DirectX 和 XAML 互操作
XAML 和 DirectX
DirectX 为二维和三维图形提供了两个功能强大的库:Direct2D 和 Microsoft Direct3D。 尽管 XAML 提供了对基本 2D 基元和效果的支持,但许多应用(例如建模和游戏应用)需要更复杂的图形支持。 对于这些应用,可以使用 Direct2D 和 Direct3D 呈现部分或全部图形,而使用 XAML 呈现所有其他内容。
如果要实现自定义 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 框架刷新计时器即可刷新图形。 此类型使你能够直接访问图形设备的交换链 (IDXGISwapChain1),并将 XAML 放在呈现目标之上。 此类型很适合需要基于 XAML 的用户界面的游戏和全屏 DirectX 应用。 若要使用此方法,你必须熟悉 DirectX,包括 Microsoft DirectX 图形基础结构 (DXGI)、Direct2D 和 Direct3D 技术。 有关详细信息,请参阅 Direct3D 11 编程指南。
SurfaceImageSource
SurfaceImageSource 提供要绘入的 DirectX 共享图面,然后将位编写到应用内容中。
下面是在代码隐藏文件中创建和更新 SurfaceImageSource 对象的基本过程:
-
通过将高度和宽度传递到 SurfaceImageSource 构造函数定义共享图面的大小。 还可以指示图面是否需要 alpha(透明度)支持。
例如:
SurfaceImageSource^ surfaceImageSource = ref new SurfaceImageSource(400, 300);
-
获取 ISurfaceImageSourceNativeWithD2D 的指针。 将 SurfaceImageSource 对象强制转换为 IInspectable(或 IUnknown),并对它调用 QueryInterface 以获取基础 ISurfaceImageSourceNativeWithD2D 实现。 你使用在此实现上定义的方法设置设备并运行绘图操作。
C++复制
Microsoft::WRL::ComPtr<ISurfaceImageSourceNativeWithD2D> m_sisNativeWithD2D; // ... IInspectable* sisInspectable = (IInspectable*) reinterpret_cast<IInspectable*>(surfaceImageSource); sisInspectable->QueryInterface( __uuidof(ISurfaceImageSourceNativeWithD2D), (void **)&m_sisNativeWithD2D);
-
首先调用 D3D11CreateDevice 和 D2D1CreateDevice,然后将设备和文本传递到 ISurfaceImageSourceNativeWithD2D::SetDevice,以创建 DXGI 和 D2D 设备。
备注
如果将从后台线程绘制到 SurfaceImageSource,则你也需要确保 DXGI 设备已启用多线程访问。 仅可在出于性能原因需要从后台线程进行绘制时才可进行此操作。
例如:
C++复制
Microsoft::WRL::ComPtr<ID3D11Device> m_d3dDevice; Microsoft::WRL::ComPtr<ID3D11DeviceContext> m_d3dContext; Microsoft::WRL::ComPtr<ID2D1Device> m_d2dDevice; // Create the DXGI device D3D11CreateDevice( NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &m_d3dDevice, NULL, &m_d3dContext); Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice; m_d3dDevice.As(&dxgiDevice); // To enable multi-threaded access (optional) Microsoft::WRL::ComPtr<ID3D10Multithread> d3dMultiThread; m_d3dDevice->QueryInterface( __uuidof(ID3D10Multithread), (void **) &d3dMultiThread); d3dMultiThread->SetMultithreadProtected(TRUE); // Create the D2D device D2D1CreateDevice(m_dxgiDevice.Get(), NULL, &m_d2dDevice); // Set the D2D device m_sisNativeWithD2D->SetDevice(m_d2dDevice.Get());
-
将 ID2D1DeviceContext 对象的指针提供给 ISurfaceImageSourceNativeWithD2D::BeginDraw,然后使用返回的绘制上下文在 SurfaceImageSource 内绘制所需的矩形内容。 可从后台线程调用 ISurfaceImageSourceNativeWithD2D::BeginDraw 和绘制命令。 仅绘制在 updateRect 参数中指定的更新区域。
此方法在 offset 参数中返回更新的目标矩形的点 (x,y) 偏移。 你可以使用此偏移来确定使用 ID2D1DeviceContext 绘制更新内容的位置。
C++复制
Microsoft::WRL::ComPtr<ID2D1DeviceContext> drawingContext; HRESULT beginDrawHR = m_sisNative->BeginDraw( updateRect, &drawingContext, &offset); if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED || beginDrawHR == DXGI_ERROR_DEVICE_RESET || beginDrawHR == D2DERR_RECREATE_TARGET) { // The D3D and D2D device was lost and need to be re-created. // Recovery steps are: // 1) Re-create the D3D and D2D devices // 2) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the new D2D // 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 D2D // device again // 2) Redraw the entire contents of the SurfaceImageSource } else { // Draw your updated rectangle with the drawingContext }
-
调用 ISurfaceImageSourceNativeWithD2D::EndDraw 完成此位图。 位图可用作 XAML Image 或 ImageBrush 的源。 仅可从 UI 线程调用 ISurfaceImageSourceNativeWithD2D::EndDraw。
C++复制
m_sisNative->EndDraw(); // ... // The SurfaceImageSource object's underlying // ISurfaceImageSourceNativeWithD2D object contains the completed bitmap. ImageBrush^ brush = ref new ImageBrush(); brush->ImageSource = surfaceImageSource;
备注
当前调用 SurfaceImageSource::SetSource(从 IBitmapSource::SetSource 继承)将引发异常。 不要从你的 SurfaceImageSource 对象调用它。
备注
当关联的 Window 隐藏时,应用必须避免绘制到 SurfaceImageSource,否则,ISurfaceImageSourceNativeWithD2D API 将会失败。 为实现此目的,请为 Window.VisibilityChanged 事件注册为事件侦听器,以跟踪可见性变化。
VirtualSurfaceImageSource
VirtualSurfaceImageSource 在内容可能比可用屏幕大时扩展 SurfaceImageSource,使内容可以获得最佳的呈现效果。
VirtualSurfaceImageSource 不同于 SurfaceImageSource,因为它使用了一个回调 IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded,你可以实现该回调,当图面区域在屏幕上可见时更新该区域。 你不需要清除隐藏的区域,因为 XAML 框架会负责此事。
这是在代码隐藏文件中创建和更新 VirtualSurfaceImageSource 对象的基本过程:
-
创建一个具有所需大小的 VirtualSurfaceImageSource 实例。 例如:
C++复制
VirtualSurfaceImageSource^ virtualSIS = ref new VirtualSurfaceImageSource(2000, 2000);
-
获取 IVirtualSurfaceImageSourceNative 和 ISurfaceImageSourceNativeWithD2D 的指针。 将 VirtualSurfaceImageSource 对象强制转换为 IInspectable 或 IUnknown,并对它调用 QueryInterface 以获取基础的 IVirtualSurfaceImageSourceNative 和 ISurfaceImageSourceNativeWithD2D 实现。 你使用在这些实现上定义的方法设置设备并运行绘图操作。
C++复制
Microsoft::WRL::ComPtr<IVirtualSurfaceImageSourceNative> m_vsisNative; Microsoft::WRL::ComPtr<ISurfaceImageSourceNativeWithD2D> m_sisNativeWithD2D; // ... IInspectable* vsisInspectable = (IInspectable*) reinterpret_cast<IInspectable*>(virtualSIS); vsisInspectable->QueryInterface( __uuidof(IVirtualSurfaceImageSourceNative), (void **) &m_vsisNative); vsisInspectable->QueryInterface( __uuidof(ISurfaceImageSourceNativeWithD2D), (void **) &m_sisNativeWithD2D);
-
首先调用 D3D11CreateDevice 和 D2D1CreateDevice,然后将 D2D 设备传递给 ISurfaceImageSourceNativeWithD2D::SetDevice,以创建 DXGI 和 D2D 设备。
备注
如果将从后台线程绘制到 VirtualSurfaceImageSource,则你也需要确保 DXGI 设备已启用多线程访问。 仅可在出于性能原因需要从后台线程进行绘制时才可进行此操作。
例如:
C++复制
Microsoft::WRL::ComPtr<ID3D11Device> m_d3dDevice; Microsoft::WRL::ComPtr<ID3D11DeviceContext> m_d3dContext; Microsoft::WRL::ComPtr<ID2D1Device> m_d2dDevice; // Create the DXGI device D3D11CreateDevice( NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &m_d3dDevice, NULL, &m_d3dContext); Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice; m_d3dDevice.As(&dxgiDevice); // To enable multi-threaded access (optional) Microsoft::WRL::ComPtr<ID3D10Multithread> d3dMultiThread; m_d3dDevice->QueryInterface( __uuidof(ID3D10Multithread), (void **) &d3dMultiThread); d3dMultiThread->SetMultithreadProtected(TRUE); // Create the D2D device D2D1CreateDevice(m_dxgiDevice.Get(), NULL, &m_d2dDevice); // Set the D2D device m_vsisNativeWithD2D->SetDevice(m_d2dDevice.Get()); m_vsisNative->SetDevice(dxgiDevice.Get());
-
调用 IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded,传入对 IVirtualSurfaceUpdatesCallbackNative 实现的引用。
C++复制
class MyContentImageSource : public IVirtualSurfaceUpdatesCallbackNative { // ... private: virtual HRESULT STDMETHODCALLTYPE UpdatesNeeded() override; } // ... HRESULT STDMETHODCALLTYPE MyContentImageSource::UpdatesNeeded() { // .. Perform drawing here ... } void MyContentImageSource::Initialize() { // ... m_vsisNative->RegisterForUpdatesNeeded(this); // ... }
框架在一个 VirtualSurfaceImageSource 区域需要更新时调用你的 IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded 实现。
在两个时刻可发生此情况:当框架确定该区域需要绘制时(例如当用户平移或缩放图面的视图时),或者在应用在该区域上调用 IVirtualSurfaceImageSourceNative::Invalidate 时。
-
在 IVirtualSurfaceImageSourceNative::UpdatesNeeded 中,使用 IVirtualSurfaceImageSourceNative::GetUpdateRectCount 和 IVirtualSurfaceImageSourceNative::GetUpdateRects 方法确定必须绘制图面的哪些区域。
C++复制
HRESULT STDMETHODCALLTYPE MyContentImageSource::UpdatesNeeded() { HRESULT hr = S_OK; try { 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) { // Drawing code here ... } } catch (Platform::Exception^ exception) { hr = exception->HResult; } return hr; }
-
最后,对于必须进行更新的每个区域:
-
将 ID2D1DeviceContext 对象的指针提供给 ISurfaceImageSourceNativeWithD2D::BeginDraw,然后使用返回的绘制上下文在 SurfaceImageSource 内绘制所需的矩形内容。 可从后台线程调用 ISurfaceImageSourceNativeWithD2D::BeginDraw 和绘制命令。 仅绘制在 updateRect 参数中指定的更新区域。
此方法在 offset 参数中返回更新的目标矩形的点 (x,y) 偏移。 你可以使用此偏移来确定使用 ID2D1DeviceContext 绘制更新内容的位置。
C++复制
Microsoft::WRL::ComPtr<ID2D1DeviceContext> drawingContext; HRESULT beginDrawHR = m_sisNative->BeginDraw( updateRect, &drawingContext, &offset); if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED || beginDrawHR == DXGI_ERROR_DEVICE_RESET || beginDrawHR == D2DERR_RECREATE_TARGET) { // The D3D and D2D devices were lost and need to be re-created. // Recovery steps are: // 1) Re-create the D3D and D2D devices // 2) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the // new D2D device // 3) Redraw the contents of the VirtualSurfaceImageSource } else if (beginDrawHR == E_SURFACE_CONTENTS_LOST) { // The devices were not lost but the entire contents of the // surface were lost. Recovery steps are: // 1) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the // D2D device again // 2) Redraw the entire contents of the VirtualSurfaceImageSource } else { // Draw your updated rectangle with the drawingContext }
-
将特定内容绘制到该区域,但是将你的图形限制在有限的区域以提高性能。
-
调用 ISurfaceImageSourceNativeWithD2D::EndDraw。 结果是位图。
-
备注
当关联的 Window 隐藏时,应用必须避免绘制到 SurfaceImageSource,否则,ISurfaceImageSourceNativeWithD2D API 将会失败。 为实现此目的,请为 Window.VisibilityChanged 事件注册为事件侦听器,以跟踪可见性变化。
SwapChainPanel 和游戏
SwapChainPanel 是用于支持高性能图形和游戏的 Windows 运行时类型,你可以在其中直接管理交换链。 在此情况下,你可以创建自己的 DirectX 交换链并管理所呈现内容的显示。
若要确保良好的性能,SwapChainPanel 类型有一些限制:
- 每个应用的 SwapChainPanel 实例不超过 4 个。
- DirectX 交换链的高度和宽度应设置 (在DXGI_SWAP_CHAIN_DESC1) 的交换链元素的当前维度。 如果不这样做,将缩放显示内容 (使用DXGI_缩放_STRETCH) 以适应。
- 您必须将 DirectX 交换链的缩放模式设置 (在DXGI_SWAP_CHAIN_DESC1) 到DXGI_缩放_STRETCH。
- 不能设置 DirectX 交换链的 alpha 模式 (在DXGI_SWAP_CHAIN_DESC1) 到DXGI_ALPHA_模式_自左乘。
- 必须调用 IDXGIFactory2::CreateSwapChainForComposition 来创建 DirectX 交换链。
你基于应用的需求来更新 SwapChainPanel,而不是 XAML 框架的更新。 如果需要将 SwapChainPanel 的更新与 XAML 框架的更新同步,可以注册 Windows::UI::Xaml::Media::CompositionTarget::Rendering 事件。 否则,如果尝试通过与更新 SwapChainPanel 的线程不同的线程更新 XAML 元素,则必须考虑任何跨线程问题。
如果你需要接收 SwapChainPanel 的低延迟指针输入,请使用 SwapChainPanel::CreateCoreIndependentInputSource。 此方法将返回 CoreIndependentInputSource 对象,该对象可用于在后台线程上以最小延迟接收输入事件。 请注意,在调用此方法后,将不会对 SwapChainPanel 引发正常 XAML 指针输入事件,因为所有输入都将重定向到后台线程。
注意 通常,你的 DirectX 应用应当在横向创建交换链,并等于显示窗口大小(这在大多数 Microsoft Store 游戏中通常为本机屏幕分辨率)。 这可确保你的应用在没有任何可见 XAML 覆盖时使用最佳交换链实现。 如果应用旋转到纵向模式,你的应用应在现有交换链上调用 IDXGISwapChain1::SetRotation,根据需要应用内容转换,然后在同一交换链上再次调用 SetSwapChain。 类似地,每当通过调用 IDXGISwapChain::ResizeBuffers 调整了交换链的大小时,你的应用都应当再次在同一交换链上调用SetSwapChain。
这是在代码隐藏文件中创建和更新 SwapChainPanel 对象的基本过程。
-
获取应用的交换链面板的实例。 这些实例在 XAML 中使用
<SwapChainPanel>
标记表示。Windows::UI::Xaml::Controls::SwapChainPanel^ swapChainPanel;
下面是一个示例
<SwapChainPanel>
标记。XML复制
<SwapChainPanel x:Name="swapChainPanel"> <SwapChainPanel.ColumnDefinitions> <ColumnDefinition Width="300*"/> <ColumnDefinition Width="1069*"/> </SwapChainPanel.ColumnDefinitions> …
-
获取 ISwapChainPanelNative 的指针。 将 SwapChainPanel 对象强制转换为 IInspectable(或 IUnknown),并对它调用 QueryInterface 以获取基础 ISwapChainPanelNative 实现。
C++复制
Microsoft::WRL::ComPtr<ISwapChainPanelNative> m_swapChainNative; // ... IInspectable* panelInspectable = (IInspectable*) reinterpret_cast<IInspectable*>(swapChainPanel); panelInspectable->QueryInterface(__uuidof(ISwapChainPanelNative), (void **)&m_swapChainNative);
-
创建 DXGI 设备和交换链,并将交换链设置为 ISwapChainPanelNative,方法是将它传递给 SetSwapChain。
C++复制
Microsoft::WRL::ComPtr<IDXGISwapChain1> m_swapChain; // ... DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0}; swapChainDesc.Width = m_bounds.Width; swapChainDesc.Height = m_bounds.Height; 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; // QI for DXGI device Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice; m_d3dDevice.As(&dxgiDevice); // Get the DXGI adapter. Microsoft::WRL::ComPtr<IDXGIAdapter> dxgiAdapter; dxgiDevice->GetAdapter(&dxgiAdapter); // Get the DXGI factory. Microsoft::WRL::ComPtr<IDXGIFactory2> dxgiFactory; dxgiAdapter->GetParent(__uuidof(IDXGIFactory2), &dxgiFactory); // Create a swap chain by calling CreateSwapChainForComposition. dxgiFactory->CreateSwapChainForComposition( m_d3dDevice.Get(), &swapChainDesc, nullptr, // Allow on any display. &m_swapChain ); m_swapChainNative->SetSwapChain(m_swapChain.Get());
-
绘制到 DirectX 交换链,并提供它来显示内容。
C++复制
HRESULT hr = m_swapChain->Present(1, 0);
在 Windows 运行时布局/呈现逻辑指示更新时,刷新 XAML 元素。