| /* | 
 |  * Copyright (C) 2014 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 <GpuMemoryTracker.h> | 
 | #include "CanvasContext.h" | 
 |  | 
 | #include "AnimationContext.h" | 
 | #include "Caches.h" | 
 | #include "EglManager.h" | 
 | #include "Frame.h" | 
 | #include "LayerUpdateQueue.h" | 
 | #include "Properties.h" | 
 | #include "RenderThread.h" | 
 | #include "hwui/Canvas.h" | 
 | #include "renderstate/RenderState.h" | 
 | #include "renderstate/Stencil.h" | 
 | #include "protos/hwui.pb.h" | 
 | #include "OpenGLPipeline.h" | 
 | #include "pipeline/skia/SkiaOpenGLPipeline.h" | 
 | #include "pipeline/skia/SkiaPipeline.h" | 
 | #include "pipeline/skia/SkiaVulkanPipeline.h" | 
 | #include "utils/GLUtils.h" | 
 | #include "utils/TimeUtils.h" | 
 |  | 
 | #include <cutils/properties.h> | 
 | #include <google/protobuf/io/zero_copy_stream_impl.h> | 
 | #include <private/hwui/DrawGlInfo.h> | 
 | #include <strings.h> | 
 |  | 
 | #include <algorithm> | 
 | #include <fcntl.h> | 
 | #include <sys/stat.h> | 
 |  | 
 | #include <cstdlib> | 
 |  | 
 | #define TRIM_MEMORY_COMPLETE 80 | 
 | #define TRIM_MEMORY_UI_HIDDEN 20 | 
 |  | 
 | #define ENABLE_RENDERNODE_SERIALIZATION false | 
 |  | 
 | #define LOG_FRAMETIME_MMA 0 | 
 |  | 
 | #if LOG_FRAMETIME_MMA | 
 | static float sBenchMma = 0; | 
 | static int sFrameCount = 0; | 
 | static const float NANOS_PER_MILLIS_F = 1000000.0f; | 
 | #endif | 
 |  | 
 | namespace android { | 
 | namespace uirenderer { | 
 | namespace renderthread { | 
 |  | 
 | CanvasContext* CanvasContext::create(RenderThread& thread, | 
 |         bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) { | 
 |  | 
 |     auto renderType = Properties::getRenderPipelineType(); | 
 |  | 
 |     switch (renderType) { | 
 |         case RenderPipelineType::OpenGL: | 
 |             return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, | 
 |                     std::make_unique<OpenGLPipeline>(thread)); | 
 |         case RenderPipelineType::SkiaGL: | 
 |             return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, | 
 |                     std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread)); | 
 |         case RenderPipelineType::SkiaVulkan: | 
 |             return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, | 
 |                                 std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread)); | 
 |         default: | 
 |             LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); | 
 |             break; | 
 |     } | 
 |     return nullptr; | 
 | } | 
 |  | 
 | void CanvasContext::destroyLayer(RenderNode* node) { | 
 |     auto renderType = Properties::getRenderPipelineType(); | 
 |     switch (renderType) { | 
 |         case RenderPipelineType::OpenGL: | 
 |             OpenGLPipeline::destroyLayer(node); | 
 |             break; | 
 |         case RenderPipelineType::SkiaGL: | 
 |         case RenderPipelineType::SkiaVulkan: | 
 |             skiapipeline::SkiaPipeline::destroyLayer(node); | 
 |             break; | 
 |         default: | 
 |             LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); | 
 |             break; | 
 |     } | 
 | } | 
 |  | 
 | void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor) { | 
 |     ATRACE_CALL(); | 
 |     auto renderType = Properties::getRenderPipelineType(); | 
 |     switch (renderType) { | 
 |         case RenderPipelineType::OpenGL: | 
 |             OpenGLPipeline::invokeFunctor(thread, functor); | 
 |             break; | 
 |         case RenderPipelineType::SkiaGL: | 
 |             skiapipeline::SkiaOpenGLPipeline::invokeFunctor(thread, functor); | 
 |             break; | 
 |         case RenderPipelineType::SkiaVulkan: | 
 |             skiapipeline::SkiaVulkanPipeline::invokeFunctor(thread, functor); | 
 |             break; | 
 |         default: | 
 |             LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); | 
 |             break; | 
 |     } | 
 | } | 
 |  | 
 | void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { | 
 |     auto renderType = Properties::getRenderPipelineType(); | 
 |     switch (renderType) { | 
 |         case RenderPipelineType::OpenGL: | 
 |             OpenGLPipeline::prepareToDraw(thread, bitmap); | 
 |             break; | 
 |         case RenderPipelineType::SkiaGL: | 
 |         case RenderPipelineType::SkiaVulkan: | 
 |             skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap); | 
 |             break; | 
 |         default: | 
 |             LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); | 
 |             break; | 
 |     } | 
 | } | 
 |  | 
 | CanvasContext::CanvasContext(RenderThread& thread, bool translucent, | 
 |         RenderNode* rootRenderNode, IContextFactory* contextFactory, | 
 |         std::unique_ptr<IRenderPipeline> renderPipeline) | 
 |         : mRenderThread(thread) | 
 |         , mOpaque(!translucent) | 
 |         , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) | 
 |         , mJankTracker(thread.mainDisplayInfo()) | 
 |         , mProfiler(mFrames) | 
 |         , mContentDrawBounds(0, 0, 0, 0) | 
 |         , mRenderPipeline(std::move(renderPipeline)) { | 
 |     rootRenderNode->makeRoot(); | 
 |     mRenderNodes.emplace_back(rootRenderNode); | 
 |     mRenderThread.renderState().registerCanvasContext(this); | 
 |     mProfiler.setDensity(mRenderThread.mainDisplayInfo().density); | 
 | } | 
 |  | 
 | CanvasContext::~CanvasContext() { | 
 |     destroy(); | 
 |     mRenderThread.renderState().unregisterCanvasContext(this); | 
 |     for (auto& node : mRenderNodes) { | 
 |         node->clearRoot(); | 
 |     } | 
 |     mRenderNodes.clear(); | 
 | } | 
 |  | 
 | void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) { | 
 |     int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size()); | 
 |     node->makeRoot(); | 
 |     mRenderNodes.emplace(mRenderNodes.begin() + pos, node); | 
 | } | 
 |  | 
 | void CanvasContext::removeRenderNode(RenderNode* node) { | 
 |     node->clearRoot(); | 
 |     mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node), | 
 |             mRenderNodes.end()); | 
 | } | 
 |  | 
 | void CanvasContext::destroy() { | 
 |     stopDrawing(); | 
 |     setSurface(nullptr); | 
 |     freePrefetchedLayers(); | 
 |     destroyHardwareResources(); | 
 |     mAnimationContext->destroy(); | 
 | } | 
 |  | 
 | void CanvasContext::setSurface(Surface* surface) { | 
 |     ATRACE_CALL(); | 
 |  | 
 |     mNativeSurface = surface; | 
 |  | 
 |     bool hasSurface = mRenderPipeline->setSurface(surface, mSwapBehavior); | 
 |  | 
 |     mFrameNumber = -1; | 
 |  | 
 |     if (hasSurface) { | 
 |          mHaveNewSurface = true; | 
 |          mSwapHistory.clear(); | 
 |     } else { | 
 |          mRenderThread.removeFrameCallback(this); | 
 |     } | 
 | } | 
 |  | 
 | void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) { | 
 |     mSwapBehavior = swapBehavior; | 
 | } | 
 |  | 
 | void CanvasContext::initialize(Surface* surface) { | 
 |     setSurface(surface); | 
 | } | 
 |  | 
 | void CanvasContext::updateSurface(Surface* surface) { | 
 |     setSurface(surface); | 
 | } | 
 |  | 
 | bool CanvasContext::pauseSurface(Surface* surface) { | 
 |     return mRenderThread.removeFrameCallback(this); | 
 | } | 
 |  | 
 | void CanvasContext::setStopped(bool stopped) { | 
 |     if (mStopped != stopped) { | 
 |         mStopped = stopped; | 
 |         if (mStopped) { | 
 |             mRenderThread.removeFrameCallback(this); | 
 |             mRenderPipeline->onStop(); | 
 |         } else if (mIsDirty && hasSurface()) { | 
 |             mRenderThread.postFrameCallback(this); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | void CanvasContext::setup(float lightRadius, | 
 |         uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { | 
 |     mLightGeometry.radius = lightRadius; | 
 |     mLightInfo.ambientShadowAlpha = ambientShadowAlpha; | 
 |     mLightInfo.spotShadowAlpha = spotShadowAlpha; | 
 | } | 
 |  | 
 | void CanvasContext::setLightCenter(const Vector3& lightCenter) { | 
 |     mLightGeometry.center = lightCenter; | 
 | } | 
 |  | 
 | void CanvasContext::setOpaque(bool opaque) { | 
 |     mOpaque = opaque; | 
 | } | 
 |  | 
 | bool CanvasContext::makeCurrent() { | 
 |     if (mStopped) return false; | 
 |  | 
 |     auto result = mRenderPipeline->makeCurrent(); | 
 |     switch (result) { | 
 |         case MakeCurrentResult::AlreadyCurrent: | 
 |             return true; | 
 |         case MakeCurrentResult::Failed: | 
 |             mHaveNewSurface = true; | 
 |             setSurface(nullptr); | 
 |             return false; | 
 |         case MakeCurrentResult::Succeeded: | 
 |             mHaveNewSurface = true; | 
 |             return true; | 
 |         default: | 
 |             LOG_ALWAYS_FATAL("unexpected result %d from IRenderPipeline::makeCurrent", | 
 |                     (int32_t) result); | 
 |     } | 
 |  | 
 |     return true; | 
 | } | 
 |  | 
 | static bool wasSkipped(FrameInfo* info) { | 
 |     return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame); | 
 | } | 
 |  | 
 | bool CanvasContext::isSwapChainStuffed() { | 
 |     static const auto SLOW_THRESHOLD = 6_ms; | 
 |  | 
 |     if (mSwapHistory.size() != mSwapHistory.capacity()) { | 
 |         // We want at least 3 frames of history before attempting to | 
 |         // guess if the queue is stuffed | 
 |         return false; | 
 |     } | 
 |     nsecs_t frameInterval = mRenderThread.timeLord().frameIntervalNanos(); | 
 |     auto& swapA = mSwapHistory[0]; | 
 |  | 
 |     // Was there a happy queue & dequeue time? If so, don't | 
 |     // consider it stuffed | 
 |     if (swapA.dequeueDuration < SLOW_THRESHOLD | 
 |             && swapA.queueDuration < SLOW_THRESHOLD) { | 
 |         return false; | 
 |     } | 
 |  | 
 |     for (size_t i = 1; i < mSwapHistory.size(); i++) { | 
 |         auto& swapB = mSwapHistory[i]; | 
 |  | 
 |         // If there's a multi-frameInterval gap we effectively already dropped a frame, | 
 |         // so consider the queue healthy. | 
 |         if (swapA.swapCompletedTime - swapB.swapCompletedTime > frameInterval * 3) { | 
 |             return false; | 
 |         } | 
 |  | 
 |         // Was there a happy queue & dequeue time? If so, don't | 
 |         // consider it stuffed | 
 |         if (swapB.dequeueDuration < SLOW_THRESHOLD | 
 |                 && swapB.queueDuration < SLOW_THRESHOLD) { | 
 |             return false; | 
 |         } | 
 |  | 
 |         swapA = swapB; | 
 |     } | 
 |  | 
 |     // All signs point to a stuffed swap chain | 
 |     ATRACE_NAME("swap chain stuffed"); | 
 |     return true; | 
 | } | 
 |  | 
 | void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, | 
 |         int64_t syncQueued, RenderNode* target) { | 
 |     mRenderThread.removeFrameCallback(this); | 
 |  | 
 |     // If the previous frame was dropped we don't need to hold onto it, so | 
 |     // just keep using the previous frame's structure instead | 
 |     if (!wasSkipped(mCurrentFrameInfo)) { | 
 |         mCurrentFrameInfo = &mFrames.next(); | 
 |     } | 
 |     mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo); | 
 |     mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued; | 
 |     mCurrentFrameInfo->markSyncStart(); | 
 |  | 
 |     info.damageAccumulator = &mDamageAccumulator; | 
 |     info.layerUpdateQueue = &mLayerUpdateQueue; | 
 |  | 
 |     mAnimationContext->startFrame(info.mode); | 
 |     for (const sp<RenderNode>& node : mRenderNodes) { | 
 |         // Only the primary target node will be drawn full - all other nodes would get drawn in | 
 |         // real time mode. In case of a window, the primary node is the window content and the other | 
 |         // node(s) are non client / filler nodes. | 
 |         info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY); | 
 |         node->prepareTree(info); | 
 |         GL_CHECKPOINT(MODERATE); | 
 |     } | 
 |     mAnimationContext->runRemainingAnimations(info); | 
 |     GL_CHECKPOINT(MODERATE); | 
 |  | 
 |     freePrefetchedLayers(); | 
 |     GL_CHECKPOINT(MODERATE); | 
 |  | 
 |     mIsDirty = true; | 
 |  | 
 |     if (CC_UNLIKELY(!mNativeSurface.get())) { | 
 |         mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); | 
 |         info.out.canDrawThisFrame = false; | 
 |         return; | 
 |     } | 
 |  | 
 |     if (CC_LIKELY(mSwapHistory.size() && !Properties::forceDrawFrame)) { | 
 |         nsecs_t latestVsync = mRenderThread.timeLord().latestVsync(); | 
 |         SwapHistory& lastSwap = mSwapHistory.back(); | 
 |         nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync); | 
 |         // The slight fudge-factor is to deal with cases where | 
 |         // the vsync was estimated due to being slow handling the signal. | 
 |         // See the logic in TimeLord#computeFrameTimeNanos or in | 
 |         // Choreographer.java for details on when this happens | 
 |         if (vsyncDelta < 2_ms) { | 
 |             // Already drew for this vsync pulse, UI draw request missed | 
 |             // the deadline for RT animations | 
 |             info.out.canDrawThisFrame = false; | 
 |         } else if (vsyncDelta >= mRenderThread.timeLord().frameIntervalNanos() * 3 | 
 |                 || (latestVsync - mLastDropVsync) < 500_ms) { | 
 |             // It's been several frame intervals, assume the buffer queue is fine | 
 |             // or the last drop was too recent | 
 |             info.out.canDrawThisFrame = true; | 
 |         } else { | 
 |             info.out.canDrawThisFrame = !isSwapChainStuffed(); | 
 |             if (!info.out.canDrawThisFrame) { | 
 |                 // dropping frame | 
 |                 mLastDropVsync = mRenderThread.timeLord().latestVsync(); | 
 |             } | 
 |         } | 
 |     } else { | 
 |         info.out.canDrawThisFrame = true; | 
 |     } | 
 |  | 
 |     if (!info.out.canDrawThisFrame) { | 
 |         mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); | 
 |     } | 
 |  | 
 |     if (info.out.hasAnimations || !info.out.canDrawThisFrame) { | 
 |         if (!info.out.requiresUiRedraw) { | 
 |             // If animationsNeedsRedraw is set don't bother posting for an RT anim | 
 |             // as we will just end up fighting the UI thread. | 
 |             mRenderThread.postFrameCallback(this); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | void CanvasContext::stopDrawing() { | 
 |     mRenderThread.removeFrameCallback(this); | 
 |     mAnimationContext->pauseAnimators(); | 
 | } | 
 |  | 
 | void CanvasContext::notifyFramePending() { | 
 |     ATRACE_CALL(); | 
 |     mRenderThread.pushBackFrameCallback(this); | 
 | } | 
 |  | 
 | void CanvasContext::draw() { | 
 |     SkRect dirty; | 
 |     mDamageAccumulator.finish(&dirty); | 
 |  | 
 |     // TODO: Re-enable after figuring out cause of b/22592975 | 
 | //    if (dirty.isEmpty() && Properties::skipEmptyFrames) { | 
 | //        mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); | 
 | //        return; | 
 | //    } | 
 |  | 
 |     mCurrentFrameInfo->markIssueDrawCommandsStart(); | 
 |  | 
 |     Frame frame = mRenderPipeline->getFrame(); | 
 |  | 
 |     SkRect windowDirty = computeDirtyRect(frame, &dirty); | 
 |  | 
 |     bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, | 
 |             mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes, &(profiler())); | 
 |  | 
 |     waitOnFences(); | 
 |  | 
 |     bool requireSwap = false; | 
 |     bool didSwap = mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, | 
 |             &requireSwap); | 
 |  | 
 |     mIsDirty = false; | 
 |  | 
 |     if (requireSwap) { | 
 |         if (!didSwap) { //some error happened | 
 |             setSurface(nullptr); | 
 |         } | 
 |         SwapHistory& swap = mSwapHistory.next(); | 
 |         swap.damage = windowDirty; | 
 |         swap.swapCompletedTime = systemTime(CLOCK_MONOTONIC); | 
 |         swap.vsyncTime = mRenderThread.timeLord().latestVsync(); | 
 |         if (mNativeSurface.get()) { | 
 |             int durationUs; | 
 |             mNativeSurface->query(NATIVE_WINDOW_LAST_DEQUEUE_DURATION, &durationUs); | 
 |             swap.dequeueDuration = us2ns(durationUs); | 
 |             mNativeSurface->query(NATIVE_WINDOW_LAST_QUEUE_DURATION, &durationUs); | 
 |             swap.queueDuration = us2ns(durationUs); | 
 |         } else { | 
 |             swap.dequeueDuration = 0; | 
 |             swap.queueDuration = 0; | 
 |         } | 
 |         mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) | 
 |                 = swap.dequeueDuration; | 
 |         mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) | 
 |                 = swap.queueDuration; | 
 |         mHaveNewSurface = false; | 
 |         mFrameNumber = -1; | 
 |     } else { | 
 |         mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = 0; | 
 |         mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = 0; | 
 |     } | 
 |  | 
 |     // TODO: Use a fence for real completion? | 
 |     mCurrentFrameInfo->markFrameCompleted(); | 
 |  | 
 | #if LOG_FRAMETIME_MMA | 
 |     float thisFrame = mCurrentFrameInfo->duration( | 
 |             FrameInfoIndex::IssueDrawCommandsStart, | 
 |             FrameInfoIndex::FrameCompleted) / NANOS_PER_MILLIS_F; | 
 |     if (sFrameCount) { | 
 |         sBenchMma = ((9 * sBenchMma) + thisFrame) / 10; | 
 |     } else { | 
 |         sBenchMma = thisFrame; | 
 |     } | 
 |     if (++sFrameCount == 10) { | 
 |         sFrameCount = 1; | 
 |         ALOGD("Average frame time: %.4f", sBenchMma); | 
 |     } | 
 | #endif | 
 |  | 
 |     mJankTracker.addFrame(*mCurrentFrameInfo); | 
 |     mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo); | 
 |     if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) { | 
 |         mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data()); | 
 |     } | 
 |  | 
 |     GpuMemoryTracker::onFrameCompleted(); | 
 | #ifdef BUGREPORT_FONT_CACHE_USAGE | 
 |     auto renderType = Properties::getRenderPipelineType(); | 
 |     if (RenderPipelineType::OpenGL == renderType) { | 
 |         Caches& caches = Caches::getInstance(); | 
 |         caches.fontRenderer.getFontRenderer().historyTracker().frameCompleted(); | 
 |     } | 
 | #endif | 
 |  | 
 | } | 
 |  | 
 | // Called by choreographer to do an RT-driven animation | 
 | void CanvasContext::doFrame() { | 
 |     if (!mRenderPipeline->isSurfaceReady()) return; | 
 |     prepareAndDraw(nullptr); | 
 | } | 
 |  | 
 | void CanvasContext::prepareAndDraw(RenderNode* node) { | 
 |     ATRACE_CALL(); | 
 |  | 
 |     nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos(); | 
 |     int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE]; | 
 |     UiFrameInfoBuilder(frameInfo) | 
 |         .addFlag(FrameInfoFlags::RTAnimation) | 
 |         .setVsync(vsync, vsync); | 
 |  | 
 |     TreeInfo info(TreeInfo::MODE_RT_ONLY, *this); | 
 |     prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node); | 
 |     if (info.out.canDrawThisFrame) { | 
 |         draw(); | 
 |     } else { | 
 |         // wait on fences so tasks don't overlap next frame | 
 |         waitOnFences(); | 
 |     } | 
 | } | 
 |  | 
 | void CanvasContext::markLayerInUse(RenderNode* node) { | 
 |     if (mPrefetchedLayers.erase(node)) { | 
 |         node->decStrong(nullptr); | 
 |     } | 
 | } | 
 |  | 
 | void CanvasContext::freePrefetchedLayers() { | 
 |     if (mPrefetchedLayers.size()) { | 
 |         for (auto& node : mPrefetchedLayers) { | 
 |             ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...", | 
 |                     node->getName()); | 
 |             node->destroyLayers(); | 
 |             node->decStrong(nullptr); | 
 |         } | 
 |         mPrefetchedLayers.clear(); | 
 |     } | 
 | } | 
 |  | 
 | void CanvasContext::buildLayer(RenderNode* node) { | 
 |     ATRACE_CALL(); | 
 |     if (!mRenderPipeline->isContextReady()) return; | 
 |  | 
 |     // buildLayer() will leave the tree in an unknown state, so we must stop drawing | 
 |     stopDrawing(); | 
 |  | 
 |     TreeInfo info(TreeInfo::MODE_FULL, *this); | 
 |     info.damageAccumulator = &mDamageAccumulator; | 
 |     info.layerUpdateQueue = &mLayerUpdateQueue; | 
 |     info.runAnimations = false; | 
 |     node->prepareTree(info); | 
 |     SkRect ignore; | 
 |     mDamageAccumulator.finish(&ignore); | 
 |     // Tickle the GENERIC property on node to mark it as dirty for damaging | 
 |     // purposes when the frame is actually drawn | 
 |     node->setPropertyFieldsDirty(RenderNode::GENERIC); | 
 |  | 
 |     mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue, mOpaque, mLightInfo); | 
 |  | 
 |     node->incStrong(nullptr); | 
 |     mPrefetchedLayers.insert(node); | 
 | } | 
 |  | 
 | bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { | 
 |     return mRenderPipeline->copyLayerInto(layer, bitmap); | 
 | } | 
 |  | 
 | void CanvasContext::destroyHardwareResources() { | 
 |     stopDrawing(); | 
 |     if (mRenderPipeline->isContextReady()) { | 
 |         freePrefetchedLayers(); | 
 |         for (const sp<RenderNode>& node : mRenderNodes) { | 
 |             node->destroyHardwareResources(); | 
 |         } | 
 |         mRenderPipeline->onDestroyHardwareResources(); | 
 |     } | 
 | } | 
 |  | 
 | void CanvasContext::trimMemory(RenderThread& thread, int level) { | 
 |     // No context means nothing to free | 
 |     if (!thread.eglManager().hasEglContext()) return; | 
 |  | 
 |     ATRACE_CALL(); | 
 |     if (level >= TRIM_MEMORY_COMPLETE) { | 
 |         thread.renderState().flush(Caches::FlushMode::Full); | 
 |         thread.eglManager().destroy(); | 
 |     } else if (level >= TRIM_MEMORY_UI_HIDDEN) { | 
 |         thread.renderState().flush(Caches::FlushMode::Moderate); | 
 |     } | 
 | } | 
 |  | 
 | DeferredLayerUpdater* CanvasContext::createTextureLayer() { | 
 |     return mRenderPipeline->createTextureLayer(); | 
 | } | 
 |  | 
 | void CanvasContext::dumpFrames(int fd) { | 
 |     mJankTracker.dump(fd); | 
 |     FILE* file = fdopen(fd, "a"); | 
 |     fprintf(file, "\n\n---PROFILEDATA---\n"); | 
 |     for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) { | 
 |         fprintf(file, "%s", FrameInfoNames[i].c_str()); | 
 |         fprintf(file, ","); | 
 |     } | 
 |     for (size_t i = 0; i < mFrames.size(); i++) { | 
 |         FrameInfo& frame = mFrames[i]; | 
 |         if (frame[FrameInfoIndex::SyncStart] == 0) { | 
 |             continue; | 
 |         } | 
 |         fprintf(file, "\n"); | 
 |         for (int i = 0; i < static_cast<int>(FrameInfoIndex::NumIndexes); i++) { | 
 |             fprintf(file, "%" PRId64 ",", frame[i]); | 
 |         } | 
 |     } | 
 |     fprintf(file, "\n---PROFILEDATA---\n\n"); | 
 |     fflush(file); | 
 | } | 
 |  | 
 | void CanvasContext::resetFrameStats() { | 
 |     mFrames.clear(); | 
 |     mRenderThread.jankTracker().reset(); | 
 | } | 
 |  | 
 | void CanvasContext::setName(const std::string&& name) { | 
 |     mJankTracker.setDescription(JankTrackerType::Window, std::move(name)); | 
 | } | 
 |  | 
 | void CanvasContext::serializeDisplayListTree() { | 
 | #if ENABLE_RENDERNODE_SERIALIZATION | 
 |     using namespace google::protobuf::io; | 
 |     char package[128]; | 
 |     // Check whether tracing is enabled for this process. | 
 |     FILE * file = fopen("/proc/self/cmdline", "r"); | 
 |     if (file) { | 
 |         if (!fgets(package, 128, file)) { | 
 |             ALOGE("Error reading cmdline: %s (%d)", strerror(errno), errno); | 
 |             fclose(file); | 
 |             return; | 
 |         } | 
 |         fclose(file); | 
 |     } else { | 
 |         ALOGE("Error opening /proc/self/cmdline: %s (%d)", strerror(errno), | 
 |                 errno); | 
 |         return; | 
 |     } | 
 |     char path[1024]; | 
 |     snprintf(path, 1024, "/data/data/%s/cache/rendertree_dump", package); | 
 |     int fd = open(path, O_CREAT | O_WRONLY, S_IRWXU | S_IRGRP | S_IROTH); | 
 |     if (fd == -1) { | 
 |         ALOGD("Failed to open '%s'", path); | 
 |         return; | 
 |     } | 
 |     proto::RenderNode tree; | 
 |     // TODO: Streaming writes? | 
 |     mRootRenderNode->copyTo(&tree); | 
 |     std::string data = tree.SerializeAsString(); | 
 |     write(fd, data.c_str(), data.length()); | 
 |     close(fd); | 
 | #endif | 
 | } | 
 |  | 
 | void CanvasContext::waitOnFences() { | 
 |     if (mFrameFences.size()) { | 
 |         ATRACE_CALL(); | 
 |         for (auto& fence : mFrameFences) { | 
 |             fence->getResult(); | 
 |         } | 
 |         mFrameFences.clear(); | 
 |     } | 
 | } | 
 |  | 
 | class CanvasContext::FuncTaskProcessor : public TaskProcessor<bool> { | 
 | public: | 
 |     explicit FuncTaskProcessor(TaskManager* taskManager) | 
 |             : TaskProcessor<bool>(taskManager) {} | 
 |  | 
 |     virtual void onProcess(const sp<Task<bool> >& task) override { | 
 |         FuncTask* t = static_cast<FuncTask*>(task.get()); | 
 |         t->func(); | 
 |         task->setResult(true); | 
 |     } | 
 | }; | 
 |  | 
 | void CanvasContext::enqueueFrameWork(std::function<void()>&& func) { | 
 |     if (!mFrameWorkProcessor.get()) { | 
 |         mFrameWorkProcessor = new FuncTaskProcessor(mRenderPipeline->getTaskManager()); | 
 |     } | 
 |     sp<FuncTask> task(new FuncTask()); | 
 |     task->func = func; | 
 |     mFrameFences.push_back(task); | 
 |     mFrameWorkProcessor->add(task); | 
 | } | 
 |  | 
 | int64_t CanvasContext::getFrameNumber() { | 
 |     // mFrameNumber is reset to -1 when the surface changes or we swap buffers | 
 |     if (mFrameNumber == -1 && mNativeSurface.get()) { | 
 |         mFrameNumber = static_cast<int64_t>(mNativeSurface->getNextFrameNumber()); | 
 |     } | 
 |     return mFrameNumber; | 
 | } | 
 |  | 
 | SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) { | 
 |     if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) { | 
 |         // can't rely on prior content of window if viewport size changes | 
 |         dirty->setEmpty(); | 
 |         mLastFrameWidth = frame.width(); | 
 |         mLastFrameHeight = frame.height(); | 
 |     } else if (mHaveNewSurface || frame.bufferAge() == 0) { | 
 |         // New surface needs a full draw | 
 |         dirty->setEmpty(); | 
 |     } else { | 
 |         if (!dirty->isEmpty() && !dirty->intersect(0, 0, frame.width(), frame.height())) { | 
 |             ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?", | 
 |                     SK_RECT_ARGS(*dirty), frame.width(), frame.height()); | 
 |             dirty->setEmpty(); | 
 |         } | 
 |         profiler().unionDirty(dirty); | 
 |     } | 
 |  | 
 |     if (dirty->isEmpty()) { | 
 |         dirty->set(0, 0, frame.width(), frame.height()); | 
 |     } | 
 |  | 
 |     // At this point dirty is the area of the window to update. However, | 
 |     // the area of the frame we need to repaint is potentially different, so | 
 |     // stash the screen area for later | 
 |     SkRect windowDirty(*dirty); | 
 |  | 
 |     // If the buffer age is 0 we do a full-screen repaint (handled above) | 
 |     // If the buffer age is 1 the buffer contents are the same as they were | 
 |     // last frame so there's nothing to union() against | 
 |     // Therefore we only care about the > 1 case. | 
 |     if (frame.bufferAge() > 1) { | 
 |         if (frame.bufferAge() > (int) mSwapHistory.size()) { | 
 |             // We don't have enough history to handle this old of a buffer | 
 |             // Just do a full-draw | 
 |             dirty->set(0, 0, frame.width(), frame.height()); | 
 |         } else { | 
 |             // At this point we haven't yet added the latest frame | 
 |             // to the damage history (happens below) | 
 |             // So we need to damage | 
 |             for (int i = mSwapHistory.size() - 1; | 
 |                     i > ((int) mSwapHistory.size()) - frame.bufferAge(); i--) { | 
 |                 dirty->join(mSwapHistory[i].damage); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     return windowDirty; | 
 | } | 
 |  | 
 | } /* namespace renderthread */ | 
 | } /* namespace uirenderer */ | 
 | } /* namespace android */ |