diff mbox

[Branch,~glmark2-dev/glmark2/trunk] Rev 115: Implement a benchmarking scene for GPU-based 2D image filtering.

Message ID 20110802144226.4934.52250.launchpad@loganberry.canonical.com
State Accepted
Headers show

Commit Message

alexandros.frantzis@linaro.org Aug. 2, 2011, 2:42 p.m. UTC
Merge authors:
  Alexandros Frantzis (afrantzis)
------------------------------------------------------------
revno: 115 [merge]
committer: Alexandros Frantzis <alexandros.frantzis@linaro.org>
branch nick: trunk
timestamp: Tue 2011-08-02 15:40:55 +0100
message:
  Implement a benchmarking scene for GPU-based 2D image filtering.
added:
  data/shaders/effect-2d-convolution.frag
  data/shaders/effect-2d.vert
  data/textures/effect-2d.png
  src/scene-effect-2d.cpp
modified:
  src/main.cpp
  src/scene-build.cpp
  src/scene-bump.cpp
  src/scene-shading.cpp
  src/scene-texture.cpp
  src/scene.h
  src/shader-source.cpp
  src/shader-source.h


--
lp:glmark2
https://code.launchpad.net/~glmark2-dev/glmark2/trunk

You are subscribed to branch lp:glmark2.
To unsubscribe from this branch go to https://code.launchpad.net/~glmark2-dev/glmark2/trunk/+edit-subscription
diff mbox

Patch

=== added file 'data/shaders/effect-2d-convolution.frag'
--- data/shaders/effect-2d-convolution.frag	1970-01-01 00:00:00 +0000
+++ data/shaders/effect-2d-convolution.frag	2011-07-28 14:06:56 +0000
@@ -0,0 +1,16 @@ 
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform sampler2D Texture0;
+varying vec2 TextureCoord;
+
+void main(void)
+{
+    vec4 result;
+
+    $CONVOLUTION$
+
+    gl_FragColor = result;
+}
+

=== added file 'data/shaders/effect-2d.vert'
--- data/shaders/effect-2d.vert	1970-01-01 00:00:00 +0000
+++ data/shaders/effect-2d.vert	2011-07-28 14:06:56 +0000
@@ -0,0 +1,10 @@ 
+attribute vec3 position;
+
+varying vec2 TextureCoord;
+
+void main(void)
+{
+    gl_Position = vec4(position, 1.0);
+
+    TextureCoord = position.xy * 0.5 + 0.5;
+}

=== added file 'data/textures/effect-2d.png'
Binary files data/textures/effect-2d.png	1970-01-01 00:00:00 +0000 and data/textures/effect-2d.png	2011-07-28 14:06:56 +0000 differ
=== modified file 'src/main.cpp'
--- src/main.cpp	2011-07-19 10:18:35 +0000
+++ src/main.cpp	2011-08-02 14:29:51 +0000
@@ -50,6 +50,8 @@ 
     "shading:shading=phong",
     "bump:bump-render=high-poly",
     "bump:bump-render=normals",
+    "effect2d:kernel=0,1,0;1,-4,1;0,1,0;",
+    "effect2d:kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;",
     "conditionals:vertex-steps=0:fragment-steps=0",
     "conditionals:vertex-steps=0:fragment-steps=5",
     "conditionals:vertex-steps=5:fragment-steps=0",
@@ -221,6 +223,7 @@ 
     Benchmark::register_scene(*new SceneFunction(canvas));
     Benchmark::register_scene(*new SceneLoop(canvas));
     Benchmark::register_scene(*new SceneBump(canvas));
