1 /*
2  * Copyright © 2010-2012 Linaro Limited
3  *
4  * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
5  *
6  * glmark2 is free software: you can redistribute it and/or modify it under the
7  * terms of the GNU General Public License as published by the Free Software
8  * Foundation, either version 3 of the License, or (at your option) any later
9  * version.
10  *
11  * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * glmark2.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * Authors:
20  *  Alexandros Frantzis (glmark2)
21  *  Jesse Barker (glmark2)
22  */
23 #include <cmath>
24 #include <climits>
25 #include <numeric>
26 
27 #include "scene.h"
28 #include "mat.h"
29 #include "options.h"
30 #include "stack.h"
31 #include "vec.h"
32 #include "log.h"
33 #include "program.h"
34 #include "shader-source.h"
35 #include "util.h"
36 #include "texture.h"
37 
SceneEffect2D(Canvas & pCanvas)38 SceneEffect2D::SceneEffect2D(Canvas &pCanvas) :
39     Scene(pCanvas, "effect2d")
40 {
41     options_["kernel"] = Scene::Option("kernel",
42         "0,0,0;0,1,0;0,0,0",
43         "The convolution kernel matrix to use [format: \"a,b,c...;d,e,f...\"");;
44     options_["normalize"] = Scene::Option("normalize", "true",
45         "Whether to normalize the supplied convolution kernel matrix",
46         "false,true");
47 }
48 
~SceneEffect2D()49 SceneEffect2D::~SceneEffect2D()
50 {
51 }
52 
53 /*
54  * Calculates the offset of the coefficient with index i
55  * from the center of the kernel matrix. Note that we are
56  * using the standard OpenGL texture coordinate system
57  * (x grows rightwards, y grows upwards).
58  */
59 static LibMatrix::vec2
calc_offset(unsigned int i,unsigned int width,unsigned int height)60 calc_offset(unsigned int i, unsigned int width, unsigned int height)
61 {
62     int x = i % width - (width - 1) / 2;
63     int y = -(i / width - (height - 1) / 2);
64 
65     return LibMatrix::vec2(static_cast<float>(x),
66                            static_cast<float>(y));
67 
68 }
69 
70 /**
71  * Creates a fragment shader implementing 2D image convolution.
72  *
73  * In the mathematical definition of 2D convolution, the kernel/filter (2D
74  * impulse response) is essentially mirrored in both directions (that is,
75  * rotated 180 degrees) when being applied on a 2D block of data (eg pixels).
76  *
77  * Most image manipulation programs, however, use the term kernel/filter to
78  * describe a 180 degree rotation of the 2D impulse response. This is more
79  * intuitive from a human understanding perspective because this rotated matrix
80  * can be regarded as a stencil that can be directly applied by just "placing"
81  * it on the image.
82  *
83  * In order to be compatible with image manipulation programs, we will
84  * use the same definition of kernel/filter (180 degree rotation of impulse
85  * response). This also means that we don't need to perform the (implicit)
86  * rotation of the kernel in our convolution implementation.
87  *
88  * @param canvas the destination Canvas for this shader
89  * @param array the array holding the filter coefficients in row-major
90  *              order
91  * @param width the width of the filter
92  * @param width the height of the filter
93  *
94  * @return a string containing the frament source code
95  */
96 static std::string
create_convolution_fragment_shader(Canvas & canvas,std::vector<float> & array,unsigned int width,unsigned int height)97 create_convolution_fragment_shader(Canvas &canvas, std::vector<float> &array,
98                                    unsigned int width, unsigned int height)
99 {
100     static const std::string frg_shader_filename(Options::data_path + "/shaders/effect-2d-convolution.frag");
101     ShaderSource source(frg_shader_filename);
102 
103     if (width * height != array.size()) {
104         Log::error("Convolution filter size doesn't match supplied dimensions\n");
105         return "";
106     }
107 
108     /* Steps are needed to be able to access nearby pixels */
109     source.add_const("TextureStepX", 1.0f/canvas.width());
110     source.add_const("TextureStepY", 1.0f/canvas.height());
111 
112     std::stringstream ss_def;
113     std::stringstream ss_convolution;
114 
115     /* Set up stringstream floating point options */
116     ss_def << std::fixed;
117     ss_convolution.precision(1);
118     ss_convolution << std::fixed;
119 
120     ss_convolution << "result = ";
121 
122     for(std::vector<float>::const_iterator iter = array.begin();
123         iter != array.end();
124         iter++)
125     {
126         unsigned int i = iter - array.begin();
127 
128         /* Add Filter coefficient const definitions */
129         ss_def << "const float Kernel" << i << " = "
130                << *iter << ";" << std::endl;
131 
132         /* Add convolution term using the current filter coefficient */
133         LibMatrix::vec2 offset(calc_offset(i, width, height));
134         ss_convolution << "texture2D(Texture0, TextureCoord + vec2("
135                        << offset.x() << " * TextureStepX, "
136                        << offset.y() << " * TextureStepY)) * Kernel" << i;
137         if (iter + 1 != array.end())
138             ss_convolution << " +" << std::endl;
139     }
140 
141     ss_convolution << ";" << std::endl;
142 
143     source.add(ss_def.str());
144     source.replace("$CONVOLUTION$", ss_convolution.str());
145 
146     return source.str();
147 }
148 
149 /**
150  * Creates a string containing a printout of a kernel matrix.
151  *
152  * @param filter the vector containing the filter coefficients
153  * @param width the width of the filter
154  *
155  * @return the printout
156  */
157 static std::string
kernel_printout(const std::vector<float> & kernel,unsigned int width)158 kernel_printout(const std::vector<float> &kernel,
159                 unsigned int width)
160 {
161     std::stringstream ss;
162     ss << std::fixed;
163 
164     for (std::vector<float>::const_iterator iter = kernel.begin();
165          iter != kernel.end();
166          iter++)
167     {
168         ss << *iter << " ";
169         if ((iter - kernel.begin()) % width == width - 1)
170             ss << std::endl;
171     }
172 
173     return ss.str();
174 }
175 
176 /**
177  * Parses a string representation of a matrix and returns it
178  * in row-major format.
179  *
180  * In the string representation, elements are delimited using
181  * commas (',') and rows are delimited using semi-colons (';').
182  * eg 0,0,0;0,1.0,0;0,0,0
183  *
184  * @param str the matrix string representation to parse
185  * @param matrix the float vector to populate
186  * @param[out] width the width of the matrix
187  * @param[out] height the height of the matrix
188  *
189  * @return whether parsing succeeded
190  */
191 static bool
parse_matrix(const std::string & str,std::vector<float> & matrix,unsigned int & width,unsigned int & height)192 parse_matrix(const std::string &str, std::vector<float> &matrix,
193              unsigned int &width, unsigned int &height)
194 {
195     std::vector<std::string> rows;
196     unsigned int w = UINT_MAX;
197 
198     Util::split(str, ';', rows, Util::SplitModeNormal);
199 
200     Log::debug("Parsing kernel matrix:\n");
201     static const std::string format("%f ");
202     static const std::string format_cont(Log::continuation_prefix + format);
203     static const std::string newline(Log::continuation_prefix + "\n");
204 
205     for (std::vector<std::string>::const_iterator iter = rows.begin();
206          iter != rows.end();
207          iter++)
208     {
209         std::vector<std::string> elems;
210         Util::split(*iter, ',', elems, Util::SplitModeNormal);
211 
212         if (w != UINT_MAX && elems.size() != w) {
213             Log::error("Matrix row %u contains %u elements, whereas previous"
214                        " rows had %u\n",
215                        iter - rows.begin(), elems.size(), w);
216             return false;
217         }
218 
219         w = elems.size();
220 
221         for (std::vector<std::string>::const_iterator iter_el = elems.begin();
222              iter_el != elems.end();
223              iter_el++)
224         {
225             float f(Util::fromString<float>(*iter_el));
226             matrix.push_back(f);
227             if (iter_el == elems.begin())
228                 Log::debug(format.c_str(), f);
229             else
230                 Log::debug(format_cont.c_str(), f);
231         }
232 
233         Log::debug(newline.c_str());
234     }
235 
236     width = w;
237     height = rows.size();
238 
239     return true;
240 }
241 
242 /**
243  * Normalizes a convolution kernel matrix.
244  *
245  * @param filter the filter to normalize
246  */
247 static void
normalize(std::vector<float> & kernel)248 normalize(std::vector<float> &kernel)
249 {
250     float sum = std::accumulate(kernel.begin(), kernel.end(), 0.0);
251 
252     /*
253      * If sum is essentially zero, perform a zero-sum normalization.
254      * This normalizes positive and negative values separately,
255      */
256     if (fabs(sum) < 0.00000001) {
257         sum = 0.0;
258         for (std::vector<float>::iterator iter = kernel.begin();
259              iter != kernel.end();
260              iter++)
261         {
262             if (*iter > 0.0)
263                 sum += *iter;
264         }
265     }
266 
267     /*
268      * We can simply compare with 0.0f here, because we just care about
269      * avoiding division-by-zero.
270      */
271     if (sum == 0.0)
272         return;
273 
274     for (std::vector<float>::iterator iter = kernel.begin();
275          iter != kernel.end();
276          iter++)
277     {
278         *iter /= sum;
279     }
280 
281 }
282 
283 bool
load()284 SceneEffect2D::load()
285 {
286     Texture::load("effect-2d", &texture_,
287                   GL_NEAREST, GL_NEAREST, 0);
288     running_ = false;
289 
290     return true;
291 }
292 
293 void
unload()294 SceneEffect2D::unload()
295 {
296     glDeleteTextures(1, &texture_);
297 }
298 
299 bool
setup()300 SceneEffect2D::setup()
301 {
302     if (!Scene::setup())
303         return false;
304 
305     Texture::find_textures();
306 
307     static const std::string vtx_shader_filename(Options::data_path + "/shaders/effect-2d.vert");
308 
309     std::vector<float> kernel;
310     unsigned int kernel_width = 0;
311     unsigned int kernel_height = 0;
312 
313     /* Parse the kernel matrix from the options */
314     if (!parse_matrix(options_["kernel"].value, kernel,
315                       kernel_width, kernel_height))
316     {
317         return false;
318     }
319 
320     /* Normalize the kernel matrix if needed */
321     if (options_["normalize"].value == "true") {
322         normalize(kernel);
323         Log::debug("Normalized kernel matrix:\n%s",
324                    kernel_printout(kernel, kernel_width).c_str());
325     }
326 
327     /* Create and load the shaders */
328     ShaderSource vtx_source(vtx_shader_filename);
329     ShaderSource frg_source;
330     frg_source.append(create_convolution_fragment_shader(canvas_, kernel,
331                                                          kernel_width,
332                                                          kernel_height));
333 
334     if (frg_source.str().empty())
335         return false;
336 
337     if (!Scene::load_shaders_from_strings(program_, vtx_source.str(),
338                                           frg_source.str()))
339     {
340         return false;
341     }
342 
343     std::vector<int> vertex_format;
344     vertex_format.push_back(3);
345     mesh_.set_vertex_format(vertex_format);
346 
347     mesh_.make_grid(1, 1, 2.0, 2.0, 0.0);
348     mesh_.build_vbo();
349 
350     std::vector<GLint> attrib_locations;
351     attrib_locations.push_back(program_["position"].location());
352     mesh_.set_attrib_locations(attrib_locations);
353 
354     program_.start();
355 
356     // Load texture sampler value
357     program_["Texture0"] = 0;
358 
359     currentFrame_ = 0;
360     running_ = true;
361     startTime_ = Util::get_timestamp_us() / 1000000.0;
362     lastUpdateTime_ = startTime_;
363 
364     return true;
365 }
366 
367 void
teardown()368 SceneEffect2D::teardown()
369 {
370     mesh_.reset();
371 
372     program_.stop();
373     program_.release();
374 
375     Scene::teardown();
376 }
377 
378 void
update()379 SceneEffect2D::update()
380 {
381     Scene::update();
382 }
383 
384 void
draw()385 SceneEffect2D::draw()
386 {
387     glActiveTexture(GL_TEXTURE0);
388     glBindTexture(GL_TEXTURE_2D, texture_);
389 
390     mesh_.render_vbo();
391 }
392 
393 Scene::ValidationResult
validate()394 SceneEffect2D::validate()
395 {
396     static const double radius_3d(std::sqrt(3.0));
397 
398     std::vector<float> kernel;
399     std::vector<float> kernel_edge;
400     std::vector<float> kernel_blur;
401     unsigned int kernel_width = 0;
402     unsigned int kernel_height = 0;
403 
404     if (!parse_matrix("0,1,0;1,-4,1;0,1,0;", kernel_edge,
405                       kernel_width, kernel_height))
406     {
407         return Scene::ValidationUnknown;
408     }
409 
410     if (!parse_matrix("1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;",
411                       kernel_blur,
412                       kernel_width, kernel_height))
413     {
414         return Scene::ValidationUnknown;
415     }
416 
417     if (!parse_matrix(options_["kernel"].value, kernel,
418                       kernel_width, kernel_height))
419     {
420         return Scene::ValidationUnknown;
421     }
422 
423     Canvas::Pixel ref;
424 
425     if (kernel == kernel_edge)
426         ref = Canvas::Pixel(0x17, 0x0c, 0x2f, 0xff);
427     else if (kernel == kernel_blur)
428         ref = Canvas::Pixel(0xc7, 0xe1, 0x8d, 0xff);
429     else
430         return Scene::ValidationUnknown;
431 
432     Canvas::Pixel pixel = canvas_.read_pixel(452, 237);
433 
434     double dist = pixel.distance_rgb(ref);
435     if (dist < radius_3d + 0.01) {
436         return Scene::ValidationSuccess;
437     }
438     Log::debug("Validation failed! Expected: 0x%x Actual: 0x%x Distance: %f\n",
439                 ref.to_le32(), pixel.to_le32(), dist);
440     return Scene::ValidationFailure;
441 }
442