Поделиться через


Использование Direct2D для отрисовки на стороне сервера

Direct2D хорошо подходит для графических приложений, требующих отрисовки на стороне сервера в Windows Server. В этом обзоре описаны основы использования Direct2D для отрисовки на стороне сервера. Он содержит следующие разделы:

Требования к отрисовке на стороне сервера

Ниже приведен типичный сценарий для сервера диаграмм: диаграммы и графики отображаются на сервере и передаются в виде растровых изображений в ответ на веб-запросы. Сервер может быть оснащен низкой графикой карта или нет графических карта вообще.

Этот сценарий показывает три требования к приложению. Во-первых, приложение должно эффективно обрабатывать несколько одновременных запросов, особенно на серверах с несколькими ядрами. Во-вторых, приложение должно использовать программную отрисовку при выполнении на серверах с низкой графикой карта или без графических карта. Наконец, приложение должно выполняться как служба в сеансе 0, чтобы пользователь не входил в систему. Дополнительные сведения о сеансе 0 см. в разделе "Совместимость приложений— изоляция сеанса 0 и ноль сеансов" для драйверов UMDF.

Параметры доступных API

Существует три варианта отрисовки на стороне сервера: GDI, GDI+ и Direct2D. Как и GDI и GDI+, Direct2D — это собственный API отрисовки 2D, который позволяет приложениям более контролировать использование графических устройств. Кроме того, Direct2D однозначно поддерживает однопоточную и многопоточную фабрику. В следующих разделах сравнивается каждый API с точки зрения качества рисования и многопоточной отрисовки на стороне сервера.

GDI

В отличие от Direct2D и GDI+, GDI не поддерживает высококачественные функции рисования. Например, GDI не поддерживает антиализию для создания гладких линий и имеет только ограниченную поддержку прозрачности. На основе результатов теста производительности графики в Windows 7 и Windows Server 2008 R2 Direct2D масштабируется более эффективно, чем GDI, несмотря на изменение блокировок в GDI. Дополнительные сведения об этих результатах теста см. в разделе "Инженерная производительность графики Windows 7".

Кроме того, приложения, использующие GDI, ограничены 10240 GDI дескрипторами на каждый процесс и 65536 GDI-дескрипторов на сеанс. Причина заключается в том, что в Windows используется 16-разрядная версия WORD для хранения индекса дескрипторов для каждого сеанса.

GDI+

Хотя GDI+ поддерживает антилиастинг и альфа-смешивание для высококачественного рисования, основная проблема с GDI+ для сценариев сервера заключается в том, что она не поддерживает выполнение в сеансе 0. Так как сеанс 0 поддерживает только неинтерактивные функции, функции, которые напрямую или косвенно взаимодействуют с устройствами отображения, следовательно, получат ошибки. Конкретные примеры функций включают не только те, которые имеют дело с устройствами отображения, но и косвенной борьбы с драйверами устройств.

Аналогично GDI, GDI+ ограничен механизмом блокировки. Механизмы блокировки в GDI+ одинаковы в Windows 7 и Windows Server 2008 R2, как и в предыдущих версиях.

Direct2D

Direct2D — это аппаратно-ускоренный, немедленный, 2-D графический API, обеспечивающий высокую производительность и высококачественную отрисовку. Она предлагает однопоточную и многопоточную фабрику и линейное масштабирование грубой отрисовки программного обеспечения.

Для этого Direct2D определяет интерфейс корневой фабрики. Как правило, объект, созданный на фабрике, можно использовать только с другими объектами, созданными из той же фабрики. Вызывающий объект может запрашивать однопотоковую или многопоточную фабрику при создании. Если запрашивается однопоточная фабрика, блокировка потоков не выполняется. Если вызывающий запрос запрашивает многопототочную фабрику, то блокировка потока на уровне фабрики приобретается при каждом вызове в Direct2D.

Кроме того, блокировка потоков в Direct2D более детализирована, чем в GDI и GDI+, поэтому увеличение числа потоков оказывает минимальное влияние на производительность.

Использование Direct2D для отрисовки на стороне сервера

В следующих разделах описывается использование программной отрисовки, оптимального использования однопоточной и многопоточной фабрики, а также способов рисования и сохранения сложного рисунка в файле.

Отрисовка программного обеспечения

Серверные приложения используют программную отрисовку путем создания целевого объекта отрисовки IWICBitmap , а целевой тип отрисовки — D2D1_RENDER_TARGET_TYPE_SOFTWARE или D2D1_RENDER_TARGET_TYPE_DEFAULT. Дополнительные сведения о целевых объектах отрисовки IWICBitmap см. в методе ID2D1Factory::CreateWicBitmapRenderTarget; дополнительные сведения о типах целевых объектов отрисовки см. в D2D1_RENDER_TARGET_TYPE.

Многопоточность

Зная, как создавать и совместно использовать фабрики и отображать целевые объекты в потоках, может значительно повлиять на производительность приложения. На следующих трех рисунках показаны три различных подхода. Оптимальный подход показан на рисунке 3.

direct2d multithreading diagram with a single render target.

На рисунке 1 разные потоки используют одну и ту же фабрику и один и тот же целевой объект отрисовки. Такой подход может привести к непредсказуемым результатам в случаях, когда несколько потоков одновременно изменяют состояние общего целевого объекта отрисовки, например одновременное задание матрицы преобразования. Так как внутренняя блокировка в Direct2D не синхронизирует общий ресурс, например целевые объекты отрисовки, этот подход может привести к сбою вызова BeginDraw в потоке 1, так как в потоке 2 вызов BeginDraw уже использует общий целевой объект отрисовки.

direct2d multithreading diagram with multiple render targets.

Чтобы избежать непредсказуемых результатов, возникших на рис. 1, на рис. 2 показана многопоточность фабрики с каждым потоком с собственным целевым объектом отрисовки. Этот подход работает, но он эффективно работает как однопоточное приложение. Причина заключается в том, что блокировка на уровне операции рисования применяется только к уровню операции рисования, а все вызовы рисования в той же фабрике, следовательно, сериализуются. В результате поток 1 блокируется при попытке ввести вызов рисования, а поток 2 находится в середине выполнения другого вызова рисования.

direct2d multithreading diagram with multiple factories and multiple render targets.

На рисунке 3 показан оптимальный подход, где используется однопоточная фабрика и цель отрисовки с одним потоком. Так как при использовании однопоточной фабрики блокировка не выполняется, операции рисования в каждом потоке могут выполняться параллельно, чтобы обеспечить оптимальную производительность.

Создание растрового файла

Чтобы создать растровый файл с помощью программной отрисовки, используйте целевой объект отрисовки IWICBitmap . Используйте IWICStream для записи растрового изображения в файл. Используйте IWICBitmapFrameEncode для кодирования растрового изображения в указанном формате изображения. В следующем примере кода показано, как нарисовать и сохранить следующее изображение в файле.

example output image.

В этом примере кода сначала создается IWICBitmap и целевой объект отрисовки IWICBitmap . Затем он отрисовывает рисунок с некоторым текстом, геометрией пути, представляющей час стекла и преобразованным часовым стеклом в растровое изображение WIC. Затем он использует IWICStream::InitializeFromFilename для сохранения растрового изображения в файле. Если приложению нужно сохранить растровое изображение в памяти, используйте IWICStream::InitializeFromMemory . Наконец, он использует IWICBitmapFrameEncode для кодирования растрового изображения.

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

Заключение

Как показано выше, использование Direct2D для отрисовки на стороне сервера является простым и простым. Кроме того, он обеспечивает высококачественную и высоко параллелизуемую отрисовку, которая может выполняться в средах с низким уровнем привилегий сервера.

Справочник по Direct2D