Windows con C++

Disposición de ventanas en capas de alto rendimiento usando el motor de composición de Windows

Kenny Kerr

Kenny KerrLas ventanas por capas me han fascinado desde la primera vez que las vi en Windows XP. La capacidad de escapar a los límites rectangulares, o casi rectangulares, de una ventana tradicional de escritorio siempre me sedujo. Y entonces llegó Windows Vista. Esa versión tan denigrada de Windows dio un atisbo del comienzo de algo mucho más emocionante y mucho más flexible. Windows Vista comenzó algo que solo hemos comenzado a apreciar ahora que tenemos Windows 8, pero también marcó la lenta caída de las ventanas por capas.

Windows Vista introdujo un servicio llamado Administrador de ventanas de escritorio. El nombre era confuso y lo sigue siendo. Se puede imaginar como si fuera el motor de composición o el compositor de Windows. Este motor de composición cambió por completo la forma de representar las ventanas de aplicaciones en el escritorio. En lugar de permitir que cada ventana se represente directamente en la pantalla, o adaptador de pantalla, cada ventana se representa en una superficie fuera de la pantalla o búfer. El sistema asigna una de estas superficies por ventana de nivel superior, y todos los gráficos GDI, Direct3D y, por supuesto, Direct2D se representan en estas superficies. Estas superficies fuera de pantalla se llaman superficies de redireccionamiento porque los comandos de dibujo GDI e, incluso, las solicitudes de presentación de cadenas de intercambio de Direct3D se redireccionan o copian (dentro de la GPU) a la superficie de redireccionamiento.

En cierto punto, independiente de cualquier ventana determinada, el motor de composición decide que es momento de componer el escritorio dado el último lote de cambios. Esto implica componer juntas todas estas superficies de redireccionamiento, agregar las áreas no cliente (a menudo llamadas cromo de ventana), tal vez, agregar algunas sombras y otros efectos, y presentar el resultado final en el adaptador de pantalla.

Este proceso de composición tiene muchas ventajas increíbles que voy a explicar en los meses siguientes a medida que explore la composición de Windows con más detalle, pero también tiene una restricción posiblemente grave, ya que estas superficies de redireccionamiento son opacas. La mayoría de las aplicaciones se conforma con esto, lo que tiene mucho sentido desde la perspectiva del rendimiento, ya que la combinación alfa consume muchos recursos. Pero esto excluye a las ventanas por capas.

Si quiero una ventana por capas, tengo que restar rendimiento. Describo las limitaciones específicas de la arquitectura en mi columna “Layered Windows with Direct2D” (Ventanas por capas con Direct2D) (msdn.microsoft.com/magazine/ee819134). En resumen, la CPU procesa las ventanas por capas, principalmente para admitir la prueba de posicionamiento de los píxeles con combinación alfa. Es decir, la CPU necesita una copia de los píxeles que componen el área de superficie de la ventana por capas. O proceso en la CPU, lo que tiende a ser mucho más lento que el procesamiento por GPU, o proceso en la GPU, en cuyo caso, debo hacerme cargo del ancho de banda de bus adicional, ya que todo lo que proceso se tiene que copiar de la memoria de vídeo a la memoria del sistema. En la columna mencionada, también muestro cómo puedo aprovechar al máximo Direct2D para obtener el mayor rendimiento posible del sistema, porque solo Direct2D me permite elegir entre el procesamiento por CPU y por GPU. La clave está en que, aunque la ventana por capas reside necesariamente en la memoria del sistema, el motor de composición la copia de inmediato a la memoria de vídeo, de modo que la composición real de la ventana por capas aún estará acelerada por hardware.

