=== added file 'data/models/asteroid-high.3ds'
Binary files data/models/asteroid-high.3ds	1970-01-01 00:00:00 +0000 and data/models/asteroid-high.3ds	2011-07-14 11:25:43 +0000 differ
=== added file 'data/models/asteroid-low.3ds'
Binary files data/models/asteroid-low.3ds	1970-01-01 00:00:00 +0000 and data/models/asteroid-low.3ds	2011-07-14 11:25:43 +0000 differ
=== added file 'data/shaders/bump-normals.frag'
--- data/shaders/bump-normals.frag	1970-01-01 00:00:00 +0000
+++ data/shaders/bump-normals.frag	2011-07-19 10:09:30 +0000
@@ -0,0 +1,50 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform vec4 LightSourcePosition;
+uniform vec3 LightSourceHalfVector;
+uniform sampler2D NormalMap;
+uniform mat4 NormalMatrix;
+
+varying vec2 TextureCoord;
+
+void main(void)
+{
+    const vec4 LightSourceAmbient = vec4(0.1, 0.1, 0.1, 1.0);
+    const vec4 LightSourceDiffuse = vec4(0.8, 0.8, 0.8, 1.0);
+    const vec4 LightSourceSpecular = vec4(0.8, 0.8, 0.8, 1.0);
+    const vec4 MaterialAmbient = vec4(1.0, 1.0, 1.0, 1.0);
+    const vec4 MaterialDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
+    const vec4 MaterialSpecular = vec4(0.2, 0.2, 0.2, 1.0);
+    const float MaterialShininess = 100.0;
+
+    // Get the raw normal XYZ data from the normal map
+    vec3 normal_raw = texture2D(NormalMap, TextureCoord).xyz;
+    // Map "color" range [0, 1.0] to normal range [-1.0, 1.0]
+    vec3 normal_scaled = normal_raw * 2.0 - 1.0;
+                                
+    // Convert the normal to eye coordinates. Note that the normal map
+    // we are using is using object coordinates (not tangent!) for the
+    // normals, so we can multiply by the NormalMatrix as usual.
+    vec3 N = normalize(vec3(NormalMatrix * vec4(normal_scaled, 1.0)));
+
+    // In the lighting model we are using here (Blinn-Phong with light at
+    // infinity, viewer at infinity), the light position/direction and the
+    // half vector is constant for the all the fragments.
+    vec3 L = normalize(LightSourcePosition.xyz);
+    vec3 H = normalize(LightSourceHalfVector);
+
+    // Calculate the diffuse color according to Lambertian reflectance
+    vec4 diffuse = MaterialDiffuse * LightSourceDiffuse * max(dot(N, L), 0.0);
+
+    // Calculate the ambient color
+    vec4 ambient = MaterialAmbient * LightSourceAmbient;
+
+    // Calculate the specular color according to the Blinn-Phong model
+    vec4 specular = MaterialSpecular * LightSourceSpecular *
+                    pow(max(dot(N,H), 0.0), MaterialShininess);
+
+    // Calculate the final color
+    gl_FragColor = ambient + specular + diffuse;
+}

=== added file 'data/shaders/bump-normals.vert'
--- data/shaders/bump-normals.vert	1970-01-01 00:00:00 +0000
+++ data/shaders/bump-normals.vert	2011-07-19 10:09:30 +0000
@@ -0,0 +1,14 @@
+attribute vec3 position;
+attribute vec2 texcoord;
+
+uniform mat4 ModelViewProjectionMatrix;
+
+varying vec2 TextureCoord;
+
+void main(void)
+{
+    TextureCoord = texcoord;
+
+    // Transform the position to clip coordinates
+    gl_Position = ModelViewProjectionMatrix * vec4(position, 1.0);
+}

