Compartilhar via


Usando Direct2D para renderização de Server-Side

Direct2D é adequado para aplicativos gráficos que exigem renderização do lado do servidor no Windows Server. Esta visão geral descreve os conceitos básicos do uso de Direct2D para renderização do lado do servidor. Ele contém as seções a seguir:

Requisitos para renderização de Server-Side

Veja a seguir um cenário típico para um servidor de gráficos: gráficos e gráficos são renderizados em um servidor e entregues como bitmaps em resposta a solicitações da Web. O servidor pode estar equipado com elementos gráficos de baixo nível cartão ou nenhum elemento gráfico cartão.

Esse cenário revela três requisitos de aplicativo. Primeiro, o aplicativo deve lidar com várias solicitações simultâneas com eficiência, especialmente em servidores multicore. Em segundo lugar, o aplicativo deve usar a renderização de software ao ser executado em servidores com gráficos de baixo nível cartão ou sem elementos gráficos cartão. Por fim, o aplicativo deve ser executado como um serviço na Sessão 0 para que ele não exija que um usuário seja conectado. Para obter mais informações sobre a Sessão 0, consulte Impacto do isolamento da sessão 0 em serviços e drivers no Windows.

Opções para APIs disponíveis

Há três opções para renderização do lado do servidor: GDI, GDI+ e Direct2D. Como GDI e GDI+, Direct2D é uma API de renderização 2D nativa que dá aos aplicativos mais controle sobre o uso de dispositivos gráficos. Além disso, Direct2D dá suporte exclusivo a uma fábrica de thread único e multithread. As seções a seguir comparam cada API em termos de qualidades de desenho e renderização multithreaded do lado do servidor.

GDI

Ao contrário de Direct2D e GDI+, o GDI não dá suporte a recursos de desenho de alta qualidade. Por exemplo, a GDI não dá suporte à suavização para a criação de linhas suaves e tem apenas suporte limitado para transparência. Com base nos resultados do teste de desempenho gráfico no Windows 7 e no Windows Server 2008 R2, Direct2D é dimensionado com mais eficiência do que o GDI, apesar da reformulação dos bloqueios na GDI. Para obter mais informações sobre esses resultados de teste, consulte Engenharia de desempenho gráfico do Windows 7.

Além disso, os aplicativos que usam GDI são limitados a 10240 identificadores GDI por processo e a 65536 identificadores GDI por sessão. O motivo é que internamente o Windows usa um WORD de 16 bits para armazenar o índice de identificadores para cada sessão.

GDI+

Embora o GDI+ dê suporte à suavização e à mesclagem alfa para desenho de alta qualidade, o main problema com o GDI+ para cenários de servidor é que ele não dá suporte à execução na Sessão 0. Como a Sessão 0 dá suporte apenas à funcionalidade não interativa, as funções que interagem direta ou indiretamente com dispositivos de exibição receberão erros. Exemplos específicos de funções incluem não apenas aqueles que lidam com dispositivos de exibição, mas também aqueles que lidam indiretamente com drivers de dispositivo.

Semelhante ao GDI, o GDI+ é limitado por seu mecanismo de bloqueio. Os mecanismos de bloqueio no GDI+ são os mesmos no Windows 7 e no Windows Server 2008 R2 como nas versões anteriores.

Direct2D

Direct2D é uma API de gráficos 2D acelerada por hardware e modo imediato que fornece renderização de alto desempenho e alta qualidade. Ele oferece uma fábrica de thread único e multithread e o dimensionamento linear da renderização de software com granularidade grosseira.

Para fazer isso, Direct2D define uma interface de fábrica raiz. Como regra, um objeto criado em uma fábrica só pode ser usado com outros objetos criados da mesma fábrica. O chamador pode solicitar uma fábrica de thread único ou multithread quando ela é criada. Se uma fábrica de thread único for solicitada, nenhum bloqueio de threads será executado. Se o chamador solicitar uma fábrica multithreaded, um bloqueio de thread em toda a fábrica será adquirido sempre que uma chamada for feita em Direct2D.

