X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=blobdiff_plain;f=src%2Ftype3_AndroidCloud%2Fanbox-master%2Fsrc%2Fanbox%2Fgraphics%2Femugl%2FRenderer.cpp;fp=src%2Ftype3_AndroidCloud%2Fanbox-master%2Fsrc%2Fanbox%2Fgraphics%2Femugl%2FRenderer.cpp;h=e3e057b64023412da1346f046355af2b9309ab7c;hb=e26c1ec581be598521517829adba8c8dd23a768f;hp=0000000000000000000000000000000000000000;hpb=6699c1aea74eeb0eb400e6299079f0c7576f716f;p=iec.git diff --git a/src/type3_AndroidCloud/anbox-master/src/anbox/graphics/emugl/Renderer.cpp b/src/type3_AndroidCloud/anbox-master/src/anbox/graphics/emugl/Renderer.cpp new file mode 100644 index 0000000..e3e057b --- /dev/null +++ b/src/type3_AndroidCloud/anbox-master/src/anbox/graphics/emugl/Renderer.cpp @@ -0,0 +1,964 @@ +/* +* 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. +*/ +#define GLM_ENABLE_EXPERIMENTAL +#include "anbox/graphics/emugl/Renderer.h" +#include "anbox/graphics/emugl/DispatchTables.h" +#include "anbox/graphics/emugl/RenderThreadInfo.h" +#include "anbox/graphics/emugl/TimeUtils.h" +#include "anbox/graphics/gl_extensions.h" +#include "anbox/logger.h" + +#include "external/android-emugl/host/include/OpenGLESDispatch/EGLDispatch.h" + +// Generated with emugl at build time +#include "gles2_dec.h" + +#include + +#include +#include +#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(Renderer *fb) : mFb(fb) { + if (!fb->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: + Renderer *mFb; +}; + +// Implementation of a ColorBuffer::Helper instance that redirects calls +// to a FrameBuffer instance. +class ColorBufferHelper : public ColorBuffer::Helper { + public: + ColorBufferHelper(Renderer *fb) : mFb(fb) {} + + virtual bool setupContext() { return mFb->bind_locked(); } + + virtual void teardownContext() { mFb->unbind_locked(); } + + virtual TextureDraw *getTextureDraw() const { return mFb->getTextureDraw(); } + + private: + Renderer *mFb; +}; +} // namespace + +HandleType Renderer::s_nextHandle = 0; + +void Renderer::finalize() { + m_colorbuffers.clear(); + 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 Renderer::initialize(EGLNativeDisplayType nativeDisplay) { + m_eglDisplay = s_egl.eglGetDisplay(nativeDisplay); + if (m_eglDisplay == EGL_NO_DISPLAY) { + ERROR("Failed to Initialize backend EGL display"); + return false; + } + + if (!s_egl.eglInitialize(m_eglDisplay, &m_caps.eglMajor, &m_caps.eglMinor)) { + ERROR("Failed to initialize EGL"); + return false; + } + + anbox::graphics::GLExtensions egl_extensions{s_egl.eglQueryString(m_eglDisplay, EGL_EXTENSIONS)}; + + const auto surfaceless_supported = egl_extensions.support("EGL_KHR_surfaceless_context"); + if (!surfaceless_supported) + DEBUG("EGL doesn't support surfaceless context"); + + s_egl.eglBindAPI(EGL_OPENGL_ES_API); + + // Create EGL context for framebuffer post rendering. + GLint surfaceType = EGL_WINDOW_BIT | EGL_PBUFFER_BIT; + const GLint configAttribs[] = {EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_SURFACE_TYPE, surfaceType, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE}; + + int n; + if ((s_egl.eglChooseConfig(m_eglDisplay, configAttribs, &m_eglConfig, + 1, &n) == EGL_FALSE) || n == 0) { + ERROR("Failed to select EGL configuration"); + return false; + } + + static const GLint glContextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE}; + + m_eglContext = s_egl.eglCreateContext(m_eglDisplay, m_eglConfig, + EGL_NO_CONTEXT, glContextAttribs); + if (m_eglContext == EGL_NO_CONTEXT) { + ERROR("Failed to create context: error=0x%x", s_egl.eglGetError()); + 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. + m_pbufContext = s_egl.eglCreateContext(m_eglDisplay, m_eglConfig, m_eglContext, glContextAttribs); + if (m_pbufContext == EGL_NO_CONTEXT) { + ERROR("Failed to create pbuffer context: error=0x%x", s_egl.eglGetError()); + return false; + } + + if (!surfaceless_supported) { + // 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}; + + m_pbufSurface = s_egl.eglCreatePbufferSurface(m_eglDisplay, m_eglConfig, pbufAttribs); + if (m_pbufSurface == EGL_NO_SURFACE) { + ERROR("Failed to create pbuffer surface: error=0x%x", s_egl.eglGetError()); + return false; + } + } else { + DEBUG("Using a surfaceless EGL context"); + m_pbufSurface = EGL_NO_SURFACE; + } + + // Make the context current + ScopedBind bind(this); + if (!bind.isValid()) { + ERROR("Failed to make current"); + return false; + } + + anbox::graphics::GLExtensions gl_extensions{reinterpret_cast(s_gles2.glGetString(GL_EXTENSIONS))}; + if (gl_extensions.support("GL_OES_EGL_image")) { + m_caps.has_eglimage_texture_2d = egl_extensions.support("EGL_KHR_gl_texture_2D_image"); + m_caps.has_eglimage_renderbuffer = egl_extensions.support("EGL_KHR_gl_renderbuffer_image"); + } else { + m_caps.has_eglimage_texture_2d = false; + 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 + if (!m_caps.has_eglimage_texture_2d) { + ERROR("Failed: Missing egl_image related extension(s)"); + bind.release(); + return false; + } + + // Initialize set of configs + m_configs = new RendererConfigList(m_eglDisplay); + if (m_configs->empty()) { + ERROR("Failed: Initialize set of configs"); + bind.release(); + return false; + } + + // Check that we have config for each GLES and GLES2 + size_t nConfigs = m_configs->size(); + int nGLConfigs = 0; + int nGL2Configs = 0; + for (size_t i = 0; i < nConfigs; ++i) { + GLint rtype = 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(); + return false; + } + + // If no GLES2 configs exist - not GLES2 capability + if (nGL2Configs == 0) { + ERROR("Failed: No GLES 2.x configs found!"); + bind.release(); + return false; + } + + // Cache the GL strings so we don't have to think about threading or + // current-context when asked for them. + m_glVendor = reinterpret_cast(s_gles2.glGetString(GL_VENDOR)); + m_glRenderer = reinterpret_cast(s_gles2.glGetString(GL_RENDERER)); + m_glVersion = reinterpret_cast(s_gles2.glGetString(GL_VERSION)); + + m_textureDraw = new TextureDraw(m_eglDisplay); + if (!m_textureDraw) { + ERROR("Failed: creation of TextureDraw instance"); + bind.release(); + return false; + } + + m_defaultProgram = m_family.add_program(vshader, defaultFShader); + m_alphaProgram = m_family.add_program(vshader, alphaFShader); + + bind.release(); + + DEBUG("Successfully initialized EGL"); + + return true; +} + +Renderer::Program::Program(GLuint program_id) { + id = program_id; + position_attr = s_gles2.glGetAttribLocation(id, "position"); + texcoord_attr = s_gles2.glGetAttribLocation(id, "texcoord"); + tex_uniform = s_gles2.glGetUniformLocation(id, "tex"); + center_uniform = s_gles2.glGetUniformLocation(id, "center"); + display_transform_uniform = + s_gles2.glGetUniformLocation(id, "display_transform"); + transform_uniform = s_gles2.glGetUniformLocation(id, "transform"); + screen_to_gl_coords_uniform = + s_gles2.glGetUniformLocation(id, "screen_to_gl_coords"); + alpha_uniform = s_gles2.glGetUniformLocation(id, "alpha"); +} + +Renderer::Renderer() + : m_configs(NULL), + m_eglDisplay(EGL_NO_DISPLAY), + m_colorBufferHelper(new ColorBufferHelper(this)), + 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_textureDraw(NULL), + m_lastPostedColorBuffer(0), + m_statsNumFrames(0), + m_statsStartTime(0LL), + m_glVendor(NULL), + m_glRenderer(NULL), + m_glVersion(NULL) { + m_fpsStats = getenv("SHOW_FPS_STATS") != NULL; +} + +Renderer::~Renderer() { + delete m_textureDraw; + delete m_configs; + delete m_colorBufferHelper; +} + +struct RendererWindow { + EGLNativeWindowType native_window = 0; + EGLSurface surface = EGL_NO_SURFACE; + anbox::graphics::Rect viewport; + glm::mat4 screen_to_gl_coords = glm::mat4(1.0f); + glm::mat4 display_transform = glm::mat4(1.0f); +}; + +RendererWindow *Renderer::createNativeWindow( + EGLNativeWindowType native_window) { + m_lock.lock(); + + auto window = new RendererWindow; + window->native_window = native_window; + window->surface = s_egl.eglCreateWindowSurface( + m_eglDisplay, m_eglConfig, window->native_window, nullptr); + if (window->surface == EGL_NO_SURFACE) { + delete window; + m_lock.unlock(); + return nullptr; + } + + if (!bindWindow_locked(window)) { + s_egl.eglDestroySurface(m_eglDisplay, window->surface); + delete window; + m_lock.unlock(); + return nullptr; + } + + s_gles2.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | + GL_STENCIL_BUFFER_BIT); + s_egl.eglSwapBuffers(m_eglDisplay, window->surface); + + unbind_locked(); + + m_nativeWindows.insert({native_window, window}); + + m_lock.unlock(); + + return window; +} + +void Renderer::destroyNativeWindow(EGLNativeWindowType native_window) { + auto w = m_nativeWindows.find(native_window); + if (w == m_nativeWindows.end()) return; + + m_lock.lock(); + + s_egl.eglMakeCurrent(m_eglDisplay, nullptr, nullptr, nullptr); + + if (w->second->surface != EGL_NO_SURFACE) + s_egl.eglDestroySurface(m_eglDisplay, w->second->surface); + + delete w->second; + m_nativeWindows.erase(w); + + m_lock.unlock(); +} + +HandleType Renderer::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 Renderer::createColorBuffer(int p_width, int p_height, + GLenum p_internalFormat) { + std::unique_lock l(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) { + ret = genHandle(); + m_colorbuffers[ret].cb = cb; + m_colorbuffers[ret].refcount = 1; + } + return ret; +} + +HandleType Renderer::createRenderContext(int p_config, HandleType p_share, + bool p_isGL2) { + std::unique_lock l(m_lock); + + HandleType ret = 0; + + const RendererConfig *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 ? share->getEGLContext() : EGL_NO_CONTEXT; + + RenderContextPtr rctx(RenderContext::create( + m_eglDisplay, config->getEglConfig(), sharedContext, p_isGL2)); + if (rctx) { + ret = genHandle(); + m_contexts[ret] = rctx; + RenderThreadInfo *tinfo = RenderThreadInfo::get(); + tinfo->m_contextSet.insert(ret); + } + return ret; +} + +HandleType Renderer::createWindowSurface(int p_config, int p_width, + int p_height) { + std::unique_lock l(m_lock); + + HandleType ret = 0; + + const RendererConfig *config = getConfigs()->get(p_config); + if (!config) { + return ret; + } + + WindowSurfacePtr win(WindowSurface::create( + getDisplay(), config->getEglConfig(), p_width, p_height)); + if (win) { + ret = genHandle(); + m_windows[ret] = std::pair(win, 0); + RenderThreadInfo *tinfo = RenderThreadInfo::get(); + tinfo->m_windowSet.insert(ret); + } + + return ret; +} + +void Renderer::drainRenderContext() { + std::unique_lock l(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 Renderer::drainWindowSurface() { + std::unique_lock l(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 Renderer::DestroyRenderContext(HandleType p_context) { + std::unique_lock l(m_lock); + + m_contexts.erase(p_context); + RenderThreadInfo *tinfo = RenderThreadInfo::get(); + if (tinfo->m_contextSet.empty()) return; + tinfo->m_contextSet.erase(p_context); +} + +void Renderer::DestroyWindowSurface(HandleType p_surface) { + std::unique_lock l(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 Renderer::openColorBuffer(HandleType p_colorbuffer) { + std::unique_lock l(m_lock); + + ColorBufferMap::iterator c(m_colorbuffers.find(p_colorbuffer)); + if (c == m_colorbuffers.end()) { + // bad colorbuffer handle + ERROR("FB: openColorBuffer cb handle %#x not found", p_colorbuffer); + return -1; + } + (*c).second.refcount++; + return 0; +} + +void Renderer::closeColorBuffer(HandleType p_colorbuffer) { + std::unique_lock l(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 Renderer::flushWindowSurfaceColorBuffer(HandleType p_surface) { + std::unique_lock l(m_lock); + + WindowSurfaceMap::iterator w(m_windows.find(p_surface)); + if (w == m_windows.end()) { + ERROR("FB::flushWindowSurfaceColorBuffer: window handle %#x not found", + p_surface); + // bad surface handle + return false; + } + + auto surface = (*w).second.first; + if (!surface) + return false; + + surface->flushColorBuffer(); + + return true; +} + +bool Renderer::setWindowSurfaceColorBuffer(HandleType p_surface, + HandleType p_colorbuffer) { + std::unique_lock l(m_lock); + + WindowSurfaceMap::iterator w(m_windows.find(p_surface)); + if (w == m_windows.end()) { + // bad surface handle + ERROR("%s: bad window surface handle %#x", __FUNCTION__, p_surface); + return false; + } + + ColorBufferMap::iterator c(m_colorbuffers.find(p_colorbuffer)); + if (c == m_colorbuffers.end()) { + DEBUG("%s: bad color buffer handle %#x", __FUNCTION__, p_colorbuffer); + // bad colorbuffer handle + return false; + } + + (*w).second.first->setColorBuffer((*c).second.cb); + (*w).second.second = p_colorbuffer; + return true; +} + +void Renderer::readColorBuffer(HandleType p_colorbuffer, int x, int y, + int width, int height, GLenum format, + GLenum type, void *pixels) { + std::unique_lock l(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 Renderer::updateColorBuffer(HandleType p_colorbuffer, int x, int y, + int width, int height, GLenum format, + GLenum type, void *pixels) { + std::unique_lock l(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 Renderer::bindColorBufferToTexture(HandleType p_colorbuffer) { + std::unique_lock l(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 Renderer::bindColorBufferToRenderbuffer(HandleType p_colorbuffer) { + std::unique_lock l(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 Renderer::bindContext(HandleType p_context, HandleType p_drawSurface, + HandleType p_readSurface) { + std::unique_lock l(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)) { + ERROR("eglMakeCurrent failed: 0x%04x", s_egl.eglGetError()); + return false; + } + + // + // Bind the surface(s) to the context + // + RenderThreadInfo *tinfo = RenderThreadInfo::get(); + WindowSurfacePtr bindDraw, bindRead; + if (!draw && !read) { + // Unbind the current read and draw surfaces from the context + bindDraw = tinfo->currDrawSurf; + bindRead = tinfo->currReadSurf; + } else { + bindDraw = draw; + bindRead = read; + } + + if (bindDraw && bindRead) { + if (bindDraw != bindRead) { + 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 Renderer::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 static_cast(reinterpret_cast(image)); +} + +EGLBoolean Renderer::destroyClientImage(HandleType image) { + return s_egl.eglDestroyImageKHR(m_eglDisplay, + reinterpret_cast(image)); +} + +// +// The framebuffer lock should be held when calling this function ! +// +bool Renderer::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)) { + ERROR("eglMakeCurrent failed: 0x%04x", s_egl.eglGetError()); + return false; + } + + m_prevContext = prevContext; + m_prevReadSurf = prevReadSurf; + m_prevDrawSurf = prevDrawSurf; + return true; +} + +bool Renderer::bindWindow_locked(RendererWindow *window) { + 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, window->surface, window->surface, + m_eglContext)) { + ERROR("eglMakeCurrent failed"); + return false; + } + + m_prevContext = prevContext; + m_prevReadSurf = prevReadSurf; + m_prevDrawSurf = prevDrawSurf; + return true; +} + +bool Renderer::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; +} + +const GLchar *const Renderer::vshader = { + "attribute vec3 position;" + "attribute vec2 texcoord;" + "uniform mat4 screen_to_gl_coords;" + "uniform mat4 display_transform;" + "uniform mat4 transform;" + "uniform vec2 center;" + "varying vec2 v_texcoord;" + "void main() {" + " vec4 mid = vec4(center, 0.0, 0.0);" + " vec4 transformed = (transform * (vec4(position, 1.0) - mid)) + mid;" + " gl_Position = display_transform * screen_to_gl_coords * transformed;" + " v_texcoord = texcoord;" + "}"}; + +const GLchar *const Renderer::alphaFShader = { + "precision mediump float;" + "uniform sampler2D tex;" + "uniform float alpha;" + "varying vec2 v_texcoord;" + "void main() {" + " vec4 frag = texture2D(tex, v_texcoord);" + " gl_FragColor = alpha*frag;" + "}"}; + +const GLchar *const Renderer::defaultFShader = + { // This is the fastest fragment shader. Use it when you can. + "precision mediump float;" + "uniform sampler2D tex;" + "varying vec2 v_texcoord;" + "void main() {" + " gl_FragColor = texture2D(tex, v_texcoord);" + "}"}; + +void Renderer::setupViewport(RendererWindow *window, + const anbox::graphics::Rect &rect) { + /* + * Here we provide a 3D perspective projection with a default 30 degrees + * vertical field of view. This projection matrix is carefully designed + * such that any vertices at depth z=0 will fit the screen coordinates. So + * client texels will fit screen pixels perfectly as long as the surface is + * at depth zero. But if you want to do anything fancy, you can also choose + * a different depth and it will appear to come out of or go into the + * screen. + */ + window->screen_to_gl_coords = + glm::translate(glm::mat4(1.0f), glm::vec3{-1.0f, 1.0f, 0.0f}); + + /* + * Perspective division is one thing that can't be done in a matrix + * multiplication. It happens after the matrix multiplications. GL just + * scales {x,y} by 1/w. So modify the final part of the projection matrix + * to set w ([3]) to be the incoming z coordinate ([2]). + */ + window->screen_to_gl_coords[2][3] = -1.0f; + + float const vertical_fov_degrees = 30.0f; + float const near = (rect.height() / 2.0f) / + std::tan((vertical_fov_degrees * M_PI / 180.0f) / 2.0f); + float const far = -near; + + window->screen_to_gl_coords = + glm::scale(window->screen_to_gl_coords, + glm::vec3{2.0f / rect.width(), -2.0f / rect.height(), + 2.0f / (near - far)}); + window->screen_to_gl_coords = glm::translate( + window->screen_to_gl_coords, glm::vec3{-rect.left(), -rect.top(), 0.0f}); + + window->viewport = rect; +} + +void Renderer::tessellate(std::vector &primitives, + const anbox::graphics::Rect &buf_size, + const Renderable &renderable) { + auto rect = renderable.screen_position(); + GLfloat left = rect.left(); + GLfloat right = rect.right(); + GLfloat top = rect.top(); + GLfloat bottom = rect.bottom(); + + anbox::graphics::Primitive rectangle; + rectangle.tex_id = 0; + rectangle.type = GL_TRIANGLE_STRIP; + + GLfloat tex_left = + static_cast(renderable.crop().left()) / buf_size.width(); + GLfloat tex_top = + static_cast(renderable.crop().top()) / buf_size.height(); + GLfloat tex_right = + static_cast(renderable.crop().right()) / buf_size.width(); + GLfloat tex_bottom = + static_cast(renderable.crop().bottom()) / buf_size.height(); + + auto &vertices = rectangle.vertices; + vertices[0] = {{left, top, 0.0f}, {tex_left, tex_top}}; + vertices[1] = {{left, bottom, 0.0f}, {tex_left, tex_bottom}}; + vertices[2] = {{right, top, 0.0f}, {tex_right, tex_top}}; + vertices[3] = {{right, bottom, 0.0f}, {tex_right, tex_bottom}}; + + primitives.resize(1); + primitives[0] = rectangle; +} + +void Renderer::draw(RendererWindow *window, const Renderable &renderable, + const Program &prog) { + const auto &color_buffer = m_colorbuffers.find(renderable.buffer()); + if (color_buffer == m_colorbuffers.end()) return; + + const auto &cb = color_buffer->second.cb; + + s_gles2.glUseProgram(prog.id); + s_gles2.glUniform1i(prog.tex_uniform, 0); + s_gles2.glUniformMatrix4fv(prog.display_transform_uniform, 1, GL_FALSE, + glm::value_ptr(window->display_transform)); + s_gles2.glUniformMatrix4fv(prog.screen_to_gl_coords_uniform, 1, GL_FALSE, + glm::value_ptr(window->screen_to_gl_coords)); + + s_gles2.glActiveTexture(GL_TEXTURE0); + + auto const &rect = renderable.screen_position(); + GLfloat centerx = rect.left() + rect.width() / 2.0f; + GLfloat centery = rect.top() + rect.height() / 2.0f; + s_gles2.glUniform2f(prog.center_uniform, centerx, centery); + + s_gles2.glUniformMatrix4fv(prog.transform_uniform, 1, GL_FALSE, + glm::value_ptr(renderable.transformation())); + + if (prog.alpha_uniform >= 0) + s_gles2.glUniform1f(prog.alpha_uniform, renderable.alpha()); + + s_gles2.glEnableVertexAttribArray(prog.position_attr); + s_gles2.glEnableVertexAttribArray(prog.texcoord_attr); + + m_primitives.clear(); + tessellate(m_primitives, { + static_cast(cb->getWidth()), + static_cast(cb->getHeight())}, renderable); + + for (auto const &p : m_primitives) { + cb->bind(); + + s_gles2.glVertexAttribPointer(prog.position_attr, 3, GL_FLOAT, GL_FALSE, + sizeof(anbox::graphics::Vertex), + &p.vertices[0].position); + s_gles2.glVertexAttribPointer(prog.texcoord_attr, 2, GL_FLOAT, GL_FALSE, + sizeof(anbox::graphics::Vertex), + &p.vertices[0].texcoord); + + s_gles2.glEnable(GL_BLEND); + s_gles2.glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, + GL_ONE_MINUS_SRC_ALPHA); + + s_gles2.glDrawArrays(p.type, 0, p.nvertices); + } + + s_gles2.glDisableVertexAttribArray(prog.texcoord_attr); + s_gles2.glDisableVertexAttribArray(prog.position_attr); +} + +bool Renderer::draw(EGLNativeWindowType native_window, + const anbox::graphics::Rect &window_frame, + const RenderableList &renderables) { + + std::unique_lock l(m_lock); + + auto w = m_nativeWindows.find(native_window); + if (w == m_nativeWindows.end()) return false; + + if (!bindWindow_locked(w->second)) + return false; + + setupViewport(w->second, window_frame); + s_gles2.glViewport(0, 0, window_frame.width(), window_frame.height()); + s_gles2.glClearColor(0.0, 0.0, 0.0, 1.0); + s_gles2.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + s_gles2.glClear(GL_COLOR_BUFFER_BIT); + + for (const auto &r : renderables) + draw(w->second, r, r.alpha() < 1.0f ? m_alphaProgram : m_defaultProgram); + + s_egl.eglSwapBuffers(m_eglDisplay, w->second->surface); + + unbind_locked(); + + return false; +}