Bag of scheduling tweaks

Bug: 15118640

 * Prevent over-stuffing the queue by dropping frames
 * Prevent double-drawing in one pulse by RT by deferring
   vsync registration until post-draw so that it catches
   the next vsync pulse instead of the current one
 * Bias vsync race condition towards the UI thread
 * Fix queueDelay to actually work

Change-Id: Ibf584258bd93ebcbba058bd976dc8b307f1c6155
This commit is contained in:
John Reck
2014-05-22 15:43:54 -07:00
parent d30241541c
commit a5dda645da
13 changed files with 161 additions and 32 deletions

View File

@@ -578,6 +578,12 @@ public abstract class HardwareRenderer {
*/
abstract void fence();
/**
* Called by {@link ViewRootImpl} when a new performTraverals is scheduled.
*/
public void notifyFramePending() {
}
/**
* Describes a series of frames that should be drawn on screen as a graph.
* Each frame is composed of 1 or more elements.

View File

@@ -290,6 +290,11 @@ public class ThreadedRenderer extends HardwareRenderer {
nFence(mNativeProxy);
}
@Override
public void notifyFramePending() {
nNotifyFramePending(mNativeProxy);
}
@Override
protected void finalize() throws Throwable {
try {
@@ -364,4 +369,5 @@ public class ThreadedRenderer extends HardwareRenderer {
private static native void nDestroyLayer(long nativeProxy, long layer);
private static native void nFence(long nativeProxy);
private static native void nNotifyFramePending(long nativeProxy);
}

View File

@@ -999,6 +999,17 @@ public final class ViewRootImpl implements ViewParent,
}
}
/**
* Notifies the HardwareRenderer that a new frame will be coming soon.
* Currently only {@link ThreadedRenderer} cares about this, and uses
* this knowledge to adjust the scheduling of off-thread animations
*/
void notifyRendererOfFramePending() {
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.notifyFramePending();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
@@ -1006,6 +1017,7 @@ public final class ViewRootImpl implements ViewParent,
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
scheduleConsumeBatchedInput();
notifyRendererOfFramePending();
}
}

View File

