Differences between AGSL and GLSL

AGSL and GLSL are very similar in syntax, allowing many GLSL fragment shader effects to be brought over to Android with minimal changes. AGSL fixes its GLSL feature set at GLSL ES 1.0 (the shading language used by OpenGL ES 2.0) to provide for maximum device reach.

A GLSL fragment shader controls the entire behavior of the GPU between the rasterizer and the blending hardware. That shader does all the work to compute a color, and the color it generates is exactly what is fed to the blending stage of the pipeline. When you write a shader in AGSL, you are programming a stage of the Android graphics pipeline. Many of the language differences stem from this.

Shader execution

Just like in a GLSL shader, an AGSL shader begins execution in a main function. Unlike GLSL, the function takes the shader position in "local" coordinates as a parameter. This is similar to gl_FragCoord, but rather than framebuffer coordinates, these coordinates may have been translated prior to calling your shader. Your shader then returns the pixel color as a vec4 in medium or high precision (similar to out vec4 color or gl_FragColor in GLSL).

mediump vec4 main(in vec2 fragCoord)

Coordinate space

GLSL vs AGSL coordinate spaces

Shader drawn using GLSL vs Near identical shader drawn using AGSL

AGSL and GLSL use different coordinate spaces by default. In GLSL, the fragment coordinate (fragCoord) is relative to the lower left. AGSL matches the screen coordinate system of Canvas, which means that the Y axis begins from the upper left corner. If needed, you can convert between these two spaces by passing in the resolution as a uniform and using resolution.y - fragCoord.y for the Y axis value. Alternatively, you can apply a local transformation matrix to your shader.

// AGSL to GLSL coordinate space transformation matrix
val localMatrix = Matrix()
localMatrix.postScale(1.0f, -1.0f)
localMatrix.postTranslate(0.0f, viewHeight)
gridShader.setLocalMatrix(localMatrix)

Precision and types

GLSL compatible precision modifiers are supported, but AGSL introduces half and short types which also represent medium precision.

Vector types can be declared as named <base type><columns>. You can use float2 instead of vec2 and bool4 instead of bvec4. Matrix types can be declared as named <base type><columns>x<rows>, so float3x3 instead of mat3. AGSL also allows GLSL-style declarations for mat and vec and these types are mapped to their float equivalents.

Preprocessor

AGSL doesn't support GLSL style preprocessor directives. Convert #define statements to const variables. AGSL's compiler supports constant folding and branch elimination for const variables, so these will be efficient.

Color spaces

Android Applications are color managed. The color space of a Canvas determines the working color space for drawing. Source content (like shaders, including BitmapShader) also have color spaces.

For certain effects, such as physically accurate lighting, math should be done in a linear color space. To help with this, AGSL provides these intrinsic functions:

half3 toLinearSrgb(half3 color)
half3 fromLinearSrgb(half3 color)

These convert colors between the working color space and Android's LINEAR_EXTENDED_SRGB color space. That space uses the sRGB color primaries (gamut), and a linear transfer function. It represents values outside of the sRGB gamut using extended range values (below 0.0 and above 1.0).

Uniforms

Since AGSL doesn't know if uniforms contain colors, it won't automatically apply a color conversion to them. You can label half4/float4/vec4 with layout(color), which lets Android know that the uniform will be used as a color, allowing Android to transform the uniform value to the working color space.

In AGSL, declare the uniform like this:

layout(color) uniform half4 iColor;  // Input color
uniform float2 iResolution;          // Viewport resolution (pixels)

In Android code, you can then set the uniform like this:

shader.setColorUniform("iColor", Color.GREEN)
shader.setFloatUniform("iResolution", canvas.width.toFloat(), canvas.height.toFloat())