From 3577668419d721da0ad5767a3a0b151bcd02fc60 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Mon, 4 Jan 2016 14:50:14 +0000
Subject: [PATCH] Documentation for shaders package.

---
 fsl/fsleyes/gl/shaders/__init__.py     |  54 +++-
 fsl/fsleyes/gl/shaders/arbp/parse.py   | 375 +++++++++++++++++++++++--
 fsl/fsleyes/gl/shaders/arbp/program.py | 139 ++++++++-
 fsl/fsleyes/gl/shaders/glsl/program.py |   4 +-
 4 files changed, 533 insertions(+), 39 deletions(-)

diff --git a/fsl/fsleyes/gl/shaders/__init__.py b/fsl/fsleyes/gl/shaders/__init__.py
index 47dd75c03..8b7bbe0c4 100644
--- a/fsl/fsleyes/gl/shaders/__init__.py
+++ b/fsl/fsleyes/gl/shaders/__init__.py
@@ -1,11 +1,41 @@
 #!/usr/bin/env python
 #
-# __init__.py -
+# __init__.py - Funtions for managing OpenGL shader programs.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
+"""The ``shaders`` package contains classes and functions for parsing,
+compiling, and managing OpenGL shader programs. Two types of shader
+program are supported:
+
+
+ - GLSL 1.20 vertex and fragment shaders.
+
+ - ``ARB_vertex_program`` and ``ARB_fragment_program`` shader programs.
+
+
+The :mod:`.glsl` and :mod:`.arbp` packages respectively define the
+:class:`.GLSLShader` and :class:`.ARBPShader` classes, which may be
+used to manage shader programs of the corresponding type.
+
+
+Some package-level functions are defined here, for finding and loading
+shader source code:
+
+
+ - :func:`getVertexShader`:   Locate and load the source code for a specific
+                              vertex shader.
+
+ - :func:`getFragmentShader`: Locate and load the source code for a specific
+                              fragment shader.
+
+
+Each of these functions locate shader source files, load the source code, and
+run them through the :func:`preprocess` function, which performs some simple
+preprocessing on the source. This applies to both GLSL and ARB assembly
+shader programs.
+"""
 
-import logging
 
 import os.path        as op
 
@@ -15,36 +45,34 @@ import glsl.program   as glslprogram
 import arbp.program   as arbpprogram
 
 
-log = logging.getLogger(__name__)
-
-
 GLSLShader = glslprogram.GLSLShader
 ARBPShader = arbpprogram.ARBPShader
 
 
 def getVertexShader(prefix):
-    """Returns the vertex shader source for the given GL object."""
+    """Returns the vertex shader source for the given GL type (e.g.
+    'glvolume').
+    """
     return _getShader(prefix, 'vert')
 
 
 def getFragmentShader(prefix):
-    """Returns the fragment shader source for the given GL object.""" 
+    """Returns the fragment shader source for the given GL type.""" 
     return _getShader(prefix, 'frag')
 
 
 def _getShader(prefix, shaderType):
-    """Returns the shader source for the given GL object and the given
+    """Returns the shader source for the given GL type and the given
     shader type ('vert' or 'frag').
     """
     fname = _getFileName(prefix, shaderType)
     with open(fname, 'rt') as f: src = f.read()
-    return _preprocess(src)    
+    return preprocess(src)    
 
 
 def _getFileName(prefix, shaderType):
-    """Returns the file name of the shader program for the given GL object
-    and shader type. The ``globj`` parameter may alternately be a string,
-    in which case it is used as the prefix for the shader program file name.
+    """Returns the file name of the shader program for the given GL type
+    and shader type.
     """
 
     if   fslgl.GL_VERSION == '2.1':
@@ -61,7 +89,7 @@ def _getFileName(prefix, shaderType):
         prefix, shaderType, suffix))
  
 
-def _preprocess(src):
+def preprocess(src):
     """'Preprocess' the given shader source.
 
     This amounts to searching for lines containing '#pragma include filename',
diff --git a/fsl/fsleyes/gl/shaders/arbp/parse.py b/fsl/fsleyes/gl/shaders/arbp/parse.py
index 5281ca72f..3228117b3 100644
--- a/fsl/fsleyes/gl/shaders/arbp/parse.py
+++ b/fsl/fsleyes/gl/shaders/arbp/parse.py
@@ -1,39 +1,334 @@
 #!/usr/bin/env python
 #
-# parse.py -
+# parse.py - Very simple parser for ARB assembly shader programs.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
+"""This module provides functions for use with OpenGL ``ARB_vertex_program``
+and ``ARB_fragment_program`` assembly source code. It defines a simple
+templating system, allowing ARB assembly programs to be written in such a way
+that input parameters, vertex attributes, texture locations, and texture
+coordinates and do not have to be hard coded in the source.
 
 
-import                re
+.. note:: This module is used by the :class:`.ARBPShader` class - if you use
+          the :class:`.ARBShader` class, you will not need to use this module
+          at all.
 
