blob: 38b0916d97eb849cbf31aee01559720110d8756d [file] [log] [blame]
/*
* 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 "gles2_dec.h"
#include "OpenGLESDispatch/EGLDispatch.h"
#include "android/base/system/System.h"
#include "emugl/common/logging.h"
#include <stdio.h>
#include <string.h>
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* getGLES2ExtensionString(EGLDisplay p_dpy)
{
EGLConfig config;
EGLSurface surface;
static const GLint configAttribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
int n;
if (!s_egl.eglChooseConfig(p_dpy, configAttribs,
&config, 1, &n) || n == 0) {
ERR("%s: Could not find GLES 2.x config!\n", __FUNCTION__);
return NULL;
}
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 2.x Pbuffer!\n", __FUNCTION__);
return NULL;
}
static const GLint gles2ContextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLContext ctx = s_egl.eglCreateContext(p_dpy, config,
EGL_NO_CONTEXT,
gles2ContextAttribs);
if (ctx == EGL_NO_CONTEXT) {
ERR("%s: Could not create GLES 2.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 2.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_gles2.glGetString(GL_EXTENSIONS);
char* extString = strdup(s ? s : "");
// It is rare but some drivers actually fail this...
if (!s_egl.eglMakeCurrent(p_dpy, NULL, NULL, NULL)) {
ERR("%s: Could not unbind context. Please try updating graphics card driver!\n", __FUNCTION__);
free(extString);
return 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* gles2Extensions = NULL;
gles2Extensions = getGLES2ExtensionString(fb->m_eglDisplay);
if (!gles2Extensions) {
// 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;
// On Linux, we need RGB888 exactly, or eglMakeCurrent will fail,
// as glXMakeContextCurrent needs to match the format of the
// native pixmap.
EGLint wantedRedSize = 8;
EGLint wantedGreenSize = 8;
EGLint wantedBlueSize = 8;
EGLint wantedAlphaSize = 0;
const GLint configAttribs[] = {
EGL_RED_SIZE, wantedRedSize,
EGL_GREEN_SIZE, wantedGreenSize,
EGL_BLUE_SIZE, wantedBlueSize,
EGL_ALPHA_SIZE, wantedAlphaSize,
EGL_SURFACE_TYPE, surfaceType,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
EGLint total_num_configs = 0;
s_egl.eglGetConfigs(fb->m_eglDisplay, NULL, 0, &total_num_configs);
std::vector<EGLConfig> all_configs(total_num_configs);
EGLint total_egl_compatible_configs = 0;
s_egl.eglChooseConfig(fb->m_eglDisplay,
configAttribs,
&all_configs[0],
total_num_configs,
&total_egl_compatible_configs);
uint32_t total_exact_matches = 0;
std::vector<EGLint> exact_match_indices;
for (EGLint i = 0; i < total_egl_compatible_configs; i++) {
EGLint r,g,b,a;
EGLConfig c = all_configs[i];
s_egl.eglGetConfigAttrib(fb->m_eglDisplay, c, EGL_RED_SIZE, &r);
s_egl.eglGetConfigAttrib(fb->m_eglDisplay, c, EGL_GREEN_SIZE, &g);
s_egl.eglGetConfigAttrib(fb->m_eglDisplay, c, EGL_BLUE_SIZE, &b);
s_egl.eglGetConfigAttrib(fb->m_eglDisplay, c, EGL_ALPHA_SIZE, &a);
if (r == wantedRedSize &&
g == wantedGreenSize &&
b == wantedBlueSize &&
a == wantedAlphaSize) {
total_exact_matches++;
exact_match_indices.push_back(i);
}
}
if (exact_match_indices.size() == 0) {
ERR("Failed on eglChooseConfig\n");
free(gles2Extensions);
delete fb;
return false;
}
fb->m_eglConfig = all_configs[exact_match_indices[0]];
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(gles2Extensions);
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(gles2Extensions);
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(gles2Extensions);
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(gles2Extensions);
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("GLES2 [%s]\n", gles2Extensions);
has_gl_oes_image = true;
if (has_gl_oes_image) {
has_gl_oes_image &= strstr(gles2Extensions, "GL_OES_EGL_image") != NULL;
}
free((void*)gles2Extensions);
gles2Extensions = 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++;
}
}
//
// Don't fail initialization if no GLES configs exist
//
//
// If no configs at all, exit
//
if (nGLConfigs + 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();
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_fpsStats(getenv("SHOW_FPS_STATS") != nullptr),
m_colorBufferHelper(new ColorBufferHelper(this)) {}
FrameBuffer::~FrameBuffer() {
delete m_textureDraw;
delete m_configs;
delete m_colorBufferHelper;
free(m_fbImage);
}
void FrameBuffer::setPostCallback(emugl::Renderer::OnPostCallback 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<FrameBuffer*>(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;
}
emugl::Mutex::AutoLock mutex(m_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.
bool updateSubWindow = !(m_x == wx && m_y == wy &&
m_windowWidth == ww && m_windowHeight == wh);
// On Mac, since window coordinates are Y-up and not Y-down, the
// subwindow may not change dimensions, but because the main window
// did, the subwindow technically needs to be re-positioned. This
// can happen on rotation, so a change in Z-rotation can be checked
// for this case. However, this *should not* be done on Windows/Linux,
// because the functions used to resize a native window on those hosts
// will block if the shape doesn't actually change, freezing the
// emulator.
#if defined(__APPLE__)
updateSubWindow |= (m_zRot != zRot);
#endif
if (updateSubWindow) {
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();
}
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;
emugl::Mutex::AutoLock mutex(m_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;
}
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.get() != NULL) {
ret = genHandle();
m_colorbuffers[ret].cb = cb;
m_colorbuffers[ret].refcount = 1;
RenderThreadInfo *tInfo = RenderThreadInfo::get();
uint64_t puid = tInfo->m_puid;
if (puid) {
m_procOwnedColorBuffers[puid].insert(ret);
}
}
return ret;
}
HandleType FrameBuffer::createRenderContext(int p_config, HandleType p_share,
bool p_isGL2)
{
emugl::Mutex::AutoLock mutex(m_lock);
emugl::ReadWriteMutex::AutoWriteLock contextLock(m_contextStructureLock);
HandleType ret = 0;
const FbConfig* config = getConfigs()->get(p_config);
if (!config) {
return ret;
}
RenderContextPtr share;
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.get() ? share->getEGLContext() : EGL_NO_CONTEXT;
RenderContextPtr rctx(RenderContext::create(
m_eglDisplay, config->getEglConfig(), sharedContext, p_isGL2));
if (rctx.get() != NULL) {
ret = genHandle();
m_contexts[ret] = rctx;
RenderThreadInfo *tinfo = RenderThreadInfo::get();
uint64_t puid = tinfo->m_puid;
// The new emulator manages render contexts per guest process.
// Fall back to per-thread management if the system image does not
// support it.
if (puid) {
m_procOwnedRenderContext[puid].insert(ret);
} else {
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.get() != NULL) {
ret = genHandle();
m_windows[ret] = std::pair<WindowSurfacePtr, HandleType>(win,0);
RenderThreadInfo *tinfo = RenderThreadInfo::get();
tinfo->m_windowSet.insert(ret);
}
return ret;
}
void FrameBuffer::drainRenderContext()
{
emugl::Mutex::AutoLock mutex(m_lock);
emugl::ReadWriteMutex::AutoWriteLock contextLock(m_contextStructureLock);
RenderThreadInfo *tinfo = RenderThreadInfo::get();
if (tinfo->m_contextSet.empty()) return;
for (std::set<HandleType>::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<HandleType>::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);
emugl::ReadWriteMutex::AutoWriteLock contextLock(m_contextStructureLock);
m_contexts.erase(p_context);
RenderThreadInfo *tinfo = RenderThreadInfo::get();
uint64_t puid = tinfo->m_puid;
// The new emulator manages render contexts per guest process.
// Fall back to per-thread management if the system image does not
// support it.
if (puid) {
auto ite = m_procOwnedRenderContext.find(puid);
if (ite != m_procOwnedRenderContext.end()) {
ite->second.erase(p_context);
}
} else {
tinfo->m_contextSet.erase(p_context);
}
}
void FrameBuffer::DestroyWindowSurface(HandleType p_surface)
{
emugl::Mutex::AutoLock mutex(m_lock);
if (m_windows.erase(p_surface) != 0) {
RenderThreadInfo *tinfo = RenderThreadInfo::get();
tinfo->m_windowSet.erase(p_surface);
}
}
int FrameBuffer::openColorBuffer(HandleType p_colorbuffer) {
RenderThreadInfo *tInfo = RenderThreadInfo::get();
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++;
uint64_t puid = tInfo->m_puid;
if (puid) {
m_procOwnedColorBuffers[puid].insert(p_colorbuffer);
}
return 0;
}
void FrameBuffer::closeColorBuffer(HandleType p_colorbuffer) {
RenderThreadInfo *tInfo = RenderThreadInfo::get();
emugl::Mutex::AutoLock mutex(m_lock);
closeColorBufferLocked(p_colorbuffer);
uint64_t puid = tInfo->m_puid;
if (puid) {
auto ite = m_procOwnedColorBuffers.find(puid);
if (ite != m_procOwnedColorBuffers.end()) {
ite->second.erase(p_colorbuffer);
}
}
}
void FrameBuffer::closeColorBufferLocked(HandleType p_colorbuffer) {
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);
}
}
void FrameBuffer::cleanupProcGLObjects(uint64_t puid) {
emugl::Mutex::AutoLock mutex(m_lock);
// Clean up color buffers.
// A color buffer needs to be closed as many times as it is opened by
// the guest process, to give the correct reference count.
// (Note that a color buffer can be shared across guest processes.)
{
auto procIte = m_procOwnedColorBuffers.find(puid);
if (procIte != m_procOwnedColorBuffers.end()) {
for (auto cb: procIte->second) {
closeColorBufferLocked(cb);
}
m_procOwnedColorBuffers.erase(procIte);
}
}
// Clean up EGLImage handles
{
// Bind context before potentially triggering any gl calls
ScopedBind bind(this);
auto procIte = m_procOwnedEGLImages.find(puid);
if (procIte != m_procOwnedEGLImages.end()) {
for (auto eglImg : procIte->second) {
s_egl.eglDestroyImageKHR(m_eglDisplay,
reinterpret_cast<EGLImageKHR>((HandleType)eglImg));
}
m_procOwnedEGLImages.erase(procIte);
}
}
// Cleanup render contexts
{
auto procIte = m_procOwnedRenderContext.find(puid);
if (procIte != m_procOwnedRenderContext.end()) {
for (auto ctx: procIte->second) {
m_contexts.erase(ctx);
}
m_procOwnedRenderContext.erase(procIte);
}
}
}
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.get();
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, read;
RenderContextPtr ctx;
//
// 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;
}
if (ctx) {
if (ctx.get()->getEmulatedGLES1Context()) {
DBG("%s: found emulated gles1 context @ %p\n", __FUNCTION__, ctx.get()->getEmulatedGLES1Context());
s_gles1.set_current_gles_context(ctx.get()->getEmulatedGLES1Context());
DBG("%s: set emulated gles1 context current in thread info\n", __FUNCTION__);
if (draw.get() == NULL) {
DBG("%s: setup make current (null draw surface)\n", __FUNCTION__);
s_gles1.make_current_setup(0, 0);
} else {
DBG("%s: setup make current (draw surface %ux%u)\n", __FUNCTION__, draw->getWidth(), draw->getHeight());
s_gles1.make_current_setup(draw->getWidth(), draw->getHeight());
}
DBG("%s: set up the emulated gles1 context's info\n", __FUNCTION__);
}
}
//
// Bind the surface(s) to the context
//
RenderThreadInfo *tinfo = RenderThreadInfo::get();
WindowSurfacePtr bindDraw, bindRead;
if (draw.get() == NULL && read.get() == NULL) {
// Unbind the current read and draw surfaces from the context
bindDraw = tinfo->currDrawSurf;
bindRead = tinfo->currReadSurf;
} else {
bindDraw = draw;
bindRead = read;
}
if (bindDraw.get() != NULL && bindRead.get() != NULL) {
if (bindDraw.get() != bindRead.get()) {
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;
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<EGLClientBuffer>(buffer), NULL);
HandleType imgHnd = (HandleType)reinterpret_cast<uintptr_t>(image);
RenderThreadInfo *tInfo = RenderThreadInfo::get();
uint64_t puid = tInfo->m_puid;
if (puid) {
emugl::Mutex::AutoLock mutex(m_lock);
m_procOwnedEGLImages[puid].insert(imgHnd);
}
return imgHnd;
}
EGLBoolean FrameBuffer::destroyClientImage(HandleType image) {
// eglDestroyImageKHR has its own lock already.
EGLBoolean ret = s_egl.eglDestroyImageKHR(m_eglDisplay,
reinterpret_cast<EGLImageKHR>(image));
if (!ret) return false;
RenderThreadInfo *tInfo = RenderThreadInfo::get();
uint64_t puid = tInfo->m_puid;
if (puid) {
emugl::Mutex::AutoLock mutex(m_lock);
m_procOwnedEGLImages[puid].erase(image);
// We don't explicitly call m_procOwnedEGLImages.erase(puid) when the size
// reaches 0, since it could go between zero and one many times in the
// lifetime of a process.
// It will be cleaned up by cleanupProcGLObjects(puid) when the process is
// dead.
}
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 (prevContext != m_pbufContext || prevReadSurf != m_pbufSurface
|| prevDrawSurf != m_pbufSurface) {
if (!s_egl.eglMakeCurrent(m_eglDisplay, m_pbufSurface,
m_pbufSurface, m_pbufContext)) {
ERR("eglMakeCurrent failed\n");
return false;
}
} else {
ERR("Nested %s call detected, should never happen", __func__);
}
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 (prevContext != m_eglContext || prevReadSurf != m_eglSurface
|| prevDrawSurf != m_eglSurface) {
if (!s_egl.eglMakeCurrent(m_eglDisplay, m_eglSurface,
m_eglSurface, m_eglContext)) {
ERR("eglMakeCurrent failed\n");
return false;
}
} else {
ERR("Nested %s call detected, should never happen", __func__);
}
//
// 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()
{
EGLContext curContext = s_egl.eglGetCurrentContext();
EGLSurface curReadSurf = s_egl.eglGetCurrentSurface(EGL_READ);
EGLSurface curDrawSurf = s_egl.eglGetCurrentSurface(EGL_DRAW);
if (m_prevContext != curContext || m_prevReadSurf != curReadSurf ||
m_prevDrawSurf != curDrawSurf) {
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;
}
void FrameBuffer::createTrivialContext(HandleType shared,
HandleType* contextOut,
HandleType* surfOut) {
assert(contextOut);
assert(surfOut);
*contextOut = createRenderContext(0, shared, true);
// Zero size is formally allowed here, but SwiftShader doesn't like it and
// fails.
*surfOut = createWindowSurface(0, 1, 1);
}
bool FrameBuffer::post(HandleType p_colorbuffer, bool needLockAndBind)
{
if (needLockAndBind) {
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 (needLockAndBind && !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
if (needLockAndBind) {
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 = android::base::System::get()->getHighResTimeUs() / 1000;
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 (needLockAndBind) {
m_lock.unlock();
}
return ret;
}
bool FrameBuffer::repost() {
if (m_lastPostedColorBuffer) {
return post(m_lastPostedColorBuffer);
}
return false;
}