We introduce a black-box render function which draws circles to the screen, explaining how the position information is represented in the memory for the renderer (a typed array of [x1, y1, r1, x2, y2, r2, …]). We draw a simple circle (at [0, 0, 100]), then many random circles to get a feel for passing rendering data by directly setting Typed Array memory. The details of the renderer are not discussed at all. We set up some simple dynamics and wall bounds, and then push the circle count up over 1,000,000 renders to demonstrate the performance of dealing with raw memory and WebGL.
Demo repo: https://github.com/guybedford/wasm-demo
Note the demo is currently only supported in Chrome Canary with the Experimental Web Platform flag (chrome://flags/#enable-experimental-web-platform-feature) enabled due to the use of ES modules and WebGL 2.0.
[00:00] We're going to use a typed array to render a number of circles directly into the page with incredibly high performance. We've got a canvas inside the body and then I'm importing a render function from a local render module, which we're just going to treat as a black box.
[00:15] This random circle takes the circle data as its first argument and the number of circles as the second argument. As a first example let's just render a single circle.
[00:24] That circle data is then exactly our typed array which represents its position and size on the screen. This position data is decimal data, and so uses the float data typing memory. Each circle is based on three numbers; its x value, its y value and its radius.
[00:41] Which are each going to be this 32-bit floating point numbers. Let's set the circle initially, where its x value is at zero, its y value is at zero and its third slot, the radius, is set to 100. That typed array data then gets copied straight to the graphics card and drawn to the screen.
[01:02] Let's position the circle randomly within the page. The third argument to the random function is an inept method. Which will get the display of width and the display of heights as convenience arguments. We can then move our assignment of the circle positions to within this inept function.
[01:19] Set the x value to a random point within the width of the page and the y value to a random point within the height of the page. To then draw any number of circles we need to make our inept function depending on the circle counts.
[01:33] On our initial data allocation we're going to allocate an x, y and r value, times the number of circles. Within the inept function we can do a loop from the first data points of the first circle, through to the last data point of the last circle, incrementing three points at a time for each circle.
[01:59] I can then update the indices to be relative to the current circle. Let's try rendering 10 circles. To add dynamics, the fourth argument to our random function is a time step function.
[02:12] This has the same signature as our initialization function, except it allows us to then modify the data positions on each frame. If we just re-randomize the positions we'll get something quite catastrophic, although it's no problem at all for the WebGL renderer.
[02:33] To properly have dynamic movement we want to store the velocities as another data points substantiated with the circle, as well as its x, y and r value.
[02:41] We can't include this in our current circle data because this represents a single piece of continuous memory that's sent to the GP on render, and it needs to be as compact as possible to only send what's necessary for rendering, in order to get the best performance.
[02:54] We're actually going to create a separate typed array which is going to contain the velocity values. We just need to have two values within this float-32 array. They're still 32-bit floats because they're decimal values. Now on our initialization function we can set the velocities to random x and y components.
[03:13] In order to index the velocities we're going to need a new index counter. Because the velocities move in sequences of two we need to index through in an order of two for these.
[03:28] In our time step function we're going to increment the x and y components by the velocity values. Convey again at the separate increment of two for the velocities array. I'm just going to remove these unnecessary re-assignments and add some x detection.
[03:55] If the x value of the circle is greater than display width or less than zero, we're going to flip the x component of the velocity, and the same for the y component. We can polish up the exact balance later. For now let's test the performance.
[04:12] Decreasing the circle radius, let's start upping that circle count. Lowering the radius again we can try 1,000. Or 10,000. Lowering it even further we can see if we can get to 100,000. Or even a million circles being rendered.
[04:42] By using typed arrays and various simple operations in JavaScript, we've been able to write very, very low-level card that acts directly on top of memory. Combined with the WebGL renderer, this is what gets us the incredible performance.