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


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

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

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

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

Этот сценарий показывает три требования к приложению. Во-первых, приложение должно эффективно обрабатывать несколько одновременных запросов, особенно на серверах с несколькими ядрами. Во-вторых, приложение должно использовать программную отрисовку при выполнении на серверах с низкой графической картой или без графической карты. Наконец, приложение должно выполняться как служба в сеансе 0, чтобы пользователь не входил в систему. Дополнительные сведения о сеансе 0 см. в Совместимость приложений: изоляция сеанса 0 и Руководство по сеансу Zero для драйверов 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 для отрисовки Server-Side

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

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

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

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

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

схему многопоточности Direct2D с одним целевым объектом отрисовки.

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

схему многопоточности Direct2D с несколькими целевыми поверхностями рендеринга.

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

схему многопоточности Direct2D с несколькими фабриками и несколькими целевыми объектами отрисовки.

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

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

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

пример выходного изображения.

В этом примере кода сначала создается 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