WebGL renderer anatomy
Series context
Renderer is one of those overloaded terms in programming that gets thrown around casually by the domain-literate but means very little to the unitiated. In this series, we’ll explore what a rendering system is and how it is designed. Most code snippets are pseudo-javascript.
Part 1: Caching
Several types of data should be cached by a typical 3d renderer:
- Textures: arrays of data containing images for sampling while drawing
- Geometry: vertices, texture coordinates and normal vectors for models
- Shaders: compiled programs that execute on the GPU
These values can usually be cached once during the setup phase of your application or scene. This prevents you from needing to send megabytes of data over the bus from CPU to GPU during your tight 16ms (60 fps) frame budget. The bandwidth of this bus is somewhere on the order of 10–50 gb/s so unneccesary data transfer should be avoided.
In this code below, data is created in the CPU program and then uploaded to the GPU by the renderer. References are stored by string to these cached values for use when drawing.
//reference to the CPU memory address of the stupidCat image
var stupidCat = new Image("/textures/stupidCat.png")//GPU shader program consisting of a vertex and fragment shader
var catProg = new Program(vertexShaderSource, fragmentShaderSource)//lists of vertices, texture coordinates and normal vectors for cat
var catGeo = new Geometry(catVerts, catTextureCoords, catNormals)var renderer = new Rendererrenderer.bufferTexture("cat", stupidCat)
renderer.loadProgram("cat", catProg)
renderer.bufferGeometry("cat", catGeo)renderer.bufferedGeometries("cat") //handle to GPU buffers for cat
renderer.loadedPrograms("cat") //handle to GPU program for cat
renderer.bufferedTextures("cat") //handle to GPU texture for cat
If we assume that you need a texture, geometry, and shader to draw a cat, we can now draw our cat as many times as we like while only sending the x,y,z location of the cat to the GPU for each draw.
//xyz = 0,0,0, lookup shader,geometry, and texture by string name
renderer.draw(0, 0, 0, "cat", "cat", "cat")
The upcoming article on batching and queuing will explain why the api shown above is not common. However, the spirit of this API is exactly correct and even much more sophisticated renderers will still essentially do this in their internal implementation.
If you have feedback/questions send them to @stv_kn on twitter and I hope you’ll stay tuned for the rest of the short series.