Pourquoi Direct2D ?
GDI a très longtemps été un bon moyen d’afficher des graphismes à l’écran. Pendant 20 ans, nous avons utilisé GDI pour afficher graphiques, images et textes dans nos applications Windows. Avec Windows XP est apparu GDI+, une extension objet de GDI, avec des possibilités plus étendues. Mais GDI a été conçu à une époque où n’existaient ni cartes graphiques ni appareils photo numériques. Avec l’arrivée de Windows Vista, de WPF, GDI montre son âge, et apparait aujourd’hui largement dépassé.
Un développeur codant avec GDI+ sous Windows XP
Faire évoluer GDI s’est avéré impossible, son architecture étant trop limitée. Par exemple, GDI ne supporte pas les pixels de plus de 32 bits, et est incompatible avec les images à haute gamme dynamique. Et surtout, GDI est incompatible avec DirectX. Impossible de faire cohabiter les deux pour bénéficier de l’accélération matérielle et des animations que l’on souhaite dans les programmes d’aujourd’hui.
En 2006-2007, Microsoft a décidé de créer une nouvelle API graphique pour Windows 7. Elle permet d’afficher des graphismes, des images et du texte, et remplacera GDI. Les objectifs sont :
- Plus de fonctionnalités que GDI et GDI+
- Utilisation de l’accélération matérielle et les processeurs GPU des cartes graphiques. Performances nettement supérieures.
- Interopérabilité avec les autres technologies graphiques de Windows : GDI, GDI+ (possibilité d’utiliser Direct2D dans un HDC créé par GDI/GDI+), Direct3D (possibilité de dessiner dans une surface Direct3D), Windows Image Codecs (WIC) pour lire et écrire les images dans les formats les plus courants, etc.
Si l’utilité de l’accélération matérielle est évidente, l’interopérabilité est également très importante. En effet, GDI est totalement incompatible avec Direct3D ou DirectDraw. Si l’on veut écrire une application graphique rapide avec DirectX, on ne peut utiliser ni GDI (pour afficher du texte par exemple), ni les contrôles standard de Windows. C’est très gênant, car cela implique une réécriture totale du code si l’on souhaite migrer de GDI à DirectDraw par exemple.
Ce problème est bien connu des développeurs de jeux avec Direct3D, qui doivent utiliser des librairies spécifiques basées uniquement sur DirectX pour afficher des interfaces homme-machines. Si l’on veut utiliser un simple champ Edit, il faut le coder en utilisant DirectX !
Autre exemple, l’application Picasa utilise l’accélération matérielle avec DirectDraw pour obtenir une fluidité suffisante. Mais les programmeurs n’ont pas pu utiliser GDI ni le système de fenêtres et de contrôles de Windows : ils ont dû écrire leur propre système pour gérer l’interface homme machine !
Microsoft a souhaité éviter ces écueils dès la conception de Direct2D.
Pour accélérer l’affichage d’une application GDI, il n’est pas nécessaire de modifier tout le code graphique. On pourra n’utiliser l’API Direct2D que sur certains points, et continuer à utiliser GDI pour le reste. Direct2D est le successeur de GDI et GDI+, et il est possible d’adopter petit à petit cette nouvelle API.
Et bien sûr, Direct2D utilise toute la puissance des processeurs GPU de nos cartes graphiques. Imaginez-vous ces chevaux qui piaffent d’impatience avec GDI dans vos cartes graphiques ? Ils sont occupés avec Direct2D !

