For those also starting out trying to properly understand GLSL rather than just reuse online code, here's what I learned over the past few days. Note the below points refer to GLSL for GL ES 2.0.
Two key points were:
Get into the GPU mindset. GPU's have 100's or 1000's or cores all working in parallel, and a shader is basically designed to process lots of data via a Vertex Buffer Object (VBO) and process them in parallel.
uniform's are limited in number. I think I read somewhere that the Intel chip sets have a 16kb limit, whereas AMD/Nvidia have a 64kb limit, and I generally get an error if I try to use more than 1024
I managed to solve my original problem by passing in a VBO of
attribute float a_vertexId. I could then get the
a_vertixId / 6 and the
a_vertexId - spriteId * 6. This enabled me to index into a
uniform of canonical square vertices, which meant I didn't have to upload sprite vertices any more.
I was also able to address texture
uv coordinates in this way too. I just sent up a texture ID per vertex via
attribute float a_texId, then used the
a_texId to index into a
uniform texture coordinates array. Even given the limits on the number of
uniform's allowed this is able to cope with around 1000 different textures per sprite atlas.
For the transformations, I just sent up the transfer parameters (per-vertex), i.e.
(tx, ty, angle, scalar) and applied the following matrix inside the vertex shader:
\sigma\cos(\theta) & -\sigma\sin(\theta) & 0 & t_x \\
\sigma\sin(\theta) & \sigma\cos(\theta) & 0 & t_y \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
Ideally, it would be best to upload a per-sprite array of transformation matrices as a
uniform, and use
spriteId to index into that for the per-vertex processing. However, as mentioned above, given that we could have 1000's of sprites means that this just isn't possible. I have heard of Shader Storage Buffer Objects (SSBO's) in ES 3.0, but from what I've read, they're not always available in the vertex shader.
In general, I realised performance will differ between systems, and there will be a trade-off between data upload, and CPU vs GPU processing, so ultimately I'm going to create a few different sprite batch shaders and do some tests to see which ways work best.
Anyway, hope this helps someone.
In the end I used two shader programs, (i) a
GL_TRIANGLES batcher for "dynamic" sprites that required rotation, scaling, translation, with transforms performed on the GPU as described above, and (ii) a
GL_POINTS batcher (i.e. Point Sprites) for "static" sprites (such as background tiles) that required only scaling and translation (but not rotation); then again, it is possible to program the fragment shader to perform rotations on point sprites, but this is slow and comes with it's own limitations, e.g. it's the point sprite texture
uv coordinates that are rotated and not the vertices so you have to be careful how you set up your sprite textures to avoid local boundary clipping.