Si bien no puedo ofrece ninguna esperanza de que las ventanas por capas tradicionales vuelvan a predominar, sí tengo buenas noticias. Las ventanas por capas ofrecen dos características específicas de interés. La primera es la composición alfa por píxel. Todo lo que procese en la ventana por capas tendrá una combinación alfa con el escritorio y con todo lo que esté detrás de la ventana en un momento dado. La segunda es la capacidad de User32 de realizar pruebas de posicionamiento de las ventanas por capas según los valores alfa de los píxeles, lo que permite que los mensajes del mouse pasen explícitamente si el píxel es transparente en un punto en particular. A partir de Windows 8 y 8.1, User32 no ha cambiado significativamente, pero lo que ha cambiado es la capacidad de admitir la combinación alfa por píxel exclusivamente en la GPU, sin el coste de transmitir la superficie de la ventana a la memoria del sistema. Es decir, ahora puedo producir el efecto de una ventana por capas sin que afecte al rendimiento, siempre que no necesite una prueba de posicionamiento por píxel. Toda la ventana tendrá una prueba de posicionamiento uniforme. Dejando a un lado la prueba de posicionamiento, es emocionante porque es algo que el sistema puede hacer, claramente, pero nunca antes las aplicaciones pudieron aprovechar esta potencia. Si te parece fascinante, sigue leyendo y te mostraré cómo se hace.

La clave para que suceda, implica adoptar el motor de composición de Windows. El motor de composición hizo su primera aparición en Windows Vista como Administrador de ventanas de escritorio con una API limitada y su famoso efecto traslúcido Aero. Luego vino Windows 8 con la API DirectComposition. Es una API más amplia para el mismo motor de composición. Con la publicación de Windows 8, Microsoft finalmente permitió que desarrolladores de otros fabricantes aprovecharan la potencia de este motor de composición que ha estado disponible por tanto tiempo. Y, claro está, tendrás que adoptar una API de gráficos compatible con Direct3D, como Direct2D. Pero primero tienes que ocuparte de esa superficie de redireccionamiento opaca.

Como mencioné anteriormente, el sistema asigna una superficie de redireccionamiento a cada ventana de nivel superior. A partir de Windows 8, se puede crear una ventana de nivel superior y solicitar que se cree sin una superficie de redireccionamiento. En sentido estricto, esto no tiene nada que ver con las ventanas por capas, así que no uses el estilo de ventana ampliado WS_EX_LAYERED. (En realidad, la compatibilidad con las ventanas por capas obtuvo una leve mejora en Windows 8, pero abordaré esto con más detalle en una próxima columna). En su lugar, tienes que usar el estilo de ventana ampliado WS_EX_NOREDIRECTIONBITMAP, que le indica al motor de composición que no asigne una superficie de redireccionamiento a la ventana. Voy a comenzar con una ventana de escritorio sencilla y tradicional. La figura 1 sirve de ejemplo de relleno de una estructura WNDCLASS, registro de la clase de ventana, creación de la ventana y bombeo de mensajes en la ventana. No hay nada nuevo en esto, pero estos aspectos básicos siguen siendo esenciales. La variable de la ventana no se usa, pero la necesitarás en un momento. Puedes copiar esto en un proyecto de Visual C++ dentro de Visual Studio o compilarlo desde el símbolo del sistema del modo siguiente:

cl /W4 /nologo Sample.cpp

Figura 1 Creación de una ventana tradicional

#ifndef UNICODE #define UNICODE #endif #include <windows.h> #pragma comment(lib, "user32.lib") int __stdcall wWinMain(HINSTANCE module, HINSTANCE, PWSTR, int) { WNDCLASS wc = {}; wc.hCursor       = LoadCursor(nullptr, IDC_ARROW); wc.hInstance     = module; wc.lpszClassName = L"window"; wc.style         = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = [] (HWND window, UINT message, WPARAM wparam, LPARAM lparam) -> LRESULT { if (WM_DESTROY == message) { PostQuitMessage(0); return 0; } return DefWindowProc(window, message, wparam, lparam); }; RegisterClass(&wc); HWND const window = CreateWindow(wc.lpszClassName, L"Sample", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, module, nullptr); MSG message; while (BOOL result = GetMessage(&message, 0, 0, 0)) { if (-1 != result) DispatchMessage(&message); } }

