C++程序  |  898行  |  25.71 KB

/*
* 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 "FrameBuffer.h"
#include "NativeSubWindow.h"
#include "FBConfig.h"
#include "EGLDispatch.h"
#include "GLDispatch.h"
#include "GL2Dispatch.h"
#include "ThreadInfo.h"
#include "TimeUtils.h"
#include <stdio.h>

FrameBuffer *FrameBuffer::s_theFrameBuffer = NULL;
HandleType FrameBuffer::s_nextHandle = 0;

#ifdef WITH_GLES2
static char* getGLES2ExtensionString(EGLDisplay p_dpy)
{
    EGLConfig config;
    EGLSurface surface;

    GLint configAttribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_NONE
    };

    int n;
    if (!s_egl.eglChooseConfig(p_dpy, configAttribs,
                               &config, 1, &n)) {
        return NULL;
    }

    EGLint pbufAttribs[] = {
        EGL_WIDTH, 1,
        EGL_HEIGHT, 1,
        EGL_NONE
    };

    surface = s_egl.eglCreatePbufferSurface(p_dpy, config, pbufAttribs);
    if (surface == EGL_NO_SURFACE) {
        return NULL;
    }

    GLint gl2ContextAttribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };

    EGLContext ctx = s_egl.eglCreateContext(p_dpy, config,
                                            EGL_NO_CONTEXT,
                                            gl2ContextAttribs);
    if (ctx == EGL_NO_CONTEXT) {
        s_egl.eglDestroySurface(p_dpy, surface);
        return NULL;
    }

    if (!s_egl.eglMakeCurrent(p_dpy, surface, surface, ctx)) {
        s_egl.eglDestroySurface(p_dpy, surface);
        s_egl.eglDestroyContext(p_dpy, ctx);
        return NULL;
    }

    // the string pointer may become invalid when the context is destroyed
    const char* s = (const char*)s_gl2.glGetString(GL_EXTENSIONS);
    char* extString = strdup(s ? s : "");

    s_egl.eglMakeCurrent(p_dpy, NULL, NULL, NULL);
    s_egl.eglDestroyContext(p_dpy, ctx);
    s_egl.eglDestroySurface(p_dpy, surface);

    return extString;
}
#endif

void FrameBuffer::finalize(){
    if(s_theFrameBuffer){
        s_theFrameBuffer->removeSubWindow();
        s_theFrameBuffer->m_colorbuffers.clear();
        s_theFrameBuffer->m_windows.clear();
        s_theFrameBuffer->m_contexts.clear();
        s_egl.eglMakeCurrent(s_theFrameBuffer->m_eglDisplay, NULL, NULL, NULL);
        s_egl.eglDestroyContext(s_theFrameBuffer->m_eglDisplay,s_theFrameBuffer->m_eglContext);
        s_egl.eglDestroyContext(s_theFrameBuffer->m_eglDisplay,s_theFrameBuffer->m_pbufContext);
        s_egl.eglDestroySurface(s_theFrameBuffer->m_eglDisplay,s_theFrameBuffer->m_pbufSurface);
        s_theFrameBuffer = NULL;
    }
}

bool FrameBuffer::initialize(int width, int height)
{
    if (s_theFrameBuffer != NULL) {
        return true;
    }

    //
    // allocate space for the FrameBuffer object
    //
    FrameBuffer *fb = new FrameBuffer(width, height);
    if (!fb) {
        ERR("Failed to create fb\n");
        return false;
    }

#ifdef WITH_GLES2
    //
    // Try to load GLES2 Plugin, not mandatory
    //
    if (getenv("ANDROID_NO_GLES2")) {
        fb->m_caps.hasGL2 = false;
    }
    else {
        fb->m_caps.hasGL2 = s_gl2_enabled;
    }
#else
    fb->m_caps.hasGL2 = false;
#endif

    //
    // Initialize backend EGL display
    //
    fb->m_eglDisplay = s_egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (fb->m_eglDisplay == EGL_NO_DISPLAY) {
        ERR("Failed to Initialize backend EGL display\n");
        delete fb;
        return false;
    }

    if (!s_egl.eglInitialize(fb->m_eglDisplay, &fb->m_caps.eglMajor, &fb->m_caps.eglMinor)) {
        ERR("Failed to eglInitialize\n");
        delete fb;
        return false;
    }

    DBG("egl: %d %d\n", fb->m_caps.eglMajor, fb->m_caps.eglMinor);
    s_egl.eglBindAPI(EGL_OPENGL_ES_API);

    //
    // if GLES2 plugin has loaded - try to make GLES2 context and
    // get GLES2 extension string
    //
    char* gl2Extensions = NULL;
#ifdef WITH_GLES2
    if (fb->m_caps.hasGL2) {
        gl2Extensions = getGLES2ExtensionString(fb->m_eglDisplay);
        if (!gl2Extensions) {
            // Could not create GLES2 context - drop GL2 capability
            fb->m_caps.hasGL2 = false;
        }
    }
#endif

    //
    // Create EGL context for framebuffer post rendering.
    //
#if 0
    GLint configAttribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
        EGL_NONE
    };
#else
    GLint configAttribs[] = {
        EGL_RED_SIZE, 1,
        EGL_GREEN_SIZE, 1,
        EGL_BLUE_SIZE, 1,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
        EGL_NONE
    };
#endif

    int n;
    if (!s_egl.eglChooseConfig(fb->m_eglDisplay, configAttribs,
                               &fb->m_eglConfig, 1, &n)) {
        ERR("Failed on eglChooseConfig\n");
        free(gl2Extensions);
        delete fb;
        return false;
    }

    GLint glContextAttribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 1,
        EGL_NONE
    };

    fb->m_eglContext = s_egl.eglCreateContext(fb->m_eglDisplay, fb->m_eglConfig,
                                              EGL_NO_CONTEXT,
                                              glContextAttribs);
    if (fb->m_eglContext == EGL_NO_CONTEXT) {
        printf("Failed to create Context 0x%x\n", s_egl.eglGetError());
        free(gl2Extensions);
        delete fb;
        return false;
    }

    //
    // Create another context which shares with the eglContext to be used
    // when we bind the pbuffer. That prevent switching drawable binding
    // back and forth on framebuffer context.
    // The main purpose of it is to solve a "blanking" behaviour we see on
    // on Mac platform when switching binded drawable for a context however
    // it is more efficient on other platforms as well.
    //
    fb->m_pbufContext = s_egl.eglCreateContext(fb->m_eglDisplay, fb->m_eglConfig,
                                               fb->m_eglContext,
                                               glContextAttribs);
    if (fb->m_pbufContext == EGL_NO_CONTEXT) {
        printf("Failed to create Pbuffer Context 0x%x\n", s_egl.eglGetError());
        free(gl2Extensions);
        delete fb;
        return false;
    }

    //
    // create a 1x1 pbuffer surface which will be used for binding
    // the FB context.
    // The FB output will go to a subwindow, if one exist.
    //
    EGLint pbufAttribs[] = {
        EGL_WIDTH, 1,
        EGL_HEIGHT, 1,
        EGL_NONE
    };

    fb->m_pbufSurface = s_egl.eglCreatePbufferSurface(fb->m_eglDisplay,
                                                  fb->m_eglConfig,
                                                  pbufAttribs);
    if (fb->m_pbufSurface == EGL_NO_SURFACE) {
        printf("Failed to create pbuf surface for FB 0x%x\n", s_egl.eglGetError());
        free(gl2Extensions);
        delete fb;
        return false;
    }

    // Make the context current
    if (!fb->bind_locked()) {
        ERR("Failed to make current\n");
        free(gl2Extensions);
        delete fb;
        return false;
    }

    //
    // Initilize framebuffer capabilities
    //
    const char *glExtensions = (const char *)s_gl.glGetString(GL_EXTENSIONS);
    bool has_gl_oes_image = false;
    if (glExtensions) {
        has_gl_oes_image = strstr(glExtensions, "GL_OES_EGL_image") != NULL;
    }

    if (fb->m_caps.hasGL2 && has_gl_oes_image) {
        has_gl_oes_image &= strstr(gl2Extensions, "GL_OES_EGL_image") != NULL;
    }
    free(gl2Extensions);
    gl2Extensions = NULL;

    const char *eglExtensions = s_egl.eglQueryString(fb->m_eglDisplay,
                                                     EGL_EXTENSIONS);

    if (eglExtensions && has_gl_oes_image) {
        fb->m_caps.has_eglimage_texture_2d =
             strstr(eglExtensions, "EGL_KHR_gl_texture_2D_image") != NULL;
        fb->m_caps.has_eglimage_renderbuffer =
             strstr(eglExtensions, "EGL_KHR_gl_renderbuffer_image") != NULL;
    }
    else {
        fb->m_caps.has_eglimage_texture_2d = false;
        fb->m_caps.has_eglimage_renderbuffer = false;
    }

    //
    // Fail initialization if not all of the following extensions
    // exist:
    //     EGL_KHR_gl_texture_2d_image
    //     GL_OES_EGL_IMAGE (by both GLES implementations [1 and 2])
    //
    if (!fb->m_caps.has_eglimage_texture_2d) {
        ERR("Failed: Missing egl_image related extension(s)\n");
        delete fb;
        return false;
    }

    //
    // Initialize set of configs
    //
    InitConfigStatus configStatus = FBConfig::initConfigList(fb);
    if (configStatus == INIT_CONFIG_FAILED) {
        ERR("Failed: Initialize set of configs\n");
        delete fb;
        return false;
    }

    //
    // Check that we have config for each GLES and GLES2
    //
    int nConfigs = FBConfig::getNumConfigs();
    int nGLConfigs = 0;
    int nGL2Configs = 0;
    for (int i=0; i<nConfigs; i++) {
        GLint rtype = FBConfig::get(i)->getRenderableType();
        if (0 != (rtype & EGL_OPENGL_ES_BIT)) {
            nGLConfigs++;
        }
        if (0 != (rtype & EGL_OPENGL_ES2_BIT)) {
            nGL2Configs++;
        }
    }

    //
    // Fail initialization if no GLES configs exist
    //
    if (nGLConfigs == 0) {
        delete fb;
        return false;
    }

    //
    // If no GLES2 configs exist - not GLES2 capability
    //
    if (nGL2Configs == 0) {
        fb->m_caps.hasGL2 = false;
    }

    //
    // Initialize some GL state in the pbuffer context
    //
    fb->initGLState();

    //
    // Cache the GL strings so we don't have to think about threading or
    // current-context when asked for them.
    //
    fb->m_glVendor = (const char*)s_gl.glGetString(GL_VENDOR);
    fb->m_glRenderer = (const char*)s_gl.glGetString(GL_RENDERER);
    fb->m_glVersion = (const char*)s_gl.glGetString(GL_VERSION);

    // release the FB context
    fb->unbind_locked();

    //
    // Keep the singleton framebuffer pointer
    //
    s_theFrameBuffer = fb;
    return true;
}

FrameBuffer::FrameBuffer(int p_width, int p_height) :
    m_width(p_width),
    m_height(p_height),
    m_eglDisplay(EGL_NO_DISPLAY),
    m_eglSurface(EGL_NO_SURFACE),
    m_eglContext(EGL_NO_CONTEXT),
    m_pbufContext(EGL_NO_CONTEXT),
    m_prevContext(EGL_NO_CONTEXT),
    m_prevReadSurf(EGL_NO_SURFACE),
    m_prevDrawSurf(EGL_NO_SURFACE),
    m_subWin((EGLNativeWindowType)0),
    m_subWinDisplay(NULL),
    m_lastPostedColorBuffer(0),
    m_zRot(0.0f),
    m_eglContextInitialized(false),
    m_statsNumFrames(0),
    m_statsStartTime(0LL),
    m_onPost(NULL),
    m_onPostContext(NULL),
    m_fbImage(NULL),
    m_glVendor(NULL),
    m_glRenderer(NULL),
    m_glVersion(NULL)
{
    m_fpsStats = getenv("SHOW_FPS_STATS") != NULL;
}

FrameBuffer::~FrameBuffer()
{
    free(m_fbImage);
}

void FrameBuffer::setPostCallback(OnPostFn onPost, void* onPostContext)
{
    emugl::Mutex::AutoLock mutex(m_lock);
    m_onPost = onPost;
    m_onPostContext = onPostContext;
    if (m_onPost && !m_fbImage) {
        m_fbImage = (unsigned char*)malloc(4 * m_width * m_height);
        if (!m_fbImage) {
            ERR("out of memory, cancelling OnPost callback");
            m_onPost = NULL;
            m_onPostContext = NULL;
            return;
        }
    }
}

bool FrameBuffer::setupSubWindow(FBNativeWindowType p_window,
                                  int p_x, int p_y,
                                  int p_width, int p_height, float zRot)
{
    bool success = false;

    if (s_theFrameBuffer) {
        s_theFrameBuffer->m_lock.lock();
        FrameBuffer *fb = s_theFrameBuffer;
        if (!fb->m_subWin) {

            // create native subwindow for FB display output
            fb->m_subWin = createSubWindow(p_window,
                                           &fb->m_subWinDisplay,
                                           p_x,p_y,p_width,p_height);
            if (fb->m_subWin) {
                fb->m_nativeWindow = p_window;

                // create EGLSurface from the generated subwindow
                fb->m_eglSurface = s_egl.eglCreateWindowSurface(fb->m_eglDisplay,
                                                    fb->m_eglConfig,
                                                    fb->m_subWin,
                                                    NULL);

                if (fb->m_eglSurface == EGL_NO_SURFACE) {
                    ERR("Failed to create surface\n");
                    destroySubWindow(fb->m_subWinDisplay, fb->m_subWin);
                    fb->m_subWin = (EGLNativeWindowType)0;
                }
                else if (fb->bindSubwin_locked()) {
                    // Subwin creation was successfull,
                    // update viewport and z rotation and draw
                    // the last posted color buffer.
                    s_gl.glViewport(0, 0, p_width, p_height);
                    fb->m_zRot = zRot;
                    fb->post( fb->m_lastPostedColorBuffer, false );
                    fb->unbind_locked();
                    success = true;
                }
             }
        }
        s_theFrameBuffer->m_lock.unlock();
     }

    return success;
}

bool FrameBuffer::removeSubWindow()
{
    bool removed = false;
    if (s_theFrameBuffer) {
        s_theFrameBuffer->m_lock.lock();
        if (s_theFrameBuffer->m_subWin) {
            s_egl.eglMakeCurrent(s_theFrameBuffer->m_eglDisplay, NULL, NULL, NULL);
            s_egl.eglDestroySurface(s_theFrameBuffer->m_eglDisplay,
                                    s_theFrameBuffer->m_eglSurface);
            destroySubWindow(s_theFrameBuffer->m_subWinDisplay,
                             s_theFrameBuffer->m_subWin);

            s_theFrameBuffer->m_eglSurface = EGL_NO_SURFACE;
            s_theFrameBuffer->m_subWin = (EGLNativeWindowType)0;
            removed = true;
        }
        s_theFrameBuffer->m_lock.unlock();
    }
    return removed;
}

HandleType FrameBuffer::genHandle()
{
    HandleType id;
    do {
        id = ++s_nextHandle;
    } while( id == 0 ||
             m_contexts.find(id) != m_contexts.end() ||
             m_windows.find(id) != m_windows.end() );

    return id;
}

HandleType FrameBuffer::createColorBuffer(int p_width, int p_height,
                                          GLenum p_internalFormat)
{
    emugl::Mutex::AutoLock mutex(m_lock);
    HandleType ret = 0;

    ColorBufferPtr cb( ColorBuffer::create(p_width, p_height, p_internalFormat) );
    if (cb.Ptr() != NULL) {
        ret = genHandle();
        m_colorbuffers[ret].cb = cb;
        m_colorbuffers[ret].refcount = 1;
    }
    return ret;
}

HandleType FrameBuffer::createRenderContext(int p_config, HandleType p_share,
                                            bool p_isGL2)
{
    emugl::Mutex::AutoLock mutex(m_lock);
    HandleType ret = 0;

    RenderContextPtr share(NULL);
    if (p_share != 0) {
        RenderContextMap::iterator s( m_contexts.find(p_share) );
        if (s == m_contexts.end()) {
            return 0;
        }
        share = (*s).second;
    }

    RenderContextPtr rctx( RenderContext::create(p_config, share, p_isGL2) );
    if (rctx.Ptr() != NULL) {
        ret = genHandle();
        m_contexts[ret] = rctx;
    }
    return ret;
}

HandleType FrameBuffer::createWindowSurface(int p_config, int p_width, int p_height)
{
    emugl::Mutex::AutoLock mutex(m_lock);

    HandleType ret = 0;
    WindowSurfacePtr win( WindowSurface::create(p_config, p_width, p_height) );
    if (win.Ptr() != NULL) {
        ret = genHandle();
        m_windows[ret] = win;
    }

    return ret;
}

void FrameBuffer::DestroyRenderContext(HandleType p_context)
{
    emugl::Mutex::AutoLock mutex(m_lock);
    m_contexts.erase(p_context);
}

void FrameBuffer::DestroyWindowSurface(HandleType p_surface)
{
    emugl::Mutex::AutoLock mutex(m_lock);
    m_windows.erase(p_surface);
}

int FrameBuffer::openColorBuffer(HandleType p_colorbuffer)
{
    emugl::Mutex::AutoLock mutex(m_lock);
    ColorBufferMap::iterator c(m_colorbuffers.find(p_colorbuffer));
    if (c == m_colorbuffers.end()) {
        // bad colorbuffer handle
        ERR("FB: openColorBuffer cb handle %#x not found\n", p_colorbuffer);
        return -1;
    }
    (*c).second.refcount++;
    return 0;
}

void FrameBuffer::closeColorBuffer(HandleType p_colorbuffer)
{
    emugl::Mutex::AutoLock mutex(m_lock);
    ColorBufferMap::iterator c(m_colorbuffers.find(p_colorbuffer));
    if (c == m_colorbuffers.end()) {
        ERR("FB: closeColorBuffer cb handle %#x not found\n", p_colorbuffer);
        // bad colorbuffer handle
        return;
    }
    if (--(*c).second.refcount == 0) {
        m_colorbuffers.erase(c);
    }
}

bool FrameBuffer::flushWindowSurfaceColorBuffer(HandleType p_surface)
{
    emugl::Mutex::AutoLock mutex(m_lock);

    WindowSurfaceMap::iterator w( m_windows.find(p_surface) );
    if (w == m_windows.end()) {
        ERR("FB::flushWindowSurfaceColorBuffer: window handle %#x not found\n", p_surface);
        // bad surface handle
        return false;
    }

    return (*w).second->flushColorBuffer();
}

bool FrameBuffer::setWindowSurfaceColorBuffer(HandleType p_surface,
                                              HandleType p_colorbuffer)
{
    emugl::Mutex::AutoLock mutex(m_lock);

    WindowSurfaceMap::iterator w( m_windows.find(p_surface) );
    if (w == m_windows.end()) {
        // bad surface handle
        ERR("%s: bad window surface handle %#x\n", __FUNCTION__, p_surface);
        return false;
    }

    ColorBufferMap::iterator c( m_colorbuffers.find(p_colorbuffer) );
    if (c == m_colorbuffers.end()) {
        ERR("%s: bad color buffer handle %#x\n", __FUNCTION__, p_colorbuffer);
        // bad colorbuffer handle
        return false;
    }

    (*w).second->setColorBuffer( (*c).second.cb );

    return true;
}

bool FrameBuffer::updateColorBuffer(HandleType p_colorbuffer,
                                    int x, int y, int width, int height,
                                    GLenum format, GLenum type, void *pixels)
{
    emugl::Mutex::AutoLock mutex(m_lock);

    ColorBufferMap::iterator c( m_colorbuffers.find(p_colorbuffer) );
    if (c == m_colorbuffers.end()) {
        // bad colorbuffer handle
        return false;
    }

    (*c).second.cb->subUpdate(x, y, width, height, format, type, pixels);

    return true;
}

bool FrameBuffer::bindColorBufferToTexture(HandleType p_colorbuffer)
{
    emugl::Mutex::AutoLock mutex(m_lock);

    ColorBufferMap::iterator c( m_colorbuffers.find(p_colorbuffer) );
    if (c == m_colorbuffers.end()) {
        // bad colorbuffer handle
        return false;
    }

    return (*c).second.cb->bindToTexture();
}

bool FrameBuffer::bindColorBufferToRenderbuffer(HandleType p_colorbuffer)
{
    emugl::Mutex::AutoLock mutex(m_lock);

    ColorBufferMap::iterator c( m_colorbuffers.find(p_colorbuffer) );
    if (c == m_colorbuffers.end()) {
        // bad colorbuffer handle
        return false;
    }

    return (*c).second.cb->bindToRenderbuffer();
}

bool FrameBuffer::bindContext(HandleType p_context,
                              HandleType p_drawSurface,
                              HandleType p_readSurface)
{
    emugl::Mutex::AutoLock mutex(m_lock);

    WindowSurfacePtr draw(NULL), read(NULL);
    RenderContextPtr ctx(NULL);

    //
    // if this is not an unbind operation - make sure all handles are good
    //
    if (p_context || p_drawSurface || p_readSurface) {
        RenderContextMap::iterator r( m_contexts.find(p_context) );
        if (r == m_contexts.end()) {
            // bad context handle
            return false;
        }

        ctx = (*r).second;
        WindowSurfaceMap::iterator w( m_windows.find(p_drawSurface) );
        if (w == m_windows.end()) {
            // bad surface handle
            return false;
        }
        draw = (*w).second;

        if (p_readSurface != p_drawSurface) {
            WindowSurfaceMap::iterator w( m_windows.find(p_readSurface) );
            if (w == m_windows.end()) {
                // bad surface handle
                return false;
            }
            read = (*w).second;
        }
        else {
            read = draw;
        }
    }

    if (!s_egl.eglMakeCurrent(m_eglDisplay,
                              draw ? draw->getEGLSurface() : EGL_NO_SURFACE,
                              read ? read->getEGLSurface() : EGL_NO_SURFACE,
                              ctx ? ctx->getEGLContext() : EGL_NO_CONTEXT)) {
        ERR("eglMakeCurrent failed\n");
        return false;
    }

    //
    // Bind the surface(s) to the context
    //
    RenderThreadInfo *tinfo = RenderThreadInfo::get();
    WindowSurfacePtr bindDraw, bindRead;
    if (draw.Ptr() == NULL && read.Ptr() == NULL) {
        // Unbind the current read and draw surfaces from the context
        bindDraw = tinfo->currDrawSurf;
        bindRead = tinfo->currReadSurf;
    } else {
        bindDraw = draw;
        bindRead = read;
    }

    if (bindDraw.Ptr() != NULL && bindRead.Ptr() != NULL) {
        if (bindDraw.Ptr() != bindRead.Ptr()) {
            bindDraw->bind(ctx, SURFACE_BIND_DRAW);
            bindRead->bind(ctx, SURFACE_BIND_READ);
        }
        else {
            bindDraw->bind(ctx, SURFACE_BIND_READDRAW);
        }
    }

    //
    // update thread info with current bound context
    //
    tinfo->currContext = ctx;
    tinfo->currDrawSurf = draw;
    tinfo->currReadSurf = read;
    if (ctx) {
        if (ctx->isGL2()) tinfo->m_gl2Dec.setContextData(&ctx->decoderContextData());
        else tinfo->m_glDec.setContextData(&ctx->decoderContextData());
    }
    else {
        tinfo->m_glDec.setContextData(NULL);
        tinfo->m_gl2Dec.setContextData(NULL);
    }
    return true;
}

//
// The framebuffer lock should be held when calling this function !
//
bool FrameBuffer::bind_locked()
{
    EGLContext prevContext = s_egl.eglGetCurrentContext();
    EGLSurface prevReadSurf = s_egl.eglGetCurrentSurface(EGL_READ);
    EGLSurface prevDrawSurf = s_egl.eglGetCurrentSurface(EGL_DRAW);

    if (!s_egl.eglMakeCurrent(m_eglDisplay, m_pbufSurface,
                              m_pbufSurface, m_pbufContext)) {
        ERR("eglMakeCurrent failed\n");
        return false;
    }

    m_prevContext = prevContext;
    m_prevReadSurf = prevReadSurf;
    m_prevDrawSurf = prevDrawSurf;
    return true;
}

bool FrameBuffer::bindSubwin_locked()
{
    EGLContext prevContext = s_egl.eglGetCurrentContext();
    EGLSurface prevReadSurf = s_egl.eglGetCurrentSurface(EGL_READ);
    EGLSurface prevDrawSurf = s_egl.eglGetCurrentSurface(EGL_DRAW);

    if (!s_egl.eglMakeCurrent(m_eglDisplay, m_eglSurface,
                              m_eglSurface, m_eglContext)) {
        ERR("eglMakeCurrent failed\n");
        return false;
    }

    //
    // initialize GL state in eglContext if not yet initilaized
    //
    if (!m_eglContextInitialized) {
        initGLState();
        m_eglContextInitialized = true;
    }

    m_prevContext = prevContext;
    m_prevReadSurf = prevReadSurf;
    m_prevDrawSurf = prevDrawSurf;
    return true;
}

bool FrameBuffer::unbind_locked()
{
    if (!s_egl.eglMakeCurrent(m_eglDisplay, m_prevDrawSurf,
                              m_prevReadSurf, m_prevContext)) {
        return false;
    }

    m_prevContext = EGL_NO_CONTEXT;
    m_prevReadSurf = EGL_NO_SURFACE;
    m_prevDrawSurf = EGL_NO_SURFACE;
    return true;
}

bool FrameBuffer::post(HandleType p_colorbuffer, bool needLock)
{
    if (needLock) m_lock.lock();
    bool ret = false;

    ColorBufferMap::iterator c( m_colorbuffers.find(p_colorbuffer) );
    if (c != m_colorbuffers.end()) {

        m_lastPostedColorBuffer = p_colorbuffer;
        if (!m_subWin) {
            // no subwindow created for the FB output
            // cannot post the colorbuffer
            if (needLock) m_lock.unlock();
            return ret;
        }


        // bind the subwindow eglSurface
        if (!bindSubwin_locked()) {
            ERR("FrameBuffer::post eglMakeCurrent failed\n");
            if (needLock) m_lock.unlock();
            return false;
        }

        //
        // render the color buffer to the window
        //
        s_gl.glPushMatrix();
        s_gl.glRotatef(m_zRot, 0.0f, 0.0f, 1.0f);
        if (m_zRot != 0.0f) {
            s_gl.glClear(GL_COLOR_BUFFER_BIT);
        }
        ret = (*c).second.cb->post();
        s_gl.glPopMatrix();

        if (ret) {
            //
            // output FPS statistics
            //
            if (m_fpsStats) {
                long long currTime = GetCurrentTimeMS();
                m_statsNumFrames++;
                if (currTime - m_statsStartTime >= 1000) {
                    float dt = (float)(currTime - m_statsStartTime) / 1000.0f;
                    printf("FPS: %5.3f\n", (float)m_statsNumFrames / dt);
                    m_statsStartTime = currTime;
                    m_statsNumFrames = 0;
                }
            }

            s_egl.eglSwapBuffers(m_eglDisplay, m_eglSurface);
        }

        // restore previous binding
        unbind_locked();

        //
        // Send framebuffer (without FPS overlay) to callback
        //
        if (m_onPost) {
            (*c).second.cb->readback(m_fbImage);
            m_onPost(m_onPostContext, m_width, m_height, -1,
                    GL_RGBA, GL_UNSIGNED_BYTE, m_fbImage);
        }

    }

    if (needLock) m_lock.unlock();
    return ret;
}

bool FrameBuffer::repost()
{
    if (m_lastPostedColorBuffer) {
        return post( m_lastPostedColorBuffer );
    }
    return false;
}

void FrameBuffer::initGLState()
{
    s_gl.glMatrixMode(GL_PROJECTION);
    s_gl.glLoadIdentity();
    s_gl.glOrthof(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
    s_gl.glMatrixMode(GL_MODELVIEW);
    s_gl.glLoadIdentity();
}