20151230

Better 3D graphics on the Arduino: avoiding flickering and tearing

A while ago I purchased a cheap $4 Chinese LCD Arduino shield from Ebay (e.g 1 2 3). The board arrived with no documentation. Disassembling the shield revealed no ICs, indicating that the driver is integrated with the LCD itself. Upon request, the vendor provided an archive containing a few amusingly translated datasheets, as well as a copy of Adafruit's Arduino LCD drivers. Evidently the product is a clone of the Adafruit LCD shields, and uses the ILI9341 LCD driver. I had hoped to use the display to show short animated GIF loops. This is not, in practice, possible. Test animations loaded slowly, with noticeable flicker and vertical tearing. The Arduino does not have enough speed or bandwidth to render full-screen animation frames, but what about 3D vector graphics?



20150827

Wilson-Cowan neural field model, in HTML/JavaScript, in under 512 bytes

This started essentially as a bet made at a bar. It turns out that one can make a Wilson-Cowan neural field demo in under 512 bytes in HTML/JavaScript, so long as one is willing to use invalid HTML that will only render in Firefox.

View demo hosted on Dropbox (Firefox only)

For these parameters, the simulation starts out with noise. Radiating waves slowly form. Eventually the solution converges to spiral waves.






Here's the source. There are 466 bytes of actual JavaScript and and 491 bytes total.
<canvas><script>c=document.body.lastChild
c.width=c.height=W=256
Z=c.getContext('2d')
l=Z.getImageData(0,0,W,W)
N=W*W
I=()=>Float32Array(N)
t=I();b=I();f=I();g=I();h=I()
K=(w,t,W)=>{for(i=N;i--;){w[i]=0;for(j=3;j--;)w[i]+=(j*4+2&7)/5*t[W*j-W+i&N-1]}}
w=()=>{K(t,b,1);K(g,t,W);K(t,f,1);K(h,t,W)
for(i=N;i--;){B=(j)=>.7/(1+Math.exp(j+3+4*h[i]-5*g[i]-Math.random()))
b[i]=B(6*h[i]-g[i])+b[i]*.3
f[i]=B(1)*.13+f[i]*.91
l.data[i*4+3]=b[i]*(W-1)}Z.putImageData(l,0,0);setTimeout(w,0)};w()</script>


In addition to refactoring expressions to reduce size and using invalid HTML, the code also takes advantage of the facts that
  • indecies into a 256 x 256 array can be wrapped horizontally and veritcally by masking with 0xffff, yielding periodic boundary conditions for free
  • a succinct way to get a grayscale canvas on a white page is to just write to the alpha bytes of the canva's image data
  • "for(i=N;i--)" iterates over 0 to N-1 in reverse order, and is shorter than "for(i=0;i<N;i++)"
It's actually very readable if you add whitespace and comments back in.
c=document.body.lastChild// get the canvas (it's the only element)
c.width=c.height=W=256   // set canvas size (and width constant)
Z=c.getContext('2d')     // get graphics context
l=Z.getImageData(0,0,W,W)// get handle to image data buffer
N=W*W                    // define a constant the length of the data buffer
I=()=>Float32Array(N)    // define short function for initializing buffers
t=I()                    // make scratch for convolution intermediate values
b=I()                    // this will be the E-cell buffer
f=I()                    // this will be the I-cell buffer
g=I()                    // buffer to hold blurred E-cell state
h=I()                    // buffer to hold blurred I-cell state
K=(w,t,W)=>{             // function for separable convolution.
                         // blurs from NxN buffer t into 256x256 buffer w
                         // W is the step size. If it is 1, the blur will be
                         // horizontal. If it is 256, the blur will be 
                         // vertical. Gaussian is convolution is separable
                         // in x/y, so we can do the 2D blur by first 
                         // doing the x and then the y
    for(i=N;i--;){       // iterate over all points
        w[i]=0;          // initialize accumulator to zero
        for(j=3;j--;)    // iterate over kernel
                         // The expression (j*4+2&7) saves one character over
                         // the more readable [2,6,2][j]/5
                         // we wrap indecies in x and y by masking with
                         // 0xffff i.e. N-1
            w[i]+=(j*4+2&7)/5*t[W*j-W+i&N-1]
    }
}
w=()=>{
    K(t,b,1)             // convolve E-cell buffer in x direction
    K(g,t,W)             // convolve E-cell buffer in y direction
    K(t,f,1)             // convolve I-cell buffer in x direction
    K(h,t,W)             // convolve I-cell buffer in y direction
    for(i=N;i--;){       // for every point
                         // B is an update function
                         // includes firing rate nonlinarity and some of the
                         // Wilson-cowan update -- expression have been
                         // refactored to minimize size
        B=(j)=>.7/(1+Math.exp(j+3+4*h[i]-5*g[i]-Math.random()))
        b[i]=B(6*h[i]-g[i])+b[i]*.3 // update E-cell field
        f[i]=B(1)*.13+f[i]*.91      // update I-cell field
        l.data[i*4+3]=b[i]*(W-1)    // store E-cell value in alpha channel
    }
    Z.putImageData(l,0,0);          // update image
    setTimeout(w,0)                 // start next frame
};
w()                                 // start simulation