Update SkSL docs slightly

- Use .eval() in all example fiddles
- Add some more explanation of how the parts of a draw/paint contribute
  to the GPU fragment shader.

Bug: skia:12302
Change-Id: Ib69b9af39368c980e1aa9206af585f26498d083e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/445640
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
This commit is contained in:
Brian Osman 2021-08-18 20:01:46 -04:00 committed by SkCQ
parent 0ed278b42d
commit 56273c9248

View File

@ -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.**
---
## <span id="children">Sampling other SkShaders</span>
## <span id="children">Evaluating (sampling) other SkShaders</span>
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:
<fiddle-embed name='f2885d3af66e5589fab19017953ac59e'></fiddle-embed>
<fiddle-embed name='8a895f12c8fd7b976bb68e6002f85a8e'></fiddle-embed>
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:
<fiddle-embed name='5fa8d1ec3af346e7c16872d138598f87'></fiddle-embed>
<fiddle-embed name='f282a4411782ed92057350e339586502'></fiddle-embed>
You can even sample another runtime effect:
Of course, you can even invoke another runtime effect, allowing you to combine
shader snippets dynamically:
<fiddle-embed name='08745d5050b250c2d8a826a739ea0ee1'></fiddle-embed>
<fiddle-embed name='2151b061428f47844a2500b57c887ddf'></fiddle-embed>
---
@ -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:
<fiddle-embed name='7ae4fd94835aa957aa6beb15f1774b6a'></fiddle-embed>
<fiddle-embed name='cc49d5a7b6b88d6a4dca1619e6df8763'></fiddle-embed>