diff options
Diffstat (limited to 'libs/hwui/renderthread/CanvasContext.cpp')
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.cpp | 363 |
1 files changed, 320 insertions, 43 deletions
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 6dfb6e811e60..89cadea775f3 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -20,21 +20,44 @@ #include "Caches.h" #include "DeferredLayerUpdater.h" #include "EglManager.h" +#include "LayerUpdateQueue.h" #include "LayerRenderer.h" #include "OpenGLRenderer.h" #include "Properties.h" #include "RenderThread.h" #include "renderstate/RenderState.h" #include "renderstate/Stencil.h" +#include "protos/hwui.pb.h" +#include "utils/TimeUtils.h" + +#if HWUI_NEW_OPS +#include "OpReorderer.h" +#endif -#include <algorithm> -#include <strings.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 { @@ -45,9 +68,10 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, , mEglManager(thread.eglManager()) , mOpaque(!translucent) , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) - , mRootRenderNode(rootRenderNode) , mJankTracker(thread.timeLord().frameIntervalNanos()) - , mProfiler(mFrames) { + , mProfiler(mFrames) + , mContentDrawBounds(0, 0, 0, 0) { + mRenderNodes.emplace_back(rootRenderNode); mRenderThread.renderState().registerCanvasContext(this); mProfiler.setDensity(mRenderThread.mainDisplayInfo().density); } @@ -87,19 +111,13 @@ void CanvasContext::setSurface(ANativeWindow* window) { const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer); mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); mHaveNewSurface = true; + mSwapHistory.clear(); makeCurrent(); } else { mRenderThread.removeFrameCallback(this); } } -void CanvasContext::swapBuffers(const SkRect& dirty, EGLint width, EGLint height) { - if (CC_UNLIKELY(!mEglManager.swapBuffers(mEglSurface, dirty, width, height))) { - setSurface(nullptr); - } - mHaveNewSurface = false; -} - void CanvasContext::requireSurface() { LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, "requireSurface() called but no surface set!"); @@ -112,9 +130,11 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) { bool CanvasContext::initialize(ANativeWindow* window) { setSurface(window); +#if !HWUI_NEW_OPS if (mCanvas) return false; mCanvas = new OpenGLRenderer(mRenderThread.renderState()); mCanvas->initProperties(); +#endif return true; } @@ -129,13 +149,23 @@ bool CanvasContext::pauseSurface(ANativeWindow* window) { // TODO: don't pass viewport size, it's automatic via EGL void CanvasContext::setup(int width, int height, float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { +#if HWUI_NEW_OPS + mLightInfo.lightRadius = lightRadius; + mLightInfo.ambientShadowAlpha = ambientShadowAlpha; + mLightInfo.spotShadowAlpha = spotShadowAlpha; +#else if (!mCanvas) return; mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha); +#endif } void CanvasContext::setLightCenter(const Vector3& lightCenter) { +#if HWUI_NEW_OPS + mLightCenter = lightCenter; +#else if (!mCanvas) return; mCanvas->setLightCenter(lightCenter); +#endif } void CanvasContext::setOpaque(bool opaque) { @@ -153,18 +183,21 @@ void CanvasContext::makeCurrent() { } void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) { +#if !HWUI_NEW_OPS bool success = layerUpdater->apply(); LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!"); if (layerUpdater->backingLayer()->deferredUpdateScheduled) { mCanvas->pushLayerUpdate(layerUpdater->backingLayer()); } +#endif } static bool wasSkipped(FrameInfo* info) { return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame); } -void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) { +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 @@ -177,11 +210,20 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mCurrentFrameInfo->markSyncStart(); info.damageAccumulator = &mDamageAccumulator; +#if HWUI_NEW_OPS + info.layerUpdateQueue = &mLayerUpdateQueue; +#else info.renderer = mCanvas; - info.canvasContext = this; +#endif mAnimationContext->startFrame(info.mode); - mRootRenderNode->prepareTree(info); + 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); + } mAnimationContext->runRemainingAnimations(info); freePrefetechedLayers(); @@ -192,13 +234,30 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy return; } - int runningBehind = 0; - // TODO: This query is moderately expensive, investigate adding some sort - // of fast-path based off when we last called eglSwapBuffers() as well as - // last vsync time. Or something. - mNativeWindow->query(mNativeWindow.get(), - NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); - info.out.canDrawThisFrame = !runningBehind; + if (CC_LIKELY(mSwapHistory.size())) { + nsecs_t latestVsync = mRenderThread.timeLord().latestVsync(); + const SwapHistory& lastSwap = mSwapHistory.back(); + int 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 (lastSwap.swapTime < latestVsync) { + info.out.canDrawThisFrame = true; + } else { + // We're maybe behind? Find out for sure + int runningBehind = 0; + mNativeWindow->query(mNativeWindow.get(), + NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); + info.out.canDrawThisFrame = !runningBehind; + } + } else { + info.out.canDrawThisFrame = true; + } if (!info.out.canDrawThisFrame) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); @@ -223,8 +282,10 @@ void CanvasContext::notifyFramePending() { } void CanvasContext::draw() { +#if !HWUI_NEW_OPS LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE, "drawRenderNode called on a context with no canvas or surface!"); +#endif SkRect dirty; mDamageAccumulator.finish(&dirty); @@ -237,46 +298,215 @@ void CanvasContext::draw() { mCurrentFrameInfo->markIssueDrawCommandsStart(); - EGLint width, height; - mEglManager.beginFrame(mEglSurface, &width, &height); - if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) { - mCanvas->setViewport(width, height); + Frame frame = mEglManager.beginFrame(mEglSurface); + + if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) { + // can't rely on prior content of window if viewport size changes dirty.setEmpty(); - } else if (!mBufferPreserved || mHaveNewSurface) { + 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, width, height)) { + 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), width, height); + SK_RECT_ARGS(dirty), frame.width(), frame.height()); dirty.setEmpty(); } profiler().unionDirty(&dirty); } - if (!dirty.isEmpty()) { - mCanvas->prepareDirty(dirty.fLeft, dirty.fTop, - dirty.fRight, dirty.fBottom, mOpaque); - } else { - mCanvas->prepare(mOpaque); + if (dirty.isEmpty()) { + dirty.set(0, 0, frame.width(), frame.height()); + } + + // At this point dirty is the area of the screen to update. However, + // the area of the frame we need to repaint is potentially different, so + // stash the screen area for later + SkRect screenDirty(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); + } + } } + mEglManager.damageFrame(frame, dirty); + +#if HWUI_NEW_OPS + OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), + mRenderNodes, mLightCenter); + mLayerUpdateQueue.clear(); + BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), + mOpaque, mLightInfo); + // TODO: profiler().draw(mCanvas); + reorderer.replayBakedOps<BakedOpDispatcher>(renderer); + + bool drew = renderer.didDraw(); + +#else + mCanvas->prepareDirty(frame.width(), frame.height(), + dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque); + Rect outBounds; - mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds); + // It there are multiple render nodes, they are laid out as follows: + // #0 - backdrop (content + caption) + // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds) + // #2 - additional overlay nodes + // Usually the backdrop cannot be seen since it will be entirely covered by the content. While + // resizing however it might become partially visible. The following render loop will crop the + // backdrop against the content and draw the remaining part of it. It will then draw the content + // cropped to the backdrop (since that indicates a shrinking of the window). + // + // Additional nodes will be drawn on top with no particular clipping semantics. + + // The bounds of the backdrop against which the content should be clipped. + Rect backdropBounds = mContentDrawBounds; + // Usually the contents bounds should be mContentDrawBounds - however - we will + // move it towards the fixed edge to give it a more stable appearance (for the moment). + Rect contentBounds; + // If there is no content bounds we ignore the layering as stated above and start with 2. + int layer = (mContentDrawBounds.isEmpty() || mRenderNodes.size() == 1) ? 2 : 0; + // Draw all render nodes. Note that + for (const sp<RenderNode>& node : mRenderNodes) { + if (layer == 0) { // Backdrop. + // Draw the backdrop clipped to the inverse content bounds, but assume that the content + // was moved to the upper left corner. + const RenderProperties& properties = node->properties(); + Rect targetBounds(properties.getLeft(), properties.getTop(), + properties.getRight(), properties.getBottom()); + // Move the content bounds towards the fixed corner of the backdrop. + const int x = targetBounds.left; + const int y = targetBounds.top; + contentBounds.set(x, y, x + mContentDrawBounds.getWidth(), + y + mContentDrawBounds.getHeight()); + // Remember the intersection of the target bounds and the intersection bounds against + // which we have to crop the content. + backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight()); + backdropBounds.doIntersect(targetBounds); + // Check if we have to draw something on the left side ... + if (targetBounds.left < contentBounds.left) { + mCanvas->save(SkCanvas::kClip_SaveFlag); + if (mCanvas->clipRect(targetBounds.left, targetBounds.top, + contentBounds.left, targetBounds.bottom, + SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + // Reduce the target area by the area we have just painted. + targetBounds.left = std::min(contentBounds.left, targetBounds.right); + mCanvas->restore(); + } + // ... or on the right side ... + if (targetBounds.right > contentBounds.right && + !targetBounds.isEmpty()) { + mCanvas->save(SkCanvas::kClip_SaveFlag); + if (mCanvas->clipRect(contentBounds.right, targetBounds.top, + targetBounds.right, targetBounds.bottom, + SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + // Reduce the target area by the area we have just painted. + targetBounds.right = std::max(targetBounds.left, contentBounds.right); + mCanvas->restore(); + } + // ... or at the top ... + if (targetBounds.top < contentBounds.top && + !targetBounds.isEmpty()) { + mCanvas->save(SkCanvas::kClip_SaveFlag); + if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right, + contentBounds.top, + SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + // Reduce the target area by the area we have just painted. + targetBounds.top = std::min(contentBounds.top, targetBounds.bottom); + mCanvas->restore(); + } + // ... or at the bottom. + if (targetBounds.bottom > contentBounds.bottom && + !targetBounds.isEmpty()) { + mCanvas->save(SkCanvas::kClip_SaveFlag); + if (mCanvas->clipRect(targetBounds.left, contentBounds.bottom, targetBounds.right, + targetBounds.bottom, SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + mCanvas->restore(); + } + } else if (layer == 1) { // Content + // It gets cropped against the bounds of the backdrop to stay inside. + mCanvas->save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + + // We shift and clip the content to match its final location in the window. + const float left = mContentDrawBounds.left; + const float top = mContentDrawBounds.top; + const float dx = backdropBounds.left - left; + const float dy = backdropBounds.top - top; + const float width = backdropBounds.getWidth(); + const float height = backdropBounds.getHeight(); + + mCanvas->translate(dx, dy); + if (mCanvas->clipRect(left, top, left + width, top + height, SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + mCanvas->restore(); + } else { // draw the rest on top at will! + mCanvas->drawRenderNode(node.get(), outBounds); + } + layer++; + } profiler().draw(mCanvas); bool drew = mCanvas->finish(); - +#endif // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point mCurrentFrameInfo->markSwapBuffers(); if (drew) { - swapBuffers(dirty, width, height); + if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) { + setSurface(nullptr); + } + SwapHistory& swap = mSwapHistory.next(); + swap.damage = screenDirty; + swap.swapTime = systemTime(CLOCK_MONOTONIC); + swap.vsyncTime = mRenderThread.timeLord().latestVsync(); + mHaveNewSurface = false; } // 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); } @@ -286,7 +516,10 @@ void CanvasContext::doFrame() { if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) { return; } + prepareAndDraw(nullptr); +} +void CanvasContext::prepareAndDraw(RenderNode* node) { ATRACE_CALL(); int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE]; @@ -295,8 +528,8 @@ void CanvasContext::doFrame() { .setVsync(mRenderThread.timeLord().computeFrameTimeNanos(), mRenderThread.timeLord().latestVsync()); - TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState()); - prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC)); + TreeInfo info(TreeInfo::MODE_RT_ONLY, *this); + prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node); if (info.out.canDrawThisFrame) { draw(); } @@ -339,9 +572,13 @@ void CanvasContext::buildLayer(RenderNode* node) { // buildLayer() will leave the tree in an unknown state, so we must stop drawing stopDrawing(); - TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState()); + TreeInfo info(TreeInfo::MODE_FULL, *this); info.damageAccumulator = &mDamageAccumulator; +#if HWUI_NEW_OPS + info.layerUpdateQueue = &mLayerUpdateQueue; +#else info.renderer = mCanvas; +#endif info.runAnimations = false; node->prepareTree(info); SkRect ignore; @@ -350,8 +587,12 @@ void CanvasContext::buildLayer(RenderNode* node) { // purposes when the frame is actually drawn node->setPropertyFieldsDirty(RenderNode::GENERIC); +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("unsupported"); +#else mCanvas->markLayersAsBuildLayers(); mCanvas->flushLayerUpdates(); +#endif node->incStrong(nullptr); mPrefetechedLayers.insert(node); @@ -366,12 +607,14 @@ void CanvasContext::destroyHardwareResources() { stopDrawing(); if (mEglManager.hasEglContext()) { freePrefetechedLayers(); - mRootRenderNode->destroyHardwareResources(); + for (const sp<RenderNode>& node : mRenderNodes) { + node->destroyHardwareResources(); + } 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); - caches.flush(Caches::kFlushMode_Layers); + mRenderThread.renderState().flush(Caches::FlushMode::Layers); } } @@ -381,10 +624,10 @@ void CanvasContext::trimMemory(RenderThread& thread, int level) { ATRACE_CALL(); if (level >= TRIM_MEMORY_COMPLETE) { - Caches::getInstance().flush(Caches::kFlushMode_Full); + thread.renderState().flush(Caches::FlushMode::Full); thread.eglManager().destroy(); } else if (level >= TRIM_MEMORY_UI_HIDDEN) { - Caches::getInstance().flush(Caches::kFlushMode_Moderate); + thread.renderState().flush(Caches::FlushMode::Moderate); } } @@ -430,6 +673,40 @@ void CanvasContext::resetFrameStats() { mRenderThread.jankTracker().reset(); } +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 +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ |