This is the eighth article in the Perfetto series, providing an in-depth introduction to the Vsync mechanism in Android and its representation in Perfetto. The article will analyze how the Android system performs frame rendering and composition based on Vsync signals from Perfetto’s perspective, covering core concepts such as Vsync, Vsync-app, Vsync-sf, and VsyncWorkDuration.
With the popularization of high refresh rate screens, understanding the Vsync mechanism has become increasingly important. This article uses 120Hz refresh rate as the main narrative thread to help developers understand the working principles of Vsync in modern Android devices, and how to observe and analyze Vsync-related performance issues in Perfetto.
Version note: Android 17 (API 37) is the current technical reference; Android 13 is mentioned where a major mechanism was introduced. Code snippets are abbreviated AOSP main excerpts, with
...omitting some branches. They are not verbatim Android 17 release source; verify against your target branch.
Table of Contents
- Series Catalog
- What is Vsync
- Basic Working Principles of Vsync in Android
- Observing Vsync in Perfetto
- How Android App Frames Work Based on Vsync
- References
- About Me && Blog
Series Catalog
- Android Perfetto Series Catalog
- Android Perfetto Series 1: Introduction to Perfetto
- Android Perfetto Series 2: Capturing Perfetto Traces
- Android Perfetto Series 3: Familiarizing with Perfetto View
- Android Perfetto Series 4: Opening Large Traces via Command Line
- Android Perfetto Series 5: Android App Rendering Flow Based on Choreographer
- Android Perfetto Series 6: Why 120Hz? Advantages and Challenges of High Refresh Rates
- Android Perfetto Series 7: MainThread and RenderThread Deep Dive
- Android Perfetto Series 8: Understanding Vsync Mechanism and Performance Analysis
- Android Perfetto Series 9: CPU Information Interpretation
- Android Perfetto Series 10: Binder Scheduling and Lock Contention
- Android Perfetto Series 11: PerfettoSQL, Trace Processor and Regression Detection
- Android Perfetto Series 12: Trace Dataflow and Data Loss
- Android Perfetto Series 13: Perfetto SDK, Track Event and App Field Traces
- Android Perfetto Series 14: heapprofd and Memory Profiling
- Android Perfetto Series 15: Boot Traces and Long-running Field Tracing
- Android Perfetto Series 16: GPU, Power Counters and Hardware Bottlenecks
- Android Perfetto Series 17: Scenario Automation and Platform Tracing
- Android Perfetto Series 18: Input Response Latency
- Video (Bilibili) - Android Perfetto Basics and Case Sharing
- Video (Bilibili) - Android Perfetto: Trace Graph Types - AOSP, WebView, Flutter + OEM System Optimization
If you haven’t read the Systrace series yet, here are the links:
- Systrace Series Catalog: A systematic introduction to Systrace, Perfetto’s predecessor, and learning about Android performance optimization and Android system operation basics through Systrace.
- Personal Blog: My personal blog, mainly Android-related content, with some life and work-related content as well.
Welcome to join the WeChat group or community on the About Me page to discuss your questions, what you’d most like to see about Perfetto, and all Android development-related topics with fellow group members.
What is Vsync
Vsync (Vertical Synchronization) is the core mechanism of the Android graphics system. Its existence is to solve a fundamental problem: how to keep the software rendering rhythm synchronized with the hardware display rhythm.
Before the Vsync mechanism existed, the common problem was Screen Tearing. When the display reads the framebuffer while the GPU writes the next frame, the same refresh will show inconsistent top and bottom parts of the image.
What Problems Does Vsync Solve?
The core idea of the Vsync mechanism is very simple: make all rendering work proceed according to the display’s refresh beat. Specifically:
- Synchronization Signal: The display emits a Vsync signal every time it starts a new refresh cycle.
- Frame Beat and Production: An app with pending frame callbacks can request Vsync through Choreographer; a Vsync does not make every app draw. After CPU submission, the GPU may execute asynchronously. SurfaceFlinger composites when it has work scheduled for a display opportunity.
- Buffering Mechanism: Using double buffering or triple buffering technology ensures the display always reads complete frame data.
This way, frame production and display are aligned with Vsync as the beat. Taking 120Hz as an example, there’s a display opportunity every 8.333ms; the application needs to submit a compositable Buffer to SurfaceFlinger before this window. The key constraint is the timing of queueBuffer/acquire_fence/present_fence; if it doesn’t catch up with this cycle, it will be delayed to the next cycle for display.
Basic Working Principles of Vsync in Android
Android system’s Vsync implementation is much more complex than the basic concept, needing to consider multiple different rendering components and their coordinated work.
Layered Architecture of Vsync Signals
In the Android system, there isn’t just one simple Vsync signal. In fact, the system maintains multiple Vsync signals for different purposes:
Hardware Vsync (HW Vsync):
This is the lowest-level Vsync signal, generated by the display hardware (HWC, Hardware Composer). Its frequency strictly corresponds to the display’s refresh rate, for example, a 60Hz display generates HW Vsync every 16.67ms, and a 120Hz display generates one every 8.333ms. (Hardware Vsync callbacks are managed by HWC/SurfaceFlinger, see frameworks/native/services/surfaceflinger related implementation)
However, HW Vsync is not always on. Since frequent hardware interrupts consume considerable power, the Android system adopts an intelligent strategy: only enabling HW Vsync when precise synchronization is needed, and using software prediction to generate Vsync signals most of the time.
Vsync-app (Application Vsync):
This is a Vsync signal specifically used to drive application layer rendering. When an application needs to perform UI updates (such as user touch, animation running, interface scrolling, etc.), the application will request to receive Vsync-app signals from the system.
1 | // frameworks/base/core/java/android/view/Choreographer.java |
Vsync-app is requested on-demand. If the application interface is static with no animations or user interaction, the application won’t request Vsync-app signals, and the system won’t generate Vsync events for this application.
Vsync-sf (SurfaceFlinger Vsync):
This is a Vsync signal specifically used to drive SurfaceFlinger for layer composition. SurfaceFlinger is the service in the Android system responsible for compositing all application layers into the final image.
Vsync-appSf (Application-SurfaceFlinger Vsync):
A new signal type introduced in Android 13. To eliminate the timing ambiguity brought by the old design where the sf EventThread both woke up SurfaceFlinger and served some Choreographer clients, the system separated the two responsibilities: vsync-sf focuses on waking up SurfaceFlinger, while vsync-appSf faces clients that need to synchronize with SurfaceFlinger.
Observing Vsync in Perfetto
Perfetto traces contain multiple Vsync-related Tracks. Understanding the meaning of these Tracks helps analyze performance issues.
In the SurfaceFlinger Process:
vsync-app
In this trace, the counter alternates between 0 and 1; its transitions help show the recorded scheduling cadence. It does not prove that any particular app received a callback.
vsync-sf
Displays SurfaceFlinger Vsync signal status. When there’s no Vsync Offset, it changes synchronously withvsync-app.
vsync-appSf
Added in Android 13+, serves special Choreographer clients that need to synchronize with SurfaceFlinger.
HW_VSYNC
Displays hardware Vsync enabled status. Value of 1 means enabled, value of 0 means disabled. To save power, hardware Vsync is only enabled when precise synchronization is needed.
In the Application Process:
FrameDisplayEventReceiver.onVsync Slice Track:
Displays the time point when the application receives Vsync signals. The connection is established through Binder, while events are delivered through BitTube/Looper channels, so timing may be slightly later than vsync-app in SurfaceFlinger.

