次の方法で共有


Server-Side レンダリングに Direct2D を使用する

Direct2D は、Windows Server でサーバー側レンダリングを必要とするグラフィックス アプリケーションに適しています。 この概要では、サーバー側レンダリングに Direct2D を使用する基本について説明します。 次のセクションが含まれます。

Server-Side レンダリングの要件

グラフ サーバーの一般的なシナリオを次に示します。グラフとグラフィックスはサーバー上にレンダリングされ、Web 要求に応答してビットマップとして配信されます。 サーバーには、ローエンドのグラフィックス カードが搭載されているか、グラフィックスカードがまったく搭載されていない可能性があります。

このシナリオでは、3 つのアプリケーション要件が明らかになります。 まず、アプリケーションは複数の同時要求を効率的に処理する必要があります(特にマルチコア サーバー上)。 次に、アプリケーションは、ローエンド グラフィックス カードまたはグラフィックス カードのないサーバーで実行されている場合に、ソフトウェア レンダリングを使用する必要があります。 最後に、ユーザーがログインする必要がないように、アプリケーションはセッション 0 でサービスとして実行する必要があります。 セッション 0 の詳細については、「 Windows のサービスとドライバーに対するセッション 0 の分離の影響」を参照してください。

使用可能な API のオプション

サーバー側レンダリングには、GDI、GDI+、Direct2D の 3 つのオプションがあります。 GDI や GDI+ と同様に、Direct2D はネイティブの 2D レンダリング API であり、アプリケーションはグラフィックス デバイスの使用をより細かく制御できます。 さらに、Direct2D では、シングル スレッドファクトリとマルチスレッド ファクトリが一意にサポートされます。 次のセクションでは、描画品質とマルチスレッド サーバー側レンダリングの観点から各 API を比較します。

GDI

Direct2D および GDI+ とは異なり、GDI は高品質の描画機能をサポートしていません。 たとえば、GDI では滑らかな線を作成するためのアンチエイリアシングはサポートされておらず、透明度のサポートは限られています。 Windows 7 および Windows Server 2008 R2 のグラフィックス パフォーマンス テストの結果に基づいて、DIRECT2D は GDI でのロックの再設計にもかかわらず、GDI よりも効率的にスケーリングされます。 これらのテスト結果の詳細については、「 Engineering Windows 7 Graphics Performance」を参照してください。

さらに、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+ よりも細かく、スレッド数の増加がパフォーマンスへの影響を最小限に抑えます。

Server-Side レンダリングに Direct2D を使用する方法

次のセクションでは、ソフトウェア レンダリングを使用する方法、シングル スレッドファクトリとマルチスレッド ファクトリを最適に使用する方法、および複雑な図面を描画してファイルに保存する方法について説明します。

ソフトウェア レンダリング

サーバー側アプリケーションでは、 IWICBitmap レンダー ターゲットを作成してソフトウェア レンダリングを使用し、レンダー ターゲットの種類を D2D1_RENDER_TARGET_TYPE_SOFTWARE または D2D1_RENDER_TARGET_TYPE_DEFAULT に設定します。 IWICBitmap レンダー ターゲットの詳細については、ID2D1Factory::CreateWicBitmapRenderTarget メソッドを参照してください。レンダー ターゲットの種類の詳細については、「D2D1_RENDER_TARGET_TYPE」を参照してください。

マルチスレッド

ファクトリを作成して共有し、スレッド間でターゲットをレンダリングする方法を知ることは、アプリケーションのパフォーマンスに大きな影響を与える可能性があります。 次の 3 つの図は、3 つのさまざまなアプローチを示しています。 最適なアプローチを図 3 に示します。

1 つのレンダー ターゲットを持つ direct2d マルチスレッド図。

図 1 では、異なるスレッドが同じファクトリと同じレンダー ターゲットを共有しています。 この方法により、変換マトリックスを同時に設定するなど、複数のスレッドで共有レンダー ターゲットの状態が同時に変更される場合に、予期しない結果が発生する可能性があります。 Direct2D の内部ロックではレンダー ターゲットなどの共有リソースが同期されないため、スレッド 2 では BeginDraw 呼び出しが既に共有レンダー ターゲットを使用しているため、この方法により、スレッド 1 で BeginDraw 呼び出しが失敗する可能性があります。

複数のレンダー ターゲットを含む direct2d マルチスレッド図。

図 1 で予期しない結果が発生しないようにするために、図 2 は、各スレッドが独自のレンダー ターゲットを持つマルチスレッド ファクトリを示しています。 この方法は機能しますが、効果的にシングルスレッド アプリケーションとして機能します。 これは、ファクトリ全体のロックが図面操作レベルにのみ適用され、同じファクトリ内のすべての図面呼び出しがシリアル化されるためです。 その結果、描画呼び出しに入ろうとするとスレッド 1 がブロックされ、スレッド 2 は別の描画呼び出しを実行する途中になります。

複数のファクトリと複数のレンダー ターゲットを含む direct2d マルチスレッド図。

図 3 は、シングルスレッド ファクトリとシングルスレッド レンダー ターゲットが使用される最適なアプローチを示しています。 シングルスレッド ファクトリを使用する場合、ロックは実行されないので、各スレッドの描画操作を同時に実行して最適なパフォーマンスを実現できます。

ビットマップ ファイルの生成

ソフトウェア レンダリングを使用してビットマップ ファイルを生成するには、 IWICBitmap レンダー ターゲットを使用します。 IWICStream を使用して、ビットマップをファイルに書き込みます。 ビットマップを指定したイメージ形式にエンコードするには、 IWICBitmapFrameEncode を使用します。 次のコード例は、次の画像を描画してファイルに保存する方法を示しています。

出力イメージの例。

このコード例では、最初に IWICBitmapIWICBitmap レンダー ターゲットを作成します。 次に、テキスト、1 時間のグラスを表すパス ジオメトリ、変換された 1 時間グラスを 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 リファレンス