Test Assumptions

I’m playing with WebGL and had this method on my renderer:

bufferVectors(vectors: Vector[]) {
  const vectorData = vectors.map(v => v.toArray()).reduce(flatten, []);

  const data = new Float32Array(vectorData);

  this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vectorsBuffer);
  this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.gl.STATIC_DRAW);
}

I added 2 performance logs in a few places as I noticed drawing to the canvas being slow as time went on. I found this was the culprit:

const t1 = performance.now();

this.bufferVectors(verts);
this.bufferColors(colors);

const t2 = performance.now();
console.log("timetaken:", t2 - t1 + "ms");

I assumed this was as I was rebuffering all the data to the GPU, as opposed to adding additional data to it using gl.bufferSubData instead of gl.bufferData. I tried to implement this but docs for WebGL2 are poor at best and could not find any useful blog posts on this. I thought this was odd as it seemed like a common case.

Turns out I was wrong. I should not have assumed this, especially because as this is an unfamiliar api to me. gl.bufferData is fast. I should have tested this earlier to truly understand why the performance hit was occurring.

After adding a few more logs inside, turns out the first line was the culprit:

const vectorData = vectors.map(v => v.toArray()).reduce(flatten, []);

Why? The complexity of this is O(n) and whilst linear time is not crazy slow, we are doing unnecessary operations and allocations here on the whole data set, as opposed to allocating once and operating only on the data subset that changed since the last render.

This was for a whiteboard pen marker. A certain number of vectors representing a single ‘dot’. How many? Well with a poly count of 64, each dot mesh is 192 vertices!!

There are many solutions to this. In this scenario we are merely adding marks and without any ability to undo / change existing marks, we can make a reference to the array and add any new data to the end. There are probably some other WebGL techniques where we store the entire mesh once on the GPU and keep resetting the offset.