/* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include "CPUTracer.h" #include "ChoreographerFilter.h" #include "ChoreographerThread.h" #include "SwappyDisplayManager.h" #include "Thread.h" #include "swappy/swappyGL.h" #include "swappy/swappyGL_extra.h" namespace swappy { // ANativeWindow_setFrameRate is supported from API 30. To allow compilation for // minSDK < 30 we need runtime support to call this API. using PFN_ANativeWindow_setFrameRate = int32_t (*)(ANativeWindow* window, float frameRate, int8_t compatibility); using namespace std::chrono_literals; struct SwappyCommonSettings { SdkVersion sdkVersion; std::chrono::nanoseconds refreshPeriod; std::chrono::nanoseconds appVsyncOffset; std::chrono::nanoseconds sfVsyncOffset; static bool getFromApp(JNIEnv* env, jobject jactivity, SwappyCommonSettings* out); static SdkVersion getSDKVersion(JNIEnv* env); static bool queryDisplayTimings(JNIEnv* env, jobject jactivity, SwappyCommonSettings* out); }; // Common part between OpenGL and Vulkan implementations. class SwappyCommon { public: enum class PipelineMode { Off, On }; // callbacks to be called during pre/post swap struct SwapHandlers { std::function lastFrameIsComplete; std::function getPrevFrameGpuTime; }; SwappyCommon(JNIEnv* env, jobject jactivity); ~SwappyCommon(); std::chrono::nanoseconds getSwapDuration(); void onChoreographer(int64_t frameTimeNanos); void onPreSwap(const SwapHandlers& h); bool needToSetPresentationTime() { return mPresentationTimeNeeded; } void onPostSwap(const SwapHandlers& h); PipelineMode getCurrentPipelineMode() { return mPipelineMode; } template struct Tracer { void (*function)(void*, T...); void* userData; }; void addTracerCallbacks(const SwappyTracer& tracer); void removeTracerCallbacks(const SwappyTracer& tracer); void setAutoSwapInterval(bool enabled); void setAutoPipelineMode(bool enabled); void setMaxAutoSwapDuration(std::chrono::nanoseconds swapDuration) { mAutoSwapIntervalThreshold = swapDuration; } std::chrono::steady_clock::time_point getPresentationTime() { return mPresentationTime; } std::chrono::nanoseconds getRefreshPeriod() const { return mCommonSettings.refreshPeriod; } bool isValid() { return mValid; } std::chrono::nanoseconds getFenceTimeout() const { return mFenceTimeout; } void setFenceTimeout(std::chrono::nanoseconds t) { mFenceTimeout = t; } bool isDeviceUnsupported(); void setANativeWindow(ANativeWindow* window); void setBufferStuffingFixWait(int32_t nFrames) { mBufferStuffingFixWait = std::max(0, nFrames); } int getSupportedRefreshPeriodsNS(uint64_t* out_refreshrates, int allocated_entries); void setLastLatencyRecordedCallback(std::function callback) { mLastLatencyRecorded = callback; } void resetFramePacing(); void enableFramePacing(bool enable); void enableBlockingWait(bool enable); protected: // Used for testing SwappyCommon(const SwappyCommonSettings& settings); private: class FrameDuration { public: FrameDuration() = default; FrameDuration(std::chrono::nanoseconds cpuTime, std::chrono::nanoseconds gpuTime, bool frameMissedDeadline) : mCpuTime(cpuTime), mGpuTime(gpuTime), mFrameMissedDeadline(frameMissedDeadline) { mCpuTime = std::min(mCpuTime, MAX_DURATION); mGpuTime = std::min(mGpuTime, MAX_DURATION); } std::chrono::nanoseconds getCpuTime() const { return mCpuTime; } std::chrono::nanoseconds getGpuTime() const { return mGpuTime; } bool frameMiss() const { return mFrameMissedDeadline; } std::chrono::nanoseconds getTime(PipelineMode pipeline) const { if (mCpuTime == 0ns && mGpuTime == 0ns) { return 0ns; } if (pipeline == PipelineMode::On) { return std::max(mCpuTime, mGpuTime) + FRAME_MARGIN; } return mCpuTime + mGpuTime + FRAME_MARGIN; } FrameDuration& operator+=(const FrameDuration& other) { mCpuTime += other.mCpuTime; mGpuTime += other.mGpuTime; return *this; } FrameDuration& operator-=(const FrameDuration& other) { mCpuTime -= other.mCpuTime; mGpuTime -= other.mGpuTime; return *this; } friend FrameDuration operator/(FrameDuration lhs, int rhs) { lhs.mCpuTime /= rhs; lhs.mGpuTime /= rhs; return lhs; } private: std::chrono::nanoseconds mCpuTime = std::chrono::nanoseconds(0); std::chrono::nanoseconds mGpuTime = std::chrono::nanoseconds(0); bool mFrameMissedDeadline = false; static constexpr std::chrono::nanoseconds MAX_DURATION = std::chrono::milliseconds(100); }; void addFrameDuration(FrameDuration duration); std::chrono::nanoseconds wakeClient( std::optional sfToVsyncDelay); bool swapFaster(int newSwapInterval) REQUIRES(mMutex); bool swapSlower(const FrameDuration& averageFrameTime, const std::chrono::nanoseconds& upperBound, int newSwapInterval) REQUIRES(mMutex); bool updateSwapInterval(); void preSwapBuffersCallbacks(); void postSwapBuffersCallbacks(); void preWaitCallbacks(); void postWaitCallbacks(std::chrono::nanoseconds cpuTime, std::chrono::nanoseconds gpuTime); void startFrameCallbacks(); void swapIntervalChangedCallbacks(); void onSettingsChanged(); void updateMeasuredSwapDuration(std::chrono::nanoseconds duration); void startFrame(); void waitUntil(int32_t target); void waitUntilTargetFrame(); void waitOneFrame(); void setPreferredDisplayModeId(int index); void setPreferredRefreshPeriod(std::chrono::nanoseconds frameTime) REQUIRES(mMutex); int calculateSwapInterval(std::chrono::nanoseconds frameTime, std::chrono::nanoseconds refreshPeriod); void updateDisplayTimings(); // Waits for the next frame, considering both Choreographer and the prior // frame's completion bool waitForNextFrame(const SwapHandlers& h); void onRefreshRateChanged(); inline bool swapFasterCondition() { return mSwapDuration <= mCommonSettings.refreshPeriod * (mAutoSwapInterval - 1) + DURATION_ROUNDING_MARGIN; } const jobject mJactivity; void* mLibAndroid = nullptr; PFN_ANativeWindow_setFrameRate mANativeWindow_setFrameRate = nullptr; JavaVM* mJVM = nullptr; SwappyCommonSettings mCommonSettings; std::unique_ptr mChoreographerFilter; bool mUsingExternalChoreographer = false; std::unique_ptr mChoreographerThread; std::mutex mWaitingMutex; std::condition_variable mWaitingCondition; std::chrono::steady_clock::time_point mCurrentFrameTimestamp = std::chrono::steady_clock::now(); int32_t mCurrentFrame = 0; std::optional mSfToVsyncDelay; std::atomic mMeasuredSwapDuration; std::chrono::steady_clock::time_point mSwapTime; std::mutex mMutex; class FrameDurations { public: void add(FrameDuration frameDuration); bool hasEnoughSamples() const; FrameDuration getAverageFrameTime() const; int getMissedFramePercent() const; void clear(); private: static constexpr std::chrono::nanoseconds FRAME_DURATION_SAMPLE_SECONDS = 2s; std::deque, FrameDuration>> mFrames; FrameDuration mFrameDurationsSum = {}; int mMissedFrameCount = 0; }; FrameDurations mFrameDurations GUARDED_BY(mMutex); bool mAutoSwapIntervalEnabled GUARDED_BY(mMutex) = true; bool mPipelineModeAutoMode GUARDED_BY(mMutex) = true; static constexpr std::chrono::nanoseconds FRAME_MARGIN = 1ms; static constexpr std::chrono::nanoseconds DURATION_ROUNDING_MARGIN = 1us; static constexpr int NON_PIPELINE_PERCENT = 50; // 50% static constexpr int FRAME_DROP_THRESHOLD = 10; // 10% std::chrono::nanoseconds mSwapDuration = 0ns; int32_t mAutoSwapInterval; std::atomic mAutoSwapIntervalThreshold = { 50ms}; // 20FPS static constexpr std::chrono::nanoseconds REFRESH_RATE_MARGIN = 500ns; std::chrono::steady_clock::time_point mStartFrameTime; struct SwappyTracerCallbacks { std::list> preWait; std::list> postWait; std::list> preSwapBuffers; std::list> postSwapBuffers; std::list> startFrame; std::list> swapIntervalChanged; }; SwappyTracerCallbacks mInjectedTracers; int32_t mTargetFrame = 0; std::chrono::steady_clock::time_point mPresentationTime = std::chrono::steady_clock::now(); bool mPresentationTimeNeeded; PipelineMode mPipelineMode = PipelineMode::On; bool mValid; std::chrono::nanoseconds mFenceTimeout = std::chrono::nanoseconds(50ms); constexpr static bool USE_DISPLAY_MANAGER = true; std::unique_ptr mDisplayManager; int mNextModeId = -1; std::shared_ptr mSupportedRefreshPeriods; struct TimingSettings { std::chrono::nanoseconds refreshPeriod = {}; std::chrono::nanoseconds swapDuration = {}; static TimingSettings from(const Settings& settings) { TimingSettings timingSettings; timingSettings.refreshPeriod = settings.getDisplayTimings().refreshPeriod; timingSettings.swapDuration = settings.getSwapDuration(); return timingSettings; } bool operator!=(const TimingSettings& other) const { return (refreshPeriod != other.refreshPeriod) || (swapDuration != other.swapDuration); } bool operator==(const TimingSettings& other) const { return !(*this != other); } }; TimingSettings mNextTimingSettings GUARDED_BY(mMutex) = {}; bool mTimingSettingsNeedUpdate GUARDED_BY(mMutex) = false; CPUTracer mCPUTracer; ANativeWindow* mWindow GUARDED_BY(mMutex) = nullptr; bool mWindowChanged GUARDED_BY(mMutex) = false; float mLatestFrameRateVote GUARDED_BY(mMutex) = 0.f; static constexpr float FRAME_RATE_VOTE_MARGIN = 1.f; // 1Hz // Callback for last latency recorded - used for buffer stuffing fix. // Latency is returned in number of V-syncs std::function mLastLatencyRecorded; // If zero, don't apply the double buffering fix. If non-zero, apply // the fix after this number of bad frames. int mBufferStuffingFixWait = 0; // When zero, buffer stuffing fixing may occur. // After a fix has been applied, this is non-zero and counts down to avoid // consecutive fixes. int mBufferStuffingFixCounter = 0; // Counts the number of consecutive missed frames (as judged by expected // latency). int mMissedFrameCounter = 0; bool mFramePacingResetRequested GUARDED_BY(mMutex) = false; std::chrono::nanoseconds mInitialRefreshPeriod; bool mFramePacingToggleRequested GUARDED_BY(mMutex) = false; bool mFramePacingEnabled GUARDED_BY(mMutex) = true; bool mBlockingWaitEnabled GUARDED_BY(mMutex) = true; }; } // namespace swappy