Un programme utilisant Direct2D s’exécute sous Windows 7 (Crédits photos www.sxc.hu/)
La compatibilité de Direct2D
Direct2D est construit sur Direct3D 10, la version de Direct3D qui est sortie avec Windows 7. Direct2D utilise toute la puissance de Direct3D. Cela lui permet non seulement d’être plus rapide que GDI, mais d’être plus puissant en même temps. Par exemple, l’affichage Direct2D de texte avec antialiasing est plus rapide que l’affichage GDI de texte sans antialising ! Dans les prochaines versions de Windows, Direct2D suivra bien sûr l’évolution des performances des versions à venir de Direct3D.
Direct2D fonctionne également avec une carte graphique compatible avec Direct3D 9. Et si votre PC possède une carte graphique ancienne, antérieure à Direct3D 9, Direct2D fonctionne en utilisant un mode logiciel.
Direct2D a été créé pour Windows 7, mais est également disponible sous Windows Vista.
Sous Windows 8, GDI n’est plus supporté dans WinRT. Il n’est pas possible d’utiliser GDI ou GDI+ pour écrire des applications Metro utilisant WinRT sous Windows 8. Il faut utiliser à la place Direct2D qui est complètement supporté dans WinRT. Voir l’article sur les API graphiques supportées dans WinRT sous Windows 8. Un argument de plus pour s’y mettre !
Par contre, Direct2D n’existe pas sous Windows XP.
Peut-être êtres vous concerné par l’absence de Direct2D sous Windows XP. Peut-être vous dites-vous : “Je ne vais pas utiliser Direct2D qui n’est pas disponible sous XP, alors qu’un tiers de mes clients utilisent encore XP. Je ne vais pas me priver d’un tiers de mes clients”.
En effet, Windows XP est encore installé sur un peu moins de 30% des PC en cette fin 2011. Mais ce chiffre diminue rapidement depuis la sortie de Windows 7, à un rythme d’environ 15-20% par an. D’ici un an, les utilisateurs de Windows XP devraient vraiment se faire rare. Un an, c’est à peu près le temps qu’il faut pour qu’un développement qui commence maintenant arrive chez les clients.
Et puis la prochaine version de Visual Studio, le 11eme du nom, qui devrait sortir en 2012, ne supportera plus Windows XP. Les applications générées par Visual Studio 2012 ne pourront pas s’exécuter sous XP. Tous les éditeurs de logiciels vont être obligés d’abandonner XP. La bonne nouvelle, c’est que vos concurrents aussi abandonneront XP en même temps que vous !
Enfin, si un client possède encore Windows XP et n’a pas encore migré vers Windows 7, c’est qu’il limite son budget. Il aura également du mal à acheter des logiciels, fussent-ils compatibles XP !
En conclusion, le temps est venu d’utiliser Direct2D sans retenue, et sans regret pour XP ! Quoi qu’il arrive, Direct2D remplacera GDI et GDI+.
Direct2D : COM or not COM
Direct2D est très différent de GDI et de GDI+. On peut placer Direct2D à mi-chemin de GDI et de Direct3D. C’est dire si Direct2D se trouve à la croisée de deux chemins très différents.
Direct2D est une API Win32, écrite en code natif bien sûr. Elle est disponible sous la forme d’objets COM. Microsoft a choisi de faire renaitre les objets COM 20 ans après la création de ce modèle objet. Même le successeur de Win32, WinRT sous Windows 8, est composé d’objets COM. C’est dire que la technologie COM est bien vivante sous Windows.
Concrètement, on pourra utiliser les objets Direct2D COM de deux manières :
- en manipulant directement les objets COM (ou en utilisant la librairie de templates ATL). Cela permet une utilisation efficace et de bas niveau. Une documentation d’ATL en français est disponible, mais elle date un peu. Il est préférable de se reporter sur une documentation en anglais d’ATL. Mais cette utilisation de bas niveau est fastidieuse et verbeuse. Si on peut s’en passer, on évitera.
- en utilisant les classes MFC qui encapsulent les objets COM Direct2D dans des objets C++, plus faciles et agréables à utiliser. Ouf ! Pas besoin d’utiliser l’horrible code COM/ATL.
Et il n’y a pas d’autre choix ! Soit vous utilisez directement les objets COM Direct2D, soit vous utilisez les MFC.
Pour vous aider à choisir entre ATL et MFC, voici un exemple code Direct2D qui crée un objet décrivant une police de caractères.
D’abord la version MFC :
// MFC : crée un objet TextFormat
CD2DTextFormat textFormat(GetRenderTarget(), L"Verdana", 50);
Puis la version COM :
// Crée un objet COM TextFormat
IDWriteTextFormat *pTextFormat = NULL;
HRESULT hr = m_pDWriteFactory->CreateTextFormat(L"Verdana", NULL,
DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
50, L"", &pTextFormat);
if (hr != S_OK)
return; // erreur
pTextFormat->Release();
En utilisant directement COM, on devra écrire du code plus verbeux, gérer les codes HRESULT, et surtout se préoccuper du comptage des références sur les pointeurs. En revanche, les MFC permettent d’utiliser une programmation plus objet notamment avec la technique RAII de C++.
Comme d’habitude au royaume des classes MFC, les classes Direct2D sont d’assez bas niveau. On pourra regretter de ne pas avoir d’interface de très haut niveau. Par contre, cela garanti une performance optimale, avec du code “près du métal”.
Note 1 : Les classes MFC encapsulant Direct2D sont une nouveauté du Service Pack 1 de Visual Studio 2010. Si vous avez Visual Studio 2010, vérifiez que vous avez bien installé le Service Pack 1 de Visual Studio 2010 pour utiliser Direct2D avec les MFC.
Note 2 : La version gratuite Visual C++ Express 2010 ne dispose pas des classes MFC. Avec Visual C++ Express, il n’y a pas d’autre choix que d’utiliser COM directement (bon courage !).
L’utilisation de Direct2D
L’utilisation de Direct2D doit se faire en trois étapes :
1 – Au début du programme : initialisation de COM, de Direct2D et création d’objets Direct2D de base :
- Les premiers objets sont des objets “Fabrique” ou “Factory”. Ils vont servir à créer tous les autres objets Direct2D. Ils sont de type ID2D1Factory ou IDWriteFactory.
- Puis il faut créer un contexte d’affichage ou RenderTarget. A la différence de GDI où le contexte d’affichage (Device Context HDC) est créé juste avant de dessiner, avec Direct2D on crée un objet RenderTarget au début du programme. Et comme GDI possède différents types de Device Context (Screen DC, Printer DC, Memory DC, …), Direct2D propose différents types de RenderTarget. Le principal type est HwndRenderTarget pour dessiner dans une fenêtre. D’autres types de RenderTarget permettent de dessiner dans un bitmap, dans une brosse, en mémoire, dans une surface Direct3D, etc.
Ce processus d’initialisation se résume au simple appel d’une fonction avec les MFC. Mais si l’on désire utiliser COM directement sans utiliser les MFC, il faut écrire à la main plusieurs dizaines de lignes de code. Les programmeurs DirectX/Direct3D connaissent bien le calvaire de l’initialisation de Direct3D !
2 – Création des ressources et objets graphiques à partir de l’objet fabrique. C’est lors de cette deuxième étape que l’on crée les formes, brosses, images, polices, couleurs, gradients, … (shapes, brushes, bitmaps, …) dont on aura besoin ultérieurement pour dessiner.
Cette manière de créer les ressources à l’avance vient de DirectX. Cela permet de factoriser le temps de création des objets graphiques, et d’optimiser le temps de dessin.
3 – Dessiner des formes, textes, images, etc. Lors de cette étape, on utilise les ressources crées précédemment, avec l’objet RenderTarget. Cette étape doit être encadrée d’appels à BeginDraw et EndDraw, un peu comme en GDI on devait utiliser BeginPaint et EndPaint :
- BeginDraw()
- Utilisation des ressources graphiques pour dessiner
- EndDraw()
A noter ici une différence importante entre GDI et Direct2D. Direct2D utilise la technique du dessin hors écran (back-buffer ou double-buffer), comme Direct3D. Les fonctions de dessin doivent être encadrées par BeginDraw et EndDraw, et rien n’apparait à l’écran avant l’appel à EndDraw. Les fonctions de dessin modifient une zone graphique située en mémoire, hors de l’écran. Cette zone est affichée instantanément lors de l’appel à EndDraw. Cette technique avancée permet d’éviter le clignotement de l’affichage, surtout avec des animations, et donne un affichage beaucoup plus fluide qu’avec GDI.
Du code MFC !
Ecrivons un peu de code ! Comme je n’aime pas trop me frotter à COM directement, créons une application MFC standard avec l’assistant AppWizard de Visual Studio 2010 (menu Fichier/Nouveau/Projet), et appelons-la comme il se doit HelloWorld.

