One of the more interesting aspects of Direct2D is that it cooperates with another API, DirectWrite in order to handle text. This is unlike other monolithic APIs, such as GDI, GDI+ or WPF. This post explores why these components are separated in this way, as well as differences and similarities between them.
One of the lessons learned by the DirectX team is that many applications cannot move completely over to a new graphics API in one release. This might be because they need to support plug-ins that still take the older interfaces, because the application itself is too large to port over to a new API in one release or because some part of the newer API is really desirable but the older API is working well enough for other parts of the application. Providing a separate component dedicated to text allows a component to adopt DirectWrite while still leaving their application GDI or GDI+ based. Having two distinct but highly interoperable APIs allows customers to only pay for the IO and working set of the APIs they use in their application.
As applications have evolved, their text processing requirements have grown increasingly complex. Initially, text was generally confined to statically laid-out UI, and the text was rendered within a well-defined box, such as a button. As applications had to be shipped in more languages this approach became more difficult to sustain since both the width and height of the translated text can vary substantially between different languages. To adapt, applications started having to dynamically lay out their UI depending on the actual rendered size of the text, rather than the other way around.
To help applications with this task, DirectWrite provides the IDWriteTextLayout interface. This API allows an application to specify a piece of text with complex characteristics such as: different fonts and font sizes, underlines, strikethroughs, bi-directional text, effects, ellipsis and even embedded non-glyph characters (such as a bitmap emoticon or an icon). The application can then change various characteristics of the text as it iteratively determines its UI layout. The layout can either position the glyphs ideally based on their widths (as WPF does), or, it can snap the glyphs to the nearest pixel positions (as GDI does). As well as obtaining text measurements, the application can hit test various parts of the text. For example, it might wish to know that a hyperlink in the text has been clicked on.
The text layout interface is completely decoupled from the input technology and the rendering API the application chooses to use. As shown below:
This separation is accomplished by allowing an application to implement a callback interface (IDWriteTextRenderer) and then target a particular glyph rendering API to render the laid-out glyphs. For GDI, this would be ExtTextOut, for Direct2D ID2D1RenderTarget::DrawGlyphRun and for DirectWrite IDWriteBitmapRenderTarget::DrawGlyphRun. Conveniently, DrawGlyphRun in both Direct2D and DirectWrite have exactly compatible parameters to the DrawGlyphRun method that the application implements on IDWriteTextRenderer.
Following a similar separation, text specific features such as font enumeration and management, glyph analysis, etc. are handled by DirectWrite rather than Direct2D. The DirectWrite objects are accepted directly by Direct2D. To help existing GDI applications to take advantage of DirectWrite, it provides the IDWriteGDIInteropMethod interface with methods to:
· Create a DirectWrite Font from a GDI Logical Font (CreateFontFromLOGFONT).
· Convert from a DirectWrite Font Face to a GDI Logical Font (ConvertFontFacetoLOGFONT).
· Retrieve the Direct Write Font Face from the one currently selected into an HDC. (CreateFontFaceFromHDC).
· Create a DirectWrite bitmap render target in system memory (CreateBitmapRenderTarget).
Both Direct2D and DirectWrite have a very strict separation in the API between Text and Glyphs. Text is a set of Unicode code points (characters), with various stylistic modifiers (fonts, weights, underlines, strikethroughs etc.) that is laid out within a rectangle. A glyph, in contrast, is a particular index into a particular font file. A glyph defines a set of curves which can be rendered, but, it doesn’t have any textual meaning. There is potentially a many to many mapping between glyphs and characters. A sequence of glyphs that come from the same Font Face and that are laid-out sequentially on a baseline is called a GlyphRun. Both DirectWrite and Direct2D call their most precise glyph rendering API DrawGlyphRun and they have very similar signatures. We’ll show Direct2D’s:
__in CONST DWRITE_GLYPH_RUN *glyphRun,
__in ID2D1Brush *foregroundBrush,
DWRITE_MEASURING_MODE measuringMode = DWRITE_MEASURING_MODE_NATURAL
Each glyph run starts at an origin and is placed on a line starting from this origin. The glyphs are modified by the current world transform and the selected text rendering settings on the associated render target. This API is generally called directly only by APIs that do their own layout (For example a Word Processor) or by an application that has implemented the IDWriteTextRenderer interface.
Architecturally, it would be ideal for Direct2D and DirectWrite to have a clear distinction of roles. Direct2D would provide rendering services (DrawGlyphRun) and DirectWrite would provide layout, fonts and other text services. However, during the design of Direct2D, we very quickly realized that asking an application to implement the text rendering interface to replace the equivalent of USER32’s DrawText API would probably be too large a burden initially. Conversely, applications will want a way to do basic glyph rendering through DirectWrite without pulling in all of D2D. So, the relationship is a little more complex, and DirectWrite provides for glyph rendering through the IDWriteBitmapRenderTarget::DrawGlyphRun API. Similarly Direct2D provides some APIs that accept text rather than Glyphs through ID2D1RenderTarget::DrawTextLayout and ID2D1RenderTarget::DrawText. An application’s usage of text tends to start simple: put OK or Cancel on a fixed-layout button, for example. But, over time it becomes more complex as internationalization and other features are added. Eventually many applications will reach the point where using DirectWrite’s text layout objects and implementing the text renderer will become necessary.
For this reason, Direct2D provides layered APIs that allow an application to start simply and grow more sophisticated without having to back-track or abandon their working code. A simplified view is shown below:
This is the simplest of the APIs to use. It takes a Unicode string, a foreground brush, a single format object and a destination rectangle. It will lay-out and render the entire string within the layout rectangle, and optionally clip it. This is useful when putting a simple piece of text within a piece of fixed-layout UI.
If the application would like to start measuring and arranging the text and other UI elements, or, if it wants to support multiple fonts, styles, underlines and strikethroughs; it can create a text layout object using DirectWrite. Direct2D provides the DrawTextLayout API that directly accepts this object and renders the text at a given point. (The width and height are provided by the layout object). As well as implementing all of the expected text layout features, Direct2D will interpret any effect object as a brush and apply that brush to the selected range of glyphs. It will also call any inline objects, which allows an application to insert non-glyph “characters” (icons) into the text if it wishes. Another advantage of using a text layout object is that the glyph positions are cached in it. Hence, there is a large performance gain to be had by reusing the same layout object for multiple draw calls and avoiding recalculating the glyph positions for each call. This capability is not present for USER32’s DrawText.
Finally, the application can implement the IDWriteTextRenderer interface themselves and call DrawGlyphRun and FillRectangle themselves (or any other rendering API). All of the existing interaction with the Text Layout object will remain unchanged.
DirectWrite allows an existing GDI application to obtain advanced text rendering features such as:
· Sub-pixel ClearType. This allows an application to place glyphs on sub-pixel positions to allow for both sharp glyph rendering as well as even glyph layout.
· Y-direction anti-aliasing. This allows smoother rendering of curves on larger glyphs.
The application can obtain these without having to pull in other DirectX APIs through the IDWriteBitmapRenderTarget API. This allows glyphs to be rendered with a solid color to a memory DC. An application moving to Direct2D will also obtain the following features:
· Hardware acceleration.
· The ability to fill text with an arbitrary brush. (Radial Gradients, Linear Gradients and Bitmaps).
· More support for layering and clipping through the PushAxisAlignedClip, PushLayer and CreateCompatibleRenderTarget APIs.
· The ability to support Grayscale text rendering. This correctly populates the destination alpha channel according to both the text brush opacity and the anti-aliasing of the text.
In order to efficiently support hardware acceleration, Direct2D uses a slightly different approximation to Gamma correction called “alpha correction”. This doesn’t require Direct2D to inspect the render target color pixel when rendering text. Although this could be done entirely on the GPU by reading from the render target to another texture, this would prevent Direct2D from batching its calls to Direct3D efficiently since a batch would not be able to span any render target read-back. (See the MSDN article by Kenny Kerr to obtain more information about Direct2D batching).
We hope that this article helps you understand the differences and similarities between Direct2D and DirectWrite as well as the architectural motivations for providing these as separate technologies. For more information please see:
· The Direct2D reference on MSDN
· The DirectWrite reference on MSDN