Verwenden von Direct2D für Server-Side Rendering

Direct2D eignet sich gut für Grafikanwendungen, die serverseitiges Rendering unter Windows Server erfordern. In dieser Übersicht werden die Grundlagen der Verwendung von Direct2D für das serverseitige Rendering beschrieben. Sie enthält die folgenden Abschnitte:

Anforderungen für Server-Side Rendering

Es folgt ein typisches Szenario für einen Diagrammserver: Diagramme und Grafiken werden auf einem Server gerendert und als Bitmaps als Reaktion auf Webanforderungen übermittelt. Der Server kann mit einem Low-End-Grafik-Karte oder gar keine Grafik Karte ausgestattet sein.

In diesem Szenario werden drei Anwendungsanforderungen erläutert. Erstens muss die Anwendung mehrere gleichzeitige Anforderungen effizient verarbeiten, insbesondere auf Mehrkernservern. Zweitens muss die Anwendung Softwarerendering verwenden, wenn sie auf Servern mit einem Low-End-Grafik-Karte oder ohne Grafik Karte ausgeführt wird. Schließlich muss die Anwendung als Dienst in Sitzung 0 ausgeführt werden, damit kein Benutzer angemeldet sein muss. Weitere Informationen zu Sitzung 0 finden Sie unter Auswirkungen der Isolation von Sitzung 0 auf Dienste und Treiber in Windows.

Optionen für verfügbare APIs

Es gibt drei Optionen für das serverseitige Rendering: GDI, GDI+ und Direct2D. Wie GDI und GDI+ ist Direct2D eine native 2D-Rendering-API, die Anwendungen mehr Kontrolle über die Verwendung von Grafikgeräten bietet. Darüber hinaus unterstützt Direct2D eindeutig eine Singlethread- und eine Multithread-Factory. In den folgenden Abschnitten werden die einzelnen API in Bezug auf Zeichnungsqualitäten und serverseitiges Multithreadrendering verglichen.

GDI

Im Gegensatz zu Direct2D und GDI+ unterstützt GDI keine hochwertigen Zeichenfeatures. Für instance unterstützt GDI kein Antialiasing für die Erstellung von glatten Linien und bietet nur eingeschränkte Unterstützung für Transparenz. Basierend auf den Grafikleistungstestergebnissen unter Windows 7 und Windows Server 2008 R2 skaliert Direct2D trotz der Neugestaltung von Sperren in GDI effizienter als GDI. Weitere Informationen zu diesen Testergebnissen finden Sie unter Engineering Windows 7 Graphics Performance.

Darüber hinaus sind Anwendungen, die GDI verwenden, auf 10240 GDI-Handles pro Prozess und 65536 GDI-Handles pro Sitzung beschränkt. Der Grund ist, dass Windows intern ein 16-Bit-WORD verwendet, um den Index der Handles für jede Sitzung zu speichern.

GDI+

Während GDI+ Antialiasing und Alpha-Blending für hochwertiges Zeichnen unterstützt, besteht das Standard Problem mit GDI+ für Serverszenarien darin, dass die Ausführung in Sitzung 0 nicht unterstützt wird. Da Sitzung 0 nur nicht interaktive Funktionen unterstützt, erhalten Funktionen, die direkt oder indirekt mit Anzeigegeräten interagieren, Fehler. Spezifische Beispiele für Funktionen sind nicht nur diejenigen, die sich mit Anzeigegeräten befassen, sondern auch diejenigen, die indirekt mit Gerätetreibern zu tun haben.

Ähnlich wie GDI ist GDI+ durch seinen Sperrmechanismus eingeschränkt. Die Sperrmechanismen in GDI+ sind in Windows 7 und Windows Server 2008 R2 identisch wie in früheren Versionen.

Direct2D

Direct2D ist eine hardwarebeschleunigte 2D-Grafik-API, die eine hohe Leistung und hochwertiges Rendering bietet. Es bietet eine Singlethreaded- und eine Multithreaded Factory sowie die lineare Skalierung von grobkörnigem Softwarerendering.

Dazu definiert Direct2D eine Root Factory-Schnittstelle. In der Regel kann ein in einer Factory erstelltes Objekt nur mit anderen Objekten verwendet werden, die aus derselben Factory erstellt wurden. Der Aufrufer kann entweder eine Singlethread- oder eine Multithread-Factory anfordern, wenn sie erstellt wird. Wenn eine Singlethread-Factory angefordert wird, wird keine Sperrung von Threads durchgeführt. Wenn der Aufrufer eine Multithread-Factory anfordert, wird eine werksweite Threadsperre abgerufen, wenn ein Aufruf in Direct2D erfolgt.

Darüber hinaus ist das Sperren von Threads in Direct2D präziser als in GDI und GDI+, sodass die Erhöhung der Anzahl von Threads minimale Auswirkungen auf die Leistung hat.

