kaliber/src/engine/renderer/opengl/renderer_opengl.cc

646 lines
19 KiB
C++

#include "engine/renderer/opengl/renderer_opengl.h"
#include <algorithm>
#include <cstring>
#include <sstream>
#include <unordered_set>
#include "base/hash.h"
#include "base/log.h"
#include "base/vecmath.h"
#include "engine/asset/image.h"
#include "engine/asset/mesh.h"
#include "engine/asset/shader_source.h"
#include "engine/renderer/geometry.h"
#include "engine/renderer/shader.h"
#include "engine/renderer/texture.h"
using namespace base;
namespace {
constexpr GLenum kGlPrimitive[eng::kPrimitive_Max] = {GL_TRIANGLES,
GL_TRIANGLE_STRIP};
constexpr GLenum kGlDataType[eng::kDataType_Max] = {
GL_UNSIGNED_BYTE, GL_FLOAT, GL_INT,
GL_SHORT, GL_UNSIGNED_INT, GL_UNSIGNED_SHORT};
const std::string kAttributeNames[eng::kAttribType_Max] = {
"in_color", "in_normal", "in_position", "in_tex_coord"};
} // namespace
namespace eng {
RendererOpenGL::RendererOpenGL(base::Closure context_lost_cb)
: Renderer(context_lost_cb) {}
RendererOpenGL::~RendererOpenGL() {
Shutdown();
OnDestroy();
}
void RendererOpenGL::OnWindowResized(int width, int height) {
screen_width_ = width;
screen_height_ = height;
glViewport(0, 0, screen_width_, screen_height_);
}
int RendererOpenGL::GetScreenWidth() const {
return screen_width_;
}
int RendererOpenGL::GetScreenHeight() const {
return screen_height_;
}
uint64_t RendererOpenGL::CreateGeometry(std::unique_ptr<Mesh> mesh) {
// Verify that we have a valid layout and get the total byte size per vertex.
GLuint vertex_size = mesh->GetVertexSize();
if (!vertex_size) {
LOG(0) << "Invalid vertex layout";
return 0;
}
GLuint vertex_array_id = 0;
if (vertex_array_objects_) {
glGenVertexArrays(1, &vertex_array_id);
glBindVertexArray(vertex_array_id);
}
// Create the vertex buffer and upload the data.
GLuint vertex_buffer_id = 0;
glGenBuffers(1, &vertex_buffer_id);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id);
glBufferData(GL_ARRAY_BUFFER, mesh->num_vertices() * vertex_size,
mesh->GetVertices(), GL_STATIC_DRAW);
// Make sure the vertex format is understood and the attribute pointers are
// set up.
std::vector<GeometryOpenGL::Element> vertex_layout;
if (!SetupVertexLayout(mesh->vertex_description(), vertex_size,
vertex_array_objects_, vertex_layout)) {
LOG(0) << "Invalid vertex layout";
return 0;
}
// Create the index buffer and upload the data.
GLuint index_buffer_id = 0;
if (mesh->GetIndices()) {
glGenBuffers(1, &index_buffer_id);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_id);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
mesh->num_indices() * mesh->GetIndexSize(), mesh->GetIndices(),
GL_STATIC_DRAW);
}
if (vertex_array_id) {
// De-activate the buffer again and we're done.
glBindVertexArray(0);
} else {
// De-activate the individual buffers.
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
uint64_t resource_id = ++last_resource_id_;
geometries_[resource_id] = {(GLsizei)mesh->num_vertices(),
(GLsizei)mesh->num_indices(),
kGlPrimitive[mesh->primitive()],
kGlDataType[mesh->index_description()],
vertex_layout,
vertex_size,
vertex_array_id,
vertex_buffer_id,
index_buffer_id};
return resource_id;
}
void RendererOpenGL::DestroyGeometry(uint64_t resource_id) {
auto it = geometries_.find(resource_id);
if (it == geometries_.end())
return;
if (it->second.index_buffer_id)
glDeleteBuffers(1, &(it->second.index_buffer_id));
if (it->second.vertex_buffer_id)
glDeleteBuffers(1, &(it->second.vertex_buffer_id));
if (it->second.vertex_array_id)
glDeleteVertexArrays(1, &(it->second.vertex_array_id));
geometries_.erase(it);
}
void RendererOpenGL::Draw(uint64_t resource_id) {
auto it = geometries_.find(resource_id);
if (it == geometries_.end())
return;
// Set up the vertex data.
if (it->second.vertex_array_id)
glBindVertexArray(it->second.vertex_array_id);
else {
glBindBuffer(GL_ARRAY_BUFFER, it->second.vertex_buffer_id);
for (GLuint attribute_index = 0;
attribute_index < (GLuint)it->second.vertex_layout.size();
++attribute_index) {
GeometryOpenGL::Element& e = it->second.vertex_layout[attribute_index];
glEnableVertexAttribArray(attribute_index);
glVertexAttribPointer(attribute_index, e.num_elements, e.type, GL_FALSE,
it->second.vertex_size,
(const GLvoid*)e.vertex_offset);
}
if (it->second.num_indices > 0)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, it->second.index_buffer_id);
}
// Draw the primitive.
if (it->second.num_indices > 0)
glDrawElements(it->second.primitive, it->second.num_indices,
it->second.index_type, NULL);
else
glDrawArrays(it->second.primitive, 0, it->second.num_vertices);
// Clean up states.
if (it->second.vertex_array_id)
glBindVertexArray(0);
else {
for (GLuint attribute_index = 0;
attribute_index < (GLuint)it->second.vertex_layout.size();
++attribute_index)
glDisableVertexAttribArray(attribute_index);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
}
uint64_t RendererOpenGL::CreateTexture() {
GLuint gl_id = 0;
glGenTextures(1, &gl_id);
BindTexture(gl_id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
uint64_t resource_id = ++last_resource_id_;
textures_[resource_id] = gl_id;
return resource_id;
}
void RendererOpenGL::UpdateTexture(uint64_t resource_id,
std::unique_ptr<Image> image) {
auto it = textures_.find(resource_id);
if (it == textures_.end())
return;
BindTexture(it->second);
if (image->IsCompressed()) {
GLenum format = 0;
switch (image->GetFormat()) {
case Image::kDXT1:
format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
break;
case Image::kDXT5:
format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
case Image::kETC1:
format = GL_ETC1_RGB8_OES;
break;
#if defined(__ANDROID__)
case Image::kATC:
format = GL_ATC_RGB_AMD;
break;
case Image::kATCIA:
format = GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD;
break;
#endif
default:
NOTREACHED() << "- Unhandled texure format: " << image->GetFormat();
}
glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, image->GetWidth(),
image->GetHeight(), 0, image->GetSize(),
image->GetBuffer());
// On some devices the first glCompressedTexImage2D call after context-lost
// returns GL_INVALID_VALUE for some reason.
GLenum err = glGetError();
if (err == GL_INVALID_VALUE) {
glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, image->GetWidth(),
image->GetHeight(), 0, image->GetSize(),
image->GetBuffer());
err = glGetError();
}
if (err != GL_NO_ERROR)
LOG(0) << "GL ERROR after glCompressedTexImage2D: " << (int)err;
} else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->GetWidth(),
image->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
image->GetBuffer());
}
}
void RendererOpenGL::DestroyTexture(uint64_t resource_id) {
auto it = textures_.find(resource_id);
if (it == textures_.end())
return;
glDeleteTextures(1, &(it->second));
textures_.erase(it);
}
void RendererOpenGL::ActivateTexture(uint64_t resource_id) {
auto it = textures_.find(resource_id);
if (it == textures_.end()) {
return;
}
BindTexture(it->second);
}
uint64_t RendererOpenGL::CreateShader(
std::unique_ptr<ShaderSource> source,
const VertexDescription& vertex_description,
Primitive primitive,
bool enable_depth_test) {
GLuint vertex_shader =
CreateShader(source->GetVertexSource(), GL_VERTEX_SHADER);
if (!vertex_shader)
return 0;
GLuint fragment_shader =
CreateShader(source->GetFragmentSource(), GL_FRAGMENT_SHADER);
if (!fragment_shader)
return 0;
GLuint id = glCreateProgram();
if (id) {
glAttachShader(id, vertex_shader);
glAttachShader(id, fragment_shader);
if (!BindAttributeLocation(id, vertex_description)) {
glDeleteProgram(id);
return 0;
}
glLinkProgram(id);
GLint linkStatus = GL_FALSE;
glGetProgramiv(id, GL_LINK_STATUS, &linkStatus);
if (linkStatus != GL_TRUE) {
GLint length = 0;
glGetProgramiv(id, GL_INFO_LOG_LENGTH, &length);
if (length > 0) {
char* buffer = (char*)malloc(length);
if (buffer) {
glGetProgramInfoLog(id, length, NULL, buffer);
LOG(0) << "Could not link program:\n" << buffer;
free(buffer);
}
}
glDeleteProgram(id);
return 0;
}
}
uint64_t resource_id = ++last_resource_id_;
shaders_[resource_id] = {id, {}, enable_depth_test};
return resource_id;
}
void RendererOpenGL::DestroyShader(uint64_t resource_id) {
auto it = shaders_.find(resource_id);
if (it == shaders_.end())
return;
glDeleteProgram(it->second.id);
shaders_.erase(it);
}
void RendererOpenGL::ActivateShader(uint64_t resource_id) {
auto it = shaders_.find(resource_id);
if (it == shaders_.end())
return;
if (it->second.id != active_shader_id_) {
glUseProgram(it->second.id);
active_shader_id_ = it->second.id;
if (it->second.enable_depth_test)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
}
}
void RendererOpenGL::SetUniform(uint64_t resource_id,
const std::string& name,
const base::Vector2f& val) {
auto it = shaders_.find(resource_id);
if (it == shaders_.end())
return;
GLint index = GetUniformLocation(it->second.id, name, it->second.uniforms);
if (index >= 0)
glUniform2fv(index, 1, val.GetData());
}
void RendererOpenGL::SetUniform(uint64_t resource_id,
const std::string& name,
const base::Vector3f& val) {
auto it = shaders_.find(resource_id);
if (it == shaders_.end())
return;
GLint index = GetUniformLocation(it->second.id, name, it->second.uniforms);
if (index >= 0)
glUniform3fv(index, 1, val.GetData());
}
void RendererOpenGL::SetUniform(uint64_t resource_id,
const std::string& name,
const base::Vector4f& val) {
auto it = shaders_.find(resource_id);
if (it == shaders_.end())
return;
GLint index = GetUniformLocation(it->second.id, name, it->second.uniforms);
if (index >= 0)
glUniform4fv(index, 1, val.GetData());
}
void RendererOpenGL::SetUniform(uint64_t resource_id,
const std::string& name,
const base::Matrix4f& val) {
auto it = shaders_.find(resource_id);
if (it == shaders_.end())
return;
GLint index = GetUniformLocation(it->second.id, name, it->second.uniforms);
if (index >= 0)
glUniformMatrix4fv(index, 1, GL_FALSE, val.GetData());
}
void RendererOpenGL::SetUniform(uint64_t resource_id,
const std::string& name,
float val) {
auto it = shaders_.find(resource_id);
if (it == shaders_.end())
return;
GLint index = GetUniformLocation(it->second.id, name, it->second.uniforms);
if (index >= 0)
glUniform1f(index, val);
}
void RendererOpenGL::SetUniform(uint64_t resource_id,
const std::string& name,
int val) {
auto it = shaders_.find(resource_id);
if (it == shaders_.end())
return;
GLint index = GetUniformLocation(it->second.id, name, it->second.uniforms);
if (index >= 0)
glUniform1i(index, val);
}
void RendererOpenGL::ContextLost() {
LOG(0) << "Context lost.";
DestroyAllResources();
context_lost_cb_();
}
size_t RendererOpenGL::GetAndResetFPS() {
int ret = fps_;
fps_ = 0;
return ret;
}
bool RendererOpenGL::InitCommon() {
// Get information about the currently active context.
const char* renderer =
reinterpret_cast<const char*>(glGetString(GL_RENDERER));
const char* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
LOG(0) << "OpenGL:";
LOG(0) << " vendor: " << (const char*)glGetString(GL_VENDOR);
LOG(0) << " renderer: " << renderer;
LOG(0) << " version: " << version;
LOG(0) << " shader version: "
<< (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
LOG(0) << "Screen size: " << screen_width_ << ", " << screen_height_;
// Setup extensions.
std::stringstream stream((const char*)glGetString(GL_EXTENSIONS));
std::string token;
std::unordered_set<std::string> extensions;
while (std::getline(stream, token, ' '))
extensions.insert(token);
#if 0
LOG(0) << " extensions:";
for (auto& ext : extensions)
LOG(0) << " " << ext.c_str());
#endif
// Check for supported texture compression extensions.
if (extensions.find("GL_OES_compressed_ETC1_RGB8_texture") !=
extensions.end())
texture_compression_.etc1 = true;
if (extensions.find("GL_EXT_texture_compression_dxt1") != extensions.end())
texture_compression_.dxt1 = true;
if (extensions.find("GL_EXT_texture_compression_latc") != extensions.end())
texture_compression_.latc = true;
if (extensions.find("GL_EXT_texture_compression_s3tc") != extensions.end())
texture_compression_.s3tc = true;
if (extensions.find("GL_IMG_texture_compression_pvrtc") != extensions.end())
texture_compression_.pvrtc = true;
if (extensions.find("GL_AMD_compressed_ATC_texture") != extensions.end() ||
extensions.find("GL_ATI_texture_compression_atitc") != extensions.end())
texture_compression_.atc = true;
if (extensions.find("GL_OES_vertex_array_object") != extensions.end()) {
// This extension seems to be broken on older PowerVR drivers.
if (!strstr(renderer, "PowerVR SGX 53") &&
!strstr(renderer, "PowerVR SGX 54") &&
!strstr(renderer, "Android Emulator")) {
vertex_array_objects_ = true;
}
}
if (extensions.count("GL_ARB_texture_non_power_of_two") ||
extensions.count("GL_OES_texture_npot")) {
npot_ = true;
}
// Ancient hardware is not supported.
if (!npot_)
LOG(0) << "NPOT not supported.";
if (vertex_array_objects_)
LOG(0) << "Supports Vertex Array Objects.";
LOG(0) << "TextureCompression:";
LOG(0) << " atc: " << texture_compression_.atc;
LOG(0) << " dxt1: " << texture_compression_.dxt1;
LOG(0) << " etc1: " << texture_compression_.etc1;
LOG(0) << " s3tc: " << texture_compression_.s3tc;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0, 0, 0, 1);
is_initialized_ = true;
return true;
}
void RendererOpenGL::DestroyAllResources() {
std::vector<uint64_t> resource_ids;
for (auto& r : geometries_)
resource_ids.push_back(r.first);
for (auto& r : resource_ids)
DestroyGeometry(r);
resource_ids.clear();
for (auto& r : shaders_)
resource_ids.push_back(r.first);
for (auto& r : resource_ids)
DestroyShader(r);
resource_ids.clear();
for (auto& r : textures_)
resource_ids.push_back(r.first);
for (auto& r : resource_ids)
DestroyTexture(r);
DCHECK(geometries_.size() == 0);
DCHECK(shaders_.size() == 0);
DCHECK(textures_.size() == 0);
}
void RendererOpenGL::BindTexture(GLuint id) {
if (id != active_texture_id_) {
glBindTexture(GL_TEXTURE_2D, id);
active_texture_id_ = id;
}
}
bool RendererOpenGL::SetupVertexLayout(
const VertexDescription& vd,
GLuint vertex_size,
bool use_vao,
std::vector<GeometryOpenGL::Element>& vertex_layout) {
GLuint attribute_index = 0;
size_t vertex_offset = 0;
for (auto& attr : vd) {
// There's a limitation of 16 attributes in OpenGL ES 2.0
if (attribute_index >= 16)
return false;
auto [attrib_type, data_type, num_elements, type_size] = attr;
// The data type is needed, the most common ones are supported.
GLenum type = kGlDataType[data_type];
// We got all we need to define this attribute.
if (use_vao) {
// This will be saved into the vertex array object.
glEnableVertexAttribArray(attribute_index);
glVertexAttribPointer(attribute_index, num_elements, type, GL_FALSE,
vertex_size, (const GLvoid*)vertex_offset);
} else {
// Need to keep this information for when rendering.
GeometryOpenGL::Element element;
element.num_elements = num_elements;
element.type = type;
element.vertex_offset = vertex_offset;
vertex_layout.push_back(element);
}
// Move on to the next attribute.
++attribute_index;
vertex_offset += num_elements * type_size;
}
return true;
}
GLuint RendererOpenGL::CreateShader(const char* source, GLenum type) {
GLuint shader = glCreateShader(type);
if (shader) {
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint length = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
if (length) {
char* buffer = (char*)malloc(length);
if (buffer) {
glGetShaderInfoLog(shader, length, NULL, buffer);
LOG(0) << "Could not compile shader " << type << ":\n" << buffer;
free(buffer);
}
glDeleteShader(shader);
shader = 0;
}
}
}
return shader;
}
bool RendererOpenGL::BindAttributeLocation(GLuint id,
const VertexDescription& vd) {
int current = 0;
int tex_coord = 0;
for (auto& attr : vd) {
AttribType attrib_type = std::get<0>(attr);
std::string attrib_name = kAttributeNames[attrib_type];
if (attrib_type == kAttribType_TexCoord)
attrib_name += std::to_string(tex_coord++);
glBindAttribLocation(id, current++, attrib_name.c_str());
}
return current > 0;
}
GLint RendererOpenGL::GetUniformLocation(
GLuint id,
const std::string& name,
std::vector<std::pair<size_t, GLuint>>& uniforms) {
// Check if we've encountered this uniform before.
auto hash = KR2Hash(name);
auto it = std::find_if(uniforms.begin(), uniforms.end(),
[&](auto& r) { return hash == std::get<0>(r); });
GLint index;
if (it != uniforms.end()) {
// Yes, we already have the mapping.
index = std::get<1>(*it);
} else {
// No, ask the driver for the mapping and save it.
index = glGetUniformLocation(id, name.c_str());
if (index >= 0) {
DCHECK(std::find_if(uniforms.begin(), uniforms.end(),
[&](auto& r) { return hash == std::get<0>(r); }) ==
uniforms.end())
<< "Hash collision";
uniforms.emplace_back(std::make_pair(hash, index));
} else {
LOG(0) << "Cannot find uniform " << name.c_str() << " (shader: " << id
<< ")";
}
}
return index;
}
} // namespace eng