La figura 2 muestra cómo se vería en mi escritorio. Observa que no se ve nada raro aquí. Si bien el ejemplo no proporciona comandos de pintura y representación, el área cliente de la ventana es opaca y el motor de composición agrega el área no cliente, el borde y la barra de título. Aplicar el estilo de ventana ampliado WS_EX_NOREDIRECTIONBITMAP para deshacerse de la superficie de redireccionamiento opaca que representa esta área cliente consiste simplemente en intercambiar la función CreateWindow por la función CreateWindowEx con su parámetro principal que acepta estilos de ventana ampliados:

 

HWND const window = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP, wc.lpszClassName, L"Sample", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, module, nullptr);

A Traditional Window on the Desktop
Figura 2 Una ventana tradicional en el escritorio

Lo único que cambió fue la adición del argumento principal, el estilo de ventana ampliado WS_EX_NOREDIRECTIONBITMAP y, por supuesto, el uso de la función CreateWindowEx en lugar de la función CreateWindow más sencilla. Sin embargo, los resultados del escritorio son mucho más radicales. La figura 3 muestra cómo se vería en mi escritorio. Observa que el área cliente de la ventana ahora es totalmente transparente. Al mover la ventana, se observa este efecto. Incluso puedo reproducir un vídeo en el fondo y no se oscurece en absoluto. Por otro lado, la prueba de posicionamiento de toda la ventana se realiza de manera uniforme y el enfoque de la ventana no se pierde al hacer clic en el área cliente. Eso es porque el subsistema responsable de la prueba de posicionamiento y la entrada del mouse no sabe que el área cliente es transparente.

A Window Without a Redirection Surface
Figura 3 Una ventana sin superficie de redireccionamiento

Por supuesto, la siguiente pregunta es: ¿cómo se puede representar algo en la ventana si no hay una superficie de redireccionamiento que proporcionarle al motor de composición? La respuesta está en la API de DirectComposition y su integración profunda con la Infraestructura de gráficos DirectX (DXGI). Es la misma técnica que permite que la implementación XAML de Windows 8.1 proporcione una composición con un rendimiento increíblemente alto de contenido dentro de una aplicación XAML. El motor de representación Trident de Internet Explorer también utiliza DirectComposition de manera extensiva para el movimiento panorámico y zoom táctil, además de animaciones CSS3, transiciones y transformaciones.

Voy a usarlo para componer una cadena de intercambio que admita transparencia con valores alfa premultiplicados por píxel y combinarla con el resto del escritorio. Las aplicaciones DirectX tradicionales normalmente crean una cadena de intercambio con el método CreateSwapChainForHwnd de la fábrica de DXGI. Esta cadena de intercambio está respaldada por un par o una colección de búferes que efectivamente se intercambiarán durante la presentación, lo que permite que la aplicación represente el siguiente fotograma mientras se copia el fotograma anterior. La superficie de la cadena de intercambio que representa la aplicación es un búfer opaco fuera de pantalla. Cuando la aplicación presenta la cadena de intercambio, DirectX copia el contenido del búfer de reserva de la cadena de intercambio en la superficie de redireccionamiento de la ventana. Como mencioné anteriormente, el motor de composición finalmente compone todas las superficies de redireccionamiento juntas para producir el escritorio en su conjunto.

En este caso, la ventana no tiene una superficie de redireccionamiento, por lo que el método CreateSwapChainForHwnd de la fábrica de DXGI no se puede usar. Sin embargo, aún necesito una cadena de intercambio para admitir la representación de Direct3D y Direct2D. Para esto está el método CreateSwapChainForComposition de la fábrica de DXGI. Puedo usar este método para crear una cadena de intercambio sin ventanas, junto con sus búferes, pero presentar esta cadena de intercambio no copia los bits en la superficie de redireccionamiento (que no existe), sino que los pone directamente a disposición del motor de composición. Luego, el motor de composición puede tomar esta superficie y usarla directamente y en lugar de la superficie de redireccionamiento de la ventana. Dado que esta superficie no es opaca, sino que su formato de píxeles admite totalmente los valores alfa premultiplicados por píxel, el resultado es una combinación alfa de píxeles perfectos en el escritorio. También es increíblemente rápido porque no hay una copia innecesaria dentro de la GPU y, ciertamente, no hay copias a través del bus en la memoria del sistema.

