From 027b2188473bd48c34cdafad1705abac7fff9356 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Thu, 18 Mar 2021 16:50:38 -0400 Subject: Integrate HWUI with PerformanceHintManager PerformanceHintManager.Session is in java, so add JNI and a HintSessionWrapper class in HardwareRenderer. Then pass the two calls as two std::functions into DrawFrameTask. Note Session is created per HardwareRenderer, not global (per RenderThread). Session includes UI thread, render thread, and the thread pool. Desired duration is from the intended start duration to the frame deadline. Add an actual frame start time to compute Add system properties: debug.hwui.use_hint_manager to enable PerformanceHintManager debug.hwui.target_cpu_time_percent to control percentage of frame time to be used for target cpu duration. Test: Manual test that there are no crashes and values make sense. Bug: 158791282 Change-Id: I83f25433c10daa20033803fb7c4ae45eab34f1d3 --- libs/hwui/FrameInfo.cpp | 29 ++----- libs/hwui/FrameInfo.h | 3 +- libs/hwui/Properties.cpp | 7 ++ libs/hwui/Properties.h | 17 ++++ .../hwui/jni/android_graphics_HardwareRenderer.cpp | 95 ++++++++++++++++++++++ libs/hwui/renderthread/DrawFrameTask.cpp | 29 +++++++ libs/hwui/renderthread/DrawFrameTask.h | 6 ++ libs/hwui/renderthread/RenderProxy.cpp | 6 ++ libs/hwui/renderthread/RenderProxy.h | 2 + libs/hwui/thread/CommonPool.cpp | 26 +++++- libs/hwui/thread/CommonPool.h | 5 ++ 11 files changed, 201 insertions(+), 24 deletions(-) (limited to 'libs/hwui') diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp index 2448cc904104..51fbf363f51c 100644 --- a/libs/hwui/FrameInfo.cpp +++ b/libs/hwui/FrameInfo.cpp @@ -21,29 +21,16 @@ namespace android { namespace uirenderer { const std::array FrameInfoNames{ - "Flags", - "FrameTimelineVsyncId", - "IntendedVsync", - "Vsync", - "InputEventId", - "HandleInputStart", - "AnimationStart", - "PerformTraversalsStart", - "DrawStart", - "FrameDeadline", - "SyncQueued", - "SyncStart", - "IssueDrawCommandsStart", - "SwapBuffers", - "FrameCompleted", - "DequeueBufferDuration", - "QueueBufferDuration", - "GpuCompleted", - "SwapBuffersCompleted", - "DisplayPresentTime", + "Flags", "FrameTimelineVsyncId", "IntendedVsync", + "Vsync", "InputEventId", "HandleInputStart", + "AnimationStart", "PerformTraversalsStart", "DrawStart", + "FrameDeadline", "FrameStartTime", "SyncQueued", + "SyncStart", "IssueDrawCommandsStart", "SwapBuffers", + "FrameCompleted", "DequeueBufferDuration", "QueueBufferDuration", + "GpuCompleted", "SwapBuffersCompleted", "DisplayPresentTime", }; -static_assert(static_cast(FrameInfoIndex::NumIndexes) == 20, +static_assert(static_cast(FrameInfoIndex::NumIndexes) == 21, "Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)"); void FrameInfo::importUiThreadInfo(int64_t* info) { diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h index e9b2f4a9bb3b..62ac4ca5fdad 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -28,7 +28,7 @@ namespace android { namespace uirenderer { -static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 10; +static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 11; enum class FrameInfoIndex { Flags = 0, @@ -41,6 +41,7 @@ enum class FrameInfoIndex { PerformTraversalsStart, DrawStart, FrameDeadline, + FrameStartTime, // End of UI frame info SyncQueued, diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index e58f31fd15eb..7af0a221d228 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -81,6 +81,9 @@ bool Properties::isolatedProcess = false; int Properties::contextPriority = 0; float Properties::defaultSdrWhitePoint = 200.f; +bool Properties::useHintManager = true; +int Properties::targetCpuTimePercentage = 70; + bool Properties::load() { bool prevDebugLayersUpdates = debugLayersUpdates; bool prevDebugOverdraw = debugOverdraw; @@ -128,6 +131,10 @@ bool Properties::load() { runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false); + useHintManager = base::GetBoolProperty(PROPERTY_USE_HINT_MANAGER, true); + targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70); + if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70; + return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw); } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index ea9cbd592d29..1cb87be98051 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -157,6 +157,20 @@ enum DebugLevel { */ #define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename" +/** + * Controls whether HWUI will send timing hints to HintManager for + * better CPU scheduling. Accepted values are "true" and "false". + */ +#define PROPERTY_USE_HINT_MANAGER "debug.hwui.use_hint_manager" + +/** + * Percentage of frame time that's used for CPU work. The rest is + * reserved for GPU work. This is used with use_hint_manager to + * provide timing hints to HintManager. Accepted values are + * integer from 1-100. + */ +#define PROPERTY_TARGET_CPU_TIME_PERCENTAGE "debug.hwui.target_cpu_time_percent" + /** * Property for whether this is running in the emulator. */ @@ -253,6 +267,9 @@ public: static float defaultSdrWhitePoint; + static bool useHintManager; + static int targetCpuTimePercentage; + private: static ProfileType sProfileType; static bool sDisableProfileBars; diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index f24ba5c1c878..bd1da985a33e 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -34,14 +34,18 @@ #include #include #include +#include #include #include #include #include #include +#include + #include #include +#include #include "android_graphics_HardwareRendererObserver.h" @@ -53,6 +57,10 @@ using namespace android::uirenderer::renderthread; struct { jclass clazz; jmethodID invokePictureCapturedCallback; + jmethodID createHintSession; + jmethodID updateTargetWorkDuration; + jmethodID reportActualWorkDuration; + jmethodID closeHintSession; } gHardwareRenderer; struct { @@ -71,6 +79,14 @@ static JNIEnv* getenv(JavaVM* vm) { return env; } +static bool hasExceptionAndClear(JNIEnv* env) { + if (GraphicsJNI::hasException(env)) { + env->ExceptionClear(); + return true; + } + return false; +} + typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface); ANW_fromSurface fromSurface; @@ -120,6 +136,67 @@ private: } }; +class HintSessionWrapper : public LightRefBase { +public: + static sp create(JNIEnv* env, RenderProxy* proxy) { + if (!Properties::useHintManager) return nullptr; + + // Include UI thread (self), render thread, and thread pool. + std::vector tids = CommonPool::getThreadIds(); + tids.push_back(proxy->getRenderThreadTid()); + tids.push_back(pthread_gettid_np(pthread_self())); + + jintArray tidsArray = env->NewIntArray(tids.size()); + if (hasExceptionAndClear(env)) return nullptr; + env->SetIntArrayRegion(tidsArray, 0, tids.size(), reinterpret_cast(tids.data())); + if (hasExceptionAndClear(env)) return nullptr; + jobject session = env->CallStaticObjectMethod( + gHardwareRenderer.clazz, gHardwareRenderer.createHintSession, tidsArray); + if (hasExceptionAndClear(env) || !session) return nullptr; + return new HintSessionWrapper(env, session); + } + + ~HintSessionWrapper() { + if (!mSession) return; + JNIEnv* env = getenv(mVm); + env->CallStaticVoidMethod(gHardwareRenderer.clazz, gHardwareRenderer.closeHintSession, + mSession); + hasExceptionAndClear(env); + env->DeleteGlobalRef(mSession); + mSession = nullptr; + } + + void updateTargetWorkDuration(long targetDurationNanos) { + if (!mSession) return; + JNIEnv* env = getenv(mVm); + env->CallStaticVoidMethod(gHardwareRenderer.clazz, + gHardwareRenderer.updateTargetWorkDuration, mSession, + static_cast(targetDurationNanos)); + hasExceptionAndClear(env); + } + + void reportActualWorkDuration(long actualDurationNanos) { + if (!mSession) return; + JNIEnv* env = getenv(mVm); + env->CallStaticVoidMethod(gHardwareRenderer.clazz, + gHardwareRenderer.reportActualWorkDuration, mSession, + static_cast(actualDurationNanos)); + hasExceptionAndClear(env); + } + +private: + HintSessionWrapper(JNIEnv* env, jobject jobject) { + env->GetJavaVM(&mVm); + if (jobject) { + mSession = env->NewGlobalRef(jobject); + LOG_ALWAYS_FATAL_IF(!mSession, "Failed to make global ref"); + } + } + + JavaVM* mVm = nullptr; + jobject mSession = nullptr; +}; + static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) { RenderProxy::rotateProcessStatsBuffer(); } @@ -147,6 +224,12 @@ static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject claz RootRenderNode* rootRenderNode = reinterpret_cast(rootRenderNodePtr); ContextFactoryImpl factory(rootRenderNode); RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory); + sp wrapper = HintSessionWrapper::create(env, proxy); + if (wrapper) { + proxy->setHintSessionCallbacks( + [wrapper](int64_t nanos) { wrapper->updateTargetWorkDuration(nanos); }, + [wrapper](int64_t nanos) { wrapper->reportActualWorkDuration(nanos); }); + } return (jlong) proxy; } @@ -769,6 +852,18 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) { gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer, "invokePictureCapturedCallback", "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V"); + gHardwareRenderer.createHintSession = + GetStaticMethodIDOrDie(env, hardwareRenderer, "createHintSession", + "([I)Landroid/os/PerformanceHintManager$Session;"); + gHardwareRenderer.updateTargetWorkDuration = + GetStaticMethodIDOrDie(env, hardwareRenderer, "updateTargetWorkDuration", + "(Landroid/os/PerformanceHintManager$Session;J)V"); + gHardwareRenderer.reportActualWorkDuration = + GetStaticMethodIDOrDie(env, hardwareRenderer, "reportActualWorkDuration", + "(Landroid/os/PerformanceHintManager$Session;J)V"); + gHardwareRenderer.closeHintSession = + GetStaticMethodIDOrDie(env, hardwareRenderer, "closeHintSession", + "(Landroid/os/PerformanceHintManager$Session;)V"); jclass frameCallbackClass = FindClassOrDie(env, "android/graphics/HardwareRenderer$FrameDrawingCallback"); diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 3408ffda3f9d..7a38a3bc9c05 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -21,6 +21,7 @@ #include "../DeferredLayerUpdater.h" #include "../DisplayList.h" +#include "../Properties.h" #include "../RenderNode.h" #include "CanvasContext.h" #include "RenderThread.h" @@ -44,6 +45,12 @@ void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context, mTargetNode = targetNode; } +void DrawFrameTask::setHintSessionCallbacks(std::function updateTargetWorkDuration, + std::function reportActualWorkDuration) { + mUpdateTargetWorkDuration = std::move(updateTargetWorkDuration); + mReportActualWorkDuration = std::move(reportActualWorkDuration); +} + void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) { LOG_ALWAYS_FATAL_IF(!mContext, "Lifecycle violation, there's no context to pushLayerUpdate with!"); @@ -102,6 +109,9 @@ void DrawFrameTask::run() { CanvasContext* context = mContext; std::function callback = std::move(mFrameCallback); mFrameCallback = nullptr; + int64_t intendedVsync = mFrameInfo[static_cast(FrameInfoIndex::IntendedVsync)]; + int64_t frameDeadline = mFrameInfo[static_cast(FrameInfoIndex::FrameDeadline)]; + int64_t frameStartTime = mFrameInfo[static_cast(FrameInfoIndex::FrameStartTime)]; // From this point on anything in "this" is *UNSAFE TO ACCESS* if (canUnblockUiThread) { @@ -124,6 +134,25 @@ void DrawFrameTask::run() { if (!canUnblockUiThread) { unblockUiThread(); } + + // These member callbacks are effectively const as they are set once during init, so it's safe + // to use these directly instead of making local copies. + if (mUpdateTargetWorkDuration && mReportActualWorkDuration) { + constexpr int64_t kSanityCheckLowerBound = 100000; // 0.1ms + constexpr int64_t kSanityCheckUpperBound = 10000000000; // 10s + int64_t targetWorkDuration = frameDeadline - intendedVsync; + targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100; + if (targetWorkDuration > kSanityCheckLowerBound && + targetWorkDuration < kSanityCheckUpperBound && + targetWorkDuration != mLastTargetWorkDuration) { + mLastTargetWorkDuration = targetWorkDuration; + mUpdateTargetWorkDuration(targetWorkDuration); + } + int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime; + if (frameDuration > kSanityCheckLowerBound && frameDuration < kSanityCheckUpperBound) { + mReportActualWorkDuration(frameDuration); + } + } } bool DrawFrameTask::syncFrameState(TreeInfo& info) { diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index 696cfaef3cd7..3bb574aff59b 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -61,6 +61,8 @@ public: virtual ~DrawFrameTask(); void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode); + void setHintSessionCallbacks(std::function updateTargetWorkDuration, + std::function reportActualWorkDuration); void setContentDrawBounds(int left, int top, int right, int bottom) { mContentDrawBounds.set(left, top, right, bottom); } @@ -107,6 +109,10 @@ private: std::function mFrameCallback; std::function mFrameCompleteCallback; + + nsecs_t mLastTargetWorkDuration = 0; + std::function mUpdateTargetWorkDuration; + std::function mReportActualWorkDuration; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 423cc08189ca..9361abd20852 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -76,6 +76,12 @@ void RenderProxy::setName(const char* name) { mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); }); } +void RenderProxy::setHintSessionCallbacks(std::function updateTargetWorkDuration, + std::function reportActualWorkDuration) { + mDrawFrameTask.setHintSessionCallbacks(std::move(updateTargetWorkDuration), + std::move(reportActualWorkDuration)); +} + void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) { if (window) { ANativeWindow_acquire(window); } mRenderThread.queue().post([this, win = window, enableTimeout]() mutable { diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 366d6b5a172c..8d55d3c55570 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -71,6 +71,8 @@ public: void setSwapBehavior(SwapBehavior swapBehavior); bool loadSystemProperties(); void setName(const char* name); + void setHintSessionCallbacks(std::function updateTargetWorkDuration, + std::function reportActualWorkDuration); void setSurface(ANativeWindow* window, bool enableTimeout = true); void setSurfaceControl(ASurfaceControl* surfaceControl); diff --git a/libs/hwui/thread/CommonPool.cpp b/libs/hwui/thread/CommonPool.cpp index d011bdfe945e..dc92f9f0d39a 100644 --- a/libs/hwui/thread/CommonPool.cpp +++ b/libs/hwui/thread/CommonPool.cpp @@ -29,14 +29,23 @@ CommonPool::CommonPool() { ATRACE_CALL(); CommonPool* pool = this; + std::mutex mLock; + std::vector tids(THREAD_COUNT); + std::vector tidConditionVars(THREAD_COUNT); + // Create 2 workers for (int i = 0; i < THREAD_COUNT; i++) { - std::thread worker([pool, i] { + std::thread worker([pool, i, &mLock, &tids, &tidConditionVars] { { std::array name{"hwuiTask"}; snprintf(name.data(), name.size(), "hwuiTask%d", i); auto self = pthread_self(); pthread_setname_np(self, name.data()); + { + std::unique_lock lock(mLock); + tids[i] = pthread_gettid_np(self); + tidConditionVars[i].notify_one(); + } setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND); auto startHook = renderthread::RenderThread::getOnStartHook(); if (startHook) { @@ -47,6 +56,15 @@ CommonPool::CommonPool() { }); worker.detach(); } + { + std::unique_lock lock(mLock); + for (int i = 0; i < THREAD_COUNT; i++) { + while (!tids[i]) { + tidConditionVars[i].wait(lock); + } + } + } + mWorkerThreadIds = std::move(tids); } CommonPool& CommonPool::instance() { @@ -58,6 +76,10 @@ void CommonPool::post(Task&& task) { instance().enqueue(std::move(task)); } +std::vector CommonPool::getThreadIds() { + return instance().mWorkerThreadIds; +} + void CommonPool::enqueue(Task&& task) { std::unique_lock lock(mLock); while (!mWorkQueue.hasSpace()) { @@ -104,4 +126,4 @@ void CommonPool::doWaitForIdle() { } } // namespace uirenderer -} // namespace android \ No newline at end of file +} // namespace android diff --git a/libs/hwui/thread/CommonPool.h b/libs/hwui/thread/CommonPool.h index 7603eeef4692..74f852bd1413 100644 --- a/libs/hwui/thread/CommonPool.h +++ b/libs/hwui/thread/CommonPool.h @@ -25,6 +25,7 @@ #include #include #include +#include namespace android { namespace uirenderer { @@ -97,6 +98,8 @@ public: return task.get_future().get(); }; + static std::vector getThreadIds(); + // For testing purposes only, blocks until all worker threads are parked. static void waitForIdle(); @@ -111,6 +114,8 @@ private: void workerLoop(); + std::vector mWorkerThreadIds; + std::mutex mLock; std::condition_variable mCondition; int mWaitingThreads = 0; -- cgit v1.2.3-59-g8ed1b