Design and Architecture of GLmol

Here I summarize design principles of GLmol and explain why I made such decision. I also describe current limitations and ideas for future development. I hope this document is helpful for developers, not only of GLmol but also other WebGL/Javascript based applications.

Comment and suggestions are welcome. Please mail to biochem_fan at sourceforge.jp.

This document is not completed yet. Some parts are missing. Also please feel free to copyedit this page and fix grammatical errors.

Basic design principles of GLmol

  • Fully written in Javascript. Do not depend on Java, Flash, server-side preprocessing etc.
  • Works smoothly. Even mobile browsers should be able to handle moderate-sized proteins.

Toward this goal,

  • Use GPU as much as possible.
  • Reduce transfers between main memory and video memory.
  • Calculate once, render many times.

3D visualization of molecules

Three dimensional visualization of molecules involves following steps.

  1. Reading atomic coordinates.
  2. Analyzing data. (e.g. Bond detection)
  3. Computing three dimentional representation (polygon generation).
  4. Rendering
  5. User interaction

As I will describe below, step 1 to 3 are done only once unless user changes representation.

Loading coordinates

Atomic coordinates are provided in many formats. Popular formats include PDB, MDL MOL/SDF and CIF. PDB and MOL are easier to parse because they are fixed-length format. CIF is more complex as it is free-spaced and has complex syntax like "repeat".

Currently, GLmol supports PDB and MOL/SDF. Supporting CIF is possible but I am not sure if it is worth doing. Chemists can easily convert CIF files to PDB or MOL.

Analyzing data

Bond detection

While MOL/SDF format contains a bond table, PDB format contains bonding information for only heteroatoms (= non standard residues and ligands). So we have to detect it.

Current bond detection algorithm is very simple. Iterate over atom pairs and if the distance is within threshold, we assume a bond is present. This naive O(n2) algorithm is too slow for large molecules. To make it faster, GLmol considers only atom pairs with similar serial numbers. This is justied because atoms in PDB files are sorted by residue ID and a atom can bond only to atoms in the same or neighboring residues (there are exceptions but they are explicitly described in BOND record).

Secondary structure assignment

Secondary structure is read from "SHEET" and "HELIX" records in the input PDB file. These records are present in PDB files downloaded from RCSB PDB. However, PDB files produced by refinement programs, modeling programs and simulation programs do not usually contain this record and GLmol fails to show their secondary structures.

De-facto standard of secondary structure assignment algorighm is DSSP. Although it is technically possible to implement it in Javascipt, much simpler and faster algorithm might be more suitable for GLmol. For example, we can use Ramachandran plot to assign secondary structures (but with less accuracy).

Computation of three dimentional representation

This is the most time consuming part of GLmol. It often takes several hundreds of milliseconds. For example, in the case of 2POR on my PC, polygon generation took 103msec while parsing and rendering took only 11msec and 5msec, respectively (you can check it on Firebug or Chrome console).

While "sphere", "line" and "stick" representation are trivial to construct, "ribbon" and "strand" for proteins are more difficult. Basic idea is to smoothly connect alpha carbons of protein's backbone (or O3' of nucleic acids). This is done by Catmull-Rom subdivision. Vectors connecting alpha carbons to carbonyl oxgens are used to define ribbon's plane. Special care must be taken for beta sheets as this vector flips residue by residue. Secondary structure (alpha helix and beta sheet) is distinguished from loops by being drawn wider.

For molecular surface calculation, please read http://webglmol.sourceforge.jp/surface.html.

Limitations and Future perspectives

I think GLmol's graphics is beautiful enough but we can improve. For example, you can give thickness to ribbons (Implemented in 0.40). Displaying arrowheads for beta sheets would be a nice improvement.

Also, you might want to "smoothen" beta sheet. This can be achieved by connecting midpoints of alpha carbons instead of alpha carbons itself. I have tested this and indeed it worked nicely (Implemented in 0.40). The drawback is that now the ribbon doesn't necessarily pass through alpha carbons so that side chain rendering gets trickier. Just connecting an alpha carbon and a beta carbon makes "floating" sidechains.

Drawing nice cartoon diagrams is a very complicated task. PyMOL does lots of calculation and fine-tuning to achive it. Indeed, PyMOL's implementation of cartoon representation is longer than the entire source code of GLmol! We can pursue this direction but it would make GLmol bigger and slower. I want to keep GLmol compact and fast. I think my current implementation achieve a good blance in this regard.

Rendering

GLmol renders a scene by WebGL through THREE.js library https://github.com/mrdoob/three.js/. Polygons generated above are stored as THREE.Object3D objects and added to the scene (THREE.Scene). A scene also contains a camera and lights.

THREE.js automatically converts polygon definitions in Javascript world (THREE.Face3, THREE.Vertex, THREE.Color, etc) into TypedArray and transfers them to GPU. Polygons are stored as VBO (Vertex Buffer Object) in GPU's memory. Therefore, rendering itself (call to glDrawArrays/glDrawElements) is very fast, indeed comparable to native code.

Symmetry mates

Crystallographic packing diagrams and biological assemblies can be constructed by applying symmetry operations to atoms in the asymmetric unit. The symmetry operations are written in PDB files as 4x4 matrix. Multiplying every atomic coordinates by this matrix is tough work for Javascript. It is also waste of time to generate polygons for each symmetry mates.

GLmol keeps track of only atoms within the asymmetric unit. Polygons are generated for these atoms. Symmetry mates are drawn by repeatedly rendering them with different transformation matrices. Now symmetry operations are carried out on GPU. Very quick!

As GLmol doesn't store atomic coordinates of symmetry neighbors, bonds across two asymmetric units cannot be detected. Although this is not a problem for biomolecules, inorganic crystals cannot be drawn correctly.

User interaction

User interaction means rotation/translation/scaling of molecules by mouse, keyboard or touchpanel. They can be (and MUST be) handled by GPU without changing polygon definition. We just change transformation matrix (model-view matrix) and render the same scene. We should never change polygon definitions in event handlers; otherwise, performance is severly compromised.

TODO: Write about modelGroup and rotationGroup

Performance issues

Calculate once, render many times

Classical molecular visualization programs construct 3D objects from atomic coordinates every frame. However, it is not necessary. Once representation is defined, polygons (vertices, faces, colors, normals) can be defined. This is CPU's task and very time consuming. But once this is done, polygons are valid until representation are changed. Rotation, translation and scaling can be done on GPU and it is very fast.

This is VERY IMPORTANT for Javascript-based applications. Although Javascript engines are getting faster and faster, there is still significant speed gap between native code and Javascript code. Even when Javascript codes run fast enough, it is very bad practice to do unnecessary computation every frame, since it will waste battery on mobile devices.

As I described above, GLmol always separates (1) Polygon generation and (2) Actual rendering. Polygon generation is done only once and rendering is done on GPU.

In terms of implementation, 3D objects are THREE.Object3D. It contains THREE.Mesh, which stores actual polygon definition(vertices, faces, colors, normals). Methods like drawAtomsAsSpheres and drawCartoon calculate polygons from atomic coordinates, create THREE.Object3Ds and add them into the scene.

Mesh merging

Here comes another important trick, mesh merging.

Most programs draw atom by atom or residue by residue. This increases context switching between CPU and GPU. This costs a lot in WebGL since we are actually traversing many layers (Javascript - native API - OS kernel driver - GPU). To reduce this cost, GLmol creates only one polygon mesh (one THREE.Object3D) for entire ribbon representation of a chain and renders in one go.

Once mesh is stored in the video memory as VBO, rendering in WebGL is very fast, almost comparable to native code. You can draw millions of polygons in less than 10msec, provided that it is rendered by one glDrawElements call. In contrast, drawing five five thousand objects(THREE.Object3Ds) seperatedly is unpractically slow already.

Merging thousands of objects

Current implementation of GLmol doesn't combine spheres and cylinders. So GLmol is not very good at drawing huge moleculars in "sphere" or "stick" representation. Below is the explanation.

Suppose you want to draw a bond, that is, a cylinder. THREE.CylinderGeometry generates polygons for a cylinder located at origin, with its long axis parallel to z axis. To connect two atoms with this cylinder, we have to move and rotate it. To do so, we multiply every vertex coordinate by a 4x4 transformation matrix. This is done by GPU and very fast. This transformation matrix is stored as "matrix" property of each THREE.Oject3D object.

Now we want to draw many bonds. We can generate many THREE.Object3D, one for each bond, and put them into the scene but this is slow at rendering. So we try to merge all polygons into one large THREE.Object3D. This can be done with THREE.GeometryUtils.merge as described in http://learningthreejs.com/blog/2011/10/05/performance-merging-geometry/. However, as you can see in its source code, this method do matrix multiplication by CPU. It is very slow!

I tested it; I loaded a structure with 12641 atoms and tried to draw it as "spheres". It is impossible to draw so many objects one by one (my browser went unresponsive while rendering). Then I tried THREE.GeometryUtils.merge. Still unresponsive!

In this case, objects to be merged are spheres, not cylinders and there is a trick. Rotation doesn't do anything on spheres; we can ignore it. Translation can be done by only three additions. This is a nice optimization(rotation needs nine multiplication and additions). In this way, I finally managed to combine mesh. With combined mesh, it took only 7msec to render. Great! However, mesh merging still took 2500msec. Do you think this is acceptable? And still we have to find a method for cylinders...

Limitation and Perspectives

The drawback is that keeping polygons is memory consuming. This is not a problem for modern PC but might be a problem for older PCs and mobile devices.

The design stated above makes it hard to dynamically change representations. We have to rebuild entire mesh to change color of one residue. I believe this is reasonable decision because we spend lots of time rotating molecules before we change representation. But if we do need interactive, realtime change of representation, we have to reconsider the architecture.

If we want to change colors only, there might be a solution. Instead of assigning 'real' colors to vertices, assign atom serial number to each vertex. Also setup a atom-serial-to-color table. Then make our fragment shader lookup the table for each pixel. Now the problem reduced to just updating the table, which is trivial and hopefully fast. Proof-of-concept implementation of this idea is uploaded to https://github.com/biochem-fan/GLmol/tree/dynamic-coloring .

Features lacking

Labels

The most important feature currently lacking might be "labels". Implementation can be done in two ways. One method is to use "billboard" with label as a texture. The other method is to render label as HTML elements and place it in right position.

Selection

Some programs (like PyMOL) allow users to pick atoms or residues by clicking on a rendered image. We can implement this by adding a special renderer, which renders each atom on FBO(frame buffer object) with different colors.

GPGPU computation

The biggest difficulty in doing GPGPU computation on WebGL is that WebGL doesn't support glReadPixels for float textures. It follows that you cannot directly transfer your calculation result from GPU to Javascript world. One way round is to pack one float into one pixel of a framebuffer (GL_RGBA = GL_BYTE x 4 = 32bit), read it by glReadPixels, unpack into float with Javascript. This is described in http://d.hatena.ne.jp/ultraist/20110608/1307539319 (written in Japanese, but you can read the code). But this kills performance a lot.

This limitation is imposed by WebGL specification and we cannot do anything (if you find some nice workaround, please let me know.) But don't be too pessimistic! What we want on browsers is visualization, not numerical value itself. As long as you don't transfer floats too often, this is not a problem. For example, we might want to do molecular dynamics on browser. Time evolution can be calculated on GPU. The result can be visualized by GPU. Unless we try to dump trajectories every step, everything works fine.