blob: 7b4b3693f24ae3d426406ca606c4acba5a44268e [file] [log] [blame]
//
// 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 <http://www.gnu.org/licenses/>.
//
// Authors:
// Aleksandar Rodic - Creator and WebGL implementation
// Jesse Barker - glmark2 port
//
#include <string>
#include <fstream>
#include <memory>
#include <iomanip>
#include "options.h"
#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(Options::data_path + "/shaders/gradient.vert");
static const string frg_shader_filename(Options::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<GLvoid*>(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<unsigned short>(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<float>(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<float>(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<float>(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::unique_ptr<std::istream> 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<string> 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<string>::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(Options::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<uint64_t>(lastUpdateTime_) % 100000000 / 1000.0;
whichCaustic_ = static_cast<uint64_t>(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(Options::data_path + "/shaders/jellyfish.vert");
static const string frg_shader_filename(Options::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<uint64_t>(now) % 100000000 / 1000.0;
whichCaustic_ = static_cast<uint64_t>(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<const GLvoid*>(dataMap_.positionOffset));
glVertexAttribPointer(normalLocation_ , 3, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<const GLvoid*>(dataMap_.normalOffset));
glVertexAttribPointer(colorLocation_ , 3, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<const GLvoid*>(dataMap_.colorOffset));
glVertexAttribPointer(texcoordLocation_ , 3, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<const GLvoid*>(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();
}