Verwenden von Direct2D für Server-Side Rendering

In den folgenden Abschnitten wird beschrieben, wie Sie Softwarerendering verwenden, wie Sie eine Singlethread- und eine Multithread-Factory optimal verwenden und wie Sie eine komplexe Zeichnung zeichnen und in einer Datei speichern.

Softwarerendering

Serverseitige Anwendungen verwenden Softwarerendering durch Erstellen eines IWICBitmap-Renderziels , wobei der Renderzieltyp entweder auf D2D1_RENDER_TARGET_TYPE_SOFTWARE oder D2D1_RENDER_TARGET_TYPE_DEFAULT festgelegt ist. Weitere Informationen zu IWICBitmap-Renderzielen finden Sie in der ID2D1Factory::CreateWicBitmapRenderTarget-Methode . Weitere Informationen zu den Renderzieltypen finden Sie unter D2D1_RENDER_TARGET_TYPE.

Multithreading

Es kann sich erheblich auf die Leistung einer Anwendung auswirken, wenn Sie wissen, wie Sie Fabriken erstellen und freigeben und Ziele über Threads hinweg rendern. Die folgenden drei Abbildungen zeigen drei unterschiedliche Ansätze. Der optimale Ansatz ist in Abbildung 3 dargestellt.

Direct2d-Multithreadingdiagramm mit einem einzelnen Renderziel.

In Abbildung 1 verwenden verschiedene Threads dieselbe Factory und dasselbe Renderziel. Dieser Ansatz kann zu unvorhersehbaren Ergebnissen führen, wenn mehrere Threads gleichzeitig den Zustand des freigegebenen Renderziels ändern, z. B. das gleichzeitige Festlegen der Transformationsmatrix. Da die interne Sperrung in Direct2D keine freigegebene Ressource wie Renderziele synchronisiert, kann dieser Ansatz dazu führen, dass der BeginDraw-Aufruf in Thread 1 fehlschlägt, da in Thread 2 der BeginDraw-Aufruf bereits das freigegebene Renderziel verwendet.

direct2d-Multithreadingdiagramm mit mehreren Renderzielen.

Um die unvorhersehbaren Ergebnisse in Abbildung 1 zu vermeiden, zeigt Abbildung 2 eine Multithread-Factory mit jedem Thread ein eigenes Renderziel. Dieser Ansatz funktioniert, funktioniert aber effektiv als Singlethread-Anwendung. Der Grund ist, dass die werksweite Sperre nur für die Zeichnungsvorgangsebene gilt und daher alle Zeichnungsaufrufe in derselben Fabrik serialisiert werden. Infolgedessen wird Thread 1 blockiert, wenn versucht wird, einen Zeichnungsaufruf einzugeben, während Thread 2 gerade einen weiteren Zeichnungsaufruf ausführt.

Direct2d-Multithreadingdiagramm mit mehreren Fabriken und mehreren Renderzielen.

Abbildung 3 zeigt den optimalen Ansatz, bei dem eine Singlethread-Factory und ein Singlethread-Renderziel verwendet werden. Da bei Verwendung einer Singlethread-Factory keine Sperrung ausgeführt wird, können Zeichnungsvorgänge in jedem Thread gleichzeitig ausgeführt werden, um eine optimale Leistung zu erzielen.

Generieren einer Bitmapdatei

Verwenden Sie zum Generieren einer Bitmapdatei mithilfe des Softwarerenderings ein IWICBitmap-Renderziel . Verwenden Sie einen IWICStream , um die Bitmap in eine Datei zu schreiben. Verwenden Sie IWICBitmapFrameEncode , um die Bitmap in ein angegebenes Bildformat zu codieren. Im folgenden Codebeispiel wird gezeigt, wie Sie das folgende Bild zeichnen und in einer Datei speichern.

Beispielausgabebild.

In diesem Codebeispiel wird zunächst ein IWICBitmap - und ein IWICBitmap-Renderziel erstellt. Anschließend wird eine Zeichnung mit text, einer Pfadgeometrie, die ein Stundenglas darstellt, und ein transformiertes Stundenglas in eine WIC-Bitmap gerendert. Anschließend wird IWICStream::InitializeFromFilename verwendet, um die Bitmap in einer Datei zu speichern. Wenn Ihre Anwendung die Bitmap im Arbeitsspeicher speichern muss, verwenden Sie stattdessen IWICStream::InitializeFromMemory . Schließlich wird IWICBitmapFrameEncode verwendet, um die Bitmap zu codieren.

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

Zusammenfassung

Wie oben gezeigt, ist die Verwendung von Direct2D für das serverseitige Rendering einfach und einfach. Darüber hinaus bietet es hochwertiges und hochgradig parallelisierbares Rendering, das in Umgebungen mit geringen Berechtigungen des Servers ausgeführt werden kann.

Direct2D-Referenz