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:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
ANDROID_API void destroyLayer(DeferredLayerUpdater* layer);
|
||||
|
||||
ANDROID_API void fence();
|
||||
ANDROID_API void notifyFramePending();
|
||||
|
||||
private:
|
||||
RenderThread& mRenderThread;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user