UI Thread Slice Track:
Contains Choreographer#doFrame and related Input, Animation, Traversal, etc. Slices. Each doFrame corresponds to one frame’s processing work.

RenderThread Slice Track:
Contains DrawFrame, syncAndDrawFrame, queueBuffer, etc. Slices, corresponding to render thread work.

How Android App Frames Work Based on Vsync
Each frame of an Android application completes the entire process from rendering to display based on the Vsync mechanism, involving multiple key steps.

Process Overview (In Order)
- Trigger Redraw/Input:
View.invalidate(), animation, data change, or input event triggers →ViewRootImpl.scheduleTraversals()→Choreographer.postCallback(TRAVERSAL) - Request Vsync:
Choreographerrequests next Vsync (app phase) throughDisplayEventReceiver.scheduleVsync() - Receive Vsync: After
DisplayEventReceiver.onVsync()receives Vsync, it posts an asynchronous message to the main thread message queue - Main Thread Frame Processing:
Choreographer.doFrame()executes five types of callbacks in order:INPUT → ANIMATION → INSETS_ANIMATION → TRAVERSAL → COMMIT - Rendering Submission:
RenderThreadexecutessyncAndDrawFrame/DrawFrame, CPU records GPU commands,queueBuffersubmits to BufferQueue - Composition Display: With pending updates,
SurfaceFlingerschedules composition (GPU or HWC) aroundvsync-sf, generatespresent_fence, and outputs to display - Frame Completion Measurement: Determines whether displayed on schedule through
FrameTimeline(PresentType/JankType) andacquire/present_fence
Below we’ll expand on each step’s key implementation and Perfetto observation points.
When Does App Request Vsync Signals
Applications don’t request Vsync signals all the time. The following are common reasons an app may request the next Vsync:
Scenarios Triggering Vsync Request:
- UI Update Needs: When View calls
invalidate() - Animation Execution: When ValueAnimator, ObjectAnimator, and other animations start
- User Interaction: Touch events, key events, etc. requiring UI response
- Data Changes: RecyclerView data updates, TextView text changes, etc.
Complete Process of App Requesting Vsync
When an application needs to update UI, it requests Vsync signals through the following process:
1 | // 1. UI component requests redraw |
TRAVERSALis still the most common trigger, but in AOSP main it is not the only path that requests Vsync.
How Main Thread Listens for Vsync Signals
The application main thread listens for Vsync signals through DisplayEventReceiver. This process involves several key steps:
1. Establish Connection:
1 | // frameworks/base/core/java/android/view/Choreographer.java |
2. Receive Vsync Signal:
1 |
|
Several Remaining Questions
Q1: Why not execute doFrame() directly in onVsync()?
- Thread boundary: In the
Choreographerpath,onVsync()runs on the bound Looper (usually the main thread). Routing work through the message queue beforedoFrame()keeps frame scheduling unified and ordered. - Scheduling control: Precisely align execution moment through
sendMessageAtTime() - Queue semantics: Enter main thread MessageQueue, ensure coordination with other high-priority tasks
Q2: If Vsync message arrives but main thread is busy, will it be lost?
- Not strictly “no loss”. A single
scheduleVsync()is a one-shot request. If the main thread stays busy, multiple hardware beats may be skipped and only a newer frame gets processed. Use FrameTimeline to decide whether this became visible jank. - AOSP
DisplayEventDispatcher::processPendingEventsexplicitly overwrites older vsync events with newer ones (only the most recent one is dispatched).
Q3: Must CPU/GPU complete within a single Vsync cycle? If any link exceeds 1 vsync, will it cause frame drops?
Modern Android systems use multi-buffering (usually triple buffering) mechanism:
Application side: Front Buffer (displaying) + Back Buffer (rendering) + possible third Buffer
SurfaceFlinger side: Also has similar buffering mechanism
This means even if one application frame exceeds the Vsync cycle, it won’t necessarily drop frames immediately.
GPU work is asynchronous, and multi-buffering can overlap production and consumption but may add queueing latency. Buffer counters alone cannot prove that no frame was dropped; align the target layer’s FrameTimeline with actual presentation.