+    Benchmark::register_scene(*new SceneEffect2D(canvas));
 
     if (Options::list_scenes) {
         list_scenes();

=== modified file 'src/scene-build.cpp'
--- src/scene-build.cpp	2011-07-27 15:02:27 +0000
+++ src/scene-build.cpp	2011-07-28 08:44:31 +0000
@@ -64,8 +64,8 @@ 
     ShaderSource vtx_source(vtx_shader_filename);
     ShaderSource frg_source(frg_shader_filename);
 
-    vtx_source.add_global_const("LightSourcePosition", lightPosition);
-    vtx_source.add_global_const("MaterialDiffuse", materialDiffuse);
+    vtx_source.add_const("LightSourcePosition", lightPosition);
+    vtx_source.add_const("MaterialDiffuse", materialDiffuse);
 
     if (!Scene::load_shaders_from_strings(mProgram, vtx_source.str(),
                                           frg_source.str()))

=== modified file 'src/scene-bump.cpp'
--- src/scene-bump.cpp	2011-07-27 15:02:27 +0000
+++ src/scene-bump.cpp	2011-07-28 08:44:31 +0000
@@ -86,8 +86,8 @@ 
     ShaderSource frg_source(frg_shader_filename);
 
     /* Add constants to shaders */
-    frg_source.add_global_const("LightSourcePosition", lightPosition);
-    frg_source.add_global_const("LightSourceHalfVector", halfVector);
+    frg_source.add_const("LightSourcePosition", lightPosition);
+    frg_source.add_const("LightSourceHalfVector", halfVector);
 
     if (!Scene::load_shaders_from_strings(mProgram, vtx_source.str(),
                                           frg_source.str()))
@@ -133,8 +133,8 @@ 
     ShaderSource frg_source(frg_shader_filename);
 
     /* Add constants to shaders */
-    frg_source.add_global_const("LightSourcePosition", lightPosition);
-    frg_source.add_global_const("LightSourceHalfVector", halfVector);
+    frg_source.add_const("LightSourcePosition", lightPosition);
+    frg_source.add_const("LightSourceHalfVector", halfVector);
 
     if (!Scene::load_shaders_from_strings(mProgram, vtx_source.str(),
                                           frg_source.str()))

=== added file 'src/scene-effect-2d.cpp'
--- src/scene-effect-2d.cpp	1970-01-01 00:00:00 +0000
+++ src/scene-effect-2d.cpp	2011-08-02 14:28:18 +0000
@@ -0,0 +1,405 @@ 
+/*
+ * Copyright © 2010-2011 Linaro Limited
+ *
+ * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
+ *
+ * glmark2 is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * glmark2.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *  Alexandros Frantzis (glmark2)
+ */
+#include <cmath>
+#include <climits>
+#include <numeric>
+
+#include "scene.h"
+#include "mat.h"
+#include "stack.h"
+#include "vec.h"
+#include "log.h"
+#include "program.h"
+#include "shader-source.h"
+
+
+SceneEffect2D::SceneEffect2D(Canvas &pCanvas) :
+    Scene(pCanvas, "effect2d")
+{
+    mOptions["kernel"] = Scene::Option("kernel",
+        "0,0,0;0,1,0;0,0,0",
+        "The convolution kernel matrix to use [format: \"a,b,c...;d,e,f...\"");;
+    mOptions["normalize"] = Scene::Option("normalize", "true",
+        "Whether to normalize the supplied convolution kernel matrix [true,false]");
+}
+
+SceneEffect2D::~SceneEffect2D()
+{
+}
+
+/*
+ * Calculates the offset of the coefficient with index i
+ * from the center of the kernel matrix. Note that we are
+ * using the standard OpenGL texture coordinate system
+ * (x grows rightwards, y grows upwards).
+ */
+static LibMatrix::vec2
+calc_offset(unsigned int i, unsigned int width, unsigned int height)
+{
+    int x = i % width - (width - 1) / 2;
+    int y = -(i / width - (height - 1) / 2);
+
+    return LibMatrix::vec2(static_cast<float>(x),
+                           static_cast<float>(y));
+
+}
+
+/**
+ * Creates a fragment shader implementing 2D image convolution.
+ *
+ * In the mathematical definition of 2D convolution, the kernel/filter (2D
+ * impulse response) is essentially mirrored in both directions (that is,
+ * rotated 180 degrees) when being applied on a 2D block of data (eg pixels).
+ *
+ * Most image manipulation programs, however, use the term kernel/filter to
+ * describe a 180 degree rotation of the 2D impulse response. This is more
+ * intuitive from a human understanding perspective because this rotated matrix
+ * can be regarded as a stencil that can be directly applied by just "placing"
+ * it on the image.
+ *
+ * In order to be compatible with image manipulation programs, we will
+ * use the same definition of kernel/filter (180 degree rotation of impulse
+ * response). This also means that we don't need to perform the (implicit)
+ * rotation of the kernel in our convolution implementation.
+ *
+ * @param array the array holding the filter coefficients in row-major
+ *              order
+ * @param width the width of the filter
+ * @param width the height of the filter
+ *
+ * @return a string containing the frament source code
+ */
+static std::string
+create_convolution_fragment_shader(std::vector<float> &array,
+                                   unsigned int width, unsigned int height)
+{
+    static const std::string frg_shader_filename(GLMARK_DATA_PATH"/shaders/effect-2d-convolution.frag");
+    ShaderSource source(frg_shader_filename);
+
+    if (width * height != array.size()) {
+        Log::error("Convolution filter size doesn't match supplied dimensions\n");
+        return "";
+    }
+
+    /* Steps are needed to be able to access nearby pixels */
+    source.add_const("TextureStepX", 1.0f/800.0f);
+    source.add_const("TextureStepY", 1.0f/600.0f);
+
+    std::stringstream ss_def;
+    std::stringstream ss_convolution;
+
+    /* Set up stringstream floating point options */
+    ss_def << std::fixed;
+    ss_convolution.precision(1);
+    ss_convolution << std::fixed;
+    
+    ss_convolution << "result = ";
+
+    for(std::vector<float>::const_iterator iter = array.begin();
+        iter != array.end();
+        iter++)
+    {
+        unsigned int i = iter - array.begin();
+
+        /* Add Filter coefficient const definitions */
+        ss_def << "const float Kernel" << i << " = "
+               << *iter << ";" << std::endl;
+
+        /* Add convolution term using the current filter coefficient */
+        LibMatrix::vec2 offset(calc_offset(i, width, height));
+        ss_convolution << "texture2D(Texture0, TextureCoord + vec2("
+                       << offset.x() << " * TextureStepX, "
+                       << offset.y() << " * TextureStepY)) * Kernel" << i;
+        if (iter + 1 != array.end())
+            ss_convolution << " +" << std::endl;
+    }
+
+    ss_convolution << ";" << std::endl;
+
+    source.add(ss_def.str());
+    source.replace("$CONVOLUTION$", ss_convolution.str());
+
+    return source.str();
+}
+
+/** 
+ * Creates a string containing a printout of a kernel matrix.
+ * 
+ * @param filter the vector containing the filter coefficients
+ * @param width the width of the filter
+ * 
+ * @return the printout
+ */
+static std::string
+kernel_printout(const std::vector<float> &kernel,
+                unsigned int width)
+{
+    std::stringstream ss;
+    ss << std::fixed;
+
+    for (std::vector<float>::const_iterator iter = kernel.begin();
+         iter != kernel.end();
+         iter++)
+    {
+        ss << *iter << " ";
+        if ((iter - kernel.begin()) % width == width - 1)
+            ss << std::endl;
+    }
+
+    return ss.str();
+}
+
+/** 
+ * Splits a string using a delimiter
+ * 
+ * @param s the string to split
+ * @param delim the delimitir to use
+ * @param elems the string vector to populate
+ */
+static void
+split(const std::string &s, char delim, std::vector<std::string> &elems)
+{
+    std::stringstream ss(s);
+
+    std::string item;
+    while(std::getline(ss, item, delim))
+        elems.push_back(item);
+}
+
+/** 
+ * Parses a string representation of a matrix and returns it
+ * in row-major format.
+ *
+ * In the string representation, elements are delimited using
+ * commas (',') and rows are delimited using semi-colons (';').
+ * eg 0,0,0;0,1.0,0;0,0,0
+ * 
+ * @param str the matrix string representation to parse
+ * @param matrix the float vector to populate
+ * @param[out] width the width of the matrix
+ * @param[out] height the height of the matrix 
+ * 
+ * @return whether parsing succeeded
+ */
+static bool
+parse_matrix(std::string &str, std::vector<float> &matrix,
+             unsigned int &width, unsigned int &height)
+{
+    std::vector<std::string> rows;
+    unsigned int w = UINT_MAX;
+
+    split(str, ';', rows);
+
+    Log::debug("Parsing kernel matrix:\n");
+
+    for (std::vector<std::string>::const_iterator iter = rows.begin();
+         iter != rows.end();
+         iter++)
+    {
+        std::vector<std::string> elems;
+        split(*iter, ',', elems);
+
+        if (w != UINT_MAX && elems.size() != w) {
+            Log::error("Matrix row %u contains %u elements, whereas previous"
+                       " rows had %u\n", 
+                       iter - rows.begin(), elems.size(), w);
+            return false;
+        }
+
+        w = elems.size();
+        
+        for (std::vector<std::string>::const_iterator iter_el = elems.begin();
+             iter_el != elems.end();
+             iter_el++)
+        {
+            std::stringstream ss(*iter_el);
+            float f;
+
+            ss >> f;
+            matrix.push_back(f);
+            Log::debug("%f ", f);
+        }
+
+        Log::debug("\n");
+    }
+
+    width = w;
+    height = rows.size();
+    
+    return true;
+}
+
+/** 
+ * Normalizes a convolution kernel matrix.
+ * 
+ * @param filter the filter to normalize
+ */
+static void
+normalize(std::vector<float> &kernel)
+{
+    float sum = std::accumulate(kernel.begin(), kernel.end(), 0.0);
+
+    /* 
+     * If sum is essentially zero, perform a zero-sum normalization.
+     * This normalizes positive and negative values separately,
+     */
+    if (fabs(sum) < 0.00000001) {
+        sum = 0.0;
+        for (std::vector<float>::iterator iter = kernel.begin();
+             iter != kernel.end();
+             iter++)
+        {
+            if (*iter > 0.0)
+                sum += *iter;
+        }
+    }
+
+    /* 
+     * We can simply compare with 0.0f here, because we just care about
+     * avoiding division-by-zero.
+     */
+    if (sum == 0.0)
+        return;
+        
+    for (std::vector<float>::iterator iter = kernel.begin();
+         iter != kernel.end();
+         iter++)
+    {
+        *iter /= sum; 
+    }
+
+}
+
+int SceneEffect2D::load()
+{
+    Texture::load(GLMARK_DATA_PATH"/textures/effect-2d.png", &texture_,
+                  GL_NEAREST, GL_NEAREST, 0);
+    mRunning = false;
+
+    return 1;
+}
+
+void SceneEffect2D::unload()
+{
+    glDeleteTextures(1, &texture_);
+}
+
+void SceneEffect2D::setup()
+{
+    Scene::setup();
+
+    static const std::string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/effect-2d.vert");
+
+    std::vector<float> kernel;
+    unsigned int kernel_width;
+    unsigned int kernel_height;
+
+    /* Parse the kernel matrix from the options */
+    if (!parse_matrix(mOptions["kernel"].value, kernel,
+                      kernel_width, kernel_height))
+    {
+        return;
+    }
+
+    /* Normalize the kernel matrix if needed */
+    if (mOptions["normalize"].value == "true") {
+        normalize(kernel);
+        Log::debug("Normalized kernel matrix:\n%s",
+                   kernel_printout(kernel, kernel_width).c_str());
+    }
+
+    /* Create and load the shaders */
+    ShaderSource vtx_source(vtx_shader_filename);
+    ShaderSource frg_source;
+    frg_source.append(create_convolution_fragment_shader(kernel,
+                                                         kernel_width,
+                                                         kernel_height));
+
+    if (frg_source.str().empty())
+        return;
+
+    if (!Scene::load_shaders_from_strings(program_, vtx_source.str(),
+                                          frg_source.str()))
+    {
+        return;
+    }
+
+    std::vector<int> vertex_format;
+    vertex_format.push_back(3);
+    mesh_.set_vertex_format(vertex_format);
+
+    mesh_.make_grid(1, 1, 2.0, 2.0, 0.0);
+    mesh_.build_vbo();
+
+    std::vector<GLint> attrib_locations;
+    attrib_locations.push_back(program_.getAttribIndex("position"));
+    mesh_.set_attrib_locations(attrib_locations);
+
+    program_.start();
+
+    // Load texture sampler value
+    program_.loadUniformScalar(0, "Texture0");
+
+    mCurrentFrame = 0;
+    mRunning = true;
+    mStartTime = Scene::get_timestamp_us() / 1000000.0;
+    mLastUpdateTime = mStartTime;
+}
+
+void SceneEffect2D::teardown()
+{
+    mesh_.reset();
+
+    program_.stop();
+    program_.release();
+
+    Scene::teardown();
+}
+
+void SceneEffect2D::update()
+{
+    double current_time = Scene::get_timestamp_us() / 1000000.0;
+    double elapsed_time = current_time - mStartTime;
+
+    mLastUpdateTime = current_time;
+
+    if (elapsed_time >= mDuration) {
+        mAverageFPS = mCurrentFrame / elapsed_time;
+        mRunning = false;
+    }
+
+    mCurrentFrame++;
+}
+
+void SceneEffect2D::draw()
+{
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, texture_);
+
+    mesh_.render_vbo();
+}
+
+Scene::ValidationResult
+SceneEffect2D::validate()
+{
+    return ValidationUnknown;
+}