=== added file 'data/shaders/bump-poly.frag'
--- data/shaders/bump-poly.frag	1970-01-01 00:00:00 +0000
+++ data/shaders/bump-poly.frag	2011-07-19 10:09:30 +0000
@@ -0,0 +1,40 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform vec4 LightSourcePosition;
+uniform vec3 LightSourceHalfVector;
+
+varying vec3 Normal;
+
+void main(void)
+{
+    const vec4 LightSourceAmbient = vec4(0.1, 0.1, 0.1, 1.0);
+    const vec4 LightSourceDiffuse = vec4(0.8, 0.8, 0.8, 1.0);
+    const vec4 LightSourceSpecular = vec4(0.8, 0.8, 0.8, 1.0);
+    const vec4 MaterialAmbient = vec4(1.0, 1.0, 1.0, 1.0);
+    const vec4 MaterialDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
+    const vec4 MaterialSpecular = vec4(0.2, 0.2, 0.2, 1.0);
+    const float MaterialShininess = 100.0;
+
+    vec3 N = normalize(Normal);
+
+    // In the lighting model we are using here (Blinn-Phong with light at
+    // infinity, viewer at infinity), the light position/direction and the
+    // half vector is constant for the all the fragments.
+    vec3 L = normalize(LightSourcePosition.xyz);
+    vec3 H = normalize(LightSourceHalfVector);
+
+    // Calculate the diffuse color according to Lambertian reflectance
+    vec4 diffuse = MaterialDiffuse * LightSourceDiffuse * max(dot(N, L), 0.0);
+
+    // Calculate the ambient color
+    vec4 ambient = MaterialAmbient * LightSourceAmbient;
+
+    // Calculate the specular color according to the Blinn-Phong model
+    vec4 specular = MaterialSpecular * LightSourceSpecular *
+                    pow(max(dot(N,H), 0.0), MaterialShininess);
+
+    // Calculate the final color
+    gl_FragColor = ambient + specular + diffuse;
+}

=== added file 'data/shaders/bump-poly.vert'
--- data/shaders/bump-poly.vert	1970-01-01 00:00:00 +0000
+++ data/shaders/bump-poly.vert	2011-07-19 10:09:30 +0000
@@ -0,0 +1,16 @@
+attribute vec3 position;
+attribute vec3 normal;
+
+uniform mat4 ModelViewProjectionMatrix;
+uniform mat4 NormalMatrix;
+
+varying vec3 Normal;
+
+void main(void)
+{
+    // Transform the normal to eye coordinates
+    Normal = normalize(vec3(NormalMatrix * vec4(normal, 1.0)));
+
+    // Transform the position to clip coordinates
+    gl_Position = ModelViewProjectionMatrix * vec4(position, 1.0);
+}

=== added file 'data/textures/asteroid-normal-map.png'
Binary files data/textures/asteroid-normal-map.png	1970-01-01 00:00:00 +0000 and data/textures/asteroid-normal-map.png	2011-07-14 11:25:43 +0000 differ
=== modified file 'src/main.cpp'
--- src/main.cpp	2011-07-12 17:53:10 +0000
+++ src/main.cpp	2011-07-19 10:18:35 +0000
@@ -48,6 +48,8 @@
     "shading:shading=gouraud",
     "shading:shading=blinn-phong-inf",
     "shading:shading=phong",
+    "bump:bump-render=high-poly",
+    "bump:bump-render=normals",
     "conditionals:vertex-steps=0:fragment-steps=0",
     "conditionals:vertex-steps=0:fragment-steps=5",
     "conditionals:vertex-steps=5:fragment-steps=0",
@@ -218,6 +220,7 @@
     Benchmark::register_scene(*new SceneConditionals(canvas));
     Benchmark::register_scene(*new SceneFunction(canvas));
     Benchmark::register_scene(*new SceneLoop(canvas));
