/* * Copyright (C) 2011-2015 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 "DispatchTables.h" #include "NativeSubWindow.h" #include "RenderThreadInfo.h" #include "TimeUtils.h" #include "gles2_dec.h" #include "OpenGLESDispatch/EGLDispatch.h" #include "emugl/common/logging.h" #include namespace { // Helper class to call the bind_locked() / unbind_locked() properly. class ScopedBind { public: // Constructor will call bind_locked() on |fb|. // Use isValid() to check for errors. ScopedBind(FrameBuffer* fb) : mFb(fb) { if (!mFb->bind_locked()) { mFb = NULL; } } // Returns true if contruction bound the framebuffer context properly. bool isValid() const { return mFb != NULL; } // Unbound the framebuffer explictly. This is also called by the // destructor. void release() { if (mFb) { mFb->unbind_locked(); mFb = NULL; } } // Destructor will call release(). ~ScopedBind() { release(); } private: FrameBuffer* mFb; }; // Implementation of a ColorBuffer::Helper instance that redirects calls // to a FrameBuffer instance. class ColorBufferHelper : public ColorBuffer::Helper { public: ColorBufferHelper(FrameBuffer* fb) : mFb(fb) {} virtual bool setupContext() { return mFb->bind_locked(); } virtual void teardownContext() { mFb->unbind_locked(); } virtual TextureDraw* getTextureDraw() const { return mFb->getTextureDraw(); } private: FrameBuffer* mFb; }; } // namespace FrameBuffer *FrameBuffer::s_theFrameBuffer = NULL; HandleType FrameBuffer::s_nextHandle = 0; static char* getGLES1ExtensionString(EGLDisplay p_dpy) { EGLConfig config; EGLSurface surface; static const GLint configAttribs[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT, EGL_NONE }; int n; if (!s_egl.eglChooseConfig(p_dpy, configAttribs, &config, 1, &n) || n == 0) { ERR("%s: Could not find GLES 1.x config!\n", __FUNCTION__); return NULL; } DBG("%s: Found config %p\n", __FUNCTION__, (void*)config); static const EGLint pbufAttribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }; surface = s_egl.eglCreatePbufferSurface(p_dpy, config, pbufAttribs); if (surface == EGL_NO_SURFACE) { ERR("%s: Could not create GLES 1.x Pbuffer!\n", __FUNCTION__); return NULL; } static const GLint gles1ContextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 1, EGL_NONE }; EGLContext ctx = s_egl.eglCreateContext(p_dpy, config, EGL_NO_CONTEXT, gles1ContextAttribs); if (ctx == EGL_NO_CONTEXT) { ERR("%s: Could not create GLES 1.x Context!\n", __FUNCTION__); s_egl.eglDestroySurface(p_dpy, surface); return NULL; } if (!s_egl.eglMakeCurrent(p_dpy, surface, surface, ctx)) { ERR("%s: Could not make GLES 1.x context current!\n", __FUNCTION__); 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_gles1.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; } void FrameBuffer::finalize(){ m_colorbuffers.clear(); if (m_useSubWindow) { removeSubWindow(); } m_windows.clear(); m_contexts.clear(); s_egl.eglMakeCurrent(m_eglDisplay, NULL, NULL, NULL); s_egl.eglDestroyContext(m_eglDisplay, m_eglContext); s_egl.eglDestroyContext(m_eglDisplay, m_pbufContext); s_egl.eglDestroySurface(m_eglDisplay, m_pbufSurface); } bool FrameBuffer::initialize(int width, int height, bool useSubWindow) { GL_LOG("FrameBuffer::initialize"); if (s_theFrameBuffer != NULL) { return true; } // // allocate space for the FrameBuffer object // FrameBuffer *fb = new FrameBuffer(width, height, useSubWindow); if (!fb) { ERR("Failed to create fb\n"); return false; } // // 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; } GL_LOG("call eglInitialize"); if (!s_egl.eglInitialize(fb->m_eglDisplay, &fb->m_caps.eglMajor, &fb->m_caps.eglMinor)) { ERR("Failed to eglInitialize\n"); GL_LOG("Failed to eglInitialize"); delete fb; return false; } DBG("egl: %d %d\n", fb->m_caps.eglMajor, fb->m_caps.eglMinor); GL_LOG("egl: %d %d", 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* gles1Extensions = NULL; gles1Extensions = getGLES1ExtensionString(fb->m_eglDisplay); if (!gles1Extensions) { // Could not create GLES2 context - drop GL2 capability ERR("Failed to obtain GLES 2.x extensions string!\n"); delete fb; return false; } // // Create EGL context for framebuffer post rendering. // GLint surfaceType = (useSubWindow ? EGL_WINDOW_BIT : 0) | EGL_PBUFFER_BIT; const GLint configAttribs[] = { EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_SURFACE_TYPE, surfaceType, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; int n; if (!s_egl.eglChooseConfig(fb->m_eglDisplay, configAttribs, &fb->m_eglConfig, 1, &n)) { ERR("Failed on eglChooseConfig\n"); free(gles1Extensions); delete fb; return false; } static const GLint glContextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; GL_LOG("attempting to create egl context"); fb->m_eglContext = s_egl.eglCreateContext(fb->m_eglDisplay, fb->m_eglConfig, EGL_NO_CONTEXT, glContextAttribs); if (fb->m_eglContext == EGL_NO_CONTEXT) { ERR("Failed to create context 0x%x\n", s_egl.eglGetError()); free(gles1Extensions); delete fb; return false; } GL_LOG("attempting to create egl pbuffer context"); // // 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) { ERR("Failed to create Pbuffer Context 0x%x\n", s_egl.eglGetError()); free(gles1Extensions); delete fb; return false; } GL_LOG("context creation successful"); // // 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. // static const 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) { ERR("Failed to create pbuf surface for FB 0x%x\n", s_egl.eglGetError()); free(gles1Extensions); delete fb; return false; } GL_LOG("attempting to make context current"); // Make the context current ScopedBind bind(fb); if (!bind.isValid()) { ERR("Failed to make current\n"); free(gles1Extensions); delete fb; return false; } GL_LOG("context-current successful"); // // Initilize framebuffer capabilities // //const char* gles2Extensions = (const char *)s_gles2.glGetString(GL_EXTENSIONS); bool has_gl_oes_image = false; // printf("GLES1 [%s]\n", gles1Extensions); // printf("GLES2 [%s]\n", gles2Extensions); has_gl_oes_image = true; if (has_gl_oes_image) { has_gl_oes_image &= strstr(gles1Extensions, "GL_OES_EGL_image") != NULL; } free((void*)gles1Extensions); gles1Extensions = 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"); bind.release(); delete fb; return false; } GL_LOG("host system has enough extensions"); // // Initialize set of configs // fb->m_configs = new FbConfigList(fb->m_eglDisplay); if (fb->m_configs->empty()) { ERR("Failed: Initialize set of configs\n"); bind.release(); delete fb; return false; } // // Check that we have config for each GLES and GLES2 // size_t nConfigs = fb->m_configs->size(); int nGLConfigs = 0; int nGL2Configs = 0; for (size_t i = 0; i < nConfigs; ++i) { GLint rtype = fb->m_configs->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) { bind.release(); delete fb; return false; } // // If no GLES2 configs exist - not GLES2 capability // if (nGL2Configs == 0) { ERR("Failed: No GLES 2.x configs found!\n"); bind.release(); delete fb; return false; } GL_LOG("There are sufficient EGLconfigs available"); // // 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_gles2.glGetString(GL_VENDOR); fb->m_glRenderer = (const char*)s_gles2.glGetString(GL_RENDERER); fb->m_glVersion = (const char*)s_gles2.glGetString(GL_VERSION); fb->m_textureDraw = new TextureDraw(fb->m_eglDisplay); if (!fb->m_textureDraw) { ERR("Failed: creation of TextureDraw instance\n"); bind.release(); delete fb; return false; } // release the FB context bind.release(); // // Keep the singleton framebuffer pointer // s_theFrameBuffer = fb; GL_LOG("basic EGL initialization successful"); return true; } FrameBuffer::FrameBuffer(int p_width, int p_height, bool useSubWindow) : m_framebufferWidth(p_width), m_framebufferHeight(p_height), m_windowWidth(p_width), m_windowHeight(p_height), m_useSubWindow(useSubWindow), m_configs(NULL), m_eglDisplay(EGL_NO_DISPLAY), m_colorBufferHelper(new ColorBufferHelper(this)), 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_textureDraw(NULL), m_lastPostedColorBuffer(0), m_zRot(0.0f), m_px(0), m_py(0), 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() { delete m_textureDraw; delete m_configs; delete m_colorBufferHelper; 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_framebufferWidth * m_framebufferHeight); if (!m_fbImage) { ERR("out of memory, cancelling OnPost callback"); m_onPost = NULL; m_onPostContext = NULL; return; } } } static void subWindowRepaint(void* param) { auto fb = static_cast(param); fb->repost(); } bool FrameBuffer::setupSubWindow(FBNativeWindowType p_window, int wx, int wy, int ww, int wh, int fbw, int fbh, float dpr, float zRot) { bool success = false; if (!m_useSubWindow) { ERR("%s: Cannot create native sub-window in this configuration\n", __FUNCTION__); return false; } m_lock.lock(); // If the subwindow doesn't exist, create it with the appropriate dimensions if (!m_subWin) { // Create native subwindow for FB display output m_x = wx; m_y = wy; m_windowWidth = ww; m_windowHeight = wh; m_subWin = createSubWindow(p_window, m_x, m_y, m_windowWidth, m_windowHeight, subWindowRepaint, this); if (m_subWin) { m_nativeWindow = p_window; // create EGLSurface from the generated subwindow m_eglSurface = s_egl.eglCreateWindowSurface(m_eglDisplay, m_eglConfig, m_subWin, NULL); if (m_eglSurface == EGL_NO_SURFACE) { // NOTE: This can typically happen with software-only renderers like OSMesa. destroySubWindow(m_subWin); m_subWin = (EGLNativeWindowType)0; } else { m_px = 0; m_py = 0; success = true; } } } // At this point, if the subwindow doesn't exist, it is because it either couldn't be created // in the first place or the EGLSurface couldn't be created. if (m_subWin && bindSubwin_locked()) { // Only attempt to update window geometry if anything has actually changed. if (!(m_x == wx && m_y == wy && m_windowWidth == ww && m_windowHeight == wh)) { m_x = wx; m_y = wy; m_windowWidth = ww; m_windowHeight = wh; success = ::moveSubWindow(m_nativeWindow, m_subWin, m_x, m_y, m_windowWidth, m_windowHeight); // Otherwise, ensure that at least viewport parameters are properly updated. } else { success = true; } if (success) { // Subwin creation or movement was successful, // update viewport and z rotation and draw // the last posted color buffer. s_gles2.glViewport(0, 0, fbw * dpr, fbh * dpr); m_dpr = dpr; m_zRot = zRot; if (m_lastPostedColorBuffer) { post(m_lastPostedColorBuffer, false); } else { s_gles2.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); s_egl.eglSwapBuffers(m_eglDisplay, m_eglSurface); } } unbind_locked(); } m_lock.unlock(); return success; } bool FrameBuffer::removeSubWindow() { if (!m_useSubWindow) { ERR("%s: Cannot remove native sub-window in this configuration\n", __FUNCTION__); return false; } bool removed = false; m_lock.lock(); if (m_subWin) { s_egl.eglMakeCurrent(m_eglDisplay, NULL, NULL, NULL); s_egl.eglDestroySurface(m_eglDisplay, m_eglSurface); destroySubWindow(m_subWin); m_eglSurface = EGL_NO_SURFACE; m_subWin = (EGLNativeWindowType)0; removed = true; } 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( getDisplay(), p_width, p_height, p_internalFormat, getCaps().has_eglimage_texture_2d, m_colorBufferHelper)); 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; const FbConfig* config = getConfigs()->get(p_config); if (!config) { return ret; } RenderContextPtr share(NULL); if (p_share != 0) { RenderContextMap::iterator s(m_contexts.find(p_share)); if (s == m_contexts.end()) { return ret; } share = (*s).second; } EGLContext sharedContext = share.Ptr() ? share->getEGLContext() : EGL_NO_CONTEXT; RenderContextPtr rctx(RenderContext::create( m_eglDisplay, config->getEglConfig(), sharedContext, p_isGL2)); if (rctx.Ptr() != NULL) { ret = genHandle(); m_contexts[ret] = rctx; RenderThreadInfo *tinfo = RenderThreadInfo::get(); tinfo->m_contextSet.insert(ret); } return ret; } HandleType FrameBuffer::createWindowSurface(int p_config, int p_width, int p_height) { emugl::Mutex::AutoLock mutex(m_lock); HandleType ret = 0; const FbConfig* config = getConfigs()->get(p_config); if (!config) { return ret; } WindowSurfacePtr win(WindowSurface::create( getDisplay(), config->getEglConfig(), p_width, p_height)); if (win.Ptr() != NULL) { ret = genHandle(); m_windows[ret] = std::pair(win,0); RenderThreadInfo *tinfo = RenderThreadInfo::get(); tinfo->m_windowSet.insert(ret); } return ret; } void FrameBuffer::drainRenderContext() { emugl::Mutex::AutoLock mutex(m_lock); RenderThreadInfo *tinfo = RenderThreadInfo::get(); if (tinfo->m_contextSet.empty()) return; for (std::set::iterator it = tinfo->m_contextSet.begin(); it != tinfo->m_contextSet.end(); ++it) { HandleType contextHandle = *it; m_contexts.erase(contextHandle); } tinfo->m_contextSet.clear(); } void FrameBuffer::drainWindowSurface() { emugl::Mutex::AutoLock mutex(m_lock); RenderThreadInfo *tinfo = RenderThreadInfo::get(); if (tinfo->m_windowSet.empty()) return; for (std::set::iterator it = tinfo->m_windowSet.begin(); it != tinfo->m_windowSet.end(); ++it) { HandleType windowHandle = *it; if (m_windows.find(windowHandle) != m_windows.end()) { HandleType oldColorBufferHandle = m_windows[windowHandle].second; if (oldColorBufferHandle) { ColorBufferMap::iterator cit(m_colorbuffers.find(oldColorBufferHandle)); if (cit != m_colorbuffers.end()) { if (--(*cit).second.refcount == 0) { m_colorbuffers.erase(cit); } } } m_windows.erase(windowHandle); } } tinfo->m_windowSet.clear(); } void FrameBuffer::DestroyRenderContext(HandleType p_context) { emugl::Mutex::AutoLock mutex(m_lock); m_contexts.erase(p_context); RenderThreadInfo *tinfo = RenderThreadInfo::get(); if (tinfo->m_contextSet.empty()) return; tinfo->m_contextSet.erase(p_context); } void FrameBuffer::DestroyWindowSurface(HandleType p_surface) { emugl::Mutex::AutoLock mutex(m_lock); if (m_windows.find(p_surface) != m_windows.end()) { m_windows.erase(p_surface); RenderThreadInfo *tinfo = RenderThreadInfo::get(); if (tinfo->m_windowSet.empty()) return; tinfo->m_windowSet.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()) { // This is harmless: it is normal for guest system to issue // closeColorBuffer command when the color buffer is already // garbage collected on the host. (we dont have a mechanism // to give guest a notice yet) 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; } WindowSurface* surface = (*w).second.first.Ptr(); surface->flushColorBuffer(); return true; } 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()) { DBG("%s: bad color buffer handle %#x\n", __FUNCTION__, p_colorbuffer); // bad colorbuffer handle return false; } (*w).second.first->setColorBuffer((*c).second.cb); (*w).second.second = p_colorbuffer; return true; } void FrameBuffer::readColorBuffer(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; } (*c).second.cb->readPixels(x, y, width, height, format, type, pixels); } 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.first; 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.first; } 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, WindowSurface::BIND_DRAW); bindRead->bind(ctx, WindowSurface::BIND_READ); } else { bindDraw->bind(ctx, WindowSurface::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; } HandleType FrameBuffer::createClientImage(HandleType context, EGLenum target, GLuint buffer) { RenderContextPtr ctx(NULL); if (context) { RenderContextMap::iterator r( m_contexts.find(context) ); if (r == m_contexts.end()) { // bad context handle return false; } ctx = (*r).second; } EGLContext eglContext = ctx ? ctx->getEGLContext() : EGL_NO_CONTEXT; EGLImageKHR image = s_egl.eglCreateImageKHR( m_eglDisplay, eglContext, target, reinterpret_cast(buffer), NULL); return (HandleType)reinterpret_cast(image); } EGLBoolean FrameBuffer::destroyClientImage(HandleType image) { return s_egl.eglDestroyImageKHR(m_eglDisplay, reinterpret_cast(image)); } // // 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) { 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()) { goto EXIT; } m_lastPostedColorBuffer = p_colorbuffer; if (m_subWin) { // bind the subwindow eglSurface if (!bindSubwin_locked()) { ERR("FrameBuffer::post(): eglMakeCurrent failed\n"); goto EXIT; } // get the viewport GLint vp[4]; s_gles2.glGetIntegerv(GL_VIEWPORT, vp); // divide by device pixel ratio because windowing coordinates ignore DPR, // but the framebuffer includes DPR vp[2] = vp[2] / m_dpr; vp[3] = vp[3] / m_dpr; // find the x and y values at the origin when "fully scrolled" // multiply by 2 because the texture goes from -1 to 1, not 0 to 1 float fx = 2.f * (vp[2] - m_windowWidth) / (float) vp[2]; float fy = 2.f * (vp[3] - m_windowHeight) / (float) vp[3]; // finally, compute translation values float dx = m_px * fx; float dy = m_py * fy; // // render the color buffer to the window // if (m_zRot != 0.0f) { s_gles2.glClear(GL_COLOR_BUFFER_BIT); } ret = (*c).second.cb->post(m_zRot, dx, dy); if (ret) { s_egl.eglSwapBuffers(m_eglDisplay, m_eglSurface); } // restore previous binding unbind_locked(); } else { // If there is no sub-window, don't display anything, the client will // rely on m_onPost to get the pixels instead. ret = true; } // // 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; } } // // Send framebuffer (without FPS overlay) to callback // if (m_onPost) { (*c).second.cb->readback(m_fbImage); m_onPost(m_onPostContext, m_framebufferWidth, m_framebufferHeight, -1, GL_RGBA, GL_UNSIGNED_BYTE, m_fbImage); } EXIT: if (needLock) { m_lock.unlock(); } return ret; } bool FrameBuffer::repost() { if (m_lastPostedColorBuffer) { return post(m_lastPostedColorBuffer); } return false; }