=== modified file 'src/scene-shading.cpp'
--- src/scene-shading.cpp	2011-07-27 15:02:27 +0000
+++ src/scene-shading.cpp	2011-07-28 08:44:31 +0000
@@ -106,11 +106,11 @@ 
     ShaderSource frg_source(frg_shader_filename);
 
     // Add constants to shaders
-    vtx_source.add_global_const("LightSourcePosition", lightPosition);
-    vtx_source.add_global_const("MaterialDiffuse", materialDiffuse);
+    vtx_source.add_const("LightSourcePosition", lightPosition);
+    vtx_source.add_const("MaterialDiffuse", materialDiffuse);
 
-    frg_source.add_global_const("LightSourcePosition", lightPosition);
-    frg_source.add_global_const("LightSourceHalfVector", halfVector);
+    frg_source.add_const("LightSourcePosition", lightPosition);
+    frg_source.add_const("LightSourceHalfVector", halfVector);
 
     if (!Scene::load_shaders_from_strings(mProgram, vtx_source.str(),
                                           frg_source.str()))

=== modified file 'src/scene-texture.cpp'
--- src/scene-texture.cpp	2011-07-27 15:02:27 +0000
+++ src/scene-texture.cpp	2011-07-28 08:44:31 +0000
@@ -63,8 +63,8 @@ 
     ShaderSource frg_source(frg_shader_filename);
 
     // Add constants to shaders
