Partager via


Utilisation de Direct2D pour le rendu Server-Side

Direct2D convient parfaitement aux applications graphiques qui nécessitent un rendu côté serveur sur Windows Server. Cette vue d’ensemble décrit les principes de base de l’utilisation de Direct2D pour le rendu côté serveur. Il contient les sections suivantes :

Configuration requise pour le rendu Server-Side

Voici un scénario classique pour un serveur de graphique : les graphiques et graphiques sont rendus sur un serveur et remis sous forme de bitmaps en réponse aux demandes web. Le serveur peut être équipé d’un graphique bas de carte ou aucun graphique carte du tout.

Ce scénario révèle trois exigences d’application. Tout d’abord, l’application doit gérer efficacement plusieurs requêtes simultanées, en particulier sur les serveurs multicœurs. Deuxièmement, l’application doit utiliser le rendu logiciel lorsqu’elle s’exécute sur des serveurs avec des graphiques bas de carte ou aucun carte graphiques. Enfin, l’application doit s’exécuter en tant que service dans la session 0 afin qu’elle ne nécessite pas la connexion d’un utilisateur. Pour plus d’informations sur la session 0, consultez Impact of Session 0 Isolation on Services and Drivers in Windows.

Options pour les API disponibles

Il existe trois options pour le rendu côté serveur : GDI, GDI+ et Direct2D. Comme GDI et GDI+, Direct2D est une API de rendu 2D native qui permet aux applications de mieux contrôler l’utilisation des périphériques graphiques. En outre, Direct2D prend en charge une usine monothread et multithread. Les sections suivantes comparent chaque API en termes de qualités de dessin et de rendu côté serveur multithread.

GDI

Contrairement à Direct2D et GDI+, GDI ne prend pas en charge les fonctionnalités de dessin de haute qualité. Par instance, GDI ne prend pas en charge l’anticrénelage pour créer des lignes lisses et n’a qu’une prise en charge limitée de la transparence. En fonction des résultats des tests de performances graphiques sur Windows 7 et Windows Server 2008 R2, Direct2D est mis à l’échelle plus efficacement que GDI, malgré la refonte des verrous dans GDI. Pour plus d’informations sur ces résultats de test, consultez Ingénierie des performances graphiques windows 7.

En outre, les applications utilisant GDI sont limitées à 10240 handles GDI par processus et à 65536 handles GDI par session. La raison en est que Windows utilise en interne un WORD 16 bits pour stocker l’index des handles pour chaque session.

GDI+

Alors que GDI+ prend en charge l’anticrénelage et le mélange alpha pour un dessin de haute qualité, le main problème avec GDI+ pour les scénarios serveur est qu’il ne prend pas en charge l’exécution dans la session 0. Étant donné que session 0 prend uniquement en charge les fonctionnalités non interactives, les fonctions qui interagissent directement ou indirectement avec les appareils d’affichage recevront donc des erreurs. Des exemples spécifiques de fonctions incluent non seulement celles qui traitent des périphériques d’affichage, mais également celles qui traitent indirectement avec les pilotes de périphérique.

Comme GDI, GDI+ est limité par son mécanisme de verrouillage. Les mécanismes de verrouillage dans GDI+ sont les mêmes dans Windows 7 et Windows Server 2008 R2 que dans les versions précédentes.

Direct2D

Direct2D est une API graphique 2D accélérée par le matériel, en mode immédiat, qui fournit des performances élevées et un rendu de haute qualité. Il offre une fabrique monothread et multithread, ainsi que la mise à l’échelle linéaire du rendu de logiciels grossiers.

Pour ce faire, Direct2D définit une interface de fabrique racine. En règle générale, un objet créé sur une fabrique ne peut être utilisé qu’avec d’autres objets créés à partir de la même fabrique. L’appelant peut demander une fabrique monothread ou multithread lors de sa création. Si une fabrique à thread unique est demandée, aucun verrouillage des threads n’est effectué. Si l’appelant demande une fabrique multithread, un verrou de thread à l’échelle de l’usine est acquis chaque fois qu’un appel est effectué dans Direct2D.

En outre, le verrouillage des threads dans Direct2D est plus granulaire que dans GDI et GDI+, de sorte que l’augmentation du nombre de threads a un impact minimal sur les performances.

