Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8c4356c0 authored by Dominik Laskowski's avatar Dominik Laskowski
Browse files

SF: Clean up RefreshRateOverlay

Flatten buffer cache, remove unused members, and fix conversion
warnings.

Skip animation transactions unless spinner is enabled.

Bug: 185535769
Bug: 129481165
Test: Apply follow-up fix and toggle overlay.
Change-Id: I14688f7b5d882f595322dfadd5cabbd5a8564301
parent 4d5052d7
Loading
Loading
Loading
Loading
+2 −3
Original line number Original line Diff line number Diff line
@@ -471,9 +471,8 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) {
        return;
        return;
    }
    }


    const auto [lowFps, highFps] = mRefreshRateConfigs->getSupportedRefreshRateRange();
    const auto fpsRange = mRefreshRateConfigs->getSupportedRefreshRateRange();
    mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(*mFlinger, lowFps.getIntValue(),
    mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, showSpinnner);
                                                               highFps.getIntValue(), showSpinnner);
    mRefreshRateOverlay->setLayerStack(getLayerStack());
    mRefreshRateOverlay->setLayerStack(getLayerStack());
    mRefreshRateOverlay->setViewport(getSize());
    mRefreshRateOverlay->setViewport(getSize());
    mRefreshRateOverlay->changeRefreshRate(getActiveMode()->getFps());
    mRefreshRateOverlay->changeRefreshRate(getActiveMode()->getFps());
+113 −93
Original line number Original line Diff line number Diff line
@@ -14,22 +14,20 @@
 * limitations under the License.
 * limitations under the License.
 */
 */


// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wextra"

#include <algorithm>
#include <algorithm>


#include "RefreshRateOverlay.h"
#include "RefreshRateOverlay.h"
#include "Client.h"
#include "Client.h"
#include "Layer.h"
#include "Layer.h"


#include <SkBlendMode.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
#include <SkCanvas.h>
#include <SkPaint.h>
#include <SkPaint.h>
#pragma clang diagnostic pop
#include <SkBlendMode.h>
#include <SkRect.h>
#include <SkRect.h>
#include <SkSurface.h>
#include <SkSurface.h>
#include <gui/IProducerListener.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SurfaceControl.h>
#include <gui/SurfaceControl.h>


@@ -37,29 +35,40 @@
#define LOG_TAG "RefreshRateOverlay"
#define LOG_TAG "RefreshRateOverlay"


namespace android {
namespace android {
namespace {

constexpr int kDigitWidth = 64;
constexpr int kDigitHeight = 100;
constexpr int kDigitSpace = 16;


void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor& color,
// Layout is digit, space, digit, space, digit, space, spinner.
constexpr int kBufferWidth = 4 * kDigitWidth + 3 * kDigitSpace;
constexpr int kBufferHeight = kDigitHeight;

} // namespace

void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor color,
                                                         SkCanvas& canvas) {
                                                         SkCanvas& canvas) {
    const SkRect rect = [&]() {
    const SkRect rect = [&]() {
        switch (segment) {
        switch (segment) {
            case Segment::Upper:
            case Segment::Upper:
                return SkRect::MakeLTRB(left, 0, left + DIGIT_WIDTH, DIGIT_SPACE);
                return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
            case Segment::UpperLeft:
            case Segment::UpperLeft:
                return SkRect::MakeLTRB(left, 0, left + DIGIT_SPACE, DIGIT_HEIGHT / 2);
                return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2);
            case Segment::UpperRight:
            case Segment::UpperRight:
                return SkRect::MakeLTRB(left + DIGIT_WIDTH - DIGIT_SPACE, 0, left + DIGIT_WIDTH,
                return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
                                        DIGIT_HEIGHT / 2);
                                        kDigitHeight / 2);
            case Segment::Middle:
            case Segment::Middle:
                return SkRect::MakeLTRB(left, DIGIT_HEIGHT / 2 - DIGIT_SPACE / 2,
                return SkRect::MakeLTRB(left, kDigitHeight / 2 - kDigitSpace / 2,
                                        left + DIGIT_WIDTH, DIGIT_HEIGHT / 2 + DIGIT_SPACE / 2);
                                        left + kDigitWidth, kDigitHeight / 2 + kDigitSpace / 2);
            case Segment::LowerLeft:
            case Segment::LowerLeft:
                return SkRect::MakeLTRB(left, DIGIT_HEIGHT / 2, left + DIGIT_SPACE, DIGIT_HEIGHT);
                return SkRect::MakeLTRB(left, kDigitHeight / 2, left + kDigitSpace, kDigitHeight);
            case Segment::LowerRight:
            case Segment::LowerRight:
                return SkRect::MakeLTRB(left + DIGIT_WIDTH - DIGIT_SPACE, DIGIT_HEIGHT / 2,
                return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2,
                                        left + DIGIT_WIDTH, DIGIT_HEIGHT);
                                        left + kDigitWidth, kDigitHeight);
            case Segment::Bottom:
            case Segment::Bottom:
                return SkRect::MakeLTRB(left, DIGIT_HEIGHT - DIGIT_SPACE, left + DIGIT_WIDTH,
                return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
                                        DIGIT_HEIGHT);
                                        kDigitHeight);
        }
        }
    }();
    }();


