blob: 9c80ab304b800e6cdca0ca2147e422d875931334 [file] [log] [blame]
/*
* 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;
nsecs_t dequeueStart = mNativeSurface->getLastDequeueStartTime();
if (dequeueStart < mCurrentFrameInfo->get(FrameInfoIndex::SyncStart)) {
// Ignoring dequeue duration as it happened prior to frame render start
// and thus is not part of the frame.
swap.dequeueDuration = 0;
} else {
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 */