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

Commit c7453586 authored by Ady Abraham's avatar Ady Abraham Committed by Android (Google) Code Review
Browse files

Merge "SF: pass DisplayMode to VsyncTracker" into main

parents 9720ecff c585dbac
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ cc_defaults {
        "libui",
        "libutils",
    ],
    static_libs: ["libsurfaceflinger_common"],
}

cc_library_headers {
+9 −21
Original line number Diff line number Diff line
@@ -118,7 +118,7 @@ void Scheduler::setPacesetterDisplay(std::optional<PhysicalDisplayId> pacesetter

void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
    auto schedulePtr = std::make_shared<VsyncSchedule>(
            displayId, mFeatures,
            selectorPtr->getActiveMode().modePtr, mFeatures,
            [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); },
            mVsyncTrackerCallback);

@@ -503,7 +503,7 @@ void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) {
}

void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable,
                                            std::optional<Fps> refreshRate) {
                                            DisplayModePtr modePtr) {
    const auto displayOpt = mDisplays.get(id);
    if (!displayOpt) {
        ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str());
@@ -512,12 +512,12 @@ void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEn
    const Display& display = *displayOpt;

    if (display.schedulePtr->isHardwareVsyncAllowed(allowToEnable)) {
        if (!refreshRate) {
            refreshRate = display.selectorPtr->getActiveMode().modePtr->getVsyncRate();
        if (!modePtr) {
            modePtr = display.selectorPtr->getActiveMode().modePtr.get();
        }
        if (refreshRate->isValid()) {
        if (modePtr->getVsyncRate().isValid()) {
            constexpr bool kForce = false;
            display.schedulePtr->startPeriodTransition(refreshRate->getPeriod(), kForce);
            display.schedulePtr->onDisplayModeChanged(ftl::as_non_null(modePtr), kForce);
        }
    }
}
@@ -563,19 +563,7 @@ void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) {
    ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
          to_string(mode.modePtr->getVsyncRate()).c_str());

    display.schedulePtr->getTracker().setDisplayModeData(
            {.renderRate = renderFrameRate,
             .notifyExpectedPresentTimeoutOpt = getNotifyExpectedPresentTimeout(mode)});
}

std::optional<Period> Scheduler::getNotifyExpectedPresentTimeout(const FrameRateMode& mode) {
    if (mode.modePtr->getVrrConfig() && mode.modePtr->getVrrConfig()->notifyExpectedPresentConfig) {
        return Period::fromNs(
                mode.modePtr->getVrrConfig()
                        ->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs);
    } else {
        return std::nullopt;
    }
    display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
}

void Scheduler::resync() {
@@ -913,9 +901,9 @@ std::shared_ptr<VsyncSchedule> Scheduler::promotePacesetterDisplayLocked(

        newVsyncSchedulePtr = pacesetter.schedulePtr;

        const Fps refreshRate = pacesetter.selectorPtr->getActiveMode().modePtr->getVsyncRate();
        constexpr bool kForce = true;
        newVsyncSchedulePtr->startPeriodTransition(refreshRate.getPeriod(), kForce);
        newVsyncSchedulePtr->onDisplayModeChanged(pacesetter.selectorPtr->getActiveMode().modePtr,
                                                  kForce);
    }
    return newVsyncSchedulePtr;
}
+4 −8
Original line number Diff line number Diff line
@@ -210,13 +210,12 @@ public:
    // If allowToEnable is true, then hardware vsync will be turned on.
    // Otherwise, if hardware vsync is not already enabled then this method will
    // no-op.
    // If refreshRate is nullopt, use the existing refresh rate of the display.
    // If modePtr is nullopt, use the active display mode.
    void resyncToHardwareVsync(PhysicalDisplayId id, bool allowToEnable,
                               std::optional<Fps> refreshRate = std::nullopt)
            EXCLUDES(mDisplayLock) {
                               DisplayModePtr modePtr = nullptr) EXCLUDES(mDisplayLock) {
        std::scoped_lock lock(mDisplayLock);
        ftl::FakeGuard guard(kMainThreadContext);
        resyncToHardwareVsyncLocked(id, allowToEnable, refreshRate);
        resyncToHardwareVsyncLocked(id, allowToEnable, modePtr);
    }
    void forceNextResync() { mLastResyncTime = 0; }

@@ -354,7 +353,7 @@ private:
    void onHardwareVsyncRequest(PhysicalDisplayId, bool enable);

    void resyncToHardwareVsyncLocked(PhysicalDisplayId, bool allowToEnable,
                                     std::optional<Fps> refreshRate = std::nullopt)
                                     DisplayModePtr modePtr = nullptr)
            REQUIRES(kMainThreadContext, mDisplayLock);
    void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock);
    void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod);
