1 //
2 // Copyright © 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 // Aleksandar Rodic - Creator and WebGL implementation
21 // Jesse Barker - glmark2 port
22 //
23 #include <string>
24 #include <fstream>
25 #include <memory>
26 #include <iomanip>
27 #include "options.h"
28 #include "scene.h"
29 #include "scene-jellyfish.h"
30 #include "log.h"
31 #include "util.h"
32 #include "texture.h"
33 #include "shader-source.h"
34
SceneJellyfish(Canvas & canvas)35 SceneJellyfish::SceneJellyfish(Canvas& canvas) :
36 Scene(canvas, "jellyfish"), priv_(0)
37 {
38
39 }
40
~SceneJellyfish()41 SceneJellyfish::~SceneJellyfish()
42 {
43 delete priv_;
44 }
45
46 bool
load()47 SceneJellyfish::load()
48 {
49 running_ = false;
50 return true;
51 }
52
53 void
unload()54 SceneJellyfish::unload()
55 {
56 }
57
58 bool
setup()59 SceneJellyfish::setup()
60 {
61 if (!Scene::setup())
62 return false;
63
64 // Set up our private object that does all of the lifting
65 priv_ = new JellyfishPrivate();
66 if (!priv_->initialize())
67 return false;
68
69 // Set core scene timing after actual initialization so we don't measure
70 // set up time.
71 startTime_ = Util::get_timestamp_us() / 1000000.0;
72 lastUpdateTime_ = startTime_;
73 running_ = true;
74
75 return true;
76 }
77
78 void
teardown()79 SceneJellyfish::teardown()
80 {
81 priv_->cleanup();
82 Scene::teardown();
83 }
84
85 void
update()86 SceneJellyfish::update()
87 {
88 Scene::update();
89 priv_->update_viewport(LibMatrix::vec2(canvas_.width(), canvas_.height()));
90 priv_->update_time();
91 }
92
93 void
draw()94 SceneJellyfish::draw()
95 {
96 priv_->draw();
97 }
98
99 Scene::ValidationResult
validate()100 SceneJellyfish::validate()
101 {
102 return Scene::ValidationUnknown;
103 }
104
105
106 //
107 // JellyfishPrivate implementation
108 //
109 using LibMatrix::mat4;
110 using LibMatrix::vec3;
111 using LibMatrix::vec2;
112 using std::string;
113 using std::vector;
114
115 bool
init()116 GradientRenderer::init()
117 {
118 // Program set up
119 static const string vtx_shader_filename(Options::data_path + "/shaders/gradient.vert");
120 static const string frg_shader_filename(Options::data_path + "/shaders/gradient.frag");
121 ShaderSource vtx_source(vtx_shader_filename);
122 ShaderSource frg_source(frg_shader_filename);
123 if (!Scene::load_shaders_from_strings(program_, vtx_source.str(),
124 frg_source.str()))
125 {
126 return false;
127 }
128 positionLocation_ = program_["position"].location();
129 uvLocation_ = program_["uvIn"].location();
130
131 // Set up the position data for our "quad".
132 vertices_.push_back(vec2(-1.0, -1.0));
133 vertices_.push_back(vec2(1.0, -1.0));
134 vertices_.push_back(vec2(-1.0, 1.0));
135 vertices_.push_back(vec2(1.0, 1.0));
136 uvs_.push_back(vec2(1.0, 1.0));
137 uvs_.push_back(vec2(1.0, 1.0));
138 uvs_.push_back(vec2(0.0, 0.0));
139 uvs_.push_back(vec2(0.0, 0.0));
140 uvOffset_ = vertices_.size() * sizeof(vec2);
141
142 // Set up the VBO and stash our position data in it.
143 glGenBuffers(1, &bufferObject_);
144 glBindBuffer(GL_ARRAY_BUFFER, bufferObject_);
145 glBufferData(GL_ARRAY_BUFFER, (vertices_.size() + uvs_.size()) * sizeof(vec2),
146 0, GL_STATIC_DRAW);
147 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices_.size() * sizeof(vec2),
148 &vertices_.front());
149 glBufferSubData(GL_ARRAY_BUFFER, uvOffset_, uvs_.size() * sizeof(vec2),
150 &uvs_.front());
151 glBindBuffer(GL_ARRAY_BUFFER, 0);
152
153 return true;
154 }
155
156 void
cleanup()157 GradientRenderer::cleanup()
158 {
159 program_.stop();
160 program_.release();
161 glBindBuffer(GL_ARRAY_BUFFER, 0);
162 glDeleteBuffers(1, &bufferObject_);
163 }
164
165 void
draw()166 GradientRenderer::draw()
167 {
168 static const vec3 lightBlue(0.360784314, 0.584313725, 1.0);
169 static const vec3 darkBlue(0.074509804, 0.156862745, 0.619607843);
170 glBindBuffer(GL_ARRAY_BUFFER, bufferObject_);
171 program_.start();
172 program_["color1"] = lightBlue;
173 program_["color2"] = darkBlue;
174 glEnableVertexAttribArray(positionLocation_);
175 glEnableVertexAttribArray(uvLocation_);
176 glVertexAttribPointer(positionLocation_, 2, GL_FLOAT, GL_FALSE, 0, 0);
177 glVertexAttribPointer(uvLocation_, 2, GL_FLOAT, GL_FALSE, 0,
178 reinterpret_cast<GLvoid*>(uvOffset_));
179 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
180 glDisableVertexAttribArray(positionLocation_);
181 glDisableVertexAttribArray(uvLocation_);
182 program_.stop();
183 glBindBuffer(GL_ARRAY_BUFFER, 0);
184 }
185
186 //!
187 // Parse index values from an OBJ file.
188 //
189 // @param source the source line to parse
190 // @param idx the unsigned short to populate
191 //
192 static void
obj_get_index(const string & source,unsigned short & idx)193 obj_get_index(const string& source, unsigned short& idx)
194 {
195 // Skip the definition type...
196 string::size_type endPos = source.find(" ");
197 string::size_type startPos(0);
198 if (endPos == string::npos)
199 {
200 Log::error("Bad element '%s'\n", source.c_str());
201 return;
202 }
203 // Find the first value...
204 startPos = endPos + 1;
205 string is(source, startPos);
206 idx = Util::fromString<unsigned short>(is);
207 }
208
209 //!
210 // Parse vec3 values from an OBJ file.
211 //
212 // @param source the source line to parse
213 // @param v the vec3 to populate
214 //
215 static void
obj_get_values(const string & source,vec3 & v)216 obj_get_values(const string& source, vec3& v)
217 {
218 // Skip the definition type...
219 string::size_type endPos = source.find(" ");
220 string::size_type startPos(0);
221 if (endPos == string::npos)
222 {
223 Log::error("Bad element '%s'\n", source.c_str());
224 return;
225 }
226 // Find the first value...
227 startPos = endPos + 1;
228 endPos = source.find(" ", startPos);
229 if (endPos == string::npos)
230 {
231 Log::error("Bad element '%s'\n", source.c_str());
232 return;
233 }
234 string::size_type numChars(endPos - startPos);
235 string xs(source, startPos, numChars);
236 float x = Util::fromString<float>(xs);
237 // Then the second value...
238 startPos = endPos + 1;
239 endPos = source.find(" ", startPos);
240 if (endPos == string::npos)
241 {
242 Log::error("Bad element '%s'\n", source.c_str());
243 return;
244 }
245 numChars = endPos - startPos;
246 string ys(source, startPos, numChars);
247 float y = Util::fromString<float>(ys);
248 // And the third value (there might be a fourth, but we don't care)...
249 startPos = endPos + 1;
250 endPos = source.find(" ", startPos);
251 if (endPos == string::npos)
252 {
253 numChars = endPos;
254 }
255 else
256 {
257 numChars = endPos - startPos;
258 }
259 string zs(source, startPos, endPos - startPos);
260 float z = Util::fromString<float>(zs);
261 v.x(x);
262 v.y(y);
263 v.z(z);
264 }
265
266 // Custom OBJ loader.
267 //
268 // To support the jellyfish model, some amendments to the OBJ format are
269 // necessary. In particular, a vertex color attribute is required, and
270 // it contains an index list rather than a face list.
271 bool
load_obj(const std::string & filename)272 JellyfishPrivate::load_obj(const std::string &filename)
273 {
274 Log::debug("Loading model from file '%s'\n", filename.c_str());
275
276 const std::unique_ptr<std::istream> input_file_ptr(Util::get_resource(filename));
277 std::istream& inputFile(*input_file_ptr);
278 if (!inputFile)
279 {
280 Log::error("Failed to open '%s'\n", filename.c_str());
281 return false;
282 }
283
284 vector<string> sourceVec;
285 string curLine;
286 while (getline(inputFile, curLine))
287 {
288 sourceVec.push_back(curLine);
289 }
290
291 static const string vertex_definition("v");
292 static const string normal_definition("vn");
293 static const string texcoord_definition("vt");
294 static const string color_definition("vc");
295 static const string index_definition("i");
296 for (vector<string>::const_iterator lineIt = sourceVec.begin();
297 lineIt != sourceVec.end();
298 lineIt++)
299 {
300 const string& curSrc = *lineIt;
301 // Is it a vertex attribute, a face description, comment or other?
302 // We only care about the first two, we ignore comments, object names,
303 // group names, smoothing groups, etc.
304 string::size_type startPos(0);
305 string::size_type spacePos = curSrc.find(" ", startPos);
306 string definitionType(curSrc, startPos, spacePos - startPos);
307 if (definitionType == vertex_definition)
308 {
309 vec3 v;
310 obj_get_values(curSrc, v);
311 positions_.push_back(v);
312 }
313 else if (definitionType == normal_definition)
314 {
315 vec3 v;
316 obj_get_values(curSrc, v);
317 normals_.push_back(v);
318 }
319 else if (definitionType == color_definition)
320 {
321 vec3 v;
322 obj_get_values(curSrc, v);
323 colors_.push_back(v);
324 }
325 else if (definitionType == texcoord_definition)
326 {
327 vec3 v;
328 obj_get_values(curSrc, v);
329 texcoords_.push_back(v);
330 }
331 else if (definitionType == index_definition)
332 {
333 unsigned short idx(0);
334 obj_get_index(curSrc, idx);
335 indices_.push_back(idx);
336 }
337 }
338
339 Log::debug("Object populated with %u vertices %u normals %u colors %u texcoords and %u indices.\n",
340 positions_.size(), normals_.size(), colors_.size(), texcoords_.size(), indices_.size());
341 return true;
342 }
343
JellyfishPrivate()344 JellyfishPrivate::JellyfishPrivate() :
345 positionLocation_(0),
346 normalLocation_(0),
347 colorLocation_(0),
348 texcoordLocation_(0),
349 viewport_(512.0, 512.0),
350 lightPosition_(10.0, 40.0, -60.0),
351 lightColor_(0.8, 1.3, 1.1, 1.0),
352 lightRadius_(200.0),
353 ambientColor_(0.3, 0.2, 1.0, 1.0),
354 fresnelColor_(0.8, 0.7, 0.6, 1.1),
355 fresnelPower_(1.0),
356 rotation_(0.0),
357 currentTime_(0.0),
358 lastUpdateTime_(0.0),
359 cullFace_(0),
360 depthTest_(0),
361 blend_(0),
362 blendFuncSrc_(0),
363 blendFuncDst_(0)
364 {
365 }
366
~JellyfishPrivate()367 JellyfishPrivate::~JellyfishPrivate()
368 {
369 positions_.clear();
370 normals_.clear();
371 colors_.clear();
372 texcoords_.clear();
373 indices_.clear();
374 }
375
376 bool
initialize()377 JellyfishPrivate::initialize()
378 {
379 static const string modelFilename(Options::data_path + "/models/jellyfish.jobj");
380 if (!load_obj(modelFilename))
381 {
382 return false;
383 }
384
385 // Now that we've setup the vertex data, we can setup the map of how
386 // that data will be laid out in the buffer object.
387 static const unsigned int sv3(sizeof(vec3));
388 dataMap_.positionOffset = 0;
389 dataMap_.positionSize = positions_.size() * sv3;
390 dataMap_.totalSize = dataMap_.positionSize;
391 dataMap_.normalOffset = dataMap_.positionOffset + dataMap_.positionSize;
392 dataMap_.normalSize = normals_.size() * sv3;
393 dataMap_.totalSize += dataMap_.normalSize;
394 dataMap_.colorOffset = dataMap_.normalOffset + dataMap_.normalSize;
395 dataMap_.colorSize = colors_.size() * sv3;
396 dataMap_.totalSize += dataMap_.colorSize;
397 dataMap_.texcoordOffset = dataMap_.colorOffset + dataMap_.colorSize;
398 dataMap_.texcoordSize = texcoords_.size() * sv3;
399 dataMap_.totalSize += dataMap_.texcoordSize;
400
401 lastUpdateTime_ = Util::get_timestamp_us() / 1000.0;
402 currentTime_ = static_cast<uint64_t>(lastUpdateTime_) % 100000000 / 1000.0;
403 whichCaustic_ = static_cast<uint64_t>(currentTime_ * 30) % 32 + 1;
404 rotation_ = 0.0;
405
406 if (!gradient_.init())
407 {
408 return false;
409 }
410
411 // Set up program first so we can store attribute and uniform locations
412 // away for the
413 using std::string;
414 static const string vtx_shader_filename(Options::data_path + "/shaders/jellyfish.vert");
415 static const string frg_shader_filename(Options::data_path + "/shaders/jellyfish.frag");
416
417 ShaderSource vtx_source(vtx_shader_filename);
418 ShaderSource frg_source(frg_shader_filename);
419
420 // Use high float precision, if available, in the jellyfish fragment shader
421 frg_source.precision(std::string(",high,,"));
422
423 if (!Scene::load_shaders_from_strings(program_, vtx_source.str(),
424 frg_source.str()))
425 {
426 return false;
427 }
428
429 // Stash away attribute and uniform locations for handy use.
430 positionLocation_ = program_["aVertexPosition"].location();
431 normalLocation_ = program_["aVertexNormal"].location();
432 colorLocation_ = program_["aVertexColor"].location();
433 texcoordLocation_ = program_["aTextureCoord"].location();
434
435 // We need 2 buffers for our work here. One for the vertex data.
436 // and one for the index data.
437 glGenBuffers(2, &bufferObjects_[0]);
438
439 // First, setup the vertex data by binding the first buffer object,
440 // allocating its data store, and filling it in with our vertex data.
441 glBindBuffer(GL_ARRAY_BUFFER, bufferObjects_[0]);
442 glBufferData(GL_ARRAY_BUFFER, dataMap_.totalSize, 0, GL_STATIC_DRAW);
443 glBufferSubData(GL_ARRAY_BUFFER, dataMap_.positionOffset,
444 dataMap_.positionSize, &positions_.front());
445 glBufferSubData(GL_ARRAY_BUFFER, dataMap_.normalOffset,
446 dataMap_.normalSize, &normals_.front());
447 glBufferSubData(GL_ARRAY_BUFFER, dataMap_.colorOffset,
448 dataMap_.colorSize, &colors_.front());
449 glBufferSubData(GL_ARRAY_BUFFER, dataMap_.texcoordOffset,
450 dataMap_.texcoordSize, &texcoords_.front());
451
452 // Now repeat for our index data.
453 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferObjects_[1]);
454 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_.size() * sizeof(unsigned short),
455 &indices_.front(), GL_STATIC_DRAW);
456
457 // "Unbind" our buffer objects to make sure the state is consistent.
458 glBindBuffer(GL_ARRAY_BUFFER, 0);
459 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
460
461 // Finally, set up our textures.
462 //
463 // First, the main jellyfish texture
464 bool gotTex = Texture::load("jellyfish256", &textureObjects_[0], GL_LINEAR,
465 GL_LINEAR, 0);
466 if (!gotTex || textureObjects_[0] == 0)
467 {
468 Log::error("Jellyfish texture set up failed!!!\n");
469 return false;
470 }
471 glBindTexture(GL_TEXTURE_2D, 0);
472 // Then, the caustics textures
473 static const string baseName("jellyfish-caustics-");
474 for (unsigned int i = 1; i < 33; i++)
475 {
476 std::stringstream ss;
477 ss << std::setw(2) << std::setfill('0') << i;
478 string curName(baseName);
479 curName += ss.str();
480 gotTex = Texture::load(curName, &textureObjects_[i], GL_LINEAR,
481 GL_LINEAR, 0);
482 if (!gotTex || textureObjects_[i] == 0)
483 {
484 Log::error("Caustics texture[%u] set up failed!!!\n", i);
485 return false;
486 }
487 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
488 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
489 glBindTexture(GL_TEXTURE_2D, 0);
490 }
491
492 // Save the GL state we are changing so we can restore it later.
493 cullFace_ = glIsEnabled(GL_CULL_FACE);
494 depthTest_ = glIsEnabled(GL_DEPTH_TEST);
495 blend_ = glIsEnabled(GL_BLEND);
496 glGetIntegerv(GL_BLEND_SRC_RGB, &blendFuncSrc_);
497 glGetIntegerv(GL_BLEND_DST_RGB, &blendFuncDst_);
498
499 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
500 glEnable(GL_BLEND);
501 glDisable(GL_CULL_FACE);
502 glDisable(GL_DEPTH_TEST);
503
504 return true;
505 }
506
507 void
update_viewport(const vec2 & vp)508 JellyfishPrivate::update_viewport(const vec2& vp)
509 {
510 if (viewport_.x() == vp.x() && viewport_.y() == vp.y())
511 {
512 return;
513 }
514 viewport_ = vp;
515 projection_.loadIdentity();
516 projection_.perspective(30.0, viewport_.x()/viewport_.y(), 20.0, 120.0);
517 }
518
519 void
update_time()520 JellyfishPrivate::update_time()
521 {
522 double now = Util::get_timestamp_us() / 1000.0;
523 double elapsedTime = now - lastUpdateTime_;
524 rotation_ += (2.0 * elapsedTime) / 1000.0;
525 currentTime_ = static_cast<uint64_t>(now) % 100000000 / 1000.0;
526 whichCaustic_ = static_cast<uint64_t>(currentTime_ * 30) % 32 + 1;
527 lastUpdateTime_ = now;
528 }
529
530 void
cleanup()531 JellyfishPrivate::cleanup()
532 {
533 // Restore the GL state we changed for the scene.
534 glBlendFunc(blendFuncSrc_, blendFuncDst_);
535 if (GL_FALSE == blend_)
536 {
537 glDisable(GL_BLEND);
538 }
539 if (GL_TRUE == cullFace_)
540 {
541 glEnable(GL_CULL_FACE);
542 }
543 if (GL_TRUE == depthTest_)
544 {
545 glEnable(GL_DEPTH_TEST);
546 }
547
548 program_.stop();
549 program_.release();
550
551 glBindBuffer(GL_ARRAY_BUFFER, 0);
552 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
553
554 glDeleteTextures(33, &textureObjects_[0]);
555 glDeleteBuffers(2, &bufferObjects_[0]);
556
557 gradient_.cleanup();
558 }
559
560 void
draw()561 JellyfishPrivate::draw()
562 {
563 // "Clear" the background to the desired gradient.
564 gradient_.draw();
565
566 // We need "world", "world view projection", and "world inverse transpose"
567 // matrix uniforms for the current shader.
568 //
569 // NOTE: Some of this seems a bit of a no-op (e.g., multipying by and
570 // inverting identity matrices), but leave it like the original
571 // WebGL files for the time being. Worth revisiting not doing all
572 // of that math every draw call (might even be good to be doing
573 // some of it in the shader as well).
574 world_.push();
575 world_.translate(0.0, 5.0, -75.0);
576 world_.rotate(sin(rotation_ / 10.0) * 30.0, 0.0, 1.0, 0.0);
577 world_.rotate(sin(rotation_ / 20.0) * 30.0, 1.0, 0.0, 0.0);
578 world_.scale(5.0, 5.0, 5.0);
579 world_.translate(0.0, sin(rotation_ / 10.0) * 2.5, 0.0);
580 mat4 worldViewProjection(projection_.getCurrent());
581 worldViewProjection *= world_.getCurrent();;
582 mat4 worldInverseTranspose(world_.getCurrent());
583 worldInverseTranspose.inverse().transpose();
584
585 // Load up the uniforms
586 program_.start();
587 program_["uWorld"] = world_.getCurrent();
588 program_["uWorldViewProj"] = worldViewProjection;
589 program_["uWorldInvTranspose"] = worldInverseTranspose;
590 program_["uCurrentTime"] = currentTime_;
591 // Revisit making these constants rather than uniforms as they appear never
592 // to change
593 program_["uLightPos"] = lightPosition_;
594 program_["uLightRadius"] = lightRadius_;
595 program_["uLightCol"] = lightColor_;
596 program_["uAmbientCol"] = ambientColor_;
597 program_["uFresnelCol"] = fresnelColor_;
598 program_["uFresnelPower"] = fresnelPower_;
599 // Set up textures for this frame.
600 glActiveTexture(GL_TEXTURE0);
601 glBindTexture(GL_TEXTURE_2D, textureObjects_[0]);
602 program_["uSampler"] = 0;
603 glActiveTexture(GL_TEXTURE1);
604 glBindTexture(GL_TEXTURE_2D, textureObjects_[whichCaustic_]);
605 program_["uSampler1"] = 1;
606
607 glBindBuffer(GL_ARRAY_BUFFER, bufferObjects_[0]);
608 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferObjects_[1]);
609
610 glEnableVertexAttribArray(positionLocation_);
611 glEnableVertexAttribArray(normalLocation_);
612 glEnableVertexAttribArray(colorLocation_);
613 glEnableVertexAttribArray(texcoordLocation_);
614 glVertexAttribPointer(positionLocation_ , 3, GL_FLOAT, GL_FALSE, 0,
615 reinterpret_cast<const GLvoid*>(dataMap_.positionOffset));
616 glVertexAttribPointer(normalLocation_ , 3, GL_FLOAT, GL_FALSE, 0,
617 reinterpret_cast<const GLvoid*>(dataMap_.normalOffset));
618 glVertexAttribPointer(colorLocation_ , 3, GL_FLOAT, GL_FALSE, 0,
619 reinterpret_cast<const GLvoid*>(dataMap_.colorOffset));
620 glVertexAttribPointer(texcoordLocation_ , 3, GL_FLOAT, GL_FALSE, 0,
621 reinterpret_cast<const GLvoid*>(dataMap_.texcoordOffset));
622
623 glDrawElements(GL_TRIANGLES, indices_.size(), GL_UNSIGNED_SHORT, 0);
624
625 glDisableVertexAttribArray(positionLocation_);
626 glDisableVertexAttribArray(normalLocation_);
627 glDisableVertexAttribArray(colorLocation_);
628 glDisableVertexAttribArray(texcoordLocation_);
629
630 glBindBuffer(GL_ARRAY_BUFFER, 0);
631 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
632
633 program_.stop();
634 world_.pop();
635 }
636