Starting with Android4.1, Google introduced Project Butter, or “Project Butter.” The goal is to improve one of the system’s biggest complaints: UI responsiveness, and Google hopes the new plan will help Android get rid of the lag in UI interaction and make it smooth as butter. Project Butter has refactored the Android Display system to introduce three core elements: VSync, Triple Buffer, and Choreographer. Today we are going to focus on the context of VSync signal.
VSync signal generation
The VSync signal is generated by the HWC hardware module based on the screen refresh rate. DispSync::Callback (VSync ::Callback) is sent from HWC to DispSync::Callback (VSync ::Callback). DispSyncSource implements this callback interface. To process the VSync signal, SurfaceFlinger starts two EventThread threads:
- MEventThread: serves client APP UI rendering.
- MSFEventThread: serves on
SurfaceFlinger
Composition and screen up.
The two VSync threads are initialized in SurfaceFlinger::init as follows:
// start the EventThread vsyncSrc indicates the Vsync for rendering
sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync, vsyncPhaseOffsetNs, true."app");
// The VSync thread used for client UI rendering
mEventThread = new EventThread(vsyncSrc, *this);
// sfVsyncSrc stands for VSync for SF synthesis
sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync, sfVsyncPhaseOffsetNs, true."sf");
// SF uses the VSync thread
mSFEventThread = new EventThread(sfVsyncSrc, *this);
//MessageQueue registers listeners with SF EventThread
mEventQueue.setEventThread(mSFEventThread);
Copy the code
DispSyncSource implements the DispSync::Callback Callback interface to receive VSync events from DispSync. This code creates two DispSyncSource objects. VsyncSrc serves the CPU and drives the client APP UI thread to render. SfVsyncSrc serves the GPU and drives the SF main thread to synthesize the upper screen. (adb shell Dumpsys SurfaceFlinger); 2 DispSyncSource specifies a different timestamp offset (relative to the standard VSync timestamp) to fine control the timing of VSync callback.
// App Phase is the offset of vsyncSrc, SF phase is the offset of sfVsyncSrc, and refresh is the refresh cycle of the main screen
DispSync configuration: app phase 1000000 ns, sf phase 1000000 ns, early sf phase 1000000 ns, present offset 0 ns (refresh 16666666 ns)
Copy the code
Each DispSyncSource object that is associated with a EventThread, DispSync VSync events will be delivered via DispSyncSource EventThread: : onVSyncEvent, They are then distributed through EventThread::Connection.
The VSync schema based on DispSync is as follows:
DispSync
With hardware VSync (HW_VSYNC
Trained a simulated VSync model (SW-VSYNC
).- The simulated VSync model adds a timestamp offset to the VSync signal drawn by the driver APP (
phase-app
), generating the vsync-app event. - The simulated VSync model adds a timestamp offset to the VSync signal synthesized by the driver SF (
phase-sf
), generating the vsync-sf event. Vsync-app
andVsync-sf
Are VSync events that the outside world actually touches.addPresentFence
It is used to check whether there is a certain error between the simulated VSync model and the hardware VSync. If so, it is necessary to restart the hardware VSync and train the new VSync model.
DispSync is a simulated VSync event. In Android system graphics system of HWC, we mention HWC2: : Device constructor will pass Device: : registerCallbacks registered three with the Display Device, the Display callback: Hot swap, refresh and VSync signals, the display device being hwC2_device_t. So, is the origin of hwc2_device_t VSync signal, then through a series of callback to SurfaceFlinger. OnVSyncReceived method. OnVSyncReceived passes the hardware VSync timestamp to DispSync via the addResyncSample method to train a simulated VSync event model. Main logic of addResyncSample:
- A few key variables:
mReferenceTime
Record the first hardware VSync timestamp;mResyncSamples
Store all hardware Vsync timestamps (array length NUM_PRESENT_SAMPLES, currently 32);mNumResyncSamples
Indicates the number of time stamps involved in calculation. The actual model calculation is triggered when the timestamp number reaches MIN_RESYNC_SAMPLES_FOR_UPDATE (constant, currently 6)updateModelLocked
. - Calculation process: First, the mean of the time difference between adjacent timestamps (excluding the maximum and minimum values) is calculated as the time period of the simulated Vsync
mPeriod
(This value is theoretically equal to 16.66666… Ms); And then compared to each hardware timestampmPeriod
And calculate the average deviationmPhase
, the range is (-mperiod /2, mPeriod/2); Finally, themPeriod
,mPhase
andmReferenceTime
Set toDispSyncThread
Threads. DispSyncThread
Thread based on the above calculationmPeriod
,mPhase
,mReferenceTime
And eachDispSync::Callback
The requirements of thePhase
, where the timestamp parameter of the Callback, not the system time, is based on the first hardware Vsync timestampmReferenceTime
It’s calculated.- If the trained simulated VSync timestamp and the hardware VSync timestamp are within a certain error range, then addResyncSample returns false, indicating that the simulated hardware VSync model is OK and the hardware VSync timestamp is no longer needed, otherwise returns true. Indicates that more hardware VSync timestamps are required to train the model.
- if
DispSync
If a large error is detected between the hardware VSync and the simulated VSync model, the new VSync model is retrained.
The core code is as follows:
// The Vsync signal of HWComposer is received
void SurfaceFlinger::onVSyncReceived(int32_t type, nsecs_t timestamp) {
bool needsHwVsync = false;
{ // Scope for the lock
Mutex::Autolock _l(mHWVsyncLock);
// Type is 0, indicating the main display
if (type == 0 && mPrimaryHWVsyncEnabled) {
// Train the model. If false is returned, the model is successfully trainedneedsHwVsync = mPrimaryDispSync.addResyncSample(timestamp); }}if (needsHwVsync) {
enableHardwareVsync();
} else { // Disable hardware VSync
disableHardwareVsync(false); }}// DispSyncThread Register listener
struct EventListener {
const char* mName;
nsecs_t mPhase; // Timestamp offset
nsecs_t mLastEventTime; // Timestamp of the last VSync event
sp<DispSync::Callback> mCallback; // Corresponding callback to DispSyncSource
};
Copy the code
Opening and closing to control hardware VSync, provides a special thread EventControlThread, as the internal mVsyncEnabled state changes, will call to the following SurfaceFlinger: : setVsyncEnabled method:
void SurfaceFlinger::setVsyncEnabled(int disp, int enabled) {
// HWC VSync is enabled or disabled, and the following Display::setVsyncEnabled is called
getHwComposer().setVsyncEnabled(disp,
enabled ? HWC2::Vsync::Enable : HWC2::Vsync::Disable);
}
// Hardware module
Error Display::setVsyncEnabled(Vsync enabled)
{
auto intEnabled = static_cast<int32_t>(enabled);
// HWC VSync is enabled or disabled
int32_t intError = mDevice.mSetVsyncEnabled(mDevice.mHwcDevice, mId, intEnabled);
return static_cast<Error>(intError);
}
Copy the code
In addition, hardware VSync can only work when the screen is on, otherwise it has to rely on trained analog hardware VSync, or even software VSync in EventThread (described below). Here is the logic for turning the screen on and off:
void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& hw, int mode){
int32_t type = hw->getDisplayType();
// current hwc power mode
int currentMode = hw->getPowerMode();
if (mode == currentMode) {
return;
}
if (currentMode == HWC_POWER_MODE_OFF) {
getHwComposer().setPowerMode(type, mode);
if (type == DisplayDevice::DISPLAY_PRIMARY) {
// Notify the client server of EventThread: the display is open
mEventThread->onScreenAcquired();
// After the monitor is turned on, you need to re-train the software VSync based on the hardware VSync signal
resyncToHardwareVsync(true);
}
mHasPoweredOff = true;
repaintEverything();
} else if (mode == HWC_POWER_MODE_OFF) {
if (type == DisplayDevice::DISPLAY_PRIMARY) {
// If the monitor is off, disable hardware VSync and stop software VSync training
disableHardwareVsync(true); // also cancels any in-progress resync
// Notify EventThread: the display is off for the client server
mEventThread->onScreenReleased();
}
getHwComposer().setPowerMode(type, mode);
// from this point on, SF will stop drawing on this display
} else{ getHwComposer().setPowerMode(type, mode); }}Copy the code
Why only mEventThread is notified of screen opening and closing, but not mSFEventThread? Because mEventThread serves the client process, even if the main screen is off, the client still relies on VSync events to do some work. So in mEventThread, you have to override the VSync event by software (i.e. waiting for the emulated hardware VSync 16ms, and using the system timestamp if the timer times out). The mSFEventThread is a composite screen service for SurfaceFlinger. When the corresponding screen is closed, the screen is no longer needed, so mSFEventThread can not work.
Adb shell Dumpsys SurfaceFlinger adb shell Dumpsys SurfaceFlinger
// Whether VSync is being used, either by hardware or by EventThread's software
VSYNC state: enabled
// If the screen is on, it is disabled. If the screen is off, it is enabled
soft-vsync: disabled
// Indicates the number of connections of interest
numListeners=90.// Represents the cumulative value of VSync received (including hardware VSync and software VSync for EventThread)
events-delivered: 181752
-1 indicates that the Connection does not receive VSync signals. The specific meaning of Connection->count is described in 👇 below.
0x76fa631480: count=- 1
0x76fa633d00: count=- 1.Copy the code
If simulation hardware VSync events (DispSyncThread) is normal, so EventThread: : onVSyncEvent receive VSync events, and updates the VSync. Count count and the header. The timestamp timestamp.
// DispSyncSource VSync signal is sent to DispSyncSource
void EventThread::onVSyncEvent(nsecs_t timestamp) {
Mutex::Autolock _l(mLock);
// Indicates a VSync event, which can also be a hot swap event
mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
mVSyncEvent[0].header.id = 0;
// Update the VSync timestamp
mVSyncEvent[0].header.timestamp = timestamp;
// The cumulative count of VSync, which is the events-DELIVERED value in the dump message
mVSyncEvent[0].vsync.count++;
mCondition.broadcast();
}
Copy the code
If the screen is closed, so the EventThread: : waitForEvent by Condition. WaitRelative VSync signal out, the core code is as follows:
// The SurfaceFlinger notification screen is off, the simulated hardware VSync signal may not be correct, need to use software simulation, i.e. 16ms
void EventThread::onScreenReleased() {
Mutex::Autolock _l(mLock);
if(! mUseSoftwareVSync) {// disable reliance on h/w vsync
mUseSoftwareVSync = true; mCondition.broadcast(); }}// The SurfaceFlinger notification screen is open, no longer need to use software to emulate VSync
void EventThread::onScreenAcquired() {
Mutex::Autolock _l(mLock);
if (mUseSoftwareVSync) {
// resume use of h/w vsync
mUseSoftwareVSync = false; mCondition.broadcast(); }}/ / it's EventThread: : waitForEvent VSync generated in software
bool softwareSync = mUseSoftwareVSync;
// If the screen is not off, then continue to wait for the analog hardware VSync signal (timeout is 1S), otherwise the wait time is 16ms. During waitRelative, if the emulated hardware VSync signal arrives, the emulated hardware VSync is continued, otherwise the vsync.count and 'header.timestamp' timestamps are updated themselves.
nsecs_t timeout = softwareSync ? ms2ns(16) : ms2ns(1000);
if (mCondition.waitRelative(mLock, timeout) == TIMED_OUT) {
// The code block here deals with the wait timeout case, i.e., the simulated hardware VSync signal does not arrive within 16ms, so software emulation is required
if(! softwareSync) {// The hardware Vsync fails
ALOGW("Timed out waiting for hw vsync; faking it");
}
mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
mVSyncEvent[0].header.id = DisplayDevice::DISPLAY_PRIMARY;
// In software mode, update to system timestamp
mVSyncEvent[0].header.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
mVSyncEvent[0].vsync.count++;
}
Copy the code
Screen is turned off, DispSyncThread thread continues to send simulated hardware VSync events, so under normal circumstances, or through EventThread: : update onVSyncEvent VSync. Count count and the header. Timestamp timestamp. The system updates the vsync.count count and header.timestamp timestamp only when the simulated hardware VSync is not received within 16ms.
Aimed at serving the CPU mEventThread, vsync. Count count and the header. The timestamp timestamp is generally through EventThread: : onVSyncEvent method to update, if the screen is closed, And in 16 ms EventThread: : onVSyncEvent method not updated VSync information, then the EventThread: : active in waitForEvent VSync updates. And to serve the GPU mSFEventThread, would only through EventThread: : onVSyncEvent method updates vsync. Count count and the header. The timestamp timestamp.
To sum up: both the mEventThread serving the CPU and the mSFEventThread serving the GPU are DispSyncSource to receive the simulated hardware VSync event. The difference is that when an EventThread::Connection requests a VSync event, but there is no VSync at this time, the waitRelative wait timeout is different: 1000ms when the screen is on and 16ms when the screen is off. SurfaceFlinger will only notify mEventThread when the screen is on or off. Therefore, mSFEventThread must wait for a timeout of 1000ms.
Vsync. In this paper, we have mentioned on the count count and EventThread: : Connection. The count value, they mainly control notification EventThread: : the frequency of the Connection, when vsync signal arrival, vsync. The count will continue to accumulate, It’s easy. And EventThread: : Connection. The count value is mainly divided into three categories:
- Count >= 1: When vsync.count % count is 0, EventThread::Connection is notified
- Count == 0: the corresponding EventThread::Connection can be notified, but the value is immediately set to -1, indicating that the notification will not be continued next time
- Count ==-1: the Connection will not be notified
The core code is in waitForEvent, as follows:
SignalConnections is a list of connections that need to be notified.
if (connection->count == 0) {
// The VSync event will not be notified next time
connection->count = - 1;
signalConnections.add(connection);
added = true;
} else if (connection->count == 1 || (vsyncCount % connection->count) == 0) {
Continuous Event, and time to report it Continuous VSync event
signalConnections.add(connection);
added = true;
}
Copy the code
EventThread::Connection provides setVsyncRate and requestNextVsync methods to modify the count, which will eventually call the EventThread method as shown below:
// Set the fixed frequency at which the specified Connection receives VSync events
void EventThread::setVsyncRate(uint32_t count, const sp<EventThread::Connection>& connection) {
if (int32_t(count) >= 0) {
Mutex::Autolock _l(mLock);
const int32_t new_count = (count == 0)?- 1 : count;
if(connection->count ! = new_count) {/ / update the connection - > countconnection->count = new_count; mCondition.broadcast(); }}}Request to receive the next VSync event, request once, receive the next VSync
void EventThread::requestNextVsync(
const sp<EventThread::Connection>& connection) {
Mutex::Autolock _l(mLock);
mFlinger.resyncWithRateLimit();
/ / update the connection - > count
if (connection->count < 0) {
connection->count = 0; mCondition.broadcast(); }}Copy the code
- SetVsyncRate needs to be specified only once
vsync.count % connection->count == 0
To receive VSync events- RequestNextVsync requests the next VSync event, requests it once, receives it once, views. Invalidate and SF composits are all used in this way.
As mentioned above, hardware VSync will be turned off when the simulated VSync model and hardware VSync are within a certain error. So how to adjust the simulated hardware VSync model over time if the error becomes larger and larger? The original, SurfaceFlinger sends PresentFence to DispSync via addPresentFence in the last step of the Layer composition (handleMessageRefresh -> postComposition). DispSync checks the error between the Present Fence and the simulated VSync cycle. If the error is too large, HWCVSync is opened and the addResyncSample training logic is re-routed. The core code is as follows:
// handleMessageRefresh -> postComposition
void SurfaceFlinger::postComposition(){
const HWComposer& hwc = getHwComposer();
sp presentFence = hwc.getDisplayFence(HWC_DISPLAY_PRIMARY);
if (presentFence->isValid()) {
if (mPrimaryDispSync.addPresentFence(presentFence)) {
// Open HWC VSync and retrain the VSync model of DispSync
enableHardwareVsync();
} else {
// Disable HWC VSync
disableHardwareVsync(false); }}}// Add Present Fence to check VSync model error
bool DispSync::addPresentFence(const sp<Fence>& fence) {
// Save the Present Fence with a maximum length of NUM_PRESENT_SAMPLES, currently 8
mPresentFences[mPresentSampleOffset] = fence;
mPresentTimes[mPresentSampleOffset] = 0;
mPresentSampleOffset = (mPresentSampleOffset + 1) % NUM_PRESENT_SAMPLES;
mNumResyncSamplesSincePresent = 0;
for (size_t i = 0; i < NUM_PRESENT_SAMPLES; i++) {
const sp<Fence>& f(mPresentFences[i]);
if(f ! =NULL) {
nsecs_t t = f->getSignalTime();
if (t < INT64_MAX) {
mPresentFences[i].clear();
// Record the timestamp of Present FencemPresentTimes[i] = t + kPresentTimeOffset; }}}// Check for errors
updateErrorLocked();
// If the error exceeds a certain threshold, true is returned, indicating that VSync needs to be restarted
return! mModelUpdated || mError > kErrorThreshold; }// Check the mean square error between the saved Present Fence and the existing VSync cycle
void DispSync::updateErrorLocked() {
// Need to compare present fences against the un-adjusted refresh period, since they might arrive between two events.
nsecs_t period = mPeriod / (1 + mRefreshSkipCount);
int numErrSamples = 0;
nsecs_t sqErrSum = 0;
for (size_t i = 0; i < NUM_PRESENT_SAMPLES; i++) {
// mReferenceTime represents the first hardware VSync timestamp previously recorded
nsecs_t sample = mPresentTimes[i] - mReferenceTime;
if (sample > mPhase) {
// Error relative to existing VSync cycles
nsecs_t sampleErr = (sample - mPhase) % period;
if (sampleErr > period / 2) {
sampleErr -= period;
}
// Take the sum of the squared errorssqErrSum += sampleErr * sampleErr; numErrSamples++; }}// Calculate the mean square error
if (numErrSamples > 0) {
mError = sqErrSum / numErrSamples;
} else {
mError = 0; }}Copy the code
When DispSync receives PresentFence added by addPresentFence (currently up to 8), the mean square error of PresentFence and existing VSync cycles is calculated. If the mean square error exceeds constant: If kErrorThreshold = 160000000000, the hardware VSync model is opened and retrained to DispSync emulation.
Finally, to sum upVSync
Operation flow of the model: whenHWC
When the VSync signal is emitted,SurfaceFlinger
The callback will be received and sent toDispSync
.DispSync
These hardware VSync timestamps are logged, and when enough hardware VSync has been accumulated (currently six or more), the VSync cycles and offsets are calculated: mPeriod and mPhase.DispSyncThread
Hardware VSync will be simulated using mPeriod and mPhase, and listeners interested in VSync will be notified, including SurfaceFlinger and the client APP. These listeners are registered as connections to EventThreads. DispSyncThread and EventThread are connected through DispSyncSource as a middleman. After EventThread receives the emulated hardware VSync, it will notify all interested connections, and the SurfaceFlinger will start compositing and the APP will start rendering. When enough hardware VSync is received and the error is within the allowable range, it will be turned off via EventControlThreadHWC
Hardware VSync. It is represented by the flow chart as follows:
OK, the above analysis of VSync event origin and distribution logic, let’s take a look at the two use scenarios of VSync event in detail: one is to drive SurfaceFlinger synthesis screen; One is to drive client APP UI rendering.
driveSurfaceFlinger
Synthesis on the screen
MessageQueue and mSFEventThread are bound in the SurfaceFlinger::init code above. MessageQueue registers EventThread::Connection with mSFEventThread. And by writing event BitTube to monitor the Connection (by EventThread: : Connection: : postEvent write event to BitTube), for example: VSync and hotplug events. The core code is as follows:
// bind mSFEventThread to MessageQueue,
void MessageQueue::setEventThread(const sp<EventThread>& eventThread)
{
mEventThread = eventThread;
/ / create EventThread: : Connection
mEvents = eventThread->createEventConnection();
mEventTube = mEvents->getDataChannel();
// Listen for the BitTube method (using Looper) and call cb_eventReceiver() whenever data arrives. This represents the passthrough argument
mLooper->addFd(mEventTube->getFd(), 0, Looper::EVENT_INPUT,
MessageQueue::cb_eventReceiver, this);
}
// Receive VSync events from EventThread and send them to MessageQueue thread
int MessageQueue::cb_eventReceiver(int fd, int events, void* data) {
MessageQueue* queue = reinterpret_cast<MessageQueue *>(data);
return queue->eventReceiver(fd, events);
}
// The Connection created is registered with EventThread only when it is first referenced
void EventThread::Connection::onFirstRef() {
// When a Connection is first referenced, it is added to the Connection queue maintained by the owning EventThread
mEventThread->registerDisplayEventConnection(this);
}
Copy the code
MessageQueue registers EventThread::Connection with mSFEventThread to receive VSync events. SurfaceFlinger will always receive VSync events. Remember connection->count from above? Is, in fact, through the Connection: : requestNextVsync on-demand request, the “on demand” means a Layer updated. Here we take a closer look through two sequence diagrams:
When is the VSync that drives SurfaceFlinger compositing on the screen requested:
SurfaceFlinger
- Finally emitted when view.invalidate is called
ViewRootImpl
toSurfaceFlinger
themEventThread
Request to receive the next VSync event (more on that later). - After receiving the VSync event, the client finally passes
performTraversals
Trigger the drawing of View system. - Here, take software drawing as an example
Surface
getCanvas
.Canvas
The bottom layer actually corresponds to the bottomSurfaceFlinger
To apply for theGraphicBuffer
. whenCanavs
After drawing is complete, passSurface.unlockCanvasAndPost
The triggerGraphicBuffer
The team entry process is throughBufferQueueProducer
theGraphicBuffer
Queue to ‘BufferQueue’. - After joining the team, pass
BufferQueueCore
In themConsumerListener
Callbacks are notified step by step toSurfaceFlinger
. SurfaceFlinger
throughMessageQueue
tomSFEventThread
Request the next VSync signal.- Vsync arrives after passing
MessageQueue
Distributed toSurfaceFlinger
Main thread, and finally throughhandleMessageRefresh
forLayer
The composition and the screen.
Drive client APP UI rendering
Similarly, the VSync logic that drives client APP UI rendering can also be divided into client request VSync signal and client receive VSync signal, which can also be viewed through two sequence diagrams:
Client request VSync:
How does the client APP request VSync events? When a View requests a redraw, Choreographer requests the mEventThread thread of SurfaceFlinger to receive the next VSync event.
- Finally emitted when view.invalidate is called
ViewRootImpl
toChoreographer
Sign up for aTraversalRunnable
. Choreographer
Save this locallyTraversalRunnable
After, will passDisplayEventReceiver.java
Call to Native layer, and finally step by stepmEventThread
Thread, change connection->count = 0(request to receive next VSync).- When Vsync arrives, the
SurfaceFlinger
The process steps back to the client process and finally firesViewRootImpl
Before the registrationTraversalRunnable
To start rendering the View tree.
How does EventThread::Connection register with mEventThread? The DisplayEventReceiver is registered when it is created, as shown below:
// Constructor for DisplayEventReceiver
DisplayEventReceiver::DisplayEventReceiver() {
// Get the SurfaceFlinger handle across processes
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
if(sf ! =NULL) {
// Register a Connection with SurfaceFlinger's mEventThread thread
mEventConnection = sf->createDisplayEventConnection();
if(mEventConnection ! =NULL) {
// The handle to listen onmDataChannel = mEventConnection->getDataChannel(); }}}// Add a Connection of interest to the mEventThread thread to notify the client APP of the VSync event
sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection() {
return mEventThread->createEventConnection();
}
Copy the code
VSync migration
In the VSync-based rendering model, three components are involved: APP, SurfaceFlinger, and Display, which all start working when VSync arrives. If we ask the View to redraw, the rendering process would look like this:
- View.invalidate request redraw, pass
Choreographer
Request to receive the next VSync signal. - When the first VSync signal arrives, the View starts drawing, and when it’s done drawing is notified to the SurfaceFlinger via the BufferQueue,
SurfaceFlinger
throughMessageQueue
Request to receive the next VSync signal. - When the second VSync signal arrives,
SurfaceFlinger
Start composing Layer and hand in the resultHWC
. - When the third VSync signal arrives, Display begins to Display the synthesized image data.
At the same time, it should be noted that when the screen starts to display frame N, SurfaceFlinger starts to synthesize Layer for frame N+1, and the client starts to process View rendering and generate frame N+2, that is, from View drawing to display on the screen, the delay is at least two frames, about 33ms.
But for most scenes, APP rendering +SurfaceFlinger compositing can be done in less than 16ms. To reduce frame delay, you can configure the timestamp offset for the VSync signal generated by the driver APP rendering and SurfaceFlinger respectively in the device’s boardconfig. mk file: VSYNC_EVENT_PHASE_OFFSET_NS And SF_VSYNC_EVENT_PHASE_OFFSET_NS (i.e. the offset of vsyncPhaseOffsetNs and sfVsyncPhaseOffsetNs passed to DispSyncSource in SurfaceFlinger::init). If not, the default is 0, that is, there is no timestamp offset relative to the emulated hardware VSync signal.
- Vsync-app (VSync of the driver App) = HW_VSync_0 + phase-app
- Vsync-sf (VSync that drives SurfaceFlinger) = HW_VSync_0 + phase-sf
Ideally, App can be drawn within the phase-Sf-Phase-APP time, and SurfaceFlinger can be synthesized within the VSync period-Phase-SF time, so the next VSync signal can be on the screen, that is, the frame delay is 16ms. However, if the drawing time of APP exceeds phase-Sf-Phase-APP, the synthesis can only start after the next Vsync-SF signal. That is, the waiting time for SurfaceFlinger to start synthesis changes from VSync period to VSync period + phase-sf-phase-app. If SurfaceFlinger’s synthesis time exceeds VSync period-phase-sf at the same time, So we have to wait for the next VSync to come on screen, the overall delay is 3 frames, nearly 50ms. Therefore, in general, the system will set phase-Sf-Phase-app to the VSync cycle.
conclusion
This paper mainly introduces the ins and outs of VSync signal, as well as the logic driving the client APP rendering and SurfaceFlinger compositing on the screen.
Refer to the article
- Implement-Vsync
- Systrace basics – Vsync interpretation
- DispSync
- All about DispSync
- Android SurfaceFlinger SW Vsync model