diff --git a/fsl/fslview/gl/__init__.py b/fsl/fslview/gl/__init__.py index fa59939788ec866f8f561e5564d3da247a6c1093..ddb4471d77a54d242d3089dd4a7037262576ebe9 100644 --- a/fsl/fslview/gl/__init__.py +++ b/fsl/fslview/gl/__init__.py @@ -37,6 +37,16 @@ Two super classes are provided for each of these cases: - The :class:`OSMesaCanvasTarget` class for off-screen rendering using OSMesa. + +After the :func:`boostrap` function has been called, the following +package-level attributes will be available: + + - ``GL_VERSION``: A string containing the target OpenGL version, in the + format ``major.minor``, e.g. ``2.1``. + + - ``glvolume_funcs``: The version-specific module containing functions for + rendering :class:`~fsl.fslview.gl.glvolume.GLVolume` + instances. """ import logging @@ -81,7 +91,8 @@ def bootstrap(glVersion=None): """ import sys - import OpenGL.GL as gl + import OpenGL.GL as gl + import OpenGL.extensions as glexts import gl14 import gl21 @@ -111,75 +122,39 @@ def bootstrap(glVersion=None): # fall back to the gl14 implementation if glpkg == gl21: - import OpenGL.extensions as glexts - exts = ['GL_ARB_texture_rg', - 'GL_EXT_gpu_shader4'] + # List any GL21 extensions here + exts = [] - exts = map(glexts.hasExtension, exts) - - if not all(exts): + if not all(map(glexts.hasExtension, exts)): + log.debug('One of these OpenGL extensions is ' + 'not available: [{}]. Falling back ' + 'to an older OpenGL implementation.' + .format(', '.join(exts))) verstr = '1.4' glpkg = gl14 + # If using GL14, and the ARB_vertex_program + # and ARB_fragment_program extensions are + # not present, we're screwed. + if glpkg == gl14: + + exts = ['GL_ARB_vertex_program', + 'GL_ARB_fragment_program'] + + if not all(map(glexts.hasExtension, exts)): + raise RuntimeError('One of these OpenGL extensions is ' + 'not available: [{}]. This software ' + 'cannot run on the available graphics ' + 'hardware.'.format(', '.join(exts))) + log.debug('Using OpenGL {} implementation'.format(verstr)) + thismod.GL_VERSION = verstr thismod.glvolume_funcs = glpkg.glvolume_funcs thismod._bootstrapped = True -def compilePrograms(vertexProgramSrc, fragmentProgramSrc): - - import OpenGL.GL as gl - import OpenGL.GL.ARB.fragment_program as arbfp - import OpenGL.GL.ARB.vertex_program as arbvp - - gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) - gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB) - - fragmentProgram = arbfp.glGenProgramsARB(1) - vertexProgram = arbvp.glGenProgramsARB(1) - - # vertex program - arbvp.glBindProgramARB(arbvp.GL_VERTEX_PROGRAM_ARB, - vertexProgram) - - arbvp.glProgramStringARB(arbvp.GL_VERTEX_PROGRAM_ARB, - arbvp.GL_PROGRAM_FORMAT_ASCII_ARB, - len(vertexProgramSrc), - vertexProgramSrc) - - if (gl.glGetError() == gl.GL_INVALID_OPERATION): - - position = gl.glGetIntegerv(arbvp.GL_PROGRAM_ERROR_POSITION_ARB) - message = gl.glGetString( arbvp.GL_PROGRAM_ERROR_STRING_ARB) - - raise RuntimeError('Error compiling vertex program ' - '({}): {}'.format(position, message)) - - # fragment program - arbfp.glBindProgramARB(arbfp.GL_FRAGMENT_PROGRAM_ARB, - fragmentProgram) - - arbfp.glProgramStringARB(arbfp.GL_FRAGMENT_PROGRAM_ARB, - arbfp.GL_PROGRAM_FORMAT_ASCII_ARB, - len(fragmentProgramSrc), - fragmentProgramSrc) - - if (gl.glGetError() == gl.GL_INVALID_OPERATION): - - position = gl.glGetIntegerv(arbfp.GL_PROGRAM_ERROR_POSITION_ARB) - message = gl.glGetString( arbfp.GL_PROGRAM_ERROR_STRING_ARB) - - raise RuntimeError('Error compiling fragment program ' - '({}): {}'.format(position, message)) - - gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB) - gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB) - - return vertexProgram, fragmentProgram - - def getWXGLContext(): """Create and return a GL context object for rendering to a :class:`wx.glcanvas.GLCanvas` canvas. diff --git a/fsl/fslview/gl/gl14/glvolume_frag.prog b/fsl/fslview/gl/gl14/glvolume_frag.prog new file mode 100644 index 0000000000000000000000000000000000000000..adf2ce3fbbcc10912d1f16b3ae17b28f54171072 --- /dev/null +++ b/fsl/fslview/gl/gl14/glvolume_frag.prog @@ -0,0 +1,89 @@ +!!ARBfp1.0 +# +# Fragment program used for rendering GLVolume instances. +# +# This fragment program does the following: +# +# 1. Retrieves the texture coordinates corresponding to the fragment +# +# 2. Transforms those coordinates into voxel coordinates +# +# 3. Uses those voxel coordinates to look up the corresponding voxel +# value in the 3D image texture. +# +# 4. Uses that voxel value to look up the corresponding colour in the +# 1D colour map texture. +# +# 5. Sets the fragment colour. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# + +TEMP dispTexCoord; +TEMP voxTexCoord; +TEMP normVoxTexCoord; +TEMP voxValue; +TEMP voxColour; +PARAM imageShape = program.local[0]; +PARAM imageShapeInv = program.local[1]; + +# This matrix scales the voxel value to +# lie in a range which is appropriate to +# the current display range +PARAM voxValXform[4] = { state.matrix.texture[1] }; + +# This matrix transforms coordinates +# from the display coordinate system +# to image voxel coordinates +PARAM dispToVoxMat[4] = { state.matrix.texture[0] }; + +# retrieve the 3D texture coordinates +# (which are in terms of the display +# coordinate system) +MOV dispTexCoord, fragment.texcoord[0]; + +# Transform said coordinates +# into voxel coordinates +DP4 voxTexCoord.x, dispToVoxMat[0], dispTexCoord; +DP4 voxTexCoord.y, dispToVoxMat[1], dispTexCoord; +DP4 voxTexCoord.z, dispToVoxMat[2], dispTexCoord; + +# Offset voxel coordinates by 0.5 +# so they are centred within a voxel +ADD voxTexCoord, voxTexCoord, { 0.5, 0.5, 0.5, 0.0 }; + +# Normalise voxel coordinates to +# lie in the range (0, 1), so they +# can be used for texture lookup +MUL normVoxTexCoord, voxTexCoord, imageShapeInv; + +# look up image voxel value +# from 3D image texture +TEX voxValue, normVoxTexCoord, texture[0], 3D; + +# Scale voxel value according +# to the current display range +MUL voxValue, voxValue, voxValXform[0].x; +ADD voxValue, voxValue, voxValXform[0].w; + +# look up the appropriate colour +# in the 1D colour map texture +TEX voxColour, voxValue.x, texture[1], 1D; + +# If any of the voxel coordinates are +# less than 0, clear the voxel colour +CMP voxColour.w, voxTexCoord.x, 0.0, voxColour.w; +CMP voxColour.w, voxTexCoord.y, 0.0, voxColour.w; +CMP voxColour.w, voxTexCoord.z, 0.0, voxColour.w; + +# If any voxel coordinates are greater than +# the image shape, clear the voxel colour +SUB voxTexCoord, voxTexCoord, imageShape; +CMP voxColour.w, voxTexCoord.x, voxColour.w, 0.0; +CMP voxColour.w, voxTexCoord.y, voxColour.w, 0.0; +CMP voxColour.w, voxTexCoord.z, voxColour.w, 0.0; + +# Colour the pixel! +MOV result.color, voxColour; + +END diff --git a/fsl/fslview/gl/gl14/glvolume_funcs.py b/fsl/fslview/gl/gl14/glvolume_funcs.py index 45aaf431a756d5a940ffcb4f8ee8ae144efb141a..875b1d7b9fa7cbb74d5a297b7e9b4a7d30c9400a 100644 --- a/fsl/fslview/gl/gl14/glvolume_funcs.py +++ b/fsl/fslview/gl/gl14/glvolume_funcs.py @@ -36,129 +36,18 @@ import OpenGL.GL as gl import OpenGL.GL.ARB.fragment_program as arbfp import OpenGL.GL.ARB.vertex_program as arbvp -import fsl.utils.transform as transform -import fsl.fslview.gl as fslgl - - -_glvolume_vertex_program = """!!ARBvp1.0 - -# Transform the vertex coordinates from the display -# coordinate system to the screen coordinate system -TEMP vertexPos; - -DP4 vertexPos.x, state.matrix.mvp.row[0], vertex.position; -DP4 vertexPos.y, state.matrix.mvp.row[1], vertex.position; -DP4 vertexPos.z, state.matrix.mvp.row[2], vertex.position; -DP4 vertexPos.w, state.matrix.mvp.row[3], vertex.position; - -MOV result.position, vertexPos; - -# Set the vertex texture coordinate -# to the vertex position -MOV result.texcoord[0], vertex.position; - -END -""" -"""The vertex program does two things: - - - Transforms vertex coordinates from display space into screen space - - - Sets the vertex texture coordinate from its display coordinate -""" - - -_glvolume_fragment_program = """!!ARBfp1.0 -TEMP dispTexCoord; -TEMP voxTexCoord; -TEMP normVoxTexCoord; -TEMP voxValue; -TEMP voxColour; -PARAM imageShape = program.local[0]; -PARAM imageShapeInv = program.local[1]; - -# This matrix scales the voxel value to -# lie in a range which is appropriate to -# the current display range -PARAM voxValXform[4] = { state.matrix.texture[1] }; - -# This matrix transforms coordinates -# from the display coordinate system -# to image voxel coordinates -PARAM dispToVoxMat[4] = { state.matrix.texture[0] }; - -# retrieve the 3D texture coordinates -# (which are in terms of the display -# coordinate system) -MOV dispTexCoord, fragment.texcoord[0]; - -# Transform said coordinates -# into voxel coordinates -DP4 voxTexCoord.x, dispToVoxMat[0], dispTexCoord; -DP4 voxTexCoord.y, dispToVoxMat[1], dispTexCoord; -DP4 voxTexCoord.z, dispToVoxMat[2], dispTexCoord; - -# Offset voxel coordinates by 0.5 -# so they are centred within a voxel -ADD voxTexCoord, voxTexCoord, { 0.5, 0.5, 0.5, 0.0 }; - -# Normalise voxel coordinates to -# lie in the range (0, 1), so they -# can be used for texture lookup -MUL normVoxTexCoord, voxTexCoord, imageShapeInv; - -# look up image voxel value -# from 3D image texture -TEX voxValue, normVoxTexCoord, texture[0], 3D; - -# Scale voxel value according -# to the current display range -MUL voxValue, voxValue, voxValXform[0].x; -ADD voxValue, voxValue, voxValXform[0].w; - -# look up the appropriate colour -# in the 1D colour map texture -TEX voxColour, voxValue.x, texture[1], 1D; - -# If any of the voxel coordinates are -# less than 0, clear the voxel colour -CMP voxColour.w, voxTexCoord.x, 0.0, voxColour.w; -CMP voxColour.w, voxTexCoord.y, 0.0, voxColour.w; -CMP voxColour.w, voxTexCoord.z, 0.0, voxColour.w; - -# If any voxel coordinates are greater than -# the image shape, clear the voxel colour -SUB voxTexCoord, voxTexCoord, imageShape; -CMP voxColour.w, voxTexCoord.x, voxColour.w, 0.0; -CMP voxColour.w, voxTexCoord.y, voxColour.w, 0.0; -CMP voxColour.w, voxTexCoord.z, voxColour.w, 0.0; - -# Colour the pixel! -MOV result.color, voxColour; - -END -""" -""" -The fragment shader does the following: - - 1. Retrieves the texture coordinates corresponding to the fragment - - 2. Transforms those coordinates into voxel coordinates - - 3. Uses those voxel coordinates to look up the corresponding voxel - value in the 3D image texture. - - 4. Uses that voxel value to look up the corresponding colour in the - 1D colour map texture. - - 5. Sets the fragment colour. -""" +import fsl.utils.transform as transform +import fsl.fslview.gl.shaders as shaders def init(glvol, xax, yax): """Compiles the vertex and fragment programs used for rendering.""" - vertexProgram, fragmentProgram = fslgl.compilePrograms( - _glvolume_vertex_program, _glvolume_fragment_program) + vertShaderSrc = shaders.getVertexShader( glvol) + fragShaderSrc = shaders.getFragmentShader(glvol) + + vertexProgram, fragmentProgram = shaders.compilePrograms( + vertShaderSrc, fragShaderSrc) glvol.vertexProgram = vertexProgram glvol.fragmentProgram = fragmentProgram diff --git a/fsl/fslview/gl/gl14/glvolume_vert.prog b/fsl/fslview/gl/gl14/glvolume_vert.prog new file mode 100644 index 0000000000000000000000000000000000000000..5833a45f9c9303d3c1a45f199db02bbada55996f --- /dev/null +++ b/fsl/fslview/gl/gl14/glvolume_vert.prog @@ -0,0 +1,29 @@ +!!ARBvp1.0 +# +# Vertex program used for rendering GLVolume instances. +# +# This vertex program does two things: +# +# - Transforms vertex coordinates from display space into screen space +# +# - Sets the vertex texture coordinate from its display coordinate +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# + +# Transform the vertex coordinates from the display +# coordinate system to the screen coordinate system +TEMP vertexPos; + +DP4 vertexPos.x, state.matrix.mvp.row[0], vertex.position; +DP4 vertexPos.y, state.matrix.mvp.row[1], vertex.position; +DP4 vertexPos.z, state.matrix.mvp.row[2], vertex.position; +DP4 vertexPos.w, state.matrix.mvp.row[3], vertex.position; + +MOV result.position, vertexPos; + +# Set the vertex texture coordinate +# to the vertex position +MOV result.texcoord[0], vertex.position; + +END diff --git a/fsl/fslview/gl/gl21/glvolume_funcs.py b/fsl/fslview/gl/gl21/glvolume_funcs.py index f594a473266ccaf4f16a792a1ca4674a7f4c9a4e..60e95a28a5e5ac5265ee61750ef7e4c5fc7a2237 100644 --- a/fsl/fslview/gl/gl21/glvolume_funcs.py +++ b/fsl/fslview/gl/gl21/glvolume_funcs.py @@ -30,61 +30,21 @@ import numpy as np import OpenGL.GL as gl import OpenGL.arrays.vbo as vbo -import shaders -import fsl.utils.transform as transform +import fsl.fslview.gl.shaders as shaders +import fsl.utils.transform as transform def _compileShaders(glvol): """Compiles and links the OpenGL GLSL vertex and fragment shader - programs, and attaches a reference to the resulting program to - the given GLVolume object. Raises an error if compilation/linking - fails. - - I'm explicitly not using the PyOpenGL - :func:`OpenGL.GL.shaders.compileProgram` function, because it attempts - to validate the program after compilation, which fails due to texture - data not being bound at the time of validation. + programs, and attaches a reference to the resulting program, and + all GLSL variables, to the given GLVolume object. """ vertShaderSrc = shaders.getVertexShader( glvol) fragShaderSrc = shaders.getFragmentShader(glvol) + glvol.shaders = shaders.compileShaders(vertShaderSrc, fragShaderSrc) - # vertex shader - vertShader = gl.glCreateShader(gl.GL_VERTEX_SHADER) - gl.glShaderSource(vertShader, vertShaderSrc) - gl.glCompileShader(vertShader) - vertResult = gl.glGetShaderiv(vertShader, gl.GL_COMPILE_STATUS) - - if vertResult != gl.GL_TRUE: - raise RuntimeError('{}'.format(gl.glGetShaderInfoLog(vertShader))) - - # fragment shader - fragShader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER) - gl.glShaderSource(fragShader, fragShaderSrc) - gl.glCompileShader(fragShader) - fragResult = gl.glGetShaderiv(fragShader, gl.GL_COMPILE_STATUS) - - if fragResult != gl.GL_TRUE: - raise RuntimeError('{}'.format(gl.glGetShaderInfoLog(fragShader))) - - # link all of the shaders! - program = gl.glCreateProgram() - gl.glAttachShader(program, vertShader) - gl.glAttachShader(program, fragShader) - - gl.glLinkProgram(program) - - gl.glDeleteShader(vertShader) - gl.glDeleteShader(fragShader) - - linkResult = gl.glGetProgramiv(program, gl.GL_LINK_STATUS) - - if linkResult != gl.GL_TRUE: - raise RuntimeError('{}'.format(gl.glGetProgramInfoLog(program))) - - glvol.shaders = program - - # Indices of all vertex/fragment shader parameters + # indices of all vertex/fragment shader parameters glvol.worldToWorldMatPos = gl.glGetUniformLocation(glvol.shaders, 'worldToWorldMat') glvol.xaxPos = gl.glGetUniformLocation(glvol.shaders, diff --git a/fsl/fslview/gl/gl21/glvolume_vert.glsl b/fsl/fslview/gl/gl21/glvolume_vert.glsl index f48f08f3b5d1fecb64a07ba9f782295d3f61f416..f368f3eb3e49f225d3aa300a34fd4447557af7ef 100644 --- a/fsl/fslview/gl/gl21/glvolume_vert.glsl +++ b/fsl/fslview/gl/gl21/glvolume_vert.glsl @@ -1,7 +1,7 @@ /* - * OpenGL vertex shader used by fsl/fslview/gl/gl21/slicecanvas_draw.py. + * OpenGL vertex shader used by fsl/fslview/gl/gl21/glvolume_funcs.py. * - * All this shader does is transfer texture coordinates through + * All this shader does is transfer texture coordinates through * to the fragment shader. * * Author: Paul McCarthy <pauldmccarthy@gmail.com> diff --git a/fsl/fslview/gl/gl21/shaders.py b/fsl/fslview/gl/gl21/shaders.py deleted file mode 100644 index 11c6ae7fcd653537290b7f258f6ebf7c260c594e..0000000000000000000000000000000000000000 --- a/fsl/fslview/gl/gl21/shaders.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python -# -# shaders.py - Convenience functions for managing vertex/fragment shaders. -# -# Author: Paul McCarthy <pauldmccarthy@gmail.com> -# -"""Convenience for managing vertex and fragment shader source code. - -The :mod:`shaders` module provides convenience functions for accessing the -vertex and fragment shader source files used to render different types of GL -objects. - -All shader programs and associated files are assumed to be located in the same -directory as this module (i.e. the :mod:`fsl.fslview.gl.gl21` package). - -When a shader file is loaded, a simple preprocessor is applied to the source - -any lines of the form '#pragma include filename', will be replaced with the -contents of the specified file. -""" - -import logging -log = logging.getLogger(__name__) - -import os.path as op - -import fsl.fslview.gl.glvolume as glvolume - - -def getVertexShader(globj): - """Returns the vertex shader source for the given GL object.""" - return _getShader(globj, 'vert') - - -def getFragmentShader(globj): - """Returns the fragment shader source for the given GL object.""" - return _getShader(globj, 'frag') - - -def _getShader(globj, shaderType): - """Returns the shader source for the given GL object and the given - shader type ('vert' or 'frag'). - """ - fname = _getFileName(globj, shaderType) - with open(fname, 'rt') as f: src = f.read() - return _preprocess(src) - - -def _getFileName(globj, shaderType): - """Returns the file name of the shader program for the given GL object - and shader type. - """ - - if shaderType not in ('vert', 'frag'): - raise RuntimeError('Invalid shader type: {}'.format(shaderType)) - - if isinstance(globj, glvolume .GLVolume): prefix = 'glvolume' - else: - raise RuntimeError('Unknown GL object type: ' - '{}'.format(type(globj))) - - return op.join(op.dirname(__file__), '{}_{}.glsl'.format( - prefix, shaderType)) - - -def _preprocess(src): - """'Preprocess' the given shader source. - - This amounts to searching for lines containing '#pragma include filename', - and replacing those lines with the contents of the specified files. - """ - - lines = src.split('\n') - lines = [l.strip() for l in lines] - - pragmas = [] - for linei, line in enumerate(lines): - if line.startswith('#pragma'): - pragmas.append(linei) - - includes = [] - for linei in pragmas: - - line = lines[linei].split() - - if len(line) != 3: continue - if line[1] != 'include': continue - - includes.append((linei, line[2])) - - for linei, fname in includes: - fname = op.join(op.dirname(__file__), fname) - with open(fname, 'rt') as f: - lines[linei] = f.read() - - return '\n'.join(lines) diff --git a/fsl/fslview/gl/shaders.py b/fsl/fslview/gl/shaders.py new file mode 100644 index 0000000000000000000000000000000000000000..3c5490c6756782057cd81f2bb4eeb77914442cd0 --- /dev/null +++ b/fsl/fslview/gl/shaders.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python +# +# shaders.py - Convenience functions for managing vertex/fragment shaders. +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# +"""Convenience for managing vertex and fragment shader source code. + +The :mod:`shaders` module provides convenience functions for accessing the +vertex and fragment shader source files used to render different types of GL +objects. + +All shader programs and associated files are assumed to be located in one of +the OpenGL version specific packages, i.e. :mod:`fsl.fslview.gl.gl14` +(ARB_vertex_program/ARB_fragment_program shaders) or +:mod:`fsl.fslview.gl.gl21` (GLSL shaders). + +When a shader file is loaded, a simple preprocessor is applied to the source - +any lines of the form '#pragma include filename', will be replaced with the +contents of the specified file. +""" + +import logging + +import os.path as op + +import fsl.fslview.gl as fslgl +import fsl.fslview.gl.glvolume as glvolume +import fsl.fslview.gl.gltensor as gltensor + + +log = logging.getLogger(__name__) + + +def compilePrograms(vertexProgramSrc, fragmentProgramSrc): + """Compiles the given vertex and fragment programs (written according + to the ARB_vertex_program and ARB_fragment_program extensions), and + returns references to the compiled programs. + """ + + import OpenGL.GL as gl + import OpenGL.GL.ARB.fragment_program as arbfp + import OpenGL.GL.ARB.vertex_program as arbvp + + gl.glEnable(arbvp.GL_VERTEX_PROGRAM_ARB) + gl.glEnable(arbfp.GL_FRAGMENT_PROGRAM_ARB) + + fragmentProgram = arbfp.glGenProgramsARB(1) + vertexProgram = arbvp.glGenProgramsARB(1) + + # vertex program + arbvp.glBindProgramARB(arbvp.GL_VERTEX_PROGRAM_ARB, + vertexProgram) + + arbvp.glProgramStringARB(arbvp.GL_VERTEX_PROGRAM_ARB, + arbvp.GL_PROGRAM_FORMAT_ASCII_ARB, + len(vertexProgramSrc), + vertexProgramSrc) + + if (gl.glGetError() == gl.GL_INVALID_OPERATION): + + position = gl.glGetIntegerv(arbvp.GL_PROGRAM_ERROR_POSITION_ARB) + message = gl.glGetString( arbvp.GL_PROGRAM_ERROR_STRING_ARB) + + raise RuntimeError('Error compiling vertex program ' + '({}): {}'.format(position, message)) + + # fragment program + arbfp.glBindProgramARB(arbfp.GL_FRAGMENT_PROGRAM_ARB, + fragmentProgram) + + arbfp.glProgramStringARB(arbfp.GL_FRAGMENT_PROGRAM_ARB, + arbfp.GL_PROGRAM_FORMAT_ASCII_ARB, + len(fragmentProgramSrc), + fragmentProgramSrc) + + if (gl.glGetError() == gl.GL_INVALID_OPERATION): + + position = gl.glGetIntegerv(arbfp.GL_PROGRAM_ERROR_POSITION_ARB) + message = gl.glGetString( arbfp.GL_PROGRAM_ERROR_STRING_ARB) + + raise RuntimeError('Error compiling fragment program ' + '({}): {}'.format(position, message)) + + gl.glDisable(arbvp.GL_VERTEX_PROGRAM_ARB) + gl.glDisable(arbfp.GL_FRAGMENT_PROGRAM_ARB) + + return vertexProgram, fragmentProgram + + +def compileShaders(vertShaderSrc, fragShaderSrc): + """Compiles and links the OpenGL GLSL vertex and fragment shader + programs, and returns a reference to the resulting program. Raises + an error if compilation/linking fails. + + I'm explicitly not using the PyOpenGL + :func:`OpenGL.GL.shaders.compileProgram` function, because it attempts + to validate the program after compilation, which fails due to texture + data not being bound at the time of validation. + """ + import OpenGL.GL as gl + + # vertex shader + vertShader = gl.glCreateShader(gl.GL_VERTEX_SHADER) + gl.glShaderSource(vertShader, vertShaderSrc) + gl.glCompileShader(vertShader) + vertResult = gl.glGetShaderiv(vertShader, gl.GL_COMPILE_STATUS) + + if vertResult != gl.GL_TRUE: + raise RuntimeError('{}'.format(gl.glGetShaderInfoLog(vertShader))) + + # fragment shader + fragShader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER) + gl.glShaderSource(fragShader, fragShaderSrc) + gl.glCompileShader(fragShader) + fragResult = gl.glGetShaderiv(fragShader, gl.GL_COMPILE_STATUS) + + if fragResult != gl.GL_TRUE: + raise RuntimeError('{}'.format(gl.glGetShaderInfoLog(fragShader))) + + # link all of the shaders! + program = gl.glCreateProgram() + gl.glAttachShader(program, vertShader) + gl.glAttachShader(program, fragShader) + + gl.glLinkProgram(program) + + gl.glDeleteShader(vertShader) + gl.glDeleteShader(fragShader) + + linkResult = gl.glGetProgramiv(program, gl.GL_LINK_STATUS) + + if linkResult != gl.GL_TRUE: + raise RuntimeError('{}'.format(gl.glGetProgramInfoLog(program))) + + return program + + +def getVertexShader(globj): + """Returns the vertex shader source for the given GL object.""" + return _getShader(globj, 'vert') + + +def getFragmentShader(globj): + """Returns the fragment shader source for the given GL object.""" + return _getShader(globj, 'frag') + + +def _getShader(globj, shaderType): + """Returns the shader source for the given GL object and the given + shader type ('vert' or 'frag'). + """ + fname = _getFileName(globj, shaderType) + with open(fname, 'rt') as f: src = f.read() + return _preprocess(src) + + +def _getFileName(globj, shaderType): + """Returns the file name of the shader program for the given GL object + and shader type. + """ + + if fslgl.GL_VERSION == '2.1': + subdir = 'gl21' + suffix = 'glsl' + elif fslgl.GL_VERSION == '1.4': + subdir = 'gl14' + suffix = 'prog' + + if shaderType not in ('vert', 'frag'): + raise RuntimeError('Invalid shader type: {}'.format(shaderType)) + + # callers can request a specific + # shader by passing the name, rather + # than passing a GLObject instance + if isinstance(globj, str): prefix = globj + elif isinstance(globj, glvolume.GLVolume): prefix = 'glvolume' + elif isinstance(globj, gltensor.GLTensor): prefix = 'gltensor' + else: + raise RuntimeError('Unknown GL object type: ' + '{}'.format(type(globj))) + + return op.join(op.dirname(__file__), subdir, '{}_{}.{}'.format( + prefix, shaderType, suffix)) + + +def _preprocess(src): + """'Preprocess' the given shader source. + + This amounts to searching for lines containing '#pragma include filename', + and replacing those lines with the contents of the specified files. + """ + + if fslgl.GL_VERSION == '2.1': subdir = 'gl21' + elif fslgl.GL_VERSION == '1.4': subdir = 'gl14' + + lines = src.split('\n') + lines = [l.strip() for l in lines] + + pragmas = [] + for linei, line in enumerate(lines): + if line.startswith('#pragma'): + pragmas.append(linei) + + includes = [] + for linei in pragmas: + + line = lines[linei].split() + + if len(line) != 3: continue + if line[1] != 'include': continue + + includes.append((linei, line[2])) + + for linei, fname in includes: + fname = op.join(op.dirname(__file__), subdir, fname) + with open(fname, 'rt') as f: + lines[linei] = f.read() + + return '\n'.join(lines)