Esa es la teoría. Ahora es momento de ponerlo en práctica. DirectX se trata de la esencia de COM, así que voy a usar la plantilla de la clase ComPtr de la Biblioteca de plantillas C++ de Windows en tiempo de ejecución para administrar los punteros de interfaz. También voy a tener que incluir y vincularme con las API DXGI, Direct3D, Direct2D y DirectComposition. El código siguiente muestra cómo se hace:

#include <wrl.h> using namespace Microsoft::WRL; #include <dxgi1_3.h> #include <d3d11_2.h> #include <d2d1_2.h> #include <d2d1_2helper.h> #include <dcomp.h> #pragma comment(lib, "dxgi") #pragma comment(lib, "d3d11") #pragma comment(lib, "d2d1") #pragma comment(lib, "dcomp")

Normalmente, lo incluyo en el encabezado precompilado. En ese caso, omitiría la directiva de uso y lo incluiría solo en el archivo de origen de mi aplicación.

Detesto los códigos de ejemplo donde el control de errores es abrumador y distrae de los aspectos específicos del tema en sí, por lo que también voy a hacer esto a un lado con una clase de excepción y una función HR para comprobar los errores. Puedes encontrar una implementación sencilla en la figura 4, pero está claro que puedes decidir tu propia directiva de control de errores.

Figura 4 Conversión de errores de HRESULT en excepciones

struct ComException { HRESULT result; ComException(HRESULT const value) : result(value) {} }; void HR(HRESULT const result) { if (S_OK != result) { throw ComException(result); } }

Ahora puedo comenzar a ensamblar la pila de procesamiento, y eso, naturalmente, comienza con un dispositivo Direct3D. Voy a pasar rápidamente por esto porque ya he descrito en detalle la infraestructura de DirectX en mi columna de marzo de 2013, “Introducing Direct2D 1.1” (Presentación de Direct2D 1.1) (msdn.microsoft.com/magazine/dn198239). Este es el puntero de interfaz de Direct3D 11:

ComPtr<ID3D11Device> direct3dDevice;

Ese es el puntero de interfaz para el dispositivo, y la función D3D11Create­Device se puede usar para crear el dispositivo:

