Add GPU completion to FrameMetrics (1/3)
- Add SurfaceStatsCallback to TransactionCompletedListener - Register a callback in RenderProxy to be called when we have surface stats from SF via the BLAST callback. - Instead of finishing a frame for frame metrics reporting immediately, wait until BLAST callback fires, note GPU completion time and finish frame. - Expose GPU_COMPLETION in FrameMetrics - Modify TOTAL_DURATION to also include GPU_COMPLETION Test: FrameMetricsListenerTest Fixes: 171046219 Change-Id: I16fa1d80cfc4e7a5527c18fec7e885409f17ee4d
This commit is contained in:
@@ -46371,8 +46371,10 @@ package android.view {
|
||||
method public long getMetric(int);
|
||||
field public static final int ANIMATION_DURATION = 2; // 0x2
|
||||
field public static final int COMMAND_ISSUE_DURATION = 6; // 0x6
|
||||
field public static final int DEADLINE = 13; // 0xd
|
||||
field public static final int DRAW_DURATION = 4; // 0x4
|
||||
field public static final int FIRST_DRAW_FRAME = 9; // 0x9
|
||||
field public static final int GPU_DURATION = 12; // 0xc
|
||||
field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
|
||||
field public static final int INTENDED_VSYNC_TIMESTAMP = 10; // 0xa
|
||||
field public static final int LAYOUT_MEASURE_DURATION = 3; // 0x3
|
||||
|
||||
@@ -154,6 +154,24 @@ public final class FrameMetrics {
|
||||
*/
|
||||
public static final int VSYNC_TIMESTAMP = 11;
|
||||
|
||||
/**
|
||||
* Metric identifier for GPU duration.
|
||||
* <p>
|
||||
* Represents the total time in nanoseconds this frame took to complete on the GPU.
|
||||
* </p>
|
||||
**/
|
||||
public static final int GPU_DURATION = 12;
|
||||
|
||||
/**
|
||||
* Metric identifier for the total duration that was available to the app to produce a frame.
|
||||
* <p>
|
||||
* Represents the total time in nanoseconds the system allocated for the app to produce its
|
||||
* frame. If FrameMetrics.TOTAL_DURATION < FrameMetrics.DEADLINE, the app hit its intended
|
||||
* deadline and there was no jank visible to the user.
|
||||
* </p>
|
||||
**/
|
||||
public static final int DEADLINE = 13;
|
||||
|
||||
private static final int FRAME_INFO_FLAG_FIRST_DRAW = 1 << 0;
|
||||
|
||||
/**
|
||||
@@ -175,6 +193,8 @@ public final class FrameMetrics {
|
||||
FIRST_DRAW_FRAME,
|
||||
INTENDED_VSYNC_TIMESTAMP,
|
||||
VSYNC_TIMESTAMP,
|
||||
GPU_DURATION,
|
||||
DEADLINE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Metric {}
|
||||
@@ -205,6 +225,8 @@ public final class FrameMetrics {
|
||||
Index.ISSUE_DRAW_COMMANDS_START,
|
||||
Index.SWAP_BUFFERS,
|
||||
Index.FRAME_COMPLETED,
|
||||
Index.GPU_COMPLETED,
|
||||
Index.SWAP_BUFFERS_COMPLETED
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Index {
|
||||
@@ -224,8 +246,10 @@ public final class FrameMetrics {
|
||||
int ISSUE_DRAW_COMMANDS_START = 13;
|
||||
int SWAP_BUFFERS = 14;
|
||||
int FRAME_COMPLETED = 15;
|
||||
int GPU_COMPLETED = 18;
|
||||
int SWAP_BUFFERS_COMPLETED = 19;
|
||||
|
||||
int FRAME_STATS_COUNT = 19; // must always be last and in sync with
|
||||
int FRAME_STATS_COUNT = 20; // must always be last and in sync with
|
||||
// FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
|
||||
}
|
||||
|
||||
@@ -251,9 +275,19 @@ public final class FrameMetrics {
|
||||
// COMMAND_ISSUE
|
||||
Index.ISSUE_DRAW_COMMANDS_START, Index.SWAP_BUFFERS,
|
||||
// SWAP_BUFFERS
|
||||
Index.SWAP_BUFFERS, Index.FRAME_COMPLETED,
|
||||
Index.SWAP_BUFFERS, Index.SWAP_BUFFERS_COMPLETED,
|
||||
// TOTAL_DURATION
|
||||
Index.INTENDED_VSYNC, Index.FRAME_COMPLETED,
|
||||
// RESERVED for FIRST_DRAW_FRAME
|
||||
0, 0,
|
||||
// RESERVED forINTENDED_VSYNC_TIMESTAMP
|
||||
0, 0,
|
||||
// RESERVED VSYNC_TIMESTAMP
|
||||
0, 0,
|
||||
// GPU_DURATION
|
||||
Index.SWAP_BUFFERS, Index.GPU_COMPLETED,
|
||||
// DEADLINE
|
||||
Index.INTENDED_VSYNC, Index.FRAME_DEADLINE,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -294,7 +328,7 @@ public final class FrameMetrics {
|
||||
* @return the value of the metric or -1 if it is not available.
|
||||
*/
|
||||
public long getMetric(@Metric int id) {
|
||||
if (id < UNKNOWN_DELAY_DURATION || id > VSYNC_TIMESTAMP) {
|
||||
if (id < UNKNOWN_DELAY_DURATION || id > DEADLINE) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -480,6 +480,8 @@ cc_defaults {
|
||||
|
||||
target: {
|
||||
android: {
|
||||
header_libs: ["libandroid_headers_private" ],
|
||||
|
||||
srcs: [
|
||||
"hwui/AnimatedImageThread.cpp",
|
||||
"pipeline/skia/ATraceMemoryDump.cpp",
|
||||
@@ -567,6 +569,7 @@ cc_defaults {
|
||||
name: "hwui_test_defaults",
|
||||
defaults: ["hwui_defaults"],
|
||||
test_suites: ["device-tests"],
|
||||
header_libs: ["libandroid_headers_private"],
|
||||
target: {
|
||||
android: {
|
||||
shared_libs: [
|
||||
@@ -604,7 +607,6 @@ cc_test {
|
||||
shared_libs: [
|
||||
"libmemunreachable",
|
||||
],
|
||||
|
||||
srcs: [
|
||||
"tests/unit/main.cpp",
|
||||
"tests/unit/ABitmapTests.cpp",
|
||||
|
||||
@@ -42,7 +42,7 @@ const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> Fram
|
||||
"GpuCompleted",
|
||||
};
|
||||
|
||||
static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 19,
|
||||
static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 20,
|
||||
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
|
||||
|
||||
void FrameInfo::importUiThreadInfo(int64_t* info) {
|
||||
|
||||
@@ -55,6 +55,7 @@ enum class FrameInfoIndex {
|
||||
QueueBufferDuration,
|
||||
|
||||
GpuCompleted,
|
||||
SwapBuffersCompleted,
|
||||
|
||||
// Must be the last value!
|
||||
// Also must be kept in sync with FrameMetrics.java#FRAME_STATS_COUNT
|
||||
@@ -120,6 +121,10 @@ public:
|
||||
|
||||
void markSwapBuffers() { set(FrameInfoIndex::SwapBuffers) = systemTime(SYSTEM_TIME_MONOTONIC); }
|
||||
|
||||
void markSwapBuffersCompleted() {
|
||||
set(FrameInfoIndex::SwapBuffersCompleted) = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||
}
|
||||
|
||||
void markFrameCompleted() { set(FrameInfoIndex::FrameCompleted) = systemTime(SYSTEM_TIME_MONOTONIC); }
|
||||
|
||||
void addFlag(int frameInfoFlag) {
|
||||
|
||||
@@ -16,14 +16,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <ui/FatVector.h>
|
||||
|
||||
#include "FrameInfo.h"
|
||||
#include "FrameMetricsObserver.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
@@ -32,9 +34,13 @@ class FrameMetricsReporter {
|
||||
public:
|
||||
FrameMetricsReporter() {}
|
||||
|
||||
void addObserver(FrameMetricsObserver* observer) { mObservers.push_back(observer); }
|
||||
void addObserver(FrameMetricsObserver* observer) {
|
||||
std::lock_guard lock(mObserversLock);
|
||||
mObservers.push_back(observer);
|
||||
}
|
||||
|
||||
bool removeObserver(FrameMetricsObserver* observer) {
|
||||
std::lock_guard lock(mObserversLock);
|
||||
for (size_t i = 0; i < mObservers.size(); i++) {
|
||||
if (mObservers[i].get() == observer) {
|
||||
mObservers.erase(mObservers.begin() + i);
|
||||
@@ -44,16 +50,28 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasObservers() { return mObservers.size() > 0; }
|
||||
bool hasObservers() {
|
||||
std::lock_guard lock(mObserversLock);
|
||||
return mObservers.size() > 0;
|
||||
}
|
||||
|
||||
void reportFrameMetrics(const int64_t* stats) {
|
||||
for (size_t i = 0; i < mObservers.size(); i++) {
|
||||
mObservers[i]->notify(stats);
|
||||
FatVector<sp<FrameMetricsObserver>, 10> copy;
|
||||
{
|
||||
std::lock_guard lock(mObserversLock);
|
||||
copy.reserve(mObservers.size());
|
||||
for (size_t i = 0; i < mObservers.size(); i++) {
|
||||
copy.push_back(mObservers[i]);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < copy.size(); i++) {
|
||||
copy[i]->notify(stats);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<sp<FrameMetricsObserver> > mObservers;
|
||||
FatVector<sp<FrameMetricsObserver>, 10> mObservers GUARDED_BY(mObserversLock);
|
||||
std::mutex mObserversLock;
|
||||
};
|
||||
|
||||
} // namespace uirenderer
|
||||
|
||||
@@ -79,7 +79,9 @@ static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas;
|
||||
// and filter it out of the frame profile data
|
||||
static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
|
||||
|
||||
JankTracker::JankTracker(ProfileDataContainer* globalData) {
|
||||
JankTracker::JankTracker(ProfileDataContainer* globalData)
|
||||
: mData(globalData->getDataMutex())
|
||||
, mDataMutex(globalData->getDataMutex()) {
|
||||
mGlobalData = globalData;
|
||||
nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
|
||||
nsecs_t sfOffset = DeviceInfo::getCompositorOffset();
|
||||
@@ -107,6 +109,8 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) {
|
||||
}
|
||||
|
||||
void JankTracker::finishFrame(const FrameInfo& frame) {
|
||||
std::lock_guard lock(mDataMutex);
|
||||
|
||||
// Fast-path for jank-free frames
|
||||
int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);
|
||||
if (mDequeueTimeForgiveness && frame[FrameInfoIndex::DequeueBufferDuration] > 500_us) {
|
||||
@@ -125,7 +129,11 @@ void JankTracker::finishFrame(const FrameInfo& frame) {
|
||||
}
|
||||
}
|
||||
|
||||
LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
|
||||
LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64 " start=%" PRIi64
|
||||
" gpuComplete=%" PRIi64, totalDuration,
|
||||
frame[FrameInfoIndex::IntendedVsync],
|
||||
frame[FrameInfoIndex::GpuCompleted]);
|
||||
|
||||
mData->reportFrame(totalDuration);
|
||||
(*mGlobalData)->reportFrame(totalDuration);
|
||||
|
||||
@@ -188,6 +196,7 @@ void JankTracker::finishFrame(const FrameInfo& frame) {
|
||||
|
||||
void JankTracker::dumpData(int fd, const ProfileDataDescription* description,
|
||||
const ProfileData* data) {
|
||||
|
||||
if (description) {
|
||||
switch (description->type) {
|
||||
case JankTrackerType::Generic:
|
||||
@@ -227,6 +236,7 @@ void JankTracker::dumpFrames(int fd) {
|
||||
}
|
||||
|
||||
void JankTracker::reset() {
|
||||
std::lock_guard lock(mDataMutex);
|
||||
mFrames.clear();
|
||||
mData->reset();
|
||||
(*mGlobalData)->reset();
|
||||
@@ -235,6 +245,7 @@ void JankTracker::reset() {
|
||||
}
|
||||
|
||||
void JankTracker::finishGpuDraw(const FrameInfo& frame) {
|
||||
std::lock_guard lock(mDataMutex);
|
||||
int64_t totalGPUDrawTime = frame.gpuDrawTime();
|
||||
if (totalGPUDrawTime >= 0) {
|
||||
mData->reportGPUFrame(totalGPUDrawTime);
|
||||
|
||||
@@ -84,12 +84,15 @@ private:
|
||||
// This is only used if we are in pipelined mode and are using HWC2,
|
||||
// otherwise it's 0.
|
||||
nsecs_t mDequeueTimeForgiveness = 0;
|
||||
ProfileDataContainer mData;
|
||||
ProfileDataContainer* mGlobalData;
|
||||
ProfileDataContainer mData GUARDED_BY(mDataMutex);
|
||||
ProfileDataContainer* mGlobalData GUARDED_BY(mDataMutex);
|
||||
ProfileDataDescription mDescription;
|
||||
|
||||
// Ring buffer large enough for 2 seconds worth of frames
|
||||
RingBuffer<FrameInfo, 120> mFrames;
|
||||
|
||||
// Mutex to protect acccess to mData and mGlobalData obtained from mGlobalData->getDataMutex
|
||||
std::mutex& mDataMutex;
|
||||
};
|
||||
|
||||
} /* namespace uirenderer */
|
||||
|
||||
@@ -38,6 +38,8 @@ void ProfileDataContainer::freeData() {
|
||||
}
|
||||
|
||||
void ProfileDataContainer::rotateStorage() {
|
||||
std::lock_guard lock(mJankDataMutex);
|
||||
|
||||
// If we are mapped we want to stop using the ashmem backend and switch to malloc
|
||||
// We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed
|
||||
// If we aren't sitting on top of ashmem then just do a reset() as it's functionally
|
||||
@@ -50,6 +52,7 @@ void ProfileDataContainer::rotateStorage() {
|
||||
}
|
||||
|
||||
void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
|
||||
std::lock_guard lock(mJankDataMutex);
|
||||
int regionSize = ashmem_get_size_region(ashmemfd);
|
||||
if (regionSize < 0) {
|
||||
int err = errno;
|
||||
@@ -70,7 +73,9 @@ void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
|
||||
return;
|
||||
}
|
||||
|
||||
newData->mergeWith(*mData);
|
||||
if (mData != nullptr) {
|
||||
newData->mergeWith(*mData);
|
||||
}
|
||||
freeData();
|
||||
mData = newData;
|
||||
mIsMapped = true;
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
#include "ProfileData.h"
|
||||
#include "utils/Macros.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
@@ -26,7 +29,8 @@ class ProfileDataContainer {
|
||||
PREVENT_COPY_AND_ASSIGN(ProfileDataContainer);
|
||||
|
||||
public:
|
||||
explicit ProfileDataContainer() {}
|
||||
explicit ProfileDataContainer(std::mutex& jankDataMutex)
|
||||
: mData(new ProfileData()), mJankDataMutex(jankDataMutex) {}
|
||||
|
||||
~ProfileDataContainer() { freeData(); }
|
||||
|
||||
@@ -36,13 +40,16 @@ public:
|
||||
ProfileData* get() { return mData; }
|
||||
ProfileData* operator->() { return mData; }
|
||||
|
||||
std::mutex& getDataMutex() { return mJankDataMutex; }
|
||||
|
||||
private:
|
||||
void freeData();
|
||||
|
||||
// By default this will use malloc memory. It may be moved later to ashmem
|
||||
// if there is shared space for it and a request comes in to do that.
|
||||
ProfileData* mData = new ProfileData;
|
||||
ProfileData* mData GUARDED_BY(mJankDataMutex);
|
||||
bool mIsMapped = false;
|
||||
std::mutex& mJankDataMutex;
|
||||
};
|
||||
|
||||
} /* namespace uirenderer */
|
||||
|
||||
@@ -178,12 +178,16 @@ void CanvasContext::setSurfaceControl(ASurfaceControl* surfaceControl) {
|
||||
if (surfaceControl == mSurfaceControl) return;
|
||||
|
||||
auto funcs = mRenderThread.getASurfaceControlFunctions();
|
||||
|
||||
if (mSurfaceControl != nullptr) {
|
||||
funcs.unregisterListenerFunc(this, &onSurfaceStatsAvailable);
|
||||
funcs.releaseFunc(mSurfaceControl);
|
||||
}
|
||||
mSurfaceControl = surfaceControl;
|
||||
mExpectSurfaceStats = surfaceControl != nullptr;
|
||||
if (mSurfaceControl != nullptr) {
|
||||
funcs.acquireFunc(mSurfaceControl);
|
||||
funcs.registerListenerFunc(surfaceControl, this, &onSurfaceStatsAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,8 +336,8 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
|
||||
// just keep using the previous frame's structure instead
|
||||
if (!wasSkipped(mCurrentFrameInfo)) {
|
||||
mCurrentFrameInfo = mJankTracker.startFrame();
|
||||
mLast4FrameInfos.next().first = mCurrentFrameInfo;
|
||||
}
|
||||
|
||||
mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
|
||||
mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
|
||||
mCurrentFrameInfo->markSyncStart();
|
||||
@@ -538,17 +542,14 @@ void CanvasContext::draw() {
|
||||
}
|
||||
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = swap.dequeueDuration;
|
||||
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = swap.queueDuration;
|
||||
mLast4FrameInfos[-1].second = frameCompleteNr;
|
||||
mHaveNewSurface = false;
|
||||
mFrameNumber = -1;
|
||||
} else {
|
||||
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = 0;
|
||||
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = 0;
|
||||
mLast4FrameInfos[-1].second = -1;
|
||||
}
|
||||
|
||||
// TODO: Use a fence for real completion?
|
||||
mCurrentFrameInfo->markFrameCompleted();
|
||||
mCurrentFrameInfo->markSwapBuffersCompleted();
|
||||
|
||||
#if LOG_FRAMETIME_MMA
|
||||
float thisFrame = mCurrentFrameInfo->duration(FrameInfoIndex::IssueDrawCommandsStart,
|
||||
@@ -572,30 +573,73 @@ void CanvasContext::draw() {
|
||||
mFrameCompleteCallbacks.clear();
|
||||
}
|
||||
|
||||
mJankTracker.finishFrame(*mCurrentFrameInfo);
|
||||
if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
|
||||
mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
|
||||
}
|
||||
|
||||
if (mLast4FrameInfos.size() == mLast4FrameInfos.capacity()) {
|
||||
// By looking 4 frames back, we guarantee all SF stats are available. There are at
|
||||
// most 3 buffers in BufferQueue. Surface object keeps stats for the last 8 frames.
|
||||
FrameInfo* forthBehind = mLast4FrameInfos.front().first;
|
||||
int64_t composedFrameId = mLast4FrameInfos.front().second;
|
||||
nsecs_t acquireTime = -1;
|
||||
if (mNativeSurface) {
|
||||
native_window_get_frame_timestamps(mNativeSurface->getNativeWindow(), composedFrameId,
|
||||
nullptr, &acquireTime, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr);
|
||||
if (requireSwap) {
|
||||
if (mExpectSurfaceStats) {
|
||||
std::lock_guard lock(mLast4FrameInfosMutex);
|
||||
std::pair<FrameInfo*, int64_t>& next = mLast4FrameInfos.next();
|
||||
next.first = mCurrentFrameInfo;
|
||||
next.second = frameCompleteNr;
|
||||
} else {
|
||||
mCurrentFrameInfo->markFrameCompleted();
|
||||
mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
|
||||
= mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
|
||||
finishFrame(mCurrentFrameInfo);
|
||||
}
|
||||
// Ignore default -1, NATIVE_WINDOW_TIMESTAMP_INVALID and NATIVE_WINDOW_TIMESTAMP_PENDING
|
||||
forthBehind->set(FrameInfoIndex::GpuCompleted) = acquireTime > 0 ? acquireTime : -1;
|
||||
mJankTracker.finishGpuDraw(*forthBehind);
|
||||
}
|
||||
|
||||
mRenderThread.cacheManager().onFrameCompleted();
|
||||
}
|
||||
|
||||
void CanvasContext::finishFrame(FrameInfo* frameInfo) {
|
||||
|
||||
// TODO (b/169858044): Consolidate this into a single call.
|
||||
mJankTracker.finishFrame(*frameInfo);
|
||||
mJankTracker.finishGpuDraw(*frameInfo);
|
||||
|
||||
// TODO (b/169858044): Move this into JankTracker to adjust deadline when queue is
|
||||
// double-stuffed.
|
||||
if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
|
||||
mFrameMetricsReporter->reportFrameMetrics(frameInfo->data());
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContext::onSurfaceStatsAvailable(void* context, ASurfaceControl* control,
|
||||
ASurfaceControlStats* stats) {
|
||||
|
||||
CanvasContext* instance = static_cast<CanvasContext*>(context);
|
||||
|
||||
const ASurfaceControlFunctions& functions =
|
||||
instance->mRenderThread.getASurfaceControlFunctions();
|
||||
|
||||
nsecs_t gpuCompleteTime = functions.getAcquireTimeFunc(stats);
|
||||
uint64_t frameNumber = functions.getFrameNumberFunc(stats);
|
||||
|
||||
FrameInfo* frameInfo = nullptr;
|
||||
{
|
||||
std::lock_guard(instance->mLast4FrameInfosMutex);
|
||||
for (size_t i = 0; i < instance->mLast4FrameInfos.size(); i++) {
|
||||
if (instance->mLast4FrameInfos[i].second == frameNumber) {
|
||||
frameInfo = instance->mLast4FrameInfos[i].first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (frameInfo != nullptr) {
|
||||
if (gpuCompleteTime == -1) {
|
||||
gpuCompleteTime = frameInfo->get(FrameInfoIndex::SwapBuffersCompleted);
|
||||
}
|
||||
if (gpuCompleteTime < frameInfo->get(FrameInfoIndex::SwapBuffers)) {
|
||||
// TODO (b/180488606): Investigate why this can happen for first frames.
|
||||
ALOGW("Impossible GPU complete time swapBuffers=%" PRIi64 " gpuComplete=%" PRIi64,
|
||||
frameInfo->get(FrameInfoIndex::SwapBuffers), gpuCompleteTime);
|
||||
gpuCompleteTime = frameInfo->get(FrameInfoIndex::SwapBuffersCompleted);
|
||||
}
|
||||
frameInfo->set(FrameInfoIndex::FrameCompleted) = gpuCompleteTime;
|
||||
frameInfo->set(FrameInfoIndex::GpuCompleted) = gpuCompleteTime;
|
||||
instance->finishFrame(frameInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Called by choreographer to do an RT-driven animation
|
||||
void CanvasContext::doFrame() {
|
||||
if (!mRenderPipeline->isSurfaceReady()) return;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <SkSize.h>
|
||||
#include <cutils/compiler.h>
|
||||
#include <utils/Functor.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <functional>
|
||||
#include <future>
|
||||
@@ -196,6 +197,10 @@ public:
|
||||
|
||||
SkISize getNextFrameSize() const;
|
||||
|
||||
// Called when SurfaceStats are available.
|
||||
static void onSurfaceStatsAvailable(void* context, ASurfaceControl* control,
|
||||
ASurfaceControlStats* stats);
|
||||
|
||||
private:
|
||||
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
|
||||
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
|
||||
@@ -212,6 +217,7 @@ private:
|
||||
void setupPipelineSurface();
|
||||
|
||||
SkRect computeDirtyRect(const Frame& frame, SkRect* dirty);
|
||||
void finishFrame(FrameInfo* frameInfo);
|
||||
|
||||
// The same type as Frame.mWidth and Frame.mHeight
|
||||
int32_t mLastFrameWidth = 0;
|
||||
@@ -261,7 +267,12 @@ private:
|
||||
std::vector<sp<RenderNode>> mRenderNodes;
|
||||
|
||||
FrameInfo* mCurrentFrameInfo = nullptr;
|
||||
RingBuffer<std::pair<FrameInfo*, int64_t>, 4> mLast4FrameInfos;
|
||||
|
||||
// List of frames that are awaiting GPU completion reporting
|
||||
RingBuffer<std::pair<FrameInfo*, int64_t>, 4> mLast4FrameInfos
|
||||
GUARDED_BY(mLast4FrameInfosMutex);
|
||||
std::mutex mLast4FrameInfosMutex;
|
||||
|
||||
std::string mName;
|
||||
JankTracker mJankTracker;
|
||||
FrameInfoVisualizer mProfiler;
|
||||
@@ -276,6 +287,9 @@ private:
|
||||
std::unique_ptr<IRenderPipeline> mRenderPipeline;
|
||||
|
||||
std::vector<std::function<void(int64_t)>> mFrameCompleteCallbacks;
|
||||
|
||||
// If set to true, we expect that callbacks into onSurfaceStatsAvailable
|
||||
bool mExpectSurfaceStats = false;
|
||||
};
|
||||
|
||||
} /* namespace renderthread */
|
||||
|
||||
@@ -215,6 +215,7 @@ void RenderProxy::notifyFramePending() {
|
||||
|
||||
void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
|
||||
mRenderThread.queue().runSync([&]() {
|
||||
std::lock_guard lock(mRenderThread.getJankDataMutex());
|
||||
mContext->profiler().dumpData(fd);
|
||||
if (dumpFlags & DumpFlags::FrameStats) {
|
||||
mContext->dumpFrames(fd);
|
||||
@@ -234,6 +235,7 @@ void RenderProxy::resetProfileInfo() {
|
||||
|
||||
uint32_t RenderProxy::frameTimePercentile(int percentile) {
|
||||
return mRenderThread.queue().runSync([&]() -> auto {
|
||||
std::lock_guard lock(mRenderThread.globalProfileData().getDataMutex());
|
||||
return mRenderThread.globalProfileData()->findPercentile(percentile);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,6 +59,26 @@ ASurfaceControlFunctions::ASurfaceControlFunctions() {
|
||||
releaseFunc = (ASC_release) dlsym(handle_, "ASurfaceControl_release");
|
||||
LOG_ALWAYS_FATAL_IF(releaseFunc == nullptr,
|
||||
"Failed to find required symbol ASurfaceControl_release!");
|
||||
|
||||
registerListenerFunc = (ASC_registerSurfaceStatsListener) dlsym(handle_,
|
||||
"ASurfaceControl_registerSurfaceStatsListener");
|
||||
LOG_ALWAYS_FATAL_IF(registerListenerFunc == nullptr,
|
||||
"Failed to find required symbol ASurfaceControl_registerSurfaceStatsListener!");
|
||||
|
||||
unregisterListenerFunc = (ASC_unregisterSurfaceStatsListener) dlsym(handle_,
|
||||
"ASurfaceControl_unregisterSurfaceStatsListener");
|
||||
LOG_ALWAYS_FATAL_IF(unregisterListenerFunc == nullptr,
|
||||
"Failed to find required symbol ASurfaceControl_unregisterSurfaceStatsListener!");
|
||||
|
||||
getAcquireTimeFunc = (ASCStats_getAcquireTime) dlsym(handle_,
|
||||
"ASurfaceControlStats_getAcquireTime");
|
||||
LOG_ALWAYS_FATAL_IF(getAcquireTimeFunc == nullptr,
|
||||
"Failed to find required symbol ASurfaceControlStats_getAcquireTime!");
|
||||
|
||||
getFrameNumberFunc = (ASCStats_getFrameNumber) dlsym(handle_,
|
||||
"ASurfaceControlStats_getFrameNumber");
|
||||
LOG_ALWAYS_FATAL_IF(getFrameNumberFunc == nullptr,
|
||||
"Failed to find required symbol ASurfaceControlStats_getFrameNumber!");
|
||||
}
|
||||
|
||||
void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
|
||||
@@ -146,7 +166,8 @@ RenderThread::RenderThread()
|
||||
, mFrameCallbackTaskPending(false)
|
||||
, mRenderState(nullptr)
|
||||
, mEglManager(nullptr)
|
||||
, mFunctorManager(WebViewFunctorManager::instance()) {
|
||||
, mFunctorManager(WebViewFunctorManager::instance())
|
||||
, mGlobalProfileData(mJankDataMutex) {
|
||||
Properties::load();
|
||||
start("RenderThread");
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef RENDERTHREAD_H_
|
||||
#define RENDERTHREAD_H_
|
||||
|
||||
#include <surface_control_private.h>
|
||||
#include <GrDirectContext.h>
|
||||
#include <SkBitmap.h>
|
||||
#include <cutils/compiler.h>
|
||||
@@ -81,11 +82,22 @@ struct VsyncSource {
|
||||
typedef void (*ASC_acquire)(ASurfaceControl* control);
|
||||
typedef void (*ASC_release)(ASurfaceControl* control);
|
||||
|
||||
typedef void (*ASC_registerSurfaceStatsListener)(ASurfaceControl* control, void* context,
|
||||
ASurfaceControl_SurfaceStatsListener func);
|
||||
typedef void (*ASC_unregisterSurfaceStatsListener)(void* context,
|
||||
ASurfaceControl_SurfaceStatsListener func);
|
||||
|
||||
typedef int64_t (*ASCStats_getAcquireTime)(ASurfaceControlStats* stats);
|
||||
typedef uint64_t (*ASCStats_getFrameNumber)(ASurfaceControlStats* stats);
|
||||
|
||||
struct ASurfaceControlFunctions {
|
||||
ASurfaceControlFunctions();
|
||||
|
||||
ASC_acquire acquireFunc;
|
||||
ASC_release releaseFunc;
|
||||
ASC_registerSurfaceStatsListener registerListenerFunc;
|
||||
ASC_unregisterSurfaceStatsListener unregisterListenerFunc;
|
||||
ASCStats_getAcquireTime getAcquireTimeFunc;
|
||||
ASCStats_getFrameNumber getFrameNumberFunc;
|
||||
};
|
||||
|
||||
class ChoreographerSource;
|
||||
@@ -114,6 +126,7 @@ public:
|
||||
RenderState& renderState() const { return *mRenderState; }
|
||||
EglManager& eglManager() const { return *mEglManager; }
|
||||
ProfileDataContainer& globalProfileData() { return mGlobalProfileData; }
|
||||
std::mutex& getJankDataMutex() { return mJankDataMutex; }
|
||||
Readback& readback();
|
||||
|
||||
GrDirectContext* getGrContext() const { return mGrContext.get(); }
|
||||
@@ -205,6 +218,7 @@ private:
|
||||
sp<VulkanManager> mVkManager;
|
||||
|
||||
ASurfaceControlFunctions mASurfaceControlFunctions;
|
||||
std::mutex mJankDataMutex;
|
||||
};
|
||||
|
||||
} /* namespace renderthread */
|
||||
|
||||
@@ -88,7 +88,7 @@ cc_library_shared {
|
||||
"libarect",
|
||||
],
|
||||
|
||||
header_libs: [ "libhwui_internal_headers",],
|
||||
header_libs: [ "libhwui_internal_headers", "libandroid_headers_private"],
|
||||
|
||||
whole_static_libs: ["libnativewindow"],
|
||||
|
||||
|
||||
@@ -300,3 +300,13 @@ LIBANDROID {
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
||||
LIBANDROID_PLATFORM {
|
||||
global:
|
||||
extern "C++" {
|
||||
ASurfaceControl_registerSurfaceStatsListener*;
|
||||
ASurfaceControl_unregisterSurfaceStatsListener*;
|
||||
ASurfaceControlStats_getAcquireTime*;
|
||||
ASurfaceControlStats_getFrameNumber*;
|
||||
};
|
||||
} LIBANDROID;
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/surface_control.h>
|
||||
#include <surface_control_private.h>
|
||||
|
||||
#include <configstore/Utils.h>
|
||||
|
||||
@@ -197,6 +198,48 @@ void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
|
||||
SurfaceControl_release(surfaceControl);
|
||||
}
|
||||
|
||||
struct ASurfaceControlStats {
|
||||
int64_t acquireTime;
|
||||
sp<Fence> previousReleaseFence;
|
||||
uint64_t frameNumber;
|
||||
};
|
||||
|
||||
void ASurfaceControl_registerSurfaceStatsListener(ASurfaceControl* control, void* context,
|
||||
ASurfaceControl_SurfaceStatsListener func) {
|
||||
SurfaceStatsCallback callback = [func](void* callback_context,
|
||||
nsecs_t,
|
||||
const sp<Fence>&,
|
||||
const SurfaceStats& surfaceStats) {
|
||||
|
||||
ASurfaceControlStats aSurfaceControlStats;
|
||||
|
||||
ASurfaceControl* aSurfaceControl =
|
||||
reinterpret_cast<ASurfaceControl*>(surfaceStats.surfaceControl.get());
|
||||
aSurfaceControlStats.acquireTime = surfaceStats.acquireTime;
|
||||
aSurfaceControlStats.previousReleaseFence = surfaceStats.previousReleaseFence;
|
||||
aSurfaceControlStats.frameNumber = surfaceStats.eventStats.frameNumber;
|
||||
|
||||
(*func)(callback_context, aSurfaceControl, &aSurfaceControlStats);
|
||||
};
|
||||
TransactionCompletedListener::getInstance()->addSurfaceStatsListener(context,
|
||||
reinterpret_cast<void*>(func), ASurfaceControl_to_SurfaceControl(control), callback);
|
||||
}
|
||||
|
||||
|
||||
void ASurfaceControl_unregisterSurfaceStatsListener(void* context,
|
||||
ASurfaceControl_SurfaceStatsListener func) {
|
||||
TransactionCompletedListener::getInstance()->removeSurfaceStatsListener(context,
|
||||
reinterpret_cast<void*>(func));
|
||||
}
|
||||
|
||||
int64_t ASurfaceControlStats_getAcquireTime(ASurfaceControlStats* stats) {
|
||||
return stats->acquireTime;
|
||||
}
|
||||
|
||||
uint64_t ASurfaceControlStats_getFrameNumber(ASurfaceControlStats* stats) {
|
||||
return stats->frameNumber;
|
||||
}
|
||||
|
||||
ASurfaceTransaction* ASurfaceTransaction_create() {
|
||||
Transaction* transaction = new Transaction;
|
||||
return reinterpret_cast<ASurfaceTransaction*>(transaction);
|
||||
@@ -215,11 +258,6 @@ void ASurfaceTransaction_apply(ASurfaceTransaction* aSurfaceTransaction) {
|
||||
transaction->apply();
|
||||
}
|
||||
|
||||
typedef struct ASurfaceControlStats {
|
||||
int64_t acquireTime;
|
||||
sp<Fence> previousReleaseFence;
|
||||
} ASurfaceControlStats;
|
||||
|
||||
struct ASurfaceTransactionStats {
|
||||
std::unordered_map<ASurfaceControl*, ASurfaceControlStats> aSurfaceControlStats;
|
||||
int64_t latchTime;
|
||||
|
||||
Reference in New Issue
Block a user