@@ -299,6 +299,12 @@ static void android_view_ThreadedRenderer_fence(JNIEnv* env, jobject clazz,
proxy->fence();
}
static void android_view_ThreadedRenderer_notifyFramePending(JNIEnv* env, jobject clazz,
jlong proxyPtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
proxy->notifyFramePending();
}
#endif
// ----------------------------------------------------------------------------
@@ -329,6 +335,7 @@ static JNINativeMethod gMethods[] = {
{ "nCopyLayerInto", "(JJJ)Z", (void*) android_view_ThreadedRenderer_copyLayerInto },
{ "nDestroyLayer", "(JJ)V", (void*) android_view_ThreadedRenderer_destroyLayer },
{ "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence },
{ "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
#endif
};

View File

@@ -52,6 +52,7 @@ struct TreeInfo {
: hasFunctors(false)
, hasAnimations(false)
, requiresUiRedraw(false)
, canDrawThisFrame(true)
{}
bool hasFunctors;
// This is only updated if evaluateAnimations is true
@@ -60,6 +61,13 @@ struct TreeInfo {
// animate itself, such as if hasFunctors is true
// This is only set if hasAnimations is true
bool requiresUiRedraw;
// This is set to true if draw() can be called this frame
// false means that we must delay until the next vsync pulse as frame
// production is outrunning consumption
// NOTE that if this is false CanvasContext will set either requiresUiRedraw
// *OR* will post itself for the next vsync automatically, use this
// only to avoid calling draw()
bool canDrawThisFrame;
} out;
// TODO: Damage calculations

View File

@@ -360,7 +360,9 @@ void CanvasContext::destroyCanvasAndSurface() {
setSurface(NULL);
}
void CanvasContext::setSurface(EGLNativeWindowType window) {
void CanvasContext::setSurface(ANativeWindow* window) {
mNativeWindow = window;
if (mEglSurface != EGL_NO_SURFACE) {
mGlobalContext->destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
@@ -393,7 +395,7 @@ void CanvasContext::requireSurface() {
makeCurrent();
}
bool CanvasContext::initialize(EGLNativeWindowType window) {
bool CanvasContext::initialize(ANativeWindow* window) {
if (mCanvas) return false;
setSurface(window);
mCanvas = new OpenGLRenderer();
@@ -401,11 +403,11 @@ bool CanvasContext::initialize(EGLNativeWindowType window) {
return true;
}
void CanvasContext::updateSurface(EGLNativeWindowType window) {
void CanvasContext::updateSurface(ANativeWindow* window) {
setSurface(window);
}
void CanvasContext::pauseSurface(EGLNativeWindowType window) {
void CanvasContext::pauseSurface(ANativeWindow* window) {
// TODO: For now we just need a fence, in the future suspend any animations
// and such to prevent from trying to render into this surface
}
@@ -456,7 +458,15 @@ void CanvasContext::prepareTree(TreeInfo& info) {
info.frameTimeMs = mRenderThread.timeLord().frameTimeMs();
mRootRenderNode->prepareTree(info);
if (info.out.hasAnimations) {
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.hasFunctors) {
info.out.requiresUiRedraw = true;
} else if (!info.out.requiresUiRedraw) {
@@ -467,6 +477,11 @@ void CanvasContext::prepareTree(TreeInfo& info) {
}
}
void CanvasContext::notifyFramePending() {
ATRACE_CALL();
mRenderThread.pushBackFrameCallback(this);
}
void CanvasContext::draw(Rect* dirty) {
LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
"drawDisplayList called on a context with no canvas or surface!");
@@ -515,7 +530,9 @@ void CanvasContext::doFrame() {
info.prepareTextures = false;
prepareTree(info);
draw(NULL);
if (info.out.canDrawThisFrame) {
draw(NULL);
}
}
void CanvasContext::invokeFunctor(Functor* functor) {

View File

@@ -48,9 +48,9 @@ public:
CanvasContext(bool translucent, RenderNode* rootRenderNode);
virtual ~CanvasContext();
bool initialize(EGLNativeWindowType window);
void updateSurface(EGLNativeWindowType window);
void pauseSurface(EGLNativeWindowType window);
bool initialize(ANativeWindow* window);
void updateSurface(ANativeWindow* window);
void pauseSurface(ANativeWindow* window);
void setup(int width, int height, const Vector3& lightCenter, float lightRadius);
void setOpaque(bool opaque);
void makeCurrent();
@@ -73,11 +73,15 @@ public:
ANDROID_API static void setTextureAtlas(const sp<GraphicBuffer>& buffer,
int64_t* map, size_t mapSize);
void notifyFramePending();
private:
friend class RegisterFrameCallbackTask;
void processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters, TreeInfo& info);
void prepareTree(TreeInfo& info);
void setSurface(EGLNativeWindowType window);
void setSurface(ANativeWindow* window);
void swapBuffers();
void requireSurface();
@@ -85,6 +89,7 @@ private:
GlobalContext* mGlobalContext;
RenderThread& mRenderThread;
sp<ANativeWindow> mNativeWindow;
EGLSurface mEglSurface;
bool mDirtyRegionsEnabled;

View File

@@ -87,7 +87,13 @@ void DrawFrameTask::postAndWait() {
void DrawFrameTask::run() {
ATRACE_NAME("DrawFrame");
bool canUnblockUiThread = syncFrameState();
bool canUnblockUiThread;
bool canDrawThisFrame;
{
TreeInfo info;
canUnblockUiThread = syncFrameState(info);
canDrawThisFrame = info.out.canDrawThisFrame;
}
// Grab a copy of everything we need
Rect dirty(mDirty);
@@ -98,7 +104,9 @@ void DrawFrameTask::run() {
unblockUiThread();
}
context->draw(&dirty);
if (CC_LIKELY(canDrawThisFrame)) {
context->draw(&dirty);
}
if (!canUnblockUiThread) {
unblockUiThread();
@@ -111,12 +119,11 @@ static void initTreeInfo(TreeInfo& info) {
info.evaluateAnimations = true;
}
bool DrawFrameTask::syncFrameState() {
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
ATRACE_CALL();
mRenderThread->timeLord().vsyncReceived(mFrameTimeNanos);
mContext->makeCurrent();
Caches::getInstance().textureCache.resetMarkInUse();
TreeInfo info;
initTreeInfo(info);
mContext->prepareDraw(&mLayers, info);
if (info.out.hasAnimations) {

View File

@@ -24,6 +24,7 @@
#include "RenderTask.h"
#include "../Rect.h"
#include "../TreeInfo.h"
namespace android {
namespace uirenderer {
@@ -65,7 +66,7 @@ public:
private:
void postAndWait();
bool syncFrameState();
bool syncFrameState(TreeInfo& info);
void unblockUiThread();
Mutex mLock;

View File

@@ -112,7 +112,7 @@ bool RenderProxy::loadSystemProperties() {
return (bool) postAndWait(task);
}
CREATE_BRIDGE2(initialize, CanvasContext* context, EGLNativeWindowType window) {
CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
return (void*) args->context->initialize(args->window);
}
@@ -123,7 +123,7 @@ bool RenderProxy::initialize(const sp<ANativeWindow>& window) {
return (bool) postAndWait(task);
}
CREATE_BRIDGE2(updateSurface, CanvasContext* context, EGLNativeWindowType window) {
CREATE_BRIDGE2(updateSurface, CanvasContext* context, ANativeWindow* window) {
args->context->updateSurface(args->window);
return NULL;
}
@@ -135,7 +135,7 @@ void RenderProxy::updateSurface(const sp<ANativeWindow>& window) {
postAndWait(task);
}
CREATE_BRIDGE2(pauseSurface, CanvasContext* context, EGLNativeWindowType window) {
CREATE_BRIDGE2(pauseSurface, CanvasContext* context, ANativeWindow* window) {
args->context->pauseSurface(args->window);
return NULL;
}
@@ -292,6 +292,17 @@ void RenderProxy::fence() {
postAndWait(task);
}
CREATE_BRIDGE1(notifyFramePending, CanvasContext* context) {
args->context->notifyFramePending();
return NULL;
}
void RenderProxy::notifyFramePending() {
SETUP_TASK(notifyFramePending);
args->context = mContext;
mRenderThread.queueAtFront(task);
}
void RenderProxy::post(RenderTask* task) {
mRenderThread.queue(task);
}

View File

@@ -82,6 +82,7 @@ public:
ANDROID_API void destroyLayer(DeferredLayerUpdater* layer);
ANDROID_API void fence();
ANDROID_API void notifyFramePending();
private:
RenderThread& mRenderThread;

View File

@@ -37,7 +37,7 @@ namespace renderthread {
static const size_t EVENT_BUFFER_SIZE = 100;
// Slight delay to give the UI time to push us a new frame before we replay
static const int DISPATCH_FRAME_CALLBACKS_DELAY = 0;
static const int DISPATCH_FRAME_CALLBACKS_DELAY = 4;
TaskQueue::TaskQueue() : mHead(0), mTail(0) {}
@@ -91,6 +91,15 @@ void TaskQueue::queue(RenderTask* task) {
}
}
void TaskQueue::queueAtFront(RenderTask* task) {
if (mTail) {
task->mNext = mHead;
mHead = task;
} else {
mTail = mHead = task;
}
}
void TaskQueue::remove(RenderTask* task) {
// TaskQueue is strict here to enforce that users are keeping track of
// their RenderTasks due to how their memory is managed
@@ -188,20 +197,22 @@ static nsecs_t latestVsyncEvent(DisplayEventReceiver* receiver) {
return latest;
}
void RenderThread::drainDisplayEventQueue() {
void RenderThread::drainDisplayEventQueue(bool skipCallbacks) {
ATRACE_CALL();
nsecs_t vsyncEvent = latestVsyncEvent(mDisplayEventReceiver);
if (vsyncEvent > 0) {
mVsyncRequested = false;
mTimeLord.vsyncReceived(vsyncEvent);
if (!mFrameCallbackTaskPending) {
if (!skipCallbacks && !mFrameCallbackTaskPending) {
ATRACE_NAME("queue mFrameCallbackTask");
mFrameCallbackTaskPending = true;
//queueDelayed(mFrameCallbackTask, DISPATCH_FRAME_CALLBACKS_DELAY);
queue(mFrameCallbackTask);
queueDelayed(mFrameCallbackTask, DISPATCH_FRAME_CALLBACKS_DELAY);
}
}
}
void RenderThread::dispatchFrameCallbacks() {
ATRACE_CALL();
mFrameCallbackTaskPending = false;
std::set<IFrameCallback*> callbacks;
@@ -212,6 +223,15 @@ void RenderThread::dispatchFrameCallbacks() {
}
}
void RenderThread::requestVsync() {
if (!mVsyncRequested) {
mVsyncRequested = true;
status_t status = mDisplayEventReceiver->requestNextVsync();
LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
"requestNextVsync failed with status: %d", status);
}
}
bool RenderThread::threadLoop() {
initializeDisplayEventReceiver();
@@ -236,6 +256,14 @@ bool RenderThread::threadLoop() {
timeoutMillis = 0;
}
}
if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
drainDisplayEventQueue(true);
mFrameCallbacks.insert(
mPendingRegistrationFrameCallbacks.begin(), mPendingRegistrationFrameCallbacks.end());
mPendingRegistrationFrameCallbacks.clear();
requestVsync();
}
}
return false;
@@ -250,6 +278,12 @@ void RenderThread::queue(RenderTask* task) {
}
}
void RenderThread::queueAtFront(RenderTask* task) {
AutoMutex _lock(mLock);
mQueue.queueAtFront(task);
mLooper->wake();
}
void RenderThread::queueDelayed(RenderTask* task, int delayMs) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
task->mRunAt = now + milliseconds_to_nanoseconds(delayMs);
@@ -262,17 +296,18 @@ void RenderThread::remove(RenderTask* task) {
}
void RenderThread::postFrameCallback(IFrameCallback* callback) {
mFrameCallbacks.insert(callback);
if (!mVsyncRequested) {
mVsyncRequested = true;
status_t status = mDisplayEventReceiver->requestNextVsync();
LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
"requestNextVsync failed with status: %d", status);
}
mPendingRegistrationFrameCallbacks.insert(callback);
}
void RenderThread::removeFrameCallback(IFrameCallback* callback) {
mFrameCallbacks.erase(callback);
mPendingRegistrationFrameCallbacks.erase(callback);
}
void RenderThread::pushBackFrameCallback(IFrameCallback* callback) {
if (mFrameCallbacks.erase(callback)) {
mPendingRegistrationFrameCallbacks.insert(callback);
}
}
RenderTask* RenderThread::nextTask(nsecs_t* nextWakeup) {
@@ -281,11 +316,13 @@ RenderTask* RenderThread::nextTask(nsecs_t* nextWakeup) {
if (!next) {
mNextWakeup = LLONG_MAX;
} else {
mNextWakeup = next->mRunAt;
// Most tasks won't be delayed, so avoid unnecessary systemTime() calls
if (next->mRunAt <= 0 || next->mRunAt <= systemTime(SYSTEM_TIME_MONOTONIC)) {
next = mQueue.next();
} else {
next = 0;
}
mNextWakeup = next->mRunAt;
}
if (nextWakeup) {
*nextWakeup = mNextWakeup;

View File

@@ -44,6 +44,7 @@ public:
RenderTask* next();
void queue(RenderTask* task);
void queueAtFront(RenderTask* task);
RenderTask* peek();
void remove(RenderTask* task);
@@ -66,12 +67,16 @@ public:
// RenderThread takes complete ownership of tasks that are queued
// and will delete them after they are run
ANDROID_API void queue(RenderTask* task);
ANDROID_API void queueAtFront(RenderTask* task);
void queueDelayed(RenderTask* task, int delayMs);
void remove(RenderTask* task);
// Mimics android.view.Choreographer
void postFrameCallback(IFrameCallback* callback);
void removeFrameCallback(IFrameCallback* callback);
// If the callback is currently registered, it will be pushed back until
// the next vsync. If it is not currently registered this does nothing.
void pushBackFrameCallback(IFrameCallback* callback);
TimeLord& timeLord() { return mTimeLord; }
@@ -87,8 +92,9 @@ private:
void initializeDisplayEventReceiver();
static int displayEventReceiverCallback(int fd, int events, void* data);
void drainDisplayEventQueue();
void drainDisplayEventQueue(bool skipCallbacks = false);
void dispatchFrameCallbacks();
void requestVsync();
// Returns the next task to be run. If this returns NULL nextWakeup is set
// to the time to requery for the nextTask to run. mNextWakeup is also
@@ -104,6 +110,11 @@ private:
DisplayEventReceiver* mDisplayEventReceiver;
bool mVsyncRequested;
std::set<IFrameCallback*> mFrameCallbacks;
// We defer the actual registration of these callbacks until
// both mQueue *and* mDisplayEventReceiver have been drained off all
// immediate events. This makes sure that we catch the next vsync, not
// the previous one
std::set<IFrameCallback*> mPendingRegistrationFrameCallbacks;
bool mFrameCallbackTaskPending;
DispatchFrameCallbacks* mFrameCallbackTask;