/* * Copyright (C) 2011 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. */ #include "base/logging.h" #include "core/gl_env.h" #include "core/gl_frame.h" #include "core/shader_program.h" #include namespace android { namespace filterfw { static const int kIdentityShaderKey = 1; // // A GLFrame stores pixel data on the GPU. It uses two kinds of GL data // containers for this: Textures and Frame Buffer Objects (FBOs). Textures are // used whenever pixel data needs to be read into a shader or the host program, // and when pixel data is uploaded to a GLFrame. The FBO is used as a rendering // target for shaders. // GLFrame::GLFrame(GLEnv* gl_env) : gl_env_(gl_env), width_(0), height_(0), vp_x_(0), vp_y_(0), vp_width_(0), vp_height_(0), texture_id_(0), fbo_id_(0), texture_target_(GL_TEXTURE_2D), texture_state_(kStateUninitialized), fbo_state_(kStateUninitialized), owns_texture_(false), owns_fbo_(false) { SetDefaultTexParameters(); } bool GLFrame::Init(int width, int height) { // Make sure we haven't been initialized already if (width_ == 0 && height_ == 0) { InitDimensions(width, height); return true; } return false; } bool GLFrame::InitWithTexture(GLint texture_id, int width, int height) { texture_id_ = texture_id; texture_state_ = glIsTexture(texture_id) ? kStateComplete : kStateGenerated; InitDimensions(width, height); return true; } bool GLFrame::InitWithFbo(GLint fbo_id, int width, int height) { fbo_id_ = fbo_id; fbo_state_ = glIsFramebuffer(fbo_id) ? kStateComplete : kStateGenerated; texture_state_ = kStateUnmanaged; InitDimensions(width, height); return true; } bool GLFrame::InitWithExternalTexture() { texture_target_ = GL_TEXTURE_EXTERNAL_OES; width_ = 0; height_ = 0; return GenerateTextureName(); } void GLFrame::InitDimensions(int width, int height) { width_ = width; height_ = height; vp_width_ = width; vp_height_ = height; } GLFrame::~GLFrame() { // Delete texture if (owns_texture_) { // Bind FBO so that texture is unbound from it during deletion if (fbo_state_ == kStateComplete) { glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); } glDeleteTextures(1, &texture_id_); } // Delete FBO if (owns_fbo_) { glDeleteFramebuffers(1, &fbo_id_); } } bool GLFrame::GenerateMipMap() { if (FocusTexture()) { glGenerateMipmap(GL_TEXTURE_2D); return !GLEnv::CheckGLError("Generating MipMap!"); } return false; } bool GLFrame::SetTextureParameter(GLenum pname, GLint value) { if (value != tex_params_[pname]) { if (FocusTexture()) { glTexParameteri(GL_TEXTURE_2D, pname, value); if (!GLEnv::CheckGLError("Setting texture parameter!")) { tex_params_[pname] = value; return true; } } } else { return true; } return false; } bool GLFrame::UpdateTexParameters() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, tex_params_[GL_TEXTURE_MAG_FILTER]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tex_params_[GL_TEXTURE_MIN_FILTER]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tex_params_[GL_TEXTURE_WRAP_S]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tex_params_[GL_TEXTURE_WRAP_T]); return !GLEnv::CheckGLError("Resetting texture parameters!"); } bool GLFrame::TexParametersModifed() { return tex_params_[GL_TEXTURE_MAG_FILTER] != GL_LINEAR || tex_params_[GL_TEXTURE_MIN_FILTER] != GL_LINEAR || tex_params_[GL_TEXTURE_WRAP_S] != GL_CLAMP_TO_EDGE || tex_params_[GL_TEXTURE_WRAP_T] != GL_CLAMP_TO_EDGE; } void GLFrame::SetDefaultTexParameters() { tex_params_[GL_TEXTURE_MAG_FILTER] = GL_LINEAR; tex_params_[GL_TEXTURE_MIN_FILTER] = GL_LINEAR; tex_params_[GL_TEXTURE_WRAP_S] = GL_CLAMP_TO_EDGE; tex_params_[GL_TEXTURE_WRAP_T] = GL_CLAMP_TO_EDGE; } bool GLFrame::ResetTexParameters() { if (TexParametersModifed()) { if (BindTexture()) { SetDefaultTexParameters(); return UpdateTexParameters(); } return false; } return true; } bool GLFrame::CopyDataTo(uint8_t* buffer, int size) { return (size >= Size()) ? CopyPixelsTo(buffer) : false; } bool GLFrame::CopyPixelsTo(uint8_t* buffer) { // Use one of the pixel reading methods below, ordered from most // efficient to least efficient. if (fbo_state_ == kStateComplete) return ReadFboPixels(buffer); else if (texture_state_ == kStateComplete) return ReadTexturePixels(buffer); else return false; } bool GLFrame::WriteData(const uint8_t* data, int data_size) { return (data_size == Size()) ? UploadTexturePixels(data) : false; } bool GLFrame::SetViewport(int x, int y, int width, int height) { vp_x_ = x; vp_y_ = y; vp_width_ = width; vp_height_ = height; return true; } GLFrame* GLFrame::Clone() const { GLFrame* target = new GLFrame(gl_env_); target->Init(width_, height_); target->CopyPixelsFrom(this); return target; } bool GLFrame::CopyPixelsFrom(const GLFrame* frame) { if (frame == this) { return true; } else if (frame && frame->width_ == width_ && frame->height_ == height_) { std::vector sources; sources.push_back(frame); GetIdentity()->Process(sources, this); return true; } return false; } int GLFrame::Size() const { return width_ * height_ * 4; } ShaderProgram* GLFrame::GetIdentity() const { ShaderProgram* stored_shader = gl_env_->ShaderWithKey(kIdentityShaderKey); if (!stored_shader) { stored_shader = ShaderProgram::CreateIdentity(gl_env_); gl_env_->AttachShader(kIdentityShaderKey, stored_shader); } return stored_shader; } bool GLFrame::BindFrameBuffer() const { // Bind the FBO glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); if (GLEnv::CheckGLError("FBO Binding")) return false; // Set viewport glViewport(vp_x_, vp_y_, vp_width_, vp_height_); if (GLEnv::CheckGLError("ViewPort Setup")) return false; return true; } bool GLFrame::FocusFrameBuffer() { // Create texture backing if necessary if (texture_state_ == kStateUninitialized) { if (!GenerateTextureName()) return false; } // Create and bind FBO to texture if necessary if (fbo_state_ != kStateComplete) { if (!GenerateFboName() || !AttachTextureToFbo()) return false; } // And bind it. return BindFrameBuffer(); } bool GLFrame::BindTexture() const { glBindTexture(GL_TEXTURE_2D, texture_id_); return !GLEnv::CheckGLError("Texture Binding"); } GLuint GLFrame::GetTextureId() const { return texture_id_; } // Returns the held FBO id. Only call this if the GLFrame holds an FBO. You // can check this by calling HoldsFbo(). GLuint GLFrame::GetFboId() const { return fbo_id_; } bool GLFrame::FocusTexture() { // Make sure we have a texture if (!GenerateTextureName()) return false; // Bind the texture if (!BindTexture()) return false; return !GLEnv::CheckGLError("Texture Binding"); } bool GLFrame::GenerateTextureName() { if (texture_state_ == kStateUninitialized) { // Make sure texture not in use already if (glIsTexture(texture_id_)) { ALOGE("GLFrame: Cannot generate texture id %d, as it is in use already!", texture_id_); return false; } // Generate the texture glGenTextures (1, &texture_id_); if (GLEnv::CheckGLError("Texture Generation")) return false; texture_state_ = kStateGenerated; owns_texture_ = true; } return true; } bool GLFrame::AllocateTexture() { // Allocate or re-allocate (if texture was deleted externally). if (texture_state_ == kStateGenerated || TextureWasDeleted()) { LOG_FRAME("GLFrame: Allocating texture: %d", texture_id_); glBindTexture(GL_TEXTURE_2D, texture_id_); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); if (!GLEnv::CheckGLError("Texture Allocation")) { UpdateTexParameters(); texture_state_ = kStateComplete; } } return texture_state_ == kStateComplete; } bool GLFrame::TextureWasDeleted() const { return texture_state_ == kStateComplete && !glIsTexture(texture_id_); } bool GLFrame::GenerateFboName() { if (fbo_state_ == kStateUninitialized) { // Make sure FBO not in use already if (glIsFramebuffer(fbo_id_)) { ALOGE("GLFrame: Cannot generate FBO id %d, as it is in use already!", fbo_id_); return false; } // Create FBO glGenFramebuffers(1, &fbo_id_); if (GLEnv::CheckGLError("FBO Generation")) return false; fbo_state_ = kStateGenerated; owns_fbo_ = true; } return true; } bool GLFrame::ReadFboPixels(uint8_t* pixels) const { if (fbo_state_ == kStateComplete) { BindFrameBuffer(); glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, pixels); return !GLEnv::CheckGLError("FBO Pixel Readout"); } return false; } bool GLFrame::ReadTexturePixels(uint8_t* pixels) const { // Read pixels from texture if we do not have an FBO // NOTE: OpenGL ES does NOT support glGetTexImage() for reading out texture // data. The only way for us to get texture data is to create a new FBO and // render the current texture frame into it. As this is quite inefficient, // and unnecessary (this can only happen if the user is reading out data // that was just set, and not run through a filter), we warn the user about // this here. ALOGW("Warning: Reading pixel data from unfiltered GL frame. This is highly " "inefficient. Please consider using your original pixel buffer " "instead!"); // Create source frame set (unfortunately this requires an ugly const-cast, // as we need to wrap ourselves in a frame-set. Still, as this set is used // as input only, we are certain we will not be modified). std::vector sources; sources.push_back(this); // Create target frame GLFrame target(gl_env_); target.Init(width_, height_); // Render the texture to the target GetIdentity()->Process(sources, &target); // Get the pixel data return target.ReadFboPixels(pixels); } bool GLFrame::AttachTextureToFbo() { // Check FBO and texture state. We do not do anything if we are not managing the texture. if (fbo_state_ == kStateComplete || texture_state_ == kStateUnmanaged) { return true; } else if (fbo_state_ != kStateGenerated) { ALOGE("Attempting to attach texture to FBO with no FBO in place!"); return false; } // If texture has been generated, make sure it is allocated. if (!AllocateTexture()) return false; // Bind the frame buffer, and check if we it is already bound glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); // Bind the texture to the frame buffer LOG_FRAME("Attaching tex %d w %d h %d to fbo %d", texture_id_, width_, height_, fbo_id_); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id_, 0); // Cleanup glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); if (GLEnv::CheckGLError("Texture Binding to FBO")) return false; else fbo_state_ = kStateComplete; return true; } bool GLFrame::ReattachTextureToFbo() { return (fbo_state_ == kStateGenerated) ? AttachTextureToFbo() : true; } bool GLFrame::DetachTextureFromFbo() { if (fbo_state_ == kStateComplete && texture_state_ == kStateComplete) { LOG_FRAME("Detaching tex %d w %d h %d from fbo %d", texture_id_, width_, height_, fbo_id_); glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); if (GLEnv::CheckGLError("Detaching texture to FBO")) return false; else fbo_state_ = kStateGenerated; } return true; } bool GLFrame::UploadTexturePixels(const uint8_t* pixels) { // Bind the texture object FocusTexture(); // Load mipmap level 0 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Set the user specified texture parameters UpdateTexParameters(); if (GLEnv::CheckGLError("Texture Pixel Upload")) return false; texture_state_ = kStateComplete; return true; } } // namespace filterfw } // namespace android