-    vtx_source.add_global_const("LightSourcePosition", lightPosition);
-    vtx_source.add_global_const("MaterialDiffuse", materialDiffuse);
+    vtx_source.add_const("LightSourcePosition", lightPosition);
+    vtx_source.add_const("MaterialDiffuse", materialDiffuse);
 
     if (!Scene::load_shaders_from_strings(mProgram, vtx_source.str(),
                                           frg_source.str()))

=== modified file 'src/scene.h'
--- src/scene.h	2011-07-19 10:09:30 +0000
+++ src/scene.h	2011-07-28 08:44:31 +0000
@@ -280,4 +280,24 @@ 
     void setup_model_normals();
 };
 
+class SceneEffect2D : public Scene
+{
+public:
+    SceneEffect2D(Canvas &pCanvas);
+    int load();
+    void unload();
+    void setup();
+    void teardown();
+    void update();
+    void draw();
+    ValidationResult validate();
+
+    ~SceneEffect2D();
+
+protected:
+    Program program_;
+
+    Mesh mesh_;
+    GLuint texture_;
+};
 #endif

=== modified file 'src/shader-source.cpp'
--- src/shader-source.cpp	2011-07-27 14:43:40 +0000
+++ src/shader-source.cpp	2011-07-28 08:44:31 +0000
@@ -156,35 +156,195 @@ 
 }
 
 /** 
- * Adds a global (per shader) vec3 constant definition.
- *
- * @param name the name of the constant
- * @param v the value of the constant
- */
-void
-ShaderSource::add_global_const(const std::string &name, const LibMatrix::vec3 &v)
+ * Adds a string (usually containing a constant definition) at
+ * global (per shader) scope.
+ *
+ * The string is placed after any default precision qualifiers.
+ * 
+ * @param function the function to add the string into
+ * @param str the string to add
+ */
+void
+ShaderSource::add_local(const std::string &str, const std::string &function)
+{
+    std::string::size_type pos = 0;
+    std::string source(source_.str());
+
+    /* Find the function */
+    pos = source.find(function);
+    pos = source.find('{', pos);
+
+    /* Go to the next line */
+    pos = source.find("\n", pos);
+    if (pos != std::string::npos)
+        pos++;
+
+    source.insert(pos, str);
+
+    source_.clear();
+    source_.str(source);
+}
+
+/** 
+ * Adds a string (usually containing a constant definition) to a shader source
+ *
+ * If the function parameter is empty, the string will be added to global
+ * scope, after any precision definitions.
+ *
+ * @param str the string to add
+ * @param function if not empty, the function to add the string into
+ */
+void
+ShaderSource::add(const std::string &str, const std::string &function)
+{
+    if (!function.empty())
+        add_local(str, function);
+    else
+        add_global(str);
+}
+
+/** 
+ * Adds a float constant definition.
+ *
+ * @param name the name of the constant
+ * @param f the value of the constant
+ * @param function if not empty, the function to put the definition in
+ */
+void
+ShaderSource::add_const(const std::string &name, float f,
+                        const std::string &function)
+{
+    std::stringstream ss;
+
+    ss << "const float " << name << " = " << std::fixed << f << ";" << std::endl;
+
+    add(ss.str(), function);
+}
+
+/** 
+ * Adds a float array constant definition.
+ *
+ * Note that various GLSL versions (including ES) don't support
+ * array constants.
+ *
+ * @param name the name of the constant
+ * @param v the value of the constant
+ * @param function if not empty, the function to put the definition in
+ */
+void
+ShaderSource::add_const(const std::string &name, std::vector<float> &array,
+                        const std::string &function)
+{
+    std::stringstream ss;
+
+    ss << "const float " << name << "[" << array.size() << "] = {" << std::fixed;
+    for(std::vector<float>::const_iterator iter = array.begin();
+        iter != array.end();
+        iter++)
+    {
+        ss << *iter;
+        if (iter + 1 != array.end())
+            ss << ", " << std::endl;
+    }
+
+    ss << "};" << std::endl;
+
+    add(ss.str(), function);
+}
+
+/** 
+ * Adds a vec3 constant definition.
+ *
+ * @param name the name of the constant
+ * @param v the value of the constant
+ * @param function if not empty, the function to put the definition in
+ */
+void
+ShaderSource::add_const(const std::string &name, const LibMatrix::vec3 &v,
+                        const std::string &function)
 {
     std::stringstream ss;
 
     ss << "const vec3 " << name << " = vec3(" << std::fixed;
     ss << v.x() << ", " << v.y() << ", " << v.z() << ");" << std::endl;
 
-    add_global(ss.str());
+    add(ss.str(), function);
 }
 
 /** 
- * Adds a global (per shader) vec4 constant definition.
+ * Adds a vec4 constant definition.
  *
  * @param name the name of the constant
  * @param v the value of the constant
+ * @param function if not empty, the function to put the definition in
  */
 void
