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
This commit is contained in:
Bo Liu
2021-03-18 16:50:38 -04:00
committed by Wei Wang
parent 25318b7316
commit 027b218847
15 changed files with 278 additions and 48 deletions

View File

@@ -737,7 +737,7 @@ public final class Choreographer {
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
vsyncEventData.frameDeadline);
vsyncEventData.frameDeadline, startNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
mLastVsyncEventData = vsyncEventData;

View File

@@ -242,18 +242,19 @@ public final class FrameMetrics {
int PERFORM_TRAVERSALS_START = 7;
int DRAW_START = 8;
int FRAME_DEADLINE = 9;
int SYNC_QUEUED = 10;
int SYNC_START = 11;
int ISSUE_DRAW_COMMANDS_START = 12;
int SWAP_BUFFERS = 13;
int FRAME_COMPLETED = 14;
int DEQUEUE_BUFFER_DURATION = 15;
int QUEUE_BUFFER_DURATION = 16;
int GPU_COMPLETED = 17;
int SWAP_BUFFERS_COMPLETED = 18;
int DISPLAY_PRESENT_TIME = 19;
int FRAME_START_TIME = 10;
int SYNC_QUEUED = 11;
int SYNC_START = 12;
int ISSUE_DRAW_COMMANDS_START = 13;
int SWAP_BUFFERS = 14;
int FRAME_COMPLETED = 15;
int DEQUEUE_BUFFER_DURATION = 16;
int QUEUE_BUFFER_DURATION = 17;
int GPU_COMPLETED = 18;
int SWAP_BUFFERS_COMPLETED = 19;
int DISPLAY_PRESENT_TIME = 20;
int FRAME_STATS_COUNT = 20; // must always be last and in sync with
int FRAME_STATS_COUNT = 21; // must always be last and in sync with
// FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
}

View File

@@ -87,18 +87,22 @@ public final class FrameInfo {
// When the frame needs to be ready by
public static final int FRAME_DEADLINE = 9;
// When frame actually started.
public static final int FRAME_START_TIME = 10;
// Must be the last one
// This value must be in sync with `UI_THREAD_FRAME_INFO_SIZE` in FrameInfo.h
private static final int FRAME_INFO_SIZE = FRAME_DEADLINE + 1;
private static final int FRAME_INFO_SIZE = FRAME_START_TIME + 1;
/** checkstyle */
public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
long frameDeadline) {
long frameDeadline, long frameStartTime) {
frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
frameInfo[INTENDED_VSYNC] = intendedVsync;
frameInfo[VSYNC] = usedVsync;
frameInfo[FLAGS] = 0;
frameInfo[FRAME_DEADLINE] = frameDeadline;
frameInfo[FRAME_START_TIME] = frameStartTime;
}
/** checkstyle */

View File

