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