Le saviez-vous ? Dennis Ritchie, co-inventeur du langage C, décédé le 12 octobre dernier, est aussi l’initiateur de la tradition d’utiliser “Hello, world” comme message de test pour un programme simple.
En utilisant les paramètres par défaut de l’assistant de création d’application MFC “AppWizard”, nous obtenons un projet C++ qui, lorsqu’il est compilé, donne à peu près l’application suivante :

Cette application ne contient aucun code Direct2D. Elle utilise GDI pour afficher l’intégralité de son contenu. Nous allons modifier cela pour que l’intérieur de la fenêtre HelloWorld1 soit dessiné avec Direct2D. Cette fenêtre correspond à la class CHelloWorldView dans notre projet, dans le fichier HelloWorldView.cpp.
Initialiser Direct2D
La première chose à faire est d’initialiser Direct2D. Nous l’avons vu un peu plus haut, l’initialisation de Direct2D est la première tâche à effectuer. Cette étape est grandement facilitée par les MFC puisqu’il suffit simplement d’appeler la méthode CWnd::EnableD2DSupport(). Comme cette méthode appartient à la classe CWnd, il faut l’appeler dans une fenêtre. Le constructeur de la classe CHelloWorldView est parfait pour cela :
CHelloWorldView::CHelloWorldView()
{
// Initialise Direct2D
EnableD2DSupport();
}
EnableD2DSupport effectue deux choses :
- Initialiser COM et Direct2D. Cette initialisation n’est effectuée qu’une seule fois, même si EnableD2DSupport est appelé plus d’une fois. Ainsi il n’est pas gênant d’appeler EnableD2DSupport dans le constructeur d’une fenêtre, même s’il est possible de créer plusieurs instances de cette fenêtre.
- Créer un objet RenderTarget pour la fenêtre associée. L’objet RenderTarget est utilisé par Direct2D pour toutes les primitives de dessin. L’objet RenderTarget est à Direct2D ce que le Device Context (HDC) est à GDI. Toutefois, un objet RenderTarget est généralement créé une seule fois, lors de la création de la fenêtre.
Note : Si l’on n’utilise pas les MFC mais COM directement, il faudra écrire les quelques dizaines de lignes de code correspondantes à la main.
Si l’on compile et exécute l’application après cette simple modification, on s’aperçoit que le contenu de la fenêtre CHelloWorldView est maintenant noir. Il s’est passé quelque chose !

