Home | History | Annotate | Download | only in conical
      1 Two-point Conical Gradient
      2 ====
      3 
      4 <script type="text/x-mathjax-config">
      5 MathJax.Hub.Config({
      6     tex2jax: {
      7         inlineMath: [['$','$'], ['\\(','\\)']]
      8     }
      9 });
     10 </script>
     11 
     12 <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML'></script>
     13 
     14 (Please refresh the page if you see a lot of dollars instead of math symbols.)
     15 
     16 We present a fast shading algorithm (compared to bruteforcely solving the quadratic equation of
     17 gradient $t$) for computing the two-point conical gradient (i.e., `createRadialGradient` in
     18 [spec](https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createradialgradient)).
     19 It reduced the number of multiplications per pixel from ~10 down to 3, and brought a speedup of up to
     20 26% in our nanobenches.
     21 
     22 This document has 3 parts:
     23 
     24 1. [Problem Statement and Setup](#problem-statement)
     25 2. [Algorithm](#algorithm)
     26 3. [Appendix](#appendix)
     27 
     28 Part 1 and 2 are self-explanatory. Part 3 shows how to geometrically proves our Theorem 1 in part
     29 2; it's more complicated but it gives us a nice picture about what's going on.
     30 
     31 ## <span id="problem-statement">Problem Statement and Setup</span>
     32 
     33 Let two circles be $C_0, r_0$ and $C_1, r_1$ where $C$ is the center and $r$ is the radius. For any
     34 point $P = (x, y)$ we want the shader to quickly compute a gradient $t \in \mathbb R$ such that $p$
     35 is on the linearly interpolated circle with center $C_t = (1-t) \cdot C_0 + t \cdot C_1$ and radius
     36 $r_t = (1-t) \cdot r_0 + t \cdot r_1 > 0$ (note that radius $r_t$ has to be *positive*). If
     37 there are multiple (at most 2) solutions of $t$, choose the bigger one.
     38 
     39 There are two degenerated cases:
     40 
     41 1. $C_0 = C_1$ so the gradient is essentially a simple radial gradient.
     42 2. $r_0 = r_1$ so the gradient is a single strip with bandwidth $2 r_0 = 2 r_1$.
     43 
     44 <!-- TODO maybe add some fiddle or images here to illustrate the two degenerated cases -->
     45 
     46 They are easy to handle so we won't cover them here. From now on, we assume $C_0 \neq C_1$ and $r_0
     47 \neq r_1$.
     48 
     49 As $r_0 \neq r_1$, we can find a focal point $C_f = (1-f) \cdot C_0 + f \cdot C_1$ where its
     50 corresponding linearly interpolated radius $r_f = (1-f) \cdot r_0 + f \cdot r_1 = 0$.
     51 Solving the latter equation gets us $f = r_0 / (r_0 - r_1)$.
     52 
     53 As $C_0 \neq C_1$, focal point $C_f$ is different from $C_1$ unless $r_1 = 0$. If $r_1 = 0$, we can
     54 swap $C_0, r_0$ with $C_1, r_1$, compute swapped gradient $t_s$ as if $r_1 \neq 0$, and finally set
     55 $t = 1 - t_s$. The only catch here is that with multiple solutions of $t_s$, we shall choose the
     56 smaller one (so $t$ could be the bigger one).
     57 
     58 Assuming that we've done swapping if necessary so $C_1 \neq C_f$, we can then do a linear
     59 transformation to map $C_f, C_1$ to $(0, 0), (1, 0)$. After the transformation:
     60 
     61 1. All centers $C_t = (x_t, 0)$ must be on the $x$ axis
     62 2. The radius $r_t$ is $x_t r_1$.
     63 3. Given $x_t$ , we can derive $t = f + (1 - f) x_t$
     64 
     65 From now on, we'll focus on how to quickly computes $x_t$. Note that $r_t > 0$ so we're only
     66 interested positive solution $x_t$. Again, if there are multiple $x_t$ solutions, we may want to
     67 find the bigger one if $1 - f > 0$, and smaller one if $1 - f < 0$, so the corresponding $t$ is
     68 always the bigger one (note that $f \neq 1$, otherwise we'll swap $C_0, r_0$ with $C_1, r_1$).
     69 
     70 ## <span id="algorithm">Algorithm</span>
     71 
     72 **Theorem 1.** The solution to $x_t$ is
     73 
     74 1. $\frac{x^2 + y^2}{(1 + r_1) x} = \frac{x^2 + y^2}{2 x}$ if $r_1 = 1$
     75 2. $\left(\sqrt{(r_1^2 - 1) y ^2 + r_1^2 x^2}  - x\right) / (r_1^2 - 1)$ if $r_1 > 1$
     76 3. $\left(\pm \sqrt{(r_1^2 - 1) y ^2 + r_1^2 x^2}  - x\right) / (r_1^2 - 1)$ if $r_1 < 1$.
     77 
     78 Case 2 always produces a valid $x_t$. Case 1 and 3 requires $x > 0$ to produce valid $x_t > 0$. Case
     79 3 may have no solution at all if $(r_1^2 - 1) y^2 + r_1^2 x^2 < 0$.
     80 
     81 *Proof.* Algebriacally, solving the quadratic equation $(x_t - x)^2 + y^2 = (x_t r_1)^2$ and
     82 eliminate negative $x_t$ solutions get us the theorem.
     83 
     84 Alternatively, we can also combine Corollary 2., 3., and Lemma 4. in the Appendix to geometrically
     85 prove the theorem. $\square$
     86 
     87 Theorem 1 by itself is not sufficient for our shader algorithm because:
     88 
     89 1. we still need to compute $t$ from $x_t$ (remember that $t = f + (1-f) x_t$);
     90 2. we still need to handle cases of choosing the bigger/smaller $x_t$;
     91 3. we still need to handle the swapped case (we swap $C_0, r_0$ with $C_1, r_1$ if $r_1 = 0$);
     92 4. there are way too many multiplications and divisions in Theorem 1 that would slow our shader.
     93 
     94 Issue 2 and 3 are solved by generating different shader code based on different situations. So they
     95 are mainly correctness issues rather than performance issues. Issue 1 and 4 are performance
     96 critical, and they will affect how we handle issue 2 and 3.
     97 
     98 The key to handle 1 and 4 efficiently is to fold as many multiplications and divisions into the
     99 linear transformation matrix, which the shader has to do anyway (remember our linear transformation
    100 to map $C_f, C_1$ to $(0, 0), (1, 0)$).
    101 
    102 For example, let $\hat x, \hat y = |1-f|x, |1-f|y$. Computing $\hat x_t$ with respect to $\hat x,
    103 \hat y$ allow us to have $t = f + (1 - f)x_t = f + \text{sign}(1-f) \cdot \hat x_t$. That saves us
    104 one multiplication. Applying similar techniques to Theorem 1 gets us:
    105 
    106 1. If $r_1 = 1$, let $x' = x/2,~ y' = y/2$, then $x_t = (x'^2 + y'^2) / x'$.
    107 2. If $r_1 > 1$, let $x' = r_1 / (r_1^2 - 1) x,~ y' = \frac{\sqrt{r_1^2 - 1}}{r_1^2 - 1} y$, then
    108     $x_t = \sqrt{x'^2 + y'^2} - x' / r_1$
    109 3. If $r_1 < 1$, let $x' = r_1 / (r_1^2 - 1) x,~ y' = \frac{\sqrt{1 - r_1^2}}{r_1^2 - 1} y$, then
    110     $x_t = \pm\sqrt{x'^2 - y'^2} - x' / r_1$
    111 
    112 Combining it with the swapping, the equation $t = f + (1-f) x_t$, and the fact that we only want
    113 positive $x_t > 0$ and bigger $t$, we have our final algorithm:
    114 
    115 **Algorithm 1.**
    116 
    117 1. Let $C'_0, r'_0, C'_1, r'_1 = C_0, r_0, C_1, r_1$ if there is no swapping and $C'_0,
    118     r'_0, C'_1, r'_1 = C_1, r_1, C_0, r_0$ if there is swapping.
    119 2. Let $f = r'_0 / (r'_0 - r'_1)$ and $1 - f = r'_1 / (r'_1 - r'_0)$
    120 3. Let $x' = x/2,~ y' = y/2$ if $r_1 = 1$, and
    121     $x' = r_1 / (r_1^2 - 1) x,~ y' = \sqrt{|r_1^2 - 1|} / (r_1^2 - 1) y$ if $r_1 \neq 1$
    122 4. Let $\hat x = |1 - f|x', \hat y = |1 - f|y'$
    123 5. If $r_1 = 1$, let $\hat x_t = (\hat x^2 + \hat y^2) / \hat x$
    124 6. If $r_1 > 1$,
    125     let $\hat x_t = \sqrt{\hat x^2 + \hat y^2} - \hat x / r_1$
    126 7. If $r_1 < 1$
    127   1. return invalid if $\hat x^2 - \hat y^2 < 0$
    128   2. let $\hat x_t =  -\sqrt{\hat x^2 - \hat y^2} - \hat x / r_1$ if we've swapped $r_0, r_1$,
    129     or if $1 - f < 0$
    130 
    131   3. let $\hat x_t =  \sqrt{\hat x^2 - \hat y^2} - \hat x / r_1$ otherwise
    132 
    133 8. $t$ is invalid if $\hat x_t < 0$ (this check is unnecessary if $r_1 > 1$)
    134 9. Let $t = f + \text{sign}(1 - f) \hat x_t$
    135 10. If swapped, let $t = 1 - t$
    136 
    137 In step 7, we try to select either the smaller or bigger $\hat x_t$ based on whether the final $t$
    138 has a negative or positive relationship with $\hat x_t$. It's negative if we've swapped, or if
    139 $\text{sign}(1 - f)$ is negative (these two cannot both happen).
    140 
    141 Note that all the computations and if decisions not involving $\hat x, \hat y$  can be precomputed
    142 before the shading stage. The two if decisions  $\hat x^2 - \hat y^2 < 0$ and $\hat x^t < 0$  can
    143 also be omitted by precomputing the shading area that never violates those conditions.
    144 
    145 The number of operations per shading is thus:
    146 
    147 * 1 addition, 2 multiplications, and 1 division if $r_1 = 1$
    148 * 2 additions, 3 multiplications, and 1 sqrt for $r_1 \neq 1$ (count substraction as addition;
    149     dividing $r_1$ is multiplying $1/r_1$)
    150 * 1 more addition operation if $f \neq 0$
    151 * 1 more addition operation if swapped.
    152 
    153 In comparison, for $r_1 \neq 1$ case, our current raster pipeline shading algorithm (which shall
    154 hopefully soon be upgraded to the algorithm described here) mainly uses formula $$t = 0.5 \cdot
    155 (1/a) \cdot \left(-b \pm \sqrt{b^2 - 4ac}\right)$$ It precomputes $a = 1 - (r_1 - r_0)^2, 1/a, r1 -
    156 r0$. Number $b = -2 \cdot (x + (r1 - r0) \cdot r0)$ costs 2 multiplications and 1 addition. Number
    157 $c = x^2 + y^2 - r_0^2$ costs 3 multiplications and 2 additions. And the final $t$ costs 5 more
    158 multiplications, 1 more sqrt, and 2 more additions. That's a total of 5 additions, 10
    159 multiplications, and 1 sqrt. (Our algorithm has 2-4 additions, 3 multiplications, and 1 sqrt.) Even
    160 if it saves the $0.5 \cdot (1/a), 4a, r_0^2$ and $(r_1 - r_0) r_0$ multiplications, there are still
    161 6 multiplications. Moreover, it sends in 4 unitofmrs to the shader while our algorithm only needs 2
    162 uniforms ($1/r_1$ and $f$).
    163 
    164 ## <span id="appendix">Appendix</span>
    165 
    166 **Lemma 1.** Draw a ray from $C_f = (0, 0)$ to $P = (x, y)$. For every
    167 intersection points $P_1$ between that ray and circle $C_1 = (1, 0), r_1$, there exists an $x_t$
    168 that equals to the length of segment $C_f P$ over length of segment $C_f P_1$. That is,
    169 $x_t = || C_f P || / ||C_f P_1||$
    170 
    171 *Proof.* Draw a line from $P$ that's parallel to $C_1 P_1$. Let it intersect with $x$-axis on point
    172 $C = (x', y')$.
    173 
    174 <img src="conical/lemma1.svg"/>
    175 
    176 Triangle $\triangle C_f C P$ is similar to triangle $\triangle C_f C_1 P_1$.
    177 Therefore $||P C|| = ||P_1 C_1|| \cdot (||C_f C|| / ||C_f C_1||) = r_1 x'$. Thus $x'$ is a solution
    178 to $x_t$. Because triangle $\triangle C_f C P$ and triangle $\triangle C_f C_1 P_1$ are similar, $x'
    179 = ||C_f C_1|| \cdot (||C_f P|| / ||C_f P_1||) = ||C_f P|| / ||C_f P_1||$. $\square$
    180 
    181 **Lemma 2.** For every solution $x_t$, if we extend/shrink segment $C_f P$ to $C_f P_1$ with ratio
    182 $1 / x_t$ (i.e., find $P_1$ on ray $C_f P$ such that $||C_f P_1|| / ||C_f P|| = 1 / x_t$), then
    183 $P_1$ must be on circle $C_1, r_1$.
    184 
    185 *Proof.* Let $C_t = (x_t, 0)$. Triangle $\triangle C_f C_t P$ is similar to $C_f C_1 P_1$. Therefore
    186 $||C_1 P_1|| = r_1$ and $P_1$ is on circle $C_1, r_1$. $\square$
    187 
    188 **Corollary 1.** By lemma 1. and 2., we conclude that the number of solutions $x_t$ is equal to the
    189 number of intersections between ray $C_f P$ and circle $C_1, r_1$. Therefore
    190 
    191 * when $r_1 > 1$, there's always one unique intersection/solution; we call this "well-behaved"; this
    192   was previously known as the "inside" case;
    193 * when $r_1 = 1$, there's either one or zero intersection/solution (excluding $C_f$ which is always
    194   on the circle); we call this "focal-on-circle"; this was previously known as the "edge" case;
    195 
    196 <img src="conical/corollary2.2.1.svg"/>
    197 <img src="conical/corollary2.2.2.svg"/>
    198 
    199 * when $r_1 < 1$, there may be $0, 1$, or $2$ solutions; this was also previously as the "outside"
    200   case.
    201 
    202 <img src="conical/corollary2.3.1.svg" width="30%"/>
    203 <img src="conical/corollary2.3.2.svg" width="30%"/>
    204 <img src="conical/corollary2.3.3.svg" width="30%"/>
    205 
    206 **Lemma 3.** When solution exists, one such solution is
    207 $$
    208     x_t = {|| C_f P || \over ||C_f P_1||} = \frac{x^2 + y^2}{x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2}}
    209 $$
    210 
    211 *Proof.* As $C_f = (0, 0), P = (x, y)$, we have $||C_f P|| = \sqrt(x^2 + y^2)$. So we'll mainly
    212 focus on how to compute $||C_f P_1||$.
    213 
    214 **When $x \geq 0$:**
    215 
    216 <img src="conical/lemma3.1.svg"/>
    217 
    218 Let $X_P = (x, 0)$ and $H$ be a point on $C_f P_1$ such that $C_1 H$ is perpendicular to $C_1
    219 P_1$. Triangle $\triangle C_1 H C_f$ is similar to triangle $\triangle P X_P C_f$. Thus
    220 $$||C_f H|| = ||C_f C_1|| \cdot (||C_f X_P|| / ||C_f P||) = x / \sqrt{x^2 + y^2}$$
    221 $$||C_1 H|| = ||C_f C_1|| \cdot (||P X_P|| / ||C_f P||) = y / \sqrt{x^2 + y^2}$$
    222 
    223 Triangle $\triangle C_1 H P_1$ is a right triangle with hypotenuse $r_1$. Hence
    224 $$ ||H P_1|| = \sqrt{r_1^2 - ||C_1 H||^2} = \sqrt{r_1^2 - y^2 / (x^2 + y^2)} $$
    225 
    226 We have
    227 \begin{align}
    228     ||C_f P_1|| &= ||C_f H|| + ||H P_1|| \\\\\\
    229         &= x / \sqrt{x^2 + y^2} + \sqrt{r_1^2 - y^2 / (x^2 + y^2)} \\\\\\
    230         &= \frac{x + \sqrt{r_1^2 (x^2 + y^2) - y^2}}{\sqrt{x^2 + y^2}} \\\\\\
    231         &= \frac{x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2}}{\sqrt{x^2 + y^2}}
    232 \end{align}
    233 
    234 **When $x < 0$:**
    235 
    236 Define $X_P$ and $H$ similarly as before except that now $H$ is on ray $P_1 C_f$ instead of
    237 $C_f P_1$.
    238 
    239 <img src="conical/lemma3.2.svg"/>
    240 
    241 As before, triangle $\triangle C_1 H C_f$ is similar to triangle $\triangle P X_P C_f$, and triangle
    242 $\triangle C_1 H P_1$ is a right triangle, so we have
    243 $$||C_f H|| = ||C_f C_1|| \cdot (||C_f X_P|| / ||C_f P||) = -x / \sqrt{x^2 + y^2}$$
    244 $$||C_1 H|| = ||C_f C_1|| \cdot (||P X_P|| / ||C_f P||) = y / \sqrt{x^2 + y^2}$$
    245 $$ ||H P_1|| = \sqrt{r_1^2 - ||C_1 H||^2} = \sqrt{r_1^2 - y^2 / (x^2 + y^2)} $$
    246 
    247 Note that the only difference is changing $x$ to $-x$ because $x$ is negative.
    248 
    249 Also note that now $||C_f P_1|| = -||C_f H|| + ||H P_1||$ and we have $-||C_f H||$ instead of
    250 $||C_f H||$. That negation cancels out the negation of $-x$ so we get the same equation
    251 of $||C_f P_1||$ for both $x \geq 0$ and $x < 0$ cases:
    252 
    253 $$
    254     ||C_f P_1|| = \frac{x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2}}{\sqrt{x^2 + y^2}}
    255 $$
    256 
    257 Finally
    258 $$
    259     x_t = \frac{||C_f P||}{||C_f P_1||} = \frac{\sqrt{x^2 + y^2}}{||C_f P_1||}
    260         = \frac{x^2 + y^2}{x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2}}
    261 $$ $\square$
    262 
    263 **Corollary 2.** If $r_1 = 1$, then the solution $x_t = \frac{x^2 + y^2}{(1 + r_1) x}$, and
    264 it's valide (i.e., $x_t > 0$) iff $x > 0$.
    265 
    266 *Proof.* Simply plug $r_1 = 1$ into the formula of Lemma 3. $\square$
    267 
    268 **Corollary 3.** If $r_1 > 1$, then the unique solution is
    269 $x_t = \left(\sqrt{(r_1^2 - 1) y ^2 + r_1^2 x^2}  - x\right) / (r_1^2 - 1)$.
    270 
    271 *Proof.* From Lemma 3., we have
    272 
    273 \begin{align}
    274     x_t &= \frac{x^2 + y^2}{x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2}} \\\\\\
    275         &=  {
    276                 (x^2 + y^2) \left ( -x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2} \right )
    277             \over
    278                 \left (x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2} \right )
    279                 \left (-x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2} \right )
    280             } \\\\\\
    281         &=  {
    282                 (x^2 + y^2) \left ( -x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2} \right )
    283             \over
    284                 -x^2 + (r_1^2 - 1) y^2 + r_1^2 x^2
    285             } \\\\\\
    286         &=  {
    287                 (x^2 + y^2) \left ( -x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2} \right )
    288             \over
    289                 (r_1^2 - 1) (x^2 + y^2)
    290             } \\\\\\
    291         &=  \left(\sqrt{(r_1^2 - 1) y ^2 + r_1^2 x^2}  - x\right) / (r_1^2 - 1)
    292 \end{align}
    293 
    294 The transformation above (multiplying $-x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2}$ to enumerator and
    295 denomenator) is always valid because $r_1 > 1$ and it's the unique solution due to Corollary 1.
    296 $\square$
    297 
    298 **Lemma 4.** If $r_1 < 1$, then
    299 
    300 1. there's no solution to $x_t$ if $(r_1^2 - 1) y^2 + r_1^2 x^2 < 0$
    301 2. otherwise, the solutions are
    302     $x_t = \left(\sqrt{(r_1^2 - 1) y ^2 + r_1^2 x^2}  - x\right) / (r_1^2 - 1)$,
    303     or
    304     $x_t = \left(-\sqrt{(r_1^2 - 1) y ^2 + r_1^2 x^2}  - x\right) / (r_1^2 - 1)$.
    305 
    306 (Note that solution $x_t$ still has to be nonnegative to be valid; also note that
    307 $x_t > 0 \Leftrightarrow x > 0$ if the solution exists.)
    308 
    309 *Proof.* Case 1 follows naturally from Lemma 3. and Corollary 1.
    310 
    311 <img src="conical/lemma4.svg"/>
    312 
    313 For case 2, we notice that $||C_f P_1||$ could be
    314 
    315 1. either $||C_f H|| + ||H P_1||$ or $||C_f H|| - ||H P_1||$ if $x \geq 0$,
    316 2. either $-||C_f H|| + ||H P_1||$ or $-||C_f H|| - ||H P_1||$ if $x < 0$.
    317 
    318 By analysis similar to Lemma 3., the solution to $x_t$ does not depend on the sign of $x$ and
    319 they are either $\frac{x^2 + y^2}{x + \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2}}$
    320 or $\frac{x^2 + y^2}{x - \sqrt{(r_1^2 - 1) y^2 + r_1^2 x^2}}$.
    321 
    322 As $r_1 \neq 1$, we can apply the similar transformation in Corollary 3. to get the two
    323 formula in the lemma.
    324 $\square$
    325