Gopher meet Plasma: A WebAssembly Experiment
Tags: javascript, webassembly, graphics, go
Published: 2019-03-03 17:32:03

---
title: Gopher meet Plasma: A WebAssembly Experiment
published: true
tags: javascript,webassembly,graphics,wasm,go
canonical_url: https://blog.jeremylikness.com/gopher-meet-plasma-a-webassembly-experiment-4048e4d3b8d7
---

#### Using WASM coded in Go to generate a plasma effect

In the early days of programming, hackers around the world participated in what was referred to as the “[demo scene](http://www.pouet.net/).” It still thrives today but I am mostly familiar with the “old school” phase that mainly involved using hardware hacks and other tricks to push personal computers to their limits. We could “trick” the video processor into drawing in areas it wasn’t supposed to and with clever timing produce more colors or sprites than were “allowed.” It was a fun time and I learned a lot.

![Picture of a marmot standing on a mountain top](https://cdn-images-1.medium.com/max/612/1*_2V1IMizsnkrAFZ2gGkmxg.jpeg)<figcaption>I don’t have a gopher picture. Settle for a marmot?</figcaption>

One popular effect is called plasma. It not only looks cool but can be processor intensive so pulling it off is considered a great trick. Really good programmers were able to generate transparent plasma and overlay multiple layers, generate “interference” patterns and even rotate the canvas on the fly.

In 2010 I decided to take some “old school” effects and implement them [in Silverlight](https://csharperimage.jeremylikness.com/2010/12/old-school-silverlight-effects.html). Later, I brought them over [to Windows 8](https://csharperimage.jeremylikness.com/2012/12/going-old-school-on-windows-8.html) and finally made a [JavaScript implementation](https://jsfiddle.net/jeremylikness/bVY6t/) using the canvas. Now that I’m dabbling in WebAssembly, plasma seemed to be a perfectly reasonable effect to try out. I chose to tackle it with the Go language to continue to improve my knowledge. I always enjoy learning new languages.

> Hey, look! If you're more of a Rust person, I did the same experiment with Rust! Check it out:

{% link https://dev.to/jeremylikness/plasma-gets-rust-y-another-webassembly-experiment-10d2 %}

Now, back to Gophers and Go...

👀 See it in action [here](https://blazorhealthapp.z5.web.core.windows.net/plasma.html)
🔗 Access the source code [here](https://github.com/JeremyLikness/PlasmaWasmGo/)

### First Attempt: Go All the Way

For my first attempt, I followed the excellent “[The world’s easiest introduction to WebAssembly with Go](https://medium.freecodecamp.org/webassembly-with-golang-is-fun-b243c0e34f02)” tutorial. I took a similar approach and set up everything inside the Go app to perform interop with the HTML DOM. I won’t duplicate the effort here to get started, but in general after you have a current copy of `wasm_exec.js` and some bootstrap code, compiling Go to WASM is as simple as:

`GOOS=js GOARCH=wasm go build -o plasma.wasm plasma.go`

My setup looked like this:

{% gist https://gist.github.com/JeremyLikness/ac6c8ec2fe45577b7eed8c9c581eaaff %}

This uses the `syscall/js` library (experimental and not supported) to create references back into the HTML and JavaScript world. The palette was built was an array of strings ready to set as fill styles for the canvas.

{% gist https://gist.github.com/JeremyLikness/45a1563532477e2edfbd08b45dd34ebb %}

Another method sets up a sine table. This is what produces the plasma cycling effect. It is generated into an array to minimize the overhead of real-time computations.

{% gist https://gist.github.com/JeremyLikness/32fba60a3666acc4700aa1fd448d3f61 %}

The “main loop” is concerned with advancing pointers through the sine table and mapping the palette based on the position. It updates several variables that are part of the global state and cycle through the sine table and palette. For the first attempt, I set the fill style and rendered the pixels directly from Go.

{% gist https://gist.github.com/JeremyLikness/14f1e95b84dab705c2f131f956cb0c82 %}

The last piece is to kick off the application, ensure it always stays loaded, and repeatedly call the update method.

{% gist https://gist.github.com/JeremyLikness/eb51a22e03c22f2c08a15486663338c8 %}

The `plasmaLoop` is a channel that never clears so everything continues to run. The `renderer` wraps the call to `updatePlasma` in a JavaScript callback that can be passed into `setTimeout` (yes, I am aware this could be `requestAnimationFrame` as well).

I compiled the app to WASM and ran it. The result was dismal. I was able to produce the plasma effect, but it was so slow it looked horrible. It ran far slower than the JavaScript-only version!

### Drawing in JavaScript

My next attempt was to build the pixel buffer in Go, pass it to JavaScript and render it there. First, I moved the JavaScript to a new `plasma.js` file and added this method to parse and render the buffer.

{% gist https://gist.github.com/JeremyLikness/1d91983ce0ebc20725fd053b7e857bab %}

Notice that I can’t accept an array directly (hmmm … or can I? If you know a better way, please weigh in!) so I accept a base-64 encoded string and convert it back to a string. Then I simply iterate the pixels, set the fill style and draw the rectangles.

To pass the array, I changed the colors from string to structures with bytes for the red, green, and blue values (I don’t use blue but have it there in case I ever go back to change the palette) and added a buffer to pass back.

{% gist https://gist.github.com/JeremyLikness/a60213e7e5cf1c1306145b977966fbb4 %}

The palette is populated by setting the values rather than generating strings.

{% gist https://gist.github.com/JeremyLikness/78e5872b33b857168bd48c6d976ef94a %}

Instead of calling into JavaScript every pixel, the values are pushed into the buffer.

{% gist https://gist.github.com/JeremyLikness/3458a42f21b53bb74a4558702b918ec0 %}

Finally, the rendering function base-64 encodes the bytes and passes them to the JavaScript function.

{% gist https://gist.github.com/JeremyLikness/de19999340ca8fe3dd64af5a7a25f963 %}

This improved the speed tremendously, but the performance still wasn’t as fluid as I wanted.

### Pixels are Pixels

Part of the performance problem is that the plasma map represents pixels, but I was drawing them using little rectangles. This means the canvas must track every point as an entity to render. Fortunately, there is another way. JavaScript supports an `ImageData` class that is a buffer of pixel information for a bitmap. You can create an instance and populate it. It expects the bytes to represent red, green, blue, and alpha channel, so there are four bytes for every pixel.

Building the buffer is easy. Instead of making it three bytes per pixel, I simply set it to four bytes and always set the alpha channel to its maximum value (255). The Go code passes the array the same way as before, the only change is on the JavaScript side.

The code needs to decompose the base-64 encoded value into an unsigned byte array. It is simple enough to iterate through each byte and populate the target array. Then, the image data is created and drawn in one pass on the canvas.

{% gist https://gist.github.com/JeremyLikness/22572212bd3ec688a855fa8ae2be5f3a %}

The performance for this approach is amazing!

![Moving Plasma Effect](https://cdn-images-1.medium.com/max/320/1*_rlAD6nZr1pUTM_vZAfQOA.gif)<figcaption>The final result</figcaption>

Only now, I realized my main optimization is in the JavaScript code. So, for an 🍎 to 🍎 comparison, I updated the JavaScript version to use the bitmap approach as well. You can [see for yourself](https://jsfiddle.net/jeremylikness/1xfh3c25/) it performs as well, if not better, than the WASM approach, so there is still work to be done! For now, however, it was a fun way to learn Go, WebAssembly, and dabble in some old school effects.

👀 Watch the live demo [here](https://blazorhealthapp.z5.web.core.windows.net/plasma.html)
🔗 See the full source code with instructions [here](https://github.com/JeremyLikness/PlasmaWasmGo/)

Regards,

![Jeremy Likness Signature](https://cdn-images-1.medium.com/max/197/1*KwXn3ElS7vnWIk8lNVhBsA.gif)

* * *