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
25namespace android {
26namespace filterfw {
27
28static 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
38GLFrame::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
56bool 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
65bool 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
72bool 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
80bool GLFrame::InitWithExternalTexture() {
81  texture_target_ = GL_TEXTURE_EXTERNAL_OES;
82  width_ = 0;
83  height_ = 0;
84  return GenerateTextureName();
85}
86
87void GLFrame::InitDimensions(int width, int height) {
88  width_ = width;
89  height_ = height;
90  vp_width_ = width;
91  vp_height_ = height;
92}
93
94GLFrame::~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
110bool GLFrame::GenerateMipMap() {
111  if (FocusTexture()) {
112    glGenerateMipmap(GL_TEXTURE_2D);
113    return !GLEnv::CheckGLError("Generating MipMap!");
114  }
115  return false;
116}
117
118bool 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
133bool 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
141bool 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
148void 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
155bool GLFrame::ResetTexParameters() {
156  if (TexParametersModifed()) {
157    if (BindTexture()) {
158      SetDefaultTexParameters();
159      return UpdateTexParameters();
160    }
161    return false;
162  }
163  return true;
164}
165
166bool GLFrame::CopyDataTo(uint8_t* buffer, int size) {
167  return (size >= Size())
168    ? CopyPixelsTo(buffer)
169    : false;
170}
171
172bool 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
183bool GLFrame::WriteData(const uint8_t* data, int data_size) {
184  return (data_size == Size()) ? UploadTexturePixels(data) : false;
185}
186
187bool 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
195GLFrame* GLFrame::Clone() const {
196  GLFrame* target = new GLFrame(gl_env_);
197  target->Init(width_, height_);
198  target->CopyPixelsFrom(this);
199  return target;
200}
201
202bool 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
214int GLFrame::Size() const {
215  return width_ * height_ * 4;
216}
217
218ShaderProgram* 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
227bool 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
239bool 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
256bool GLFrame::BindTexture() const {
257  glBindTexture(GL_TEXTURE_2D, texture_id_);
258  return !GLEnv::CheckGLError("Texture Binding");
259}
260
261GLuint 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().
267GLuint GLFrame::GetFboId() const {
268  return fbo_id_;
269}
270
271bool 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
283bool 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
302bool 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
324bool GLFrame::TextureWasDeleted() const {
325  return texture_state_ == kStateComplete && !glIsTexture(texture_id_);
326}
327
328bool 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
347bool 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
362bool 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
391bool 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
427bool GLFrame::ReattachTextureToFbo() {
428  return (fbo_state_ == kStateGenerated) ? AttachTextureToFbo() : true;
429}
430
431bool 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
448bool 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