Add new SF backdoor option: hdr/sdr ratio overlay
- follow how RefreshRateOverlay did in general.
- 1043 as new opcode to enable hdr/sdr ratio overlay.
- decouple SevenSegmentDrawer and SurfaceControlHandle helper classes
from RefreshRateOverlay.
- add corresponding SF backdoor test for validation.
- for non-HDR-supported device, we don't reject them to use SF backdoor
but 1.00 will be always shown on the screen if enabling.
Test: adb shell call service call SurfaceFlinger 1043 1; atest
SurfaceFlinger_test
Bug: 277957056
Change-Id: Ifce2d435f127b53ab9d01aba4e24ffd4fdb010a7
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 89c80bc..326645e 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -168,6 +168,7 @@
"FrameTracer/FrameTracer.cpp",
"FrameTracker.cpp",
"HdrLayerInfoReporter.cpp",
+ "HdrSdrRatioOverlay.cpp",
"WindowInfosListenerInvoker.cpp",
"Layer.cpp",
"LayerFE.cpp",
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index f6ca9e2..32bd890 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -37,11 +37,11 @@
#include <configstore/Utils.h>
#include <log/log.h>
#include <system/window.h>
-#include <ui/GraphicTypes.h>
#include "Display/DisplaySnapshot.h"
#include "DisplayDevice.h"
#include "FrontEnd/DisplayInfo.h"
+#include "HdrSdrRatioOverlay.h"
#include "Layer.h"
#include "RefreshRateOverlay.h"
#include "SurfaceFlinger.h"
@@ -261,6 +261,9 @@
if (mRefreshRateOverlay) {
mRefreshRateOverlay->setLayerStack(filter.layerStack);
}
+ if (mHdrSdrRatioOverlay) {
+ mHdrSdrRatioOverlay->setLayerStack(filter.layerStack);
+ }
}
void DisplayDevice::setFlags(uint32_t flags) {
@@ -274,10 +277,14 @@
if (mRefreshRateOverlay) {
mRefreshRateOverlay->setViewport(size);
}
+ if (mHdrSdrRatioOverlay) {
+ mHdrSdrRatioOverlay->setViewport(size);
+ }
}
void DisplayDevice::setProjection(ui::Rotation orientation, Rect layerStackSpaceRect,
Rect orientedDisplaySpaceRect) {
+ mIsOrientationChanged = mOrientation != orientation;
mOrientation = orientation;
// We need to take care of display rotation for globalTransform for case if the panel is not
@@ -411,6 +418,26 @@
capabilities.getDesiredMinLuminance());
}
+void DisplayDevice::enableHdrSdrRatioOverlay(bool enable) {
+ if (!enable) {
+ mHdrSdrRatioOverlay.reset();
+ return;
+ }
+
+ mHdrSdrRatioOverlay = std::make_unique<HdrSdrRatioOverlay>();
+ mHdrSdrRatioOverlay->setLayerStack(getLayerStack());
+ mHdrSdrRatioOverlay->setViewport(getSize());
+ updateHdrSdrRatioOverlayRatio(mHdrSdrRatio);
+}
+
+void DisplayDevice::updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio) {
+ ATRACE_CALL();
+ mHdrSdrRatio = currentHdrSdrRatio;
+ if (mHdrSdrRatioOverlay) {
+ mHdrSdrRatioOverlay->changeHdrSdrRatio(currentHdrSdrRatio);
+ }
+}
+
void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner,
bool showRenderRate, bool showInMiddle) {
if (!enable) {
@@ -463,10 +490,23 @@
return false;
}
-void DisplayDevice::animateRefreshRateOverlay() {
+void DisplayDevice::animateOverlay() {
if (mRefreshRateOverlay) {
mRefreshRateOverlay->animate();
}
+ if (mHdrSdrRatioOverlay) {
+ // hdr sdr ratio is designed to be on the top right of the screen,
+ // therefore, we need to re-calculate the display's width and height
+ if (mIsOrientationChanged) {
+ auto width = getWidth();
+ auto height = getHeight();
+ if (mOrientation == ui::ROTATION_90 || mOrientation == ui::ROTATION_270) {
+ std::swap(width, height);
+ }
+ mHdrSdrRatioOverlay->setViewport({width, height});
+ }
+ mHdrSdrRatioOverlay->animate();
+ }
}
auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force)
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index dc5f8a8..e92125a 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -55,6 +55,7 @@
class Fence;
class HWComposer;
+class HdrSdrRatioOverlay;
class IGraphicBufferProducer;
class Layer;
class RefreshRateOverlay;
@@ -235,13 +236,19 @@
return mRefreshRateSelector;
}
+ void animateOverlay();
+
// Enables an overlay to be displayed with the current refresh rate
void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate,
bool showInMiddle) REQUIRES(kMainThreadContext);
void updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc = false);
bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
- void animateRefreshRateOverlay();
+
+ // Enables an overlay to be display with the hdr/sdr ratio
+ void enableHdrSdrRatioOverlay(bool enable) REQUIRES(kMainThreadContext);
+ void updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio);
+ bool isHdrSdrRatioOverlayEnabled() const { return mHdrSdrRatioOverlay != nullptr; }
nsecs_t getVsyncPeriodFromHWC() const;
@@ -271,6 +278,7 @@
const ui::Rotation mPhysicalOrientation;
ui::Rotation mOrientation = ui::ROTATION_0;
+ bool mIsOrientationChanged = false;
// Allow nullopt as initial power mode.
using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>;
@@ -297,6 +305,9 @@
std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
std::unique_ptr<RefreshRateOverlay> mRefreshRateOverlay;
+ std::unique_ptr<HdrSdrRatioOverlay> mHdrSdrRatioOverlay;
+ // This parameter is only used for hdr/sdr ratio overlay
+ float mHdrSdrRatio = 1.0f;
mutable std::mutex mActiveModeLock;
ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock);
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.cpp b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
new file mode 100644
index 0000000..2c0f518
--- /dev/null
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
@@ -0,0 +1,178 @@
+/**
+ * Copyright (C) 2023 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.
+ */
+// #define LOG_NDEBUG 0
+#include <algorithm>
+
+#include "HdrSdrRatioOverlay.h"
+
+#include <SkSurface.h>
+
+#undef LOG_TAG
+#define LOG_TAG "HdrSdrRatioOverlay"
+
+namespace android {
+
+void HdrSdrRatioOverlay::drawNumber(float number, int left, SkColor color, SkCanvas& canvas) {
+ if (!isfinite(number) || number >= 10.f) return;
+ // We assume that the number range is [1.f, 10.f)
+ // and the decimal places are 2.
+ int value = static_cast<int>(number * 100);
+ SegmentDrawer::drawDigit(value / 100, left, color, canvas);
+
+ left += kDigitWidth + kDigitSpace;
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::DecimalPoint, left, color, canvas);
+ left += kDigitWidth + kDigitSpace;
+
+ SegmentDrawer::drawDigit((value / 10) % 10, left, color, canvas);
+ left += kDigitWidth + kDigitSpace;
+ SegmentDrawer::drawDigit(value % 10, left, color, canvas);
+}
+
+sp<GraphicBuffer> HdrSdrRatioOverlay::draw(float currentHdrSdrRatio, SkColor color,
+ ui::Transform::RotationFlags rotation) {
+ SkMatrix canvasTransform = SkMatrix();
+ const auto [bufferWidth, bufferHeight] = [&]() -> std::pair<int, int> {
+ switch (rotation) {
+ case ui::Transform::ROT_90:
+ canvasTransform.setTranslate(kBufferHeight, 0);
+ canvasTransform.preRotate(90.f);
+ return {kBufferHeight, kBufferWidth};
+ case ui::Transform::ROT_270:
+ canvasTransform.setRotate(270.f, kBufferWidth / 2.f, kBufferWidth / 2.f);
+ return {kBufferHeight, kBufferWidth};
+ default:
+ return {kBufferWidth, kBufferHeight};
+ }
+ }();
+
+ const auto kUsageFlags = static_cast<uint64_t>(
+ GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_TEXTURE);
+ sp<GraphicBuffer> buffer =
+ sp<GraphicBuffer>::make(static_cast<uint32_t>(bufferWidth),
+ static_cast<uint32_t>(bufferHeight), HAL_PIXEL_FORMAT_RGBA_8888,
+ 1u, kUsageFlags, "HdrSdrRatioOverlay");
+
+ const status_t bufferStatus = buffer->initCheck();
+ LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "HdrSdrRatioOverlay: Buffer failed to allocate: %d",
+ bufferStatus);
+
+ sk_sp<SkSurface> surface =
+ SkSurfaces::Raster(SkImageInfo::MakeN32Premul(bufferWidth, bufferHeight));
+ SkCanvas* canvas = surface->getCanvas();
+ canvas->setMatrix(canvasTransform);
+
+ drawNumber(currentHdrSdrRatio, 0, color, *canvas);
+
+ void* pixels = nullptr;
+ buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&pixels));
+
+ const SkImageInfo& imageInfo = surface->imageInfo();
+ const size_t dstRowBytes = buffer->getStride() * static_cast<size_t>(imageInfo.bytesPerPixel());
+
+ canvas->readPixels(imageInfo, pixels, dstRowBytes, 0, 0);
+ buffer->unlock();
+ return buffer;
+}
+
+HdrSdrRatioOverlay::HdrSdrRatioOverlay()
+ : mSurfaceControl(
+ SurfaceControlHolder::createSurfaceControlHolder(String8("HdrSdrRatioOverlay"))) {
+ if (!mSurfaceControl) {
+ ALOGE("%s: Failed to create buffer state layer", __func__);
+ return;
+ }
+ SurfaceComposerClient::Transaction()
+ .setLayer(mSurfaceControl->get(), INT32_MAX - 2)
+ .setTrustedOverlay(mSurfaceControl->get(), true)
+ .apply();
+}
+
+void HdrSdrRatioOverlay::changeHdrSdrRatio(float currentHdrSdrRatio) {
+ mCurrentHdrSdrRatio = currentHdrSdrRatio;
+ animate();
+}
+
+void HdrSdrRatioOverlay::setLayerStack(ui::LayerStack stack) {
+ SurfaceComposerClient::Transaction().setLayerStack(mSurfaceControl->get(), stack).apply();
+}
+
+void HdrSdrRatioOverlay::setViewport(ui::Size viewport) {
+ constexpr int32_t kMaxWidth = 1000;
+ const auto width = std::min({kMaxWidth, viewport.width, viewport.height});
+ const auto height = 2 * width;
+ Rect frame((5 * width) >> 4, height >> 5);
+ // set the ratio frame to the top right of the screen
+ frame.offsetBy(viewport.width - frame.width(), height >> 4);
+
+ SurfaceComposerClient::Transaction()
+ .setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth),
+ 0, 0, frame.getHeight() / static_cast<float>(kBufferHeight))
+ .setPosition(mSurfaceControl->get(), frame.left, frame.top)
+ .apply();
+}
+
+auto HdrSdrRatioOverlay::getOrCreateBuffers(float currentHdrSdrRatio) -> const sp<GraphicBuffer> {
+ static const sp<GraphicBuffer> kNoBuffer;
+ if (!mSurfaceControl) return kNoBuffer;
+
+ const auto transformHint =
+ static_cast<ui::Transform::RotationFlags>(mSurfaceControl->get()->getTransformHint());
+
+ // Tell SurfaceFlinger about the pre-rotation on the buffer.
+ const auto transform = [&] {
+ switch (transformHint) {
+ case ui::Transform::ROT_90:
+ return ui::Transform::ROT_270;
+ case ui::Transform::ROT_270:
+ return ui::Transform::ROT_90;
+ default:
+ return ui::Transform::ROT_0;
+ }
+ }();
+
+ SurfaceComposerClient::Transaction().setTransform(mSurfaceControl->get(), transform).apply();
+
+ constexpr SkColor kMinRatioColor = SK_ColorBLUE;
+ constexpr SkColor kMaxRatioColor = SK_ColorGREEN;
+ constexpr float kAlpha = 0.8f;
+
+ // 9.f is picked here as ratio range, given that we assume that
+ // hdr/sdr ratio is [1.f, 10.f)
+ const float scale = currentHdrSdrRatio / 9.f;
+
+ SkColor4f colorBase = SkColor4f::FromColor(kMaxRatioColor) * scale;
+ const SkColor4f minRatioColor = SkColor4f::FromColor(kMinRatioColor) * (1 - scale);
+
+ colorBase.fR = colorBase.fR + minRatioColor.fR;
+ colorBase.fG = colorBase.fG + minRatioColor.fG;
+ colorBase.fB = colorBase.fB + minRatioColor.fB;
+ colorBase.fA = kAlpha;
+
+ const SkColor color = colorBase.toSkColor();
+
+ auto buffer = draw(currentHdrSdrRatio, color, transformHint);
+ return buffer;
+}
+
+void HdrSdrRatioOverlay::animate() {
+ if (!std::isfinite(mCurrentHdrSdrRatio) || mCurrentHdrSdrRatio < 1.0f) return;
+
+ SurfaceComposerClient::Transaction()
+ .setBuffer(mSurfaceControl->get(), getOrCreateBuffers(mCurrentHdrSdrRatio))
+ .apply();
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.h b/services/surfaceflinger/HdrSdrRatioOverlay.h
new file mode 100644
index 0000000..8a2586e
--- /dev/null
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.h
@@ -0,0 +1,45 @@
+/**
+ * Copyright (C) 2023 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 "Utils/OverlayUtils.h"
+
+#include <ui/Size.h>
+#include <utils/StrongPointer.h>
+
+class SkCanvas;
+
+namespace android {
+class HdrSdrRatioOverlay {
+public:
+ HdrSdrRatioOverlay();
+ void setLayerStack(ui::LayerStack);
+ void setViewport(ui::Size);
+ void animate();
+ void changeHdrSdrRatio(float currentRatio);
+
+private:
+ float mCurrentHdrSdrRatio = 1.f;
+
+ static sp<GraphicBuffer> draw(float currentHdrSdrRatio, SkColor, ui::Transform::RotationFlags);
+ static void drawNumber(float number, int left, SkColor, SkCanvas&);
+
+ const sp<GraphicBuffer> getOrCreateBuffers(float currentHdrSdrRatio);
+
+ const std::unique_ptr<SurfaceControlHolder> mSurfaceControl;
+};
+} // namespace android
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 607bec2..577211f 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -16,104 +16,20 @@
#include <algorithm>
-#include "BackgroundExecutor.h"
#include "Client.h"
#include "Layer.h"
#include "RefreshRateOverlay.h"
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#include <SkCanvas.h>
-#include <SkPaint.h>
-#pragma clang diagnostic pop
-#include <SkBlendMode.h>
-#include <SkRect.h>
#include <SkSurface.h>
-#include <gui/SurfaceControl.h>
#undef LOG_TAG
#define LOG_TAG "RefreshRateOverlay"
namespace android {
-namespace {
-constexpr int kDigitWidth = 64;
-constexpr int kDigitHeight = 100;
-constexpr int kDigitSpace = 16;
-
-constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1;
-constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace;
-constexpr int kBufferHeight = kDigitHeight;
-
-} // namespace
-
-SurfaceControlHolder::~SurfaceControlHolder() {
- // Hand the sp<SurfaceControl> to the helper thread to release the last
- // reference. This makes sure that the SurfaceControl is destructed without
- // SurfaceFlinger::mStateLock held.
- BackgroundExecutor::getInstance().sendCallbacks(
- {[sc = std::move(mSurfaceControl)]() mutable { sc.clear(); }});
-}
-
-void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor color,
- SkCanvas& canvas) {
- const SkRect rect = [&]() {
- switch (segment) {
- case Segment::Upper:
- return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
- case Segment::UpperLeft:
- return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2);
- case Segment::UpperRight:
- return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
- kDigitHeight / 2);
- case Segment::Middle:
- return SkRect::MakeLTRB(left, kDigitHeight / 2 - kDigitSpace / 2,
- left + kDigitWidth, kDigitHeight / 2 + kDigitSpace / 2);
- case Segment::LowerLeft:
- return SkRect::MakeLTRB(left, kDigitHeight / 2, left + kDigitSpace, kDigitHeight);
- case Segment::LowerRight:
- return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2,
- left + kDigitWidth, kDigitHeight);
- case Segment::Bottom:
- return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
- kDigitHeight);
- }
- }();
-
- SkPaint paint;
- paint.setColor(color);
- paint.setBlendMode(SkBlendMode::kSrc);
- canvas.drawRect(rect, paint);
-}
-
-void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkColor color,
- SkCanvas& canvas) {
- if (digit < 0 || digit > 9) return;
-
- if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 7 ||
- digit == 8 || digit == 9)
- drawSegment(Segment::Upper, left, color, canvas);
- if (digit == 0 || digit == 4 || digit == 5 || digit == 6 || digit == 8 || digit == 9)
- drawSegment(Segment::UpperLeft, left, color, canvas);
- if (digit == 0 || digit == 1 || digit == 2 || digit == 3 || digit == 4 || digit == 7 ||
- digit == 8 || digit == 9)
- drawSegment(Segment::UpperRight, left, color, canvas);
- if (digit == 2 || digit == 3 || digit == 4 || digit == 5 || digit == 6 || digit == 8 ||
- digit == 9)
- drawSegment(Segment::Middle, left, color, canvas);
- if (digit == 0 || digit == 2 || digit == 6 || digit == 8)
- drawSegment(Segment::LowerLeft, left, color, canvas);
- if (digit == 0 || digit == 1 || digit == 3 || digit == 4 || digit == 5 || digit == 6 ||
- digit == 7 || digit == 8 || digit == 9)
- drawSegment(Segment::LowerRight, left, color, canvas);
- if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 8 ||
- digit == 9)
- drawSegment(Segment::Bottom, left, color, canvas);
-}
-
-auto RefreshRateOverlay::SevenSegmentDrawer::draw(int displayFps, int renderFps, SkColor color,
- ui::Transform::RotationFlags rotation,
- ftl::Flags<Features> features) -> Buffers {
+auto RefreshRateOverlay::draw(int displayFps, int renderFps, SkColor color,
+ ui::Transform::RotationFlags rotation, ftl::Flags<Features> features)
+ -> Buffers {
const size_t loopCount = features.test(Features::Spinner) ? 6 : 1;
Buffers buffers;
@@ -159,22 +75,27 @@
if (features.test(Features::Spinner)) {
switch (i) {
case 0:
- drawSegment(Segment::Upper, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::Upper, left, color, *canvas);
break;
case 1:
- drawSegment(Segment::UpperRight, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::UpperRight, left, color,
+ *canvas);
break;
case 2:
- drawSegment(Segment::LowerRight, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::LowerRight, left, color,
+ *canvas);
break;
case 3:
- drawSegment(Segment::Bottom, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::Bottom, left, color,
+ *canvas);
break;
case 4:
- drawSegment(Segment::LowerLeft, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::LowerLeft, left, color,
+ *canvas);
break;
case 5:
- drawSegment(Segment::UpperLeft, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::UpperLeft, left, color,
+ *canvas);
break;
}
}
@@ -200,34 +121,27 @@
return buffers;
}
-void RefreshRateOverlay::SevenSegmentDrawer::drawNumber(int number, int left, SkColor color,
- SkCanvas& canvas) {
+void RefreshRateOverlay::drawNumber(int number, int left, SkColor color, SkCanvas& canvas) {
if (number < 0 || number >= 1000) return;
if (number >= 100) {
- drawDigit(number / 100, left, color, canvas);
+ SegmentDrawer::drawDigit(number / 100, left, color, canvas);
}
left += kDigitWidth + kDigitSpace;
if (number >= 10) {
- drawDigit((number / 10) % 10, left, color, canvas);
+ SegmentDrawer::drawDigit((number / 10) % 10, left, color, canvas);
}
left += kDigitWidth + kDigitSpace;
- drawDigit(number % 10, left, color, canvas);
-}
-
-std::unique_ptr<SurfaceControlHolder> createSurfaceControlHolder() {
- sp<SurfaceControl> surfaceControl =
- SurfaceComposerClient::getDefault()
- ->createSurface(String8("RefreshRateOverlay"), kBufferWidth, kBufferHeight,
- PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceBufferState);
- return std::make_unique<SurfaceControlHolder>(std::move(surfaceControl));
+ SegmentDrawer::drawDigit(number % 10, left, color, canvas);
}
RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, ftl::Flags<Features> features)
- : mFpsRange(fpsRange), mFeatures(features), mSurfaceControl(createSurfaceControlHolder()) {
+ : mFpsRange(fpsRange),
+ mFeatures(features),
+ mSurfaceControl(
+ SurfaceControlHolder::createSurfaceControlHolder(String8("RefreshRateOverlay"))) {
if (!mSurfaceControl) {
ALOGE("%s: Failed to create buffer state layer", __func__);
return;
@@ -296,8 +210,7 @@
const SkColor color = colorBase.toSkColor();
- auto buffers = SevenSegmentDrawer::draw(displayIntFps, renderIntFps, color, transformHint,
- mFeatures);
+ auto buffers = draw(displayIntFps, renderIntFps, color, transformHint, mFeatures);
it = mBufferCache
.try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers))
.first;
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 0b89b8e..65c61cb 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -16,12 +16,12 @@
#pragma once
-#include <SkColor.h>
+#include "Utils/OverlayUtils.h"
+
#include <vector>
#include <ftl/flags.h>
#include <ftl/small_map.h>
-#include <gui/SurfaceComposerClient.h>
#include <ui/LayerStack.h>
#include <ui/Size.h>
#include <ui/Transform.h>
@@ -34,22 +34,8 @@
namespace android {
class GraphicBuffer;
-class SurfaceControl;
class SurfaceFlinger;
-// Helper class to delete the SurfaceControl on a helper thread as
-// SurfaceControl assumes its destruction happens without SurfaceFlinger::mStateLock held.
-class SurfaceControlHolder {
-public:
- explicit SurfaceControlHolder(sp<SurfaceControl> sc) : mSurfaceControl(std::move(sc)){};
- ~SurfaceControlHolder();
-
- const sp<SurfaceControl>& get() const { return mSurfaceControl; }
-
-private:
- sp<SurfaceControl> mSurfaceControl;
-};
-
class RefreshRateOverlay {
public:
enum class Features {
@@ -70,18 +56,9 @@
private:
using Buffers = std::vector<sp<GraphicBuffer>>;
- class SevenSegmentDrawer {
- public:
- static Buffers draw(int displayFps, int renderFps, SkColor, ui::Transform::RotationFlags,
- ftl::Flags<Features>);
-
- private:
- enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom };
-
- static void drawSegment(Segment, int left, SkColor, SkCanvas&);
- static void drawDigit(int digit, int left, SkColor, SkCanvas&);
- static void drawNumber(int number, int left, SkColor, SkCanvas&);
- };
+ static Buffers draw(int displayFps, int renderFps, SkColor, ui::Transform::RotationFlags,
+ ftl::Flags<Features>);
+ static void drawNumber(int number, int left, SkColor, SkCanvas&);
const Buffers& getOrCreateBuffers(Fps, Fps);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index c46da11..8646c0c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1963,6 +1963,11 @@
FTL_FAKE_GUARD(kMainThreadContext,
display->stageBrightness(brightness.displayBrightness));
+ float currentHdrSdrRatio =
+ compositionDisplay->editState().displayBrightnessNits /
+ compositionDisplay->editState().sdrWhitePointNits;
+ FTL_FAKE_GUARD(kMainThreadContext,
+ display->updateHdrSdrRatioOverlayRatio(currentHdrSdrRatio));
if (brightness.sdrWhitePointNits / brightness.displayBrightnessNits !=
currentDimmingRatio) {
@@ -2454,10 +2459,10 @@
mPowerAdvisor->updateTargetWorkDuration(idealVsyncPeriod);
}
- if (mRefreshRateOverlaySpinner) {
+ if (mRefreshRateOverlaySpinner || mHdrSdrRatioOverlay) {
Mutex::Autolock lock(mStateLock);
if (const auto display = getDefaultDisplayDeviceLocked()) {
- display->animateRefreshRateOverlay();
+ display->animateOverlay();
}
}
@@ -6462,9 +6467,9 @@
code == IBinder::SYSPROPS_TRANSACTION) {
return OK;
}
- // Numbers from 1000 to 1042 are currently used for backdoors. The code
+ // Numbers from 1000 to 1043 are currently used for backdoors. The code
// in onTransact verifies that the user is root, and has access to use SF.
- if (code >= 1000 && code <= 1042) {
+ if (code >= 1000 && code <= 1043) {
ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
return OK;
}
@@ -6925,6 +6930,24 @@
reply->writeInt32(NO_ERROR);
return NO_ERROR;
}
+ // hdr sdr ratio overlay
+ case 1043: {
+ auto future = mScheduler->schedule(
+ [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+ n = data.readInt32();
+ mHdrSdrRatioOverlay = n != 0;
+ switch (n) {
+ case 0:
+ case 1:
+ enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay);
+ break;
+ default:
+ reply->writeBool(isHdrSdrRatioOverlayEnabled());
+ }
+ });
+ future.wait();
+ return NO_ERROR;
+ }
}
}
return err;
@@ -8023,6 +8046,16 @@
}
}
+void SurfaceFlinger::enableHdrSdrRatioOverlay(bool enable) {
+ for (const auto& [id, display] : mPhysicalDisplays) {
+ if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
+ if (const auto device = getDisplayDeviceLocked(id)) {
+ device->enableHdrSdrRatioOverlay(enable);
+ }
+ }
+ }
+}
+
int SurfaceFlinger::getGpuContextPriority() {
return getRenderEngine().getContextPriority();
}
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index d97a747..49aff14 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -672,6 +672,8 @@
bool mRefreshRateOverlayRenderRate = false;
// Show render rate overlay offseted to the middle of the screen (e.g. for circular displays)
bool mRefreshRateOverlayShowInMiddle = false;
+ // Show hdr sdr ratio overlay
+ bool mHdrSdrRatioOverlay = false;
void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
REQUIRES(mStateLock);
@@ -1288,7 +1290,6 @@
ui::Dataspace mDefaultCompositionDataspace;
ui::Dataspace mWideColorGamutCompositionDataspace;
ui::Dataspace mColorSpaceAgnosticDataspace;
- float mDimmingRatio = -1.f;
std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
std::atomic<int> mNumTrustedPresentationListeners = 0;
@@ -1336,6 +1337,8 @@
void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext);
+ void enableHdrSdrRatioOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext);
+
// Flag used to set override desired display mode from backdoor
bool mDebugDisplayModeSetByBackdoor = false;
@@ -1381,6 +1384,10 @@
return hasDisplay(
[](const auto& display) { return display.isRefreshRateOverlayEnabled(); });
}
+ bool isHdrSdrRatioOverlayEnabled() const REQUIRES(mStateLock) {
+ return hasDisplay(
+ [](const auto& display) { return display.isHdrSdrRatioOverlayEnabled(); });
+ }
std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
std::optional<ui::LayerStack> layerStack, uint32_t uid,
std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)>
diff --git a/services/surfaceflinger/Utils/OverlayUtils.h b/services/surfaceflinger/Utils/OverlayUtils.h
new file mode 100644
index 0000000..0ef0be1
--- /dev/null
+++ b/services/surfaceflinger/Utils/OverlayUtils.h
@@ -0,0 +1,146 @@
+/**
+ * Copyright (C) 2023 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.
+ */
+
+// #define LOG_NDEBUG 0
+#pragma once
+
+#include "BackgroundExecutor.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#pragma clang diagnostic pop
+
+#include <gui/SurfaceComposerClient.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+inline constexpr int kDigitWidth = 64;
+inline constexpr int kDigitHeight = 100;
+inline constexpr int kDigitSpace = 16;
+
+// HdrSdrRatioOverlay re-uses this value though it doesn't really need such amount buffer.
+// for output good-looking and code conciseness.
+inline constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1;
+inline constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace;
+inline constexpr int kBufferHeight = kDigitHeight;
+
+class SurfaceControl;
+
+// Helper class to delete the SurfaceControl on a helper thread as
+// SurfaceControl assumes its destruction happens without SurfaceFlinger::mStateLock held.
+class SurfaceControlHolder {
+public:
+ explicit SurfaceControlHolder(sp<SurfaceControl> sc) : mSurfaceControl(std::move(sc)){};
+
+ ~SurfaceControlHolder() {
+ // Hand the sp<SurfaceControl> to the helper thread to release the last
+ // reference. This makes sure that the SurfaceControl is destructed without
+ // SurfaceFlinger::mStateLock held.
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[sc = std::move(mSurfaceControl)]() mutable { sc.clear(); }});
+ }
+
+ static std::unique_ptr<SurfaceControlHolder> createSurfaceControlHolder(const String8& name) {
+ sp<SurfaceControl> surfaceControl =
+ SurfaceComposerClient::getDefault()
+ ->createSurface(name, kBufferWidth, kBufferHeight, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState);
+ return std::make_unique<SurfaceControlHolder>(std::move(surfaceControl));
+ }
+
+ const sp<SurfaceControl>& get() const { return mSurfaceControl; }
+
+private:
+ sp<SurfaceControl> mSurfaceControl;
+};
+
+// Helper class to draw digit and decimal point.
+class SegmentDrawer {
+public:
+ enum class Segment {
+ Upper,
+ UpperLeft,
+ UpperRight,
+ Middle,
+ LowerLeft,
+ LowerRight,
+ Bottom,
+ DecimalPoint
+ };
+ static void drawSegment(Segment segment, int left, SkColor color, SkCanvas& canvas) {
+ const SkRect rect = [&]() {
+ switch (segment) {
+ case Segment::Upper:
+ return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
+ case Segment::UpperLeft:
+ return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2.);
+ case Segment::UpperRight:
+ return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
+ kDigitHeight / 2.);
+ case Segment::Middle:
+ return SkRect::MakeLTRB(left, kDigitHeight / 2. - kDigitSpace / 2.,
+ left + kDigitWidth,
+ kDigitHeight / 2. + kDigitSpace / 2.);
+ case Segment::LowerLeft:
+ return SkRect::MakeLTRB(left, kDigitHeight / 2., left + kDigitSpace,
+ kDigitHeight);
+ case Segment::LowerRight:
+ return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2.,
+ left + kDigitWidth, kDigitHeight);
+ case Segment::Bottom:
+ return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
+ kDigitHeight);
+ case Segment::DecimalPoint:
+ return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitSpace,
+ kDigitHeight);
+ }
+ }();
+
+ SkPaint paint;
+ paint.setColor(color);
+ paint.setBlendMode(SkBlendMode::kSrc);
+ canvas.drawRect(rect, paint);
+ }
+
+ static void drawDigit(int digit, int left, SkColor color, SkCanvas& canvas) {
+ if (digit < 0 || digit > 9) return;
+
+ if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 7 ||
+ digit == 8 || digit == 9)
+ drawSegment(Segment::Upper, left, color, canvas);
+ if (digit == 0 || digit == 4 || digit == 5 || digit == 6 || digit == 8 || digit == 9)
+ drawSegment(Segment::UpperLeft, left, color, canvas);
+ if (digit == 0 || digit == 1 || digit == 2 || digit == 3 || digit == 4 || digit == 7 ||
+ digit == 8 || digit == 9)
+ drawSegment(Segment::UpperRight, left, color, canvas);
+ if (digit == 2 || digit == 3 || digit == 4 || digit == 5 || digit == 6 || digit == 8 ||
+ digit == 9)
+ drawSegment(Segment::Middle, left, color, canvas);
+ if (digit == 0 || digit == 2 || digit == 6 || digit == 8)
+ drawSegment(Segment::LowerLeft, left, color, canvas);
+ if (digit == 0 || digit == 1 || digit == 3 || digit == 4 || digit == 5 || digit == 6 ||
+ digit == 7 || digit == 8 || digit == 9)
+ drawSegment(Segment::LowerRight, left, color, canvas);
+ if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 8 ||
+ digit == 9)
+ drawSegment(Segment::Bottom, left, color, canvas);
+ }
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 62b539a..b5168b0 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -37,8 +37,9 @@
"DisplayConfigs_test.cpp",
"DisplayEventReceiver_test.cpp",
"EffectLayer_test.cpp",
- "LayerBorder_test.cpp",
+ "HdrSdrRatioOverlay_test.cpp",
"InvalidHandles_test.cpp",
+ "LayerBorder_test.cpp",
"LayerCallback_test.cpp",
"LayerRenderTypeTransaction_test.cpp",
"LayerState_test.cpp",
diff --git a/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp b/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp
new file mode 100644
index 0000000..77a8f9c
--- /dev/null
+++ b/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp
@@ -0,0 +1,89 @@
+/**
+ * Copyright (C) 2023 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 <thread>
+
+#include <gtest/gtest.h>
+
+#include <gui/SurfaceComposerClient.h>
+#include <private/gui/ComposerService.h>
+#include <chrono>
+
+using ::std::literals::chrono_literals::operator""s;
+
+static constexpr int kHdrSdrRatioOverlayCode = 1043;
+static constexpr int kHdrSdrRatioOverlayEnable = 1;
+static constexpr int kHdrSdrRatioOverlayDisable = 0;
+static constexpr int kHdrSdrRatioOverlayQuery = 2;
+
+// These values must match the ones we used for developer options in
+// com.android.settings.development.ShowHdrSdrRatioPreferenceController
+static_assert(kHdrSdrRatioOverlayCode == 1043);
+static_assert(kHdrSdrRatioOverlayEnable == 1);
+static_assert(kHdrSdrRatioOverlayDisable == 0);
+static_assert(kHdrSdrRatioOverlayQuery == 2);
+
+namespace android {
+
+namespace {
+void sendCommandToSf(int command, Parcel& reply) {
+ sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+ Parcel request;
+ request.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+ request.writeInt32(command);
+ ASSERT_EQ(NO_ERROR,
+ IInterface::asBinder(sf)->transact(kHdrSdrRatioOverlayCode, request, &reply));
+}
+
+bool isOverlayEnabled() {
+ Parcel reply;
+ sendCommandToSf(kHdrSdrRatioOverlayQuery, reply);
+ return reply.readBool();
+}
+
+void waitForOverlay(bool enabled) {
+ static constexpr auto kTimeout = std::chrono::nanoseconds(1s);
+ static constexpr auto kIterations = 10;
+ for (int i = 0; i < kIterations; i++) {
+ if (enabled == isOverlayEnabled()) {
+ return;
+ }
+ std::this_thread::sleep_for(kTimeout / kIterations);
+ }
+}
+
+void toggleOverlay(bool enabled) {
+ if (enabled == isOverlayEnabled()) {
+ return;
+ }
+
+ Parcel reply;
+ const auto command = enabled ? kHdrSdrRatioOverlayEnable : kHdrSdrRatioOverlayDisable;
+ sendCommandToSf(command, reply);
+ waitForOverlay(enabled);
+ ASSERT_EQ(enabled, isOverlayEnabled());
+}
+
+} // namespace
+
+TEST(HdrSdrRatioOverlayTest, enableAndDisableOverlay) {
+ toggleOverlay(true);
+ toggleOverlay(false);
+
+ toggleOverlay(true);
+ toggleOverlay(false);
+}
+
+} // namespace android