Merge "atrace: add priority inheritance trace point" into pi-dev
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index c9e43fd..a9c5c82 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -1,6 +1,6 @@
## Permissions to allow system-wide tracing to the kernel trace buffer.
##
-on post-fs
+on late-init
# Allow writing to the kernel trace log.
chmod 0222 /sys/kernel/debug/tracing/trace_marker
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 5b1e631..6a69844 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -45,6 +45,7 @@
"libpdx_default_transport",
"libprotobuf-cpp-lite",
"libsync",
+ "libtimestats_proto",
"libui",
"libutils",
"libvulkan",
@@ -124,6 +125,7 @@
"SurfaceFlinger.cpp",
"SurfaceInterceptor.cpp",
"SurfaceTracing.cpp",
+ "TimeStats/TimeStats.cpp",
"Transform.cpp",
],
}
@@ -172,6 +174,7 @@
"liblayers_proto",
"liblog",
"libsurfaceflinger",
+ "libtimestats_proto",
"libutils",
],
static_libs: [
@@ -213,5 +216,6 @@
subdirs = [
"layerproto",
+ "TimeStats/timestatsproto",
"tests",
]
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 7fd9d01..4c3844e 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -316,6 +316,9 @@
nsecs_t desiredPresentTime = mConsumer->getTimestamp();
mFrameTracker.setDesiredPresentTime(desiredPresentTime);
+ const std::string layerName(getName().c_str());
+ mTimeStats.setDesiredTime(layerName, mCurrentFrameNumber, desiredPresentTime);
+
std::shared_ptr<FenceTime> frameReadyFence = mConsumer->getCurrentFenceTime();
if (frameReadyFence->isValid()) {
mFrameTracker.setFrameReadyFence(std::move(frameReadyFence));
@@ -326,12 +329,15 @@
}
if (presentFence->isValid()) {
+ mTimeStats.setPresentFence(layerName, mCurrentFrameNumber, presentFence);
mFrameTracker.setActualPresentFence(std::shared_ptr<FenceTime>(presentFence));
} else {
// The HWC doesn't support present fences, so use the refresh
// timestamp instead.
- mFrameTracker.setActualPresentTime(
- mFlinger->getHwComposer().getRefreshTimestamp(HWC_DISPLAY_PRIMARY));
+ const nsecs_t actualPresentTime =
+ mFlinger->getHwComposer().getRefreshTimestamp(HWC_DISPLAY_PRIMARY);
+ mTimeStats.setPresentTime(layerName, mCurrentFrameNumber, actualPresentTime);
+ mFrameTracker.setActualPresentTime(actualPresentTime);
}
mFrameTracker.advanceFrame();
@@ -441,6 +447,7 @@
// and return early
if (queuedBuffer) {
Mutex::Autolock lock(mQueueItemLock);
+ mTimeStats.removeTimeRecord(getName().c_str(), mQueueItems[0].mFrameNumber);
mQueueItems.removeAt(0);
android_atomic_dec(&mQueuedFrames);
}
@@ -454,6 +461,7 @@
Mutex::Autolock lock(mQueueItemLock);
mQueueItems.clear();
android_atomic_and(0, &mQueuedFrames);
+ mTimeStats.clearLayerRecord(getName().c_str());
}
// Once we have hit this state, the shadow queue may no longer
@@ -474,10 +482,15 @@
// Remove any stale buffers that have been dropped during
// updateTexImage
while (mQueueItems[0].mFrameNumber != currentFrameNumber) {
+ mTimeStats.removeTimeRecord(getName().c_str(), mQueueItems[0].mFrameNumber);
mQueueItems.removeAt(0);
android_atomic_dec(&mQueuedFrames);
}
+ const std::string layerName(getName().c_str());
+ mTimeStats.setAcquireFence(layerName, currentFrameNumber, mQueueItems[0].mFenceTime);
+ mTimeStats.setLatchTime(layerName, currentFrameNumber, latchTime);
+
mQueueItems.removeAt(0);
}
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index bbc974d..2802fc7 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1532,10 +1532,16 @@
void Layer::onDisconnect() {
Mutex::Autolock lock(mFrameEventHistoryMutex);
mFrameEventHistory.onDisconnect();
+ mTimeStats.onDisconnect(getName().c_str());
}
void Layer::addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
FrameEventHistoryDelta* outDelta) {
+ if (newTimestamps) {
+ mTimeStats.setPostTime(getName().c_str(), newTimestamps->frameNumber,
+ newTimestamps->postedTime);
+ }
+
Mutex::Autolock lock(mFrameEventHistoryMutex);
if (newTimestamps) {
// If there are any unsignaled fences in the aquire timeline at this
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index be3967b..0b15b67 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -41,6 +41,7 @@
#include "LayerVector.h"
#include "MonitoredProducer.h"
#include "SurfaceFlinger.h"
+#include "TimeStats/TimeStats.h"
#include "Transform.h"
#include <layerproto/LayerProtoHeader.h>
@@ -737,6 +738,8 @@
FenceTimeline mAcquireTimeline;
FenceTimeline mReleaseTimeline;
+ TimeStats& mTimeStats = TimeStats::getInstance();
+
// main thread
int mActiveBufferSlot;
sp<GraphicBuffer> mActiveBuffer;
diff --git a/services/surfaceflinger/RenderEngine/Description.cpp b/services/surfaceflinger/RenderEngine/Description.cpp
index 0ccdbc4..c218e4d 100644
--- a/services/surfaceflinger/RenderEngine/Description.cpp
+++ b/services/surfaceflinger/RenderEngine/Description.cpp
@@ -52,9 +52,30 @@
}
void Description::setColorMatrix(const mat4& mtx) {
- const mat4 identity;
mColorMatrix = mtx;
- mColorMatrixEnabled = (mtx != identity);
+}
+
+void Description::setInputTransformMatrix(const mat3& matrix) {
+ mInputTransformMatrix = matrix;
+}
+
+void Description::setOutputTransformMatrix(const mat4& matrix) {
+ mOutputTransformMatrix = matrix;
+}
+
+bool Description::hasInputTransformMatrix() const {
+ const mat3 identity;
+ return mInputTransformMatrix != identity;
+}
+
+bool Description::hasOutputTransformMatrix() const {
+ const mat4 identity;
+ return mOutputTransformMatrix != identity;
+}
+
+bool Description::hasColorMatrix() const {
+ const mat4 identity;
+ return mColorMatrix != identity;
}
const mat4& Description::getColorMatrix() const {
diff --git a/services/surfaceflinger/RenderEngine/Description.h b/services/surfaceflinger/RenderEngine/Description.h
index b09e3a8..6ebb340 100644
--- a/services/surfaceflinger/RenderEngine/Description.h
+++ b/services/surfaceflinger/RenderEngine/Description.h
@@ -43,6 +43,11 @@
void setColor(const half4& color);
void setProjectionMatrix(const mat4& mtx);
void setColorMatrix(const mat4& mtx);
+ void setInputTransformMatrix(const mat3& matrix);
+ void setOutputTransformMatrix(const mat4& matrix);
+ bool hasInputTransformMatrix() const;
+ bool hasOutputTransformMatrix() const;
+ bool hasColorMatrix() const;
const mat4& getColorMatrix() const;
void setY410BT2020(bool enable);
@@ -72,11 +77,6 @@
// color used when texturing is disabled or when setting alpha.
half4 mColor;
- // projection matrix
- mat4 mProjectionMatrix;
-
- bool mColorMatrixEnabled = false;
- mat4 mColorMatrix;
// true if the sampled pixel values are in Y410/BT2020 rather than RGBA
bool mY410BT2020 = false;
@@ -86,6 +86,12 @@
TransferFunction mOutputTransferFunction = TransferFunction::LINEAR;
float mDisplayMaxLuminance;
+
+ // projection matrix
+ mat4 mProjectionMatrix;
+ mat4 mColorMatrix;
+ mat3 mInputTransformMatrix;
+ mat4 mOutputTransformMatrix;
};
} /* namespace android */
diff --git a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
index 8e3c837..08cd5b0 100644
--- a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
+++ b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
@@ -131,15 +131,24 @@
// mColorBlindnessCorrection = M;
if (mPlatformHasWideColor) {
- // Compute sRGB to DisplayP3 color transform
- // NOTE: For now, we are limiting wide-color support to
- // Display-P3 only.
- mSrgbToDisplayP3 = mat4(
- ColorSpaceConnector(ColorSpace::sRGB(), ColorSpace::DisplayP3()).getTransform());
+ ColorSpace srgb(ColorSpace::sRGB());
+ ColorSpace displayP3(ColorSpace::DisplayP3());
+ ColorSpace bt2020(ColorSpace::BT2020());
- // Compute BT2020 to DisplayP3 color transform
- mBt2020ToDisplayP3 = mat4(
- ColorSpaceConnector(ColorSpace::BT2020(), ColorSpace::DisplayP3()).getTransform());
+ // Compute sRGB to Display P3 transform matrix.
+ // NOTE: For now, we are limiting output wide color space support to
+ // Display-P3 only.
+ mSrgbToDisplayP3 = mat4(ColorSpaceConnector(srgb, displayP3).getTransform());
+
+ // Compute Display P3 to sRGB transform matrix.
+ mDisplayP3ToSrgb = mat4(ColorSpaceConnector(displayP3, srgb).getTransform());
+
+ // no chromatic adaptation needed since all color spaces use D65 for their white points.
+ mSrgbToXyz = srgb.getRGBtoXYZ();
+ mDisplayP3ToXyz = displayP3.getRGBtoXYZ();
+ mBt2020ToXyz = bt2020.getRGBtoXYZ();
+ mXyzToDisplayP3 = mat4(displayP3.getXYZtoRGB());
+ mXyzToBt2020 = mat4(bt2020.getXYZtoRGB());
}
}
@@ -307,44 +316,96 @@
glVertexAttribPointer(Program::position, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
mesh.getByteStride(), mesh.getPositions());
- // TODO(b/73825729) Refactor this code block to handle BT2020 color space properly.
- // DISPLAY_P3 is the only supported wide color output
- if (mPlatformHasWideColor && mOutputDataSpace == Dataspace::DISPLAY_P3) {
+ // By default, DISPLAY_P3 is the only supported wide color output. However,
+ // when HDR content is present, hardware composer may be able to handle
+ // BT2020 data space, in that case, the output data space is set to be
+ // BT2020_HLG or BT2020_PQ respectively. In GPU fall back we need
+ // to respect this and convert non-HDR content to HDR format.
+ if (mPlatformHasWideColor) {
Description wideColorState = mState;
- switch (mDataSpace) {
- case Dataspace::DISPLAY_P3:
- // input matches output
- break;
- case Dataspace::BT2020_PQ:
- case Dataspace::BT2020_ITU_PQ:
- wideColorState.setColorMatrix(mState.getColorMatrix() * mBt2020ToDisplayP3);
- wideColorState.setInputTransferFunction(Description::TransferFunction::ST2084);
- wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB);
- break;
- case Dataspace::BT2020_HLG:
- case Dataspace::BT2020_ITU_HLG:
- wideColorState.setColorMatrix(mState.getColorMatrix() * mBt2020ToDisplayP3);
- wideColorState.setInputTransferFunction(Description::TransferFunction::HLG);
- wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB);
- break;
- default:
- // treat all other dataspaces as sRGB
- wideColorState.setColorMatrix(mState.getColorMatrix() * mSrgbToDisplayP3);
- switch (static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK)) {
- case Dataspace::TRANSFER_LINEAR:
- wideColorState.setInputTransferFunction(
- Description::TransferFunction::LINEAR);
- break;
- default:
- // treat all other transfer functions as sRGB
- wideColorState.setInputTransferFunction(
- Description::TransferFunction::SRGB);
- break;
- }
- wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB);
- ALOGV("drawMesh: gamut transform applied");
- break;
+ Dataspace inputStandard = static_cast<Dataspace>(mDataSpace & Dataspace::STANDARD_MASK);
+ Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
+ Dataspace outputStandard = static_cast<Dataspace>(mOutputDataSpace &
+ Dataspace::STANDARD_MASK);
+ Dataspace outputTransfer = static_cast<Dataspace>(mOutputDataSpace &
+ Dataspace::TRANSFER_MASK);
+ bool needsXYZConversion = needsXYZTransformMatrix();
+
+ if (needsXYZConversion) {
+ // The supported input color spaces are standard RGB, Display P3 and BT2020.
+ switch (inputStandard) {
+ case Dataspace::STANDARD_DCI_P3:
+ wideColorState.setInputTransformMatrix(mDisplayP3ToXyz);
+ break;
+ case Dataspace::STANDARD_BT2020:
+ wideColorState.setInputTransformMatrix(mBt2020ToXyz);
+ break;
+ default:
+ wideColorState.setInputTransformMatrix(mSrgbToXyz);
+ break;
+ }
+
+ // The supported output color spaces are Display P3 and BT2020.
+ switch (outputStandard) {
+ case Dataspace::STANDARD_BT2020:
+ wideColorState.setOutputTransformMatrix(mXyzToBt2020);
+ break;
+ default:
+ wideColorState.setOutputTransformMatrix(mXyzToDisplayP3);
+ break;
+ }
+ } else if (inputStandard != outputStandard) {
+ // At this point, the input data space and output data space could be both
+ // HDR data spaces, but they match each other, we do nothing in this case.
+ // In addition to the case above, the input data space could be
+ // - scRGB linear
+ // - scRGB non-linear
+ // - sRGB
+ // - Display P3
+ // The output data spaces could be
+ // - sRGB
+ // - Display P3
+ if (outputStandard == Dataspace::STANDARD_BT709) {
+ wideColorState.setOutputTransformMatrix(mDisplayP3ToSrgb);
+ } else if (outputStandard == Dataspace::STANDARD_DCI_P3) {
+ wideColorState.setOutputTransformMatrix(mSrgbToDisplayP3);
+ }
}
+
+ // we need to convert the RGB value to linear space and convert it back when:
+ // - there is a color matrix that is not an identity matrix, or
+ // - there is an output transform matrix that is not an identity matrix, or
+ // - the input transfer function doesn't match the output transfer function.
+ if (wideColorState.hasColorMatrix() || wideColorState.hasOutputTransformMatrix() ||
+ inputTransfer != outputTransfer) {
+ switch (inputTransfer) {
+ case Dataspace::TRANSFER_ST2084:
+ wideColorState.setInputTransferFunction(Description::TransferFunction::ST2084);
+ break;
+ case Dataspace::TRANSFER_HLG:
+ wideColorState.setInputTransferFunction(Description::TransferFunction::HLG);
+ break;
+ case Dataspace::TRANSFER_LINEAR:
+ wideColorState.setInputTransferFunction(Description::TransferFunction::LINEAR);
+ break;
+ default:
+ wideColorState.setInputTransferFunction(Description::TransferFunction::SRGB);
+ break;
+ }
+
+ switch (outputTransfer) {
+ case Dataspace::TRANSFER_ST2084:
+ wideColorState.setOutputTransferFunction(Description::TransferFunction::ST2084);
+ break;
+ case Dataspace::TRANSFER_HLG:
+ wideColorState.setOutputTransferFunction(Description::TransferFunction::HLG);
+ break;
+ default:
+ wideColorState.setOutputTransferFunction(Description::TransferFunction::SRGB);
+ break;
+ }
+ }
+
ProgramCache::getInstance().useProgram(wideColorState);
glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
@@ -373,6 +434,33 @@
dataspaceDetails(static_cast<android_dataspace>(mOutputDataSpace)).c_str());
}
+bool GLES20RenderEngine::isHdrDataSpace(const Dataspace dataSpace) const {
+ const Dataspace standard = static_cast<Dataspace>(dataSpace & Dataspace::STANDARD_MASK);
+ const Dataspace transfer = static_cast<Dataspace>(dataSpace & Dataspace::TRANSFER_MASK);
+ return standard == Dataspace::STANDARD_BT2020 &&
+ (transfer == Dataspace::TRANSFER_ST2084 || transfer == Dataspace::TRANSFER_HLG);
+}
+
+// For convenience, we want to convert the input color space to XYZ color space first,
+// and then convert from XYZ color space to output color space when
+// - SDR and HDR contents are mixed, either SDR content will be converted to HDR or
+// HDR content will be tone-mapped to SDR; Or,
+// - there are HDR PQ and HLG contents presented at the same time, where we want to convert
+// HLG content to PQ content.
+// In either case above, we need to operate the Y value in XYZ color space. Thus, when either
+// input data space or output data space is HDR data space, and the input transfer function
+// doesn't match the output transfer function, we would enable an intermediate transfrom to
+// XYZ color space.
+bool GLES20RenderEngine::needsXYZTransformMatrix() const {
+ const bool isInputHdrDataSpace = isHdrDataSpace(mDataSpace);
+ const bool isOutputHdrDataSpace = isHdrDataSpace(mOutputDataSpace);
+ const Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
+ const Dataspace outputTransfer = static_cast<Dataspace>(mOutputDataSpace &
+ Dataspace::TRANSFER_MASK);
+
+ return (isInputHdrDataSpace || isOutputHdrDataSpace) && inputTransfer != outputTransfer;
+}
+
// ---------------------------------------------------------------------------
} // namespace impl
} // namespace RE
diff --git a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.h b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.h
index 7177aad..9acd79b 100644
--- a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.h
+++ b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.h
@@ -98,7 +98,18 @@
// Currently only supporting sRGB, BT2020 and DisplayP3 color spaces
const bool mPlatformHasWideColor = false;
mat4 mSrgbToDisplayP3;
- mat4 mBt2020ToDisplayP3;
+ mat4 mDisplayP3ToSrgb;
+ mat3 mSrgbToXyz;
+ mat3 mBt2020ToXyz;
+ mat3 mDisplayP3ToXyz;
+ mat4 mXyzToDisplayP3;
+ mat4 mXyzToBt2020;
+
+private:
+ // A data space is considered HDR data space if it has BT2020 color space
+ // with PQ or HLG transfer function.
+ bool isHdrDataSpace(const ui::Dataspace dataSpace) const;
+ bool needsXYZTransformMatrix() const;
};
// ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/RenderEngine/Program.cpp b/services/surfaceflinger/RenderEngine/Program.cpp
index e5261c2..fd2c968 100644
--- a/services/surfaceflinger/RenderEngine/Program.cpp
+++ b/services/surfaceflinger/RenderEngine/Program.cpp
@@ -58,13 +58,13 @@
mVertexShader = vertexId;
mFragmentShader = fragmentId;
mInitialized = true;
-
- mColorMatrixLoc = glGetUniformLocation(programId, "colorMatrix");
mProjectionMatrixLoc = glGetUniformLocation(programId, "projection");
mTextureMatrixLoc = glGetUniformLocation(programId, "texture");
mSamplerLoc = glGetUniformLocation(programId, "sampler");
mColorLoc = glGetUniformLocation(programId, "color");
mDisplayMaxLuminanceLoc = glGetUniformLocation(programId, "displayMaxLuminance");
+ mInputTransformMatrixLoc = glGetUniformLocation(programId, "inputTransformMatrix");
+ mOutputTransformMatrixLoc = glGetUniformLocation(programId, "outputTransformMatrix");
// set-up the default values for our uniforms
glUseProgram(programId);
@@ -134,8 +134,16 @@
const float color[4] = {desc.mColor.r, desc.mColor.g, desc.mColor.b, desc.mColor.a};
glUniform4fv(mColorLoc, 1, color);
}
- if (mColorMatrixLoc >= 0) {
- glUniformMatrix4fv(mColorMatrixLoc, 1, GL_FALSE, desc.mColorMatrix.asArray());
+ if (mInputTransformMatrixLoc >= 0) {
+ glUniformMatrix3fv(mInputTransformMatrixLoc, 1, GL_FALSE,
+ desc.mInputTransformMatrix.asArray());
+ }
+ if (mOutputTransformMatrixLoc >= 0) {
+ // The output transform matrix and color matrix can be combined as one matrix
+ // that is applied right before applying OETF.
+ mat4 outputTransformMatrix = desc.mColorMatrix * desc.mOutputTransformMatrix;
+ glUniformMatrix4fv(mOutputTransformMatrixLoc, 1, GL_FALSE,
+ outputTransformMatrix.asArray());
}
if (mDisplayMaxLuminanceLoc >= 0) {
glUniform1f(mDisplayMaxLuminanceLoc, desc.mDisplayMaxLuminance);
diff --git a/services/surfaceflinger/RenderEngine/Program.h b/services/surfaceflinger/RenderEngine/Program.h
index 55b9cdd..ae796c5 100644
--- a/services/surfaceflinger/RenderEngine/Program.h
+++ b/services/surfaceflinger/RenderEngine/Program.h
@@ -69,9 +69,6 @@
/* location of the projection matrix uniform */
GLint mProjectionMatrixLoc;
- /* location of the color matrix uniform */
- GLint mColorMatrixLoc;
-
/* location of the texture matrix uniform */
GLint mTextureMatrixLoc;
@@ -83,6 +80,10 @@
/* location of display luminance uniform */
GLint mDisplayMaxLuminanceLoc;
+
+ /* location of transform matrix */
+ GLint mInputTransformMatrixLoc;
+ GLint mOutputTransformMatrixLoc;
};
} /* namespace android */
diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.cpp b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
index 5d5462f..89ee64b 100644
--- a/services/surfaceflinger/RenderEngine/ProgramCache.cpp
+++ b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
@@ -125,13 +125,17 @@
description.mPremultipliedAlpha ? Key::BLEND_PREMULT : Key::BLEND_NORMAL)
.set(Key::OPACITY_MASK,
description.mOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
- .set(Key::COLOR_MATRIX_MASK,
- description.mColorMatrixEnabled ? Key::COLOR_MATRIX_ON : Key::COLOR_MATRIX_OFF);
+ .set(Key::Key::INPUT_TRANSFORM_MATRIX_MASK,
+ description.hasInputTransformMatrix() ?
+ Key::INPUT_TRANSFORM_MATRIX_ON : Key::INPUT_TRANSFORM_MATRIX_OFF)
+ .set(Key::Key::OUTPUT_TRANSFORM_MATRIX_MASK,
+ description.hasOutputTransformMatrix() || description.hasColorMatrix() ?
+ Key::OUTPUT_TRANSFORM_MATRIX_ON : Key::OUTPUT_TRANSFORM_MATRIX_OFF);
needs.set(Key::Y410_BT2020_MASK,
description.mY410BT2020 ? Key::Y410_BT2020_ON : Key::Y410_BT2020_OFF);
- if (needs.hasColorMatrix()) {
+ if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF())) {
switch (description.mInputTransferFunction) {
case Description::TransferFunction::LINEAR:
default:
@@ -441,13 +445,44 @@
)__SHADER__";
}
- if (needs.hasColorMatrix()) {
- fs << "uniform mat4 colorMatrix;";
+ if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF())) {
// Currently, only the OOTF of BT2020 PQ needs display maximum luminance.
if (needs.getInputTF() == Key::INPUT_TF_ST2084) {
fs << "uniform float displayMaxLuminance";
}
+ if (needs.hasInputTransformMatrix()) {
+ fs << "uniform mat3 inputTransformMatrix;";
+ fs << R"__SHADER__(
+ highp vec3 InputTransform(const highp vec3 color) {
+ return inputTransformMatrix * color;
+ }
+ )__SHADER__";
+ } else {
+ fs << R"__SHADER__(
+ highp vec3 InputTransform(const highp vec3 color) {
+ return color;
+ }
+ )__SHADER__";
+ }
+
+ // the transformation from a wider colorspace to a narrower one can
+ // result in >1.0 or <0.0 pixel values
+ if (needs.hasOutputTransformMatrix()) {
+ fs << "uniform mat4 outputTransformMatrix;";
+ fs << R"__SHADER__(
+ highp vec3 OutputTransform(const highp vec3 color) {
+ return clamp(vec3(outputTransformMatrix * vec4(color, 1.0)), 0.0, 1.0);
+ }
+ )__SHADER__";
+ } else {
+ fs << R"__SHADER__(
+ highp vec3 OutputTransform(const highp vec3 color) {
+ return clamp(color, 0.0, 1.0);
+ }
+ )__SHADER__";
+ }
+
generateEOTF(fs, needs);
generateOOTF(fs, needs);
generateOETF(fs, needs);
@@ -476,18 +511,13 @@
}
}
- if (needs.hasColorMatrix()) {
+ if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF())) {
if (!needs.isOpaque() && needs.isPremultiplied()) {
// un-premultiply if needed before linearization
// avoid divide by 0 by adding 0.5/256 to the alpha channel
fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);";
}
- fs << "vec4 transformed = colorMatrix * vec4(OOTF(EOTF(gl_FragColor.rgb)), 1);";
- // the transformation from a wider colorspace to a narrower one can
- // result in >1.0 or <0.0 pixel values
- fs << "transformed.rgb = clamp(transformed.rgb, 0.0, 1.0);";
- // We assume the last row is always {0,0,0,1} and we skip the division by w
- fs << "gl_FragColor.rgb = OETF(transformed.rgb);";
+ fs << "gl_FragColor.rgb = OETF(OutputTransform(OOTF(InputTransform(EOTF(gl_FragColor.rgb)))));";
if (!needs.isOpaque() && needs.isPremultiplied()) {
// and re-premultiply if needed after gamma correction
fs << "gl_FragColor.rgb = gl_FragColor.rgb * (gl_FragColor.a + 0.0019);";
diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.h b/services/surfaceflinger/RenderEngine/ProgramCache.h
index d18163a..e1398eb 100644
--- a/services/surfaceflinger/RenderEngine/ProgramCache.h
+++ b/services/surfaceflinger/RenderEngine/ProgramCache.h
@@ -72,26 +72,31 @@
TEXTURE_EXT = 1 << TEXTURE_SHIFT,
TEXTURE_2D = 2 << TEXTURE_SHIFT,
- COLOR_MATRIX_SHIFT = 5,
- COLOR_MATRIX_MASK = 1 << COLOR_MATRIX_SHIFT,
- COLOR_MATRIX_OFF = 0 << COLOR_MATRIX_SHIFT,
- COLOR_MATRIX_ON = 1 << COLOR_MATRIX_SHIFT,
+ INPUT_TRANSFORM_MATRIX_SHIFT = 5,
+ INPUT_TRANSFORM_MATRIX_MASK = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
+ INPUT_TRANSFORM_MATRIX_OFF = 0 << INPUT_TRANSFORM_MATRIX_SHIFT,
+ INPUT_TRANSFORM_MATRIX_ON = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
- INPUT_TF_SHIFT = 6,
+ OUTPUT_TRANSFORM_MATRIX_SHIFT = 6,
+ OUTPUT_TRANSFORM_MATRIX_MASK = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
+ OUTPUT_TRANSFORM_MATRIX_OFF = 0 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
+ OUTPUT_TRANSFORM_MATRIX_ON = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
+
+ INPUT_TF_SHIFT = 7,
INPUT_TF_MASK = 3 << INPUT_TF_SHIFT,
INPUT_TF_LINEAR = 0 << INPUT_TF_SHIFT,
INPUT_TF_SRGB = 1 << INPUT_TF_SHIFT,
INPUT_TF_ST2084 = 2 << INPUT_TF_SHIFT,
INPUT_TF_HLG = 3 << INPUT_TF_SHIFT,
- OUTPUT_TF_SHIFT = 8,
+ OUTPUT_TF_SHIFT = 9,
OUTPUT_TF_MASK = 3 << OUTPUT_TF_SHIFT,
OUTPUT_TF_LINEAR = 0 << OUTPUT_TF_SHIFT,
OUTPUT_TF_SRGB = 1 << OUTPUT_TF_SHIFT,
OUTPUT_TF_ST2084 = 2 << OUTPUT_TF_SHIFT,
OUTPUT_TF_HLG = 3 << OUTPUT_TF_SHIFT,
- Y410_BT2020_SHIFT = 10,
+ Y410_BT2020_SHIFT = 11,
Y410_BT2020_MASK = 1 << Y410_BT2020_SHIFT,
Y410_BT2020_OFF = 0 << Y410_BT2020_SHIFT,
Y410_BT2020_ON = 1 << Y410_BT2020_SHIFT,
@@ -110,7 +115,15 @@
inline bool isPremultiplied() const { return (mKey & BLEND_MASK) == BLEND_PREMULT; }
inline bool isOpaque() const { return (mKey & OPACITY_MASK) == OPACITY_OPAQUE; }
inline bool hasAlpha() const { return (mKey & ALPHA_MASK) == ALPHA_LT_ONE; }
- inline bool hasColorMatrix() const { return (mKey & COLOR_MATRIX_MASK) == COLOR_MATRIX_ON; }
+ inline bool hasInputTransformMatrix() const {
+ return (mKey & INPUT_TRANSFORM_MATRIX_MASK) == INPUT_TRANSFORM_MATRIX_ON;
+ }
+ inline bool hasOutputTransformMatrix() const {
+ return (mKey & OUTPUT_TRANSFORM_MATRIX_MASK) == OUTPUT_TRANSFORM_MATRIX_ON;
+ }
+ inline bool hasTransformMatrix() const {
+ return hasInputTransformMatrix() || hasOutputTransformMatrix();
+ }
inline int getInputTF() const { return (mKey & INPUT_TF_MASK); }
inline int getOutputTF() const { return (mKey & OUTPUT_TF_MASK); }
inline bool isY410BT2020() const { return (mKey & Y410_BT2020_MASK) == Y410_BT2020_ON; }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f736c8c..cdf7cca 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -244,7 +244,6 @@
mPrimaryDispSync("PrimaryDispSync"),
mPrimaryHWVsyncEnabled(false),
mHWVsyncAvailable(false),
- mHasColorMatrix(false),
mHasPoweredOff(false),
mNumLayers(0),
mVrFlingerRequestsDisplay(false),
@@ -723,10 +722,13 @@
}
void SurfaceFlinger::readPersistentProperties() {
+ Mutex::Autolock _l(mStateLock);
+
char value[PROPERTY_VALUE_MAX];
property_get("persist.sys.sf.color_saturation", value, "1.0");
mGlobalSaturationFactor = atof(value);
+ updateColorMatrixLocked();
ALOGV("Saturation is set to %.2f", mGlobalSaturationFactor);
property_get("persist.sys.sf.native_mode", value, "0");
@@ -1470,9 +1472,13 @@
Fence::SIGNAL_TIME_PENDING);
ATRACE_INT("FrameMissed", static_cast<int>(frameMissed));
if (mPropagateBackpressure && frameMissed) {
+ mTimeStats.incrementMissedFrames(true);
signalLayerUpdate();
break;
}
+ if (frameMissed) {
+ mTimeStats.incrementMissedFrames(false);
+ }
// Now that we're going to make it to the handleMessageTransaction()
// call below it's safe to call updateVrFlinger(), which will
@@ -1771,6 +1777,11 @@
mAnimFrameTracker.advanceFrame();
}
+ mTimeStats.incrementTotalFrames();
+ if (mHadClientComposition) {
+ mTimeStats.incrementClientCompositionFrames();
+ }
+
if (getBE().mHwc->isConnected(HWC_DISPLAY_PRIMARY) &&
hw->getPowerMode() == HWC_POWER_MODE_OFF) {
return;
@@ -1859,22 +1870,6 @@
}
}
-mat4 SurfaceFlinger::computeSaturationMatrix() const {
- if (mGlobalSaturationFactor == 1.0f) {
- return mat4();
- }
-
- // Rec.709 luma coefficients
- float3 luminance{0.213f, 0.715f, 0.072f};
- luminance *= 1.0f - mGlobalSaturationFactor;
- return mat4(
- vec4{luminance.r + mGlobalSaturationFactor, luminance.r, luminance.r, 0.0f},
- vec4{luminance.g, luminance.g + mGlobalSaturationFactor, luminance.g, 0.0f},
- vec4{luminance.b, luminance.b, luminance.b + mGlobalSaturationFactor, 0.0f},
- vec4{0.0f, 0.0f, 0.0f, 1.0f}
- );
-}
-
// Returns a dataspace that fits all visible layers. The returned dataspace
// can only be one of
//
@@ -2000,9 +1995,6 @@
}
}
-
- mat4 colorMatrix = mColorMatrix * computeSaturationMatrix() * mDaltonizer();
-
// Set the per-frame data
for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) {
auto& displayDevice = mDisplays[displayId];
@@ -2011,9 +2003,9 @@
if (hwcId < 0) {
continue;
}
- if (colorMatrix != mPreviousColorMatrix) {
- displayDevice->setColorTransform(colorMatrix);
- status_t result = getBE().mHwc->setColorTransform(hwcId, colorMatrix);
+ if (mDrawingState.colorMatrixChanged) {
+ displayDevice->setColorTransform(mDrawingState.colorMatrix);
+ status_t result = getBE().mHwc->setColorTransform(hwcId, mDrawingState.colorMatrix);
ALOGE_IF(result != NO_ERROR, "Failed to set color transform on "
"display %zd: %d", displayId, result);
}
@@ -2046,7 +2038,7 @@
}
}
- mPreviousColorMatrix = colorMatrix;
+ mDrawingState.colorMatrixChanged = false;
for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) {
auto& displayDevice = mDisplays[displayId];
@@ -2635,6 +2627,9 @@
mAnimCompositionPending = mAnimTransactionPending;
mDrawingState = mCurrentState;
+ // clear the "changed" flags in current state
+ mCurrentState.colorMatrixChanged = false;
+
mDrawingState.traverseInZOrder([](Layer* layer) {
layer->commitChildList();
});
@@ -2887,9 +2882,8 @@
mat4 legacySrgbSaturationMatrix = mLegacySrgbSaturationMatrix;
const bool applyColorMatrix = !hasDeviceComposition && !skipClientColorTransform;
if (applyColorMatrix) {
- mat4 colorMatrix = mColorMatrix * computeSaturationMatrix() * mDaltonizer();
- oldColorMatrix = getRenderEngine().setupColorTransform(colorMatrix);
- legacySrgbSaturationMatrix = colorMatrix * legacySrgbSaturationMatrix;
+ oldColorMatrix = getRenderEngine().setupColorTransform(mDrawingState.colorMatrix);
+ legacySrgbSaturationMatrix = mDrawingState.colorMatrix * legacySrgbSaturationMatrix;
}
if (hasClientComposition) {
@@ -3824,12 +3818,6 @@
size_t index = 0;
size_t numArgs = args.size();
- if (asProto) {
- LayersProto layersProto = dumpProtoInfo(LayerVector::StateSet::Current);
- result.append(layersProto.SerializeAsString().c_str(), layersProto.ByteSize());
- dumpAll = false;
- }
-
if (numArgs) {
if ((index < numArgs) &&
(args[index] == String16("--list"))) {
@@ -3906,10 +3894,21 @@
mLayerStats.dump(result);
dumpAll = false;
}
+
+ if ((index < numArgs) && (args[index] == String16("--timestats"))) {
+ index++;
+ mTimeStats.parseArgs(asProto, args, index, result);
+ dumpAll = false;
+ }
}
if (dumpAll) {
- dumpAllLocked(args, index, result);
+ if (asProto) {
+ LayersProto layersProto = dumpProtoInfo(LayerVector::StateSet::Current);
+ result.append(layersProto.SerializeAsString().c_str(), layersProto.ByteSize());
+ } else {
+ dumpAllLocked(args, index, result);
+ }
}
if (locked) {
@@ -4349,6 +4348,30 @@
return true;
}
+void SurfaceFlinger::updateColorMatrixLocked() {
+ mat4 colorMatrix;
+ if (mGlobalSaturationFactor != 1.0f) {
+ // Rec.709 luma coefficients
+ float3 luminance{0.213f, 0.715f, 0.072f};
+ luminance *= 1.0f - mGlobalSaturationFactor;
+ mat4 saturationMatrix = mat4(
+ vec4{luminance.r + mGlobalSaturationFactor, luminance.r, luminance.r, 0.0f},
+ vec4{luminance.g, luminance.g + mGlobalSaturationFactor, luminance.g, 0.0f},
+ vec4{luminance.b, luminance.b, luminance.b + mGlobalSaturationFactor, 0.0f},
+ vec4{0.0f, 0.0f, 0.0f, 1.0f}
+ );
+ colorMatrix = mClientColorMatrix * saturationMatrix * mDaltonizer();
+ } else {
+ colorMatrix = mClientColorMatrix * mDaltonizer();
+ }
+
+ if (mCurrentState.colorMatrix != colorMatrix) {
+ mCurrentState.colorMatrix = colorMatrix;
+ mCurrentState.colorMatrixChanged = true;
+ setTransactionFlags(eTransactionNeeded);
+ }
+}
+
status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) {
switch (code) {
case CREATE_CONNECTION:
@@ -4483,6 +4506,7 @@
return NO_ERROR;
}
case 1014: {
+ Mutex::Autolock _l(mStateLock);
// daltonize
n = data.readInt32();
switch (n % 10) {
@@ -4504,33 +4528,33 @@
} else {
mDaltonizer.setMode(ColorBlindnessMode::Simulation);
}
- invalidateHwcGeometry();
- repaintEverything();
+
+ updateColorMatrixLocked();
return NO_ERROR;
}
case 1015: {
+ Mutex::Autolock _l(mStateLock);
// apply a color matrix
n = data.readInt32();
if (n) {
// color matrix is sent as a column-major mat4 matrix
for (size_t i = 0 ; i < 4; i++) {
for (size_t j = 0; j < 4; j++) {
- mColorMatrix[i][j] = data.readFloat();
+ mClientColorMatrix[i][j] = data.readFloat();
}
}
} else {
- mColorMatrix = mat4();
+ mClientColorMatrix = mat4();
}
// Check that supplied matrix's last row is {0,0,0,1} so we can avoid
// the division by w in the fragment shader
- float4 lastRow(transpose(mColorMatrix)[3]);
+ float4 lastRow(transpose(mClientColorMatrix)[3]);
if (any(greaterThan(abs(lastRow - float4{0, 0, 0, 1}), float4{1e-4f}))) {
ALOGE("The color transform's last row must be (0, 0, 0, 1)");
}
- invalidateHwcGeometry();
- repaintEverything();
+ updateColorMatrixLocked();
return NO_ERROR;
}
// This is an experimental interface
@@ -4573,10 +4597,10 @@
return NO_ERROR;
}
case 1022: { // Set saturation boost
+ Mutex::Autolock _l(mStateLock);
mGlobalSaturationFactor = std::max(0.0f, std::min(data.readFloat(), 2.0f));
- invalidateHwcGeometry();
- repaintEverything();
+ updateColorMatrixLocked();
return NO_ERROR;
}
case 1023: { // Set native mode
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 33706da..54cf63c 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -63,6 +63,7 @@
#include "SurfaceInterceptor.h"
#include "SurfaceTracing.h"
#include "StartPropertySetThread.h"
+#include "TimeStats/TimeStats.h"
#include "VSyncModulator.h"
#include "DisplayHardware/HWC2.h"
@@ -372,6 +373,10 @@
// always uses the Drawing StateSet.
layersSortedByZ = other.layersSortedByZ;
displays = other.displays;
+ colorMatrixChanged = other.colorMatrixChanged;
+ if (colorMatrixChanged) {
+ colorMatrix = other.colorMatrix;
+ }
return *this;
}
@@ -379,6 +384,9 @@
LayerVector layersSortedByZ;
DefaultKeyedVector< wp<IBinder>, DisplayDeviceState> displays;
+ bool colorMatrixChanged = true;
+ mat4 colorMatrix;
+
void traverseInZOrder(const LayerVector::Visitor& visitor) const;
void traverseInReverseZOrder(const LayerVector::Visitor& visitor) const;
};
@@ -652,8 +660,6 @@
ui::ColorMode* outMode,
ui::Dataspace* outDataSpace) const;
- mat4 computeSaturationMatrix() const;
-
void setUpHWComposer();
void doComposition();
void doDebugFlashRegions();
@@ -740,6 +746,8 @@
// Check to see if we should handoff to vr flinger.
void updateVrFlinger();
+ void updateColorMatrixLocked();
+
/* ------------------------------------------------------------------------
* Attributes
*/
@@ -753,6 +761,11 @@
bool mAnimTransactionPending;
SortedVector< sp<Layer> > mLayersPendingRemoval;
+ // global color transform states
+ Daltonizer mDaltonizer;
+ float mGlobalSaturationFactor = 1.0f;
+ mat4 mClientColorMatrix;
+
// Can't be unordered_set because wp<> isn't hashable
std::set<wp<IBinder>> mGraphicBufferProducerList;
size_t mMaxGraphicBufferProducerListSize = MAX_LAYERS;
@@ -815,6 +828,7 @@
std::make_unique<impl::SurfaceInterceptor>(this);
SurfaceTracing mTracing;
LayerStats mLayerStats;
+ TimeStats& mTimeStats = TimeStats::getInstance();
bool mUseHwcVirtualDisplays = false;
// Restrict layers to use two buffers in their bufferqueues.
@@ -842,12 +856,6 @@
bool mInjectVSyncs;
- Daltonizer mDaltonizer;
-
- mat4 mPreviousColorMatrix;
- mat4 mColorMatrix;
- bool mHasColorMatrix;
-
// Static screen stats
bool mHasPoweredOff;
@@ -865,8 +873,6 @@
DisplayColorSetting mDisplayColorSetting = DisplayColorSetting::MANAGED;
// Applied on sRGB layers when the render intent is non-colorimetric.
mat4 mLegacySrgbSaturationMatrix;
- // Applied globally.
- float mGlobalSaturationFactor = 1.0f;
bool mBuiltinDisplaySupportsEnhance = false;
using CreateBufferQueueFunction =
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
new file mode 100644
index 0000000..5f2dd32
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -0,0 +1,498 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#undef LOG_TAG
+#define LOG_TAG "TimeStats"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "TimeStats.h"
+
+#include <android-base/stringprintf.h>
+
+#include <log/log.h>
+
+#include <utils/String8.h>
+#include <utils/Trace.h>
+
+#include <algorithm>
+#include <regex>
+
+namespace android {
+
+TimeStats& TimeStats::getInstance() {
+ static std::unique_ptr<TimeStats> sInstance;
+ static std::once_flag sOnceFlag;
+
+ std::call_once(sOnceFlag, [] { sInstance.reset(new TimeStats); });
+ return *sInstance.get();
+}
+
+void TimeStats::parseArgs(bool asProto, const Vector<String16>& args, size_t& index,
+ String8& result) {
+ ATRACE_CALL();
+
+ if (args.size() > index + 10) {
+ ALOGD("Invalid args count");
+ return;
+ }
+
+ std::unordered_map<std::string, int32_t> argsMap;
+ while (index < args.size()) {
+ argsMap[std::string(String8(args[index]).c_str())] = index;
+ ++index;
+ }
+
+ if (argsMap.count("-disable")) {
+ disable();
+ }
+
+ if (argsMap.count("-dump")) {
+ int64_t maxLayers = 0;
+ auto iter = argsMap.find("-maxlayers");
+ if (iter != argsMap.end() && iter->second + 1 < static_cast<int32_t>(args.size())) {
+ maxLayers = strtol(String8(args[iter->second + 1]).c_str(), nullptr, 10);
+ maxLayers = std::clamp(maxLayers, int64_t(0), int64_t(UINT32_MAX));
+ }
+
+ dump(asProto, static_cast<uint32_t>(maxLayers), result);
+ }
+
+ if (argsMap.count("-clear")) {
+ clear();
+ }
+
+ if (argsMap.count("-enable")) {
+ enable();
+ }
+}
+
+void TimeStats::incrementTotalFrames() {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ timeStats.totalFrames++;
+}
+
+void TimeStats::incrementMissedFrames(bool propagateBackpressure) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (propagateBackpressure) {
+ timeStats.totalFrames--;
+ }
+ timeStats.missedFrames++;
+}
+
+void TimeStats::incrementClientCompositionFrames() {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ timeStats.clientCompositionFrames++;
+}
+
+bool TimeStats::recordReadyLocked(const std::string& layerName, TimeRecord* timeRecord) {
+ if (!timeRecord->ready) {
+ ALOGV("[%s]-[%" PRIu64 "]-presentFence is still not received", layerName.c_str(),
+ timeRecord->frameNumber);
+ return false;
+ }
+
+ if (timeRecord->acquireFence != nullptr) {
+ if (timeRecord->acquireFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING) {
+ return false;
+ }
+ if (timeRecord->acquireFence->getSignalTime() != Fence::SIGNAL_TIME_INVALID) {
+ timeRecord->acquireTime = timeRecord->acquireFence->getSignalTime();
+ timeRecord->acquireFence = nullptr;
+ } else {
+ ALOGV("[%s]-[%" PRIu64 "]-acquireFence signal time is invalid", layerName.c_str(),
+ timeRecord->frameNumber);
+ }
+ }
+
+ if (timeRecord->presentFence != nullptr) {
+ if (timeRecord->presentFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING) {
+ return false;
+ }
+ if (timeRecord->presentFence->getSignalTime() != Fence::SIGNAL_TIME_INVALID) {
+ timeRecord->presentTime = timeRecord->presentFence->getSignalTime();
+ timeRecord->presentFence = nullptr;
+ } else {
+ ALOGV("[%s]-[%" PRIu64 "]-presentFence signal time invalid", layerName.c_str(),
+ timeRecord->frameNumber);
+ }
+ }
+
+ return true;
+}
+
+static int32_t msBetween(nsecs_t start, nsecs_t end) {
+ int64_t delta = (end - start) / 1000000;
+ delta = std::clamp(delta, int64_t(INT32_MIN), int64_t(INT32_MAX));
+ return static_cast<int32_t>(delta);
+}
+
+void TimeStats::flushAvailableRecordsToStatsLocked(const std::string& layerName) {
+ ATRACE_CALL();
+
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord;
+ std::vector<TimeRecord>& timeRecords = layerRecord.timeRecords;
+ while (!timeRecords.empty()) {
+ if (!recordReadyLocked(layerName, &timeRecords[0])) break;
+ ALOGV("[%s]-[%" PRIu64 "]-presentFenceTime[%" PRId64 "]", layerName.c_str(),
+ timeRecords[0].frameNumber, timeRecords[0].presentTime);
+
+ if (prevTimeRecord.ready) {
+ if (!timeStats.stats.count(layerName)) {
+ timeStats.stats[layerName].layerName = layerName;
+ timeStats.stats[layerName].statsStart = static_cast<int64_t>(std::time(0));
+ }
+ TimeStatsHelper::TimeStatsLayer& timeStatsLayer = timeStats.stats[layerName];
+ timeStatsLayer.totalFrames++;
+
+ const int32_t postToPresentMs =
+ msBetween(timeRecords[0].postTime, timeRecords[0].presentTime);
+ ALOGV("[%s]-[%" PRIu64 "]-post2present[%d]", layerName.c_str(),
+ timeRecords[0].frameNumber, postToPresentMs);
+ timeStatsLayer.deltas["post2present"].insert(postToPresentMs);
+
+ const int32_t acquireToPresentMs =
+ msBetween(timeRecords[0].acquireTime, timeRecords[0].presentTime);
+ ALOGV("[%s]-[%" PRIu64 "]-acquire2present[%d]", layerName.c_str(),
+ timeRecords[0].frameNumber, acquireToPresentMs);
+ timeStatsLayer.deltas["acquire2present"].insert(acquireToPresentMs);
+
+ const int32_t latchToPresentMs =
+ msBetween(timeRecords[0].latchTime, timeRecords[0].presentTime);
+ ALOGV("[%s]-[%" PRIu64 "]-latch2present[%d]", layerName.c_str(),
+ timeRecords[0].frameNumber, latchToPresentMs);
+ timeStatsLayer.deltas["latch2present"].insert(latchToPresentMs);
+
+ const int32_t desiredToPresentMs =
+ msBetween(timeRecords[0].desiredTime, timeRecords[0].presentTime);
+ ALOGV("[%s]-[%" PRIu64 "]-desired2present[%d]", layerName.c_str(),
+ timeRecords[0].frameNumber, desiredToPresentMs);
+ timeStatsLayer.deltas["desired2present"].insert(desiredToPresentMs);
+
+ const int32_t presentToPresentMs =
+ msBetween(prevTimeRecord.presentTime, timeRecords[0].presentTime);
+ ALOGV("[%s]-[%" PRIu64 "]-present2present[%d]", layerName.c_str(),
+ timeRecords[0].frameNumber, presentToPresentMs);
+ timeStatsLayer.deltas["present2present"].insert(presentToPresentMs);
+
+ timeStats.stats[layerName].statsEnd = static_cast<int64_t>(std::time(0));
+ }
+ prevTimeRecord = timeRecords[0];
+ // TODO(zzyiwei): change timeRecords to use std::deque
+ timeRecords.erase(timeRecords.begin());
+ layerRecord.waitData--;
+ }
+}
+
+static bool layerNameIsValid(const std::string& layerName) {
+ // This regular expression captures the following layer names for instance:
+ // 1) StatusBat#0
+ // 2) NavigationBar#1
+ // 3) com.*#0
+ // 4) SurfaceView - com.*#0
+ // Using [-\\s\t]+ for the conjunction part between SurfaceView and com.* is
+ // a bit more robust in case there's a slight change.
+ // The layer name would only consist of . / $ _ 0-9 a-z A-Z in most cases.
+ std::regex re("(((SurfaceView[-\\s\\t]+)?com\\.[./$\\w]+)|((Status|Navigation)Bar))#\\d+");
+ return std::regex_match(layerName.begin(), layerName.end(), re);
+}
+
+void TimeStats::setPostTime(const std::string& layerName, uint64_t frameNumber, nsecs_t postTime) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-[%" PRIu64 "]-PostTime[%" PRId64 "]", layerName.c_str(), frameNumber, postTime);
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName) && !layerNameIsValid(layerName)) {
+ return;
+ }
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ if (layerRecord.timeRecords.size() == MAX_NUM_TIME_RECORDS) {
+ ALOGV("[%s]-timeRecords is already at its maximum size[%zu]", layerName.c_str(),
+ MAX_NUM_TIME_RECORDS);
+ // TODO(zzyiwei): if this happens, there must be a present fence missing
+ // or waitData is not in the correct position. Need to think out a
+ // reasonable way to recover from this state.
+ return;
+ }
+ // For most media content, the acquireFence is invalid because the buffer is
+ // ready at the queueBuffer stage. In this case, acquireTime should be given
+ // a default value as postTime.
+ TimeRecord timeRecord = {
+ .frameNumber = frameNumber,
+ .postTime = postTime,
+ .acquireTime = postTime,
+ };
+ layerRecord.timeRecords.push_back(timeRecord);
+ if (layerRecord.waitData < 0 ||
+ layerRecord.waitData >= static_cast<int32_t>(layerRecord.timeRecords.size()))
+ layerRecord.waitData = layerRecord.timeRecords.size() - 1;
+}
+
+void TimeStats::setLatchTime(const std::string& layerName, uint64_t frameNumber,
+ nsecs_t latchTime) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-[%" PRIu64 "]-LatchTime[%" PRId64 "]", layerName.c_str(), frameNumber, latchTime);
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName)) return;
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+ if (timeRecord.frameNumber == frameNumber) {
+ timeRecord.latchTime = latchTime;
+ }
+}
+
+void TimeStats::setDesiredTime(const std::string& layerName, uint64_t frameNumber,
+ nsecs_t desiredTime) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-[%" PRIu64 "]-DesiredTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+ desiredTime);
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName)) return;
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+ if (timeRecord.frameNumber == frameNumber) {
+ timeRecord.desiredTime = desiredTime;
+ }
+}
+
+void TimeStats::setAcquireTime(const std::string& layerName, uint64_t frameNumber,
+ nsecs_t acquireTime) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-[%" PRIu64 "]-AcquireTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+ acquireTime);
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName)) return;
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+ if (timeRecord.frameNumber == frameNumber) {
+ timeRecord.acquireTime = acquireTime;
+ }
+}
+
+void TimeStats::setAcquireFence(const std::string& layerName, uint64_t frameNumber,
+ const std::shared_ptr<FenceTime>& acquireFence) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-[%" PRIu64 "]-AcquireFenceTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+ acquireFence->getSignalTime());
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName)) return;
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+ if (timeRecord.frameNumber == frameNumber) {
+ timeRecord.acquireFence = acquireFence;
+ }
+}
+
+void TimeStats::setPresentTime(const std::string& layerName, uint64_t frameNumber,
+ nsecs_t presentTime) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-[%" PRIu64 "]-PresentTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+ presentTime);
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName)) return;
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+ if (timeRecord.frameNumber == frameNumber) {
+ timeRecord.presentTime = presentTime;
+ timeRecord.ready = true;
+ layerRecord.waitData++;
+ }
+
+ flushAvailableRecordsToStatsLocked(layerName);
+}
+
+void TimeStats::setPresentFence(const std::string& layerName, uint64_t frameNumber,
+ const std::shared_ptr<FenceTime>& presentFence) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-[%" PRIu64 "]-PresentFenceTime[%" PRId64 "]", layerName.c_str(), frameNumber,
+ presentFence->getSignalTime());
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName)) return;
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ TimeRecord& timeRecord = layerRecord.timeRecords[layerRecord.waitData];
+ if (timeRecord.frameNumber == frameNumber) {
+ timeRecord.presentFence = presentFence;
+ timeRecord.ready = true;
+ layerRecord.waitData++;
+ }
+
+ flushAvailableRecordsToStatsLocked(layerName);
+}
+
+void TimeStats::onDisconnect(const std::string& layerName) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-onDisconnect", layerName.c_str());
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName)) return;
+ flushAvailableRecordsToStatsLocked(layerName);
+ timeStatsTracker.erase(layerName);
+}
+
+void TimeStats::clearLayerRecord(const std::string& layerName) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-clearLayerRecord", layerName.c_str());
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName)) return;
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ layerRecord.timeRecords.clear();
+ layerRecord.prevTimeRecord.ready = false;
+ layerRecord.waitData = -1;
+}
+
+void TimeStats::removeTimeRecord(const std::string& layerName, uint64_t frameNumber) {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+ ALOGV("[%s]-[%" PRIu64 "]-removeTimeRecord", layerName.c_str(), frameNumber);
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!timeStatsTracker.count(layerName)) return;
+ LayerRecord& layerRecord = timeStatsTracker[layerName];
+ size_t removeAt = 0;
+ for (const TimeRecord& record : layerRecord.timeRecords) {
+ if (record.frameNumber == frameNumber) break;
+ removeAt++;
+ }
+ if (removeAt == layerRecord.timeRecords.size()) return;
+ layerRecord.timeRecords.erase(layerRecord.timeRecords.begin() + removeAt);
+ if (layerRecord.waitData > static_cast<int32_t>(removeAt)) {
+ --layerRecord.waitData;
+ }
+}
+
+void TimeStats::enable() {
+ if (mEnabled.load()) return;
+
+ ATRACE_CALL();
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ ALOGD("Enabled");
+ mEnabled.store(true);
+ timeStats.statsStart = static_cast<int64_t>(std::time(0));
+}
+
+void TimeStats::disable() {
+ if (!mEnabled.load()) return;
+
+ ATRACE_CALL();
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ ALOGD("Disabled");
+ mEnabled.store(false);
+ timeStats.statsEnd = static_cast<int64_t>(std::time(0));
+}
+
+void TimeStats::clear() {
+ ATRACE_CALL();
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ ALOGD("Cleared");
+ timeStats.dumpStats.clear();
+ timeStats.stats.clear();
+ timeStats.statsStart = (mEnabled.load() ? static_cast<int64_t>(std::time(0)) : 0);
+ timeStats.statsEnd = 0;
+ timeStats.totalFrames = 0;
+ timeStats.missedFrames = 0;
+ timeStats.clientCompositionFrames = 0;
+}
+
+bool TimeStats::isEnabled() {
+ return mEnabled.load();
+}
+
+void TimeStats::dump(bool asProto, uint32_t maxLayers, String8& result) {
+ ATRACE_CALL();
+
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (timeStats.statsStart == 0) {
+ return;
+ }
+
+ timeStats.statsEnd = static_cast<int64_t>(std::time(0));
+
+ // TODO(zzyiwei): refactor dumpStats into TimeStatsHelper
+ timeStats.dumpStats.clear();
+ for (auto& ele : timeStats.stats) {
+ timeStats.dumpStats.push_back(&ele.second);
+ }
+
+ std::sort(timeStats.dumpStats.begin(), timeStats.dumpStats.end(),
+ [](TimeStatsHelper::TimeStatsLayer* const& l,
+ TimeStatsHelper::TimeStatsLayer* const& r) {
+ return l->totalFrames > r->totalFrames;
+ });
+
+ if (maxLayers != 0 && maxLayers < timeStats.dumpStats.size()) {
+ timeStats.dumpStats.resize(maxLayers);
+ }
+
+ if (asProto) {
+ dumpAsProtoLocked(result);
+ } else {
+ dumpAsTextLocked(result);
+ }
+}
+
+void TimeStats::dumpAsTextLocked(String8& result) {
+ ALOGD("Dumping TimeStats as text");
+ result.append(timeStats.toString().c_str());
+ result.append("\n");
+}
+
+void TimeStats::dumpAsProtoLocked(String8& result) {
+ ALOGD("Dumping TimeStats as proto");
+ SFTimeStatsGlobalProto timeStatsProto = timeStats.toProto();
+ result.append(timeStatsProto.SerializeAsString().c_str(), timeStatsProto.ByteSize());
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
new file mode 100644
index 0000000..2410265
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <timestatsproto/TimeStatsHelper.h>
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include <ui/FenceTime.h>
+
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <mutex>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::surfaceflinger;
+
+namespace android {
+class String8;
+
+class TimeStats {
+ // TODO(zzyiwei): Bound the timeStatsTracker with weighted LRU
+ // static const size_t MAX_NUM_LAYER_RECORDS = 200;
+ static const size_t MAX_NUM_TIME_RECORDS = 64;
+
+ struct TimeRecord {
+ bool ready = false;
+ uint64_t frameNumber = 0;
+ nsecs_t postTime = 0;
+ nsecs_t latchTime = 0;
+ nsecs_t acquireTime = 0;
+ nsecs_t desiredTime = 0;
+ nsecs_t presentTime = 0;
+ std::shared_ptr<FenceTime> acquireFence;
+ std::shared_ptr<FenceTime> presentFence;
+ };
+
+ struct LayerRecord {
+ // This is the index in timeRecords, at which the timestamps for that
+ // specific frame are still not fully received. This is not waiting for
+ // fences to signal, but rather waiting to receive those fences/timestamps.
+ int32_t waitData = -1;
+ TimeRecord prevTimeRecord;
+ std::vector<TimeRecord> timeRecords;
+ };
+
+public:
+ static TimeStats& getInstance();
+ void parseArgs(bool asProto, const Vector<String16>& args, size_t& index, String8& result);
+ void incrementTotalFrames();
+ void incrementMissedFrames(bool propagateBackpressure);
+ void incrementClientCompositionFrames();
+
+ void setPostTime(const std::string& layerName, uint64_t frameNumber, nsecs_t postTime);
+ void setLatchTime(const std::string& layerName, uint64_t frameNumber, nsecs_t latchTime);
+ void setDesiredTime(const std::string& layerName, uint64_t frameNumber, nsecs_t desiredTime);
+ void setAcquireTime(const std::string& layerName, uint64_t frameNumber, nsecs_t acquireTime);
+ void setAcquireFence(const std::string& layerName, uint64_t frameNumber,
+ const std::shared_ptr<FenceTime>& acquireFence);
+ void setPresentTime(const std::string& layerName, uint64_t frameNumber, nsecs_t presentTime);
+ void setPresentFence(const std::string& layerName, uint64_t frameNumber,
+ const std::shared_ptr<FenceTime>& presentFence);
+ void onDisconnect(const std::string& layerName);
+ void clearLayerRecord(const std::string& layerName);
+ void removeTimeRecord(const std::string& layerName, uint64_t frameNumber);
+
+private:
+ TimeStats() = default;
+
+ bool recordReadyLocked(const std::string& layerName, TimeRecord* timeRecord);
+ void flushAvailableRecordsToStatsLocked(const std::string& layerName);
+
+ void enable();
+ void disable();
+ void clear();
+ bool isEnabled();
+ void dump(bool asProto, uint32_t maxLayer, String8& result);
+ void dumpAsTextLocked(String8& result);
+ void dumpAsProtoLocked(String8& result);
+
+ std::atomic<bool> mEnabled = false;
+ std::mutex mMutex;
+ TimeStatsHelper::TimeStatsGlobal timeStats;
+ std::unordered_map<std::string, LayerRecord> timeStatsTracker;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/Android.bp b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
new file mode 100644
index 0000000..66aa719
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
@@ -0,0 +1,55 @@
+cc_library_shared {
+ name: "libtimestats_proto",
+ vendor_available: true,
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "TimeStatsHelper.cpp",
+ "timestats.proto",
+ ],
+
+ shared_libs: [
+ "android.hardware.graphics.common@1.1",
+ "libui",
+ "libprotobuf-cpp-lite",
+ "libbase",
+ "liblog",
+ ],
+
+ proto: {
+ export_proto_headers: true,
+ },
+
+ cppflags: [
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wno-format",
+ "-Wno-c++98-compat-pedantic",
+ "-Wno-float-conversion",
+ "-Wno-disabled-macro-expansion",
+ "-Wno-float-equal",
+ "-Wno-sign-conversion",
+ "-Wno-padded",
+ "-Wno-old-style-cast",
+ "-Wno-undef",
+ ],
+
+}
+
+java_library_static {
+ name: "timestatsprotosnano",
+ host_supported: true,
+ proto: {
+ type: "nano",
+ },
+ srcs: ["*.proto"],
+ no_framework_libs: true,
+ target: {
+ android: {
+ jarjar_rules: "jarjar-rules.txt",
+ },
+ host: {
+ static_libs: ["libprotobuf-java-nano"],
+ },
+ },
+}
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
new file mode 100644
index 0000000..3e5007c
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android-base/stringprintf.h>
+#include <timestatsproto/TimeStatsHelper.h>
+
+#include <array>
+#include <regex>
+
+#define HISTOGRAM_SIZE 85
+
+using android::base::StringAppendF;
+using android::base::StringPrintf;
+
+namespace android {
+namespace surfaceflinger {
+
+// Time buckets for histogram, the calculated time deltas will be lower bounded
+// to the buckets in this array.
+static const std::array<int32_t, HISTOGRAM_SIZE> histogramConfig =
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
+ 34, 36, 38, 40, 42, 44, 46, 48, 50, 54, 58, 62, 66, 70, 74, 78, 82,
+ 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126, 130, 134, 138, 142, 146, 150,
+ 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000};
+
+void TimeStatsHelper::Histogram::insert(int32_t delta) {
+ if (delta < 0) return;
+ // std::lower_bound won't work on out of range values
+ if (delta > histogramConfig[HISTOGRAM_SIZE - 1]) {
+ hist[histogramConfig[HISTOGRAM_SIZE - 1]]++;
+ return;
+ }
+ auto iter = std::lower_bound(histogramConfig.begin(), histogramConfig.end(), delta);
+ hist[*iter]++;
+}
+
+float TimeStatsHelper::Histogram::averageTime() {
+ int64_t ret = 0;
+ int64_t count = 0;
+ for (auto ele : hist) {
+ count += ele.second;
+ ret += ele.first * ele.second;
+ }
+ return static_cast<float>(ret) / count;
+}
+
+std::string TimeStatsHelper::Histogram::toString() {
+ std::string result;
+ for (int32_t i = 0; i < HISTOGRAM_SIZE; ++i) {
+ int32_t bucket = histogramConfig[i];
+ int32_t count = (hist.count(bucket) == 0) ? 0 : hist[bucket];
+ StringAppendF(&result, "%dms=%d ", bucket, count);
+ }
+ result.back() = '\n';
+ return result;
+}
+
+static std::string getPackageName(const std::string& layerName) {
+ // This regular expression captures the following for instance:
+ // StatusBar in StatusBar#0
+ // com.appname in com.appname/com.appname.activity#0
+ // com.appname in SurfaceView - com.appname/com.appname.activity#0
+ const std::regex re("(?:SurfaceView[-\\s\\t]+)?([^/]+).*#\\d+");
+ std::smatch match;
+ if (std::regex_match(layerName.begin(), layerName.end(), match, re)) {
+ // There must be a match for group 1 otherwise the whole string is not
+ // matched and the above will return false
+ return match[1];
+ }
+ return "";
+}
+
+std::string TimeStatsHelper::TimeStatsLayer::toString() {
+ std::string result = "";
+ StringAppendF(&result, "layerName = %s\n", layerName.c_str());
+ packageName = getPackageName(layerName);
+ StringAppendF(&result, "packageName = %s\n", packageName.c_str());
+ StringAppendF(&result, "statsStart = %lld\n", static_cast<long long int>(statsStart));
+ StringAppendF(&result, "statsEnd = %lld\n", static_cast<long long int>(statsEnd));
+ StringAppendF(&result, "totalFrames= %d\n", totalFrames);
+ if (deltas.find("present2present") != deltas.end()) {
+ StringAppendF(&result, "averageFPS = %.3f\n",
+ 1000.0 / deltas["present2present"].averageTime());
+ }
+ for (auto ele : deltas) {
+ StringAppendF(&result, "%s histogram is as below:\n", ele.first.c_str());
+ StringAppendF(&result, "%s", ele.second.toString().c_str());
+ }
+
+ return result;
+}
+
+std::string TimeStatsHelper::TimeStatsGlobal::toString() {
+ std::string result = "SurfaceFlinger TimeStats:\n";
+ StringAppendF(&result, "statsStart = %lld\n", static_cast<long long int>(statsStart));
+ StringAppendF(&result, "statsEnd = %lld\n", static_cast<long long int>(statsEnd));
+ StringAppendF(&result, "totalFrames= %d\n", totalFrames);
+ StringAppendF(&result, "missedFrames= %d\n", missedFrames);
+ StringAppendF(&result, "clientCompositionFrames= %d\n", clientCompositionFrames);
+ StringAppendF(&result, "TimeStats for each layer is as below:\n");
+ for (auto ele : dumpStats) {
+ StringAppendF(&result, "%s", ele->toString().c_str());
+ }
+
+ return result;
+}
+
+SFTimeStatsLayerProto TimeStatsHelper::TimeStatsLayer::toProto() {
+ SFTimeStatsLayerProto layerProto;
+ layerProto.set_layer_name(layerName);
+ packageName = getPackageName(layerName);
+ layerProto.set_package_name(packageName);
+ layerProto.set_stats_start(statsStart);
+ layerProto.set_stats_end(statsEnd);
+ layerProto.set_total_frames(totalFrames);
+ for (auto ele : deltas) {
+ SFTimeStatsDeltaProto* deltaProto = layerProto.add_deltas();
+ deltaProto->set_delta_name(ele.first);
+ for (auto histEle : ele.second.hist) {
+ SFTimeStatsHistogramBucketProto* histProto = deltaProto->add_histograms();
+ histProto->set_render_millis(histEle.first);
+ histProto->set_frame_count(histEle.second);
+ }
+ }
+ return layerProto;
+}
+
+SFTimeStatsGlobalProto TimeStatsHelper::TimeStatsGlobal::toProto() {
+ SFTimeStatsGlobalProto globalProto;
+ globalProto.set_stats_start(statsStart);
+ globalProto.set_stats_end(statsEnd);
+ globalProto.set_total_frames(totalFrames);
+ globalProto.set_missed_frames(missedFrames);
+ globalProto.set_client_composition_frames(clientCompositionFrames);
+ for (auto ele : dumpStats) {
+ SFTimeStatsLayerProto* layerProto = globalProto.add_stats();
+ layerProto->CopyFrom(ele->toProto());
+ }
+ return globalProto;
+}
+
+} // namespace surfaceflinger
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
new file mode 100644
index 0000000..c876f21
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include <math/vec4.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace surfaceflinger {
+
+class TimeStatsHelper {
+public:
+ class Histogram {
+ public:
+ // Key is the delta time between timestamps
+ // Value is the number of appearances of that delta
+ std::unordered_map<int32_t, int32_t> hist;
+
+ void insert(int32_t delta);
+ float averageTime();
+ std::string toString();
+ };
+
+ class TimeStatsLayer {
+ public:
+ std::string layerName;
+ std::string packageName;
+ int64_t statsStart = 0;
+ int64_t statsEnd = 0;
+ int32_t totalFrames = 0;
+ std::unordered_map<std::string, Histogram> deltas;
+
+ std::string toString();
+ SFTimeStatsLayerProto toProto();
+ };
+
+ class TimeStatsGlobal {
+ public:
+ int64_t statsStart = 0;
+ int64_t statsEnd = 0;
+ int32_t totalFrames = 0;
+ int32_t missedFrames = 0;
+ int32_t clientCompositionFrames = 0;
+ std::unordered_map<std::string, TimeStatsLayer> stats;
+ std::vector<TimeStatsLayer*> dumpStats;
+
+ std::string toString();
+ SFTimeStatsGlobalProto toProto();
+ };
+};
+
+} // namespace surfaceflinger
+} // namespace android
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h
new file mode 100644
index 0000000..fe0d150
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsProtoHeader.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Projectlayerproto/LayerProtoHeader.h
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// pragma is used here to disable the warnings emitted from the protobuf
+// headers. By adding #pragma before including layer.pb.h, it supresses
+// protobuf warnings, but allows the rest of the files to continuing using
+// the current flags.
+// This file should be included instead of directly including layer.b.h
+#pragma GCC system_header
+#include <timestats.pb.h>
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt b/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt
new file mode 100644
index 0000000..40043a8
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.nano.** com.android.framework.protobuf.nano.@1
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
new file mode 100644
index 0000000..a8f6fa8
--- /dev/null
+++ b/services/surfaceflinger/TimeStats/timestatsproto/timestats.proto
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.surfaceflinger;
+
+option optimize_for = LITE_RUNTIME;
+
+// frameworks/base/core/proto/android/service/sftimestats.proto is based on
+// this proto. Please only make valid protobuf changes to these messages, and
+// keep the other file in sync with this one.
+
+message SFTimeStatsGlobalProto {
+ // The start & end timestamps in UTC as
+ // milliseconds since January 1, 1970
+ optional int64 stats_start = 1;
+ optional int64 stats_end = 2;
+ // Total frames
+ optional int32 total_frames = 3;
+ // Total missed frames of SurfaceFlinger.
+ optional int32 missed_frames = 4;
+ // Total frames fallback to client composition.
+ optional int32 client_composition_frames = 5;
+
+ repeated SFTimeStatsLayerProto stats = 6;
+}
+
+message SFTimeStatsLayerProto {
+ // The layer name
+ optional string layer_name = 1;
+ // The package name
+ optional string package_name = 2;
+ // The start & end timestamps in UTC as
+ // milliseconds since January 1, 1970
+ optional int64 stats_start = 3;
+ optional int64 stats_end = 4;
+ // Distinct frame count.
+ optional int32 total_frames = 5;
+
+ repeated SFTimeStatsDeltaProto deltas = 6;
+}
+
+message SFTimeStatsDeltaProto {
+ // Name of the time interval
+ optional string delta_name = 1;
+ // Histogram of the delta time
+ repeated SFTimeStatsHistogramBucketProto histograms = 2;
+}
+
+message SFTimeStatsHistogramBucketProto {
+ // Lower bound of render time in milliseconds.
+ optional int32 render_millis = 1;
+ // Number of frames in the bucket.
+ optional int32 frame_count = 2;
+}
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 7523399..322e8a0 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -36,6 +36,7 @@
"liblayers_proto",
"liblog",
"libprotobuf-cpp-full",
+ "libtimestats_proto",
"libui",
"libutils",
]
diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp
index 00bc621..520df2d 100644
--- a/services/surfaceflinger/tests/fakehwc/Android.bp
+++ b/services/surfaceflinger/tests/fakehwc/Android.bp
@@ -25,6 +25,7 @@
"liblog",
"libnativewindow",
"libsync",
+ "libtimestats_proto",
"libui",
"libutils",
],