HR(D3D11CreateDevice(nullptr,    // Adapter D3D_DRIVER_TYPE_HARDWARE, nullptr,    // Module D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, // Highest available feature level D3D11_SDK_VERSION, &direct3dDevice, nullptr,    // Actual feature level nullptr));  // Device context

Esto no tiene nada sorprendente. Estoy creando un dispositivo Direct3D respaldado por una GPU. El indicador D3D11_CREATE_DEVICE_BGRA_SUPPORT permite la interoperabilidad con Direct2D. DXGI mantiene unida la familia DirectX, ya que ofrece activos comunes de administración de recursos de GPU entre las distintas API de DirectX. Por lo tanto, debo consultar al dispositivo Direct3D por su interfaz DXGI:

ComPtr<IDXGIDevice> dxgiDevice; HR(direct3dDevice.As(&dxgiDevice));

El método ComPtr es un contenedor para el método QueryInterface. Una vez creado el dispositivo Direct3D, puedo crear la cadena de intercambio que se usará en la composición. Para ello, primero debo acceder a la fábrica de DXGI:

ComPtr<IDXGIFactory2> dxFactory; HR(CreateDXGIFactory2( DXGI_CREATE_FACTORY_DEBUG, __uuidof(dxFactory), reinterpret_cast<void **>(dxFactory.GetAddressOf())));

Aquí opto por información de depuración adicional, una ayuda inestimable durante el desarrollo. La parte más difícil de crear una cadena de intercambio es decidir cómo describir la cadena de intercambio deseada en la fábrica de DXGI. Esta información de depuración es inmensamente útil en el ajuste de la estructura necesaria de DXGI_SWAP_CHAIN_DESC1:

DXGI_SWAP_CHAIN_DESC1 description = {};

Esto inicializa la estructura y pone todo a cero. Luego puede comenzar a rellenar las propiedades que me interesan:

description.Format           = DXGI_FORMAT_B8G8R8A8_UNORM; description.BufferUsage      = DXGI_USAGE_RENDER_TARGET_OUTPUT; description.SwapEffect       = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; description.BufferCount      = 2; description.SampleDesc.Count = 1; description.AlphaMode        = DXGI_ALPHA_MODE_PREMULTIPLIED;

El formato particular, un formato de píxeles de 32 bits con 8 bits para cada canal de color junto con un componente alfa de 8 bits premultiplicado, no es la única opción, pero ofrece el mejor rendimiento y compatibilidad entre dispositivos y API.

El uso del búfer de la cadena de intercambio debe configurarse para permitir dirigir allí la salida de destino de representación. Esto es necesario para que el contexto del dispositivo Direct2D pueda crear un mapa de bits dirigido a la superficie de DXGI con comandos de dibujo. El propio mapa de bits de Direct2D es simplemente una abstracción respaldada por la cadena de intercambio.

Las cadenas de intercambio de composición solo admiten el efecto de intercambio Flip-Sequential. Así es como la cadena de intercambio se relaciona con el motor de composición en lugar de una superficie de redireccionamiento. En el modelo Flip, todos los búferes se comparten directamente con el motor de composición. Luego, el motor de composición puede componer el escritorio directamente desde el búfer de reserva de la cadena de intercambio sin copias adicionales. En general, este es el modelo más eficiente. También se requiere para la composición, por eso lo uso. El modelo Flip también requiere al menos dos búferes, pero no admite el muestreo múltiple, por lo que BufferCount se configura en dos y SampleDesc.Count se configura en uno. Este recuento es el número de muestras múltiples por píxel. Configurarlo en uno realmente deshabilita el muestreo múltiple.

Finalmente, el modo alfa es fundamental. Normalmente, se omitiría para cadenas de intercambio opacas, pero en este caso, quiero incluir el comportamiento de transparencia. Los valores de alfa premultiplicados normalmente proporcionan el mejor rendimiento, además de ser la única opción compatible con el modelo Flip.

El ingrediente final antes de poder crear la cadena de intercambio es determinar el tamaño deseado de los búferes. Normalmente, al llamar al método CreateSwapChainForHwnd, puedo omitir el tamaño, y la fábrica de DXGI consultará la ventana para conocer el tamaño del área cliente. En este caso, DXGI no tiene idea de lo que quiero hacer con la cadena de intercambio, así que tengo que decirle específicamente de qué tamaño tiene que ser. Con la ventana creada, es sencillamente cuestión de consultar al área cliente de la ventana y actualizar la descripción de la cadena de intercambio en consecuencia:

RECT rect = {}; GetClientRect(window, &rect); description.Width  = rect.right - rect.left; description.Height = rect.bottom - rect.top;

Ahora puedo crear la cadena de intercambio de composición con esta descripción y crear el puntero para el dispositivo Direct3D. Se pueden usar punteros de interfaz Direct3D o DXGI:

ComPtr<IDXGISwapChain1> swapChain; HR(dxFactory->CreateSwapChainForComposition(dxgiDevice.Get(), &description, nullptr, // Don’t restrict swapChain.GetAddressOf()));

Ahora que se creó la cadena de intercambio, puedo usar cualquier código de procesamiento de gráficos Direct3D o Direct2D para dibujar la aplicación, usando valores alfa según sea necesario para crear la transparencia deseada. No tiene nada de nuevo, así que consulta mi columna de marzo de 2013 otra vez para conocer los aspectos específicos del procesamiento de una cadena de intercambio con Direct2D. La figura 5 proporciona un ejemplo sencillo si me sigues. No olvides admitir el reconocimiento de PPP por monitor, como lo describo en mi columna de febrero de 2014, “Write High-DPI Apps for Windows 8.1” (Escribir aplicaciones de configuración elevada de PPP para Windows 8.1) (msdn.microsoft.com/magazine/dn574798).

Figura 5 Dibujar en la cadena de intercambio con Direct2D

// Create a single-threaded Direct2D factory with debugging information ComPtr<ID2D1Factory2> d2Factory; D2D1_FACTORY_OPTIONS const options = { D2D1_DEBUG_LEVEL_INFORMATION }; HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, options, d2Factory.GetAddressOf())); // Create the Direct2D device that links back to the Direct3D device ComPtr<ID2D1Device1> d2Device; HR(d2Factory->CreateDevice(dxgiDevice.Get(), d2Device.GetAddressOf())); // Create the Direct2D device context that is the actual render target // and exposes drawing commands ComPtr<ID2D1DeviceContext> dc; HR(d2Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, dc.GetAddressOf())); // Retrieve the swap chain's back buffer ComPtr<IDXGISurface2> surface; HR(swapChain->GetBuffer( 0, // index __uuidof(surface), reinterpret_cast<void **>(surface.GetAddressOf()))); // Create a Direct2D bitmap that points to the swap chain surface D2D1_BITMAP_PROPERTIES1 properties = {}; properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; properties.pixelFormat.format    = DXGI_FORMAT_B8G8R8A8_UNORM; properties.bitmapOptions         = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW; ComPtr<ID2D1Bitmap1> bitmap; HR(dc->CreateBitmapFromDxgiSurface(surface.Get(), properties, bitmap.GetAddressOf())); // Point the device context to the bitmap for rendering dc->SetTarget(bitmap.Get()); // Draw something dc->BeginDraw(); dc->Clear(); ComPtr<ID2D1SolidColorBrush> brush; D2D1_COLOR_F const brushColor = D2D1::ColorF(0.18f,  // red 0.55f,  // green 0.34f,  // blue 0.75f); // alpha HR(dc->CreateSolidColorBrush(brushColor, brush.GetAddressOf())); D2D1_POINT_2F const ellipseCenter = D2D1::Point2F(150.0f,  // x 150.0f); // y D2D1_ELLIPSE const ellipse = D2D1::Ellipse(ellipseCenter, 100.0f,  // x radius 100.0f); // y radius dc->FillEllipse(ellipse, brush.Get()); HR(dc->EndDraw()); // Make the swap chain available to the composition engine HR(swapChain->Present(1,   // sync 0)); // flags

Ahora ya puedo empezar a usar la API DirectComposition para unirlo todo. Si bien el motor de composición de Windows trata la representación y la composición del escritorio en su conjunto, la API DirectComposition te permite usar esta misma tecnología para componer los elementos visuales de las aplicaciones. Las aplicaciones componen diferentes elementos en conjunto, llamados elementos visuales, para general el aspecto de la ventana de aplicación. Estos elementos visuales se pueden animar y transformar en una variedad de formas para producir interfaces de usuario enriquecidas y fluidas. El propio proceso de composición también se realiza junto con la composición del escritorio en su conjunto, por lo que una mayor parte de la presentación de tu aplicación se quita de los subprocesos de la aplicación para una mejor respuesta.

DirectComposition se trata principalmente de componer juntos distintos mapas de bits. Al igual que con Direct2D, el concepto de un mapa de bits es más una abstracción para permitir que distintas pilas de representación cooperen y produzcan experiencias de usuario de uniformes y atractivas en la aplicación.

Al igual que Direct3D y Direct2D, DirectComposition es una API de DirectX respaldada y procesada por la GPU. Un dispositivo DirectComposition se crea apuntando de vuelta al dispositivo Direct3D, de manera muy similar a la forma en que un dispositivo Direct2D apunta de vuelta al dispositivo Direct3D subyacente. Uso el mismo dispositivo Direct3D que usé anteriormente para crear la cadena de intercambio y el mismo destino de representación Direct2D para crear un dispositivo DirectComposition:

ComPtr<IDCompositionDevice> dcompDevice; HR(DCompositionCreateDevice( dxgiDevice.Get(), __uuidof(dcompDevice), reinterpret_cast<void **>(dcompDevice.GetAddressOf())));

La función DCompositionCreateDevice espera la interfaz DXGI del dispositivo Direct3D y devuelve un puntero de interfaz IDCompositionDevice a un dispositivo DirectComposition creado recientemente. El dispositivo DirectComposition funciona como fábrica para otros objetos DirectComposition y proporciona el método Commit tan importante que confirma el lote de comandos de representación en el motor de composición para la futura composición y presentación.

A continuación, tengo que crear un objetivo de composición que asocie los elementos visuales que se compondrán en el destino, que es la ventana de la aplicación:

ComPtr<IDCompositionTarget> target; HR(dcompDevice->CreateTargetForHwnd(window, true, // Top most target.GetAddressOf()));

El primer parámetro del método CreateTargetForHwnd es el identificador de ventana devuelto por la función CreateWindowEx. El segundo parámetro indica cómo se combinarán los elementos visuales con cualquier otro elemento de la ventana. El resultado es un puntero de interfaz IDCompositionTarget cuyo único método se llama SetRoot. Me permite definir el elemento visual raíz como un posible árbol de elementos visuales que se compondrán juntos. No necesito un árbol completo de elementos visuales, pero necesito al menos un objeto visual y, para eso, puedo volver una vez más al dispositivo DirectComposition:

ComPtr<IDCompositionVisual> visual; HR(dcompDevice->CreateVisual(visual.GetAddressOf()));

El elemento visual contiene una referencia a un mapa de bits y proporciona un conjunto de propiedades que afectan a cómo se procesará y compondrá ese elemento visual en relación con otros elementos visuales en el árbol y el propio destino. Ya tengo el contenido que quiero que este elemento visual transmita al motor de composición. Es la cadena de intercambio que creé anteriormente:

HR(visual->SetContent(swapChain.Get()));

El elemento visual está listo y puedo configurarlo como la raíz del destino de composición:

HR(target->SetRoot(visual.Get()));

Finalmente, una vez que se ha establecido la forma del árbol de elementos visuales, simplemente puedo llamar al método Commit en el dispositivo DirectComposition para informar al motor de composición de que terminé:

HR(dcompDevice->Commit());

Para esta aplicación en particular, donde el árbol de elementos visuales no cambia, solo necesito llamar a Commit una vez al comienzo de la aplicación y nunca más. En un principio, supuse que se debía llamar al método Commit después de presentar la cadena de intercambio, pero no es así porque la presentación de la cadena de intercambio no se sincroniza con los cambios en el árbol de elementos visuales.

La figura 6 muestra cómo se ve la ventana de aplicación ahora que Direct2D ha procesado la cadena de intercambio y Direct-Composition ha proporcionado la cadena de intercambio parcialmente transparente al motor de composición.

Direct2D Drawing on a DirectComposition Surface
Figura 6 Dibujo de Direct2D en una superficie de DirectComposition

Es emocionante tener finalmente una solución para un antiguo problema: la capacidad de producir ventanas de alto rendimiento con una combinación alfa con el resto del escritorio. Me emocionan las posibilidades que trae consigo la API DirectComposition y lo que esto significa para el futuro del diseño de aplicaciones para la experiencia del usuario y el desarrollo de código nativo.

¿Quieres dibujar tu propio cromo de ventana? No hay problema, simplemente sustituye el estilo de ventana WS_OVERLAPPEDWINDOW por el estilo de ventana WS_POPUP al crear la ventana. ¡Que disfrute programando!

Kenny Kerr es programador informático radicado en Canadá, además de autor para Pluralsight y MVP de Microsoft. Tiene un blog en kennykerr.ca y puede seguirlo en Twitter en twitter.com/kennykerr.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Leonardo Blanco (Microsoft)