Uso de Direct2D para la representación de Server-Side
Direct2D es adecuado para aplicaciones gráficas que requieren representación del lado servidor en Windows Server. En esta introducción se describen los conceptos básicos del uso de Direct2D para la representación del lado servidor. Contiene las secciones siguientes:
- Requisitos para la representación de Server-Side
- Opciones para las API disponibles
- Uso de Direct2D para la representación de Server-Side
- Conclusión
- Temas relacionados
Requisitos para la representación de Server-Side
A continuación se muestra un escenario típico para un servidor de gráficos: los gráficos y gráficos se representan en un servidor y se entregan como mapas de bits en respuesta a las solicitudes web. El servidor puede estar equipado con una tarjeta gráfica de gama baja o ninguna tarjeta gráfica.
Este escenario revela tres requisitos de aplicación. En primer lugar, la aplicación debe controlar varias solicitudes simultáneas de forma eficaz, especialmente en servidores de varios núcleos. En segundo lugar, la aplicación debe usar la representación de software al ejecutarse en servidores con una tarjeta gráfica de gama baja o sin tarjeta gráfica. Por último, la aplicación debe ejecutarse como un servicio en la sesión 0 para que no requiera que un usuario inicie sesión. Para obtener más información sobre la sesión 0, vea Impacto del aislamiento de sesión 0 en servicios y controladores en Windows.
Opciones para las API disponibles
Hay tres opciones para la representación del lado servidor: GDI, GDI+ y Direct2D. Al igual que GDI y GDI+, Direct2D es una API de representación 2D nativa que proporciona a las aplicaciones más control sobre el uso de dispositivos gráficos. Además, Direct2D admite de forma única un único subproceso y una fábrica multiproceso. En las secciones siguientes se compara cada API en términos de calidades de dibujo y representación del lado servidor multiproceso.
GDI
A diferencia de Direct2D y GDI+, GDI no admite características de dibujo de alta calidad. Por ejemplo, GDI no admite suavizado para crear líneas suaves y solo tiene compatibilidad limitada para la transparencia. En función de los resultados de las pruebas de rendimiento de gráficos en Windows 7 y Windows Server 2008 R2, Direct2D se escala de forma más eficaz que GDI, a pesar del rediseño de bloqueos en GDI. Para obtener más información sobre estos resultados de prueba, consulta Ingeniería del rendimiento de gráficos de Windows 7.
Además, las aplicaciones que usan GDI están limitadas a 10240 identificadores GDI por proceso y 65536 identificadores GDI por sesión. El motivo es que Windows usa internamente un WORD de 16 bits para almacenar el índice de identificadores para cada sesión.
GDI+
Aunque GDI+ admite suavizado de contornos y combinación alfa para dibujo de alta calidad, el principal problema con GDI+ para escenarios de servidor es que no admite la ejecución en la sesión 0. Dado que la sesión 0 solo admite funciones no interactivas, las funciones que interactúan directa o indirectamente con los dispositivos de visualización recibirán errores. Algunos ejemplos específicos de funciones incluyen no solo aquellos que tratan con dispositivos de pantalla, sino también aquellos que tratan indirectamente con controladores de dispositivos.
De forma similar a GDI, GDI+ está limitado por su mecanismo de bloqueo. Los mecanismos de bloqueo en GDI+ son los mismos en Windows 7 y Windows Server 2008 R2 que en versiones anteriores.
Direct2D
Direct2D es una API de gráficos en modo inmediato y acelerada por hardware que proporciona un alto rendimiento y una representación de alta calidad. Ofrece un generador multiproceso y una fábrica multiproceso y el escalado lineal de la representación de software general.
Para ello, Direct2D define una interfaz de fábrica raíz. Como regla, un objeto creado en un generador solo se puede usar con otros objetos creados a partir de la misma fábrica. El autor de la llamada puede solicitar un generador multiproceso o multiproceso cuando se crea. Si se solicita un generador de un solo subproceso, no se realiza ningún bloqueo de subprocesos. Si el autor de la llamada solicita un generador multiproceso, se adquiere un bloqueo de subproceso de toda la fábrica cada vez que se realiza una llamada en Direct2D.
Además, el bloqueo de subprocesos en Direct2D es más granular que en GDI y GDI+, de modo que el aumento del número de subprocesos tenga un impacto mínimo en el rendimiento.
Uso de Direct2D para la representación de Server-Side
En las secciones siguientes se describe cómo usar la representación de software, cómo usar de forma óptima un generador multiproceso y multiproceso, y cómo dibujar y guardar un dibujo complejo en un archivo.
Representación de software
Las aplicaciones del lado servidor usan la representación de software mediante la creación de un destino de representación de IWICBitmap , con el tipo de destino de representación establecido en D2D1_RENDER_TARGET_TYPE_SOFTWARE o D2D1_RENDER_TARGET_TYPE_DEFAULT. Para obtener más información sobre los destinos de representación de IWICBitmap , vea el método ID2D1Factory::CreateWicBitmapRenderTarget ; para obtener más información sobre los tipos de destino de representación, vea D2D1_RENDER_TARGET_TYPE.
Subprocesamiento múltiple
Saber cómo crear y compartir factorías y representar destinos entre subprocesos puede afectar significativamente al rendimiento de una aplicación. Las tres figuras siguientes muestran tres enfoques variados. El enfoque óptimo se muestra en la figura 3.
En la figura 1, los subprocesos diferentes comparten el mismo generador y el mismo destino de representación. Este enfoque puede provocar resultados impredecibles en los casos en los que varios subprocesos cambian simultáneamente el estado del destino de representación compartido, como establecer simultáneamente la matriz de transformación. Dado que el bloqueo interno en Direct2D no sincroniza un recurso compartido como destinos de representación, este enfoque puede hacer que la llamada a BeginDraw produzca un error en el subproceso 1, ya que en el subproceso 2, la llamada a BeginDraw ya usa el destino de representación compartido.
Para evitar los resultados impredecibles encontrados en la figura 1, la figura 2 muestra un generador multiproceso con cada subproceso que tiene su propio destino de representación. Este enfoque funciona, pero funciona eficazmente como una aplicación de un solo subproceso. El motivo es que el bloqueo en todo el generador se aplica solo al nivel de operación de dibujo y todas las llamadas de dibujo en la misma fábrica, por lo tanto, se serializan. Como resultado, el subproceso 1 se bloquea al intentar escribir una llamada de dibujo, mientras que el subproceso 2 está en medio de ejecutar otra llamada de dibujo.
En la figura 3 se muestra el enfoque óptimo, donde se usa un generador de subprocesos único y un destino de representación de un solo subproceso. Dado que no se realiza ningún bloqueo al usar un generador de un solo subproceso, las operaciones de dibujo de cada subproceso se pueden ejecutar simultáneamente para lograr un rendimiento óptimo.
Generación de un archivo de mapa de bits
Para generar un archivo de mapa de bits mediante la representación de software, use un destino de representación de IWICBitmap . Use un IWICStream para escribir el mapa de bits en un archivo. Use IWICBitmapFrameEncode para codificar el mapa de bits en un formato de imagen especificado. En el ejemplo de código siguiente se muestra cómo dibujar y guardar la siguiente imagen en un archivo.
En primer lugar, este ejemplo de código crea un objeto IWICBitmap y un destino de representación de IWICBitmap . A continuación, representa un dibujo con texto, una geometría de trazado que representa un vidrio de hora y un vidrio de hora transformado en un mapa de bits WIC. A continuación, usa IWICStream::InitializeFromFilename para guardar el mapa de bits en un archivo. Si la aplicación necesita guardar el mapa de bits en memoria, use IWICStream::InitializeFromMemory en su lugar. Por último, usa IWICBitmapFrameEncode para codificar el mapa de bits.
// Create an IWICBitmap and RT
static const UINT sc_bitmapWidth = 640;
static const UINT sc_bitmapHeight = 480;
if (SUCCEEDED(hr))
{
hr = pWICFactory->CreateBitmap(
sc_bitmapWidth,
sc_bitmapHeight,
GUID_WICPixelFormat32bppBGR,
WICBitmapCacheOnLoad,
&pWICBitmap
);
}
// Set the render target type to D2D1_RENDER_TARGET_TYPE_DEFAULT to use software rendering.
if (SUCCEEDED(hr))
{
hr = pD2DFactory->CreateWicBitmapRenderTarget(
pWICBitmap,
D2D1::RenderTargetProperties(),
&pRT
);
}
// Create text format and a path geometry representing an hour glass.
if (SUCCEEDED(hr))
{
static const WCHAR sc_fontName[] = L"Calibri";
static const FLOAT sc_fontSize = 50;
hr = pDWriteFactory->CreateTextFormat(
sc_fontName,
NULL,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
sc_fontSize,
L"", //locale
&pTextFormat
);
}
if (SUCCEEDED(hr))
{
pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
hr = pD2DFactory->CreatePathGeometry(&pPathGeometry);
}
if (SUCCEEDED(hr))
{
hr = pPathGeometry->Open(&pSink);
}
if (SUCCEEDED(hr))
{
pSink->SetFillMode(D2D1_FILL_MODE_ALTERNATE);
pSink->BeginFigure(
D2D1::Point2F(0, 0),
D2D1_FIGURE_BEGIN_FILLED
);
pSink->AddLine(D2D1::Point2F(200, 0));
pSink->AddBezier(
D2D1::BezierSegment(
D2D1::Point2F(150, 50),
D2D1::Point2F(150, 150),
D2D1::Point2F(200, 200))
);
pSink->AddLine(D2D1::Point2F(0, 200));
pSink->AddBezier(
D2D1::BezierSegment(
D2D1::Point2F(50, 150),
D2D1::Point2F(50, 50),
D2D1::Point2F(0, 0))
);
pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
hr = pSink->Close();
}
if (SUCCEEDED(hr))
{
static const D2D1_GRADIENT_STOP stops[] =
{
{ 0.f, { 0.f, 1.f, 1.f, 1.f } },
{ 1.f, { 0.f, 0.f, 1.f, 1.f } },
};
hr = pRT->CreateGradientStopCollection(
stops,
ARRAYSIZE(stops),
&pGradientStops
);
}
if (SUCCEEDED(hr))
{
hr = pRT->CreateLinearGradientBrush(
D2D1::LinearGradientBrushProperties(
D2D1::Point2F(100, 0),
D2D1::Point2F(100, 200)),
D2D1::BrushProperties(),
pGradientStops,
&pLGBrush
);
}
if (SUCCEEDED(hr))
{
hr = pRT->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black),
&pBlackBrush
);
}
if (SUCCEEDED(hr))
{
// Render into the bitmap.
pRT->BeginDraw();
pRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_SIZE_F rtSize = pRT->GetSize();
// Set the world transform to a 45 degree rotation at the center of the render target
// and write "Hello, World".
pRT->SetTransform(
D2D1::Matrix3x2F::Rotation(
45,
D2D1::Point2F(
rtSize.width / 2,
rtSize.height / 2))
);
static const WCHAR sc_helloWorld[] = L"Hello, World!";
pRT->DrawText(
sc_helloWorld,
ARRAYSIZE(sc_helloWorld) - 1,
pTextFormat,
D2D1::RectF(0, 0, rtSize.width, rtSize.height),
pBlackBrush);
// Reset back to the identity transform.
pRT->SetTransform(D2D1::Matrix3x2F::Translation(0, rtSize.height - 200));
pRT->FillGeometry(pPathGeometry, pLGBrush);
pRT->SetTransform(D2D1::Matrix3x2F::Translation(rtSize.width - 200, 0));
pRT->FillGeometry(pPathGeometry, pLGBrush);
hr = pRT->EndDraw();
}
if (SUCCEEDED(hr))
{
// Save the image to a file.
hr = pWICFactory->CreateStream(&pStream);
}
WICPixelFormatGUID format = GUID_WICPixelFormatDontCare;
// Use InitializeFromFilename to write to a file. If there is need to write inside the memory, use InitializeFromMemory.
if (SUCCEEDED(hr))
{
static const WCHAR filename[] = L"output.png";
hr = pStream->InitializeFromFilename(filename, GENERIC_WRITE);
}
if (SUCCEEDED(hr))
{
hr = pWICFactory->CreateEncoder(GUID_ContainerFormatPng, NULL, &pEncoder);
}
if (SUCCEEDED(hr))
{
hr = pEncoder->Initialize(pStream, WICBitmapEncoderNoCache);
}
if (SUCCEEDED(hr))
{
hr = pEncoder->CreateNewFrame(&pFrameEncode, NULL);
}
// Use IWICBitmapFrameEncode to encode the bitmap into the picture format you want.
if (SUCCEEDED(hr))
{
hr = pFrameEncode->Initialize(NULL);
}
if (SUCCEEDED(hr))
{
hr = pFrameEncode->SetSize(sc_bitmapWidth, sc_bitmapHeight);
}
if (SUCCEEDED(hr))
{
hr = pFrameEncode->SetPixelFormat(&format);
}
if (SUCCEEDED(hr))
{
hr = pFrameEncode->WriteSource(pWICBitmap, NULL);
}
if (SUCCEEDED(hr))
{
hr = pFrameEncode->Commit();
}
if (SUCCEEDED(hr))
{
hr = pEncoder->Commit();
}
Conclusión
Como se ha visto en lo anterior, el uso de Direct2D para la representación del lado servidor es sencillo y sencillo. Además, proporciona una representación de alta calidad y altamente paralelizable que se puede ejecutar en entornos con pocos privilegios del servidor.
Temas relacionados