共用方式為


使用 Direct2D 進行伺服器端轉譯

Direct2D 非常適合需要 Windows Server 上伺服器端轉譯的圖形應用程式。 本概觀說明使用 Direct2D 進行伺服器端轉譯的基本概念。 它包含以下各節:

伺服器端轉譯的需求

以下是圖表伺服器的一般案例:圖表和圖形會在伺服器上轉譯,並以點陣圖的形式傳遞以回應 Web 要求。 伺服器可能配備低端圖形卡,或完全沒有圖形卡。

此案例會顯示三個應用程式需求。 首先,應用程式必須有效率地處理多個並行要求,特別是在多核心伺服器上。 其次,應用程式必須在具有低端圖形卡或沒有圖形卡的伺服器上執行時使用軟體轉譯。 最後,應用程式必須在會話 0 中以服務的形式執行,因此不需要使用者登入。 如需會話 0 的詳細資訊,請參閱應用程式相容性 - UMDF 驅動程式的會話 0 隔離和會話零指導方針。

可用 API 的選項

伺服器端轉譯有三個選項:GDI、GDI+ 和 Direct2D。 如同 GDI 和 GDI+,Direct2D 是原生 2D 轉譯 API,可讓應用程式更充分掌控圖形裝置的使用。 此外,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+ 支援高品質繪圖的反鋸齒和 Alpha 混合,但伺服器案例的 GDI+ 主要問題是它不支援在會話 0 中執行。 由於會話 0 僅支援非互動式功能,因此直接或間接與顯示裝置互動的函式會收到錯誤。 函式的特定範例不僅包括處理顯示裝置,還包括間接處理設備驅動器的範例。

與 GDI 類似,GDI+ 受限於其鎖定機制。 GDI+ 中的鎖定機制在 Windows 7 和 Windows Server 2008 R2 中與舊版相同。

Direct2D

Direct2D 是硬體加速的即時模式 2D 圖形 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.

此程式代碼範例會先建立 IWICBitmapIWICBitmap 轉譯目標。 然後,它會以一些文字呈現繪圖、代表一小時玻璃的路徑幾何,以及轉換成 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 參考