Le fait de créer un objet RenderTarget et de l’avoir associé à la fenêtre a pour conséquence de confier son rendu à Direct2D plutôt qu’à GDI.
La taille de l’objet RenderTarget créé correspond à toute la zone client de la fenêtre HelloWorld1. C’est ce que les MFC font par défaut. Mais on aurait pu, à la main, créer un objet RenderTarget plus petit si l’on avait souhaité mélanger rendu GDI et rendu Direct2D dans la même fenêtre.
Ajoutons la ligne suivante en haut du fichier CHelloWorldView.cpp, cela facilitera l’écriture du code :
using namespace D2D1;
WM_PAINT et AFX_WM_DRAW2D
La chose à savoir maintenant est qu’avec Direct2D, il faut toujours répondre aux évènements WM_PAINT, comme avec ce bon vieux GDI. Evidemment, on n’appellera pas BeginPaint/EndPaint, mais BeginDraw/EndDraw à la place.
Dans le gestionnaire du message WM_PAINT, nous pourrions récupérer l’objet RenderTarget créé précédemment, et l’utiliser pour dessiner, encadré par des appels à BeginDraw/EndDraw :
void OnPaint()
{
// Récupère la taille de la zone à dessiner
D2D1_SIZE_F renderTargetSize = m_pRenderTarget->GetSize();
m_pRenderTarget->BeginDraw();
// utilise m_pRenderTarget pour dessiner
// ...
hr = m_pRenderTarget->EndDraw();
ValidateRect(hwnd, NULL);
}
Sans oublier d’appeler ValidateRect, qui indique que la zone a dessiner a bien été traitée.
En fait, le code ci-dessus correspond à la manière d’utiliser Direct2D sans les MFC.
Si l’on utilise les MFC, on a droit à un traitement de faveur avec du code plus simple, même si, sous le capot, les MFC font quelque chose de totalement équivalent.
Les MFC ont défini le message AFX_WM_DRAW2D qui est envoyé à la fenêtre à la place du message WM_PAINT si la fenêtre possède un objet RenderTarget.
Autrement dit, si l’on veut dessiner avec Direct2D dans notre fenêtre MFC, nous utiliserons le message AFX_WM_DRAW2D à la place du message WM_PAINT. AFX_WM_DRAW2D est un message enregistré de Windows (registered message).
Ajoutons donc dans la classe CHelloWorldView un gestionnaire d’évènement pour le message AFX_WM_DRAW2D. Pour cela, utilisons l’assistant ClassWizard de Visual Studio (menu Projet / Class Wizard ou bien Ctrl+Maj+X). Cet assistant permet d’ajouter facilement des gestionnaires d’évènements.
Pour ajouter le gestionnaire d’évènement OnDraw2D correspondant au message AFX_WM_DRAW2D, il faut sélectionner la classe CHelloWorldView, cliquer sur l’onglet Messages, puis sur le bouton Ajouter un message, et saisir les noms de l’évènement et de la méthode. Ne pas oublier de cocher la case qui indique que l’évènement est un registered message.

L’assistant génère automatiquement le gestionnaire d’évènement CHelloWorldView::OnDraw2D. On peut y ajouter une ligne pour effacer le fond de la fenêtre :
// Gestionnaire d'évènement AFX_WM_DRAW2D
afx_msg LRESULT CHelloWorldView::OnDraw2d(WPARAM wParam, LPARAM lParam)
{
CHwndRenderTarget* pRenderTarget = (CHwndRenderTarget*)lParam;
pRenderTarget->Clear(ColorF(ColorF::Beige));
return TRUE;
}
Le contexte de dessin (l’objet CHwndRenderTarget de la fenêtre) est passé en paramètre. On peut l’utiliser pour dessiner. Pour l’instant nous effacerons simplement le contenu de la fenêtre en beige grâce à la méthode CRenderTarget::Clear qui à son tour appelle la méthode COM ID2D1RenderTarget::Clear.
Le gestionnaire d’évènement OnDraw2D retourne TRUE pour indiquer que la fenêtre a été complètement redessinée. La zone de dessin sera validée. En retournant FALSE, la fenêtre est considérée comme n’étant pas redessinée, et un message WM_PAINT classique devra être traité pour dessiner dans la fenêtre.
Il peut être intéressant d’utiliser à la fois OnPaint/WM_PAINT classique et OnDraw2D/AFX_WM_DRAW2D. Si l’on souhaite créer une application qui s’exécute aussi sous Windows XP, on voudra assurer la compatibilité en utilisant GDI et WM_PAINT. Comme les MFC ne sont pas liées statiquement avec la DLL Direct2D mais utilisent LoadLibrary/GetProcAddress, notre application qui utilise Direct2D sous Vista ou Windows 7 peut aussi se lancer sous Windows XP. Même si dans ce dernier cas bien sûr, on ne pourra pas utiliser Direct2D pour afficher le contenu d’une fenêtre.
Hello, world, le texte
L’affichage du texte ne fait pas à proprement dit partie de Direct2D, mais de DirectWrite. DirectWrite est le compagnon de Direct2D qui gère les polices de caractères et l’affichage de texte. Si le nom des objets COM de Direct2D commence par ID2D1, le nom des objets COM de DirectWrite commence par IDWrite. Microsoft a décidé de séparer arbitrairement DirectWrite et Direct2D, mais ils sont complémentaires et fonctionnent de la même façon. D’ailleurs le nom des classes MFC gérant DirectWrite commencent par CD2D, comme les classes Direct2D.
Comme nous l’avons vu plus haut, l’affichage avec Direct2D s’effectue en deux étapes ;
- la création des ressources graphiques
- le rendu des objets graphiques dans un RenderTarget
L’affichage d’un texte ne déroge pas à la règle. Nous avons besoin de créer deux objets pour afficher un texte :
- une police de caractère, ou plutôt un objet TextFormat (interface COM IDWriteTextFormat, ou objet MFC CD2DTextFormat). TextFormat définit les attributs de police de caractères (nom, taille, style, …), mais aussi la manière de formater le texte (alignement, justification, interligne, retour à la ligne, etc).
- une brosse (brush) de type ID2D1SolidColorBrush ou CD2DSolidColorBrush qui indiquera la couleur du texte à afficher.
Direct2D possède différents types de brosses. Nous utiliserons une couleur unie (ID2D1SolidColorBrush ou CD2DSolidColorBrush), mais il existe des dégradés linéaires ou radiants, et des brosses images :