@@ -28,6 +28,7 @@ import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.PerformanceHintManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
@@ -165,7 +166,7 @@ public class HardwareRenderer {
* to opaque with no light source configured.
*/
public HardwareRenderer() {
ProcessInitializer.sInstance.initDisplayInfo();
ProcessInitializer.sInstance.initUsingContext();
mRootNode = RenderNode.adopt(nCreateRootRenderNode());
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
@@ -365,7 +366,8 @@ public class HardwareRenderer {
*/
public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
// TODO(b/168552873): populate vsync Id once available to Choreographer public API
mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE);
mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE,
vsyncTime);
mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
return this;
}
@@ -835,6 +837,36 @@ public class HardwareRenderer {
callback.onPictureCaptured(picture);
}
/** called by native */
static PerformanceHintManager.Session createHintSession(int[] tids) {
PerformanceHintManager performanceHintManager =
ProcessInitializer.sInstance.getHintManager();
if (performanceHintManager == null) {
return null;
}
// Native code will always set a target duration before reporting actual durations.
// So this is just a placeholder value that's never used.
long targetDurationNanos = 16666667;
return performanceHintManager.createHintSession(tids, targetDurationNanos);
}
/** called by native */
static void updateTargetWorkDuration(PerformanceHintManager.Session session,
long targetDurationNanos) {
session.updateTargetWorkDuration(targetDurationNanos);
}
/** called by native */
static void reportActualWorkDuration(PerformanceHintManager.Session session,
long actualDurationNanos) {
session.reportActualWorkDuration(actualDurationNanos);
}
/** called by native */
static void closeHintSession(PerformanceHintManager.Session session) {
session.close();
}
/**
* Interface used to receive callbacks when a frame is being drawn.
*
@@ -1071,6 +1103,7 @@ public class HardwareRenderer {
private boolean mIsolated = false;
private Context mContext;
private String mPackageName;
private PerformanceHintManager mPerformanceHintManager;
private IGraphicsStats mGraphicsStatsService;
private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
@Override
@@ -1082,6 +1115,10 @@ public class HardwareRenderer {
private ProcessInitializer() {
}
synchronized PerformanceHintManager getHintManager() {
return mPerformanceHintManager;
}
synchronized void setPackageName(String name) {
if (mInitialized) return;
mPackageName = name;
@@ -1127,15 +1164,23 @@ public class HardwareRenderer {
}
}
synchronized void initDisplayInfo() {
if (mDisplayInitialized) return;
synchronized void initUsingContext() {
if (mContext == null) return;
// If we're in an isolated sandbox mode then we shouldn't try to communicate with DMS
initDisplayInfo();
// HintManager and HintSession are designed to be accessible from isoalted processes
// so not checking for isolated process here.
initHintSession();
// Defensively clear out the context in case we were passed a context that can leak
// if we live longer than it, e.g. an activity context.
mContext = null;
}
private void initDisplayInfo() {
if (mDisplayInitialized) return;
if (mIsolated) {
// Defensively clear out the context in case we were passed a context that can leak
// if we live longer than it, e.g. an activity context.
mContext = null;
mDisplayInitialized = true;
return;
}
@@ -1167,11 +1212,14 @@ public class HardwareRenderer {
display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
// Defensively clear out the context
mContext = null;
mDisplayInitialized = true;
}
private void initHintSession() {
if (mContext == null) return;
mPerformanceHintManager = mContext.getSystemService(PerformanceHintManager.class);
}
private void rotateBuffer() {
nRotateProcessStatsBuffer();
requestBuffer();

View File

@@ -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<int>(FrameInfoIndex::NumIndexes) == 20,
static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 21,
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
void FrameInfo::importUiThreadInfo(int64_t* info) {

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -34,14 +34,18 @@
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
#include <renderthread/RenderThread.h>
#include <thread/CommonPool.h>
#include <utils/Color.h>
#include <utils/RefBase.h>
#include <utils/StrongPointer.h>
#include <utils/Timers.h>
#include <utils/TraceUtils.h>
#include <pthread.h>
#include <algorithm>
#include <atomic>
#include <vector>
#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<HintSessionWrapper> {
public:
static sp<HintSessionWrapper> create(JNIEnv* env, RenderProxy* proxy) {
if (!Properties::useHintManager) return nullptr;
// Include UI thread (self), render thread, and thread pool.
std::vector<int> 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<jint*>(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<jlong>(targetDurationNanos));
hasExceptionAndClear(env);
}
void reportActualWorkDuration(long actualDurationNanos) {
if (!mSession) return;
JNIEnv* env = getenv(mVm);
env->CallStaticVoidMethod(gHardwareRenderer.clazz,
gHardwareRenderer.reportActualWorkDuration, mSession,
static_cast<jlong>(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<RootRenderNode*>(rootRenderNodePtr);
ContextFactoryImpl factory(rootRenderNode);
RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory);
sp<HintSessionWrapper> 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");

View File

@@ -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<void(int64_t)> updateTargetWorkDuration,
std::function<void(int64_t)> 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<void(int64_t)> callback = std::move(mFrameCallback);
mFrameCallback = nullptr;
int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)];
int64_t frameStartTime = mFrameInfo[static_cast<int>(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) {

View File

@@ -61,6 +61,8 @@ public:
virtual ~DrawFrameTask();
void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
void setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
std::function<void(int64_t)> reportActualWorkDuration);
void setContentDrawBounds(int left, int top, int right, int bottom) {
mContentDrawBounds.set(left, top, right, bottom);
}
@@ -107,6 +109,10 @@ private:
std::function<void(int64_t)> mFrameCallback;
std::function<void(int64_t)> mFrameCompleteCallback;
nsecs_t mLastTargetWorkDuration = 0;
std::function<void(int64_t)> mUpdateTargetWorkDuration;
std::function<void(int64_t)> mReportActualWorkDuration;
};
} /* namespace renderthread */

View File

@@ -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<void(int64_t)> updateTargetWorkDuration,
std::function<void(int64_t)> 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 {

View File

@@ -71,6 +71,8 @@ public:
void setSwapBehavior(SwapBehavior swapBehavior);
bool loadSystemProperties();
void setName(const char* name);
void setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
std::function<void(int64_t)> reportActualWorkDuration);
void setSurface(ANativeWindow* window, bool enableTimeout = true);
void setSurfaceControl(ASurfaceControl* surfaceControl);

View File

@@ -29,14 +29,23 @@ CommonPool::CommonPool() {
ATRACE_CALL();
CommonPool* pool = this;
std::mutex mLock;
std::vector<int> tids(THREAD_COUNT);
std::vector<std::condition_variable> 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<char, 20> 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<int> 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
} // namespace android

View File

@@ -25,6 +25,7 @@
#include <functional>
#include <future>
#include <mutex>
#include <vector>
namespace android {
namespace uirenderer {
@@ -97,6 +98,8 @@ public:
return task.get_future().get();
};
static std::vector<int> getThreadIds();
// For testing purposes only, blocks until all worker threads are parked.
static void waitForIdle();
@@ -111,6 +114,8 @@ private:
void workerLoop();
std::vector<int> mWorkerThreadIds;
std::mutex mLock;
std::condition_variable mCondition;
int mWaitingThreads = 0;