Q5: How do GPU and CPU coordinate?:
GPU rendering is asynchronous, bringing additional complexity:
- CPU works normally, GPU becomes bottleneck: Even if application main thread completes work within Vsync cycle, excessive GPU rendering time will still cause frame drops
- GPU Fence mechanism: In the latch stage, the critical sync point is usually
acquire fence(when the Buffer is safe to read).present fenceis more about when the frame is actually presented on display. With the systemLatch Unsignaled Buffersstrategy, SurfaceFlinger can move forward first under certain conditions, then wait on fence only when needed, hiding part of the latency.

Q6: What is the real role of Vsync Phase (phase difference)?:
- Shift the available window: Changing the app/SF phase can sometimes let a frame reach an earlier composition opportunity, but it does not universally reduce latency from three cycles to two.
- Move the deadline: A different phase changes available time; it cannot replace reducing excessive app or GPU work.
VsyncWorkDurationis closer to scheduling budget visualization (workDuration/readyDuration) and is not a 1:1 mapping to a single app offset value. Correlate it withvsync-app/sfand FrameTimeline.- The time period shown in the figure below is the app offset (13.3ms) configured on my phone

Technical Implementation of Vsync Offset / WorkDuration
In current AOSP main, the entry point is the VsyncConfiguration abstraction, which returns a VsyncConfigSet organized by scenario. Implementation-wise, PhaseOffsets is the legacy path, while WorkDuration is one of the common paths in newer systems:
1 | // frameworks/native/services/surfaceflinger/Scheduler/VsyncConfiguration.h |
Key Concepts:
workDuration/readyDuration: scheduling budget and lead time used to compute callback wakeupapp/sf offset: still useful as an analysis lens, but it is an outcome of config sets plus scheduler model- In common practice, “app/sf offset delta” means their phase gap (often viewed as
|sfOffset - appOffset|; sign conventions depend on device implementation and metric definition)
Simplified Phase-Timing Example
The following 120Hz timeline uses hypothetical times to illustrate how phase can change a presentation opportunity; it is not a device measurement:
Without Offset (Traditional Method):
- T0: Application and SurfaceFlinger receive Vsync simultaneously
- T0+3ms: Application completes rendering
- T0+8.333ms: Next Vsync, SurfaceFlinger starts composition
- T0+16.666ms: User sees the image (total latency 16.666ms)
With Offset (Optimized Method):
- T0+1ms: Application receives Vsync-app, starts rendering
- T0+3ms: Application completes rendering, submits Buffer
- T0+4ms: SurfaceFlinger receives Vsync-sf, immediately starts composition
- T0+6ms: SurfaceFlinger completes composition
- T0+8.333ms: User sees the image (total latency 8.333ms)
In this idealized schedule, the changed phase lets the frame reach an earlier display opportunity. It is not a measured or universal halving of input-to-display latency.
Actual Time Budget Allocation:
For a 120Hz mode (8.333ms nominal interval):
- App and SF windows: Their deadlines depend on the scheduler’s phases; adding their durations as if all work ran serially in one interval gives the wrong budget.
- Overlapping work: Different frames can be in the App, GPU, and SurfaceFlinger stages at the same time; buffering does not guarantee an on-time present.
- GPU limitation: Sustained late GPU completion can delay the acquire fence; measure the target device rather than assuming a fixed GPU duration.
Real Causes of Frame Drops:
- Application timeout + Buffer exhaustion: Continuous multiple frame timeouts lead to no available Buffers in BufferQueue
- GPU rendering timeout: Even if CPU work is normal, GPU rendering timeout will still cause frame drops
- SurfaceFlinger timeout: System-level composition timeout affects all applications
- System resource competition: CPU/GPU/memory and other resources occupied by other processes
Complete Code Flow of Vsync Signals
The complete chain of Vsync signals from hardware to application layer is as follows.
Key Code Paths Aligned with AOSP main (Condensed Excerpts)
The snippets below are aligned with current AOSP main method signatures, with non-essential branches/logging omitted.
1) Choreographer requests the next Vsync (Java)
1 | // frameworks/base/core/java/android/view/Choreographer.java |
2) Choreographer receives Vsync (Java)
1 | // frameworks/base/core/java/android/view/Choreographer.java |
3) JNI bridge: DisplayEventDispatcher (C++)
1 | // frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp |
4) Native transport: DisplayEventReceiver + BitTube (C++)
1 | // frameworks/native/libs/gui/DisplayEventReceiver.cpp |
5) SurfaceFlinger scheduling and distribution (C++)
1 | // frameworks/native/services/surfaceflinger/Scheduler/VSyncDispatch.h |
1 | // frameworks/native/services/surfaceflinger/Scheduler/EventThread.cpp |
Key Timing Point Analysis
Through the above code flow, we can see the complete timing chain:
- HWC hardware callbacks or software-predicted timing → SurfaceFlinger Scheduler updates display cadence
- Scheduler computes wakeup window →
VSyncDispatch::schedule(...) - EventThread creates/distributes events →
DisplayEventReceiver::EventviaBitTube - App-side Native receives event →
DisplayEventDispatcher::dispatchVsync(...) - Java
FrameDisplayEventReceivercallback → async message enters Looper queue Choreographer#doFrame(...)executes → Input/Animation/Traversal/Commit
Each link has different responsibilities and optimization points. Understanding the complete process helps analyze Vsync-related performance issues in Perfetto.
FrameTimeline
Both App and SurfaceFlinger have FrameTimeline