-import jinja2      as j2
-import jinja2.meta as j2meta
 
+Instead, place holder tokens can be used in the source code. These tokens may
+be parsed (using ``jinja2``) by the :func:`parseARBP` function. Values can
+then be assigned to the place holders using the :func:`fillARBP` function.
 
-#
-# {{ param_paramName }} -> program.local[X]
-#
-# Or, if 'paramName' has length > 1
-#
-# {{ param4_paramName }} -> { program.local[x], program.local[x + 1], ... }
 
-# {{ attr_attName }}     -> vertex.texcoord[N]
-#
-# {{ texture_texName }} -> texture[N]
+An example
+----------
 
+As an example, consider the following vertex program, for drawing a slice
+from a 3D image texture::
 
-# Maybe this too? In vertex program:
-# {{ varying_outputName }} -> result.texcoord[N]
-#
-# In fragment program:
-# {{ varying_outputName }} -> fragment.texcoord[N]
+
+    !!ARBvp1.0
+
+    PARAM imageShape = program.local[0];
+
+    # Transform the vertex position into display coordinates
+    DP4 result.position.x, state.matrix.mvp.row[0], vertex.position;
+    DP4 result.position.y, state.matrix.mvp.row[1], vertex.position;
+    DP4 result.position.z, state.matrix.mvp.row[2], vertex.position;
+    DP4 result.position.w, state.matrix.mvp.row[3], vertex.position;
+
+    # Transform the texture coordinates (which are
+    # between 0 and 1) into voxel coordinates (which
+    # are within the image voxel dimensions).
+    MOV voxCoord, vertex.texcoord[0];
+    MUL voxCoord, voxCoord, imageShape;
+
+    # Pass the texture coordinates and
+    # corresponding voxel coordinates
+    # through to the fragment program.
+    MOV result.texcoord[0], vertex.texcoord[0];
+    MOV result.texcoord[1], voxCoord;
+
+    END
+
+
+And the corresponding fragment program, which looks up the voxel value
+and colours the fragment accordingly:
+
+
+    !!ARBfp1.0
+
+    TEMP voxValue;
+
+    # A transformation matrix (encoding a linear
+    # offset/scale) which transforms a voxel value
+    # from the image texture data range to the
+    # colour map texture input coordinate range.
+    PARAM voxValXform[4] = { program.local[0],
+                             program.local[1],
+                             program.local[2],
+                             program.local[3] };
+
+    # Get the voxel value
+    TEX voxValue.x, fragment.texcoord[0], texture[0], 3D;
+
+    # Transform the voxel value 
+    MAD voxValue, voxValue, voxValXform[0].x, voxValXform[3].x;
+
+    # Get the colour that corresponds to the voxel value
+    TEX result.color, voxValue.x, texture[1], 1D;
+
+
+This program requires:
+
+ - The image shape to be specified as a program parameter at index 0.
+
+ - Image texture coordinates to be passed as coordinates on texture unit 0.
+
+ - Both the vertex and fragment programs to know which texture units the
+   texture and voxel coordinates are passed through on.
+
+ - The image texture to be bound to texture unit 0.
+
+ - The colour map texture to be bound to texture unit 1.
+
+
+By using this module, all of these requirements can be removed by re-writing
+the vertex program as follows::
+
+
+    !!ARBvp1.0
+
+    PARAM imageShape = {{ param_imageShape }};
+
+    TEMP voxCoord;
+
+    # Transform the vertex position into display coordinates
+    DP4 result.position.x, state.matrix.mvp.row[0], vertex.position;
+    DP4 result.position.y, state.matrix.mvp.row[1], vertex.position;
+    DP4 result.position.z, state.matrix.mvp.row[2], vertex.position;
+    DP4 result.position.w, state.matrix.mvp.row[3], vertex.position;
+
+    # Transform the texture coordinates (which are
+    # between 0 and 1) into voxel coordinates (which
+    # are within the image voxel dimensions). 
+    MOV voxCoord, {{ attr_texCoord }};
+    MUL voxCoord, voxCoord, imageShape;
+
+    # Pass the texture coordinates and
+    # corresponding voxel coordinates
+    # through to the fragment program.
+    MOV {{ varying_texCoord }}, {{ attr_texCoord }};
+    MOV {{ varying_voxCoord }}, voxCoord;
+
+    END
+
+
+And the fragment program::
+
+
+    !!ARBfp1.0
+
+    TEMP voxValue;
+
+    # A transformation matrix (encoding a linear
+    # offset/scale) which transforms a voxel value
+    # from the image texture data range to the
+    # colour map texture input coordinate range.
+    PARAM voxValXform[4] = {{ param4_voxValXform }}; 
+
+    # Get the voxel value
+    TEX voxValue.x, {{ varying_texCoord }}, {{ texture_imageTexture }}, 3D;
+
+    # Transform the voxel value 
+    MAD voxValue, voxValue, voxValXform[0].x, voxValXform[3].x;
+
+    # Get the colour that corresponds to the voxel value
+    TEX result.color, voxValue.x, {{ texture_colourMapTexture }}, 1D;
+
+
+The :func:`parseARBP` function parses the source code and returns information
+about all declared items. The :func:`fillARBP` function can then be used to
+assign explicit values to each of the items::
+
+
+    vertSrc = '!!ARBvp1.0 vertex shader source'
+    fragSrc = '!!ARBfp1.0 fragment shader source'
+
+    # Get information about all parameters,
+    # attributes, textures, and varyings.
+    items = parse.parseARBP(vertSrc, fragSrc)
+
+    # ...
+    # You have to calculate positions for
+    # parameters, attributes and textures.
+    # Positions for varying items are
+    # automatically calculated for you.
+    # ...
+    vertParams    = {'imageShape'        : 0}
+    vertParamLens = {'imageShape'        : 1}
+    fragParams    = {'voxValXform'       : 0}
+    fragParamLens = {'voxValXform'       : 4}
+    textures      = {'imageTexture'      : 0,
+                     'colourMapTexture'  : 1}
+    attrs         = {'texCoord'          : 0}
+
+    # Fill in the template
+    vertSrc, fragSrc = parse.fillARBP(vertSrc,
+                                      fragSrc,
+                                      vertParams,
+                                      vertParamLens,
+                                      fragParams,
+                                      fragParamLens,
+                                      textures,
+                                      attrs)
+
+    # Now you can compile the source
+    # code and run your program!
+
+
+Template tokens
+---------------
+
+
+The following items may be specified as template tokens. As depicted in
+the example above, a token is specified in the following manner::
+
+    {{ tokenPrefix_itemName }}
+
+
+Prefixes for each item type are as follows:
+
+
+ ===================== ============
+ Item                  Token prefix
+ ===================== ============
+ *Parameters*:         ``param``
+ *Vertex attributes*   ``attr``
+ *Textures*            ``texture``
+ *Varying attributes*  ``varying``
+ ===================== ============
+
+
+Parameters
+==========
+
+*Parameters* are constant values which are passed to every instantiation of a
+shader program - they are equivalent to ``uniform`` values in a GLSL program.
+In a normal ARB assembly program, parameters are accessed as follows::
+
+    PARAM imageShape = program.local[0];
+
+
+When using this module, you may instead access parameters in this way::
+
+    PARAM imageShape = {{ param_imageShape }};
+
+
+Parameters with a length greater than 1 (e.g. matrix parameters) are
+traditionally accessed in this way::
+
+    PARAM xform[4] = { program.local[0],
+                       program.local[1],
+                       program.local[2],
+                       program.local[3] };
+
+
+When using this module, you may access matrix parameters in this way::
+
+    PARAM xform[4] = {{ param4_xform }};
+
+
+Vertex attributes
+=================
+
+
+*Vertex attributes* are values which are associated with every rendered
+vertex. They are equivalent to ``attribute`` values in a GLSL program.
+In a normal ARB assembly program, one would typically pass vertex
+attributes as texture coordinates bound to a specified texture unit::
+
+    PARAM texCoord = vertex.texcoord[0];
+
+
+When using this module, you may access vertex attributes as follows::
+
+    PARAM texCoord = {{ attr_texCoord }};
+
+
+Textures
+========
+
+In a typical ARB assembly program, the texture unit to which each texture is
+bound must be hard coded::
+
+    TEX voxelValue, texCoord, texture[0], 3D;
+
+
+This can be avoided by using texture tokens::
+
+    TEX voxelValue, texCoord, {{ texture_imageTexture }}, 3D;
+
+
+Varying attributes
+==================
+
+
+Varying attributes are attributes which are generated in the vertex program,
+and passed through to the fragment program. They are equivalent to ``varying``
+values in a GLSXL program. In an ARB assembly program, they are typically
+passed and accessed as texture coordinates::
+
+    !!ARBvp1.0
+    # In the vertex program, we pass varying
+    # attribute through as texture coordinates:
+    MOV result.texcoord[0], texCoord;
+    MOV result.texcoord[1], voxCoord;
+    # ...
+
+
+    !!ARBfp1.0
+    # ...
+    # In the fragment program, we access varying
+    # attrbutes as texture coordinates
+    TEMP texCoord;
+    TEMP voxCoord;
+    MOV texCoord, fragment.texcoord[0];
+    MOV voxCoord, fragment.texcoord[1];
+    # ...
+
+
+This can be avoided by using the :func:`fillARBP` function, which will
+automatically assign texture coordinate positions to each varying attribute.
+The assembly code can thus be re-written as follows::
+
+    !!ARBvp1.0
+    # ...
+    MOV {{ varying_texCoord }}, texCoord;
+    MOV {{ varying_voxCoord }}, voxCoord;
+    # ...
+
+
+    !!ARBfp1.0
+    # ...
+    TEMP texCoord;
+    TEMP voxCoord;
+    MOV texCoord, {{ varying_texCoord }};
+    MOV voxCoord, {{ varying_voxCoord }};
+    # ...
+"""
+
+
+import                re
+
+import jinja2      as j2
+import jinja2.meta as j2meta
 
 
 def parseARBP(vertSrc, fragSrc):
     """Parses the given ``ARB_vertex_program`` and ``ARB_fragment_program``