@@ -431,9 +430,6 @@ private:
    Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
    void resync() override EXCLUDES(mDisplayLock);

    std::optional<Period> getNotifyExpectedPresentTimeout(const FrameRateMode&)
            REQUIRES(mDisplayLock);

    // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
    struct Connection {
        sp<EventThreadConnection> connection;
+64 −52
Original line number Diff line number Diff line
@@ -47,16 +47,16 @@ static auto constexpr kMaxPercent = 100u;

VSyncPredictor::~VSyncPredictor() = default;

VSyncPredictor::VSyncPredictor(PhysicalDisplayId id, nsecs_t idealPeriod, size_t historySize,
VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
                               IVsyncTrackerCallback& callback)
      : mId(id),
      : mId(modePtr->getPhysicalDisplayId()),
        mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
        kHistorySize(historySize),
        kMinimumSamplesForPrediction(minimumSamplesForPrediction),
        kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
        mVsyncTrackerCallback(callback),
        mIdealPeriod(idealPeriod) {
        mDisplayModePtr(modePtr) {
    resetModel();
}

@@ -74,13 +74,18 @@ inline size_t VSyncPredictor::next(size_t i) const {
    return (i + 1) % mTimestamps.size();
}

nsecs_t VSyncPredictor::idealPeriod() const {
    return mDisplayModePtr->getVsyncRate().getPeriodNsecs();
}

bool VSyncPredictor::validate(nsecs_t timestamp) const {
    if (mLastTimestampIndex < 0 || mTimestamps.empty()) {
        return true;
    }

    auto const aValidTimestamp = mTimestamps[mLastTimestampIndex];
    auto const percent = (timestamp - aValidTimestamp) % mIdealPeriod * kMaxPercent / mIdealPeriod;
    const auto aValidTimestamp = mTimestamps[mLastTimestampIndex];
    const auto percent =
            (timestamp - aValidTimestamp) % idealPeriod() * kMaxPercent / idealPeriod();
    if (percent >= kOutlierTolerancePercent &&
        percent <= (kMaxPercent - kOutlierTolerancePercent)) {
        return false;
@@ -90,7 +95,7 @@ bool VSyncPredictor::validate(nsecs_t timestamp) const {
                                       [timestamp](nsecs_t a, nsecs_t b) {
                                           return std::abs(timestamp - a) < std::abs(timestamp - b);
                                       });
    const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / mIdealPeriod;
    const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod();
    if (distancePercent < kOutlierTolerancePercent) {
        // duplicate timestamp
        return false;
@@ -100,7 +105,7 @@ bool VSyncPredictor::validate(nsecs_t timestamp) const {

nsecs_t VSyncPredictor::currentPeriod() const {
    std::lock_guard lock(mMutex);
    return mRateMap.find(mIdealPeriod)->second.slope;
    return mRateMap.find(idealPeriod())->second.slope;
}

bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
@@ -137,7 +142,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {

    const size_t numSamples = mTimestamps.size();
    if (numSamples < kMinimumSamplesForPrediction) {
        mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
        mRateMap[idealPeriod()] = {idealPeriod(), 0};
        return true;
    }

@@ -161,7 +166,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {

    // Normalizing to the oldest timestamp cuts down on error in calculating the intercept.
    const auto oldestTS = *std::min_element(mTimestamps.begin(), mTimestamps.end());
    auto it = mRateMap.find(mIdealPeriod);
    auto it = mRateMap.find(idealPeriod());
    auto const currentPeriod = it->second.slope;

    // The mean of the ordinals must be precise for the intercept calculation, so scale them up for
@@ -199,7 +204,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
    }

    if (CC_UNLIKELY(bottom == 0)) {
        it->second = {mIdealPeriod, 0};
        it->second = {idealPeriod(), 0};
        clearTimestamps();
        return false;
    }
@@ -207,9 +212,9 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
    nsecs_t const anticipatedPeriod = top * kScalingFactor / bottom;
    nsecs_t const intercept = meanTS - (anticipatedPeriod * meanOrdinal / kScalingFactor);

    auto const percent = std::abs(anticipatedPeriod - mIdealPeriod) * kMaxPercent / mIdealPeriod;
    auto const percent = std::abs(anticipatedPeriod - idealPeriod()) * kMaxPercent / idealPeriod();
    if (percent >= kOutlierTolerancePercent) {
        it->second = {mIdealPeriod, 0};
        it->second = {idealPeriod(), 0};
        clearTimestamps();
        return false;
    }
@@ -241,8 +246,8 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) co
    if (mTimestamps.empty()) {
        traceInt64("VSP-mode", 1);
        auto const knownTimestamp = mKnownTimestamp ? *mKnownTimestamp : timePoint;
        auto const numPeriodsOut = ((timePoint - knownTimestamp) / mIdealPeriod) + 1;
        return knownTimestamp + numPeriodsOut * mIdealPeriod;
        auto const numPeriodsOut = ((timePoint - knownTimestamp) / idealPeriod()) + 1;
        return knownTimestamp + numPeriodsOut * idealPeriod();
    }

    auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end());
@@ -278,11 +283,11 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
    mLastVsyncSequence = getVsyncSequenceLocked(timePoint);

    const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int {
        if (!mDisplayModeDataOpt) return 0;
        if (!mRenderRateOpt) return 0;

        const auto divisor =
                RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod),
                                                         mDisplayModeDataOpt->renderRate);
                RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
                                                         *mRenderRateOpt);
        if (divisor <= 1) return 0;

        const int mod = mLastVsyncSequence->seq % divisor;
@@ -293,12 +298,12 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {

    if (renderRatePhase == 0) {
        const auto vsyncTime = mLastVsyncSequence->vsyncTime;
        if (FlagManager::getInstance().vrr_config() && mDisplayModeDataOpt) {
        if (FlagManager::getInstance().vrr_config()) {
            const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
            ATRACE_FORMAT("%s InPhase vsyncIn %.2fms", __func__,
                          ticks<std::milli, float>(vsyncTimePoint - TimePoint::now()));
            mVsyncTrackerCallback.onVsyncGenerated(mId, vsyncTimePoint, *mDisplayModeDataOpt,
                                                   Period::fromNs(mIdealPeriod));
            const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
            mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
        }
        return vsyncTime;
    }
@@ -307,12 +312,13 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
    const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
    const auto nextAnticipatedVsyncTime =
            nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2);
    if (FlagManager::getInstance().vrr_config() && mDisplayModeDataOpt) {
    if (FlagManager::getInstance().vrr_config()) {
        const auto nextAnticipatedVsyncTimePoint = TimePoint::fromNs(nextAnticipatedVsyncTime);
        ATRACE_FORMAT("%s outOfPhase vsyncIn %.2fms", __func__,
                      ticks<std::milli, float>(nextAnticipatedVsyncTimePoint - TimePoint::now()));
        mVsyncTrackerCallback.onVsyncGenerated(mId, nextAnticipatedVsyncTimePoint,
                                               *mDisplayModeDataOpt, Period::fromNs(mIdealPeriod));
        const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
        mVsyncTrackerCallback.onVsyncGenerated(nextAnticipatedVsyncTimePoint, mDisplayModePtr,
                                               renderRate);
    }
    return nextAnticipatedVsyncTime;
}
@@ -328,7 +334,8 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const {
    std::lock_guard lock(mMutex);
    const auto divisor =
            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate);
            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
                                                     frameRate);
    return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(divisor));
}

@@ -344,7 +351,7 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c
        return true;
    }

    const nsecs_t period = mRateMap[mIdealPeriod].slope;
    const nsecs_t period = mRateMap[idealPeriod()].slope;
    const nsecs_t justBeforeTimePoint = timePoint - period / 2;
    const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint);
    ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64,
