Introducción a la interoperabilidad de Direct2D y Direct3D
Los gráficos 2D y 3D acelerados por hardware se están convirtiendo cada vez más en parte de las aplicaciones que no son de juego, y la mayoría de las aplicaciones de juegos usan gráficos 2D en forma de menús y Heads-Up pantallas (HUD). Hay un montón de valor que se puede agregar al permitir que la representación tradicional 2D se mezcle con la representación de Direct3D de forma eficaz.
En este tema se describe cómo integrar gráficos 2D y 3D mediante Direct2D y Direct3D.
Requisitos previos
En esta introducción se da por supuesto que está familiarizado con las operaciones básicas de dibujo de Direct2D. Para ver un tutorial, consulta Creación de una aplicación direct2D sencilla. También se supone que puedes programar mediante Direct3D 10.1.
Versiones de Direct3D admitidas
Con DirectX 11.0, Direct2D solo admite la interoperabilidad con dispositivos Direct3D 10.1. Con DirectX 11.1 o posterior, Direct2D también admite la interoperabilidad con Direct3D 11.
Interoperabilidad mediante DXGI
A partir de Direct3D 10, el entorno de ejecución de Direct3D usa DXGI para la administración de recursos. La capa de tiempo de ejecución dxGI proporciona un uso compartido entre procesos de superficies de memoria de vídeo y sirve como base para otras plataformas en tiempo de ejecución basadas en memoria de vídeo. Direct2D usa DXGI para interoperar con Direct3D.
Hay dos formas principales de usar Direct2D y Direct3D juntos:
- Puedes escribir contenido de Direct2D en una superficie de Direct3D obteniendo un IDXGISurface y usándolo con CreateDxgiSurfaceRenderTarget para crear un ID2D1RenderTarget. A continuación, puedes usar el destino de representación para agregar una interfaz bidimensional o un fondo a gráficos tridimensionales, o usar un dibujo direct2D como textura para un objeto tridimensional.
- Con CreateSharedBitmap para crear un id2D1Bitmap a partir de un IDXGISurface, puedes escribir una escena de Direct3D en un mapa de bits y representarla con Direct2D.
Escribir en una superficie de Direct3D con un destino de representación de superficie DXGI
Para escribir en una superficie de Direct3D, obtiene un IDXGISurface y lo pasa al método CreateDxgiSurfaceRenderTarget para crear un destino de representación de superficie DXGI. A continuación, puedes usar el destino de representación de superficie DXGI para dibujar contenido 2D en la superficie DXGI.
Un destino de representación de superficie DXGI es un tipo de ID2D1RenderTarget. Al igual que otros destinos de representación de Direct2D, puedes usarlo para crear recursos y emitir comandos de dibujo.
El destino de representación de la superficie DXGI y la superficie DXGI deben usar el mismo formato DXGI. Si especifica el formato de DXGI_FORMAT_UNKOWN al crear el destino de representación, usará automáticamente el formato de la superficie.
El destino de representación de superficie DXGI no realiza la sincronización de superficie DXGI.
Creación de una superficie DXGI
Con Direct3D 10, hay varias maneras de obtener una superficie DXGI. Puedes crear un IDXGISwapChain para un dispositivo y, a continuación, usar el método GetBuffer de la cadena de intercambio para obtener una superficie DXGI. O bien, puedes usar un dispositivo para crear una textura y, a continuación, usar esa textura como una superficie DXGI.
Independientemente de cómo crees la superficie DXGI, la superficie debe usar uno de los formatos DXGI admitidos por los destinos de representación de superficie DXGI. Para obtener una lista, consulte Formatos de píxeles admitidos y Modos alfa.
Además, el ID3D10Device1 asociado a la superficie DXGI debe admitir formatos DXGI BGRA para que la superficie funcione con Direct2D. Para garantizar esta compatibilidad, use la marca D3D10_CREATE_DEVICE_BGRA_SUPPORT al llamar al método D3D10CreateDevice1 para crear el dispositivo.
El código siguiente define un método que crea un id3D10Device1. Selecciona el mejor nivel de característica disponible y recurre a la Plataforma de rasterización avanzada de Windows (WARP) cuando la representación de hardware no está disponible.
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;
}
En el ejemplo de código siguiente se usa el método CreateD3DDevice que se muestra en el ejemplo anterior para crear un dispositivo Direct3D que pueda crear superficies DXGI para su uso con Direct2D.
// Create device
hr = CreateD3DDevice(
NULL,
D3D10_DRIVER_TYPE_HARDWARE,
nDeviceFlags,
&pDevice
);
if (FAILED(hr))
{
hr = CreateD3DDevice(
NULL,
D3D10_DRIVER_TYPE_WARP,
nDeviceFlags,
&pDevice
);
}
Escribir contenido de Direct2D en un búfer de cadena de intercambio
La manera más sencilla de agregar contenido de Direct2D a una escena de Direct3D es usar el método GetBuffer de un IDXGISwapChain para obtener una superficie DXGI y, a continuación, usar la superficie con el método CreateDxgiSurfaceRenderTarget para crear un ID2D1RenderTarget con el que dibujar el contenido 2DD.
Este enfoque no representa el contenido en tres dimensiones; no tendrá perspectiva ni profundidad. Sin embargo, resulta útil para varias tareas comunes:
- Crear un fondo 2D para una escena 3D.
- Crear una interfaz 2D delante de una escena 3D.
- Usar el muestreo múltiple de Direct3D al representar contenido de Direct2D.
En la sección siguiente se muestra cómo crear un fondo 2D para una escena 3D.
Ejemplo: Dibujar un fondo 2D
En los pasos siguientes se describe cómo crear un destino de representación de superficie DXGI y usarlo para dibujar un fondo degradado.
Use el método CreateSwapChain para crear una cadena de intercambio para un id3D10Device1 (la variable m_pDevice ). La cadena de intercambio usa el formato DXGI DXGI_FORMAT_B8G8R8A8_UNORM , uno de los formatos DXGI admitidos por 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); }
Usa el método GetBuffer de la cadena de intercambio para obtener una superficie DXGI.
// Get a surface in the swap chain hr = m_pSwapChain->GetBuffer( 0, IID_PPV_ARGS(&pBackBuffer) );
Usa la superficie DXGI para crear un destino de representación 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 el destino de representación para dibujar un fondo degradado.
// 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(); } ...
El código se omite de este ejemplo.
Uso del contenido de Direct2D como textura
Otra manera de usar el contenido de Direct2D con Direct3D es usar Direct2D para generar una textura 2D y, a continuación, aplicar esa textura a un modelo 3D. Para ello, cree un ID3D10Texture2D, obtenga una superficie DXGI de la textura y, a continuación, use la superficie para crear un destino de representación de superficie DXGI. La superficie ID3D10Texture2D debe usar la marca de enlace D3D10_BIND_RENDER_TARGET y usar un formato DXGI compatible con los destinos de representación de superficie DXGI. Para obtener una lista de los formatos DXGI admitidos, consulta Formatos de píxeles admitidos y Modos alfa.
Ejemplo: Usar el contenido de Direct2D como textura
En los ejemplos siguientes se muestra cómo crear un destino de representación de superficie DXGI que se representa en una textura 2D (representada por un ID3D10Texture2D).
En primer lugar, usa un dispositivo Direct3D para crear una textura 2D. La textura usa las marcas de enlace de D3D10_BIND_RENDER_TARGET y D3D10_BIND_SHADER_RESOURCE , y usa el formato DXGI DXGI_FORMAT_B8G8R8A8_UNORM , uno de los formatos DXGI admitidos por 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);
Usa la textura para obtener una superficie DXGI.
IDXGISurface *pDxgiSurface = NULL; hr = m_pOffscreenTexture->QueryInterface(&pDxgiSurface);
Usa la superficie con el método CreateDxgiSurfaceRenderTarget para obtener un destino de representación de 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); }
Ahora que has obtenido un destino de representación de Direct2D y lo has asociado a una textura de Direct3D, puedes usar el destino de representación para dibujar contenido de Direct2D en esa textura y puedes aplicar esa textura a primitivos de Direct3D.
El código se omite de este ejemplo.
Cambio del tamaño de un destino de representación de superficie DXGI
Los destinos de representación de superficie DXGI no admiten el método ID2D1RenderTarget::Resize . Para cambiar el tamaño de un destino de representación de superficie DXGI, la aplicación debe liberarla y volver a crearla.
Esta operación puede crear problemas de rendimiento. El destino de representación podría ser el último recurso activo de Direct2D que mantiene una referencia a id3D10Device1 asociado a la superficie DXGI del destino de representación. Si la aplicación libera el destino de representación y se destruye la referencia ID3D10Device1 , se debe volver a crear una nueva.
Puedes evitar esta operación potencialmente costosa manteniendo al menos un recurso direct2D creado por el destino de representación mientras vuelves a crear ese destino de representación. Estos son algunos recursos de Direct2D que funcionan para este enfoque:
- ID2D1Bitmap (que un ID2D1BitmapBrush puede mantener indirectamente)
- ID2D1Layer
- ID2D1Mesh
Para adaptarse a este enfoque, el método de cambio de tamaño debe probar para ver si el dispositivo Direct3D está disponible. Si está disponible, libere y vuelva a crear los destinos de representación de la superficie DXGI, pero mantenga todos los recursos que crearon anteriormente y reutilícelas. Esto funciona porque, como se describe en Información general de recursos, los recursos creados por dos destinos de representación son compatibles cuando ambos destinos de representación están asociados con el mismo dispositivo Direct3D.