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 
GLFrame(GLEnv * gl_env)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 
Init(int width,int height)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 
InitWithTexture(GLint texture_id,int width,int height)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 
InitWithFbo(GLint fbo_id,int width,int height)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 
InitWithExternalTexture()80 bool GLFrame::InitWithExternalTexture() {
81   texture_target_ = GL_TEXTURE_EXTERNAL_OES;
82   width_ = 0;
83   height_ = 0;
84   return GenerateTextureName();
85 }
86 
InitDimensions(int width,int height)87 void GLFrame::InitDimensions(int width, int height) {
88   width_ = width;
89   height_ = height;
90   vp_width_ = width;
91   vp_height_ = height;
92 }
93 
~GLFrame()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 
GenerateMipMap()110 bool GLFrame::GenerateMipMap() {
111   if (FocusTexture()) {
112     glGenerateMipmap(GL_TEXTURE_2D);
113     return !GLEnv::CheckGLError("Generating MipMap!");
114   }
115   return false;
116 }
117 
SetTextureParameter(GLenum pname,GLint value)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 
UpdateTexParameters()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 
TexParametersModifed()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 
SetDefaultTexParameters()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 
ResetTexParameters()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 
CopyDataTo(uint8_t * buffer,int size)166 bool GLFrame::CopyDataTo(uint8_t* buffer, int size) {
167   return (size >= Size())
168     ? CopyPixelsTo(buffer)
169     : false;
170 }
171 
CopyPixelsTo(uint8_t * buffer)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 
WriteData(const uint8_t * data,int data_size)183 bool GLFrame::WriteData(const uint8_t* data, int data_size) {
184   return (data_size == Size()) ? UploadTexturePixels(data) : false;
185 }
186 
SetViewport(int x,int y,int width,int height)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 
Clone() const195 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 
CopyPixelsFrom(const GLFrame * frame)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 
Size() const214 int GLFrame::Size() const {
215   return width_ * height_ * 4;
216 }
217 
GetIdentity() const218 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 
BindFrameBuffer() const227 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 
FocusFrameBuffer()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 
BindTexture() const256 bool GLFrame::BindTexture() const {
257   glBindTexture(GL_TEXTURE_2D, texture_id_);
258   return !GLEnv::CheckGLError("Texture Binding");
259 }
260 
GetTextureId() const261 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().
GetFboId() const267 GLuint GLFrame::GetFboId() const {
268   return fbo_id_;
269 }
270 
FocusTexture()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 
GenerateTextureName()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 
AllocateTexture()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 
TextureWasDeleted() const324 bool GLFrame::TextureWasDeleted() const {
325   return texture_state_ == kStateComplete && !glIsTexture(texture_id_);
326 }
327 
GenerateFboName()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 
ReadFboPixels(uint8_t * pixels) const347 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 
ReadTexturePixels(uint8_t * pixels) const362 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 
AttachTextureToFbo()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 
ReattachTextureToFbo()427 bool GLFrame::ReattachTextureToFbo() {
428   return (fbo_state_ == kStateGenerated) ? AttachTextureToFbo() : true;
429 }
430 
DetachTextureFromFbo()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 
UploadTexturePixels(const uint8_t * pixels)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