- Tracks:
Expected Timeline,Actual Timeline - PresentType/JankType:
- PresentType indicates how this frame was presented (e.g., On-time, Late), JankType indicates jank type source
- Common JankType:
AppDeadlineMissed,BufferStuffing,SfCpuDeadlineMissed,SfGpuDeadlineMissed, etc.
- Operation Steps (Perfetto UI):
- Select target
Surface/Layerin application process or filter using FrameToken - Align Expected with Actual, view offset and color coding
- Drill up:
Choreographer#doFrame,RenderThread,queueBuffer,acquire/present_fence
- Select target
- Avoiding Misjudgment:
- Judging frame drops solely by
doFrameduration is unreliable; use FrameTimeline’s PresentType/JankType as standard - Multi-buffering may mask single frame timeout, need to look at consecutive frames and Buffer availability
- Judging frame drops solely by
Impact of Refresh Rate/Display Mode/VRR on Vsync and Offset/Prediction
- Mode Switching: Refresh rate changes will reconfigure
VsyncConfiguration, affecting app/sf Offset and prediction model;- Perfetto: Check
display mode changeevents and subsequentvsyncinterval changes
- Perfetto: Check
- VRR (Variable Refresh Rate): Target cycle is not constant, software prediction relies more on present_fence feedback calibration;
- Perfetto: Observe
vsyncinterval distribution andpresent_fencedeviation
- Perfetto: Observe
- Multi-display/External Display: Hardware can report vsync per
physicalDisplayId; however, app-side Choreographer usually still follows internal/pacesetter timing (details vary by branch and version). Confirm whether you’re reading HWC/SF tracks or app tracks before drawing conclusions;- On Android 12–17, distinguish framework/HWC per-display VSYNC capability from app-side
Choreographerrequest paths; the latter may depend on internal-display or pacesetter implementation, so verify against the target branch and device trace - Perfetto: Filter related Counter/Slice by display ID
- On Android 12–17, distinguish framework/HWC per-display VSYNC capability from app-side
Perfetto Practice Checklist (Recommended Viewing Order)
- Vsync Signal and Cycle
- Whether
vsync-app / vsync-sf / vsync-appSfintervals are stable (60/90/120Hz corresponding cycles) - Whether there are abnormally dense/sparse Vsyncs (prediction jitter)

