// // Copyright © 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 . // // Authors: // Aleksandar Rodic - Creator and WebGL implementation // Jesse Barker - glmark2 port // #include #include #include #include #include "scene.h" #include "scene-jellyfish.h" #include "log.h" #include "util.h" #include "texture.h" #include "shader-source.h" SceneJellyfish::SceneJellyfish(Canvas& canvas) : Scene(canvas, "jellyfish"), priv_(0) { } SceneJellyfish::~SceneJellyfish() { delete priv_; } bool SceneJellyfish::load() { running_ = false; return true; } void SceneJellyfish::unload() { } bool SceneJellyfish::setup() { if (!Scene::setup()) return false; // Set up our private object that does all of the lifting priv_ = new JellyfishPrivate(); if (!priv_->initialize()) return false; // Set core scene timing after actual initialization so we don't measure // set up time. startTime_ = Util::get_timestamp_us() / 1000000.0; lastUpdateTime_ = startTime_; running_ = true; return true; } void SceneJellyfish::teardown() { priv_->cleanup(); Scene::teardown(); } void SceneJellyfish::update() { Scene::update(); priv_->update_viewport(LibMatrix::vec2(canvas_.width(), canvas_.height())); priv_->update_time(); } void SceneJellyfish::draw() { priv_->draw(); } Scene::ValidationResult SceneJellyfish::validate() { return Scene::ValidationUnknown; } // // JellyfishPrivate implementation // using LibMatrix::mat4; using LibMatrix::vec3; using LibMatrix::vec2; using std::string; using std::vector; bool GradientRenderer::init() { // Program set up static const string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/gradient.vert"); static const string frg_shader_filename(GLMARK_DATA_PATH"/shaders/gradient.frag"); ShaderSource vtx_source(vtx_shader_filename); ShaderSource frg_source(frg_shader_filename); if (!Scene::load_shaders_from_strings(program_, vtx_source.str(), frg_source.str())) { return false; } positionLocation_ = program_["position"].location(); uvLocation_ = program_["uvIn"].location(); // Set up the position data for our "quad". vertices_.push_back(vec2(-1.0, -1.0)); vertices_.push_back(vec2(1.0, -1.0)); vertices_.push_back(vec2(-1.0, 1.0)); vertices_.push_back(vec2(1.0, 1.0)); uvs_.push_back(vec2(1.0, 1.0)); uvs_.push_back(vec2(1.0, 1.0)); uvs_.push_back(vec2(0.0, 0.0)); uvs_.push_back(vec2(0.0, 0.0)); uvOffset_ = vertices_.size() * sizeof(vec2); // Set up the VBO and stash our position data in it. glGenBuffers(1, &bufferObject_); glBindBuffer(GL_ARRAY_BUFFER, bufferObject_); glBufferData(GL_ARRAY_BUFFER, (vertices_.size() + uvs_.size()) * sizeof(vec2), 0, GL_STATIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, vertices_.size() * sizeof(vec2), &vertices_.front()); glBufferSubData(GL_ARRAY_BUFFER, uvOffset_, uvs_.size() * sizeof(vec2), &uvs_.front()); glBindBuffer(GL_ARRAY_BUFFER, 0); return true; } void GradientRenderer::cleanup() { program_.stop(); program_.release(); glBindBuffer(GL_ARRAY_BUFFER, 0); glDeleteBuffers(1, &bufferObject_); } void GradientRenderer::draw() { static const vec3 lightBlue(0.360784314, 0.584313725, 1.0); static const vec3 darkBlue(0.074509804, 0.156862745, 0.619607843); glBindBuffer(GL_ARRAY_BUFFER, bufferObject_); program_.start(); program_["color1"] = lightBlue; program_["color2"] = darkBlue; glEnableVertexAttribArray(positionLocation_); glEnableVertexAttribArray(uvLocation_); glVertexAttribPointer(positionLocation_, 2, GL_FLOAT, GL_FALSE, 0, 0); glVertexAttribPointer(uvLocation_, 2, GL_FLOAT, GL_FALSE, 0, reinterpret_cast(uvOffset_)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(positionLocation_); glDisableVertexAttribArray(uvLocation_); program_.stop(); glBindBuffer(GL_ARRAY_BUFFER, 0); } //! // Parse index values from an OBJ file. // // @param source the source line to parse // @param idx the unsigned short to populate // static void obj_get_index(const string& source, unsigned short& idx) { // Skip the definition type... string::size_type endPos = source.find(" "); string::size_type startPos(0); if (endPos == string::npos) { Log::error("Bad element '%s'\n", source.c_str()); return; } // Find the first value... startPos = endPos + 1; string is(source, startPos); idx = Util::fromString(is); } //! // Parse vec3 values from an OBJ file. // // @param source the source line to parse // @param v the vec3 to populate // static void obj_get_values(const string& source, vec3& v) { // Skip the definition type... string::size_type endPos = source.find(" "); string::size_type startPos(0); if (endPos == string::npos) { Log::error("Bad element '%s'\n", source.c_str()); return; } // Find the first value... startPos = endPos + 1; endPos = source.find(" ", startPos); if (endPos == string::npos) { Log::error("Bad element '%s'\n", source.c_str()); return; } string::size_type numChars(endPos - startPos); string xs(source, startPos, numChars); float x = Util::fromString(xs); // Then the second value... startPos = endPos + 1; endPos = source.find(" ", startPos); if (endPos == string::npos) { Log::error("Bad element '%s'\n", source.c_str()); return; } numChars = endPos - startPos; string ys(source, startPos, numChars); float y = Util::fromString(ys); // And the third value (there might be a fourth, but we don't care)... startPos = endPos + 1; endPos = source.find(" ", startPos); if (endPos == string::npos) { numChars = endPos; } else { numChars = endPos - startPos; } string zs(source, startPos, endPos - startPos); float z = Util::fromString(zs); v.x(x); v.y(y); v.z(z); } // Custom OBJ loader. // // To support the jellyfish model, some amendments to the OBJ format are // necessary. In particular, a vertex color attribute is required, and // it contains an index list rather than a face list. bool JellyfishPrivate::load_obj(const std::string &filename) { Log::debug("Loading model from file '%s'\n", filename.c_str()); const std::auto_ptr input_file_ptr(Util::get_resource(filename)); std::istream& inputFile(*input_file_ptr); if (!inputFile) { Log::error("Failed to open '%s'\n", filename.c_str()); return false; } vector sourceVec; string curLine; while (getline(inputFile, curLine)) { sourceVec.push_back(curLine); } static const string vertex_definition("v"); static const string normal_definition("vn"); static const string texcoord_definition("vt"); static const string color_definition("vc"); static const string index_definition("i"); for (vector::const_iterator lineIt = sourceVec.begin(); lineIt != sourceVec.end(); lineIt++) { const string& curSrc = *lineIt; // Is it a vertex attribute, a face description, comment or other? // We only care about the first two, we ignore comments, object names, // group names, smoothing groups, etc. string::size_type startPos(0); string::size_type spacePos = curSrc.find(" ", startPos); string definitionType(curSrc, startPos, spacePos - startPos); if (definitionType == vertex_definition) { vec3 v; obj_get_values(curSrc, v); positions_.push_back(v); } else if (definitionType == normal_definition) { vec3 v; obj_get_values(curSrc, v); normals_.push_back(v); } else if (definitionType == color_definition) { vec3 v; obj_get_values(curSrc, v); colors_.push_back(v); } else if (definitionType == texcoord_definition) { vec3 v; obj_get_values(curSrc, v); texcoords_.push_back(v); } else if (definitionType == index_definition) { unsigned short idx(0); obj_get_index(curSrc, idx); indices_.push_back(idx); } } Log::debug("Object populated with %u vertices %u normals %u colors %u texcoords and %u indices.\n", positions_.size(), normals_.size(), colors_.size(), texcoords_.size(), indices_.size()); return true; } JellyfishPrivate::JellyfishPrivate() : positionLocation_(0), normalLocation_(0), colorLocation_(0), texcoordLocation_(0), viewport_(512.0, 512.0), lightPosition_(10.0, 40.0, -60.0), lightColor_(0.8, 1.3, 1.1, 1.0), lightRadius_(200.0), ambientColor_(0.3, 0.2, 1.0, 1.0), fresnelColor_(0.8, 0.7, 0.6, 1.1), fresnelPower_(1.0), rotation_(0.0), currentTime_(0.0), lastUpdateTime_(0.0), cullFace_(0), depthTest_(0), blend_(0), blendFuncSrc_(0), blendFuncDst_(0) { } JellyfishPrivate::~JellyfishPrivate() { positions_.clear(); normals_.clear(); colors_.clear(); texcoords_.clear(); indices_.clear(); } bool JellyfishPrivate::initialize() { static const string modelFilename(GLMARK_DATA_PATH"/models/jellyfish.jobj"); if (!load_obj(modelFilename)) { return false; } // Now that we've setup the vertex data, we can setup the map of how // that data will be laid out in the buffer object. static const unsigned int sv3(sizeof(vec3)); dataMap_.positionOffset = 0; dataMap_.positionSize = positions_.size() * sv3; dataMap_.totalSize = dataMap_.positionSize; dataMap_.normalOffset = dataMap_.positionOffset + dataMap_.positionSize; dataMap_.normalSize = normals_.size() * sv3; dataMap_.totalSize += dataMap_.normalSize; dataMap_.colorOffset = dataMap_.normalOffset + dataMap_.normalSize; dataMap_.colorSize = colors_.size() * sv3; dataMap_.totalSize += dataMap_.colorSize; dataMap_.texcoordOffset = dataMap_.colorOffset + dataMap_.colorSize; dataMap_.texcoordSize = texcoords_.size() * sv3; dataMap_.totalSize += dataMap_.texcoordSize; lastUpdateTime_ = Util::get_timestamp_us() / 1000.0; currentTime_ = static_cast(lastUpdateTime_) % 100000000 / 1000.0; whichCaustic_ = static_cast(currentTime_ * 30) % 32 + 1; rotation_ = 0.0; if (!gradient_.init()) { return false; } // Set up program first so we can store attribute and uniform locations // away for the using std::string; static const string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/jellyfish.vert"); static const string frg_shader_filename(GLMARK_DATA_PATH"/shaders/jellyfish.frag"); ShaderSource vtx_source(vtx_shader_filename); ShaderSource frg_source(frg_shader_filename); if (!Scene::load_shaders_from_strings(program_, vtx_source.str(), frg_source.str())) { return false; } // Stash away attribute and uniform locations for handy use. positionLocation_ = program_["aVertexPosition"].location(); normalLocation_ = program_["aVertexNormal"].location(); colorLocation_ = program_["aVertexColor"].location(); texcoordLocation_ = program_["aTextureCoord"].location(); // We need 2 buffers for our work here. One for the vertex data. // and one for the index data. glGenBuffers(2, &bufferObjects_[0]); // First, setup the vertex data by binding the first buffer object, // allocating its data store, and filling it in with our vertex data. glBindBuffer(GL_ARRAY_BUFFER, bufferObjects_[0]); glBufferData(GL_ARRAY_BUFFER, dataMap_.totalSize, 0, GL_STATIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, dataMap_.positionOffset, dataMap_.positionSize, &positions_.front()); glBufferSubData(GL_ARRAY_BUFFER, dataMap_.normalOffset, dataMap_.normalSize, &normals_.front()); glBufferSubData(GL_ARRAY_BUFFER, dataMap_.colorOffset, dataMap_.colorSize, &colors_.front()); glBufferSubData(GL_ARRAY_BUFFER, dataMap_.texcoordOffset, dataMap_.texcoordSize, &texcoords_.front()); // Now repeat for our index data. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferObjects_[1]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_.size() * sizeof(unsigned short), &indices_.front(), GL_STATIC_DRAW); // "Unbind" our buffer objects to make sure the state is consistent. glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // Finally, set up our textures. // // First, the main jellyfish texture bool gotTex = Texture::load("jellyfish256", &textureObjects_[0], GL_LINEAR, GL_LINEAR, 0); if (!gotTex || textureObjects_[0] == 0) { Log::error("Jellyfish texture set up failed!!!\n"); return false; } glBindTexture(GL_TEXTURE_2D, 0); // Then, the caustics textures static const string baseName("jellyfish-caustics-"); for (unsigned int i = 1; i < 33; i++) { std::stringstream ss; ss << std::setw(2) << std::setfill('0') << i; string curName(baseName); curName += ss.str(); gotTex = Texture::load(curName, &textureObjects_[i], GL_LINEAR, GL_LINEAR, 0); if (!gotTex || textureObjects_[i] == 0) { Log::error("Caustics texture[%u] set up failed!!!\n", i); return false; } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glBindTexture(GL_TEXTURE_2D, 0); } // Save the GL state we are changing so we can restore it later. cullFace_ = glIsEnabled(GL_CULL_FACE); depthTest_ = glIsEnabled(GL_DEPTH_TEST); blend_ = glIsEnabled(GL_BLEND); glGetIntegerv(GL_BLEND_SRC_RGB, &blendFuncSrc_); glGetIntegerv(GL_BLEND_DST_RGB, &blendFuncDst_); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); return true; } void JellyfishPrivate::update_viewport(const vec2& vp) { if (viewport_.x() == vp.x() && viewport_.y() == vp.y()) { return; } viewport_ = vp; projection_.loadIdentity(); projection_.perspective(30.0, viewport_.x()/viewport_.y(), 20.0, 120.0); } void JellyfishPrivate::update_time() { double now = Util::get_timestamp_us() / 1000.0; double elapsedTime = now - lastUpdateTime_; rotation_ += (2.0 * elapsedTime) / 1000.0; currentTime_ = static_cast(now) % 100000000 / 1000.0; whichCaustic_ = static_cast(currentTime_ * 30) % 32 + 1; lastUpdateTime_ = now; } void JellyfishPrivate::cleanup() { // Restore the GL state we changed for the scene. glBlendFunc(blendFuncSrc_, blendFuncDst_); if (GL_FALSE == blend_) { glDisable(GL_BLEND); } if (GL_TRUE == cullFace_) { glEnable(GL_CULL_FACE); } if (GL_TRUE == depthTest_) { glEnable(GL_DEPTH_TEST); } program_.stop(); program_.release(); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteTextures(33, &textureObjects_[0]); glDeleteBuffers(2, &bufferObjects_[0]); gradient_.cleanup(); } void JellyfishPrivate::draw() { // "Clear" the background to the desired gradient. gradient_.draw(); // We need "world", "world view projection", and "world inverse transpose" // matrix uniforms for the current shader. // // NOTE: Some of this seems a bit of a no-op (e.g., multipying by and // inverting identity matrices), but leave it like the original // WebGL files for the time being. Worth revisiting not doing all // of that math every draw call (might even be good to be doing // some of it in the shader as well). world_.push(); world_.translate(0.0, 5.0, -75.0); world_.rotate(sin(rotation_ / 10.0) * 30.0, 0.0, 1.0, 0.0); world_.rotate(sin(rotation_ / 20.0) * 30.0, 1.0, 0.0, 0.0); world_.scale(5.0, 5.0, 5.0); world_.translate(0.0, sin(rotation_ / 10.0) * 2.5, 0.0); mat4 worldViewProjection(projection_.getCurrent()); worldViewProjection *= world_.getCurrent();; mat4 worldInverseTranspose(world_.getCurrent()); worldInverseTranspose.inverse().transpose(); // Load up the uniforms program_.start(); program_["uWorld"] = world_.getCurrent(); program_["uWorldViewProj"] = worldViewProjection; program_["uWorldInvTranspose"] = worldInverseTranspose; program_["uCurrentTime"] = currentTime_; // Revisit making these constants rather than uniforms as they appear never // to change program_["uLightPos"] = lightPosition_; program_["uLightRadius"] = lightRadius_; program_["uLightCol"] = lightColor_; program_["uAmbientCol"] = ambientColor_; program_["uFresnelCol"] = fresnelColor_; program_["uFresnelPower"] = fresnelPower_; // Set up textures for this frame. glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textureObjects_[0]); program_["uSampler"] = 0; glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, textureObjects_[whichCaustic_]); program_["uSampler1"] = 1; glBindBuffer(GL_ARRAY_BUFFER, bufferObjects_[0]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferObjects_[1]); glEnableVertexAttribArray(positionLocation_); glEnableVertexAttribArray(normalLocation_); glEnableVertexAttribArray(colorLocation_); glEnableVertexAttribArray(texcoordLocation_); glVertexAttribPointer(positionLocation_ , 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast(dataMap_.positionOffset)); glVertexAttribPointer(normalLocation_ , 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast(dataMap_.normalOffset)); glVertexAttribPointer(colorLocation_ , 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast(dataMap_.colorOffset)); glVertexAttribPointer(texcoordLocation_ , 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast(dataMap_.texcoordOffset)); glDrawElements(GL_TRIANGLES, indices_.size(), GL_UNSIGNED_SHORT, 0); glDisableVertexAttribArray(positionLocation_); glDisableVertexAttribArray(normalLocation_); glDisableVertexAttribArray(colorLocation_); glDisableVertexAttribArray(texcoordLocation_); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); program_.stop(); world_.pop(); }