Utilisation de Direct2D pour le rendu Server-Side

Les sections suivantes décrivent comment utiliser le rendu logiciel, comment utiliser de manière optimale une fabrique monothread et multithread, et comment dessiner et enregistrer un dessin complexe dans un fichier.

Rendu logiciel

Les applications côté serveur utilisent le rendu logiciel en créant une cible de rendu IWICBitmap , avec le type de cible de rendu défini sur D2D1_RENDER_TARGET_TYPE_SOFTWARE ou D2D1_RENDER_TARGET_TYPE_DEFAULT. Pour plus d’informations sur les cibles de rendu IWICBitmap , consultez la méthode ID2D1Factory::CreateWicBitmapRenderTarget ; Pour plus d’informations sur les types cibles de rendu, consultez D2D1_RENDER_TARGET_TYPE.

Multithreading

Savoir comment créer et partager des fabriques et des cibles de rendu sur des threads peut avoir un impact significatif sur les performances d’une application. Les trois figures suivantes illustrent trois approches variées. L’approche optimale est illustrée dans la figure 3.

diagramme multithreading direct2d avec une seule cible de rendu.

Dans la figure 1, différents threads partagent la même fabrique et la même cible de rendu. Cette approche peut aboutir à des résultats imprévisibles dans les cas où plusieurs threads modifient simultanément l’état de la cible de rendu partagée, par exemple en définissant simultanément la matrice de transformation. Comme le verrouillage interne dans Direct2D ne synchronise pas une ressource partagée telle que des cibles de rendu, cette approche peut entraîner l’échec de l’appel BeginDraw dans le thread 1, car dans le thread 2, l’appel BeginDraw utilise déjà la cible de rendu partagée.

diagramme multithreading direct2d avec plusieurs cibles de rendu.

Pour éviter les résultats imprévisibles rencontrés dans la figure 1, la figure 2 montre une fabrique multithread avec chaque thread ayant sa propre cible de rendu. Cette approche fonctionne, mais elle fonctionne efficacement comme une application monothread. La raison en est que le verrou à l’échelle de l’usine s’applique uniquement au niveau de l’opération de dessin et que tous les appels de dessin dans la même usine sont par conséquent sérialisés. Par conséquent, le thread 1 est bloqué lors de la tentative d’entrée d’un appel de dessin, tandis que le thread 2 se trouve au milieu de l’exécution d’un autre appel de dessin.

diagramme multithreading direct2d avec plusieurs usines et plusieurs cibles de rendu.

La figure 3 montre l’approche optimale, où une fabrique à thread unique et une cible de rendu à thread unique sont utilisées. Étant donné qu’aucun verrouillage n’est effectué lors de l’utilisation d’une fabrique à thread unique, les opérations de dessin dans chaque thread peuvent s’exécuter simultanément pour obtenir des performances optimales.

Génération d’un fichier bitmap

Pour générer un fichier bitmap à l’aide du rendu logiciel, utilisez une cible de rendu IWICBitmap . Utilisez un IWICStream pour écrire l’image bitmap dans un fichier. Utilisez IWICBitmapFrameEncode pour encoder la bitmap dans un format d’image spécifié. L’exemple de code suivant montre comment dessiner et enregistrer l’image suivante dans un fichier.

exemple d’image de sortie.

Cet exemple de code crée d’abord une cible de rendu IWICBitmap et IWICBitmap . Il restitue ensuite un dessin avec du texte, une géométrie de chemin représentant un verre d’heure et un verre d’heure transformé en bitmap WIC. Il utilise ensuite IWICStream::InitializeFromFilename pour enregistrer l’image bitmap dans un fichier. Si votre application doit enregistrer l’image bitmap en mémoire, utilisez plutôt IWICStream::InitializeFromMemory . Enfin, il utilise IWICBitmapFrameEncode pour encoder l’image bitmap.

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

Conclusion

Comme indiqué ci-dessus, l’utilisation de Direct2D pour le rendu côté serveur est simple et directe. En outre, il fournit un rendu de haute qualité et hautement parallélisable qui peut s’exécuter dans des environnements à faible privilège du serveur.

Référence Direct2D