- Whether
- Vsync Phase Difference Configuration
- Whether
VsyncWorkDurationmatches device’s expected app/sf Offset - Whether app and sf sequence matches “draw first then composite” strategy

- Whether
- FrameTimeline Interpretation
- First look at
PresentType, thenJankType; confirm whether it’s app or SF/GPU side issue - Select target Surface/FrameToken to locate specific frame

- First look at
- Application Main Thread and Render Thread
Choreographer#doFrameeach stage duration (Input/Animation/Traversal)- Whether
RenderThread‘ssyncAndDrawFrame/DrawFrameduration is abnormal
- BufferQueue and Fence
- Producer: After RenderThread
queueBuffer, the Buffer enters the consumable queue. Whether SF can latch it immediately still depends onacquire fence.present fenceis mainly for confirming when the frame is actually presented. Newer versions may advance with unsignaled buffers under specific policy, then wait later when needed.
- Consumer SF and BufferTX: When there is a pending update, SF may use a new Buffer for the target layer. BufferTX at 0 usually means no pending Buffer on that layer. This is normal for static content; investigate app starvation only if the layer was expected to update. It does not mean SF globally stopped compositing.

- Producer: After RenderThread
- Composition Strategy and Display
- Whether SF frequently goes ClientComposition; whether HWC validate/present is abnormal
- Whether there’s obvious prediction deviation during multi-display/mode switching/VRR

- Resources and Other Interference
- CPU competition (big core occupation), GPU busy, IO/memory jitter (GC/compaction)
- Whether other foreground apps/system services occupy key resources

References
- Android Graphics Architecture
- VSYNC Implementation Guide
- Frame Pacing
- Perfetto Documentation
- Android Perfetto Series 5: Android App Rendering Flow Based on Choreographer
- Android Perfetto Series 6: Why 120Hz? Advantages and Challenges of High Refresh Rates
- Vsync offset Related Technical Analysis
- Android 13/14 High Version SurfaceFlinger VSYNC-app/VSYNC-appSf/VSYNC-sf Analysis
- AOSP - Choreographer.java (main)
- AOSP - android_view_DisplayEventReceiver.cpp (main)
- AOSP - DisplayEventDispatcher.h (main)
- AOSP - DisplayEventReceiver.cpp (main)
- AOSP - VSyncDispatch.h (main)
- AOSP - EventThread.cpp (main)
- Android Multi-display (official)
- AOSP - VsyncConfiguration.h (main)
- AOSP - DisplayEventDispatcher.cpp (main)
- Unsignaled buffer latch (official)
About Me && Blog
Below is a personal introduction and related links. I look forward to communicating with you all. When three people walk together, one of them can be my teacher!
- Blogger Personal Introduction: Contains personal WeChat and WeChat group links.
- Blog Content Navigation: A navigation of personal blog content.
- Excellent Blog Articles Collected and Organized by Individuals - Must-Know for Android Performance Optimization: Welcome everyone to recommend yourself and recommend (WeChat private chat is fine)
- Android Performance Optimization Knowledge Planet: Welcome to join, thanks for support~
One person can go faster, a group of people can go further