Além disso, o bloqueio de threads em Direct2D é mais granular do que em GDI e GDI+, de modo que o aumento do número de threads tenha impacto mínimo no desempenho.

Como usar Direct2D para renderização Server-Side

As seções a seguir descrevem como usar a renderização de software, como usar de forma ideal uma fábrica de thread único e multithread e como desenhar e salvar um desenho complexo em um arquivo.

Renderização de software

Os aplicativos do lado do servidor usam a renderização de software criando o destino de renderização IWICBitmap , com o tipo de destino de renderização definido como D2D1_RENDER_TARGET_TYPE_SOFTWARE ou D2D1_RENDER_TARGET_TYPE_DEFAULT. Para obter mais informações sobre destinos de renderização IWICBitmap , consulte o método ID2D1Factory::CreateWicBitmapRenderTarget ; para obter mais informações sobre os tipos de destino de renderização, consulte D2D1_RENDER_TARGET_TYPE.

Multithreading

Saber como criar e compartilhar fábricas e renderizar destinos entre threads pode afetar significativamente o desempenho de um aplicativo. Os três números a seguir mostram três abordagens variadas. A abordagem ideal é mostrada na figura 3.

diagrama de multithreading direct2d com um único destino de renderização.

Na figura 1, threads diferentes compartilham a mesma fábrica e o mesmo destino de renderização. Essa abordagem pode levar a resultados imprevisíveis em casos em que vários threads alteram simultaneamente o estado do destino de renderização compartilhada, como definir simultaneamente a matriz de transformação. Como o bloqueio interno no Direct2D não sincroniza um recurso compartilhado, como destinos de renderização, essa abordagem pode fazer com que a chamada BeginDraw falhe no thread 1, pois no thread 2, a chamada BeginDraw já está usando o destino de renderização compartilhado.

diagrama multithreading direct2d com vários destinos de renderização.

Para evitar os resultados imprevisíveis encontrados na figura 1, a figura 2 mostra uma fábrica multithread com cada thread tendo seu próprio destino de renderização. Essa abordagem funciona, mas funciona efetivamente como um aplicativo de thread único. O motivo é que o bloqueio em toda a fábrica se aplica apenas ao nível de operação de desenho e todas as chamadas de desenho na mesma fábrica, consequentemente, são serializadas. Como resultado, o thread 1 fica bloqueado ao tentar inserir uma chamada de desenho, enquanto o thread 2 está no meio da execução de outra chamada de desenho.

diagrama multithreading direct2d com várias fábricas e vários destinos de renderização.

A Figura 3 mostra a abordagem ideal, em que uma fábrica de thread único e um destino de renderização de thread único são usados. Como nenhum bloqueio é executado ao usar uma fábrica de thread único, as operações de desenho em cada thread podem ser executadas simultaneamente para obter o desempenho ideal.

Gerando um arquivo bitmap

Para gerar um arquivo bitmap usando a renderização de software, use um destino de renderização IWICBitmap . Use um IWICStream para gravar o bitmap em um arquivo. Use IWICBitmapFrameEncode para codificar o bitmap em um formato de imagem especificado. O exemplo de código a seguir mostra como desenhar e salvar a imagem a seguir em um arquivo.

exemplo de imagem de saída.

Este exemplo de código primeiro cria um IWICBitmap e um destino de renderização IWICBitmap . Em seguida, ele renderiza um desenho com algum texto, uma geometria de caminho que representa um vidro de uma hora e um vidro de hora transformado em um bitmap WIC. Em seguida, ele usa IWICStream::InitializeFromFilename para salvar o bitmap em um arquivo. Se o aplicativo precisar salvar o bitmap na memória, use IWICStream::InitializeFromMemory . Por fim, ele usa IWICBitmapFrameEncode para codificar o bitmap.

// 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();
}

Conclusão

Como visto acima, usar Direct2D para renderização do lado do servidor é simples e simples. Além disso, ele fornece renderização de alta qualidade e altamente paralelizável que pode ser executada em ambientes de baixo privilégio do servidor.

Referência de Direct2D