diff --git a/site/docs/user/sksl.md b/site/docs/user/sksl.md index 5a6f9f031d..64007e5fb8 100644 --- a/site/docs/user/sksl.md +++ b/site/docs/user/sksl.md @@ -7,8 +7,9 @@ linkTitle: 'SkSL' **SkSL** is Skia's [shading language](https://en.wikipedia.org/wiki/Shading_language). -**`SkRuntimeEffect`** is a Skia C++ object that can be used to create `SkShader` -and `SkColorFilter` objects with behavior controlled by SkSL code. +**`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 @@ -18,26 +19,65 @@ stage of the [GPU pipeline](https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview). 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 the `SkPaint`. The `SkShader` can actually be a + tree of objects (due to `SkShaders::Blend` and other features described + below). +- Similar logic for the `SkColorFilter` (which can also be a tree, due to + `SkColorFilters::Compose`, `SkColorFilters::Blend`, and features described + below). +- Blending code (for certain `SkBlendMode`s, or for custom blending specified + with `SkPaint::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.** + --- -## Sampling other SkShaders +## Evaluating (sampling) other SkShaders In GLSL, a fragment shader can sample a texture. With runtime effects, the -object that you bind (in C++) and sample (in SkSL) is an `SkShader`. Skia has -simple methods for creating an `SkShader` from an `SkImage`, so it's easy to use -images in your runtime effects: +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 sample is an `SkShader`, you can directly use -any Skia shader, without necessarily turning it into an image (texture) first. -For example, you can sample a linear gradient: +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: - + -You can even sample another runtime effect: +Of course, you can even invoke another runtime effect, allowing you to combine +shader snippets dynamically: - + --- @@ -59,7 +99,7 @@ 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 an - `SkImageShader`, and sample that shader... the resulting colors will be + `SkImageShader`, 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` by `A`. @@ -68,7 +108,7 @@ Skia's use of premultiplied alpha implies: 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 +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 @@ -90,9 +130,9 @@ 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 sampling an `SkImageShader` with +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: - +