A vous de modifier le code pour utiliser les autres types de brush !
Il faut ajouter la déclaration des deux ressources graphiques dans le fichier HelloWorldView.h :
protected:
CD2DTextFormat* m_pTextFormat;
CD2DSolidColorBrush* m_pBlueBrush;
Puis, ajouter le code qui crée ces deux ressources dans le constructeur de la fenêtre CHelloWorldView, dans HelloWorldView.cpp :
using namespace D2D1;
CHelloWorldView::CHelloWorldView()
{
// Initialise Direct2D
EnableD2DSupport();
// Crée les ressources graphiques D2D
m_pBlueBrush = new CD2DSolidColorBrush(GetRenderTarget(),
ColorF(ColorF::RoyalBlue));
m_pTextFormat = new CD2DTextFormat(GetRenderTarget(), _T("Gabriola"), 50);
m_pTextFormat->Get()->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
m_pTextFormat->Get()->
SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
}
Après avoir initialisé Direct2D pour la fenêtre CHelloWorldView, on crée un objet Brush de couleur bleue, puis un objet TextFormat. m_pTextFormat contient la description de la police de caractères à utiliser pour l’affichage du texte, ainsi que des attributs de formatage de texte. Ici, le texte est centré verticalement et horizontalement dans la zone où il est affiché.
Il ne reste plus qu’à afficher “Hello, World” dans le gestionnaire d’évènement OnDraw2D de la classe CHelloWorldView, en utilisant les objets Brush et TextFormat créés :
// Gestionnaire d'évènement AFX_WM_DRAW2D
afx_msg LRESULT CHelloWorldView::OnDraw2d(WPARAM wParam, LPARAM lParam)
{
CHwndRenderTarget* pRenderTarget = (CHwndRenderTarget*)lParam;
ASSERT_VALID(pRenderTarget);
// Efface le fond de la fenêtre
pRenderTarget->Clear(ColorF(ColorF::Beige));
// Dessine le texte
CRect rect;
GetClientRect(rect);
pRenderTarget->DrawText(_T("Hello, World!"), rect,
m_pBlueBrush, m_pTextFormat);
return TRUE;
}
Le résultat obtenu à l’exécution est quand même extraordinaire, non ?

Télécharger le code
L’exemple de code montré ici est très simple, mais la description du fonctionnement des MFC sous le capot est riche d’enseignement.
Le code complet de la solution est téléchargeable sur la galerie de code de MSDN, sur ce lien : http://code.msdn.microsoft.com/MFC-Direct2D-Hello-World-9aa6ae00
Le code et la page de téléchargement sont disponible en français ou en anglais. Vous pouvez choisir la langue sur le site MSDN en haut à droite de la page.
Pour aller plus loin…
D’autres articles viendront compléter ce Hello World. En attendant, pour aller plus loin, il existe quelques ressources sur le web :