@@ -69,7 +78,7 @@ void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int le
    canvas.drawRect(rect, paint);
    canvas.drawRect(rect, paint);
}
}


void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkColor& color,
void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkColor color,
                                                       SkCanvas& canvas) {
                                                       SkCanvas& canvas) {
    if (digit < 0 || digit > 9) return;
    if (digit < 0 || digit > 9) return;


@@ -94,37 +103,45 @@ void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkCo
        drawSegment(Segment::Bottom, left, color, canvas);
        drawSegment(Segment::Bottom, left, color, canvas);
}
}


std::vector<sp<GraphicBuffer>> RefreshRateOverlay::SevenSegmentDrawer::draw(
auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color,
        int number, SkColor& color, ui::Transform::RotationFlags rotation, bool showSpinner) {
                                                  ui::Transform::RotationFlags rotation,
                                                  bool showSpinner) -> Buffers {
    if (number < 0 || number > 1000) return {};
    if (number < 0 || number > 1000) return {};


    const auto hundreds = number / 100;
    const auto hundreds = number / 100;
    const auto tens = (number / 10) % 10;
    const auto tens = (number / 10) % 10;
    const auto ones = number % 10;
    const auto ones = number % 10;


    std::vector<sp<GraphicBuffer>> buffers;
    const size_t loopCount = showSpinner ? 6 : 1;
    const auto loopCount = showSpinner ? 6 : 1;

    for (int i = 0; i < loopCount; i++) {
    Buffers buffers;
    buffers.reserve(loopCount);

    for (size_t i = 0; i < loopCount; i++) {
        // Pre-rotate the buffer before it reaches SurfaceFlinger.
        // Pre-rotate the buffer before it reaches SurfaceFlinger.
        SkMatrix canvasTransform = SkMatrix();
        SkMatrix canvasTransform = SkMatrix();
        auto [bufferWidth, bufferHeight] = [&] {
        const auto [bufferWidth, bufferHeight] = [&]() -> std::pair<int, int> {
            switch (rotation) {
            switch (rotation) {
                case ui::Transform::ROT_90:
                case ui::Transform::ROT_90:
                    canvasTransform.setTranslate(BUFFER_HEIGHT, 0);
                    canvasTransform.setTranslate(kBufferHeight, 0);
                    canvasTransform.preRotate(90);
                    canvasTransform.preRotate(90.f);
                    return std::make_tuple(BUFFER_HEIGHT, BUFFER_WIDTH);
                    return {kBufferHeight, kBufferWidth};
                case ui::Transform::ROT_270:
                case ui::Transform::ROT_270:
                    canvasTransform.setRotate(270, BUFFER_WIDTH / 2.0, BUFFER_WIDTH / 2.0);
                    canvasTransform.setRotate(270.f, kBufferWidth / 2.f, kBufferWidth / 2.f);
                    return std::make_tuple(BUFFER_HEIGHT, BUFFER_WIDTH);
                    return {kBufferHeight, kBufferWidth};
                default:
                default:
                    return std::make_tuple(BUFFER_WIDTH, BUFFER_HEIGHT);
                    return {kBufferWidth, kBufferHeight};
            }
            }
        }();
        }();

        sp<GraphicBuffer> buffer =
        sp<GraphicBuffer> buffer =
                new GraphicBuffer(bufferWidth, bufferHeight, HAL_PIXEL_FORMAT_RGBA_8888, 1,
                new GraphicBuffer(static_cast<uint32_t>(bufferWidth),
                                  static_cast<uint32_t>(bufferHeight), HAL_PIXEL_FORMAT_RGBA_8888,
                                  1,
                                  GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER |
                                  GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER |
                                          GRALLOC_USAGE_HW_TEXTURE,
                                          GRALLOC_USAGE_HW_TEXTURE,
                                  "RefreshRateOverlayBuffer");
                                  "RefreshRateOverlayBuffer");

        const status_t bufferStatus = buffer->initCheck();
        const status_t bufferStatus = buffer->initCheck();
        LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "RefreshRateOverlay: Buffer failed to allocate: %d",
        LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "RefreshRateOverlay: Buffer failed to allocate: %d",
                            bufferStatus);
                            bufferStatus);
@@ -137,15 +154,15 @@ std::vector<sp<GraphicBuffer>> RefreshRateOverlay::SevenSegmentDrawer::draw(
        if (hundreds != 0) {
        if (hundreds != 0) {
            drawDigit(hundreds, left, color, *canvas);
            drawDigit(hundreds, left, color, *canvas);
        }
        }
        left += DIGIT_WIDTH + DIGIT_SPACE;
        left += kDigitWidth + kDigitSpace;


        if (tens != 0) {
        if (tens != 0) {
            drawDigit(tens, left, color, *canvas);
            drawDigit(tens, left, color, *canvas);
        }
        }
        left += DIGIT_WIDTH + DIGIT_SPACE;
        left += kDigitWidth + kDigitSpace;


        drawDigit(ones, left, color, *canvas);
        drawDigit(ones, left, color, *canvas);
        left += DIGIT_WIDTH + DIGIT_SPACE;
        left += kDigitWidth + kDigitSpace;


        if (showSpinner) {
        if (showSpinner) {
            switch (i) {
            switch (i) {
@@ -172,51 +189,48 @@ std::vector<sp<GraphicBuffer>> RefreshRateOverlay::SevenSegmentDrawer::draw(


        void* pixels = nullptr;
        void* pixels = nullptr;
        buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&pixels));
        buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&pixels));

        const SkImageInfo& imageInfo = surface->imageInfo();
        const SkImageInfo& imageInfo = surface->imageInfo();
        size_t dstRowBytes = buffer->getStride() * imageInfo.bytesPerPixel();
        const size_t dstRowBytes =
                buffer->getStride() * static_cast<size_t>(imageInfo.bytesPerPixel());

        canvas->readPixels(imageInfo, pixels, dstRowBytes, 0, 0);
        canvas->readPixels(imageInfo, pixels, dstRowBytes, 0, 0);
        buffer->unlock();
        buffer->unlock();
        buffers.emplace_back(buffer);
        buffers.push_back(std::move(buffer));
    }
    }
    return buffers;
    return buffers;
}
}


