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
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())