Skip to content

WebGL implementation

starwed edited this page Apr 10, 2014 · 3 revisions

How our WebGL backend works

First the broad picture of what's necessary:

Rendering an entity with WebGL requires two things

  • A pair of shader and vertex programs that take data related to the entity and draw it to the canvas
  • A routine that creates that data and buffers it

The rendering routine

WebGL ultimately renders anything as a mesh of triangles. Crafty's rectangular entities will be decomposed in to two triangles each. To get the best performance, we want to render as many entities in one pass as possible. To that end:

  • Each entity has four rows of vertex data.
  • This data lives in an attribute buffer.
  • We render using drawElements with the gl.TRIANGLES argument, which allows us to render multiple disjoint entities in one pass.
  • We use an index buffer to let GL know what order to render the vertices in the attribute buffer. Each entity is represented by six indices.
    • As an example, if there's only one entity, the index buffer would just be [0, 1, 2, 1, 2, 3]; the entity is drawn as two triangles.
  • To allow proper transparency, the entities must be ordered by their z value; this is also done by the index buffer.

We can render multiple entities which use the same program at a time, but can't interleave, say, Color and Sprite entities, or even Sprite entities which don't use the same texture.

So our rendering routine goes through all the entities in z-order, and every time it needs to switch programs, renders the current batch. (An entity contains an explicit reference to its program, so we can just check whether that's equal to the current program or not.)

The arrays and buffers are kept separately by each program object.

The interface between WebGL and Crafty

The main functionality sits in three places:

  • The Crafty.webgl object implements anything involving the GL context directly. It handles intiating the context, responds to viewport events, and provides methods for compiling programs and binding creating textures.
  • The "WebGL" component provides a framework for initiating entities, and the generic part of their drawing routine
  • The RenderProgramWrapper object provides a convenient way to create and define programs.

Components

If a component wants to support WebGL, it has to call ._initShader with

  • A unique ID for the program it will use
  • The source code for the fragment shader
  • The source code for the vertex shader
  • A listing of the attributes it uses

This will return a RenderProgramWrapper object.

(If using a texture, it will also need to init the texture, and provide unique ids for each image.)

It then needs to bind a drawing routine to the "Draw" event. Typically this will only consist of writing the required component-unique data into the attribute array. The "WebGL" entity will provide the general 2D data (position, rotation, etc.)

RenderProgramWrappers

These objects keep references to the actual renderprogram to be used, track the locations of buffers and attributes, and handle the typed arrays which are copied into the GL buffers.

They also implement switchTo and renderBatch methods which are called by the main rendering routine. These could potentially be overwritten if a component needed to do something fancy.

The shaders

The vertex and shader program are written in HLSL. To increase readability/maintainability, these are kept as separate files which are inlined during the build process.

Uniforms and attributes

The vertex program takes information about the entity, and renders it accordingly. This information is a mixture of uniform variables (shared by all entities) and attributes. Data which varies between entities but not per vertex (such as the color or rotation of an entity) must unfortunately be duplicated for each vertex, so that it's accessible to the vertex shader. (We can't use uniforms because we need to render multiple entities per pass.)

There are some variables which are necessary in any program:

  • uniform uViewport: <x, y, scaled width, scaled height> the coordinates of the viewport combined with its current scale
  • attribute vec2 aPosition: <x, y> The actual location of each vertex.
  • attribute vec3 aOrientation: <ox, oy, rotation> The actual (not relative) position of the entity's center and its rotation about that center. (Duplicated)
  • attribute vec4 aExtra: <globalZ, alpha, flipX, flipY> flipX and flipY aren't used, and globalZ actually has no effect in normal rendering (see above re: the z-ordering). (Duplicated)

The "Color" entity defines

  • attribute vec4 aColor: <r, g, b, a> Non-premultiplied color values. (Duplicated)

The "Sprite" entity defines

  • attribute vec2 aTextureCoord: <u, v> The coordinates on the sprite sheet associated with each vertex.
  • uniform vec2 uTextureDimensions: <w, h> the width and height of the base texture.