Instead of manually maintaining them, which is error prone. Change-Id: If78a564e4afe238d195b952fea276154b2e12b06 No-Try: true Reviewed-on: https://skia-review.googlesource.com/c/skia/+/449917 Reviewed-by: John Stiles <johnstiles@google.com> Commit-Queue: Joe Gregorio <jcgregorio@google.com>
6.7 KiB
title | linkTitle |
---|---|
SkSL & Runtime Effects | SkSL |
Overview
SkSL is Skia's
shading language.
SkRuntimeEffect
is a Skia C++ object that can be used to create
SkShader
, SkColorFilter
, and SkBlender
objects with behavior controlled by
SkSL code.
You can experiment with SkSL at https://shaders.skia.org/. The syntax is very similar to GLSL. When using SkSL effects in your Skia application, there are important differences (from GLSL) to remember. Most of these differences are because of one basic fact: With GPU shading languages, you are programming a stage of the GPU pipeline. With SkSL, you are programming a stage of the Skia pipeline.
In particular, a GLSL fragment shader controls the entire behavior of the GPU between the rasterizer and the blending hardware. That shader does all of the work to compute a color, and the color it generates is exactly what is fed to the fixed-function blending stage of the pipeline.
SkSL effects exist as part of the larger Skia pipeline. When you issue a canvas drawing operation, Skia (generally) assembles a single GPU fragment shader to do all of the required work. This shader typically includes several pieces. For example, it might include:
- Evaluating whether a pixel falls inside or outside of the shape being drawn (or on the border, where it might apply antialiasing).
- Evaluating whether a pixel falls inside or outside of the clipping region (again, with possible antialiasing logic for border pixels).
- Logic for the
SkShader
on theSkPaint
. TheSkShader
can actually be a tree of objects (due toSkShaders::Blend
and other features described below). - Similar logic for the
SkColorFilter
(which can also be a tree, due toSkColorFilters::Compose
,SkColorFilters::Blend
, and features described below). - Blending code (for certain
SkBlendMode
s, or for custom blending specified withSkPaint::setBlender
).
Even if the SkPaint
has a complex tree of objects in the SkShader
,
SkColorFilter
, or SkBlender
fields, there is still only a single GPU
fragment shader. Each node in that tree creates a single function. The clipping
code and geometry code each create a function. The blending code might create a
function. The overall fragment shader then calls all of these functions (which
may call other functions, e.g. in the case of an SkShader
tree).
Your SkSL effect contributes a function to the GPU's fragment shader.
Evaluating (sampling) other SkShaders
In GLSL, a fragment shader can sample a texture. With runtime effects, the
object that you bind (in C++) is an SkShader
, represented by a shader
in
SkSL. To make it clear that you are operating on an object that will emit its
own shader code, you don't use sample
. Instead, the shader
object has a
.eval()
method. Regardless, Skia has simple methods for creating an SkShader
from an SkImage
, so it's easy to use images in your runtime effects:
Because the object you bind and evaluate is an SkShader
, you can directly use
any Skia shader, without necessarily turning it into an image (texture) first.
For example, you can evaluate a linear gradient. In this example, there is no
texture created to hold the gradient. Skia generates a single fragment shader
that computes the gradient color, samples from the image's texture, and then
multiplies the two together:
Of course, you can even invoke another runtime effect, allowing you to combine shader snippets dynamically:
Premultiplied Alpha
When dealing with transparent colors, there are two (common)
possible representations.
Skia calls these unpremultiplied (what Wikipedia calls straight), and
premultiplied. In the Skia pipeline, every SkShader
returns premultiplied
colors.
If you're familiar with OpenGL blending, you can think of it in terms of the
blend equation. For common alpha blending (called
source-over),
you would normally configure your blend function as
(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
. Skia defines source-over blending as
if the blend function were (GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
.
Skia's use of premultiplied alpha implies:
- If you start with an unpremultiplied
SkImage
(like a PNG), turn that into anSkImageShader
, and evaluate that shader... the resulting colors will be[R*A, G*A, B*A, A]
, not[R, G, B, A]
. - If your SkSL will return transparent colors, it must be sure to multiply the
RGB
byA
. - For more complex shaders, you must understand which of your colors are premultiplied vs. unpremultiplied. Many operations don't make sense if you mix both kinds of color together.
Skia enforces that the color produced by your SkSL is a valid premultiplied
color. In other words, RGB ≤ A
. If your SkSL returns colors where that is not
true, they will be clamped, leading to incorrect colors. The image below
demonstrates this: properly premultiplied colors produce a smooth gradient as
alpha decreases. Unpremultipled colors cause the gradient to display
incorrectly, with a shift in hue as the alpha changes. This hue shift is caused
by Skia clamping the color's RGB values to its alpha.
Coordinate Spaces
To understand how coordinates work in SkSL, you first need to understand
how they work in Skia. If you're comfortable with
Skia's coordinate spaces, then just remember that the coordinates supplied to
your main()
are local coordinates. They will be relative to the coordinate
space of the SkShader
. This will match the local space of the canvas and any
localMatrix
transformations. Additionally, if the shader is invoked by
another, that parent shader may modify them arbitrarily.
In addition, the SkShader
produced from an SkImage
does not use normalized
coordinates (like a texture in GLSL). It uses (0, 0)
in the upper-left corner,
and (w, h)
in the bottom-right corner. Normally, this is exactly what you
want. If you're evaluating an SkImageShader
with coordinates based on the ones
passed to you, the scale is correct. However, if you want to adjust those
coordinates (to do some kind of re-mapping of the image), remember that the
coordinates are scaled up to the dimensions of the image: