It delay framerate:fps

Linear waves on 512×512 square.

2D wave equation

For the wave equation
    t2u = Δu ,     u|t=0 = exp(-(r - ro )2/ a2),     ∂tu|t=0 = 0,
the simple explicit scheme
    ux,yt+1 = 2ux,yt - ux,yt-1 + (ux+1,yt + ux-1,yt + ux,y+1t + ux,y-1t - 4ux,yt)(Δt/Δx)2   (*)
on square grid is used.

Simulations on GPU

To use calculated data many times (on every time step) we store them in 3 Float32 N×N textures. Every pixel corresponds to a lattice node. We need only one component texture (i.e. LUMINANCE) but WebGL shader can render to RGBA textures yet. To render the fragment shader output to these textures they are bound to FBO, FBO1, FBO2 correspondingly. In accordance with (*) calculations are made in 3 steps:
    texture + texture1 → FBO2 (texture2)
    texture1 + texture2 → FBO (texture)
    texture2 + texture → FBO1 (texture1)
The first fragment shader makes the main calculations in accordance with (*). All pixels are rendered into texture attached to one of FBOs
<script id="shader-fs" type="x-shader/x-fragment"> 
precision highp float;
  uniform sampler2D samp;
  uniform sampler2D samp1;
  varying vec2 vTexCoord;
  const float d = 1./512., dth2 = .2;
void main(void) {
   float u = texture2D(samp, vTexCoord).r;
   float u1  = texture2D(samp1, vTexCoord).r;
   u = 2.*u1 - u +
     (texture2D(samp1, vec2(vTexCoord.x, vTexCoord.y + d) ).r +
      texture2D(samp1, vec2(vTexCoord.x, vTexCoord.y - d) ).r +
      texture2D(samp1, vec2(vTexCoord.x + d, vTexCoord.y) ).r +
      texture2D(samp1, vec2(vTexCoord.x - d, vTexCoord.y) ).r +
      - 4.*u1)*dth2;
   gl_FragColor = vec4(u, 0., 0., 0. );
}
</script> 
The second fragment shader is used to show computed texture on screen
<script id="shader-fs-show" type="x-shader/x-fragment"> 
precision highp float;
  uniform sampler2D samp;
  varying vec2 vTexCoord;
void main(void) {
   float c = texture2D(samp, vTexCoord).r;
   if (c < 0.) gl_FragColor = vec4(0., 0., -c, 1.);
   else gl_FragColor = vec4(c, 0., 0., 1.);
}
</script> 
The vertex shader calculates vertex position and texture coordinate
<script id="shader-vs" type="x-shader/x-vertex"> 
  attribute vec2 aPos;
  attribute vec2 aTexCoord;
  varying   vec2 vTexCoord;
void main(void) {
   gl_Position = vec4(aPos, 0., 1.);
   vTexCoord = aTexCoord;
}
</script> 
Similar to the fractal generator to execute fragment shaders we draw 2×2 square. The function draw() starts calculations twice then shows result
function draw(){
   gl.useProgram(prog);
   gl.uniform1i(sampLoc, 0);
   gl.uniform1i(samp1Loc, 1);
   gl.bindFramebuffer(gl.FRAMEBUFFER, FBO2);
   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

   gl.uniform1i(sampLoc, 1);
   gl.uniform1i(samp1Loc, 2);
   gl.bindFramebuffer(gl.FRAMEBUFFER, FBO);
   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

   gl.uniform1i(sampLoc, 2);
   gl.uniform1i(samp1Loc, 0);
   gl.bindFramebuffer(gl.FRAMEBUFFER, FBO1);
   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

   gl.useProgram(prog_show);
   gl.bindFramebuffer(gl.FRAMEBUFFER, null);
   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
To avoid Read-and-write-to-the-same-texture 3 textures are "ping-ponged".
Simulations on GPU
updated 22 Oct 2013