-ShaderSource::add_global_const(const std::string &name, const LibMatrix::vec4 &v)
+ShaderSource::add_const(const std::string &name, const LibMatrix::vec4 &v,
+                        const std::string &function)
 {
     std::stringstream ss;
 
     ss << "const vec4 " << name << " = vec4(" << std::fixed;
     ss << v.x() << ", " << v.y() << ", " << v.z() << ", " << v.w() << ");" << std::endl;
 
-    add_global(ss.str());
+    add(ss.str(), function);
+}
+
+/** 
+ * Adds a mat3 constant definition.
+ *
+ * @param name the name of the constant
+ * @param v the value of the constant
+ * @param function if not empty, the function to put the definition in
+ */
+void
+ShaderSource::add_const(const std::string &name, const LibMatrix::mat3 &m,
+                        const std::string &function)
+{
+    std::stringstream ss;
+
+    ss << "const mat3 " << name << " = mat3(" << std::fixed;
+    ss << m[0][0] << ", " << m[1][0] << ", " << m[2][0] << "," << std::endl;
+    ss << m[0][1] << ", " << m[1][1] << ", " << m[2][1] << "," << std::endl;
+    ss << m[0][2] << ", " << m[1][2] << ", " << m[2][2] << std::endl;
+    ss << ");" << std::endl;
+
+    add(ss.str(), function);
+}
+
+/** 
+ * Adds a float array declaration and initialization.
+ *
+ * @param name the name of the array
+ * @param array the array values
+ * @param init_function the function to put the initialization in
+ * @param decl_function if not empty, the function to put the declaration in
+ */
+void
+ShaderSource::add_array(const std::string &name, std::vector<float> &array,
+                        const std::string &init_function,
+                        const std::string &decl_function)
+{
+    if (init_function.empty() || name.empty())
+        return;
+
+    std::stringstream ss;
+    ss << "float " << name << "[" << array.size() << "];" << std::endl;
+
+    std::string decl(ss.str());
+
+    ss.clear();
+    ss.str("");
+    ss << std::fixed;
+
+    for(std::vector<float>::const_iterator iter = array.begin();
+        iter != array.end();
+        iter++)
+    {
+        ss << name << "[" << iter - array.begin() << "] = " << *iter << ";" << std::endl; 
+    }
+
+    add(ss.str(), init_function);
+
+    add(decl, decl_function);
 }

