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