dwrite-hlsl
dwrite-hlsl copied to clipboard
Blend text in a HLSL shader and have it look like native DirectWrite
dwrite-hlsl
This project demonstrates how to blend text in a HLSL shader and have it look like native DirectWrite.
It implements both, ClearType (D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE) and grayscale (D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE) anti-aliasing.
License
This project is an extract of Windows Terminal. See LICENSE.
Missing features
- Emojis
Emojis are luckily very simple to draw and I'll add support later:- Draw them with grayscale antialiasing (and not ClearType).
- Don't use linear gamma space (meaning:
DWrite_GetRenderParams). Instead callSetTextRenderingParams(nullptr)before using the rendering target, to reset it to the regular, gamma corrected parameters. - Do a simple premultiplied alpha blend with the Emoji's RGBA values on your background color in your shader.
- This demo doesn't actually use a glyph atlas and only shows the "blending" part of the algorithm.
How to use this
- dwrite.hlsl contains all of the shader functions relevant for grayscale-antialiased alpha blending.
DWrite_GetGrayScaleCorrectedAlphais the entrypoint function that you need to call in your shader. - dwrite.cpp contains support functions which are required to fill out the parameters for
DWrite_GetGrayScaleCorrectedAlpha. - Draw your glyphs with DirectWrite into your texture atlas however you like them. Basically there are 3 ways to do this:
-
ID2D1RenderTarget::DrawTextLayout
Direct2D provides various different kinds of render targets. It handles font fallback, provides you with metrics, etc. and is simple to use. However it's not particularly configurable, not the most performant solution and uses extra GPU/CPU memory for Direct2D's internal glyph atlas. This demo application uses this approach. -
Implement your own custom
IDWriteTextRenderer
Personally I'm not sure about the value of this approach. I feel like it's the worst of both worlds, as it still ties you into the Direct2D ecosystem and allows you to only marginally improve performance, while simultaneously being very difficult to implement. I'd not suggest anyone to use this. -
IDWriteGlyphRunAnalysis::CreateAlphaTexture
This is the most performant and also most complex approach, but it's what every serious DirectWrite application uses, including libraries like skia. It straight up yields rasterized glyphs and allows you to do your own anti-aliasing. In order to call this function you need to segment and layout your input text yourself, by calling, as far as I know, at a minimum in that order:IDWriteFontFallback::MapCharactersIDWriteTextAnalyzer::AnalyzeBidiIDWriteTextAnalyzer::AnalyzeScriptIDWriteTextAnalyzer1::GetTextComplexity(optionally; allows you to skipGetGlyphs)IDWriteTextAnalyzer::GetGlyphsIDWriteTextAnalyzer::GetGlyphPlacements
Grayscale antialiasing using
IDWriteGlyphRunAnalysis::CreateAlphaTexturerequires you to render the glyph at 4 times the actual pixel size in both directions (oversampling). Afterwards you can compute the alpha values of the glyph as a percentage of how many of the 16 oversampled pixels are present for each actual pixel. As we need to create the glyph texture in linear gamma space, the implementation can use a linear gradient for the alpha value. The complexity of calling all the text segmentation/layouting functions however isn't that great. But it's doable in a few hundred lines of code and I plan to add a custom glyph atlas implementation based on that in the future. In the meantime I'm using an approach based onID2D1RenderTarget::DrawTextLayoutmyself.
-