@@ -352,45 +359,50 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c
    return vsyncSequence.seq % divisor == 0;
}

void VSyncPredictor::setDisplayModeData(const DisplayModeData& displayModeData) {
    ALOGV("%s %s: RenderRate %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(),
          to_string(displayModeData.renderRate).c_str(),
          displayModeData.notifyExpectedPresentTimeoutOpt
                  ? std::to_string(displayModeData.notifyExpectedPresentTimeoutOpt->ns()).c_str()
                  : "N/A");
void VSyncPredictor::setRenderRate(Fps renderRate) {
    ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
    ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str());
    std::lock_guard lock(mMutex);
    mDisplayModeDataOpt = displayModeData;
}

VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
    mRenderRateOpt = renderRate;
}

void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) {
    LOG_ALWAYS_FATAL_IF(mId != modePtr->getPhysicalDisplayId(),
                        "mode does not belong to the display");
    ATRACE_FORMAT("%s %s", __func__, to_string(*modePtr).c_str());
    const auto timeout = modePtr->getVrrConfig()
            ? modePtr->getVrrConfig()->notifyExpectedPresentConfig
            : std::nullopt;
    ALOGV("%s %s: DisplayMode %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(),
          to_string(*modePtr).c_str(),
          timeout ? std::to_string(timeout->notifyExpectedPresentTimeoutNs).c_str() : "N/A");
    std::lock_guard lock(mMutex);
    const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
    return {model.slope, model.intercept};
}

VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
    return mRateMap.find(mIdealPeriod)->second;
}