+    Benchmark::register_scene(*new SceneBump(canvas));
 
     if (Options::list_scenes) {
         list_scenes();

=== added file 'src/scene-bump.cpp'
--- src/scene-bump.cpp	1970-01-01 00:00:00 +0000
+++ src/scene-bump.cpp	2011-07-19 11:57:20 +0000
@@ -0,0 +1,247 @@
+/*
+ * 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 "scene.h"
+#include "log.h"
+#include "mat.h"
+#include "stack.h"
+#include <cmath>
+
+SceneBump::SceneBump(Canvas &pCanvas) :
+    Scene(pCanvas, "bump")
+{
+    mOptions["bump-render"] = Scene::Option("bump-render", "off",
+                                            "How to render bumps [off, normals, high-poly]");
+}
+
+SceneBump::~SceneBump()
+{
+}
+
+int SceneBump::load()
+{
+    mRotationSpeed = 36.0f;
+
+    mRunning = false;
+
+    return 1;
+}
+
+void SceneBump::unload()
+{
+}
+
+void
+SceneBump::setup_model_plain(const std::string &type)
+{
+    static const std::string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/bump-poly.vert");
+    static const std::string frg_shader_filename(GLMARK_DATA_PATH"/shaders/bump-poly.frag");
+    static const std::string low_poly_filename(GLMARK_DATA_PATH"/models/asteroid-low.3ds");
+    static const std::string high_poly_filename(GLMARK_DATA_PATH"/models/asteroid-high.3ds");
+    Model model;
+
+    std::string poly_filename = type == "high-poly" ?
+                                high_poly_filename : low_poly_filename;
+
+    if(!model.load_3ds(poly_filename))
+        return;
+
+    model.calculate_normals();
+
+    /* Tell the converter that we only care about position and normal attributes */
+    std::vector<std::pair<Model::AttribType, int> > attribs;
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypePosition, 3));
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypeNormal, 3));
+
+    model.convert_to_mesh(mMesh, attribs);
+
+    if (!Scene::load_shaders_from_files(mProgram, vtx_shader_filename,
+                                        frg_shader_filename))
+    {
+        return;
+    }
+
+    std::vector<GLint> attrib_locations;
+    attrib_locations.push_back(mProgram.getAttribIndex("position"));
+    attrib_locations.push_back(mProgram.getAttribIndex("normal"));
+    mMesh.set_attrib_locations(attrib_locations);
+}
+
+void
+SceneBump::setup_model_normals()
+{
+    static const std::string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/bump-normals.vert");
+    static const std::string frg_shader_filename(GLMARK_DATA_PATH"/shaders/bump-normals.frag");
+    Model model;
+
+    if(!model.load_3ds(GLMARK_DATA_PATH"/models/asteroid-low.3ds"))
+        return;
+
+    /* 
+     * We don't care about the vertex normals. We are using a per-fragment
+     * normal map (in object space coordinates).
+     */
+    std::vector<std::pair<Model::AttribType, int> > attribs;
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypePosition, 3));
+    attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypeTexcoord, 2));
+
+    model.convert_to_mesh(mMesh, attribs);
+
+    if (!Scene::load_shaders_from_files(mProgram, vtx_shader_filename,
+                                        frg_shader_filename))
+    {
+        return;
+    }
+
+    std::vector<GLint> attrib_locations;
+    attrib_locations.push_back(mProgram.getAttribIndex("position"));
+    attrib_locations.push_back(mProgram.getAttribIndex("texcoord"));
+    mMesh.set_attrib_locations(attrib_locations);
+
+    Texture::load(GLMARK_DATA_PATH"/textures/asteroid-normal-map.png", &mTexture,
+                  GL_NEAREST, GL_NEAREST, 0);
+}
+
+void SceneBump::setup()
+{
+    Scene::setup();
+
+    const std::string &bump_render = mOptions["bump-render"].value;
+
+    if (bump_render == "normals")
+        setup_model_normals();
+    else if (bump_render == "off" || bump_render == "high-poly")
+        setup_model_plain(bump_render);
+
+    static const LibMatrix::vec4 lightPosition(20.0f, 20.0f, 10.0f, 1.0f);
+
+    mMesh.build_vbo();
+
+    mProgram.start();
+
+    // Load lighting and material uniforms
+    mProgram.loadUniformVector(lightPosition, "LightSourcePosition");
+
+    // Calculate and load the half vector
+    LibMatrix::vec3 halfVector(lightPosition.x(), lightPosition.y(), lightPosition.z());
+    halfVector.normalize();
+    halfVector += LibMatrix::vec3(0.0, 0.0, 1.0);
+    halfVector.normalize();
+    mProgram.loadUniformVector(halfVector, "LightSourceHalfVector");
+
+    // Load texture sampler value
+    mProgram.loadUniformScalar(0, "NormalMap");
+
+    mCurrentFrame = 0;
+    mRotation = 0.0;
+    mRunning = true;
+    mStartTime = Scene::get_timestamp_us() / 1000000.0;
+    mLastUpdateTime = mStartTime;
+}
+
+void
+SceneBump::teardown()
+{
+    mMesh.reset();
+
+    mProgram.stop();
+    mProgram.release();
+
+    glDeleteTextures(1, &mTexture);
+
+    Scene::teardown();
+}
+
+void SceneBump::update()
+{
+    double current_time = Scene::get_timestamp_us() / 1000000.0;
+    double dt = current_time - mLastUpdateTime;
+    double elapsed_time = current_time - mStartTime;
+
+    mLastUpdateTime = current_time;
+
+    if (elapsed_time >= mDuration) {
+        mAverageFPS = mCurrentFrame / elapsed_time;
+        mRunning = false;
+    }
+
+    mRotation += mRotationSpeed * dt;
+
+    mCurrentFrame++;
+}
+
+void SceneBump::draw()
+{
+    LibMatrix::Stack4 model_view;
+
+    // Load the ModelViewProjectionMatrix uniform in the shader
+    LibMatrix::mat4 model_view_proj(mCanvas.projection());
+
+    model_view.translate(0.0f, 0.0f, -3.5f);
+    model_view.rotate(mRotation, 0.0f, 1.0f, 0.0f);
+    model_view_proj *= model_view.getCurrent();
+
+    mProgram.loadUniformMatrix(model_view_proj, "ModelViewProjectionMatrix");
+
+    // Load the NormalMatrix uniform in the shader. The NormalMatrix is the
+    // inverse transpose of the model view matrix.
+    LibMatrix::mat4 normal_matrix(model_view.getCurrent());
+    normal_matrix.inverse().transpose();
+    mProgram.loadUniformMatrix(normal_matrix, "NormalMatrix");
+
+    mMesh.render_vbo();
+}
+
+Scene::ValidationResult
+SceneBump::validate()
+{
+    static const double radius_3d(std::sqrt(3.0));
+
+    if (mRotation != 0) 
+        return Scene::ValidationUnknown;
+
+    Canvas::Pixel ref;
+
+    Canvas::Pixel pixel = mCanvas.read_pixel(mCanvas.width() / 2,
+                                             mCanvas.height() / 2);
+
+    const std::string &bump_render = mOptions["bump-render"].value;
+
+    if (bump_render == "off")
+        ref = Canvas::Pixel(0x81, 0x81, 0x81, 0xff);
+    else if (bump_render == "high-poly")
+        ref = Canvas::Pixel(0x9c, 0x9c, 0x9c, 0xff);
+    else if (bump_render == "normals")
+        ref = Canvas::Pixel(0xa4, 0xa4, 0xa4, 0xff);
+    else
+        return Scene::ValidationUnknown;
+
+    double dist = pixel_value_distance(pixel, ref);
+
+    if (dist < radius_3d + 0.01) {
+        return Scene::ValidationSuccess;
+    }
+    else {
+        Log::debug("Validation failed! Expected: 0x%x Actual: 0x%x Distance: %f\n",
+                    ref.to_le32(), pixel.to_le32(), dist);
+        return Scene::ValidationFailure;
+    }
+}

=== modified file 'src/scene.h'
--- src/scene.h	2011-07-08 12:48:36 +0000
+++ src/scene.h	2011-07-19 10:09:30 +0000
@@ -253,4 +253,31 @@
 
     ~SceneLoop();
 };
+
+class SceneBump : public Scene
+{
+public:
+    SceneBump(Canvas &pCanvas);
+    int load();
+    void unload();
+    void setup();
+    void teardown();
+    void update();
+    void draw();
+    ValidationResult validate();
+
+    ~SceneBump();
+
+protected:
+    Program mProgram;
+
+    Mesh mMesh;
+    GLuint mTexture;
+    float mRotation;
+    float mRotationSpeed;
+private:
+    void setup_model_plain(const std::string &type);
+    void setup_model_normals();
+};
+
 #endif

