Overview ======== SkSL ("Skia Shading Language") is a variant of GLSL which is used as Skia's internal shading language. SkSL is, at its heart, a single standardized version of GLSL which avoids all of the various version and dialect differences found in GLSL "in the wild", but it does bring a few of its own changes to the table. Skia uses the SkSL compiler to convert SkSL code to GLSL, GLSL ES, or SPIR-V before handing it over to the graphics driver. Differences from GLSL ===================== * Precision modifiers are not used. 'float', 'int', and 'uint' are always high precision. New types 'half', 'short', and 'ushort' are medium precision (we do not use low precision). * Vector types are named , so float2 instead of vec2 and bool4 instead of bvec4 * Matrix types are named x, so float2x3 instead of mat2x3 and double4x4 instead of dmat4 * "@if" and "@switch" are static versions of if and switch. They behave exactly the same as if and switch in all respects other than it being a compile-time error to use a non-constant expression as a test. * GLSL caps can be referenced via the syntax 'sk_Caps.', e.g. sk_Caps.sampleVariablesSupport. The value will be a constant boolean or int, as appropriate. As SkSL supports constant folding and branch elimination, this means that an 'if' statement which statically queries a cap will collapse down to the chosen branch, meaning that: if (sk_Caps.externalTextureSupport) do_something(); else do_something_else(); will compile as if you had written either 'do_something();' or 'do_something_else();', depending on whether that cap is enabled or not. * no #version statement is required, and it will be ignored if present * the output color is sk_FragColor (do not declare it) * use sk_Position instead of gl_Position. sk_Position is in device coordinates rather than normalized coordinates. * use sk_PointSize instead of gl_PointSize * use sk_VertexID instead of gl_VertexID * use sk_InstanceID instead of gl_InstanceID * the fragment coordinate is sk_FragCoord, and is always relative to the upper left. * use sk_Clockwise instead of gl_FrontFacing. This is always relative to an upper left origin. * you do not need to include ".0" to make a number a float (meaning that "float2(x, y) * 4" is perfectly legal in SkSL, unlike GLSL where it would often have to be expressed "float2(x, y) * 4.0". There is no performance penalty for this, as the number is converted to a float at compile time) * type suffixes on numbers (1.0f, 0xFFu) are both unnecessary and unsupported * creating a smaller vector from a larger vector (e.g. float2(float3(1))) is intentionally disallowed, as it is just a wordier way of performing a swizzle. Use swizzles instead. * Swizzle components, in addition to the normal rgba / xyzw components, can also be LTRB (meaning "left/top/right/bottom", for when we store rectangles in vectors), and may also be the constants '0' or '1' to produce a constant 0 or 1 in that channel instead of selecting anything from the source vector. foo.rgb1 is equivalent to float4(foo.rgb, 1). * All texture functions are named "sample", e.g. sample(sampler2D, float3) is equivalent to GLSL's textureProj(sampler2D, float3). * Render target width and height are available via sk_Width and sk_Height * some built-in functions and one or two rarely-used language features are not yet supported (sorry!) SkSL is still under development, and is expected to diverge further from GLSL over time. SkSL Fragment Processors ======================== ******************************************************************************** *** IMPORTANT: You must set gn arg "skia_compile_processors = true" to cause *** *** .fp files to be recompiled! In order for compilation to succeed, you *** *** must run bin/fetch-clang-format (once) to install our blessed version. *** ******************************************************************************** An extension of SkSL allows for the creation of fragment processors in pure SkSL. The program defines its inputs similarly to a normal SkSL program (with 'in' and 'uniform' variables), but the 'main()' function represents only this fragment processor's portion of the overall fragment shader. Within an '.fp' fragment processor file: * C++ code can be embedded in sections of the form: @section_name { } Supported section are: @header (in the .h file, outside the class declaration) @headerEnd (at the end of the .h file) @class (in the .h file, inside the class declaration) @cpp (in the .cpp file) @cppEnd (at the end of the .cpp file) @constructorParams (extra parameters to the constructor, comma-separated) @constructor (replaces the default constructor) @initializers (constructor initializer list, comma-separated) @emitCode (extra code for the emitCode function) @fields (extra private fields, each terminated with a semicolon) @make (replaces the default Make function) @clone (replaces the default clone() function) @setData() (extra code for the setData function, where is the name of the GrGLSLProgramDataManager) @test() (the body of the TestCreate function, where is the name of the GrProcessorTestData* parameter) @coordTransform() (the matrix to attach to the named sampler2D's GrCoordTransform) @samplerParams() (the sampler params to attach to the named sampler2D) * global 'in' variables represent data passed to the fragment processor at construction time. These variables become constructor parameters and are stored in fragment processor fields. By default float2/half2 maps to SkPoints, and float4/half4 maps to SkRects (in x, y, width, height) order. Similarly, int2/short2 maps to SkIPoint and int4/half4 maps to SkIRect. Use ctype (below) to override this default mapping. * global variables support an additional 'ctype' layout key, providing the type they should be represented as from within the C++ code. For instance, you can use 'layout(ctype=SkPMColor4f) in half4 color;' to create a variable that looks like a half4 on the SkSL side of things, and a SkPMColor4f on the C++ side of things. * 'uniform' variables become, as one would expect, top-level uniforms. By default they do not have any data provided to them; you will need to provide them with data via the @setData section. * 'in uniform' variables are uniforms that are automatically wired up to fragment processor constructor parameters. The fragment processor will accept a parameter representing the uniform's value, and automatically plumb it through to the uniform's value in its generated setData() function. * 'in uniform' variables support a 'tracked' flag in the layout that will have the generated code automatically implement state tracking on the uniform value to minimize GPU calls. * the 'sk_TransformedCoords2D' array provides access to 2D transformed coordinates. sk_TransformedCoords2D[0] is equivalent to calling fragBuilder->ensureCoords2D(args.fTransformedCoords[0]) (and the result is cached, so you need not worry about using the value repeatedly). * Uniform variables support an additional 'when' layout key. 'layout(when=foo) uniform int x;' means that this uniform will only be emitted when the 'foo' expression is true. * 'in' variables support an additional 'key' layout key. 'layout(key) in uniform int x;' means that this uniform should be included in the program's key. Matrix variables additionally support 'key=identity', which causes the key to consider only whether or not the matrix is an identity matrix. * child processors can be declared with 'in fragmentProcessor ;', and can be invoked by calling 'sample()' or 'sample(, )'. The first variant emits the child with a solid white input color. The second variant emits the child with the result of the 2nd argument's expression, which must evaluate to a half4. The process function returns a half4. * By default, fragment processors must be non-null. The type for a nullable fragment processor is 'fragmentProcessor?', as in 'in fragmentProcessor? '. You can check for the presence of such a fragment processor by comparing it to 'null'. Creating a new .fp file ======================= 1. Ensure that you have set gn arg "skia_compile_processors = true" 2. Create your new .fp file, generally under src/gpu/effects. 3. Add the .fp file to sksl.gni. 4. Build Skia. This will cause the .fp file to be compiled, resulting in a new .cpp and .h file for the fragment processor. 5. Add the .cpp and .h files to gpu.gni. 6. Add the new processor's ClassID (k_ClassID) to GrProcessor::ClassID. 7. At this point you can reference the new fragment processor from within Skia. Once you have done this initial setup, simply re-build Skia to pick up any changes to the .fp file.