From 68822c8b9020830d5e983f29f2eb6f2654e1e016 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Wed, 14 Oct 2015 10:04:44 +0100 Subject: [PATCH] Documentation for GLModel --- fsl/fsleyes/gl/gl14/edge2D.prog | 2 +- fsl/fsleyes/gl/gl14/glmodel_frag.prog | 8 +- fsl/fsleyes/gl/gl14/glmodel_funcs.py | 18 +++- fsl/fsleyes/gl/gl14/glmodel_vert.prog | 9 +- fsl/fsleyes/gl/gl21/glmodel_frag.glsl | 9 ++ fsl/fsleyes/gl/gl21/glmodel_funcs.py | 17 +++- fsl/fsleyes/gl/gl21/glmodel_vert.glsl | 2 +- fsl/fsleyes/gl/glmodel.py | 113 ++++++++++++++++++++++++-- 8 files changed, 158 insertions(+), 20 deletions(-) diff --git a/fsl/fsleyes/gl/gl14/edge2D.prog b/fsl/fsleyes/gl/gl14/edge2D.prog index d1a3d55ef..9f0dbe52b 100644 --- a/fsl/fsleyes/gl/gl14/edge2D.prog +++ b/fsl/fsleyes/gl/gl14/edge2D.prog @@ -1,4 +1,4 @@ -# EDge detection routine for 2D textures - see edge3D.prog. +# Edge detection routine for 2D textures - see also edge3D.prog. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # diff --git a/fsl/fsleyes/gl/gl14/glmodel_frag.prog b/fsl/fsleyes/gl/gl14/glmodel_frag.prog index ced6c8ffc..295911073 100644 --- a/fsl/fsleyes/gl/gl14/glmodel_frag.prog +++ b/fsl/fsleyes/gl/gl14/glmodel_frag.prog @@ -3,9 +3,13 @@ # Fragment program used for rendering GLModel instances. # # Inputs: -# fragment.texcoord[0] +# fragment.texcoord[0] - Texture coordinates +# program.local[0] - Offsets (used as edge widths - see edge2D.prog) # -# program.local[0] - Offsets +# Outputs: +# result.color - Fragment colour. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> # TEMP val; diff --git a/fsl/fsleyes/gl/gl14/glmodel_funcs.py b/fsl/fsleyes/gl/gl14/glmodel_funcs.py index fce0a4ea0..cc1fea8aa 100644 --- a/fsl/fsleyes/gl/gl14/glmodel_funcs.py +++ b/fsl/fsleyes/gl/gl14/glmodel_funcs.py @@ -1,9 +1,12 @@ #!/usr/bin/env python # -# glmodel_funcs.py - +# glmodel_funcs.py - OpenGL 1.4 functions used by the GLModel class. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides functions which are used by the :class:`.GLModel` +class to render :class:`.Model` overlays in an OpenGL 1.4 compatible manner. +""" import OpenGL.GL as gl @@ -15,6 +18,10 @@ import fsl.fsleyes.gl.shaders as shaders def compileShaders(self): + """Compiles vertex and fragment shader programs for the given + :class:`.GLModel` instance. The shaders are added as attributes of the + instance. + """ vertShaderSrc = shaders.getVertexShader( self) fragShaderSrc = shaders.getFragmentShader(self) @@ -27,12 +34,17 @@ def compileShaders(self): def destroy(self): + """Deletes the vertex/fragment shader programs that were compiled by + :func:`compileShaders`. + """ arbvp.glDeleteProgramsARB(1, gltypes.GLuint(self.vertexProgram)) arbfp.glDeleteProgramsARB(1, gltypes.GLuint(self.fragmentProgram)) def updateShaders(self): - + """Updates the state of the vertex/fragment shaders. This involves + setting the parameter values used by the shaders. + """ offsets = self.getOutlineOffsets() loadShaders(self) @@ -41,6 +53,7 @@ def updateShaders(self): def loadShaders(self): + """Loads the :class:`.GLModel` vertex/fragment shader programs. """ gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB) @@ -51,5 +64,6 @@ def loadShaders(self): def unloadShaders(self): + """Un-loads the :class:`.GLModel` vertex/fragment shader programs. """ gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB) gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB) diff --git a/fsl/fsleyes/gl/gl14/glmodel_vert.prog b/fsl/fsleyes/gl/gl14/glmodel_vert.prog index c1ff267a1..0b38e8949 100644 --- a/fsl/fsleyes/gl/gl14/glmodel_vert.prog +++ b/fsl/fsleyes/gl/gl14/glmodel_vert.prog @@ -3,13 +3,14 @@ # Vertex program for rendering GLModel instances. # # Inputs: -# state.matrix.mvp -# vertex.position -# vertex.texcoord[0] +# vertex.texcoord[0] - Texture coordinates # # Outputs: # result.position -# result.texcoord[0] +# result.texcoord[0] - Texture coordinates (passed through to fragment +# shader). +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> # # Transform the vertex position into display coordinates diff --git a/fsl/fsleyes/gl/gl21/glmodel_frag.glsl b/fsl/fsleyes/gl/gl21/glmodel_frag.glsl index 7c13880b0..e3d17c87f 100644 --- a/fsl/fsleyes/gl/gl21/glmodel_frag.glsl +++ b/fsl/fsleyes/gl/gl21/glmodel_frag.glsl @@ -1,3 +1,8 @@ +/* + * Fragment shader used for drawing GLModel instances. + * + * Author: Paul McCarthy <pauldmccarthy@gmail.com> + */ #version 120 #pragma include edge.glsl @@ -11,6 +16,10 @@ void main(void) { vec4 colour = texture2D(tex, texCoord); vec4 tol = 1.0 / vec4(255, 255, 255, 255); + /* + * If the fragment lies on an edge + * colour it, otherwise clear it. + */ if (edge2D(tex, texCoord, colour, tol, offsets)) gl_FragColor = colour; else gl_FragColor.a = 0.0; } diff --git a/fsl/fsleyes/gl/gl21/glmodel_funcs.py b/fsl/fsleyes/gl/gl21/glmodel_funcs.py index d78b7c20c..2092f73c3 100644 --- a/fsl/fsleyes/gl/gl21/glmodel_funcs.py +++ b/fsl/fsleyes/gl/gl21/glmodel_funcs.py @@ -1,9 +1,12 @@ #!/usr/bin/env python # -# glmodel_funcs.py - +# glmodel_funcs.py - OpenGL 2.1 functions used by the GLModel class. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides functions which are used by the :class:`.GLModel` +class to render :class:`.Model` overlays in an OpenGL 2.1 compatible manner. +""" import numpy as np import OpenGL.GL as gl @@ -12,6 +15,10 @@ import fsl.fsleyes.gl.shaders as shaders def compileShaders(self): + """Compiles vertex and fragment shaders for the given :class:`.GLModel` + instance. The shaders, and locations of uniform variables, are added + as attributes of the instance. + """ vertShaderSrc = shaders.getVertexShader( self) fragShaderSrc = shaders.getFragmentShader(self) self.shaders = shaders.compileShaders(vertShaderSrc, fragShaderSrc) @@ -21,10 +28,16 @@ def compileShaders(self): def destroy(self): + """Deletes the vertex/fragment shaders that were compiled by + :func:`compileShaders`. + """ gl.glDeleteProgram(self.shaders) def updateShaders(self): + """Updates the state of the vertex/fragment shaders. This involves + setting the uniform variable values used by the shaders. + """ width, height = self._renderTexture.getSize() outlineWidth = self.opts.outlineWidth @@ -44,8 +57,10 @@ def updateShaders(self): def loadShaders(self): + """Loads the :class:`.GLModel` vertex/fragment shaders. """ gl.glUseProgram(self.shaders) def unloadShaders(self): + """Un-loads the :class:`.GLModel` vertex/fragment shaders. """ gl.glUseProgram(0) diff --git a/fsl/fsleyes/gl/gl21/glmodel_vert.glsl b/fsl/fsleyes/gl/gl21/glmodel_vert.glsl index 2efb49a27..6b98518a0 100644 --- a/fsl/fsleyes/gl/gl21/glmodel_vert.glsl +++ b/fsl/fsleyes/gl/gl21/glmodel_vert.glsl @@ -1,5 +1,5 @@ /* - * Vertex shader for GLModel instances. + * Vertex shader used for drawing GLModel instances. * * Author: Paul McCarthy <pauldmccarthy@gmail.com> */ diff --git a/fsl/fsleyes/gl/glmodel.py b/fsl/fsleyes/gl/glmodel.py index 841d80699..96c091d10 100644 --- a/fsl/fsleyes/gl/glmodel.py +++ b/fsl/fsleyes/gl/glmodel.py @@ -1,9 +1,12 @@ #!/usr/bin/env python # -# glmodel.py - +# glmodel.py - The GLModel class. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module provides the :class:`GLModel` class, a :class:`.GLObject` used +to render :class:`.Model` overlays. +""" import numpy as np @@ -18,8 +21,55 @@ import fsl.fsleyes.colourmaps as fslcmaps class GLModel(globject.GLObject): + """The ``GLModel`` class is a :class:`.GLObject` which encapsulates the + logic required to render 2D slices of a :class:`.Model` overlay. + + The ``GLModel`` renders cross-sections of a ``Model`` overlay using a + three-pass technique, roughly following that described at + http://glbook.gamedev.net/GLBOOK/glbook.gamedev.net/moglgp/advclip.html: + + + 1. The front face of the model is rendered to the stencil buffer. + + 2. The back face is rendered to the stencil buffer, and subtracted + from the front face. + + 3. This intersection (of the model with a plane at the slice Z location) + is then rendered to the canvas. + + + The ``GLModel`` class has the ability to draw the model with a fill + colour, or to draw only the model outline. To accomplish this, in step 3 + above, the intersection mask is actually drawn to an off-screen + :class:`.GLObjectRenderTexture`. If outline mode (controlled by the + :attr:`.ModelOpts.outline` property), an edge-detection shader program is + run on this off-screen texture. Then, the texture is rendered to the + canvas. If outline mode is disabled, the shader programs are not executed. + + + The ``GLModel`` class makes use of two OpenGL version-specific modules, + :mod:`.gl14.glmodel_funcs`, and :mod:`.gl21.glmodel_funcs`. These version + specific modules must provide the following functions: + + =========================== ====================================== + ``destroy(GLModel)`` Performs any necessary clean up. + ``compileShaders(GLModel)`` Compiles vertex/fragment shaders. + ``updateShaders(GLModel)`` Updates vertex/fragment shader states. + ``loadShaders(GLModel)`` Loads vertex/fragment shaders. + ``unloadShaders(GLModel)`` Unloads vertex/fragment shaders. + =========================== ====================================== + """ + + def __init__(self, overlay, display): + """Create a ``GLModel``. + + :arg overlay: A :class:`.Model` overlay. + + :arg display: A :class:`.Display` instance defining how the + ``overlay`` is to be displayed. + """ globject.GLObject.__init__(self) @@ -39,6 +89,10 @@ class GLModel(globject.GLObject): def destroy(self): + """Must be called when this ``GLModel`` is no longer needed. Removes + some property listeners and destroys the off-screen + :class:`.GLObjectRenderTexture`. + """ self._renderTexture.destroy() fslgl.glmodel_funcs.destroy(self) self.removeListeners() @@ -49,6 +103,10 @@ class GLModel(globject.GLObject): def addListeners(self): + """Called by :meth:`__init__`. Adds some property listeners to the + :class:`.Display` and :class:`.ModelOpts` instances so the OpenGL + representation can be updated when the display properties are changed.. + """ name = self.name display = self.display @@ -74,6 +132,9 @@ class GLModel(globject.GLObject): def removeListeners(self): + """Called by :meth:`destroy`. Removes all of the listeners added by + the :meth:`addListeners` method. + """ self.opts .removeListener('refImage', self.name) self.opts .removeListener('coordSpace', self.name) self.opts .removeListener('transform', self.name) @@ -84,8 +145,21 @@ class GLModel(globject.GLObject): self.display.removeListener('contrast', self.name) self.display.removeListener('alpha', self.name) + + def setAxes(self, xax, yax): + """Overrides :meth:`.GLObject.setAxes`. Calls the base class + implementation, and calls :meth:`.GLObjectRenderTexture.setAxes` + on the off-screen texture. + """ + globject.GLObject.setAxes(self, xax, yax) + self._renderTexture.setAxes(xax, yax) + def _updateVertices(self, *a): + """Called by :meth:`__init__`, and when certain display properties + change. (Re-)generates the model vertices and indices. They are stored + as attributes called ``vertices`` and ``indices`` respectively. + """ vertices = self.overlay.vertices indices = self.overlay.indices @@ -101,11 +175,18 @@ class GLModel(globject.GLObject): def getDisplayBounds(self): + """Overrides :meth:`.GLObject.getDisplayBounds`. Returns a bounding + box which contains the model vertices. + """ return (self.opts.bounds.getLo(), self.opts.bounds.getHi()) def getDataResolution(self, xax, yax): + """Overrides :meth:`.GLObject.getDataResolution`. Returns a + resolution (in pixels), along each display coordinate system axis, + suitable for drawing this ``GLModel``. + """ # TODO How can I have this resolution tied # to the rendering target resolution (i.e. @@ -142,14 +223,14 @@ class GLModel(globject.GLObject): return resolution - - def setAxes(self, xax, yax): - globject.GLObject.setAxes(self, xax, yax) - self._renderTexture.setAxes(xax, yax) - def getOutlineOffsets(self): - """Used by the :mod:`glmodel_funcs` modules. + """Returns an array containing two values, which are to be used as the + outline widths along the horizontal/vertical screen axes (if outline + mode is being used). The values are in display coordinate system units. + + .. note:: This method is used by the :mod:`.gl14.glmodel_funcs` and + :mod:`.gl21.glmodel_funcs` modules. """ width, height = self._renderTexture.getSize() outlineWidth = self.opts.outlineWidth @@ -165,10 +246,14 @@ class GLModel(globject.GLObject): def preDraw(self): + """Overrides :meth:`.GLObject.preDraw`. This method does nothing. """ pass def draw(self, zpos, xform=None): + """Overrids :meth:`.GLObject.draw`. Draws a 2D slice of the + :class:`.Model`, at the specified Z location. + """ display = self.display opts = self.opts @@ -192,6 +277,11 @@ class GLModel(globject.GLObject): if zpos < zmin or zpos > zmax: return + # Figure out the equation of a plane + # perpendicular to the Z axis, and + # located at the z position. This is + # used as a clipping plane to draw + # the model intersection. clipPlaneVerts = np.zeros((4, 3), dtype=np.float32) clipPlaneVerts[0, [xax, yax]] = [xmin, ymin] clipPlaneVerts[1, [xax, yax]] = [xmin, ymax] @@ -251,9 +341,9 @@ class GLModel(globject.GLObject): gl.GL_UNSIGNED_INT, indices) - # third pass - render the intersection + # Third pass - render the intersection # of the front and back faces from the - # stencil buffer to the render texture + # stencil buffer to the render texture. gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE) gl.glDisable(gl.GL_CLIP_PLANE0) @@ -283,6 +373,10 @@ class GLModel(globject.GLObject): self._renderTexture.unbindAsRenderTarget() self._renderTexture.restoreViewport() + # If drawing the model outline, run the + # render texture through the shader + # programs. Otherwise, the render texture + # is just drawn directly to the canvas. if opts.outline: fslgl.glmodel_funcs.loadShaders(self) @@ -294,4 +388,5 @@ class GLModel(globject.GLObject): def postDraw(self): + """Overrides :meth:`.GLObject.postDraw`. This method does nothing. """ pass -- GitLab