xref: /aosp_15_r20/external/skia/site/docs/user/sksl.md (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1---
2title: 'SkSL & Runtime Effects'
3linkTitle: 'SkSL'
4---
5
6## Overview
7
8**SkSL** is Skia's
9[shading language](https://en.wikipedia.org/wiki/Shading_language).
10**`SkRuntimeEffect`** is a Skia C++ object that can be used to create
11`SkShader`, `SkColorFilter`, and `SkBlender` objects with behavior controlled by
12SkSL code.
13
14You can experiment with SkSL at https://shaders.skia.org/. The syntax is very
15similar to GLSL. When using SkSL effects in your Skia application, there are
16important differences (from GLSL) to remember. Most of these differences are
17because of one basic fact: **With GPU shading languages, you are programming a
18stage of the
19[GPU pipeline](https://www.khronos.org/opengl/wiki/Rendering_Pipeline_Overview).
20With SkSL, you are programming a stage of the Skia pipeline.**
21
22In particular, a GLSL fragment shader controls the entire behavior of the GPU
23between the rasterizer and the blending hardware. That shader does all of the
24work to compute a color, and the color it generates is exactly what is fed to
25the fixed-function blending stage of the pipeline.
26
27SkSL effects exist as part of the larger Skia pipeline. When you issue a canvas
28drawing operation, Skia (generally) assembles a single GPU fragment shader to do
29all of the required work. This shader typically includes several pieces. For
30example, it might include:
31
32- Evaluating whether a pixel falls inside or outside of the shape being drawn
33  (or on the border, where it might apply antialiasing).
34- Evaluating whether a pixel falls inside or outside of the clipping region
35  (again, with possible antialiasing logic for border pixels).
36- Logic for the `SkShader` on the `SkPaint`. The `SkShader` can actually be a
37  tree of objects (due to `SkShaders::Blend` and other features described
38  below).
39- Similar logic for the `SkColorFilter` (which can also be a tree, due to
40  `SkColorFilters::Compose`, `SkColorFilters::Blend`, and features described
41  below).
42- Blending code (for certain `SkBlendMode`s, or for custom blending specified
43  with `SkPaint::setBlender`).
44- Color space conversion code, as part of Skia's [color management](/docs/user/color).
45
46Even if the `SkPaint` has a complex tree of objects in the `SkShader`,
47`SkColorFilter`, or `SkBlender` fields, there is still only a _single_ GPU
48fragment shader. Each node in that tree creates a single function. The clipping
49code and geometry code each create a function. The blending code might create a
50function. The overall fragment shader then calls all of these functions (which
51may call other functions, e.g. in the case of an `SkShader` tree).
52
53**Your SkSL effect contributes a function to the GPU's fragment shader.**
54
55---
56
57## Evaluating (sampling) other SkShaders
58
59In GLSL, a fragment shader can sample a texture. With runtime effects, the
60object that you bind (in C++) is an `SkShader`, represented by a `shader` in
61SkSL. To make it clear that you are operating on an object that will emit its
62own shader code, you don't use `sample`. Instead, the `shader` object has a
63`.eval()` method. Regardless, Skia has simple methods for creating an `SkShader`
64from an `SkImage`, so it's easy to use images in your runtime effects:
65
66<fiddle-embed-sk name='@SkSL_EvaluatingImageShader'></fiddle-embed-sk>
67
68Because the object you bind and evaluate is an `SkShader`, you can directly use
69any Skia shader, without necessarily turning it into an image (texture) first.
70For example, you can evaluate a linear gradient. In this example, there is no
71texture created to hold the gradient. Skia generates a single fragment shader
72that computes the gradient color, samples from the image's texture, and then
73multiplies the two together:
74
75<fiddle-embed-sk name='@SkSL_EvaluatingTwoShaders'></fiddle-embed-sk>
76
77Of course, you can even invoke another runtime effect, allowing you to combine
78shader snippets dynamically:
79
80<fiddle-embed-sk name='@SkSL_EvaluatingNestedShaders'></fiddle-embed-sk>
81
82---
83
84## Coordinate Spaces
85
86To understand how coordinates work in SkSL, you first need to understand
87[how they work in Skia](/docs/user/coordinates). If you're comfortable with
88Skia's coordinate spaces, then just remember that the coordinates supplied to
89your `main()` are **local** coordinates. They will be relative to the coordinate
90space of the `SkShader`. This will match the local space of the canvas and any
91`localMatrix` transformations. Additionally, if the shader is invoked by
92another, that parent shader may modify them arbitrarily.
93
94In addition, the `SkShader` produced from an `SkImage` does not use normalized
95coordinates (like a texture in GLSL). It uses `(0, 0)` in the upper-left corner,
96and `(w, h)` in the bottom-right corner. Normally, this is exactly what you
97want. If you're evaluating an `SkImageShader` with coordinates based on the ones
98passed to you, the scale is correct. However, if you want to adjust those
99coordinates (to do some kind of re-mapping of the image), remember that the
100coordinates are scaled up to the dimensions of the image:
101
102<fiddle-embed-sk name='@SkSL_CoordinateSpaces'></fiddle-embed-sk>
103
104---
105
106## Color Spaces
107
108Applications using Skia are usually [color managed](/docs/user/color). The color
109space of a surface (destination) determines the working color space for a draw.
110Source content (like shaders, including `SkImageShader`) also have color spaces.
111By default, inputs to your SkSL shader will be transformed to the working color
112space. Some inputs require special care to get (or inhibit) this behavior, though.
113
114First, let's see Skia's color management in action. Here, we're drawing a portion
115of the mandrill image twice. The first time, we've drawn it normally, respecting
116the color space stored in the file (this happens to be the [sRGB](https://en.wikipedia.org/wiki/SRGB)
117color space. The second time, we've assigned the Rec. 2020 color space to the image.
118This simply tells Skia to treat the image as if the colors stored are actually in
119that color space. Skia then transforms those values from Rec. 2020 to the
120destination surface's color space (sRGB). As a result, all of the colors look more
121vivid. More importantly, if the image really *were* in some other color space, or
122if the destination surface were in some other color space, this automatic conversion
123is desirable, because it ensures content looks consistently correct on any user's
124screen.
125
126<fiddle-embed-sk name='@SkSL_ColorSpaces'></fiddle-embed-sk>
127
128### Uniforms
129
130Skia and SkSL doesn't know if your `uniform` variables contain colors, so it won't
131automatically apply color conversion to them. In the below example, there are two
132uniforms declared: `color` and `not_a_color`. The SkSL simply fades in one of the
133two uniform "colors" horizontally, choosing a different uniform for the top and
134bottom half of the shader. The code passes the same values to both uniforms, four
135floating point values `{1,0,0,1}` that represent "red".
136
137To really see the effect of automatic uniform conversion, the fiddle draws to an
138offscreen surface in the [Rec. 2020](https://en.wikipedia.org/wiki/Rec._2020) color
139space. Rec. 2020 has a very _wide gamut_, which means that it can represent more
140vivid colors than the common default [sRGB](https://en.wikipedia.org/wiki/SRGB)
141color space. In particular, the purest red in sRGB is fairly dull compared to pure
142red in Rec. 2020.
143
144To understand what happens in this fiddle, we'll explain the steps for the two
145different cases. For the top half, we use `not_a_color`. Skia and SkSL don't know
146that you intend to use this as a color, so the raw floating point values you supply
147are fed directly to the SkSL shader. In other words - when the SkSL executes,
148`not_a_color` will contain `{1,0,0,1}`, regardless of the surface's color space.
149This produces the most vivid red possible in the destination's color space (which
150ends up looking like a very bright red in this case).
151
152For the bottom half, we have declared the uniform `color` with the special syntax
153`layout(color)`. That tells SkSL that this variable will be used as a color.
154`layout(color)` can only be used on uniform values that are `vec3` (i.e., RGB) or
155`vec4` (i.e., RGBA). In either case, the colors you supply when providing uniform data
156should be unpremultiplied sRGB colors. Those colors can include values outside of
157the range `[0,1]`, if you want to supply wide gamut colors. This is the same way
158that Skia accepts and stores colors on `SkPaint`. When the SkSL executes, Skia
159transforms the uniform value to the working color space. In this case, that means
160that `color` (which starts out as sRGB red) is turned into whatever values represent
161that same color in the Rec. 2020 color space.
162
163The overall effect here is to make the correctly labeled uniform much duller, but
164that is actually what you want when working with uniform colors. By labeling uniform
165colors this way, your source colors (that you place in uniforms) will represent the
166same, consistent color regardless of the color space of the destination surface.
167
168<fiddle-embed-sk name='@SkSL_Uniforms'></fiddle-embed-sk>
169
170### Raw Image Shaders
171
172Although most images contain colors that should be color managed, some images
173contain data that isn't actually colors. This includes images storing normals,
174material properties (e.g., roughness), heightmaps, or any other purely
175mathematical data that happens to be stored in an image. When using these kinds
176of images in SkSL, you probably want to use a *raw* image shader, created with
177`SkImage::makeRawShader`. These work like regular image shaders (including
178filtering and tiling), with a few major differences:
179  - No color space transformation is ever applied (the color space of the image
180    is ignored).
181  - Images with an alpha type of kUnpremul are **not** automatically premultiplied.
182  - Bicubic filtering is not supported. Requesting bicubic filtering when
183    calling `makeRawShader` will return `nullptr`.
184
185Here, we create an image holding a spherical normal map. Then we use that with
186a lighting shader to show what happens when rendering to a different color space.
187If we use a regular image shader, the normals will be treated as colors, and
188transformed to the working color space. This alters the normals, incorrectly.
189For the final draw, we use a raw image shader, which returns the original
190normals, ignoring the working color space.
191
192<fiddle-embed-sk name='@SkSL_RawImageShaders'></fiddle-embed-sk>
193
194### Working In a Known Color Space
195
196Within an SkSL shader, you don't know what the working color space is. For many
197effects, this is fine - evaluating image shaders, and doing simple color math
198is usually going to give reasonable results (particularly if you know that
199the working color space for an application is always sRGB, for example). For
200certain effects, though, it may be important to do some math in a fixed, known
201color space. The most common example is lighting -- to get physically accurate
202lighting, math should be done in a _linear_ color space. To help with this,
203SkSL provides two intrinsic functions:
204
205```
206vec3 toLinearSrgb(vec3 color);
207vec3 fromLinearSrgb(vec3 color);
208```
209
210These convert colors between the working color space and the linear sRGB color
211space. That space uses the sRGB color primaries (gamut), and a linear transfer
212function. It represents values outside of the sRGB gamut using extended range
213values (below 0.0 and above 1.0). This corresponds to Android's
214[LINEAR_EXTENDED_SRGB](https://developer.android.com/reference/android/graphics/ColorSpace.Named.html#LINEAR_EXTENDED_SRGB)
215or Apple's
216[extendedLinearSRGB](https://developer.apple.com/documentation/coregraphics/cgcolorspace/1690961-extendedlinearsrgb),
217for example.
218
219Here's an example showing a sphere, with lighting math being done in the default
220working space (sRGB), and again with the math done in a linear space:
221
222<fiddle-embed-sk name='@SkSL_LinearSRGB'></fiddle-embed-sk>
223
224---
225
226## Premultiplied Alpha
227
228When dealing with transparent colors, there are two (common)
229[possible representations](https://en.wikipedia.org/wiki/Alpha_compositing#Straight_versus_premultiplied).
230Skia calls these _unpremultiplied_ (what Wikipedia calls _straight_), and
231_premultiplied_. In the Skia pipeline, every `SkShader` returns premultiplied
232colors.
233
234If you're familiar with OpenGL blending, you can think of it in terms of the
235blend equation. For common alpha blending (called
236[source-over](https://developer.android.com/reference/android/graphics/PorterDuff.Mode#SRC_OVER)),
237you would normally configure your blend function as
238`(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)`. Skia defines source-over blending as
239if the blend function were `(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)`.
240
241Skia's use of premultiplied alpha implies:
242
243- If you start with an unpremultiplied `SkImage` (like a PNG), turn that into an
244  `SkImageShader`, and evaluate that shader... the resulting colors will be
245  `[R*A, G*A, B*A, A]`, **not** `[R, G, B, A]`.
246- If your SkSL will return transparent colors, it must be sure to multiply the
247  `RGB` by `A`.
248- For more complex shaders, you must understand which of your colors are
249  premultiplied vs. unpremultiplied. Many operations don't make sense if you mix
250  both kinds of color together.
251
252The image below demonstrates this: properly premultiplied colors produce a smooth
253gradient as alpha decreases. Unpremultipled colors cause the gradient to display
254incorrectly, becoming too bright and shifting hue as the alpha changes.
255
256<fiddle-embed-sk name='@SkSL_PremultipliedAlpha'></fiddle-embed-sk>
257
258---
259
260## Minified SkSL
261
262Skia includes a minifier tool which can automatically reduce the size of your Runtime Effect
263or SkMesh code. The tool eliminates whitespace and comments, shortens function and variable names,
264and deletes unreferenced code.
265
266As an example, here is the previous demo in its minified form. The shader code is reduced to
267approximately half of its original size, while displaying the exact same result.
268
269<fiddle-embed-sk name='@SkSL_MinifiedSkSL'></fiddle-embed-sk>
270
271To enable this tool, add `skia_compile_modules = true` to your gn argument list. (At the command
272line, type `gn args out/yourbuild` to access the arguments, or edit the file `out/yourbuild/args.gn`
273directly.) Use `ninja` to compile Skia once more, and you will now have a new utility called
274`sksl-minify` in the output directory.
275
276When minifying a mesh program, you must supply `struct Varyings` and `struct Attributes` which
277correspond to the SkMeshSpecification. These structs will be eliminated from the minified program
278for convenience.
279
280`sksl-minify` takes the following command line arguments:
281
282- An output path, e.g. `MyShader.minified.sksl`
283- An input path, e.g. `MyShader.sksl`
284- (Optional) Pass `--stringify` to wrap the minified SkSL text in a quoted C++ string.
285  By default, the output file will contain plain SkSL. The minified shader string in the example
286  code above was created with --stringify.
287- (Optional) Pass `--shader`, `--colorfilter`, `--blender`, `--meshfrag` or `--meshvert` to set
288  the program kind. The default value is `--shader`.
289