RefreshRateOverlay::RefreshRateOverlay(SurfaceFlinger& flinger, uint32_t lowFps, uint32_t highFps,
RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, bool showSpinner)
                                       bool showSpinner)
      : mFpsRange(fpsRange),
      : mFlinger(flinger),
        mClient(new Client(&mFlinger)),
        mShowSpinner(showSpinner),
        mShowSpinner(showSpinner),
        mLowFps(lowFps),
        mSurfaceControl(SurfaceComposerClient::getDefault()
        mHighFps(highFps) {
                                ->createSurface(String8("RefreshRateOverlay"), kBufferWidth,
    createLayer();
                                                kBufferHeight, PIXEL_FORMAT_RGBA_8888,
}
                                                ISurfaceComposerClient::eFXSurfaceBufferState)) {

bool RefreshRateOverlay::createLayer() {
    mSurfaceControl =
            SurfaceComposerClient::getDefault()
                    ->createSurface(String8("RefreshRateOverlay"), SevenSegmentDrawer::getWidth(),
                                    SevenSegmentDrawer::getHeight(), PIXEL_FORMAT_RGBA_8888,
                                    ISurfaceComposerClient::eFXSurfaceBufferState);

    if (!mSurfaceControl) {
    if (!mSurfaceControl) {
        ALOGE("failed to create buffer state layer");
        ALOGE("%s: Failed to create buffer state layer", __func__);
        return false;
        return;
    }
    }


    constexpr float kFrameRate = 0.f;
    constexpr int8_t kCompatibility = static_cast<int8_t>(Layer::FrameRateCompatibility::NoVote);
    constexpr int8_t kSeamlessness = ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS;

    SurfaceComposerClient::Transaction()
    SurfaceComposerClient::Transaction()
            .setFrameRate(mSurfaceControl, 0.0f,
            .setFrameRate(mSurfaceControl, kFrameRate, kCompatibility, kSeamlessness)
                          static_cast<int8_t>(Layer::FrameRateCompatibility::NoVote),
                          static_cast<int8_t>(scheduler::Seamlessness::OnlySeamless))
            .setLayer(mSurfaceControl, INT32_MAX - 2)
            .setLayer(mSurfaceControl, INT32_MAX - 2)
            .setTrustedOverlay(mSurfaceControl, true)
            .setTrustedOverlay(mSurfaceControl, true)
            .apply();
            .apply();

    return true;
}
}