=== modified file 'src/shader-source.h'
--- src/shader-source.h	2011-07-27 14:43:40 +0000
+++ src/shader-source.h	2011-07-28 08:44:31 +0000
@@ -22,7 +22,9 @@ 
 
 #include <string>
 #include <sstream>
+#include <vector>
 #include "vec.h"
+#include "mat.h"
 
 /** 
  * Helper class for loading and manipulating shader sources.
@@ -39,13 +41,28 @@ 
     void replace(const std::string &remove, const std::string &insert);
     void replace_with_file(const std::string &remove, const std::string &filename);
 
-    void add_global(const std::string &str);
-    void add_global_const(const std::string &name, const LibMatrix::vec3 &v);
-    void add_global_const(const std::string &name, const LibMatrix::vec4 &v);
+    void add(const std::string &str, const std::string &function = "");
+
+    void add_const(const std::string &name, float f,
+                   const std::string &function = "");
+    void add_const(const std::string &name, std::vector<float> &f,
+                   const std::string &function = "");
+    void add_const(const std::string &name, const LibMatrix::vec3 &v,
+                   const std::string &function = "");
+    void add_const(const std::string &name, const LibMatrix::vec4 &v,
+                   const std::string &function = "");
+    void add_const(const std::string &name, const LibMatrix::mat3 &m,
+                   const std::string &function = "");
+
+    void add_array(const std::string &name, std::vector<float> &array,
+                   const std::string &init_function,
+                   const std::string &decl_function = "");
 
     std::string str() { return source_.str(); }
 
 private:
+    void add_global(const std::string &str);
+    void add_local(const std::string &str, const std::string &function);
     bool load_file(const std::string& filename, std::string& str);
 
     std::stringstream source_;