Jorim Jaggi 786afcb3ee Trim graphics memory when closing the shade
Graphics memory usually gets trimmed in applications when the
activity goes into the background. We use quite a lot of graphics
memory when the shade/lockscreen is open, and some of them never gets
freed unless the recents activity is closed, because we don't have
these activity-trimming-heuristics for the shade. This change
proactively trims the graphics memory when the shade gets closed or
when the lockscreen is hidden, to emulate the same heuristics as for
activities.

This change also adds trimMemory on RenderThread to systrace to
verify that no jank is introduced with this change.

This change immediately saves around 10-30 MB on an xxhdpi device
after the shade is closed.

Bug: 17581375
Change-Id: I4fb622efb51815fe08187be97ba15d012d4de5d4
2014-10-08 18:30:54 +00:00

365 lines
11 KiB
C++

/*
* 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 "CanvasContext.h"
#include <algorithm>
#include <private/hwui/DrawGlInfo.h>
#include <strings.h>
#include "EglManager.h"
#include "RenderThread.h"
#include "../AnimationContext.h"
#include "../Caches.h"
#include "../DeferredLayerUpdater.h"
#include "../RenderState.h"
#include "../LayerRenderer.h"
#include "../OpenGLRenderer.h"
#include "../Stencil.h"
#define TRIM_MEMORY_COMPLETE 80
#define TRIM_MEMORY_UI_HIDDEN 20
namespace android {
namespace uirenderer {
namespace renderthread {
CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory)
: mRenderThread(thread)
, mEglManager(thread.eglManager())
, mEglSurface(EGL_NO_SURFACE)
, mDirtyRegionsEnabled(false)
, mOpaque(!translucent)
, mCanvas(NULL)
, mHaveNewSurface(false)
, mRootRenderNode(rootRenderNode) {
mAnimationContext = contextFactory->createAnimationContext(mRenderThread.timeLord());
mRenderThread.renderState().registerCanvasContext(this);
}
CanvasContext::~CanvasContext() {
destroy();
delete mAnimationContext;
mRenderThread.renderState().unregisterCanvasContext(this);
}
void CanvasContext::destroy() {
stopDrawing();
freePrefetechedLayers();
destroyHardwareResources();
mAnimationContext->destroy();
if (mCanvas) {
delete mCanvas;
mCanvas = 0;
}
setSurface(NULL);
}
void CanvasContext::setSurface(ANativeWindow* window) {
mNativeWindow = window;
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
}
if (window) {
mEglSurface = mEglManager.createSurface(window);
}
if (mEglSurface != EGL_NO_SURFACE) {
mDirtyRegionsEnabled = mEglManager.enableDirtyRegions(mEglSurface);
mHaveNewSurface = true;
makeCurrent();
} else {
mRenderThread.removeFrameCallback(this);
}
}
void CanvasContext::swapBuffers() {
if (CC_UNLIKELY(!mEglManager.swapBuffers(mEglSurface))) {
setSurface(NULL);
}
mHaveNewSurface = false;
}
void CanvasContext::requireSurface() {
LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
"requireSurface() called but no surface set!");
makeCurrent();
}
bool CanvasContext::initialize(ANativeWindow* window) {
setSurface(window);
if (mCanvas) return false;
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
mCanvas->initProperties();
return true;
}
void CanvasContext::updateSurface(ANativeWindow* window) {
setSurface(window);
}
void CanvasContext::pauseSurface(ANativeWindow* window) {
stopDrawing();
}
// TODO: don't pass viewport size, it's automatic via EGL
void CanvasContext::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
if (!mCanvas) return;
mCanvas->initLight(lightCenter, lightRadius, ambientShadowAlpha, spotShadowAlpha);
}
void CanvasContext::setOpaque(bool opaque) {
mOpaque = opaque;
}
void CanvasContext::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
mHaveNewSurface |= mEglManager.makeCurrent(mEglSurface);
}
void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
bool success = layerUpdater->apply();
LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
}
}
void CanvasContext::prepareTree(TreeInfo& info) {
mRenderThread.removeFrameCallback(this);
info.damageAccumulator = &mDamageAccumulator;
info.renderer = mCanvas;
if (mPrefetechedLayers.size() && info.mode == TreeInfo::MODE_FULL) {
info.canvasContext = this;
}
mAnimationContext->startFrame(info.mode);
mRootRenderNode->prepareTree(info);
mAnimationContext->runRemainingAnimations(info);
if (info.canvasContext) {
freePrefetechedLayers();
}
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 (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);
}
void CanvasContext::notifyFramePending() {
ATRACE_CALL();
mRenderThread.pushBackFrameCallback(this);
}
void CanvasContext::draw() {
LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
"drawRenderNode called on a context with no canvas or surface!");
profiler().markPlaybackStart();
SkRect dirty;
mDamageAccumulator.finish(&dirty);
EGLint width, height;
mEglManager.beginFrame(mEglSurface, &width, &height);
if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
mCanvas->setViewport(width, height);
dirty.setEmpty();
} else if (!mDirtyRegionsEnabled || mHaveNewSurface) {
dirty.setEmpty();
} else {
if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) {
ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
SK_RECT_ARGS(dirty), width, height);
dirty.setEmpty();
}
profiler().unionDirty(&dirty);
}
status_t status;
if (!dirty.isEmpty()) {
status = mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
dirty.fRight, dirty.fBottom, mOpaque);
} else {
status = mCanvas->prepare(mOpaque);
}
Rect outBounds;
status |= mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
profiler().draw(mCanvas);
mCanvas->finish();
profiler().markPlaybackEnd();
if (status & DrawGlInfo::kStatusDrew) {
swapBuffers();
}
profiler().finishFrame();
}
// Called by choreographer to do an RT-driven animation
void CanvasContext::doFrame() {
if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) {
return;
}
ATRACE_CALL();
profiler().startFrame();
TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
prepareTree(info);
if (info.out.canDrawThisFrame) {
draw();
}
}
void CanvasContext::invokeFunctor(RenderThread& thread, Functor* functor) {
ATRACE_CALL();
DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
if (thread.eglManager().hasEglContext()) {
thread.eglManager().requireGlContext();
mode = DrawGlInfo::kModeProcess;
}
thread.renderState().invokeFunctor(functor, mode, NULL);
}
void CanvasContext::markLayerInUse(RenderNode* node) {
if (mPrefetechedLayers.erase(node)) {
node->decStrong(0);
}
}
static void destroyPrefetechedNode(RenderNode* node) {
ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...", node->getName());
node->destroyHardwareResources();
node->decStrong(0);
}
void CanvasContext::freePrefetechedLayers() {
if (mPrefetechedLayers.size()) {
requireGlContext();
std::for_each(mPrefetechedLayers.begin(), mPrefetechedLayers.end(), destroyPrefetechedNode);
mPrefetechedLayers.clear();
}
}
void CanvasContext::buildLayer(RenderNode* node) {
ATRACE_CALL();
if (!mEglManager.hasEglContext() || !mCanvas) {
return;
}
requireGlContext();
// buildLayer() will leave the tree in an unknown state, so we must stop drawing
stopDrawing();
TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState());
info.damageAccumulator = &mDamageAccumulator;
info.renderer = mCanvas;
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);
mCanvas->markLayersAsBuildLayers();
mCanvas->flushLayerUpdates();
node->incStrong(0);
mPrefetechedLayers.insert(node);
}
bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
requireGlContext();
layer->apply();
return LayerRenderer::copyLayer(mRenderThread.renderState(), layer->backingLayer(), bitmap);
}
void CanvasContext::destroyHardwareResources() {
stopDrawing();
if (mEglManager.hasEglContext()) {
requireGlContext();
freePrefetechedLayers();
mRootRenderNode->destroyHardwareResources();
Caches::getInstance().flush(Caches::kFlushMode_Layers);
}
}
void CanvasContext::trimMemory(RenderThread& thread, int level) {
// No context means nothing to free
if (!thread.eglManager().hasEglContext()) return;
ATRACE_CALL();
thread.eglManager().requireGlContext();
if (level >= TRIM_MEMORY_COMPLETE) {
Caches::getInstance().flush(Caches::kFlushMode_Full);
thread.eglManager().destroy();
} else if (level >= TRIM_MEMORY_UI_HIDDEN) {
Caches::getInstance().flush(Caches::kFlushMode_Moderate);
}
}
void CanvasContext::runWithGlContext(RenderTask* task) {
requireGlContext();
task->run();
}
Layer* CanvasContext::createTextureLayer() {
requireSurface();
return LayerRenderer::createTextureLayer(mRenderThread.renderState());
}
void CanvasContext::requireGlContext() {
mEglManager.requireGlContext();
}
void CanvasContext::setTextureAtlas(RenderThread& thread,
const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize) {
thread.eglManager().setTextureAtlas(buffer, map, mapSize);
}
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */