[Impeller] use varying interpolation to compute linear gradients. #148651
Labels
e: impeller
Impeller rendering backend issues and features requests
P3
Issues that are less important to the Flutter project
team-engine
Owned by Engine team
triaged-engine
Triaged by Engine team
See #148496 for a motivating example.
Background
Impeller's current gradient shader is quite slow (I know I wrote it), due to accomidating an extremely flexible gradient API into a single shader. Flutter has approximately the same API as https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient , which supports an arbitrary number of colors, color stops, potentially inset in the rendered geometry. Skia generates new gradient shaders at runtime based on the number of colors and tile mode, but this isn't a viable option with Impeller. We can do better than this anyway.
Overview
Use varying interpolation to compute the gradient colors for us. This requires us to potentially change the vertices associated with a draw so that there is sufficient information to perform the interpolation.
For example, suppose we are applying a linear gradient with 2 colors to a rectangle (red and blue), with the start and end at the top center and bottom center (respectively).
It is apparent that assigning red to varyings for vertices 1, 2 and blue to vertices 3, 4 will lead to exactly the interpolation needed by the gradient. Thus the fragment stage can be simplified to computing the premultiplied color (our gradients interpolate in unpremul space) and applying the ordered dithering.
What if we have more than two colors?
When there are more than two colors, we must insert additional vertices for our interpolation. In the case of 3 colors (red, green, blue) and the same rectangle, we need to insert new vertices to assign the green color 2, at the correct location determined by the color stop value (if unspecified 0.5). This is equivalent to dividing the rectangle in half
In this case we have red at vertices 1, 2, green at 3, 4, and blue at 5, 6. This process can be repeated for an arbitrary number of divisions, provided that S/E are located on the coverage rect. The division point can be computed by using the stop value to lerp start and end (
point_i = (1.0 - t_i) * start + t_i * end
).What if the S/E are located elsewhere?
IF the start/end are horizontal, then I believe we could handle this by rotating the shape such that it was vertical, computing the triangles, and then rotating back. Likewise if the directions are reversed - these are all 90, 180, 270 degree rotations.
If The start and end are diagonal in some way, things get more interesting. We have a few possibilities here, but I haven't thought through all of the options.
What if the S/E are inside the rectangle
Then we need to handle the tile mode, which computes a value of t similar to a texture sampler. For example, t may clamp, or repeat, or be mirrored. I think the best option would be to expand the geometry and compute t for each stop. If its decal though, we can avoid this.
What if the S/E are outside the rectangle
Assuming the existing conditions are met, we treat the starting point on the rectangle as whatever the value of
t
would be.What if the shape isn't a rectangle
With StC, all shapes can be rectangles so this isn't a problem. Draw the actual shape with the stencil buffer and then the coverage rect can be substituted for the geometry rect.
What about other gradient types?
These are less common. I think we could do concentric circles for the radial gradient. Conical/Sweep 🤷
The text was updated successfully, but these errors were encountered: