|  | commit b0a679b048f4055649c12280f7257e5f81e8b913
 | 
|  | Author: John Galt <johngaltfirstrun@gmail.com>
 | 
|  | Date:   Fri Oct 24 11:57:04 2025 -0400
 | 
|  | 
 | 
|  |     app/Composer: vsync improvements
 | 
|  |     
 | 
|  |      - Use CLOCK_MONOTONIC to solve drift (like when closing app and reopening)
 | 
|  |      - Implement an adaptive pacing offset logic, decreasing with consecutive
 | 
|  |        hits (8) and increasing immediately on miss to restore stability.
 | 
|  |        All tunables are documented, and all but decrease step are adaptive
 | 
|  |        based on current period.
 | 
|  |      - Improve vsyncPeriod with a sanity check and minimize small rounding
 | 
|  |        errors.
 | 
|  |     
 | 
|  |     Always defaults to 60hz until first period.
 | 
|  | 
 | 
|  | diff --git a/app/app/src/main/cpp/ComposerImpl.cpp b/app/app/src/main/cpp/ComposerImpl.cpp
 | 
|  | index 3c9c06b..ef69571 100644
 | 
|  | --- a/app/app/src/main/cpp/ComposerImpl.cpp
 | 
|  | +++ b/app/app/src/main/cpp/ComposerImpl.cpp
 | 
|  | @@ -3,6 +3,8 @@
 | 
|  |  #include <aidlcommonsupport/NativeHandle.h>
 | 
|  |  #include <android/hardware_buffer.h>
 | 
|  |  #include <android/native_window.h>
 | 
|  | +#include <cmath>
 | 
|  | +#include <unistd.h>
 | 
|  |  #include <private/android/AHardwareBufferHelpers.h>
 | 
|  |  #include <sys/prctl.h>
 | 
|  |  #include <utils/Log.h>
 | 
|  | @@ -132,7 +134,17 @@ ndk::ScopedAStatus ComposerImpl::setBuffer(int64_t in_displayId, const HardwareB
 | 
|  |              // ALOGE("%s: Get NativeWindow Failed!", __FUNCTION__);
 | 
|  |              return ndk::ScopedAStatus::ok();
 | 
|  |          }
 | 
|  | -        *_aidl_return = ANativeWindow_queueBuffer(mDisplays[in_displayId]->nativeWindow, buffer, -1);
 | 
|  | +        int64_t intendedVsync = 0;
 | 
|  | +        if (mDisplays[in_displayId]->pacerConfigured) {
 | 
|  | +            auto times = mDisplays[in_displayId]->schedulePresentLL();
 | 
|  | +            intendedVsync = times.second;
 | 
|  | +        }
 | 
|  | +        int fenceFd = in_acquireFence.get();
 | 
|  | +        *_aidl_return = ANativeWindow_queueBuffer(mDisplays[in_displayId]->nativeWindow, buffer, fenceFd);
 | 
|  | +        sp<Fence> acquireFence = (fenceFd >= 0) ? new Fence(dup(fenceFd)) : Fence::NO_FENCE;
 | 
|  | +        if (mDisplays[in_displayId]->pacerConfigured) {
 | 
|  | +            mDisplays[in_displayId]->learnFromFenceOrNowLL(acquireFence, intendedVsync);
 | 
|  | +        }
 | 
|  |      }
 | 
|  |      AHardwareBuffer_release(ahwb);
 | 
|  |  
 | 
|  | @@ -199,7 +211,12 @@ void ComposerImpl::onSurfaceChanged(int64_t displayId, sp<Surface> surface, ANat
 | 
|  |      displayConfig.height = ANativeWindow_getHeight(nativeWindow);
 | 
|  |      displayConfig.dpi.x = dpi;
 | 
|  |      displayConfig.dpi.y = dpi;
 | 
|  | -    displayConfig.vsyncPeriod = 10E8 / refresh;
 | 
|  | +    double rate = static_cast<double>(refresh);
 | 
|  | +    if (!(rate > 1.0 && rate < 1000.0)) {
 | 
|  | +        rate = 60.0; //60hz default
 | 
|  | +    }
 | 
|  | +    const int64_t periodNs = static_cast<int64_t>(llround(1000000000.0 / rate));
 | 
|  | +    displayConfig.vsyncPeriod = periodNs;
 | 
|  |  
 | 
|  |      bool needRefresh = false;
 | 
|  |      auto display = mDisplays.find(displayId);
 | 
|  | @@ -209,9 +226,15 @@ void ComposerImpl::onSurfaceChanged(int64_t displayId, sp<Surface> surface, ANat
 | 
|  |               display->second->displayConfig.height != displayConfig.height)) {
 | 
|  |              needRefresh = true;
 | 
|  |          }
 | 
|  | +        const bool periodChanged = (display->second->displayConfig.vsyncPeriod != displayConfig.vsyncPeriod);
 | 
|  |          display->second->nativeWindow = nativeWindow;
 | 
|  |          display->second->surface = surface;
 | 
|  |          display->second->displayConfig = displayConfig;
 | 
|  | +        if (periodChanged) {
 | 
|  | +            display->second->mVsyncThread.stop();
 | 
|  | +            display->second->mVsyncThread.start(0, displayConfig.vsyncPeriod);
 | 
|  | +            display->second->configurePacer(displayConfig.vsyncPeriod);
 | 
|  | +        }
 | 
|  |      } else {
 | 
|  |          ComposerDisplay *targetDisplay = new ComposerDisplay();
 | 
|  |          targetDisplay->nativeWindow = nativeWindow;
 | 
|  | @@ -225,6 +248,7 @@ void ComposerImpl::onSurfaceChanged(int64_t displayId, sp<Surface> surface, ANat
 | 
|  |              mCallbacks->onVsyncReceived(mSequenceId, displayId, timestamp);
 | 
|  |          });
 | 
|  |          targetDisplay->mVsyncThread.start(0, displayConfig.vsyncPeriod);
 | 
|  | +        targetDisplay->configurePacer(displayConfig.vsyncPeriod);
 | 
|  |          mDisplays[displayId] = targetDisplay;
 | 
|  |      }
 | 
|  |  
 | 
|  | diff --git a/app/app/src/main/cpp/ComposerImpl.h b/app/app/src/main/cpp/ComposerImpl.h
 | 
|  | index 6bdd1ae..5e429c7 100644
 | 
|  | --- a/app/app/src/main/cpp/ComposerImpl.h
 | 
|  | +++ b/app/app/src/main/cpp/ComposerImpl.h
 | 
|  | @@ -1,6 +1,9 @@
 | 
|  |  #pragma once
 | 
|  |  
 | 
|  |  #include <condition_variable>
 | 
|  | +#include <atomic>
 | 
|  | +#include <chrono>
 | 
|  | +#include <ctime>
 | 
|  |  #include <mutex>
 | 
|  |  #include <thread>
 | 
|  |  
 | 
|  | @@ -23,6 +26,7 @@ using android::Mutex;
 | 
|  |  using android::sp;
 | 
|  |  using android::Surface;
 | 
|  |  using android::SurfaceListener;
 | 
|  | +using android::Fence;
 | 
|  |  
 | 
|  |  namespace aidl {
 | 
|  |  namespace vendor {
 | 
|  | @@ -31,6 +35,71 @@ namespace composer {
 | 
|  |  
 | 
|  |  typedef std::function<void(int64_t)> vsync_callback_t;
 | 
|  |  
 | 
|  | +static inline int64_t monotonic_now_ns() {
 | 
|  | +    timespec ts;
 | 
|  | +    clock_gettime(CLOCK_MONOTONIC, &ts);
 | 
|  | +    return static_cast<int64_t>(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
 | 
|  | +}
 | 
|  | +static inline void sleep_until_abs_ns(int64_t t) {
 | 
|  | +    timespec req{.tv_sec = t / 1000000000LL, .tv_nsec = t % 1000000000LL};
 | 
|  | +    while (true) {
 | 
|  | +        int r = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, nullptr);
 | 
|  | +        if (r == 0) return;
 | 
|  | +        if (r != EINTR) return;
 | 
|  | +    }
 | 
|  | +}
 | 
|  | +
 | 
|  | +struct PresentPacerLL {
 | 
|  | +    void configurePeriod(int64_t periodNs) {
 | 
|  | +        if (periodNs > 0) mPeriod = periodNs;
 | 
|  | +    }
 | 
|  | +    int64_t nextSubmitNs(int64_t nowNs) const {
 | 
|  | +        const int64_t p = mPeriod > 0 ? mPeriod : 16666666; // 60hz default
 | 
|  | +        const int64_t shifted = nowNs + mOffset;
 | 
|  | +        const int64_t ticks = shifted >= 0 ? (shifted / p) + 1 : 1;
 | 
|  | +        const int64_t nextVsync = ticks * p;
 | 
|  | +        return nextVsync - mOffset;
 | 
|  | +    }
 | 
|  | +    void waitUntil(int64_t t) const { sleep_until_abs_ns(t); }
 | 
|  | +    void recordOutcome(bool hit) {
 | 
|  | +        if (hit) {
 | 
|  | +            // After 8 hits, begin decreasing by 0.1ms
 | 
|  | +            if (++mHitStreak >= 8) {
 | 
|  | +                const int64_t step = stepNs();
 | 
|  | +                mOffset = (mOffset - step > kFloor) ? (mOffset - step) : kFloor;
 | 
|  | +                mHitStreak = 0;
 | 
|  | +            }
 | 
|  | +        } else {
 | 
|  | +            // Missed: attempt to regain stability
 | 
|  | +            mHitStreak = 0;
 | 
|  | +            const int64_t step = stepNs();
 | 
|  | +            const int64_t ceil = ceilingNs();
 | 
|  | +            const int64_t next = mOffset + 4 * step;
 | 
|  | +            mOffset = (next < ceil) ? next : ceil;
 | 
|  | +        }
 | 
|  | +    }
 | 
|  | +    int64_t intendedVsyncForSubmit(int64_t submitNs) const { return submitNs + mOffset; }
 | 
|  | +
 | 
|  | +    // Tunables
 | 
|  | +    static constexpr int64_t kFloor = 200000; // 0.2ms min offset
 | 
|  | +    int64_t ceilingNs() const {
 | 
|  | +        const int64_t halfP = mPeriod > 0 ? (mPeriod / 2) : 8333333;
 | 
|  | +        const int64_t fiveMs = 5000000;
 | 
|  | +        return (halfP < fiveMs) ? halfP : fiveMs;
 | 
|  | +    }
 | 
|  | +    int64_t stepNs() const {
 | 
|  | +        const int64_t p = (mPeriod > 0) ? mPeriod : 16666666; // 60hz def
 | 
|  | +        int64_t s = p / 80; // 1.25% of period
 | 
|  | +        if (s < 50000) s = 50000;   // 0.05 ms floor
 | 
|  | +        if (s > 200000) s = 200000; // 0.20 ms cap
 | 
|  | +        return s;
 | 
|  | +    }
 | 
|  | +
 | 
|  | +    int64_t mPeriod{16666666};
 | 
|  | +    int64_t mOffset{kFloor};
 | 
|  | +    int     mHitStreak{0};
 | 
|  | +};
 | 
|  | +
 | 
|  |  class VsyncThread {
 | 
|  |  public:
 | 
|  |      static int64_t now();
 | 
|  | @@ -63,6 +132,28 @@ struct ComposerDisplay {
 | 
|  |      bool plugged;
 | 
|  |      sp<SurfaceListener> listener;
 | 
|  |      VsyncThread mVsyncThread;
 | 
|  | +    PresentPacerLL pacer;
 | 
|  | +    bool pacerConfigured{false};
 | 
|  | +
 | 
|  | +    void configurePacer(int64_t periodNs) {
 | 
|  | +        pacer.configurePeriod(periodNs);
 | 
|  | +        pacerConfigured = true;
 | 
|  | +    }
 | 
|  | +    std::pair<int64_t,int64_t> schedulePresentLL() {
 | 
|  | +        const int64_t now = monotonic_now_ns();
 | 
|  | +        const int64_t submitAt = pacer.nextSubmitNs(now);
 | 
|  | +        pacer.waitUntil(submitAt);
 | 
|  | +        const int64_t intendedVsync = pacer.intendedVsyncForSubmit(submitAt);
 | 
|  | +        return {submitAt, intendedVsync};
 | 
|  | +    }
 | 
|  | +    void learnFromFenceLL(const sp<Fence>& fence, int64_t intendedVsyncNs) {
 | 
|  | +        bool hit = false;
 | 
|  | +        if (fence.get() != nullptr) {
 | 
|  | +            const int64_t st = fence->getSignalTime();
 | 
|  | +            hit = (st >= 0 && st <= intendedVsyncNs);
 | 
|  | +        }
 | 
|  | +        pacer.recordOutcome(hit);
 | 
|  | +    }
 | 
|  |  };
 | 
|  |  
 | 
|  |  class ComposerImpl : public BnComposer {
 |