blob: 1e08b7adbfa31e13cc9a4b85dd61acb0a5e9d5ad [file] [log] [blame]
/*
* Copyright © 2010-2012 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)
* Jesse Barker (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"
#include "util.h"
#include "texture.h"
SceneEffect2D::SceneEffect2D(Canvas &pCanvas) :
Scene(pCanvas, "effect2d")
{
options_["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...\"");;
options_["normalize"] = Scene::Option("normalize", "true",
"Whether to normalize the supplied convolution kernel matrix",
"false,true");
}
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 canvas the destination Canvas for this shader
* @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(Canvas &canvas, 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/canvas.width());
source.add_const("TextureStepY", 1.0f/canvas.height());
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();
}
/**
* 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(const std::string &str, std::vector<float> &matrix,
unsigned int &width, unsigned int &height)
{
std::vector<std::string> rows;
unsigned int w = UINT_MAX;
Util::split(str, ';', rows);
Log::debug("Parsing kernel matrix:\n");
static const std::string format("%f ");
static const std::string format_cont(Log::continuation_prefix + format);
static const std::string newline(Log::continuation_prefix + "\n");
for (std::vector<std::string>::const_iterator iter = rows.begin();
iter != rows.end();
iter++)
{
std::vector<std::string> elems;
Util::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++)
{
float f(Util::fromString<float>(*iter_el));
matrix.push_back(f);
if (iter_el == elems.begin())
Log::debug(format.c_str(), f);
else
Log::debug(format_cont.c_str(), f);
}
Log::debug(newline.c_str());
}
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;
}
}
bool
SceneEffect2D::load()
{
Texture::load("effect-2d", &texture_,
GL_NEAREST, GL_NEAREST, 0);
running_ = false;
return true;
}
void
SceneEffect2D::unload()
{
glDeleteTextures(1, &texture_);
}
void
SceneEffect2D::setup()
{
Scene::setup();
Texture::find_textures();
static const std::string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/effect-2d.vert");
std::vector<float> kernel;
unsigned int kernel_width = 0;
unsigned int kernel_height = 0;
/* Parse the kernel matrix from the options */
if (!parse_matrix(options_["kernel"].value, kernel,
kernel_width, kernel_height))
{
return;
}
/* Normalize the kernel matrix if needed */
if (options_["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(canvas_, 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_["position"].location());
mesh_.set_attrib_locations(attrib_locations);
program_.start();
// Load texture sampler value
program_["Texture0"] = 0;
currentFrame_ = 0;
running_ = true;
startTime_ = Util::get_timestamp_us() / 1000000.0;
lastUpdateTime_ = startTime_;
}
void
SceneEffect2D::teardown()
{
mesh_.reset();
program_.stop();
program_.release();
Scene::teardown();
}
void
SceneEffect2D::update()
{
Scene::update();
}
void
SceneEffect2D::draw()
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_);
mesh_.render_vbo();
}
Scene::ValidationResult
SceneEffect2D::validate()
{
static const double radius_3d(std::sqrt(3.0));
std::vector<float> kernel;
std::vector<float> kernel_edge;
std::vector<float> kernel_blur;
unsigned int kernel_width = 0;
unsigned int kernel_height = 0;
if (!parse_matrix("0,1,0;1,-4,1;0,1,0;", kernel_edge,
kernel_width, kernel_height))
{
return Scene::ValidationUnknown;
}
if (!parse_matrix("1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;",
kernel_blur,
kernel_width, kernel_height))
{
return Scene::ValidationUnknown;
}
if (!parse_matrix(options_["kernel"].value, kernel,
kernel_width, kernel_height))
{
return Scene::ValidationUnknown;
}
Canvas::Pixel ref;
if (kernel == kernel_edge)
ref = Canvas::Pixel(0x17, 0x0c, 0x2f, 0xff);
else if (kernel == kernel_blur)
ref = Canvas::Pixel(0xc7, 0xe1, 0x8d, 0xff);
else
return Scene::ValidationUnknown;
Canvas::Pixel pixel = canvas_.read_pixel(452, 237);
double dist = pixel.distance_rgb(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;
}
return Scene::ValidationUnknown;
}