/* * Copyright (C) 2016 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 "OpenGLPipeline.h" #include "DeferredLayerUpdater.h" #include "EglManager.h" #include "Frame.h" #include "GlLayer.h" #include "OpenGLReadback.h" #include "ProfileRenderer.h" #include "renderstate/RenderState.h" #include "TreeInfo.h" #include <cutils/properties.h> #include <strings.h> namespace android { namespace uirenderer { namespace renderthread { OpenGLPipeline::OpenGLPipeline(RenderThread& thread) : mEglManager(thread.eglManager()), mRenderThread(thread) {} MakeCurrentResult OpenGLPipeline::makeCurrent() { // TODO: Figure out why this workaround is needed, see b/13913604 // In the meantime this matches the behavior of GLRenderer, so it is not a regression EGLint error = 0; bool haveNewSurface = mEglManager.makeCurrent(mEglSurface, &error); Caches::getInstance().textureCache.resetMarkInUse(this); if (!haveNewSurface) { return MakeCurrentResult::AlreadyCurrent; } return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded; } Frame OpenGLPipeline::getFrame() { LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, "drawRenderNode called on a context with no surface!"); return mEglManager.beginFrame(mEglSurface); } bool OpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) { mEglManager.damageFrame(frame, dirty); bool drew = false; auto& caches = Caches::getInstance(); FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), lightGeometry, caches); frameBuilder.deferLayers(*layerUpdateQueue); layerUpdateQueue->clear(); frameBuilder.deferRenderNodeScene(renderNodes, contentDrawBounds); BakedOpRenderer renderer(caches, mRenderThread.renderState(), opaque, wideColorGamut, lightInfo); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); ProfileRenderer profileRenderer(renderer); profiler->draw(profileRenderer); drew = renderer.didDraw(); // post frame cleanup caches.clearGarbage(); caches.pathCache.trim(); caches.tessellationCache.trim(); #if DEBUG_MEMORY_USAGE caches.dumpMemoryUsage(); #else if (CC_UNLIKELY(Properties::debugLevel & kDebugMemory)) { caches.dumpMemoryUsage(); } #endif return drew; } bool OpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) { GL_CHECKPOINT(LOW); // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point currentFrameInfo->markSwapBuffers(); *requireSwap = drew || mEglManager.damageRequiresSwap(); if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) { return false; } return *requireSwap; } bool OpenGLPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { ATRACE_CALL(); // acquire most recent buffer for drawing layer->updateTexImage(); layer->apply(); return OpenGLReadbackImpl::copyLayerInto(mRenderThread, static_cast<GlLayer&>(*layer->backingLayer()), bitmap); } static Layer* createLayer(RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight, sk_sp<SkColorFilter> colorFilter, int alpha, SkBlendMode mode, bool blend) { GlLayer* layer = new GlLayer(renderState, layerWidth, layerHeight, colorFilter, alpha, mode, blend); Caches::getInstance().textureState().activateTexture(0); layer->generateTexture(); return layer; } DeferredLayerUpdater* OpenGLPipeline::createTextureLayer() { mEglManager.initialize(); return new DeferredLayerUpdater(mRenderThread.renderState(), createLayer, Layer::Api::OpenGL); } void OpenGLPipeline::onStop() { if (mEglManager.isCurrent(mEglSurface)) { mEglManager.makeCurrent(EGL_NO_SURFACE); } } bool OpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, ColorMode colorMode) { if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); mEglSurface = EGL_NO_SURFACE; } if (surface) { const bool wideColorGamut = colorMode == ColorMode::WideColorGamut; mEglSurface = mEglManager.createSurface(surface, wideColorGamut); } if (mEglSurface != EGL_NO_SURFACE) { const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer); mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); return true; } return false; } bool OpenGLPipeline::isSurfaceReady() { return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE); } bool OpenGLPipeline::isContextReady() { return CC_LIKELY(mEglManager.hasEglContext()); } void OpenGLPipeline::onDestroyHardwareResources() { Caches& caches = Caches::getInstance(); // Make sure to release all the textures we were owning as there won't // be another draw caches.textureCache.resetMarkInUse(this); mRenderThread.renderState().flush(Caches::FlushMode::Layers); } void OpenGLPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo) { static const std::vector<sp<RenderNode>> emptyNodeList; auto& caches = Caches::getInstance(); FrameBuilder frameBuilder(*layerUpdateQueue, lightGeometry, caches); layerUpdateQueue->clear(); // TODO: Handle wide color gamut contexts BakedOpRenderer renderer(caches, mRenderThread.renderState(), opaque, wideColorGamut, lightInfo); LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case"); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); } TaskManager* OpenGLPipeline::getTaskManager() { return &Caches::getInstance().tasks; } static bool layerMatchesWH(OffscreenBuffer* layer, int width, int height) { return layer->viewportWidth == (uint32_t)width && layer->viewportHeight == (uint32_t)height; } bool OpenGLPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, bool wideColorGamut, ErrorHandler* errorHandler) { RenderState& renderState = mRenderThread.renderState(); OffscreenBufferPool& layerPool = renderState.layerPool(); bool transformUpdateNeeded = false; if (node->getLayer() == nullptr) { node->setLayer( layerPool.get(renderState, node->getWidth(), node->getHeight(), wideColorGamut)); transformUpdateNeeded = true; } else if (!layerMatchesWH(node->getLayer(), node->getWidth(), node->getHeight())) { // TODO: remove now irrelevant, currently enqueued damage (respecting damage ordering) // Or, ideally, maintain damage between frames on node/layer so ordering is always correct if (node->properties().fitsOnLayer()) { node->setLayer(layerPool.resize(node->getLayer(), node->getWidth(), node->getHeight())); } else { destroyLayer(node); } transformUpdateNeeded = true; } if (transformUpdateNeeded && node->getLayer()) { // update the transform in window of the layer to reset its origin wrt light source position Matrix4 windowTransform; damageAccumulator.computeCurrentTransform(&windowTransform); node->getLayer()->setWindowTransform(windowTransform); } if (!node->hasLayer()) { Caches::getInstance().dumpMemoryUsage(); if (errorHandler) { std::ostringstream err; err << "Unable to create layer for " << node->getName(); const int maxTextureSize = Caches::getInstance().maxTextureSize; if (node->getWidth() > maxTextureSize || node->getHeight() > maxTextureSize) { err << ", size " << node->getWidth() << "x" << node->getHeight() << " exceeds max size " << maxTextureSize; } else { err << ", see logcat for more info"; } errorHandler->onError(err.str()); } } return transformUpdateNeeded; } bool OpenGLPipeline::pinImages(LsaVector<sk_sp<Bitmap>>& images) { TextureCache& cache = Caches::getInstance().textureCache; bool prefetchSucceeded = true; for (auto& bitmapResource : images) { prefetchSucceeded &= cache.prefetchAndMarkInUse(this, bitmapResource.get()); } return prefetchSucceeded; } void OpenGLPipeline::unpinImages() { Caches::getInstance().textureCache.resetMarkInUse(this); } void OpenGLPipeline::destroyLayer(RenderNode* node) { if (OffscreenBuffer* layer = node->getLayer()) { layer->renderState.layerPool().putOrDelete(layer); node->setLayer(nullptr); } } void OpenGLPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { if (Caches::hasInstance() && thread.eglManager().hasEglContext()) { ATRACE_NAME("Bitmap#prepareToDraw task"); Caches::getInstance().textureCache.prefetch(bitmap); } } void OpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) { DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext; if (thread.eglManager().hasEglContext()) { mode = DrawGlInfo::kModeProcess; } thread.renderState().invokeFunctor(functor, mode, nullptr); } #define FENCE_TIMEOUT 2000000000 class AutoEglFence { public: AutoEglFence(EGLDisplay display) : mDisplay(display) { fence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL); } ~AutoEglFence() { if (fence != EGL_NO_SYNC_KHR) { eglDestroySyncKHR(mDisplay, fence); } } EGLSyncKHR fence = EGL_NO_SYNC_KHR; private: EGLDisplay mDisplay = EGL_NO_DISPLAY; }; class AutoEglImage { public: AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer) : mDisplay(display) { EGLint imageAttrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs); } ~AutoEglImage() { if (image != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(mDisplay, image); } } EGLImageKHR image = EGL_NO_IMAGE_KHR; private: EGLDisplay mDisplay = EGL_NO_DISPLAY; }; class AutoGlTexture { public: AutoGlTexture(uirenderer::Caches& caches) : mCaches(caches) { glGenTextures(1, &mTexture); caches.textureState().bindTexture(mTexture); } ~AutoGlTexture() { mCaches.textureState().deleteTexture(mTexture); } private: uirenderer::Caches& mCaches; GLuint mTexture = 0; }; static bool uploadBitmapToGraphicBuffer(uirenderer::Caches& caches, SkBitmap& bitmap, GraphicBuffer& buffer, GLint format, GLint type) { EGLDisplay display = eglGetCurrentDisplay(); LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, "Failed to get EGL_DEFAULT_DISPLAY! err=%s", uirenderer::renderthread::EglManager::eglErrorString()); // We use an EGLImage to access the content of the GraphicBuffer // The EGL image is later bound to a 2D texture EGLClientBuffer clientBuffer = (EGLClientBuffer)buffer.getNativeBuffer(); AutoEglImage autoImage(display, clientBuffer); if (autoImage.image == EGL_NO_IMAGE_KHR) { ALOGW("Could not create EGL image, err =%s", uirenderer::renderthread::EglManager::eglErrorString()); return false; } AutoGlTexture glTexture(caches); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image); GL_CHECKPOINT(MODERATE); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), format, type, bitmap.getPixels()); GL_CHECKPOINT(MODERATE); // The fence is used to wait for the texture upload to finish // properly. We cannot rely on glFlush() and glFinish() as // some drivers completely ignore these API calls AutoEglFence autoFence(display); if (autoFence.fence == EGL_NO_SYNC_KHR) { LOG_ALWAYS_FATAL("Could not create sync fence %#x", eglGetError()); return false; } // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a // pipeline flush (similar to what a glFlush() would do.) EGLint waitStatus = eglClientWaitSyncKHR(display, autoFence.fence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT); if (waitStatus != EGL_CONDITION_SATISFIED_KHR) { LOG_ALWAYS_FATAL("Failed to wait for the fence %#x", eglGetError()); return false; } return true; } // TODO: handle SRGB sanely static PixelFormat internalFormatToPixelFormat(GLint internalFormat) { switch (internalFormat) { case GL_LUMINANCE: return PIXEL_FORMAT_RGBA_8888; case GL_SRGB8_ALPHA8: return PIXEL_FORMAT_RGBA_8888; case GL_RGBA: return PIXEL_FORMAT_RGBA_8888; case GL_RGB: return PIXEL_FORMAT_RGB_565; case GL_RGBA16F: return PIXEL_FORMAT_RGBA_FP16; default: LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", internalFormat); return PIXEL_FORMAT_UNKNOWN; } } sk_sp<Bitmap> OpenGLPipeline::allocateHardwareBitmap(RenderThread& renderThread, SkBitmap& skBitmap) { renderThread.eglManager().initialize(); uirenderer::Caches& caches = uirenderer::Caches::getInstance(); const SkImageInfo& info = skBitmap.info(); if (info.colorType() == kUnknown_SkColorType || info.colorType() == kAlpha_8_SkColorType) { ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType()); return nullptr; } bool needSRGB = uirenderer::transferFunctionCloseToSRGB(skBitmap.info().colorSpace()); bool hasLinearBlending = caches.extensions().hasLinearBlending(); GLint format, type, internalFormat; uirenderer::Texture::colorTypeToGlFormatAndType(caches, skBitmap.colorType(), needSRGB && hasLinearBlending, &internalFormat, &format, &type); PixelFormat pixelFormat = internalFormatToPixelFormat(internalFormat); sp<GraphicBuffer> buffer = new GraphicBuffer( info.width(), info.height(), pixelFormat, GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | GraphicBuffer::USAGE_SW_READ_NEVER, std::string("Bitmap::allocateHardwareBitmap pid [") + std::to_string(getpid()) + "]"); status_t error = buffer->initCheck(); if (error < 0) { ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()"); return nullptr; } SkBitmap bitmap; if (CC_UNLIKELY( uirenderer::Texture::hasUnsupportedColorType(skBitmap.info(), hasLinearBlending))) { sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB(); bitmap = uirenderer::Texture::uploadToN32(skBitmap, hasLinearBlending, std::move(sRGB)); } else { bitmap = skBitmap; } if (!uploadBitmapToGraphicBuffer(caches, bitmap, *buffer, format, type)) { return nullptr; } return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info())); } } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */