Visão geral de interoperabilidade entre Direct2D e Direct3D
Os elementos gráficos 2D e 3D acelerados por hardware estão se tornando cada vez mais parte de aplicativos que não são de jogos, e a maioria dos aplicativos de jogos usa elementos gráficos 2D na forma de menus e HUDs (Heads-Up Displays). Há muito valor que pode ser adicionado, permitindo que a renderização 2D tradicional seja misturada com a renderização direct3D de maneira eficiente.
Este tópico descreve como integrar elementos gráficos 2D e 3D usando Direct2D e Direct3D.
Pré-requisitos
Esta visão geral pressupõe que você esteja familiarizado com operações básicas de desenho Direct2D. Para obter um tutorial, consulte Criar um aplicativo de Direct2D simples. Ele também pressupõe que você possa programar usando o Direct3D 10.1.
Versões do Direct3D com suporte
Com o DirectX 11.0, Direct2D dá suporte à interoperabilidade somente com dispositivos Direct3D 10.1. Com o DirectX 11.1 ou posterior, Direct2D também dá suporte à interoperabilidade com Direct3D 11.
Interoperabilidade por meio de DXGI
A partir do Direct3D 10, o runtime do Direct3D usa DXGI para gerenciamento de recursos. A camada de runtime DXGI fornece compartilhamento entre processos de superfícies de memória de vídeo e serve como base para outras plataformas de runtime baseadas em memória de vídeo. Direct2D usa DXGI para interoperar com Direct3D.
Há duas maneiras principais de usar Direct2D e Direct3D juntos:
- Você pode gravar Direct2D conteúdo em uma superfície direct3D obtendo um IDXGISurface e usando-o com o CreateDxgiSurfaceRenderTarget para criar um ID2D1RenderTarget. Em seguida, você pode usar o destino de renderização para adicionar uma interface bidimensional ou tela de fundo a elementos gráficos tridimensionais ou usar um desenho Direct2D como uma textura para um objeto tridimensional.
- Usando CreateSharedBitmap para criar um ID2D1Bitmap de um IDXGISurface, você pode escrever uma cena direct3D em um bitmap e renderizá-la com Direct2D.
Gravando em uma superfície Direct3D com um destino de renderização de superfície DXGI
Para gravar em uma superfície Direct3D, você obtém um IDXGISurface e passa-o para o método CreateDxgiSurfaceRenderTarget para criar um destino de renderização de superfície DXGI. Em seguida, você pode usar o destino de renderização da superfície DXGI para desenhar conteúdo 2D para a superfície DXGI.
Um destino de renderização de superfície DXGI é um tipo de ID2D1RenderTarget. Como outros Direct2D destinos de renderização, você pode usá-lo para criar recursos e emitir comandos de desenho.
O destino de renderização da superfície DXGI e a superfície DXGI devem usar o mesmo formato DXGI. Se você especificar o formato DXGI_FORMAT_UNKOWN ao criar o destino de renderização, ele usará automaticamente o formato da superfície.
O destino de renderização da superfície DXGI não executa a sincronização de superfície DXGI.
Criando uma superfície DXGI
Com o Direct3D 10, há várias maneiras de obter uma superfície DXGI. Você pode criar um IDXGISwapChain para um dispositivo e, em seguida, usar o método GetBuffer da cadeia de troca para obter uma superfície DXGI. Ou você pode usar um dispositivo para criar uma textura e, em seguida, usar essa textura como uma superfície DXGI.
Independentemente de como você cria a superfície DXGI, a superfície deve usar um dos formatos DXGI compatíveis com destinos de renderização de superfície DXGI. Para obter uma lista, consulte Formatos de pixel com suporte e modos alfa.
Além disso, o ID3D10Device1 associado à superfície DXGI deve dar suporte a formatos DXGI BGRA para que a superfície funcione com Direct2D. Para garantir esse suporte, use o sinalizador D3D10_CREATE_DEVICE_BGRA_SUPPORT ao chamar o método D3D10CreateDevice1 para criar o dispositivo.
O código a seguir define um método que cria um ID3D10Device1. Ele seleciona o melhor nível de recurso disponível e volta para WARP (Windows Advanced Rasterization Platform) quando a renderização de hardware não está disponível.
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;
}
O próximo exemplo de código usa o método CreateD3DDevice mostrado no exemplo anterior para criar um dispositivo Direct3D que pode criar superfícies DXGI para uso com Direct2D.
// Create device
hr = CreateD3DDevice(
NULL,
D3D10_DRIVER_TYPE_HARDWARE,
nDeviceFlags,
&pDevice
);
if (FAILED(hr))
{
hr = CreateD3DDevice(
NULL,
D3D10_DRIVER_TYPE_WARP,
nDeviceFlags,
&pDevice
);
}
Gravando Direct2D conteúdo em um buffer de cadeia de troca
A maneira mais simples de adicionar Direct2D conteúdo a uma cena direct3D é usar o método GetBuffer de um IDXGISwapChain para obter uma superfície DXGI e, em seguida, usar a superfície com o método CreateDxgiSurfaceRenderTarget para criar um ID2D1RenderTarget com o qual desenhar seu conteúdo 2D.
Essa abordagem não renderiza seu conteúdo em três dimensões; ele não terá perspectiva ou profundidade. No entanto, é útil para várias tarefas comuns:
- Criando uma tela de fundo 2D para uma cena 3D.
- Criando uma interface 2D na frente de uma cena 3D.
- Usar várias amostras do Direct3D ao renderizar Direct2D conteúdo.
A próxima seção mostra como criar uma tela de fundo 2D para uma cena 3D.
Exemplo: Desenhar uma tela de fundo 2D
As etapas a seguir descrevem como criar um destino de renderização de superfície DXGI e usá-lo para desenhar uma tela de fundo de gradiente.
Use o método CreateSwapChain para criar uma cadeia de troca para um ID3D10Device1 (a variável m_pDevice ). A cadeia de troca usa o formato DXGI DXGI_FORMAT_B8G8R8A8_UNORM, um dos formatos DXGI compatíveis com Direct2D.
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); }
Use o método GetBuffer da cadeia de troca para obter uma superfície DXGI.
// Get a surface in the swap chain hr = m_pSwapChain->GetBuffer( 0, IID_PPV_ARGS(&pBackBuffer) );
Use a superfície DXGI para criar um destino de renderização 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);
Use o destino de renderização para desenhar uma tela de fundo de gradiente.
// 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(); } ...
O código é omitido deste exemplo.
Usando Direct2D conteúdo como textura
Outra maneira de usar Direct2D conteúdo com Direct3D é usar Direct2D para gerar uma textura 2D e, em seguida, aplicar essa textura a um modelo 3D. Faça isso criando um ID3D10Texture2D, obtendo uma superfície DXGI da textura e, em seguida, usando a superfície para criar um destino de renderização de superfície DXGI. A superfície ID3D10Texture2D deve usar o sinalizador de associação D3D10_BIND_RENDER_TARGET e usar um formato DXGI compatível com destinos de renderização de superfície DXGI. Para obter uma lista de formatos DXGI com suporte, consulte Formatos de pixel com suporte e modos alfa.
Exemplo: usar Direct2D conteúdo como textura
Os exemplos a seguir mostram como criar um destino de renderização de superfície DXGI renderizado em uma textura 2D (representada por um ID3D10Texture2D).
Primeiro, use um dispositivo Direct3D para criar uma textura 2D. A textura usa os sinalizadores de associação D3D10_BIND_RENDER_TARGET e D3D10_BIND_SHADER_RESOURCE e usa o formato DXGI DXGI_FORMAT_B8G8R8A8_UNORM, um dos formatos DXGI compatíveis com Direct2D.
// 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);
Use a textura para obter uma superfície DXGI.
IDXGISurface *pDxgiSurface = NULL; hr = m_pOffscreenTexture->QueryInterface(&pDxgiSurface);
Use a superfície com o método CreateDxgiSurfaceRenderTarget para obter um destino de renderização 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); }
Agora que você obteve um Direct2D destino de renderização e o associou a uma textura Direct3D, você pode usar o destino de renderização para desenhar Direct2D conteúdo a essa textura e aplicar essa textura a primitivos Direct3D.
O código é omitido deste exemplo.
Redimensionar um destino de renderização de superfície DXGI
Os destinos de renderização da superfície DXGI não dão suporte ao método ID2D1RenderTarget::Resize . Para redimensionar um destino de renderização de superfície DXGI, o aplicativo deve liberá-lo e recriá-lo.
Essa operação pode potencialmente criar problemas de desempenho. O destino de renderização pode ser o último recurso ativo Direct2D que mantém uma referência à ID3D10Device1 associada à superfície DXGI do destino de renderização. Se o aplicativo liberar o destino de renderização e a referência ID3D10Device1 for destruída, um novo deverá ser recriado.
Você pode evitar essa operação potencialmente cara mantendo pelo menos uma Direct2D recurso que foi criado pelo destino de renderização enquanto recria esse destino de renderização. Veja a seguir alguns recursos Direct2D que funcionam para essa abordagem:
- ID2D1Bitmap (que pode ser mantido indiretamente por um ID2D1BitmapBrush)
- ID2D1Layer
- ID2D1Mesh
Para acomodar essa abordagem, seu método de redimensionamento deve testar para ver se o dispositivo Direct3D está disponível. Se estiver disponível, libere e recrie seus destinos de renderização de superfície DXGI, mas mantenha todos os recursos que eles criaram anteriormente e reutilize-os. Isso funciona porque, conforme descrito na Visão geral de recursos, os recursos criados por dois destinos de renderização são compatíveis quando ambos os destinos de renderização são associados ao mesmo dispositivo Direct3D.