gl_frame.cpp revision 776102d45a18a5df53d2ec76c5d93f20b3e99da1
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  InitDimensions(width, height);
76  return true;
77}
78
79bool GLFrame::InitWithExternalTexture() {
80  texture_target_ = GL_TEXTURE_EXTERNAL_OES;
81  width_ = 0;
82  height_ = 0;
83  return CreateTexture();
84}
85
86void GLFrame::InitDimensions(int width, int height) {
87  width_ = width;
88  height_ = height;
89  vp_width_ = width;
90  vp_height_ = height;
91}
92
93GLFrame::~GLFrame() {
94  // Delete texture
95  if (owns_texture_) {
96    // Bind FBO so that texture is unbound from it during deletion
97    if (fbo_state_ == kStateComplete) {
98      glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
99    }
100    glDeleteTextures(1, &texture_id_);
101  }
102
103  // Delete FBO
104  if (owns_fbo_) {
105    glDeleteFramebuffers(1, &fbo_id_);
106  }
107}
108
109bool GLFrame::GenerateMipMap() {
110  if (FocusTexture()) {
111    glGenerateMipmap(GL_TEXTURE_2D);
112    return !GLEnv::CheckGLError("Generating MipMap!");
113  }
114  return false;
115}
116
117bool GLFrame::SetTextureParameter(GLenum pname, GLint value) {
118  if (value != tex_params_[pname]) {
119    if (FocusTexture()) {
120      glTexParameteri(GL_TEXTURE_2D, pname, value);
121      if (!GLEnv::CheckGLError("Setting texture parameter!")) {
122        tex_params_[pname] = value;
123        return true;
124      }
125    }
126  } else {
127    return true;
128  }
129  return false;
130}
131
132bool GLFrame::UpdateTexParameters() {
133  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, tex_params_[GL_TEXTURE_MAG_FILTER]);
134  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tex_params_[GL_TEXTURE_MIN_FILTER]);
135  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tex_params_[GL_TEXTURE_WRAP_S]);
136  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tex_params_[GL_TEXTURE_WRAP_T]);
137  return !GLEnv::CheckGLError("Resetting texture parameters!");
138}
139
140bool GLFrame::TexParametersModifed() {
141  return tex_params_[GL_TEXTURE_MAG_FILTER] != GL_LINEAR
142    ||   tex_params_[GL_TEXTURE_MIN_FILTER] != GL_LINEAR
143    ||   tex_params_[GL_TEXTURE_WRAP_S] != GL_CLAMP_TO_EDGE
144    ||   tex_params_[GL_TEXTURE_WRAP_T] != GL_CLAMP_TO_EDGE;
145}
146
147void GLFrame::SetDefaultTexParameters() {
148  tex_params_[GL_TEXTURE_MAG_FILTER] = GL_LINEAR;
149  tex_params_[GL_TEXTURE_MIN_FILTER] = GL_LINEAR;
150  tex_params_[GL_TEXTURE_WRAP_S] = GL_CLAMP_TO_EDGE;
151  tex_params_[GL_TEXTURE_WRAP_T] = GL_CLAMP_TO_EDGE;
152}
153
154bool GLFrame::ResetTexParameters() {
155  if (TexParametersModifed()) {
156    if (BindTexture()) {
157      SetDefaultTexParameters();
158      return UpdateTexParameters();
159    }
160    return false;
161  }
162  return true;
163}
164
165bool GLFrame::CopyDataTo(uint8_t* buffer, int size) {
166  return (size >= Size())
167    ? CopyPixelsTo(buffer)
168    : false;
169}
170
171bool GLFrame::CopyPixelsTo(uint8_t* buffer) {
172  if (texture_state_ == kStateComplete)
173    return ReadTexturePixels(buffer);
174  else if (fbo_state_ == kStateComplete)
175    return ReadFboPixels(buffer);
176  else
177    return false;
178}
179
180bool GLFrame::WriteData(const uint8_t* data, int data_size) {
181  return (data_size == Size()) ? UploadTexturePixels(data) : false;
182}
183
184bool GLFrame::SetViewport(int x, int y, int width, int height) {
185  vp_x_ = x;
186  vp_y_ = y;
187  vp_width_ = width;
188  vp_height_ = height;
189  return true;
190}
191
192GLFrame* GLFrame::Clone() const {
193  GLFrame* target = new GLFrame(gl_env_);
194  target->Init(width_, height_);
195  target->CopyPixelsFrom(this);
196  return target;
197}
198
199bool GLFrame::CopyPixelsFrom(const GLFrame* frame) {
200  if (frame == this) {
201    return true;
202  } else if (frame && frame->width_ == width_ && frame->height_ == height_) {
203    std::vector<const GLFrame*> sources;
204    sources.push_back(frame);
205    GetIdentity()->Process(sources, this);
206    return true;
207  }
208  return false;
209}
210
211int GLFrame::Size() const {
212  return width_ * height_ * 4;
213}
214
215ShaderProgram* GLFrame::GetIdentity() const {
216  ShaderProgram* stored_shader = gl_env_->ShaderWithKey(kIdentityShaderKey);
217  if (!stored_shader) {
218    stored_shader = ShaderProgram::CreateIdentity(gl_env_);
219    gl_env_->AttachShader(kIdentityShaderKey, stored_shader);
220  }
221  return stored_shader;
222}
223
224bool GLFrame::BindFrameBuffer() const {
225  // Bind the FBO
226  glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
227  if (GLEnv::CheckGLError("FBO Binding")) return false;
228
229  // Set viewport
230  glViewport(vp_x_, vp_y_, vp_width_, vp_height_);
231  if (GLEnv::CheckGLError("ViewPort Setup")) return false;
232
233  return true;
234}
235
236bool GLFrame::FocusFrameBuffer() {
237  // Create texture backing if necessary
238  if (texture_state_ == kStateUninitialized) {
239    if (!CreateTexture())
240      return false;
241  }
242
243  // Create and bind FBO to texture if necessary
244  if (fbo_state_ != kStateComplete) {
245    if (!CreateFBO() || !AttachTextureToFBO())
246      return false;
247  }
248
249  // And bind it.
250  return BindFrameBuffer();
251}
252
253bool GLFrame::BindTexture() const {
254  glBindTexture(GL_TEXTURE_2D, texture_id_);
255  return !GLEnv::CheckGLError("Texture Binding");
256}
257
258GLuint GLFrame::GetTextureId() const {
259  return texture_id_;
260}
261
262// Returns the held FBO id. Only call this if the GLFrame holds an FBO. You
263// can check this by calling HoldsFbo().
264GLuint GLFrame::GetFboId() const {
265  return fbo_id_;
266}
267
268bool GLFrame::FocusTexture() {
269  // Make sure we have a texture
270  if (!CreateTexture())
271    return false;
272
273  // Bind the texture
274  if (!BindTexture())
275    return false;
276
277  return !GLEnv::CheckGLError("Texture Binding");
278}
279
280bool GLFrame::CreateTexture() {
281  if (texture_state_ == kStateUninitialized) {
282    // Make sure texture not in use already
283    if (glIsTexture(texture_id_)) {
284      LOGE("GLFrame: Cannot generate texture id %d, as it is in use already!", texture_id_);
285      return false;
286    }
287
288    // Generate the texture
289    glGenTextures (1, &texture_id_);
290    if (GLEnv::CheckGLError("Texture Generation"))
291      return false;
292    texture_state_ = kStateGenerated;
293    owns_texture_ = true;
294  }
295
296  return true;
297}
298
299bool GLFrame::CreateFBO() {
300  if (fbo_state_ == kStateUninitialized) {
301    // Make sure FBO not in use already
302    if (glIsFramebuffer(fbo_id_)) {
303      LOGE("GLFrame: Cannot generate FBO id %d, as it is in use already!", fbo_id_);
304      return false;
305    }
306
307    // Create FBO
308    glGenFramebuffers(1, &fbo_id_);
309    if (GLEnv::CheckGLError("FBO Generation"))
310      return false;
311    fbo_state_ = kStateGenerated;
312    owns_fbo_ = true;
313  }
314
315  return true;
316}
317
318bool GLFrame::ReadFboPixels(uint8_t* pixels) const {
319  if (fbo_state_ == kStateComplete) {
320    BindFrameBuffer();
321    glReadPixels(0,
322                 0,
323                 width_,
324                 height_,
325                 GL_RGBA,
326                 GL_UNSIGNED_BYTE,
327                 pixels);
328    return !GLEnv::CheckGLError("FBO Pixel Readout");
329  }
330  return false;
331}
332
333bool GLFrame::ReadTexturePixels(uint8_t* pixels) const {
334  // Read pixels from texture if we do not have an FBO
335  // NOTE: OpenGL ES does NOT support glGetTexImage() for reading out texture
336  // data. The only way for us to get texture data is to create a new FBO and
337  // render the current texture frame into it. As this is quite inefficient,
338  // and unnecessary (this can only happen if the user is reading out data
339  // that was just set, and not run through a filter), we warn the user about
340  // this here.
341  LOGW("Warning: Reading pixel data from unfiltered GL frame. This is highly "
342       "inefficient. Please consider using your original pixel buffer "
343       "instead!");
344
345  // Create source frame set (unfortunately this requires an ugly const-cast,
346  // as we need to wrap ourselves in a frame-set. Still, as this set is used
347  // as input only, we are certain we will not be modified).
348  std::vector<const GLFrame*> sources;
349  sources.push_back(this);
350
351  // Create target frame
352  GLFrame target(gl_env_);
353  target.Init(width_, height_);
354
355  // Render the texture to the target
356  GetIdentity()->Process(sources, &target);
357
358  // Get the pixel data
359  return target.ReadFboPixels(pixels);
360}
361
362bool GLFrame::AttachTextureToFBO() {
363  // Check if we are already bound
364  if (fbo_state_ == kStateComplete)
365    return true;
366
367  // Otherwise check if the texture and fbo states are acceptable
368  if ((texture_state_ != kStateGenerated && texture_state_ != kStateComplete)
369  ||  (fbo_state_     != kStateGenerated))
370    return false;
371
372  // Bind the frame buffer, and check if we it is already bound
373  glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
374  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
375    // Bind texture
376    glBindTexture(GL_TEXTURE_2D, texture_id_);
377
378    // Set the texture format
379    glTexImage2D(GL_TEXTURE_2D,
380                 0,
381                 GL_RGBA,
382                 width_,
383                 height_,
384                 0,
385                 GL_RGBA,
386                 GL_UNSIGNED_BYTE,
387                 NULL);
388
389    // Set the user specified texture parameters
390    UpdateTexParameters();
391
392    // Bind the texture to the frame buffer
393    LOG_FRAME("Binding tex %d w %d h %d to fbo %d", texture_id_, width_, height_, fbo_id_);
394    glFramebufferTexture2D(GL_FRAMEBUFFER,
395                           GL_COLOR_ATTACHMENT0,
396                           GL_TEXTURE_2D,
397                           texture_id_,
398                           0);
399  }
400
401  // Cleanup
402  glBindTexture(GL_TEXTURE_2D, 0);
403  glBindFramebuffer(GL_FRAMEBUFFER, 0);
404
405  if (GLEnv::CheckGLError("Texture Binding to FBO"))
406    return false;
407
408  // FBO is now complete
409  fbo_state_ = kStateComplete;
410  return true;
411}
412
413bool GLFrame::UploadTexturePixels(const uint8_t* pixels) {
414  // Bind the texture object
415  FocusTexture();
416
417  // Load mipmap level 0
418  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_,
419               0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
420
421  // Set the user specified texture parameters
422  UpdateTexParameters();
423
424  if (GLEnv::CheckGLError("Texture Pixel Upload"))
425    return false;
426
427  texture_state_ = kStateComplete;
428  return true;
429}
430
431} // namespace filterfw
432} // namespace android
433