1# Text Rendering
2
3This document describes the details of how WebRender renders text, particularly the blending stage of text rendering.
4We will go into grayscale text blending, subpixel text blending, and "subpixel text with background color" blending.
5
6### Prerequisites
7
8The description below assumes you're familiar with regular rgba compositing, operator over,
9and the concept of premultiplied alpha.
10
11### Not covered in this document
12
13We are going to treat the origin of the text mask as a black box.
14We're also going to assume we can blend text in the device color space and will not go into the gamma correction and linear pre-blending that happens in some of the backends that produce the text masks.
15
16## Grayscale Text Blending
17
18Grayscale text blending is the simplest form of text blending. Our blending function has three inputs:
19
20 - The text color, as a premultiplied rgba color.
21 - The text mask, as a single-channel alpha texture.
22 - The existing contents of the framebuffer that we're rendering to, the "destination". This is also a premultiplied rgba buffer.
23
24Note: The word "grayscale" here does *not* mean that we can only draw gray text.
25It means that the mask only has a single alpha value per pixel, so we can visualize
26the mask in our minds as a grayscale image.
27
28### Deriving the math
29
30We want to mask our text color using the single-channel mask, and composite that to the destination.
31This compositing step uses operator "over", just like regular compositing of rgba images.
32
33I'll be using GLSL syntax to describe the blend equations, but please consider most of the code below pseudocode.
34
35We can express the blending described above as the following blend equation:
36
37```glsl
38vec4 textblend(vec4 text_color, vec4 mask, vec4 dest) {
39  return over(in(text_color, mask), dest);
40}
41```
42
43with `over` being the blend function for (premultiplied) operator "over":
44
45```glsl
46vec4 over(vec4 src, vec4 dest) {
47  return src + (1.0 - src.a) * dest;
48}
49```
50
51and `in` being the blend function for (premultiplied) operator "in", i.e. the masking operator:
52
53```glsl
54vec4 in(vec4 src, vec4 mask) {
55  return src * mask.a;
56}
57```
58
59So the complete blending function is:
60
61```glsl
62result.r = text_color.r * mask.a + (1.0 - text_color.a * mask.a) * dest.r;
63result.g = text_color.g * mask.a + (1.0 - text_color.a * mask.a) * dest.g;
64result.b = text_color.b * mask.a + (1.0 - text_color.a * mask.a) * dest.b;
65result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.a;
66```
67
68### Rendering this with OpenGL
69
70In general, a fragment shader does not have access to the destination.
71So the full blend equation needs to be expressed in a way that the shader only computes values that are independent of the destination,
72and the parts of the equation that use the destination values need to be applied by the OpenGL blend pipeline itself.
73The OpenGL blend pipeline can be tweaked using the functions `glBlendEquation` and `glBlendFunc`.
74
75In our example, the fragment shader can output just `text_color * mask.a`:
76
77```glsl
78  oFragColor = text_color * mask.a;
79```
80
81and the OpenGL blend pipeline can be configured like so:
82
83```rust
84    pub fn set_blend_mode_premultiplied_alpha(&self) {
85        self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
86        self.gl.blend_equation(gl::FUNC_ADD);
87    }
88```
89
90This results in an overall blend equation of
91
92```
93result.r = 1 * oFragColor.r + (1 - oFragColor.a) * dest.r;
94           ^                ^  ^^^^^^^^^^^^^^^^^
95           |                |         |
96           +--gl::ONE       |         +-- gl::ONE_MINUS_SRC_ALPHA
97                            |
98                            +-- gl::FUNC_ADD
99
100         = 1 * (text_color.r * mask.a) + (1 - (text_color.a * mask.a)) * dest.r
101         = text_color.r * mask.a + (1 - text_color.a * mask.a) * dest.r
102```
103
104which is exactly what we wanted.
105
106### Differences to the actual WebRender code
107
108There are two minor differences between the shader code above and the actual code in the text run shader in WebRender:
109
110```glsl
111oFragColor = text_color * mask.a;    // (shown above)
112// vs.
113oFragColor = vColor * mask * alpha;  // (actual webrender code)
114```
115
116`vColor` is set to the text color. The differences are:
117
118 - WebRender multiplies with all components of `mask` instead of just with `mask.a`.
119   However, our font rasterization code fills the rgb values of `mask` with the value of `mask.a`,
120   so this is completely equivalent.
121 - WebRender applies another alpha to the text. This is coming from the clip.
122   You can think of this alpha to be a pre-adjustment of the text color for that pixel, or as an
123   additional mask that gets applied to the mask.
124
125## Subpixel Text Blending
126
127Now that we have the blend equation for single-channel text blending, we can look at subpixel text blending.
128
129The main difference between subpixel text blending and grayscale text blending is the fact that,
130for subpixel text, the text mask contains a separate alpha value for each color component.
131
132### Component alpha
133
134Regular painting uses four values per pixel: three color values, and one alpha value. The alpha value applies to all components of the pixel equally.
135
136Imagine for a second a world in which you have *three alpha values per pixel*, one for each color component.
137
138 - Old world: Each pixel has four values: `color.r`, `color.g`, `color.b`, and `color.a`.
139 - New world: Each pixel has *six* values: `color.r`, `color.a_r`, `color.g`, `color.a_g`, `color.b`, and `color.a_b`.
140
141In such a world we can define a component-alpha-aware operator "over":
142
143```glsl
144vec6 over_comp(vec6 src, vec6 dest) {
145  vec6 result;
146  result.r = src.r + (1.0 - src.a_r) * dest.r;
147  result.g = src.g + (1.0 - src.a_g) * dest.g;
148  result.b = src.b + (1.0 - src.a_b) * dest.b;
149  result.a_r = src.a_r + (1.0 - src.a_r) * dest.a_r;
150  result.a_g = src.a_g + (1.0 - src.a_g) * dest.a_g;
151  result.a_b = src.a_b + (1.0 - src.a_b) * dest.a_b;
152  return result;
153}
154```
155
156and a component-alpha-aware operator "in":
157
158```glsl
159vec6 in_comp(vec6 src, vec6 mask) {
160  vec6 result;
161  result.r = src.r * mask.a_r;
162  result.g = src.g * mask.a_g;
163  result.b = src.b * mask.a_b;
164  result.a_r = src.a_r * mask.a_r;
165  result.a_g = src.a_g * mask.a_g;
166  result.a_b = src.a_b * mask.a_b;
167  return result;
168}
169```
170
171and even a component-alpha-aware version of `textblend`:
172
173```glsl
174vec6 textblend_comp(vec6 text_color, vec6 mask, vec6 dest) {
175  return over_comp(in_comp(text_color, mask), dest);
176}
177```
178
179This results in the following set of equations:
180
181```glsl
182result.r = text_color.r * mask.a_r + (1.0 - text_color.a_r * mask.a_r) * dest.r;
183result.g = text_color.g * mask.a_g + (1.0 - text_color.a_g * mask.a_g) * dest.g;
184result.b = text_color.b * mask.a_b + (1.0 - text_color.a_b * mask.a_b) * dest.b;
185result.a_r = text_color.a_r * mask.a_r + (1.0 - text_color.a_r * mask.a_r) * dest.a_r;
186result.a_g = text_color.a_g * mask.a_g + (1.0 - text_color.a_g * mask.a_g) * dest.a_g;
187result.a_b = text_color.a_b * mask.a_b + (1.0 - text_color.a_b * mask.a_b) * dest.a_b;
188```
189
190### Back to the real world
191
192If we want to transfer the component alpha blend equation into the real world, we need to make a few small changes:
193
194 - Our text color only needs one alpha value.
195   So we'll replace all instances of `text_color.a_r/g/b` with `text_color.a`.
196 - We're currently not making use of the mask's `r`, `g` and `b` values, only of the `a_r`, `a_g` and `a_b` values.
197   So in the real world, we can use the rgb channels of `mask` to store those component alphas and
198   replace `mask.a_r/g/b` with `mask.r/g/b`.
199
200These two changes give us:
201
202```glsl
203result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r;
204result.g = text_color.g * mask.g + (1.0 - text_color.a * mask.g) * dest.g;
205result.b = text_color.b * mask.b + (1.0 - text_color.a * mask.b) * dest.b;
206result.a_r = text_color.a * mask.r + (1.0 - text_color.a * mask.r) * dest.a_r;
207result.a_g = text_color.a * mask.g + (1.0 - text_color.a * mask.g) * dest.a_g;
208result.a_b = text_color.a * mask.b + (1.0 - text_color.a * mask.b) * dest.a_b;
209```
210
211There's a third change we need to make:
212
213 - We're rendering to a destination surface that only has one alpha channel instead of three.
214   So `dest.a_r/g/b` and `result.a_r/g/b` will need to become `dest.a` and `result.a`.
215
216This creates a problem: We're currently assigning different values to `result.a_r`, `result.a_g` and `result.a_b`.
217Which of them should we use to compute `result.a`?
218
219This question does not have an answer. One alpha value per pixel is simply not sufficient
220to express the same information as three alpha values.
221
222However, see what happens if the destination is already opaque:
223
224We have `dest.a_r == 1`, `dest.a_g == 1`, and `dest.a_b == 1`.
225
226```
227result.a_r = text_color.a * mask.r + (1 - text_color.a * mask.r) * dest.a_r
228           = text_color.a * mask.r + (1 - text_color.a * mask.r) * 1
229           = text_color.a * mask.r + 1 - text_color.a * mask.r
230           = 1
231same for result.a_g and result.a_b
232```
233
234In other words, for opaque destinations, it doesn't matter what which channel of the mask we use when computing `result.a`, the result will always be completely opaque anyways. In WebRender we just pick `mask.g` (or rather,
235have font rasterization set `mask.a` to the value of `mask.g`) because it's as good as any.
236
237The takeaway here is: **Subpixel text blending is only supported for opaque destinations.** Attempting to render subpixel
238text into partially transparent destinations will result in bad alpha values. Or rather, it will result in alpha values which
239are not anticipated by the r, g, and b values in the same pixel, so that subsequent blend operations, which will mix r and a values
240from the same pixel, will produce incorrect colors.
241
242Here's the final subpixel blend function:
243
244```glsl
245vec4 subpixeltextblend(vec4 text_color, vec4 mask, vec4 dest) {
246  vec4 result;
247  result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r;
248  result.g = text_color.g * mask.g + (1.0 - text_color.a * mask.g) * dest.g;
249  result.b = text_color.b * mask.b + (1.0 - text_color.a * mask.b) * dest.b;
250  result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.a;
251  return result;
252}
253```
254
255or for short:
256
257```glsl
258vec4 subpixeltextblend(vec4 text_color, vec4 mask, vec4 dest) {
259  return text_color * mask + (1.0 - text_color.a * mask) * dest;
260}
261```
262
263To recap, here's what we gained and lost by making the transition from the full-component-alpha world to the
264regular rgba world: All colors and textures now only need four values to be represented, we still use a
265component alpha mask, and the results are equivalent to the full-component-alpha result assuming that the
266destination is opaque. We lost the ability to draw to partially transparent destinations.
267
268### Making this work in OpenGL
269
270We have the complete subpixel blend function.
271Now we need to cut it into pieces and mix it with the OpenGL blend pipeline in such a way that
272the fragment shader does not need to know about the destination.
273
274Compare the equation for the red channel and the alpha channel between the two ways of text blending:
275
276```
277  single-channel alpha:
278    result.r = text_color.r * mask.a + (1.0 - text_color.a * mask.a) * dest.r
279    result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.r
280
281  component alpha:
282    result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r
283    result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.r
284```
285
286Notably, in the single-channel alpha case, all three destination color channels are multiplied with the same thing:
287`(1.0 - text_color.a * mask.a)`. This factor also happens to be "one minus `oFragColor.a`".
288So we were able to take advantage of OpenGL's `ONE_MINUS_SRC_ALPHA` blend func.
289
290In the component alpha case, we're not so lucky: Each destination color channel
291is multiplied with a different factor. We can use `ONE_MINUS_SRC_COLOR` instead,
292and output `text_color.a * mask` from our fragment shader.
293But then there's still the problem that the first summand of the computation for `result.r` uses
294`text_color.r * mask.r` and the second summand uses `text_color.a * mask.r`.
295
296There are multiple ways to deal with this. They are:
297
298 1. Making use of `glBlendColor` and the `GL_CONSTANT_COLOR` blend func.
299 2. Using a two-pass method.
300 3. Using "dual source blending".
301
302Let's look at them in order.
303
304#### 1. Subpixel text blending in OpenGL using `glBlendColor`
305
306In this approach we return `text_color.a * mask` from the shader.
307Then we set the blend color to `text_color / text_color.a` and use `GL_CONSTANT_COLOR` as the source blendfunc.
308This results in the following blend equation:
309
310```
311result.r = (text_color.r / text_color.a) * oFragColor.r + (1 - oFragColor.r) * dest.r;
312           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                ^  ^^^^^^^^^^^^^^^^^
313                         |                              |      |
314                         +--gl::CONSTANT_COLOR          |      +-- gl::ONE_MINUS_SRC_COLOR
315                                                        |
316                                                        +-- gl::FUNC_ADD
317
318         = (text_color.r / text_color.a) * (text_color.a * mask.r) + (1 - (text_color.a * mask.r)) * dest.r
319         = text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r
320```
321
322At the very beginning of this document, we defined `text_color` as the *premultiplied* text color.
323So instead of actually doing the calculation `text_color.r / text_color.a` when specifying the blend color,
324we really just want to use the *unpremultiplied* text color in that place.
325That's usually the representation we start with anyway.
326
327#### 2. Two-pass subpixel blending in OpenGL
328
329The `glBlendColor` method has the disadvantage that the text color is part of the OpenGL state.
330So if we want to draw text with different colors, we have two use separate batches / draw calls
331to draw the differently-colored parts of text.
332
333Alternatively, we can use a two-pass method which avoids the need to use the `GL_CONSTANT_COLOR` blend func:
334
335 - The first pass outputs `text_color.a * mask` from the fragment shader and
336   uses `gl::ZERO, gl::ONE_MINUS_SRC_COLOR` as the glBlendFuncs. This achieves:
337
338```
339oFragColor = text_color.a * mask;
340
341result_after_pass0.r = 0 * oFragColor.r + (1 - oFragColor.r) * dest.r
342                     = (1 - text_color.a * mask.r) * dest.r
343
344result_after_pass0.g = 0 * oFragColor.g + (1 - oFragColor.g) * dest.r
345                     = (1 - text_color.a * mask.r) * dest.r
346
347...
348```
349
350 - The second pass outputs `text_color * mask` from the fragment shader and uses
351   `gl::ONE, gl::ONE` as the glBlendFuncs. This results in the correct overall blend equation.
352
353```
354oFragColor = text_color * mask;
355
356result_after_pass1.r
357 = 1 * oFragColor.r + 1 * result_after_pass0.r
358 = text_color.r * mask.r + result_after_pass0.r
359 = text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r
360```
361
362#### 3. Dual source subpixel blending in OpenGL
363
364The third approach is similar to the second approach, but makes use of the [`ARB_blend_func_extended`](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_blend_func_extended.txt) extension
365in order to fold the two passes into one:
366Instead of outputting the two different colors in two separate passes, we output them from the same pass,
367as two separate fragment shader outputs.
368Those outputs can then be treated as two different sources in the blend equation.
369
370## Subpixel Text Rendering to Transparent Destinations with a Background Color Hint
371
372### Motivation
373
374As we've seen in the previous section, subpixel text drawing has the limitation that it only works on opaque destinations.
375
376In other words, if you use the `subpixeltextblend` function to draw something to a transparent surface,
377and then composite that surface onto on opaque background,
378the result will generally be different from drawing the text directly onto the opaque background.
379
380Let's express that inequality in code.
381
382```
383 - vec4 text_color
384 - vec4 mask
385 - vec4 transparency = vec4(0.0, 0.0, 0.0, 0.0)
386 - vec4 background with background.a == 1.0
387
388over(subpixeltextblend(text_color, mask, transparency), background).rgb
389 is, in general, not equal to
390subpixeltextblend(text_color, mask, background).rgb
391```
392
393However, one interesting observation is that if the background is black, the two *are* equal:
394
395```
396vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
397
398over(subpixeltextblend(text_color, mask, transparency), black).r
399 = subpixeltextblend(text_color, mask, transparency).r +
400     (1 - subpixeltextblend(text_color, mask, transparency).a) * black.r
401 = subpixeltextblend(text_color, mask, transparency).r +
402     (1 - subpixeltextblend(text_color, mask, transparency).a) * 0
403 = subpixeltextblend(text_color, mask, transparency).r
404 = text_color.r * mask.r + (1 - text_color.a * mask.r) * transparency.r
405 = text_color.r * mask.r + (1 - text_color.a * mask.r) * 0
406 = text_color.r * mask.r + (1 - text_color.a * mask.r) * black.r
407 = subpixeltextblend(text_color, mask, black).r
408```
409
410So it works out for black backgrounds. The further your *actual* background color gets away from black,
411the more incorrect your result will be.
412
413If it works for black, is there a way to make it work for other colors?
414This is the motivating question for this third way of text blending:
415
416We want to be able to specify an *estimated background color*, and have a blending function
417`vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest)`,
418in such a way that the error we get by using an intermediate surface is somehow in relation
419to the error we made when estimating the background color. In particular, if we estimated
420the background color perfectly, we want the intermediate surface to go unnoticed.
421
422Expressed as code:
423
424```
425over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency), bg_color)
426 should always be equal to
427subpixeltextblend(text_color, mask, bg_color)
428```
429
430This is one of three constraints we'd like `subpixeltextblend_withbgcolor` to satisfy.
431
432The next constraint is the following: If `dest` is already opaque, `subpixeltextblend_withbgcolor`
433should have the same results as `subpixeltextblend`, and the background color hint should be ignored.
434
435```
436 If dest.a == 1.0,
437subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest)
438 should always be equal to
439subpixeltextblend(text_color, mask, dest)
440```
441
442And there's a third condition we'd like it to fulfill:
443In places where the mask is zero, the destination should be unaffected.
444
445```
446subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest)
447 should always be equal to
448dest
449```
450
451### Use cases
452
453The primary use case for such a blend method is text on top of vibrant areas of a window on macOS.
454
455Vibrant backgrounds with behind-window blending are computed by the window server, and they are tinted
456in a color that's based on the chosen vibrancy type.
457
458The window's rgba buffer is transparent in the vibrant areas. Window contents, even text, are drawn onto
459that transparent rgba buffer. Then the window server composites the window onto an opaque backdrop.
460So the results on the screen are computed as follows:
461
462```glsl
463window_buffer_pixel = subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency);
464screen_pixel = over(window_buffer_pixel, window_backdrop);
465```
466
467### Prior art
468
469Apple has implemented such a method of text blending in CoreGraphics, specifically for rendering text onto vibrant backgrounds.
470It's hidden behind the private API `CGContextSetFontSmoothingBackgroundColor` and is called by AppKit internally before
471calling the `-[NSView drawRect:]` method of your `NSVisualEffectView`, with the appropriate font smoothing background color
472for the vibrancy type of that view.
473
474I'm not aware of any public documentation of this way of text blending.
475It seems to be considered an implementation detail by Apple, and is probably hidden by default because it can be a footgun:
476If the font smoothing background color you specify is very different from the actual background that our surface is placed
477on top of, the text will look glitchy.
478
479### Deriving the blending function from first principles
480
481Before we dive into the math, let's repeat our goal once more.
482
483We want to create a blending function of the form
484`vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest)`
485(with `bg_color` being an opaque color)
486which satisfies the following three constraints:
487
488```
489Constraint I:
490  over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency), bg_color)
491   should always be equal to
492  subpixeltextblend(text_color, mask, bg_color)
493
494Constraint II:
495   If dest.a == 1.0,
496  subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest)
497   should always be equal to
498  subpixeltextblend(text_color, mask, dest)
499
500Constraint II:
501  subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest)
502   should always be equal to
503  dest
504```
505
506Constraint I and constraint II are about what happens depending on the destination's alpha.
507In particular: If the destination is completely transparent, we should blend into the
508estimated background color, and if it's completely opaque, we should blend into the destination color.
509In fact, we really want to blend into `over(dest, bg_color)`: we want `bg_color` to be used
510as a backdrop *behind* the current destination. So let's combine constraints I and II into a new
511constraint IV:
512
513```
514Constraint IV:
515  over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color)
516   should always be equal to
517  subpixeltextblend(text_color, mask, over(dest, bg_color))
518```
519
520Let's look at just the left side of that equation and rejiggle it a bit:
521
522```
523over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r
524 = subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r +
525   (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
526
527<=>
528
529over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r -
530(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
531 = subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r
532```
533
534Now insert the right side of constraint IV:
535
536```
537subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r
538 = over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r -
539   (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
540 = subpixeltextblend(text_color, mask, over(dest, bg_color)).r -
541   (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
542```
543
544Our blend function is almost finished. We just need select an alpha for our result.
545Constraints I, II and IV don't really care about the alpha value. But constraint III requires that:
546
547```
548  subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest).a
549   should always be equal to
550  dest.a
551```
552
553so the computation of the alpha value somehow needs to take into account the mask.
554
555Let's say we have an unknown function `make_alpha(text_color.a, mask)` which returns
556a number between 0 and 1 and which is 0 if the mask is entirely zero, and let's defer
557the actual implementation of that function until later.
558
559Now we can define the alpha of our overall function using the `over` function:
560
561```
562subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a
563 := make_alpha(text_color.a, mask) + (1 - make_alpha(text_color.a, mask)) * dest.a
564```
565
566We can plug this in to our previous result:
567
568```
569subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r
570 = subpixeltextblend(text_color, mask, over(dest, bg_color)).r
571   - (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r
572 = subpixeltextblend(text_color, mask, over(dest, bg_color)).r
573   - (1 - (make_alpha(text_color.a, mask) +
574           (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
575 = text_color.r * mask.r + (1 - text_color.a * mask.r) * over(dest, bg_color).r
576   - (1 - (make_alpha(text_color.a, mask)
577           + (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
578 = text_color.r * mask.r
579   + (1 - text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r)
580   - (1 - (make_alpha(text_color.a, mask)
581           + (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
582 = text_color.r * mask.r
583   + (1 - text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r)
584   - (1 - (make_alpha(text_color.a, mask)
585           + (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r
586 = text_color.r * mask.r
587   + (dest.r + (1 - dest.a) * bg_color.r)
588   - (text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r)
589   - (1 - make_alpha(text_color.a, mask)
590      - (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r
591 = text_color.r * mask.r
592   + dest.r + (1 - dest.a) * bg_color.r
593   - text_color.a * mask.r * dest.r
594   - text_color.a * mask.r * (1 - dest.a) * bg_color.r
595   - (1 - make_alpha(text_color.a, mask)
596      - (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r
597 = text_color.r * mask.r
598   + dest.r + (1 - dest.a) * bg_color.r
599   - text_color.a * mask.r * dest.r
600   - text_color.a * mask.r * (1 - dest.a) * bg_color.r
601   - ((1 - make_alpha(text_color.a, mask)) * 1
602      - (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r
603 = text_color.r * mask.r
604   + dest.r + (1 - dest.a) * bg_color.r
605   - text_color.a * mask.r * dest.r
606   - text_color.a * mask.r * (1 - dest.a) * bg_color.r
607   - ((1 - make_alpha(text_color.a, mask)) * (1 - dest.a)) * bg_color.r
608 = text_color.r * mask.r
609   + dest.r - text_color.a * mask.r * dest.r
610   + (1 - dest.a) * bg_color.r
611   - text_color.a * mask.r * (1 - dest.a) * bg_color.r
612   - (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
613 = text_color.r * mask.r
614   + (1 - text_color.a * mask.r) * dest.r
615   + (1 - dest.a) * bg_color.r
616   - text_color.a * mask.r * (1 - dest.a) * bg_color.r
617   - (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
618 = text_color.r * mask.r
619   + (1 - text_color.a * mask.r) * dest.r
620   + (1 - text_color.a * mask.r) * (1 - dest.a) * bg_color.r
621   - (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
622 = text_color.r * mask.r
623   + (1 - text_color.a * mask.r) * dest.r
624   + ((1 - text_color.a * mask.r)
625      - (1 - make_alpha(text_color.a, mask))) * (1 - dest.a) * bg_color.r
626 = text_color.r * mask.r
627   + (1 - text_color.a * mask.r) * dest.r
628   + (1 - text_color.a * mask.r
629      - 1 + make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r
630 = text_color.r * mask.r
631   + (1 - text_color.a * mask.r) * dest.r
632   + (make_alpha(text_color.a, mask) - text_color.a * mask.r) * (1 - dest.a) * bg_color.r
633```
634
635We now have a term of the form `A + B + C`, with `A` and `B` being guaranteed to
636be between zero and one.
637
638We also want `C` to be between zero and one.
639We can use this restriction to help us decide on an implementation of `make_alpha`.
640
641If we define `make_alpha` as
642
643```glsl
644float make_alpha(text_color_a, mask) {
645  float max_rgb = max(max(mask.r, mask.g), mask.b);
646  return text_color_a * max_rgb;
647}
648```
649
650, then `(make_alpha(text_color.a, mask) - text_color.a * mask.r)` becomes
651`(text_color.a * max(max(mask.r, mask.g), mask.b) - text_color.a * mask.r)`, which is
652`text_color.a * (max(max(mask.r, mask.g), mask.b) - mask.r)`, and the subtraction will
653always yield something that's greater or equal to zero for r, g, and b,
654because we will subtract each channel from the maximum of the channels.
655
656Putting this all together, we have:
657
658```glsl
659vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest) {
660  float max_rgb = max(max(mask.r, mask.g), mask.b);
661  vec4 result;
662  result.r = text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r +
663             text_color.a * bg_color.r * (max_rgb - mask.r) * (1 - dest.a);
664  result.g = text_color.g * mask.g + (1 - text_color.a * mask.g) * dest.g +
665             text_color.a * bg_color.g * (max_rgb - mask.g) * (1 - dest.a);
666  result.b = text_color.b * mask.b + (1 - text_color.a * mask.b) * dest.b +
667             text_color.a * bg_color.b * (max_rgb - mask.b) * (1 - dest.a);
668  result.a = text_color.a * max_rgb + (1 - text_color.a * max_rgb) * dest.a;
669  return result;
670}
671```
672
673This is the final form of this blend function. It satisfies all of the four constraints.
674
675### Implementing it with OpenGL
676
677Our color channel equations consist of three pieces:
678
679 - `text_color.r * mask.r`, which simply gets added to the rest.
680 - `(1 - text_color.a * mask.r) * dest.r`, a factor which gets multiplied with the destination color.
681 - `text_color.a * bg_color.r * (max_rgb - mask.r) * (1 - dest.a)`, a factor which gets multiplied
682   with "one minus destination alpha".
683
684We will need three passes. Each pass modifies the color channels in the destination.
685This means that the part that uses `dest.r` needs to be applied first.
686Then we can apply the part that uses `1 - dest.a`.
687(This means that the first pass needs to leave `dest.a` untouched.)
688And the final pass can apply the `result.a` equation and modify `dest.a`.
689
690```
691pub fn set_blend_mode_subpixel_with_bg_color_pass0(&self) {
692    self.gl.blend_func_separate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE);
693}
694pub fn set_blend_mode_subpixel_with_bg_color_pass1(&self) {
695    self.gl.blend_func_separate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
696}
697pub fn set_blend_mode_subpixel_with_bg_color_pass2(&self) {
698    self.gl.blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
699}
700
701Pass0:
702    oFragColor = vec4(text.color.a) * mask;
703Pass1:
704    oFragColor = vec4(text.color.a) * text.bg_color * (vec4(mask.a) - mask);
705Pass2:
706    oFragColor = text.color * mask;
707
708result_after_pass0.r = 0 * (text_color.a * mask.r) + (1 - text_color.a * mask.r) * dest.r
709result_after_pass0.a = 0 * (text_color.a * mask.a) + 1 * dest.a
710
711result_after_pass1.r = (1 - result_after_pass0.a) * (text_color.a * (mask.max_rgb - mask.r) * bg_color.r) + 1 * result_after_pass0.r
712result_after_pass1.a = 0 * (text_color.a * (mask.max_rgb - mask.a) * bg_color.a) + 1 * result_after_pass0.a
713
714result_after_pass2.r = 1 * (text_color.r * mask.r) + 1 * result_after_pass1.r
715result_after_pass2.a = 1 * (text_color.a * mask.max_rgb) + (1 - text_color.a * mask.max_rgb) * result_after_pass1.a
716```
717
718Instead of computing `max_rgb` in the shader, we can just require the font rasterization code to fill
719`mask.a` with the `max_rgb` value.
720
721