void VSyncPredictor::setPeriod(nsecs_t period) {
    ATRACE_FORMAT("%s %s", __func__, to_string(mId).c_str());
    traceInt64("VSP-setPeriod", period);
    mDisplayModePtr = modePtr;
    traceInt64("VSP-setPeriod", modePtr->getVsyncRate().getPeriodNsecs());

    std::lock_guard lock(mMutex);
    static constexpr size_t kSizeLimit = 30;
    if (CC_UNLIKELY(mRateMap.size() == kSizeLimit)) {
        mRateMap.erase(mRateMap.begin());
    }

    // TODO(b/308610306) mIdealPeriod to be updated with setDisplayModeData
    mIdealPeriod = period;
    if (mRateMap.find(period) == mRateMap.end()) {
        mRateMap[mIdealPeriod] = {period, 0};
    if (mRateMap.find(idealPeriod()) == mRateMap.end()) {
        mRateMap[idealPeriod()] = {idealPeriod(), 0};
    }

    clearTimestamps();
}

VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
    std::lock_guard lock(mMutex);
    const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
    return {model.slope, model.intercept};
}

VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
    return mRateMap.find(idealPeriod())->second;
}

void VSyncPredictor::clearTimestamps() {
    if (!mTimestamps.empty()) {
        auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
@@ -412,18 +424,18 @@ bool VSyncPredictor::needsMoreSamples() const {

void VSyncPredictor::resetModel() {
    std::lock_guard lock(mMutex);
    mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
    mRateMap[idealPeriod()] = {idealPeriod(), 0};
    clearTimestamps();
}

void VSyncPredictor::dump(std::string& result) const {
    std::lock_guard lock(mMutex);
    StringAppendF(&result, "\tmIdealPeriod=%.2f\n", mIdealPeriod / 1e6f);
    StringAppendF(&result, "\tmDisplayModePtr=%s\n", to_string(*mDisplayModePtr).c_str());
    StringAppendF(&result, "\tRefresh Rate Map:\n");
    for (const auto& [idealPeriod, periodInterceptTuple] : mRateMap) {
    for (const auto& [period, periodInterceptTuple] : mRateMap) {
        StringAppendF(&result,
                      "\t\tFor ideal period %.2fms: period = %.2fms, intercept = %" PRId64 "\n",
                      idealPeriod / 1e6f, periodInterceptTuple.slope / 1e6f,
                      period / 1e6f, periodInterceptTuple.slope / 1e6f,
                      periodInterceptTuple.intercept);
    }
}
+8 −14
Original line number Diff line number Diff line
@@ -31,14 +31,14 @@ class VSyncPredictor : public VSyncTracker {
public:
    /*
     * \param [in] PhysicalDisplayid The display this corresponds to.
     * \param [in] idealPeriod  The initial ideal period to use.
     * \param [in] modePtr  The initial display mode
     * \param [in] historySize  The internal amount of entries to store in the model.
     * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before
     * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter
     * samples that fall outlierTolerancePercent from an anticipated vsync event.
     * \param [in] IVsyncTrackerCallback The callback for the VSyncTracker.
     */
    VSyncPredictor(PhysicalDisplayId, nsecs_t idealPeriod, size_t historySize,
    VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
                   size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
                   IVsyncTrackerCallback&);
    ~VSyncPredictor();
@@ -48,15 +48,6 @@ public:
    nsecs_t currentPeriod() const final EXCLUDES(mMutex);
    void resetModel() final EXCLUDES(mMutex);

    /*
     * Inform the model that the period is anticipated to change to a new value.
     * model will use the period parameter to predict vsync events until enough
     * timestamps with the new period have been collected.
     *
     * \param [in] period   The new period that should be used.
     */
    void setPeriod(nsecs_t period) final EXCLUDES(mMutex);

    /* Query if the model is in need of more samples to make a prediction.
     * \return  True, if model would benefit from more samples, False if not.
     */
@@ -71,7 +62,9 @@ public:

    bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex);

    void setDisplayModeData(const DisplayModeData&) final EXCLUDES(mMutex);
    void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex);

    void setRenderRate(Fps) final EXCLUDES(mMutex);

    void dump(std::string& result) const final EXCLUDES(mMutex);

@@ -96,6 +89,7 @@ private:
        int64_t seq;
    };
    VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex);
    nsecs_t idealPeriod() const REQUIRES(mMutex);

    bool const mTraceOn;
    size_t const kHistorySize;
@@ -104,7 +98,6 @@ private:
    IVsyncTrackerCallback& mVsyncTrackerCallback;
    std::mutex mutable mMutex;

    nsecs_t mIdealPeriod GUARDED_BY(mMutex);
    std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);

    // Map between ideal vsync period and the calculated model
@@ -113,7 +106,8 @@ private:
    size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
    std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);

    std::optional<DisplayModeData> mDisplayModeDataOpt GUARDED_BY(mMutex);
    ftl::NonNull<DisplayModePtr> mDisplayModePtr GUARDED_BY(mMutex);
    std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex);

    mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex);
};
Loading