/* * 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; }