/*
 * Copyright 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

 //--------------------------------------------------------------------------------
 // MoreTeapotsRenderer.cpp
 // Render teapots
 //--------------------------------------------------------------------------------
 //--------------------------------------------------------------------------------
 // Include files
 //--------------------------------------------------------------------------------
#include "MoreTeapotsRenderer.h"

#include <string.h>

//--------------------------------------------------------------------------------
// Teapot model data
//--------------------------------------------------------------------------------
#include "teapot.inl"

//--------------------------------------------------------------------------------
// Ctor
//--------------------------------------------------------------------------------
MoreTeapotsRenderer::MoreTeapotsRenderer()
	: geometry_instancing_support_(false) {}

//--------------------------------------------------------------------------------
// Dtor
//--------------------------------------------------------------------------------
MoreTeapotsRenderer::~MoreTeapotsRenderer()
{
	LOGV("@@MoreTeapotsRenderer::~MoreTeapotsRenderer, BEGIN...");
	Unload();
	LOGV("@@MoreTeapotsRenderer::~MoreTeapotsRenderer, END...");
}

//--------------------------------------------------------------------------------
// Init
//--------------------------------------------------------------------------------
void MoreTeapotsRenderer::Init(const int32_t numX, const int32_t numY, const int32_t numZ)
{
	LOGV("@@MoreTeapotsRenderer::Init, BEGIN...");
	if (ndk_helper::GLContext::GetInstance()->GetGLVersion() >= 3.0) {
		geometry_instancing_support_ = true;
	}
	else if (ndk_helper::GLContext::GetInstance()->CheckExtension(
		"GL_NV_draw_instanced") &&
		ndk_helper::GLContext::GetInstance()->CheckExtension(
			"GL_NV_uniform_buffer_object")) {
		LOGI("Supported via extension!");
		//_bGeometryInstancingSupport = true;
		//_bARBSupport = true; //Need to patch shaders
		// Currently this has been disabled
	}

	// Settings
	glFrontFace(GL_CCW);

	// Create Index buffer
	num_indices_ = sizeof(teapotIndices) / sizeof(teapotIndices[0]);
	glGenBuffers(1, &ibo_);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(teapotIndices), teapotIndices,
		GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	// Create VBO
	num_vertices_ = sizeof(teapotPositions) / sizeof(teapotPositions[0]) / 3;
	int32_t stride = sizeof(TEAPOT_VERTEX);
	int32_t index = 0;
	TEAPOT_VERTEX* p = new TEAPOT_VERTEX[num_vertices_];
	for (int32_t i = 0; i < num_vertices_; ++i) {
		p[i].pos[0] = teapotPositions[index];
		p[i].pos[1] = teapotPositions[index + 1];
		p[i].pos[2] = teapotPositions[index + 2];

		p[i].normal[0] = teapotNormals[index];
		p[i].normal[1] = teapotNormals[index + 1];
		p[i].normal[2] = teapotNormals[index + 2];
		index += 3;
	}
	glGenBuffers(1, &vbo_);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_);
	glBufferData(GL_ARRAY_BUFFER, stride * num_vertices_, p, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	delete[] p;

	// Init Projection matrices
	teapot_x_ = numX;
	teapot_y_ = numY;
	teapot_z_ = numZ;
	vec_mat_models_.reserve(teapot_x_ * teapot_y_ * teapot_z_);

	UpdateViewport();//FIXME!

	const float total_width = 100.f;//500.f;
	float gap_x = total_width / (teapot_x_ - 1);
	float gap_y = total_width / (teapot_y_ - 1);
	float gap_z = total_width / (teapot_z_ - 1);
	float offset_x = -total_width / 2.f;
	float offset_y = -total_width / 2.f;
	float offset_z = -total_width / 2.f;

	for (int32_t x = 0; x < teapot_x_; ++x)
		for (int32_t y = 0; y < teapot_y_; ++y)
			for (int32_t z = 0; z < teapot_z_; ++z) {
				vec_mat_models_.push_back(ndk_helper::Mat4::Translation(
					x * gap_x + offset_x, y * gap_y + offset_y,
					z * gap_z + offset_z));
				vec_colors_.push_back(ndk_helper::Vec3(
					random() / float(RAND_MAX * 1.1), random() / float(RAND_MAX * 1.1),
					random() / float(RAND_MAX * 1.1)));

				float rotation_x = random() / float(RAND_MAX) - 0.5f;
				float rotation_y = random() / float(RAND_MAX) - 0.5f;
				vec_rotations_.push_back(ndk_helper::Vec2(rotation_x * 0.05f, rotation_y * 0.05f));
				vec_current_rotations_.push_back(
					ndk_helper::Vec2(rotation_x * M_PI, rotation_y * M_PI));
			}

	if (geometry_instancing_support_) {
		//
		// Create parameter dictionary for shader patch
		std::map<std::string, std::string> param;
		param[std::string("%NUM_TEAPOT%")] =
			ToString(teapot_x_ * teapot_y_ * teapot_z_);
		param[std::string("%LOCATION_VERTEX%")] = ToString(ATTRIB_VERTEX);
		param[std::string("%LOCATION_NORMAL%")] = ToString(ATTRIB_NORMAL);
		if (arb_support_)
			param[std::string("%ARB%")] = std::string("ARB");
		else
			param[std::string("%ARB%")] = std::string("");

		// Load shader
		bool b = LoadShadersES3(&shader_param_, "Shaders/VS_ShaderPlainES3.vsh",
			"Shaders/ShaderPlainES3.fsh", param);
		if (b) {
			//
			// Create uniform buffer
			//
			GLuint bindingPoint = 1;
			GLuint blockIndex;
			blockIndex = glGetUniformBlockIndex(shader_param_.program_, "ParamBlock");
			glUniformBlockBinding(shader_param_.program_, blockIndex, bindingPoint);

			// Retrieve array stride value
			int32_t num_indices;
			glGetActiveUniformBlockiv(shader_param_.program_, blockIndex,
				GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &num_indices);
			GLint i[num_indices];
			GLint stride[num_indices];
			glGetActiveUniformBlockiv(shader_param_.program_, blockIndex,
				GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, i);
			glGetActiveUniformsiv(shader_param_.program_, num_indices, (GLuint*)i,
				GL_UNIFORM_ARRAY_STRIDE, stride);

			ubo_matrix_stride_ = stride[0] / sizeof(float);
			ubo_vector_stride_ = stride[2] / sizeof(float);

			glGenBuffers(1, &ubo_);
			glBindBuffer(GL_UNIFORM_BUFFER, ubo_);
			glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, ubo_);

			// Store color value which wouldn't be updated every frame
			int32_t size = teapot_x_ * teapot_y_ * teapot_z_ *
				(ubo_matrix_stride_ + ubo_matrix_stride_ +
					ubo_vector_stride_);  // Mat4 + Mat4 + Vec3 + 1 stride
			float* pBuffer = new float[size];
			float* pColor =
				pBuffer + teapot_x_ * teapot_y_ * teapot_z_ * ubo_matrix_stride_ * 2;
			for (int32_t i = 0; i < teapot_x_ * teapot_y_ * teapot_z_; ++i) {
				memcpy(pColor, &vec_colors_[i], 3 * sizeof(float));
				pColor += ubo_vector_stride_;  // Assuming std140 layout which is 4
											   // DWORD stride for vectors
			}

			glBufferData(GL_UNIFORM_BUFFER, size * sizeof(float), pBuffer,
				GL_DYNAMIC_DRAW);
			delete[] pBuffer;
		}
		else {
			LOGI("Shader compilation failed!! Falls back to ES2.0 pass");
			// This happens some devices.
			geometry_instancing_support_ = false;
			// Load shader for GLES2.0
			LoadShaders(&shader_param_, "Shaders/VS_ShaderPlain.vsh",
				"Shaders/ShaderPlain.fsh");
		}
	}
	else {
		// Load shader for GLES2.0
		LoadShaders(&shader_param_, "Shaders/VS_ShaderPlain.vsh",
			"Shaders/ShaderPlain.fsh");
	}
	LOGV("@@MoreTeapotsRenderer::Init, END...");
}

void MoreTeapotsRenderer::UpdateViewport()//FIXME!
{
	//LOGV("@@MoreTeapotsRenderer::UpdateViewport, BEGIN...");
	//int32_t viewport[4];
	//glGetIntegerv(GL_VIEWPORT, viewport);
	////
	//const float CAM_NEAR = 5.f;
	//const float CAM_FAR = 10000.f;
	//if (viewport[2] < viewport[3]) {
	//	float aspect =
	//		static_cast<float>(viewport[2]) / static_cast<float>(viewport[3]);
	//	mat_projection_ =
	//		ndk_helper::Mat4::Perspective(aspect, 1.0f, CAM_NEAR, CAM_FAR);
	//}
	//else {
	//	float aspect =
	//		static_cast<float>(viewport[3]) / static_cast<float>(viewport[2]);
	//	mat_projection_ =
	//		ndk_helper::Mat4::Perspective(1.0f, aspect, CAM_NEAR, CAM_FAR);
	//}
	//LOGV("@@MoreTeapotsRenderer::UpdateViewport, END...");
}

//--------------------------------------------------------------------------------
// Unload
//--------------------------------------------------------------------------------
void MoreTeapotsRenderer::Unload()
{
	LOGV("@@MoreTeapotsRenderer::Unload, BEGIN...");
	if (vbo_) {
		glDeleteBuffers(1, &vbo_);
		vbo_ = 0;
	}
	if (ubo_) {
		glDeleteBuffers(1, &ubo_);
		ubo_ = 0;
	}
	if (ibo_) {
		glDeleteBuffers(1, &ibo_);
		ibo_ = 0;
	}
	if (shader_param_.program_) {
		glDeleteProgram(shader_param_.program_);
		shader_param_.program_ = 0;
	}
	LOGV("@@MoreTeapotsRenderer::Unload, END...");
}

//--------------------------------------------------------------------------------
// Update
//--------------------------------------------------------------------------------
void MoreTeapotsRenderer::Update()//void MoreTeapotsRenderer::Update(float fTime)
{
	LOGV("@@MoreTeapotsRenderer::Update, BEGIN...");
	//const float CAM_X = 0.f;
	//const float CAM_Y = 0.f;
	//const float CAM_Z = 2000.f;
	//mat_view_ = ndk_helper::Mat4::LookAt(ndk_helper::Vec3(CAM_X, CAM_Y, CAM_Z),
	//	ndk_helper::Vec3(0.f, 0.f, 0.f),
	//	ndk_helper::Vec3(0.f, 1.f, 0.f));
	//if (camera_) {
	//	camera_->Update();
	//	mat_view_ = camera_->GetTransformMatrix() * mat_view_ *
	//		camera_->GetRotationMatrix();
	//}


	//!!QiyuApi
	qiyu_DeviceInfo di = qiyu_GetDeviceInfo();
	float fPredictedTimeMs = qiyu_PredictDisplayTime();
	m_headPoseState = qiyu_PredictHeadPose(fPredictedTimeMs);

	//qiyu_Vector3 leftEyePos;
	//leftEyePos.x = di.frustumLeftEye.position.x;
	//leftEyePos.y = di.frustumLeftEye.position.y;
	//leftEyePos.z = di.frustumLeftEye.position.z;
	//qiyu_Vector3 rightEyePos;
	//rightEyePos.x = di.frustumRightEye.position.x;
	//rightEyePos.y = di.frustumRightEye.position.y;
	//rightEyePos.z = di.frustumRightEye.position.z;
	qiyu_Quaternion leftEyeRot;// glm quat is (w)(xyz), BUT here is xyzw
	leftEyeRot.x = di.frustumLeftEye.rotation.x;
	leftEyeRot.y = di.frustumLeftEye.rotation.y;
	leftEyeRot.z = di.frustumLeftEye.rotation.z;
	leftEyeRot.w = di.frustumLeftEye.rotation.w;
	qiyu_Quaternion rightEyeRot;// glm quat is (w)(xyz), BUT here is xyzw
	rightEyeRot.x = di.frustumRightEye.rotation.x;
	rightEyeRot.y = di.frustumRightEye.rotation.y;
	rightEyeRot.z = di.frustumRightEye.rotation.z;
	rightEyeRot.w = di.frustumRightEye.rotation.w;
	//LOGV_("@@MoreTeapotsRenderer::Update, leftEyePos(%f, %f, %f), rightEyePos(%f, %f, %f), leftEyeRot(%f, %f, %f, %f), rightEyeRot(%f, %f, %f, %f)", leftEyePos.x, leftEyePos.y, leftEyePos.z, rightEyePos.x, rightEyePos.y, rightEyePos.z, leftEyeRot.x, leftEyeRot.y, leftEyeRot.z, leftEyeRot.w, rightEyeRot.x, rightEyeRot.y, rightEyeRot.z, rightEyeRot.w);
	qiyu_Matrix4 outLeftEyeMatrix;
	qiyu_Matrix4 outRightEyeMatrix;
	float g_fTrackingOffset = 0.0f;//FIXME! //TODO!
	if (!qiyu_GetViewMatrix(outLeftEyeMatrix, outRightEyeMatrix, g_fTrackingOffset, m_headPoseState, leftEyeRot, rightEyeRot))
	{
		LOGE("@@MoreTeapotsRenderer::Update, fail to qiyu_GetViewMatrix");
	}
	assert(sizeof(mat_view_[EYE_Left]) == sizeof(outLeftEyeMatrix));
	memcpy(mat_view_[EYE_Left].Ptr(), &outLeftEyeMatrix, sizeof(outLeftEyeMatrix));
	memcpy(mat_view_[EYE_Right].Ptr(), &outRightEyeMatrix, sizeof(outLeftEyeMatrix));

	qiyu_ViewFrustum* pLeftFrust = &di.frustumLeftEye;
	qiyu_ViewFrustum* pRightFrust = &di.frustumRightEye;
	assert(pLeftFrust->right - pLeftFrust->left >= 0);
	assert(pLeftFrust->top - pLeftFrust->bottom >= 0);
	assert(pRightFrust->right - pRightFrust->left >= 0);
	assert(pRightFrust->top - pRightFrust->bottom >= 0);
	mat_projection_[EYE_Left] = ndk_helper::Mat4::Perspective(pLeftFrust->right - pLeftFrust->left, pLeftFrust->top - pLeftFrust->bottom, pLeftFrust->near, pLeftFrust->far);
	mat_projection_[EYE_Right] = ndk_helper::Mat4::Perspective(pRightFrust->right - pRightFrust->left, pRightFrust->top - pRightFrust->bottom, pRightFrust->near, pRightFrust->far);
	
	LOGV("@@MoreTeapotsRenderer::Update, END...");
}

//--------------------------------------------------------------------------------
// Render
//--------------------------------------------------------------------------------
void MoreTeapotsRenderer::Render(qiyu_Eye eyeType)//void MoreTeapotsRenderer::Render()
{
	LOGV("@@MoreTeapotsRenderer::Render, BEGIN...");
	// Bind the VBO
	glBindBuffer(GL_ARRAY_BUFFER, vbo_);

	int32_t iStride = sizeof(TEAPOT_VERTEX);
	// Pass the vertex data
	glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, GL_FALSE, iStride,
		BUFFER_OFFSET(0));
	glEnableVertexAttribArray(ATTRIB_VERTEX);

	glVertexAttribPointer(ATTRIB_NORMAL, 3, GL_FLOAT, GL_FALSE, iStride,
		BUFFER_OFFSET(3 * sizeof(GLfloat)));
	glEnableVertexAttribArray(ATTRIB_NORMAL);

	// Bind the IB
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);

	glUseProgram(shader_param_.program_);

	TEAPOT_MATERIALS material = { {1.0f, 1.0f, 1.0f, 10.f}, {0.1f, 0.1f, 0.1f}, };

	// Update uniforms
	//
	// using glUniform3fv here was troublesome..
	//
	glUniform4f(shader_param_.material_specular_, material.specular_color[0],
		material.specular_color[1], material.specular_color[2],
		material.specular_color[3]);
	glUniform3f(shader_param_.material_ambient_, material.ambient_color[0],
		material.ambient_color[1], material.ambient_color[2]);

	glUniform3f(shader_param_.light0_, 100.f, -200.f, -600.f);

	LOGV("@@MoreTeapotsRenderer::Render, geometry_instancing_support_(%d)", geometry_instancing_support_);
	if (geometry_instancing_support_)
	{
		//
		// Geometry instancing, new feature in GLES3.0
		//

		// Update UBO
		glBindBuffer(GL_UNIFORM_BUFFER, ubo_);
		float* p = (float*)glMapBufferRange(
			GL_UNIFORM_BUFFER, 0, teapot_x_ * teapot_y_ * teapot_z_ *
			(ubo_matrix_stride_ * 2) * sizeof(float),
			GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
		float* mat_mvp = p;
		float* mat_mv = p + teapot_x_ * teapot_y_ * teapot_z_ * ubo_matrix_stride_;
		for (int32_t i = 0; i < teapot_x_ * teapot_y_ * teapot_z_; ++i)
		{
			// Rotation
			float x, y;
			vec_current_rotations_[i] += vec_rotations_[i];
			vec_current_rotations_[i].Value(x, y);
			ndk_helper::Mat4 mat_rotation =
				ndk_helper::Mat4::RotationX(x) * ndk_helper::Mat4::RotationY(y);

			// Feed Projection and Model View matrices to the shaders
			//vec_mat_models_[i] = ndk_helper::Mat4::Translation(0.0f, 0.0f, -100.0f);//TEST!
			//mat_rotation = ndk_helper::Mat4::Identity();//TEST!
			ndk_helper::Mat4 mat_v = mat_view_[eyeType] * vec_mat_models_[i] * mat_rotation;//ndk_helper::Mat4 mat_v = mat_view_ * vec_mat_models_[i] * mat_rotation;
			ndk_helper::Mat4 mat_vp = mat_projection_[eyeType] * mat_v;//ndk_helper::Mat4 mat_vp = mat_projection_ * mat_v;

			memcpy(mat_mvp, mat_vp.Ptr(), sizeof(mat_v));
			mat_mvp += ubo_matrix_stride_;

			memcpy(mat_mv, mat_v.Ptr(), sizeof(mat_v));
			mat_mv += ubo_matrix_stride_;
		}
		glUnmapBuffer(GL_UNIFORM_BUFFER);

		// Instanced rendering
		glDrawElementsInstanced(GL_TRIANGLES, num_indices_, GL_UNSIGNED_SHORT,
			BUFFER_OFFSET(0),
			teapot_x_ * teapot_y_ * teapot_z_);
	}
	else
	{
		// Regular rendering pass
		for (int32_t i = 0; i < teapot_x_ * teapot_y_ * teapot_z_; ++i)
		{
			// Set diffuse
			float x, y, z;
			vec_colors_[i].Value(x, y, z);
			glUniform4f(shader_param_.material_diffuse_, x, y, z, 1.f);

			// Rotation
			vec_current_rotations_[i] += vec_rotations_[i];
			vec_current_rotations_[i].Value(x, y);
			ndk_helper::Mat4 mat_rotation =
				ndk_helper::Mat4::RotationX(x) * ndk_helper::Mat4::RotationY(y);

			// Feed Projection and Model View matrices to the shaders
			//vec_mat_models_[i] = ndk_helper::Mat4::Translation(0.0f, 0.0f, -100.0f);//TEST!
			//mat_rotation = ndk_helper::Mat4::Identity();//TEST!
			ndk_helper::Mat4 mat_v = mat_view_[eyeType] * vec_mat_models_[i] * mat_rotation;//ndk_helper::Mat4 mat_v = mat_view_ * vec_mat_models_[i] * mat_rotation;
			ndk_helper::Mat4 mat_vp = mat_projection_[eyeType] * mat_v;//ndk_helper::Mat4 mat_vp = mat_projection_ * mat_v;
			glUniformMatrix4fv(shader_param_.matrix_projection_, 1, GL_FALSE,
				mat_vp.Ptr());
			glUniformMatrix4fv(shader_param_.matrix_view_, 1, GL_FALSE, mat_v.Ptr());

			glDrawElements(GL_TRIANGLES, num_indices_, GL_UNSIGNED_SHORT,
				BUFFER_OFFSET(0));
		}
	}

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	LOGV("@@MoreTeapotsRenderer::Render, END...");
}

//--------------------------------------------------------------------------------
// LoadShaders
//--------------------------------------------------------------------------------
bool MoreTeapotsRenderer::LoadShaders(SHADER_PARAMS* params, const char* strVsh, const char* strFsh)
{
	LOGV("@@MoreTeapotsRenderer::LoadShaders, BEGIN...");
	//
	// Shader load for GLES2
	// In GLES2.0, shader attribute locations need to be explicitly specified
	// before linking
	//
	GLuint program;
	GLuint vertShader, fragShader;

	// Create shader program
	program = glCreateProgram();
	LOGI("Created Shader %d", program);

	// Create and compile vertex shader
	if (!ndk_helper::shader::CompileShader(&vertShader, GL_VERTEX_SHADER,
		strVsh)) {
		LOGI("Failed to compile vertex shader");
		glDeleteProgram(program);
		return false;
	}

	// Create and compile fragment shader
	if (!ndk_helper::shader::CompileShader(&fragShader, GL_FRAGMENT_SHADER,
		strFsh)) {
		LOGI("Failed to compile fragment shader");
		glDeleteProgram(program);
		return false;
	}

	// Attach vertex shader to program
	glAttachShader(program, vertShader);

	// Attach fragment shader to program
	glAttachShader(program, fragShader);

	// Bind attribute locations
	// this needs to be done prior to linking
	glBindAttribLocation(program, ATTRIB_VERTEX, "myVertex");
	glBindAttribLocation(program, ATTRIB_NORMAL, "myNormal");

	// Link program
	if (!ndk_helper::shader::LinkProgram(program)) {
		LOGI("Failed to link program: %d", program);

		if (vertShader) {
			glDeleteShader(vertShader);
			vertShader = 0;
		}
		if (fragShader) {
			glDeleteShader(fragShader);
			fragShader = 0;
		}
		if (program) {
			glDeleteProgram(program);
		}
		return false;
	}

	// Get uniform locations
	params->matrix_projection_ = glGetUniformLocation(program, "uPMatrix");
	params->matrix_view_ = glGetUniformLocation(program, "uMVMatrix");

	params->light0_ = glGetUniformLocation(program, "vLight0");
	params->material_diffuse_ = glGetUniformLocation(program, "vMaterialDiffuse");
	params->material_ambient_ = glGetUniformLocation(program, "vMaterialAmbient");
	params->material_specular_ =
		glGetUniformLocation(program, "vMaterialSpecular");

	// Release vertex and fragment shaders
	if (vertShader) glDeleteShader(vertShader);
	if (fragShader) glDeleteShader(fragShader);

	params->program_ = program;
	LOGV("@@MoreTeapotsRenderer::LoadShaders, END...");
	return true;
}

bool MoreTeapotsRenderer::LoadShadersES3(
	SHADER_PARAMS* params, const char* strVsh, const char* strFsh,
	std::map<std::string, std::string>& shaderParams)
{
	LOGV("@@MoreTeapotsRenderer::LoadShadersES3, BEGIN...");
	//
	// Shader load for GLES3
	// In GLES3.0, shader attribute index can be described in a shader code
	// directly with layout() attribute
	//
	GLuint program;
	GLuint vertShader, fragShader;

	// Create shader program
	program = glCreateProgram();
	LOGI("Created Shader %d", program);

	// Create and compile vertex shader
	if (!ndk_helper::shader::CompileShader(&vertShader, GL_VERTEX_SHADER, strVsh,
		shaderParams)) {
		LOGI("Failed to compile vertex shader");
		glDeleteProgram(program);
		return false;
	}

	// Create and compile fragment shader
	if (!ndk_helper::shader::CompileShader(&fragShader, GL_FRAGMENT_SHADER,
		strFsh, shaderParams)) {
		LOGI("Failed to compile fragment shader");
		glDeleteProgram(program);
		return false;
	}

	// Attach vertex shader to program
	glAttachShader(program, vertShader);

	// Attach fragment shader to program
	glAttachShader(program, fragShader);

	// Link program
	if (!ndk_helper::shader::LinkProgram(program)) {
		LOGI("Failed to link program: %d", program);

		if (vertShader) {
			glDeleteShader(vertShader);
			vertShader = 0;
		}
		if (fragShader) {
			glDeleteShader(fragShader);
			fragShader = 0;
		}
		if (program) {
			glDeleteProgram(program);
		}

		return false;
	}

	// Get uniform locations
	params->light0_ = glGetUniformLocation(program, "vLight0");
	params->material_ambient_ = glGetUniformLocation(program, "vMaterialAmbient");
	params->material_specular_ =
		glGetUniformLocation(program, "vMaterialSpecular");

	// Release vertex and fragment shaders
	if (vertShader) glDeleteShader(vertShader);
	if (fragShader) glDeleteShader(fragShader);

	params->program_ = program;
	LOGV("@@MoreTeapotsRenderer::LoadShadersES3, END...");
	return true;
}

//--------------------------------------------------------------------------------
// Bind
//--------------------------------------------------------------------------------
bool MoreTeapotsRenderer::Bind(ndk_helper::TapCamera* camera)
{
	LOGV("@@MoreTeapotsRenderer::Bind, BEGIN...");
	//camera_ = camera;
	LOGV("@@MoreTeapotsRenderer::Bind, END...");
	return true;
}

//--------------------------------------------------------------------------------
// Helper functions
//--------------------------------------------------------------------------------
std::string MoreTeapotsRenderer::ToString(const int32_t i) {
	char str[64];
	snprintf(str, sizeof(str), "%d", i);
	return std::string(str);
}
