1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "base/logging.h" 18 19 #include "core/gl_env.h" 20 #include "core/gl_frame.h" 21 #include "core/shader_program.h" 22 23 #include <vector> 24 25 namespace android { 26 namespace filterfw { 27 28 static const int kIdentityShaderKey = 1; 29 30 // 31 // A GLFrame stores pixel data on the GPU. It uses two kinds of GL data 32 // containers for this: Textures and Frame Buffer Objects (FBOs). Textures are 33 // used whenever pixel data needs to be read into a shader or the host program, 34 // and when pixel data is uploaded to a GLFrame. The FBO is used as a rendering 35 // target for shaders. 36 // 37 38 GLFrame::GLFrame(GLEnv* gl_env) 39 : gl_env_(gl_env), 40 width_(0), 41 height_(0), 42 vp_x_(0), 43 vp_y_(0), 44 vp_width_(0), 45 vp_height_(0), 46 texture_id_(0), 47 fbo_id_(0), 48 texture_target_(GL_TEXTURE_2D), 49 texture_state_(kStateUninitialized), 50 fbo_state_(kStateUninitialized), 51 owns_texture_(false), 52 owns_fbo_(false) { 53 SetDefaultTexParameters(); 54 } 55 56 bool GLFrame::Init(int width, int height) { 57 // Make sure we haven't been initialized already 58 if (width_ == 0 && height_ == 0) { 59 InitDimensions(width, height); 60 return true; 61 } 62 return false; 63 } 64 65 bool GLFrame::InitWithTexture(GLint texture_id, int width, int height) { 66 texture_id_ = texture_id; 67 texture_state_ = glIsTexture(texture_id) ? kStateComplete : kStateGenerated; 68 InitDimensions(width, height); 69 return true; 70 } 71 72 bool GLFrame::InitWithFbo(GLint fbo_id, int width, int height) { 73 fbo_id_ = fbo_id; 74 fbo_state_ = glIsFramebuffer(fbo_id) ? kStateComplete : kStateGenerated; 75 texture_state_ = kStateUnmanaged; 76 InitDimensions(width, height); 77 return true; 78 } 79 80 bool GLFrame::InitWithExternalTexture() { 81 texture_target_ = GL_TEXTURE_EXTERNAL_OES; 82 width_ = 0; 83 height_ = 0; 84 return GenerateTextureName(); 85 } 86 87 void GLFrame::InitDimensions(int width, int height) { 88 width_ = width; 89 height_ = height; 90 vp_width_ = width; 91 vp_height_ = height; 92 } 93 94 GLFrame::~GLFrame() { 95 // Delete texture 96 if (owns_texture_) { 97 // Bind FBO so that texture is unbound from it during deletion 98 if (fbo_state_ == kStateComplete) { 99 glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); 100 } 101 glDeleteTextures(1, &texture_id_); 102 } 103 104 // Delete FBO 105 if (owns_fbo_) { 106 glDeleteFramebuffers(1, &fbo_id_); 107 } 108 } 109 110 bool GLFrame::GenerateMipMap() { 111 if (FocusTexture()) { 112 glGenerateMipmap(GL_TEXTURE_2D); 113 return !GLEnv::CheckGLError("Generating MipMap!"); 114 } 115 return false; 116 } 117 118 bool GLFrame::SetTextureParameter(GLenum pname, GLint value) { 119 if (value != tex_params_[pname]) { 120 if (FocusTexture()) { 121 glTexParameteri(GL_TEXTURE_2D, pname, value); 122 if (!GLEnv::CheckGLError("Setting texture parameter!")) { 123 tex_params_[pname] = value; 124 return true; 125 } 126 } 127 } else { 128 return true; 129 } 130 return false; 131 } 132 133 bool GLFrame::UpdateTexParameters() { 134 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, tex_params_[GL_TEXTURE_MAG_FILTER]); 135 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tex_params_[GL_TEXTURE_MIN_FILTER]); 136 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tex_params_[GL_TEXTURE_WRAP_S]); 137 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tex_params_[GL_TEXTURE_WRAP_T]); 138 return !GLEnv::CheckGLError("Resetting texture parameters!"); 139 } 140 141 bool GLFrame::TexParametersModifed() { 142 return tex_params_[GL_TEXTURE_MAG_FILTER] != GL_LINEAR 143 || tex_params_[GL_TEXTURE_MIN_FILTER] != GL_LINEAR 144 || tex_params_[GL_TEXTURE_WRAP_S] != GL_CLAMP_TO_EDGE 145 || tex_params_[GL_TEXTURE_WRAP_T] != GL_CLAMP_TO_EDGE; 146 } 147 148 void GLFrame::SetDefaultTexParameters() { 149 tex_params_[GL_TEXTURE_MAG_FILTER] = GL_LINEAR; 150 tex_params_[GL_TEXTURE_MIN_FILTER] = GL_LINEAR; 151 tex_params_[GL_TEXTURE_WRAP_S] = GL_CLAMP_TO_EDGE; 152 tex_params_[GL_TEXTURE_WRAP_T] = GL_CLAMP_TO_EDGE; 153 } 154 155 bool GLFrame::ResetTexParameters() { 156 if (TexParametersModifed()) { 157 if (BindTexture()) { 158 SetDefaultTexParameters(); 159 return UpdateTexParameters(); 160 } 161 return false; 162 } 163 return true; 164 } 165 166 bool GLFrame::CopyDataTo(uint8_t* buffer, int size) { 167 return (size >= Size()) 168 ? CopyPixelsTo(buffer) 169 : false; 170 } 171 172 bool GLFrame::CopyPixelsTo(uint8_t* buffer) { 173 // Use one of the pixel reading methods below, ordered from most 174 // efficient to least efficient. 175 if (fbo_state_ == kStateComplete) 176 return ReadFboPixels(buffer); 177 else if (texture_state_ == kStateComplete) 178 return ReadTexturePixels(buffer); 179 else 180 return false; 181 } 182 183 bool GLFrame::WriteData(const uint8_t* data, int data_size) { 184 return (data_size == Size()) ? UploadTexturePixels(data) : false; 185 } 186 187 bool GLFrame::SetViewport(int x, int y, int width, int height) { 188 vp_x_ = x; 189 vp_y_ = y; 190 vp_width_ = width; 191 vp_height_ = height; 192 return true; 193 } 194 195 GLFrame* GLFrame::Clone() const { 196 GLFrame* target = new GLFrame(gl_env_); 197 target->Init(width_, height_); 198 target->CopyPixelsFrom(this); 199 return target; 200 } 201 202 bool GLFrame::CopyPixelsFrom(const GLFrame* frame) { 203 if (frame == this) { 204 return true; 205 } else if (frame && frame->width_ == width_ && frame->height_ == height_) { 206 std::vector<const GLFrame*> sources; 207 sources.push_back(frame); 208 GetIdentity()->Process(sources, this); 209 return true; 210 } 211 return false; 212 } 213 214 int GLFrame::Size() const { 215 return width_ * height_ * 4; 216 } 217 218 ShaderProgram* GLFrame::GetIdentity() const { 219 ShaderProgram* stored_shader = gl_env_->ShaderWithKey(kIdentityShaderKey); 220 if (!stored_shader) { 221 stored_shader = ShaderProgram::CreateIdentity(gl_env_); 222 gl_env_->AttachShader(kIdentityShaderKey, stored_shader); 223 } 224 return stored_shader; 225 } 226 227 bool GLFrame::BindFrameBuffer() const { 228 // Bind the FBO 229 glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); 230 if (GLEnv::CheckGLError("FBO Binding")) return false; 231 232 // Set viewport 233 glViewport(vp_x_, vp_y_, vp_width_, vp_height_); 234 if (GLEnv::CheckGLError("ViewPort Setup")) return false; 235 236 return true; 237 } 238 239 bool GLFrame::FocusFrameBuffer() { 240 // Create texture backing if necessary 241 if (texture_state_ == kStateUninitialized) { 242 if (!GenerateTextureName()) 243 return false; 244 } 245 246 // Create and bind FBO to texture if necessary 247 if (fbo_state_ != kStateComplete) { 248 if (!GenerateFboName() || !AttachTextureToFbo()) 249 return false; 250 } 251 252 // And bind it. 253 return BindFrameBuffer(); 254 } 255 256 bool GLFrame::BindTexture() const { 257 glBindTexture(GL_TEXTURE_2D, texture_id_); 258 return !GLEnv::CheckGLError("Texture Binding"); 259 } 260 261 GLuint GLFrame::GetTextureId() const { 262 return texture_id_; 263 } 264 265 // Returns the held FBO id. Only call this if the GLFrame holds an FBO. You 266 // can check this by calling HoldsFbo(). 267 GLuint GLFrame::GetFboId() const { 268 return fbo_id_; 269 } 270 271 bool GLFrame::FocusTexture() { 272 // Make sure we have a texture 273 if (!GenerateTextureName()) 274 return false; 275 276 // Bind the texture 277 if (!BindTexture()) 278 return false; 279 280 return !GLEnv::CheckGLError("Texture Binding"); 281 } 282 283 bool GLFrame::GenerateTextureName() { 284 if (texture_state_ == kStateUninitialized) { 285 // Make sure texture not in use already 286 if (glIsTexture(texture_id_)) { 287 ALOGE("GLFrame: Cannot generate texture id %d, as it is in use already!", texture_id_); 288 return false; 289 } 290 291 // Generate the texture 292 glGenTextures (1, &texture_id_); 293 if (GLEnv::CheckGLError("Texture Generation")) 294 return false; 295 texture_state_ = kStateGenerated; 296 owns_texture_ = true; 297 } 298 299 return true; 300 } 301 302 bool GLFrame::AllocateTexture() { 303 // Allocate or re-allocate (if texture was deleted externally). 304 if (texture_state_ == kStateGenerated || TextureWasDeleted()) { 305 LOG_FRAME("GLFrame: Allocating texture: %d", texture_id_); 306 glBindTexture(GL_TEXTURE_2D, texture_id_); 307 glTexImage2D(GL_TEXTURE_2D, 308 0, 309 GL_RGBA, 310 width_, 311 height_, 312 0, 313 GL_RGBA, 314 GL_UNSIGNED_BYTE, 315 NULL); 316 if (!GLEnv::CheckGLError("Texture Allocation")) { 317 UpdateTexParameters(); 318 texture_state_ = kStateComplete; 319 } 320 } 321 return texture_state_ == kStateComplete; 322 } 323 324 bool GLFrame::TextureWasDeleted() const { 325 return texture_state_ == kStateComplete && !glIsTexture(texture_id_); 326 } 327 328 bool GLFrame::GenerateFboName() { 329 if (fbo_state_ == kStateUninitialized) { 330 // Make sure FBO not in use already 331 if (glIsFramebuffer(fbo_id_)) { 332 ALOGE("GLFrame: Cannot generate FBO id %d, as it is in use already!", fbo_id_); 333 return false; 334 } 335 336 // Create FBO 337 glGenFramebuffers(1, &fbo_id_); 338 if (GLEnv::CheckGLError("FBO Generation")) 339 return false; 340 fbo_state_ = kStateGenerated; 341 owns_fbo_ = true; 342 } 343 344 return true; 345 } 346 347 bool GLFrame::ReadFboPixels(uint8_t* pixels) const { 348 if (fbo_state_ == kStateComplete) { 349 BindFrameBuffer(); 350 glReadPixels(0, 351 0, 352 width_, 353 height_, 354 GL_RGBA, 355 GL_UNSIGNED_BYTE, 356 pixels); 357 return !GLEnv::CheckGLError("FBO Pixel Readout"); 358 } 359 return false; 360 } 361 362 bool GLFrame::ReadTexturePixels(uint8_t* pixels) const { 363 // Read pixels from texture if we do not have an FBO 364 // NOTE: OpenGL ES does NOT support glGetTexImage() for reading out texture 365 // data. The only way for us to get texture data is to create a new FBO and 366 // render the current texture frame into it. As this is quite inefficient, 367 // and unnecessary (this can only happen if the user is reading out data 368 // that was just set, and not run through a filter), we warn the user about 369 // this here. 370 ALOGW("Warning: Reading pixel data from unfiltered GL frame. This is highly " 371 "inefficient. Please consider using your original pixel buffer " 372 "instead!"); 373 374 // Create source frame set (unfortunately this requires an ugly const-cast, 375 // as we need to wrap ourselves in a frame-set. Still, as this set is used 376 // as input only, we are certain we will not be modified). 377 std::vector<const GLFrame*> sources; 378 sources.push_back(this); 379 380 // Create target frame 381 GLFrame target(gl_env_); 382 target.Init(width_, height_); 383 384 // Render the texture to the target 385 GetIdentity()->Process(sources, &target); 386 387 // Get the pixel data 388 return target.ReadFboPixels(pixels); 389 } 390 391 bool GLFrame::AttachTextureToFbo() { 392 // Check FBO and texture state. We do not do anything if we are not managing the texture. 393 if (fbo_state_ == kStateComplete || texture_state_ == kStateUnmanaged) { 394 return true; 395 } else if (fbo_state_ != kStateGenerated) { 396 ALOGE("Attempting to attach texture to FBO with no FBO in place!"); 397 return false; 398 } 399 400 // If texture has been generated, make sure it is allocated. 401 if (!AllocateTexture()) 402 return false; 403 404 // Bind the frame buffer, and check if we it is already bound 405 glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); 406 407 // Bind the texture to the frame buffer 408 LOG_FRAME("Attaching tex %d w %d h %d to fbo %d", texture_id_, width_, height_, fbo_id_); 409 glFramebufferTexture2D(GL_FRAMEBUFFER, 410 GL_COLOR_ATTACHMENT0, 411 GL_TEXTURE_2D, 412 texture_id_, 413 0); 414 415 // Cleanup 416 glBindTexture(GL_TEXTURE_2D, 0); 417 glBindFramebuffer(GL_FRAMEBUFFER, 0); 418 419 if (GLEnv::CheckGLError("Texture Binding to FBO")) 420 return false; 421 else 422 fbo_state_ = kStateComplete; 423 424 return true; 425 } 426 427 bool GLFrame::ReattachTextureToFbo() { 428 return (fbo_state_ == kStateGenerated) ? AttachTextureToFbo() : true; 429 } 430 431 bool GLFrame::DetachTextureFromFbo() { 432 if (fbo_state_ == kStateComplete && texture_state_ == kStateComplete) { 433 LOG_FRAME("Detaching tex %d w %d h %d from fbo %d", texture_id_, width_, height_, fbo_id_); 434 glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); 435 glFramebufferTexture2D(GL_FRAMEBUFFER, 436 GL_COLOR_ATTACHMENT0, 437 GL_TEXTURE_2D, 438 0, 439 0); 440 if (GLEnv::CheckGLError("Detaching texture to FBO")) 441 return false; 442 else 443 fbo_state_ = kStateGenerated; 444 } 445 return true; 446 } 447 448 bool GLFrame::UploadTexturePixels(const uint8_t* pixels) { 449 // Bind the texture object 450 FocusTexture(); 451 452 // Load mipmap level 0 453 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 454 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 455 456 // Set the user specified texture parameters 457 UpdateTexParameters(); 458 459 if (GLEnv::CheckGLError("Texture Pixel Upload")) 460 return false; 461 462 texture_state_ = kStateComplete; 463 return true; 464 } 465 466 } // namespace filterfw 467 } // namespace android 468