const std::vector<sp<GraphicBuffer>>& RefreshRateOverlay::getOrCreateBuffers(uint32_t fps) {
auto RefreshRateOverlay::getOrCreateBuffers(Fps fps) -> const Buffers& {
    ui::Transform::RotationFlags transformHint =
    static const Buffers kNoBuffers;
    if (!mSurfaceControl) return kNoBuffers;

    const auto transformHint =
            static_cast<ui::Transform::RotationFlags>(mSurfaceControl->getTransformHint());
            static_cast<ui::Transform::RotationFlags>(mSurfaceControl->getTransformHint());

    // Tell SurfaceFlinger about the pre-rotation on the buffer.
    // Tell SurfaceFlinger about the pre-rotation on the buffer.
    const auto transform = [&] {
    const auto transform = [&] {
        switch (transformHint) {
        switch (transformHint) {
@@ -233,40 +247,49 @@ const std::vector<sp<GraphicBuffer>>& RefreshRateOverlay::getOrCreateBuffers(uin
    t.setTransform(mSurfaceControl, transform);
    t.setTransform(mSurfaceControl, transform);
    t.apply();
    t.apply();


    if (mBufferCache.find(transformHint) == mBufferCache.end() ||
    BufferCache::const_iterator it = mBufferCache.find({fps.getIntValue(), transformHint});
        mBufferCache.at(transformHint).find(fps) == mBufferCache.at(transformHint).end()) {
    if (it == mBufferCache.end()) {
        // Ensure the range is > 0, so we don't divide by 0.
        const int minFps = mFpsRange.min.getIntValue();
        const auto rangeLength = std::max(1u, mHighFps - mLowFps);
        const int maxFps = mFpsRange.max.getIntValue();
        // Clip values outside the range [mLowFps, mHighFps]. The current fps may be outside

        // of this range if the display has changed its set of supported refresh rates.
        // Clamp to the range. The current fps may be outside of this range if the display has
        fps = std::max(fps, mLowFps);
        // changed its set of supported refresh rates.
        fps = std::min(fps, mHighFps);
        const int intFps = std::clamp(fps.getIntValue(), minFps, maxFps);
        const auto fpsScale = static_cast<float>(fps - mLowFps) / rangeLength;

        SkColor4f colorBase = SkColor4f::FromColor(HIGH_FPS_COLOR) * fpsScale;
        // Ensure non-zero range to avoid division by zero.
        SkColor4f lowFpsColor = SkColor4f::FromColor(LOW_FPS_COLOR) * (1 - fpsScale);
        const float fpsScale = static_cast<float>(intFps - minFps) / std::max(1, maxFps - minFps);
        colorBase.fR = colorBase.fR + lowFpsColor.fR;

        colorBase.fG = colorBase.fG + lowFpsColor.fG;
        constexpr SkColor kMinFpsColor = SK_ColorRED;
        colorBase.fB = colorBase.fB + lowFpsColor.fB;
        constexpr SkColor kMaxFpsColor = SK_ColorGREEN;
        colorBase.fA = ALPHA;
        constexpr float kAlpha = 0.8f;
        SkColor color = colorBase.toSkColor();

        auto buffers = SevenSegmentDrawer::draw(fps, color, transformHint, mShowSpinner);
        SkColor4f colorBase = SkColor4f::FromColor(kMaxFpsColor) * fpsScale;
        mBufferCache[transformHint].emplace(fps, buffers);
        const SkColor4f minFpsColor = SkColor4f::FromColor(kMinFpsColor) * (1 - fpsScale);

        colorBase.fR = colorBase.fR + minFpsColor.fR;
        colorBase.fG = colorBase.fG + minFpsColor.fG;
        colorBase.fB = colorBase.fB + minFpsColor.fB;
        colorBase.fA = kAlpha;

        const SkColor color = colorBase.toSkColor();

        auto buffers = SevenSegmentDrawer::draw(intFps, color, transformHint, mShowSpinner);
        it = mBufferCache.try_emplace({intFps, transformHint}, std::move(buffers)).first;
    }
    }


    return mBufferCache[transformHint][fps];
    return it->second;
}
}


void RefreshRateOverlay::setViewport(ui::Size viewport) {
void RefreshRateOverlay::setViewport(ui::Size viewport) {
    constexpr int32_t kMaxWidth = 1000;
    constexpr int32_t kMaxWidth = 1000;
    const auto width = std::min(kMaxWidth, std::min(viewport.width, viewport.height));
    const auto width = std::min({kMaxWidth, viewport.width, viewport.height});
    const auto height = 2 * width;
    const auto height = 2 * width;
    Rect frame((3 * width) >> 4, height >> 5);
    Rect frame((3 * width) >> 4, height >> 5);
    frame.offsetBy(width >> 5, height >> 4);
    frame.offsetBy(width >> 5, height >> 4);


    SurfaceComposerClient::Transaction t;
    SurfaceComposerClient::Transaction t;
    t.setMatrix(mSurfaceControl,
    t.setMatrix(mSurfaceControl, frame.getWidth() / static_cast<float>(kBufferWidth), 0, 0,
                frame.getWidth() / static_cast<float>(SevenSegmentDrawer::getWidth()), 0, 0,
                frame.getHeight() / static_cast<float>(kBufferHeight));
                frame.getHeight() / static_cast<float>(SevenSegmentDrawer::getHeight()));
    t.setPosition(mSurfaceControl, frame.left, frame.top);
    t.setPosition(mSurfaceControl, frame.left, frame.top);
    t.apply();
    t.apply();
}
}
@@ -278,25 +301,22 @@ void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) {
}
}


void RefreshRateOverlay::changeRefreshRate(Fps fps) {
void RefreshRateOverlay::changeRefreshRate(Fps fps) {
    mCurrentFps = fps.getIntValue();
    mCurrentFps = fps;
    auto buffer = getOrCreateBuffers(*mCurrentFps)[mFrame];
    const auto buffer = getOrCreateBuffers(fps)[mFrame];
    SurfaceComposerClient::Transaction t;
    SurfaceComposerClient::Transaction t;
    t.setBuffer(mSurfaceControl, buffer);
    t.setBuffer(mSurfaceControl, buffer);
    t.apply();
    t.apply();
}
}


void RefreshRateOverlay::animate() {
void RefreshRateOverlay::animate() {
    if (!mCurrentFps.has_value()) return;
    if (!mShowSpinner || !mCurrentFps) return;


    const auto& buffers = getOrCreateBuffers(*mCurrentFps);
    const auto& buffers = getOrCreateBuffers(*mCurrentFps);
    mFrame = (mFrame + 1) % buffers.size();
    mFrame = (mFrame + 1) % buffers.size();
    auto buffer = buffers[mFrame];
    const auto buffer = buffers[mFrame];
    SurfaceComposerClient::Transaction t;
    SurfaceComposerClient::Transaction t;
    t.setBuffer(mSurfaceControl, buffer);
    t.setBuffer(mSurfaceControl, buffer);
    t.apply();
    t.apply();
}
}


} // namespace android
} // namespace android

// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
+24 −43
Original line number Original line Diff line number Diff line
@@ -16,32 +16,27 @@


#pragma once
#pragma once


#include <SkCanvas.h>
#include <SkColor.h>
#include <SkColor.h>
#include <unordered_map>
#include <vector>


#include <math/vec4.h>
#include <ftl/small_map.h>
#include <renderengine/RenderEngine.h>
#include <ui/LayerStack.h>
#include <ui/LayerStack.h>
#include <ui/Rect.h>
#include <ui/Size.h>
#include <ui/Size.h>
#include <ui/Transform.h>
#include <utils/StrongPointer.h>
#include <utils/StrongPointer.h>


#include <scheduler/Fps.h>
#include <scheduler/Fps.h>


class SkCanvas;

namespace android {
namespace android {


class Client;
class GraphicBuffer;
class GraphicBuffer;
class IBinder;
class IGraphicBufferProducer;
class Layer;
class SurfaceFlinger;
class SurfaceControl;
class SurfaceControl;


class RefreshRateOverlay {
class RefreshRateOverlay {
public:
public:
    RefreshRateOverlay(SurfaceFlinger&, uint32_t lowFps, uint32_t highFps, bool showSpinner);
    RefreshRateOverlay(FpsRange, bool showSpinner);


    void setLayerStack(ui::LayerStack);
    void setLayerStack(ui::LayerStack);
    void setViewport(ui::Size);
    void setViewport(ui::Size);
@@ -49,52 +44,38 @@ public:
    void animate();
    void animate();


private:
private:
    using Buffers = std::vector<sp<GraphicBuffer>>;

    class SevenSegmentDrawer {
    class SevenSegmentDrawer {
    public:
    public:
        static std::vector<sp<GraphicBuffer>> draw(int number, SkColor& color,
        static Buffers draw(int number, SkColor, ui::Transform::RotationFlags, bool showSpinner);
                                                   ui::Transform::RotationFlags, bool showSpinner);
        static uint32_t getHeight() { return BUFFER_HEIGHT; }
        static uint32_t getWidth() { return BUFFER_WIDTH; }


    private:
    private:
        enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom };
        enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom };


        static void drawSegment(Segment segment, int left, SkColor& color, SkCanvas& canvas);
        static void drawSegment(Segment, int left, SkColor, SkCanvas&);
        static void drawDigit(int digit, int left, SkColor& color, SkCanvas& canvas);
        static void drawDigit(int digit, int left, SkColor, SkCanvas&);

        static constexpr uint32_t DIGIT_HEIGHT = 100;
        static constexpr uint32_t DIGIT_WIDTH = 64;
        static constexpr uint32_t DIGIT_SPACE = 16;
        static constexpr uint32_t BUFFER_HEIGHT = DIGIT_HEIGHT;
        static constexpr uint32_t BUFFER_WIDTH =
                4 * DIGIT_WIDTH + 3 * DIGIT_SPACE; // Digit|Space|Digit|Space|Digit|Space|Spinner
    };
    };


    bool createLayer();
    const Buffers& getOrCreateBuffers(Fps);


    const std::vector<sp<GraphicBuffer>>& getOrCreateBuffers(uint32_t fps);
    struct Key {
        int fps;
        ui::Transform::RotationFlags flags;


    SurfaceFlinger& mFlinger;
        bool operator==(Key other) const { return fps == other.fps && flags == other.flags; }
    const sp<Client> mClient;
    };
    sp<IBinder> mIBinder;
    sp<IGraphicBufferProducer> mGbp;


    std::unordered_map<ui::Transform::RotationFlags,
    using BufferCache = ftl::SmallMap<Key, Buffers, 9>;
                       std::unordered_map<int, std::vector<sp<GraphicBuffer>>>>
    BufferCache mBufferCache;
            mBufferCache;
    std::optional<int> mCurrentFps;
    int mFrame = 0;
    static constexpr float ALPHA = 0.8f;
    const SkColor LOW_FPS_COLOR = SK_ColorRED;
    const SkColor HIGH_FPS_COLOR = SK_ColorGREEN;


    const bool mShowSpinner;
    std::optional<Fps> mCurrentFps;
    size_t mFrame = 0;


    // Interpolate the colors between these values.
    const FpsRange mFpsRange; // For color interpolation.
    const uint32_t mLowFps;
    const bool mShowSpinner;
    const uint32_t mHighFps;


    sp<SurfaceControl> mSurfaceControl;
    const sp<SurfaceControl> mSurfaceControl;
};
};


} // namespace android
} // namespace android