Direct2D 和 Direct3D 互操作性概述
硬件加速二维和三维图形正日益成为非游戏应用程序的一部分,大多数游戏应用程序以菜单和Heads-Up显示器的形式使用二维图形, (HUD) 。 通过将传统的二维渲染与 Direct3D 渲染高效混合,可以增加许多价值。
本主题介绍如何使用 Direct2D 和 Direct3D 集成二维和三维图形。
先决条件
本概述假定你熟悉基本的 Direct2D 绘图操作。 有关教程,请参阅 创建简单的 Direct2D 应用程序。 它还假定可以使用 Direct3D 10.1 进行编程。
支持的 Direct3D 版本
在 DirectX 11.0 中,Direct2D 仅支持与 Direct3D 10.1 设备的互操作性。 对于 DirectX 11.1 或更高版本,Direct2D 还支持与 Direct3D 11 的互操作性。
通过 DXGI 实现互操作性
从 Direct3D 10 起,Direct3D 运行时使用 DXGI 进行资源管理。 DXGI 运行时层提供视频内存图面的跨进程共享,并充当其他基于视频内存的运行时平台的基础。 Direct2D 使用 DXGI 与 Direct3D 互操作。
可通过两种主要方式同时使用 Direct2D 和 Direct3D:
- 可以通过获取 IDXGISurface 并将其与 CreateDxgiSurfaceRenderTarget 配合使用来创建 ID2D1RenderTarget,将 Direct2D 内容写入 Direct3D 图面。 然后,可以使用呈现目标向三维图形添加二维界面或背景,或使用 Direct2D 绘图作为三维对象的纹理。
- 通过使用 CreateSharedBitmap 从 IDXGISurface 创建 ID2D1Bitmap,可以将 Direct3D 场景写入位图并使用 Direct2D 呈现。
使用 DXGI 表面呈现目标写入 Direct3D 图面
若要写入 Direct3D 图面,请获取 IDXGISurface 并将其传递给 CreateDxgiSurfaceRenderTarget 方法,以创建 DXGI 表面呈现目标。 然后,可以使用 DXGI 表面呈现目标将二维内容绘制到 DXGI 图面。
DXGI 表面呈现目标是一种 ID2D1RenderTarget。 与其他 Direct2D 呈现目标一样,可以使用它创建资源并发出绘图命令。
DXGI 表面呈现目标和 DXGI 图面必须使用相同的 DXGI 格式。 如果在创建呈现目标时指定 DXGI_FORMAT_UNKOWN 格式,则它将自动使用图面的格式。
DXGI 表面呈现目标不执行 DXGI 表面同步。
创建 DXGI 图面
使用 Direct3D 10,可通过多种方式获取 DXGI 表面。 可以为设备创建 IDXGISwapChain ,然后使用交换链的 GetBuffer 方法获取 DXGI 图面。 或者,可以使用设备创建纹理,然后将该纹理用作 DXGI 表面。
无论如何创建 DXGI 图面,该图面都必须使用 DXGI 表面呈现目标支持的 DXGI 格式之一。 有关列表,请参阅 支持的像素格式和 Alpha 模式。
此外,与 DXGI 图面关联的 ID3D10Device1 必须支持 BGRA DXGI 格式,才能使 Surface 与 Direct2D 配合使用。 若要确保此支持,请在调用 D3D10CreateDevice1 方法创建设备时使用 D3D10_CREATE_DEVICE_BGRA_SUPPORT 标志。
以下代码定义创建 ID3D10Device1 的方法。 它选择可用的最佳功能级别,并在硬件渲染不可用时回退到 Windows 高级光栅化平台 (WARP) 。
HRESULT DXGISampleApp::CreateD3DDevice(
IDXGIAdapter *pAdapter,
D3D10_DRIVER_TYPE driverType,
UINT flags,
ID3D10Device1 **ppDevice
)
{
HRESULT hr = S_OK;
static const D3D10_FEATURE_LEVEL1 levelAttempts[] =
{
D3D10_FEATURE_LEVEL_10_0,
D3D10_FEATURE_LEVEL_9_3,
D3D10_FEATURE_LEVEL_9_2,
D3D10_FEATURE_LEVEL_9_1,
};
for (UINT level = 0; level < ARRAYSIZE(levelAttempts); level++)
{
ID3D10Device1 *pDevice = NULL;
hr = D3D10CreateDevice1(
pAdapter,
driverType,
NULL,
flags,
levelAttempts[level],
D3D10_1_SDK_VERSION,
&pDevice
);
if (SUCCEEDED(hr))
{
// transfer reference
*ppDevice = pDevice;
pDevice = NULL;
break;
}
}
return hr;
}
下一个代码示例使用上一示例中所示的 CreateD3DDevice 方法创建一个 Direct3D 设备,该设备可以创建 DXGI 图面以用于 Direct2D。
// Create device
hr = CreateD3DDevice(
NULL,
D3D10_DRIVER_TYPE_HARDWARE,
nDeviceFlags,
&pDevice
);
if (FAILED(hr))
{
hr = CreateD3DDevice(
NULL,
D3D10_DRIVER_TYPE_WARP,
nDeviceFlags,
&pDevice
);
}
将 Direct2D 内容写入交换链缓冲区
将 Direct2D 内容添加到 Direct3D 场景的最简单方法是使用 IDXGISwapChain 的 GetBuffer 方法获取 DXGI 图面,然后使用 Surface 和 CreateDxgiSurfaceRenderTarget 方法创建用于绘制二维内容的 ID2D1RenderTarget。
此方法不会在三个维度中呈现内容:它不会具有透视或深度。 但是,它对于几个常见任务很有用:
- 为三维场景创建二维背景。
- 在三维场景前创建二维接口。
- 在呈现 Direct2D 内容时使用 Direct3D 多重采样。
下一部分介绍如何为三维场景创建二维背景。
示例:绘制二维背景
以下步骤介绍如何创建 DXGI 表面呈现目标并使用它绘制渐变背景。
使用 CreateSwapChain 方法为 ID3D10Device1 创建交换链, (m_pDevice 变量) 。 交换链使用 DXGI_FORMAT_B8G8R8A8_UNORM DXGI 格式,这是 Direct2D 支持的 DXGI 格式之一。
if (SUCCEEDED(hr)) { hr = pDevice->QueryInterface(&m_pDevice); } if (SUCCEEDED(hr)) { hr = pDevice->QueryInterface(&pDXGIDevice); } if (SUCCEEDED(hr)) { hr = pDXGIDevice->GetAdapter(&pAdapter); } if (SUCCEEDED(hr)) { hr = pAdapter->GetParent(IID_PPV_ARGS(&pDXGIFactory)); } if (SUCCEEDED(hr)) { DXGI_SWAP_CHAIN_DESC swapDesc; ::ZeroMemory(&swapDesc, sizeof(swapDesc)); swapDesc.BufferDesc.Width = nWidth; swapDesc.BufferDesc.Height = nHeight; swapDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; swapDesc.BufferDesc.RefreshRate.Numerator = 60; swapDesc.BufferDesc.RefreshRate.Denominator = 1; swapDesc.SampleDesc.Count = 1; swapDesc.SampleDesc.Quality = 0; swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapDesc.BufferCount = 1; swapDesc.OutputWindow = m_hwnd; swapDesc.Windowed = TRUE; hr = pDXGIFactory->CreateSwapChain(m_pDevice, &swapDesc, &m_pSwapChain); }
使用交换链的 GetBuffer 方法获取 DXGI 图面。
// Get a surface in the swap chain hr = m_pSwapChain->GetBuffer( 0, IID_PPV_ARGS(&pBackBuffer) );
使用 DXGI 图面创建 DXGI 呈现目标。
// Initialize *hwnd* with the handle of the window displaying the rendered content. HWND hwnd; // Create the DXGI Surface Render Target. float dpi = GetDpiForWindow(hwnd); D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), dpiX, dpiY); // Create a Direct2D render target that can draw into the surface in the swap chain hr = m_pD2DFactory->CreateDxgiSurfaceRenderTarget( pBackBuffer, &props, &m_pBackBufferRT);
使用呈现目标绘制渐变背景。
// Swap chain will tell us how big the back buffer is DXGI_SWAP_CHAIN_DESC swapDesc; hr = m_pSwapChain->GetDesc(&swapDesc); if (SUCCEEDED(hr)) { // Draw a gradient background. if (m_pBackBufferRT) { D2D1_SIZE_F targetSize = m_pBackBufferRT->GetSize(); m_pBackBufferRT->BeginDraw(); m_pBackBufferGradientBrush->SetTransform( D2D1::Matrix3x2F::Scale(targetSize) ); D2D1_RECT_F rect = D2D1::RectF( 0.0f, 0.0f, targetSize.width, targetSize.height ); m_pBackBufferRT->FillRectangle(&rect, m_pBackBufferGradientBrush); hr = m_pBackBufferRT->EndDraw(); } ...
此示例中省略了代码。
使用 Direct2D 内容作为纹理
将 Direct2D 内容与 Direct3D 配合使用的另一种方法是使用 Direct2D 生成二维纹理,然后将该纹理应用于三维模型。 为此,可以创建 ID3D10Texture2D,从纹理获取 DXGI 图面,然后使用该表面创建 DXGI 表面呈现目标。 ID3D10Texture2D 图面必须使用D3D10_BIND_RENDER_TARGET绑定标志,并使用 DXGI 表面呈现目标支持的 DXGI 格式。 有关支持的 DXGI 格式的列表,请参阅 支持的像素格式和 Alpha 模式。
示例:使用 Direct2D 内容作为纹理
以下示例演示如何创建一个 DXGI 表面呈现目标,该目标呈现为 由 ID3D10Texture2D) 表示的二维纹理 (。
首先,使用 Direct3D 设备创建二维纹理。 纹理使用 D3D10_BIND_RENDER_TARGET 和 D3D10_BIND_SHADER_RESOURCE 绑定标志,并使用 DXGI_FORMAT_B8G8R8A8_UNORM DXGI 格式,这是 Direct2D 支持的 DXGI 格式之一。
// Allocate an offscreen D3D surface for D2D to render our 2D content into D3D10_TEXTURE2D_DESC texDesc; texDesc.ArraySize = 1; texDesc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE; texDesc.CPUAccessFlags = 0; texDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; texDesc.Height = 512; texDesc.Width = 512; texDesc.MipLevels = 1; texDesc.MiscFlags = 0; texDesc.SampleDesc.Count = 1; texDesc.SampleDesc.Quality = 0; texDesc.Usage = D3D10_USAGE_DEFAULT; hr = m_pDevice->CreateTexture2D(&texDesc, NULL, &m_pOffscreenTexture);
使用纹理获取 DXGI 表面。
IDXGISurface *pDxgiSurface = NULL; hr = m_pOffscreenTexture->QueryInterface(&pDxgiSurface);
将 Surface 与 CreateDxgiSurfaceRenderTarget 方法结合使用,以获取 Direct2D 呈现目标。
if (SUCCEEDED(hr)) { // Create a D2D render target that can draw into our offscreen D3D // surface. Given that we use a constant size for the texture, we // fix the DPI at 96. D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), 96, 96); hr = m_pD2DFactory->CreateDxgiSurfaceRenderTarget( pDxgiSurface, &props, &m_pRenderTarget); }
获取 Direct2D 呈现目标并将其与 Direct3D 纹理关联后,可以使用呈现目标将 Direct2D 内容绘制到该纹理,并且可以将该纹理应用于 Direct3D 基元。
此示例中省略了代码。
调整 DXGI 表面呈现目标的大小
DXGI 表面呈现目标不支持 ID2D1RenderTarget::Resize 方法。 若要调整 DXGI 图面呈现目标的大小,应用程序必须释放并重新创建它。
此操作可能会导致性能问题。 呈现器目标可能是最后一个活动 Direct2D 资源,该资源保留对与呈现目标的 DXGI 图面关联的 ID3D10Device1 的引用。 如果应用程序释放呈现器目标,并且 ID3D10Device1 引用被销毁,则必须重新创建一个新引用。
可以通过在重新创建呈现目标时保留至少一个由呈现器目标创建的 Direct2D 资源来避免这种可能代价高昂的操作。 下面是适用于此方法的一些 Direct2D 资源:
- ID2D1Bitmap (可由 ID2D1BitmapBrush 间接持有)
- ID2D1Layer
- ID2D1Mesh
若要适应此方法,应测试 resize 方法以查看 Direct3D 设备是否可用。 如果可用,请释放并重新创建 DXGI 表面呈现目标,但保留以前创建的所有资源并重复使用它们。 这样做是因为,如 资源概述中所述,当两个呈现目标都与同一 Direct3D 设备关联时,由两个呈现器目标创建的资源是兼容的。