Note: In an ideal world there would be a way to know if full 32-integer support is present (say via a predefined macro) so shaders could have multiple hash & random number generators for both cases.
When you do have full integer support, you can use integers all the way down. Older and current WebGL shaders make thing blurry since they are (more or less) using variants of one function for both purposes.
NOTE: All of my code is "typing in post" so I could be dropping the ball somewhere.
So the 2D->1D hashing function I was intended to suggest is:
uint hash(in uvec2 c) { c = uvec2(0x3504f333, 0xf1bbcdcb)*c; return 741103597*(c.x^c.y); }
The floating point version of this might be problematic...it has a smaller domain than the classic sin-based. Once you've hashed an n-dimensional coordinate you have an integer value which can be used as your first random number (with rescaling). When you need more random numbers, you switch to using an integer based random-number generator of some sort for the rest. As an example the classic LCG, reworked to be a little easier to use:
// returns [0,1]
float u32tofpi(in uint v) { return (1.0/4294967296.0)*float(v); }
// returns [0,1)
float u32tofp(in uint v) { return (1.0/16777216.0)*float(v>>8); }
// base LCG
uint lcg(in uint v) { return 741103597*v + 0x1234567; }
// return the currect value as a float and then updates
float rng(inout uint v} { float r = u32tofp(v); v = lcg(v); return r; }
This could be replace with anything else, say an Xorshift for higher quality (in terms of randomness). So a makeshift example might be:
void doSomething(vecN coord)
{
uint seed = hash(coord); // use hash to get first integer from coordinate
float n0 = rng(seed); // transform 'seed' to first float and update state.
float n1 = rng(seed); // second random number...
// however many more is needed. This helper function way I've structured
// this means we will always be doing one more integer random number
// generating step than needed. Since I've typed it that way I'll leave as is.
}