// © 2021 Attila Uygun // Licensed under the MIT license. #include "engine/renderer/opengl/renderer_opengl.h" #include #include #include #include #include "base/log.h" #include "base/vecmath.h" #ifdef THREADED_RENDERING #include "base/task_runner.h" #endif // THREADED_RENDERING #include "engine/image.h" #include "engine/mesh.h" #include "engine/renderer/geometry.h" #include "engine/renderer/opengl/render_command.h" #include "engine/renderer/shader.h" #include "engine/renderer/texture.h" #include "engine/shader_source.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 { #ifdef THREADED_RENDERING RendererOpenGL::RendererOpenGL(base::Closure context_lost_cb) : Renderer(context_lost_cb), main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {} #else RendererOpenGL::RendererOpenGL() = default; #endif // THREADED_RENDERING RendererOpenGL::~RendererOpenGL() { Shutdown(); OnDestroy(); } void RendererOpenGL::Shutdown() { is_initialized_ = false; #ifdef THREADED_RENDERING if (terminate_render_thread_) return; LOG << "Shutting down renderer."; { std::unique_lock scoped_lock(mutex_); terminate_render_thread_ = true; } cv_.notify_one(); LOG << "Terminating render thread"; render_thread_.join(); #else ShutdownInternal(); #endif // THREADED_RENDERING } uint64_t RendererOpenGL::CreateGeometry(std::unique_ptr mesh) { auto cmd = std::make_unique(); cmd->mesh = std::move(mesh); cmd->resource_id = ++last_resource_id_; EnqueueCommand(std::move(cmd)); return last_resource_id_; } void RendererOpenGL::DestroyGeometry(uint64_t resource_id) { auto cmd = std::make_unique(); cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::Draw(uint64_t resource_id) { auto cmd = std::make_unique(); cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } uint64_t RendererOpenGL::CreateTexture() { auto cmd = std::make_unique(); cmd->resource_id = ++last_resource_id_; EnqueueCommand(std::move(cmd)); return last_resource_id_; } void RendererOpenGL::UpdateTexture(uint64_t resource_id, std::unique_ptr image) { auto cmd = std::make_unique(); cmd->image = std::move(image); cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::DestroyTexture(uint64_t resource_id) { auto cmd = std::make_unique(); cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::ActivateTexture(uint64_t resource_id) { auto cmd = std::make_unique(); cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } uint64_t RendererOpenGL::CreateShader( std::unique_ptr source, const VertexDescription& vertex_description, Primitive primitive, bool enable_depth_test) { auto cmd = std::make_unique(); cmd->source = std::move(source); cmd->vertex_description = vertex_description; cmd->resource_id = ++last_resource_id_; cmd->enable_depth_test = enable_depth_test; EnqueueCommand(std::move(cmd)); return last_resource_id_; } void RendererOpenGL::DestroyShader(uint64_t resource_id) { auto cmd = std::make_unique(); cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::ActivateShader(uint64_t resource_id) { auto cmd = std::make_unique(); cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::SetUniform(uint64_t resource_id, const std::string& name, const base::Vector2f& val) { auto cmd = std::make_unique(); cmd->name = name; cmd->v = val; cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::SetUniform(uint64_t resource_id, const std::string& name, const base::Vector3f& val) { auto cmd = std::make_unique(); cmd->name = name; cmd->v = val; cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::SetUniform(uint64_t resource_id, const std::string& name, const base::Vector4f& val) { auto cmd = std::make_unique(); cmd->name = name; cmd->v = val; cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::SetUniform(uint64_t resource_id, const std::string& name, const base::Matrix4f& val) { auto cmd = std::make_unique(); cmd->name = name; cmd->m = val; cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::SetUniform(uint64_t resource_id, const std::string& name, float val) { auto cmd = std::make_unique(); cmd->name = name; cmd->f = val; cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::SetUniform(uint64_t resource_id, const std::string& name, int val) { auto cmd = std::make_unique(); cmd->name = name; cmd->i = val; cmd->resource_id = resource_id; EnqueueCommand(std::move(cmd)); } void RendererOpenGL::Present() { EnqueueCommand(std::make_unique()); #ifdef THREADED_RENDERING draw_complete_semaphore_.acquire(); #endif // THREADED_RENDERING fps_++; } void RendererOpenGL::ContextLost() { LOG << "Context lost."; #ifdef THREADED_RENDERING { std::unique_lock scoped_lock(mutex_); global_commands_.clear(); draw_commands_[0].clear(); draw_commands_[1].clear(); } DestroyAllResources(); main_thread_task_runner_->PostTask(HERE, context_lost_cb_); #else DestroyAllResources(); context_lost_cb_(); #endif // THREADED_RENDERING } 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(glGetString(GL_RENDERER)); const char* version = reinterpret_cast(glGetString(GL_VERSION)); LOG << "OpenGL:"; LOG << " vendor: " << (const char*)glGetString(GL_VENDOR); LOG << " renderer: " << renderer; LOG << " version: " << version; LOG << " shader version: " << (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); LOG << "Screen size: " << screen_width_ << ", " << screen_height_; // Setup extensions. std::stringstream stream((const char*)glGetString(GL_EXTENSIONS)); std::string token; std::unordered_set extensions; while (std::getline(stream, token, ' ')) extensions.insert(token); #if 0 LOG << " extensions:"; for (auto& ext : extensions) LOG << " " << 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 << "NPOT not supported."; if (vertex_array_objects_) LOG << "Supports Vertex Array Objects."; LOG << "TextureCompression:"; LOG << " atc: " << texture_compression_.atc; LOG << " dxt1: " << texture_compression_.dxt1; LOG << " etc1: " << texture_compression_.etc1; LOG << " s3tc: " << texture_compression_.s3tc; glViewport(0, 0, screen_width_, screen_height_); 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 resource_ids; for (auto& r : geometries_) resource_ids.push_back(r.first); for (auto& r : resource_ids) { auto cmd = std::make_unique(); cmd->resource_id = r; ProcessCommand(cmd.get()); } resource_ids.clear(); for (auto& r : shaders_) resource_ids.push_back(r.first); for (auto& r : resource_ids) { auto cmd = std::make_unique(); cmd->resource_id = r; ProcessCommand(cmd.get()); } resource_ids.clear(); for (auto& r : textures_) resource_ids.push_back(r.first); for (auto& r : resource_ids) { auto cmd = std::make_unique(); cmd->resource_id = r; ProcessCommand(cmd.get()); } DCHECK(geometries_.size() == 0); DCHECK(shaders_.size() == 0); DCHECK(textures_.size() == 0); } bool RendererOpenGL::StartRenderThread() { #ifdef THREADED_RENDERING LOG << "Starting render thread."; global_commands_.clear(); draw_commands_[0].clear(); draw_commands_[1].clear(); terminate_render_thread_ = false; std::promise promise; std::future future = promise.get_future(); render_thread_ = std::thread(&RendererOpenGL::RenderThreadMain, this, std::move(promise)); future.wait(); return future.get(); #else LOG << "Single threaded rendering."; return InitInternal(); #endif // THREADED_RENDERING } #ifdef THREADED_RENDERING void RendererOpenGL::RenderThreadMain(std::promise promise) { promise.set_value(InitInternal()); std::deque> cq[2]; for (;;) { { std::unique_lock scoped_lock(mutex_); cv_.wait(scoped_lock, [&]() -> bool { return !global_commands_.empty() || !draw_commands_[0].empty() || terminate_render_thread_; }); if (terminate_render_thread_) { ShutdownInternal(); return; } cq[0].swap(global_commands_); cq[1].swap(draw_commands_[0]); } #if 0 LOG << "qlobal queue size: " << (int)cq[0].size(); LOG << "draw queue size: " << (int)cq[1].size(); #endif for (int i = 0; i < 2; ++i) { while (!cq[i].empty()) { ProcessCommand(cq[i].front().get()); cq[i].pop_front(); } } } } #endif // THREADED_RENDERING void RendererOpenGL::EnqueueCommand(std::unique_ptr cmd) { #ifdef THREADED_RENDERING if (cmd->global) { { std::unique_lock scoped_lock(mutex_); global_commands_.push_back(std::move(cmd)); } cv_.notify_one(); return; } bool new_frame = cmd->cmd_id == CmdPresent::CMD_ID; draw_commands_[1].push_back(std::move(cmd)); if (new_frame) { { std::unique_lock scoped_lock(mutex_); draw_commands_[0].swap(draw_commands_[1]); } cv_.notify_one(); draw_commands_[1].clear(); } #else ProcessCommand(cmd.get()); #endif // THREADED_RENDERING } void RendererOpenGL::ProcessCommand(RenderCommand* cmd) { #if 0 LOG << "Processing command: " << cmd->cmd_name.c_str(); #endif switch (cmd->cmd_id) { case CmdPresent::CMD_ID: HandleCmdPresent(cmd); break; case CmdCreateTexture::CMD_ID: HandleCmdCreateTexture(cmd); break; case CmdUpdateTexture::CMD_ID: HandleCmdUpdateTexture(cmd); break; case CmdDestoryTexture::CMD_ID: HandleCmdDestoryTexture(cmd); break; case CmdActivateTexture::CMD_ID: HandleCmdActivateTexture(cmd); break; case CmdCreateGeometry::CMD_ID: HandleCmdCreateGeometry(cmd); break; case CmdDestroyGeometry::CMD_ID: HandleCmdDestroyGeometry(cmd); break; case CmdDrawGeometry::CMD_ID: HandleCmdDrawGeometry(cmd); break; case CmdCreateShader::CMD_ID: HandleCmdCreateShader(cmd); break; case CmdDestroyShader::CMD_ID: HandleCmdDestroyShader(cmd); break; case CmdActivateShader::CMD_ID: HandleCmdActivateShader(cmd); break; case CmdSetUniformVec2::CMD_ID: HandleCmdSetUniformVec2(cmd); break; case CmdSetUniformVec3::CMD_ID: HandleCmdSetUniformVec3(cmd); break; case CmdSetUniformVec4::CMD_ID: HandleCmdSetUniformVec4(cmd); break; case CmdSetUniformMat4::CMD_ID: HandleCmdSetUniformMat4(cmd); break; case CmdSetUniformFloat::CMD_ID: HandleCmdSetUniformFloat(cmd); break; case CmdSetUniformInt::CMD_ID: HandleCmdSetUniformInt(cmd); break; default: NOTREACHED << "- Unknown render command: " << cmd->cmd_id; } } void RendererOpenGL::HandleCmdCreateTexture(RenderCommand* cmd) { auto* c = static_cast(cmd); 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); textures_.insert({c->resource_id, {gl_id}}); } void RendererOpenGL::HandleCmdUpdateTexture(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = textures_.find(c->resource_id); if (it == textures_.end()) return; BindTexture(it->second.id); if (c->image->IsCompressed()) { GLenum format = 0; switch (c->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: " << c->image->GetFormat(); } glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, c->image->GetWidth(), c->image->GetHeight(), 0, c->image->GetSize(), c->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, c->image->GetWidth(), c->image->GetHeight(), 0, c->image->GetSize(), c->image->GetBuffer()); err = glGetError(); } if (err != GL_NO_ERROR) LOG << "GL ERROR after glCompressedTexImage2D: " << (int)err; } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, c->image->GetWidth(), c->image->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, c->image->GetBuffer()); } } void RendererOpenGL::HandleCmdDestoryTexture(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = textures_.find(c->resource_id); if (it == textures_.end()) return; glDeleteTextures(1, &(it->second.id)); textures_.erase(it); } void RendererOpenGL::HandleCmdActivateTexture(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = textures_.find(c->resource_id); if (it == textures_.end()) { return; } BindTexture(it->second.id); } void RendererOpenGL::HandleCmdCreateGeometry(RenderCommand* cmd) { auto* c = static_cast(cmd); // Verify that we have a valid layout and get the total byte size per vertex. GLuint vertex_size = c->mesh->GetVertexSize(); if (!vertex_size) { LOG << "Invalid vertex layout"; return; } 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, c->mesh->num_vertices() * vertex_size, c->mesh->GetVertices(), GL_STATIC_DRAW); // Make sure the vertex format is understood and the attribute pointers are // set up. std::vector vertex_layout; if (!SetupVertexLayout(c->mesh->vertex_description(), vertex_size, vertex_array_objects_, vertex_layout)) return; // Create the index buffer and upload the data. GLuint index_buffer_id = 0; if (c->mesh->GetIndices()) { glGenBuffers(1, &index_buffer_id); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_id); glBufferData(GL_ELEMENT_ARRAY_BUFFER, c->mesh->num_indices() * c->mesh->GetIndexSize(), c->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); } geometries_[c->resource_id] = {(GLsizei)c->mesh->num_vertices(), (GLsizei)c->mesh->num_indices(), kGlPrimitive[c->mesh->primitive()], kGlDataType[c->mesh->index_description()], vertex_layout, vertex_size, vertex_array_id, vertex_buffer_id, index_buffer_id}; } void RendererOpenGL::HandleCmdDestroyGeometry(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = geometries_.find(c->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::HandleCmdDrawGeometry(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = geometries_.find(c->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); } } void RendererOpenGL::HandleCmdCreateShader(RenderCommand* cmd) { auto* c = static_cast(cmd); GLuint vertex_shader = CreateShader(c->source->GetVertexSource(), GL_VERTEX_SHADER); if (!vertex_shader) return; GLuint fragment_shader = CreateShader(c->source->GetFragmentSource(), GL_FRAGMENT_SHADER); if (!fragment_shader) return; GLuint id = glCreateProgram(); if (id) { glAttachShader(id, vertex_shader); glAttachShader(id, fragment_shader); if (!BindAttributeLocation(id, c->vertex_description)) return; 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 << "Could not link program:\n" << buffer; free(buffer); } } glDeleteProgram(id); return; } } shaders_[c->resource_id] = {id, {}, c->enable_depth_test}; } void RendererOpenGL::HandleCmdDestroyShader(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = shaders_.find(c->resource_id); if (it == shaders_.end()) return; glDeleteProgram(it->second.id); shaders_.erase(it); } void RendererOpenGL::HandleCmdActivateShader(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = shaders_.find(c->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::HandleCmdSetUniformVec2(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = shaders_.find(c->resource_id); if (it == shaders_.end()) return; GLint index = GetUniformLocation(it->second.id, c->name, it->second.uniforms); if (index >= 0) glUniform2fv(index, 1, c->v.GetData()); } void RendererOpenGL::HandleCmdSetUniformVec3(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = shaders_.find(c->resource_id); if (it == shaders_.end()) return; GLint index = GetUniformLocation(it->second.id, c->name, it->second.uniforms); if (index >= 0) glUniform3fv(index, 1, c->v.GetData()); } void RendererOpenGL::HandleCmdSetUniformVec4(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = shaders_.find(c->resource_id); if (it == shaders_.end()) return; GLint index = GetUniformLocation(it->second.id, c->name, it->second.uniforms); if (index >= 0) glUniform4fv(index, 1, c->v.GetData()); } void RendererOpenGL::HandleCmdSetUniformMat4(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = shaders_.find(c->resource_id); if (it == shaders_.end()) return; GLint index = GetUniformLocation(it->second.id, c->name, it->second.uniforms); if (index >= 0) glUniformMatrix4fv(index, 1, GL_FALSE, c->m.GetData()); } void RendererOpenGL::HandleCmdSetUniformFloat(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = shaders_.find(c->resource_id); if (it == shaders_.end()) return; GLint index = GetUniformLocation(it->second.id, c->name, it->second.uniforms); if (index >= 0) glUniform1f(index, c->f); } void RendererOpenGL::HandleCmdSetUniformInt(RenderCommand* cmd) { auto* c = static_cast(cmd); auto it = shaders_.find(c->resource_id); if (it == shaders_.end()) return; GLint index = GetUniformLocation(it->second.id, c->name, it->second.uniforms); if (index >= 0) glUniform1i(index, c->i); } 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& 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 << "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::unordered_map& uniforms) { // Check if we've encountered this uniform before. auto i = uniforms.find(name); GLint index; if (i != uniforms.end()) { // Yes, we already have the mapping. index = i->second; } else { // No, ask the driver for the mapping and save it. index = glGetUniformLocation(id, name.c_str()); if (index >= 0) uniforms[name] = index; else LOG << "Cannot find uniform " << name.c_str() << " (shader: " << id << ")"; } return index; } } // namespace eng