Files
frameworks_base/libs/hwui/renderthread/CanvasContext.cpp
Thomas Buhot 0bcd0cb6b1 libhwui: make setSurface asynchronous
On the critical path of the cold launch of applications
the main thread of the started application tells the RenderThread
to create a surface. This process is synchronous and blocks
the main thread of the application until the creation
of the EGLContext is complete.
As a consequence the launch time of the application is delayed
by time spent allocating the EGL Context in the RenderThread.

With this optimization the launch time of any application
is improved (for example settings by 20 to 40 ms).

Change-Id: Ibf47aaa0abb8dedf7aa00693073db3785d9d6b08
Signed-off-by: Thomas Buhot <thomas.buhot@intel.com>
Signed-off-by: Zhiquan Liu <zhiquan.liu@intel.com>
2015-12-10 14:51:58 +08:00

435 lines
14 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 "AnimationContext.h"
#include "Caches.h"
#include "DeferredLayerUpdater.h"
#include "EglManager.h"
#include "LayerRenderer.h"
#include "OpenGLRenderer.h"
#include "Properties.h"
#include "RenderThread.h"
#include "renderstate/RenderState.h"
#include "renderstate/Stencil.h"
#include <algorithm>
#include <strings.h>
#include <cutils/properties.h>
#include <private/hwui/DrawGlInfo.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())
, mOpaque(!translucent)
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
, mRootRenderNode(rootRenderNode)
, mJankTracker(thread.timeLord().frameIntervalNanos())
, mProfiler(mFrames) {
mRenderThread.renderState().registerCanvasContext(this);
mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
}
CanvasContext::~CanvasContext() {
destroy();
mRenderThread.renderState().unregisterCanvasContext(this);
}
void CanvasContext::destroy() {
stopDrawing();
setSurface(nullptr);
freePrefetechedLayers();
destroyHardwareResources();
mAnimationContext->destroy();
if (mCanvas) {
delete mCanvas;
mCanvas = nullptr;
}
}
void CanvasContext::setSurface(ANativeWindow* window) {
ATRACE_CALL();
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) {
const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer);
mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
mHaveNewSurface = true;
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!");
makeCurrent();
}
void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) {
mSwapBehavior = swapBehavior;
}
void CanvasContext::initialize(ANativeWindow* window) {
setSurface(window);
if (mCanvas) return;
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
mCanvas->initProperties();
}
void CanvasContext::updateSurface(ANativeWindow* window) {
setSurface(window);
}
bool CanvasContext::pauseSurface(ANativeWindow* window) {
return mRenderThread.removeFrameCallback(this);
}
// 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 (!mCanvas) return;
mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha);
}
void CanvasContext::setLightCenter(const Vector3& lightCenter) {
if (!mCanvas) return;
mCanvas->setLightCenter(lightCenter);
}
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
EGLint error = 0;
mHaveNewSurface |= mEglManager.makeCurrent(mEglSurface, &error);
if (error) {
setSurface(nullptr);
}
}
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());
}
}
static bool wasSkipped(FrameInfo* info) {
return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
}
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) {
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.renderer = mCanvas;
info.canvasContext = this;
mAnimationContext->startFrame(info.mode);
mRootRenderNode->prepareTree(info);
mAnimationContext->runRemainingAnimations(info);
freePrefetechedLayers();
if (CC_UNLIKELY(!mNativeWindow.get())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
info.out.canDrawThisFrame = false;
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 (!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);
}
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!");
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();
EGLint width, height;
mEglManager.beginFrame(mEglSurface, &width, &height);
if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
mCanvas->setViewport(width, height);
dirty.setEmpty();
} else if (!mBufferPreserved || 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);
}
if (!dirty.isEmpty()) {
mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
dirty.fRight, dirty.fBottom, mOpaque);
} else {
mCanvas->prepare(mOpaque);
}
Rect outBounds;
mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
profiler().draw(mCanvas);
bool drew = mCanvas->finish();
// 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);
}
// TODO: Use a fence for real completion?
mCurrentFrameInfo->markFrameCompleted();
mJankTracker.addFrame(*mCurrentFrameInfo);
mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
}
// Called by choreographer to do an RT-driven animation
void CanvasContext::doFrame() {
if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) {
return;
}
ATRACE_CALL();
int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
UiFrameInfoBuilder(frameInfo)
.addFlag(FrameInfoFlags::RTAnimation)
.setVsync(mRenderThread.timeLord().computeFrameTimeNanos(),
mRenderThread.timeLord().latestVsync());
TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC));
if (info.out.canDrawThisFrame) {
draw();
}
}
void CanvasContext::invokeFunctor(RenderThread& thread, Functor* functor) {
ATRACE_CALL();
DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
if (thread.eglManager().hasEglContext()) {
mode = DrawGlInfo::kModeProcess;
}
thread.renderState().invokeFunctor(functor, mode, nullptr);
}
void CanvasContext::markLayerInUse(RenderNode* node) {
if (mPrefetechedLayers.erase(node)) {
node->decStrong(nullptr);
}
}
static void destroyPrefetechedNode(RenderNode* node) {
ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...", node->getName());
node->destroyHardwareResources();
node->decStrong(nullptr);
}
void CanvasContext::freePrefetechedLayers() {
if (mPrefetechedLayers.size()) {
std::for_each(mPrefetechedLayers.begin(), mPrefetechedLayers.end(), destroyPrefetechedNode);
mPrefetechedLayers.clear();
}
}
void CanvasContext::buildLayer(RenderNode* node) {
ATRACE_CALL();
if (!mEglManager.hasEglContext() || !mCanvas) {
return;
}
// 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(nullptr);
mPrefetechedLayers.insert(node);
}
bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
layer->apply();
return LayerRenderer::copyLayer(mRenderThread.renderState(), layer->backingLayer(), bitmap);
}
void CanvasContext::destroyHardwareResources() {
stopDrawing();
if (mEglManager.hasEglContext()) {
freePrefetechedLayers();
mRootRenderNode->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);
}
}
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) {
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) {
LOG_ALWAYS_FATAL_IF(!mEglManager.hasEglContext(),
"GL context not initialized!");
task->run();
}
Layer* CanvasContext::createTextureLayer() {
requireSurface();
return LayerRenderer::createTextureLayer(mRenderThread.renderState());
}
void CanvasContext::setTextureAtlas(RenderThread& thread,
const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize) {
thread.eglManager().setTextureAtlas(buffer, map, mapSize);
}
void CanvasContext::dumpFrames(int 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();
}
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */