If you want a non GPU version here's a compact JavaScript thing that I use sometimes.
const min = Math.min;
const max = Math.max;
const median = (a, b, c) => max(min(a, b), min(max(a, b), c));
const intHash = x => {
x *= 0xed5ad4bb;
x ^= x >> 11;
x = Math.imul(x,0xac4c1b51);
x ^= x >> 15;
x = Math.imul(x,0x31848bab);
x ^= x >> 14;
return x;
}
const makeRandom = seed => {
let index=0;
let random = (x=index) => {
let pattern = intHash( (x + seed) & 0x7fffffff)
let intValue = intHash( (x + pattern) & 0x7fffffff);
index=++x;
return intValue / 0x7fffffff;
}
return random;
}
let ss=(v,a=0,b=1,w=v*v*v*(v*(v*6-15)+10))=>(1.0-w)*a+(w*b);
let positiveMod=(v,size,q=v%size)=>v<0?size+q:q;
let clamp = a=> a<0?0:a>1?1:a;
let perlinRandom = makeRandom(seed);
let perlin=(x,y,wrapX=256,wrapY=wrapX)=> {
let dg=(ix,iy,gi=perlinRandom(positiveMod(iy,wrapY)*wrapX+positiveMod(ix,wrapX)*2)*Math.PI*2) =>
((x-ix)*Math.sin(gi)) + ((y-iy)*Math.cos(gi));
let u=0|x;
let v=0|y;
let sx=x-u;
let sy=y-v;
let u1=(u+1);
let v1=(v+1);
return ss(sy,ss(sx,dg(u,v),dg(u1,v)),ss(sx,dg(u,v1),dg(u1,v1)));
}
let wrapPerlin=(x,y,wrapX=2,wrapY=wrapX)=>perlin(x*wrapX,y*wrapY,wrapX,wrapY);
It's a bit cryptic because it's used for size coding, ss() is a smoothstep. dg() is apparent enough if you look at any Perlin description. wrapPerlin can be used if you want 0..1 to repeat n times.