-    code, and returns information about all declared variables.
+    code, and returns information about all declared variables. 
     """
 
     vParams, vTextures, vAttrs, vVaryings = _findDeclaredVariables(vertSrc)
@@ -58,6 +353,36 @@ def fillARBP(vertSrc,
              fragParamLens,
              textures,
              attrs):
+    """Fills in the given ARB assembly code, replacing all template tokens
+    with the values specified by the various arguments.
+
+    :arg vertSrc:       Vertex program source.
+    
+    :arg fragSrc:       Fragment program source.
+    
+    :arg vertParams:    Dictionary of ``{name : position}`` mappings, 
+                        specifying the position indices of all vertex
+                        program parameters.
+    
+    :arg vertParamLens: Dictionary ``{name : length}`` mappings, 
+                        specifying the lengths of all vertex program
+                        parameters.
+    
+    :arg fragParams:    Dictionary of ``{name : position}`` mappings, 
+                        specifying the position indices of all fragment
+                        program parameters.
+    
+    :arg fragParamLens: Dictionary ``{name : length}`` mappings, 
+                        specifying the lengths of all fragment program
+                        parameters.
+    
+    :arg textures:      Dictionary of `{name : textureUnit}`` mappings,
+                        specifying the texture unit to use for each texture.
+    
+    :arg attrs:         Dictionary of `{name : textureUnit}`` mappings,
+                        specifying the texture unit to use for each vertex
+                        attribute.
+    """
     
     vertVars = _findDeclaredVariables(vertSrc)
     fragVars = _findDeclaredVariables(fragSrc)
@@ -113,6 +438,9 @@ def fillARBP(vertSrc,
 
 
 def _findDeclaredVariables(source):
+    """Parses the given ARB assembly program source, and returns information
+    about all template tokens defined within.
+    """
 
     env   = j2.Environment()
     ast   = env.parse(source)
@@ -157,6 +485,9 @@ def _checkVariableValidity(vertVars,
                            fragParamMap,
                            textureMap,
                            attrMap):
+    """Checks the information about a vertex/fragment program, and raises
+    an error if it looks like something is wrong.
+    """
     vParams, vTextures, vAttrs, vVaryings = vertVars
     fParams, fTextures, fAttrs, fVaryings = fragVars
 
@@ -187,6 +518,7 @@ def _checkVariableValidity(vertVars,
     
 
 def _makeVaryingMap(vertVaryings, fragVaryings):
+    """Generates texture unit indices for all varying attributes. """
 
     indices    = range(len(vertVaryings))
     varyingMap = dict(zip(vertVaryings, indices))
@@ -194,6 +526,8 @@ def _makeVaryingMap(vertVaryings, fragVaryings):
 
                                     
 def _param(number, length):
+    """Generates ARB assembly for the named vertex/fragment program parameter.
+    """
 
     if length == 1: 
         return 'program.local[{}]'.format(number)
@@ -205,13 +539,16 @@ def _param(number, length):
 
 
 def _texture(number):
+    """Generates ARB assembly for a texture.""" 
     return 'texture[{}]'.format(number)
 
 
 def _attr(number):
+    """Generates ARB assembly for a vertex attribute. """ 
     return 'vertex.texcoord[{}]'.format(number)
 
 
 def _varying(number, vert):
+    """Generates ARB assembly for a varying attribute. """ 
     if vert: return 'result.texcoord[{}]'  .format(number)
     else:    return 'fragment.texcoord[{}]'.format(number)
diff --git a/fsl/fsleyes/gl/shaders/arbp/program.py b/fsl/fsleyes/gl/shaders/arbp/program.py
index 6d20a3abb..c4ebc3652 100644
--- a/fsl/fsleyes/gl/shaders/arbp/program.py
+++ b/fsl/fsleyes/gl/shaders/arbp/program.py
@@ -1,9 +1,13 @@
 #!/usr/bin/env python
 #
-# program.py -
+# program.py - The ARBPShader class.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
+"""This module provides the :class:`ARBPShader` class, which encapsulates
+an OpenGL shader program written according to the ``ARB_vertex_program``
+and ``ARB_fragment_program`` extensions.
+"""
 
 import logging
 
@@ -21,8 +25,92 @@ log = logging.getLogger(__name__)
 
 
 class ARBPShader(object):
+    """The ``ARBPShader`` class encapsulates an OpenGL shader program
+    written according to the ``ARB_vertex_program`` and
+    ``ARB_fragment_program`` extensions. It parses and compiles vertex
+    and fragment program code, and provides methods to load/unload
+    the program, and to set vertex/fragment program parameters and vertex
+    attributes.
+
+    
+    The ``ARBPShader`` class assumes that vertex/fragment program source
+    has been written to work with the functions defined in the 
+    :mod:`.arbp.parse` module, which allows programs to be written so that 
+    parameter, vertex attribute and texture locations do not have to be hard
+    coded in the source.  Texture locations may be specified in
+    :meth:`__init__`, and parameter/vertex attribute locations are
+    automatically assigned by the ``ARBPShader``.
+
+
+    The following methods are available on an ``ARBPShader`` instance:
+
+    .. autosummary::
+       :nosignatures:
+
+       load
+       unload
+       delete
+       setVertParam
+       setFragParam
+       setAttr
+
+    Typcical usage of an ``ARBPShader`` will look something like the
+    following::
+
+        vertSrc = 'vertex shader source'
+        fragSrc = 'vertex shader source'
+
+        # You must specify the texture unit
+        # assignments at creation time.
+        textures = {
+            'colourMapTexture' : 0,
+            'dataTexture'      : 1
+        } 
+
+        program = GLSLShader(vertSrc, fragSrc, textures)
+
+        # Load the program
+        program.load()
+
+        # Set some parameters
+        program.setVertParam('transform', np.eye(4))
+        program.setFragParam('clipping',  [0, 1, 0, 0])
+
+        # Create and set vertex attributes
+        vertices, normals = createVertices() 
+
+        program.setAttr('normals', normals)
+
+        # Draw the scene
+        gl.glDrawArrays(gl.GL_TRIANGLES, 0, len(vertices))
+
+        # Clear the GL state
+        program.unload()
+
+        # Delete the program when
+        # it is no longer needed
+        program.delete()
+
+
+    .. warning:: The ``ARBPShader`` uses texture coordinates to pass vertex
+                 attributes to the shader programs. Therefore, if you are using
+                 an ``ARBPShader`` you cannot directly use texture coordinates.
+
+
+    See also the :class:`.GLSLShader`, which provides similar functionality for
+    GLSL shader programs.
+    """
     
     def __init__(self, vertSrc, fragSrc, textureMap=None):
+        """Create an ``ARBPShader``.
+
+        :arg vertSrc:    Vertex program source.
+        
+        :arg fragSrc:    Fragment program source.
+        
+        :arg textureMap: A dictionary of ``{name : int}`` mappings, specifying
+                         the texture unit assignments.
+        """
 
         decs = parse.parseARBP(vertSrc, fragSrc)
 
@@ -65,14 +153,23 @@ class ARBPShader(object):
         
         self.vertexProgram   = vp
         self.fragmentProgram = fp
+
+        log.memory('{}.init({})'.format(type(self).__name__, id(self)))
+
+
+    def __del__(self):
+        """Prints a log message. """
+        log.memory('{}.del({})'.format(type(self).__name__, id(self))) 
         
 
     def delete(self):
+        """Deletes all GL resources managed by this ``ARBPShader``. """
         arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram))
         arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram))
 
 
     def load(self):
+        """Loads the shader program. """
         gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) 
         gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
 
@@ -89,6 +186,7 @@ class ARBPShader(object):
 
 
     def unload(self):
+        """Unloads the shader program. """
         gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB)
         gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB)
 
@@ -100,6 +198,12 @@ class ARBPShader(object):
 
 
     def setVertParam(self, name, value):
+        """Sets the value of the specified vertex program parameter.
+
+        .. note:: It is assumed that the value is either a sequence of length
+        4 (for vector parameters), or a ``numpy`` array of shape ``(n, 4)``
+        (for matrix parameters).
+        """
 
         pos   = self.vertParamPositions[name]
         value = np.array(value, dtype=np.float32).reshape((-1, 4))
@@ -111,7 +215,9 @@ class ARBPShader(object):
 
     
     def setFragParam(self, name, value):
-
+        """Sets the value of the specified vertex program parameter. See 
+        :meth:`setVertParam` for infomration about possible values.
+        """
         pos   = self.fragParamPositions[name]
         value = np.array(value, dtype=np.float32).reshape((-1, 4))
     
@@ -122,7 +228,12 @@ class ARBPShader(object):
 
 
     def setAttr(self, name, value):
-
+        """Sets the value of the specified vertex attribute. Each vertex
+        attribute is mapped to a texture coordinate. It is assumed that
+        the given value is a ``numpy`` array of shape ``(n, l)``, where
+        ``n`` is the number of vertices being drawn, and ``l`` is the
+        number of components in each vertex attribute coordinate.
+        """
         texUnit = self.__getAttrTexUnit(name)
         size    = value.shape[1]
         value   = np.array(value, dtype=np.float32)
@@ -133,6 +244,9 @@ class ARBPShader(object):
 
 
     def __getAttrTexUnit(self, attr):
+        """Returns the texture unit identifier which corresponds to the named
+        vertex attribute.
+        """
 
         pos     = self.attrPositions[attr]
         texUnit = 'GL_TEXTURE{}'.format(pos)
@@ -142,6 +256,20 @@ class ARBPShader(object):
 
 
     def __generatePositions(self, textureMap=None):
+        """Called by :meth:`__init__`. Generates positions for vertex/fragment
+        program parameters and vertex attributes.
+
+        The lengths of each vertex/fragment parameter are known (see
+        :mod:`.arbp.parse`), so these parameters are set up to be sequentially
+        stored in the program parameter memory.
+
+        Vertex attributes are passed to the vertex program as texture
+        coordinates.
+
+        If texture units were not specified in ``__init__``, texture units are
+        also automatically assigned to each texture used in the fragment
+        program.
+        """
 
         vpPoses   = {}
         fpPoses   = {}
@@ -179,8 +307,9 @@ class ARBPShader(object):
 
 
     def __compile(self, vertSrc, fragSrc):
-        """Compiles the vertex and fragment programs and returns references
-        to the compiled programs.
+        """Called by :meth:`__init__`. Compiles the vertex and fragment
+        programs and returns references to the compiled programs.
+
         """
 
         gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) 
diff --git a/fsl/fsleyes/gl/shaders/glsl/program.py b/fsl/fsleyes/gl/shaders/glsl/program.py
index 0537fa386..3bfe11452 100644
--- a/fsl/fsleyes/gl/shaders/glsl/program.py
+++ b/fsl/fsleyes/gl/shaders/glsl/program.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# program.py - Class which encapsulates a GLSL shader program.
+# program.py - The GLSLShader class.
 #
 # Author: Paul McCarthy <pauldmccarthy@gmail.com>
 #
@@ -40,7 +40,7 @@ class GLSLShader(object):
     a GLSL 1.20 shader program, comprising a vertex shader and a fragment
     shader. It provides methods to set shader attribute and uniform values,
     to configure attributes, and to load/unload the program. Furthermore,
-    the ``GLSLShader`` makes sure that all uniform and attribut variables
+    the ``GLSLShader`` makes sure that all uniform and attribute variables
     are converted to the appropriate type. The following methods are available
     on a ``GLSLShader``:
 
-- 
GitLab