CN

Android Systrace Basics - Vsync Explained

Word count: 2.5kReading time: 15 min
2019/12/01
loading

This is the seventh article in the Systrace series, primarily introducing the Vsync mechanism in Android. This article examines the display of each frame in the Android system from the perspective of Systrace. Vsync is a critical mechanism in Systrace. Although invisible and intangible when operating a phone, we can see in Systrace how the Android system, guided by Vsync signals, orderly performs rendering and composition for each frame, ensuring stable frame rates.

The purpose of this series is to view the overall operation of the Android system from a different perspective using Systrace, while also providing an alternative angle for learning the Framework. Perhaps you’ve read many articles about the Framework but can never remember the code, or you’re unclear about the execution flow. Maybe from Systrace’s graphical perspective, you can gain a deeper understanding.

Table of Contents

Series Article Index

  1. Introduction to Systrace
  2. Systrace Basics - Prerequisites for Systrace
  3. Systrace Basics - Why 60 fps?
  4. Android Systrace Basics - SystemServer Explained
  5. Systrace Basics - SurfaceFlinger Explained
  6. Systrace Basics - Input Explained
  7. Systrace Basics - Vsync Explained
  8. Systrace Basics - Vsync-App: Detailed Explanation of Choreographer-Based Rendering Mechanism
  9. Systrace Basics - MainThread and RenderThread Explained
  10. Systrace Basics - Binder and Lock Contention Explained
  11. Systrace Basics - Triple Buffer Explained
  12. Systrace Basics - CPU Info Explained
  13. Systrace Smoothness in Action 1: Understanding Jank Principles
  14. Systrace Smoothness in Action 2: Case Analysis - MIUI Launcher Scroll Jank Analysis
  15. Systrace Smoothness in Action 3: FAQs During Jank Analysis
  16. Systrace Responsiveness in Action 1: Understanding Responsiveness Principles
  17. Systrace Responsiveness in Action 2: Responsiveness Analysis - Using App Startup as an Example
  18. Systrace Responsiveness in Action 3: Extended Knowledge on Responsiveness
  19. Systrace Thread CPU State Analysis Tips - Runnable
  20. Systrace Thread CPU State Analysis Tips - Running
  21. Systrace Thread CPU State Analysis Tips - Sleep and Uninterruptible Sleep

Main Content

Version note — reviewed October 13, 2026: The article and captures date from 2019; their full device builds were not recorded. Interpret DispSync, phase dumps, and old function names against AOSP android-9.0.0_r1 / android-10.0.0_r1. Android 16 uses a different prediction and callback-scheduling implementation. The 60 Hz and 0 / 4 ms offsets are example conditions, not universal device settings.

For newer captures, pair this article with Perfetto 8: Vsync Mechanism and Performance Analysis, following the same frame through the App, RenderThread, SurfaceFlinger, GPU, and Display/HWC. On Android 12 (API 31) and later, FrameTimeline can compare expected and actual timing when the required data was captured. Missing tracks are not zero duration, and adjacent slices do not alone establish frame identity.

Hardware timing and software scheduling work together. HWC reports display-related Vsync timestamps to SurfaceFlinger. Older DispSync builds a periodic model and uses software timing to issue App/SF callbacks at selected phases. That model can run while hardware callbacks are temporarily disabled. Thus “Vsync is now mostly hardware-generated” does not explain the origin of every App callback.

The old diagram’s retire fence provides display feedback; it is not each App’s GPU-completion notification. Retire/present fence terminology, meaning, and hardware timing offsets vary across implementations and should not be collapsed into one instant.

As described in Choreographer rendering, the App requests frame callbacks as needed and executes doFrame through its Looper. An unchanged screen need not redraw every display cycle. Common 60 / 90 / 120 Hz intervals are approximately 16.67 / 11.11 / 8.33 ms; App frame rate, display refresh rate, and callback rate need not match. See also Perfetto 6: Why 120Hz?.

SurfaceFlinger processes transactions, selects Buffers, and arranges composition according to scheduling and layer state. It does not unconditionally redraw every Surface on every signal. Here are the relevant legacy Systrace tracks:

Every 0/1 transition on these VSYNC-app / VSYNC-sf counters marks an event, not only rising edges. The high interval is not execution time. Software callbacks, delivery to the App, and actual CPU scheduling have distinct times, while a queued-Buffer counter does not prove GPU completion.

Android Graphics Data Flow

Use a conventional hardware-accelerated View’s older GL path to follow content toward the display. Games, software rendering, and other backends can have different functions and stages.

  1. doFrame processes due input, animation, and Traversal work, including necessary Measure, Layout, and Draw. A DisplayList records drawing operations; it is not a literal list of OpenGL commands.
  2. RenderThread synchronizes drawing state and drives the backend, potentially preparing resources, encoding, and submitting work. flush drawing commands is not simply a CPU-to-GPU upload. Shared memory does not eliminate cache coherency, lifetime, or synchronization requirements.
  3. GPU execution is usually asynchronous, but CPU threads may still wait for Buffers, driver queues, or fences. Submission is not guaranteed to return immediately on every physical device.
  4. The GL/EGL path uses operations such as eglSwapBuffersWithDamageKHR to hand off a Buffer and synchronization information. Swap / queueBuffer return is neither GPU completion nor presentation. A Vulkan path need not contain these EGL names.
  5. SurfaceFlinger arranges client/device composition using layer state and HWC negotiation. RenderEngine can use the GPU for client composition, while device composition can use display hardware. Fences constrain consumption and can be waited on by GPU/display hardware; SF’s main thread need not block on each layer before GPU-compositing everything. Legacy onMessageReceived, invalidate, and refresh names are not universal modern interfaces. See Hardware Composer.
  6. The display pipeline presents/scans out the result at an appropriate time. Buffer submission, a present fence signal, and every pixel finishing its visible update are different events. Every composition mode does not require one consolidated final frame buffer first.

This historical diagram includes both SF GPU composition and display-controller paths, illustrating why composition is not always entirely GPU work.

These diagrams introduce producers, Buffers, consumers, and display output rather than the entire Android 16 process/queue layout. Modern View paths can also involve BLASTBufferQueue.

Graphic Data Flow in Systrace

Follow the App, SurfaceFlinger, and composer service in this older capture:

  1. The App/SF markers here have nearly the same phase. That does not mean both threads run at the same nanosecond.
  2. SF selects a Buffer already eligible for that composition, labeled App frame N−1 in the image.
  3. The App processes frame N and submits a new Buffer through main-thread and RenderThread work. The queue counter changes; the image’s “rendering complete” annotation is shorthand for submission, not proof of GPU completion.
  4. Later SF work handles frame N while the App can prepare the next frame. This Buffer was submitted in step 3, not “rendered in step 2” as the original text stated.

This is one steady-pipeline sample. Old content may remain visible, and new frames may be delayed, replaced, or dropped. Use Buffer/frame identity, fences, and FrameTimeline where available instead of mechanically assigning N−1 / N to every capture.

Vsync Offset

Older DispSync maintains a model from hardware timestamps and display feedback, then schedules callbacks with App/SF phases:

disp_sync_arch

phase-app and phase-sf are relative to the reference display timing. In this simplified diagram, the App starts earlier and SF works at a later phase.

For comparison, this article calls Δ = phase-sf − phase-app the offset between the two. That difference is not the entire Vsync configuration. Phases can be negative or span a cycle, and early/late configurations can change. They are not universally limited to 0–16.6 ms, and the article has no evidence that most manufacturers set the difference to zero. Equal planned phases do not guarantee simultaneous delivery or CPU execution.

adb shell dumpsys SurfaceFlinger can expose the device’s configuration, with version-dependent fields. These two historical dumps are retained as examples, not Android 16’s fixed output format.

Difference = 0: App and SF each have a phase of 1,000,000 ns, or 1 ms relative to the reference. Neither phase itself is zero.

1
2
3
4
5
6
Sync configuration: [using: EGL_ANDROID_native_fence_sync EGL_KHR_wait_sync]
DispSync configuration:
app phase 1000000 ns, sf phase 1000000 ns
early app phase 1000000 ns, early sf phase 1000000 ns
early app gl phase 1000000 ns, early sf gl phase 1000000 ns
present offset 0 ns refresh 16666666 ns

Difference = 4 ms: 6,000,000 − 2,000,000 = 4,000,000 ns. The 16,666,666 ns period is approximately 60 Hz.

1
2
3
4
5
6
7
Sync configuration: [using: EGL_ANDROID_native_fence_sync EGL_KHR_wait_sync]

VSYNC configuration:
         app phase:   2000000 ns          SF phase:   6000000 ns
   early app phase:   2000000 ns    early SF phase:   6000000 ns
GL early app phase:   2000000 ns GL early SF phase:   6000000 ns
    present offset:         0 ns      VSYNC period:  16666666 ns

present offset is a separate display-feedback/timing setting, not the App/SF phase difference. Early and early-GL rows are candidate configurations; a static dump does not identify which one a particular frame used.

Offset = 0

Here is the original zero-difference example:

The planned callbacks approximately align, and the App’s new Buffer is used by the following SF work. The image’s roughly 16.6 ms label spans Vsync events. Waiting from Buffer submission to consumption is only the remaining part of that cycle, not invariably another full 16.6 ms.

At this point you may be wondering: if the App’s Buffer is ready, could SurfaceFlinger use it sooner and save some of that wait? A later SF phase can allow a new App frame to become eligible before that SF work. This is not an event-driven policy that immediately composites whenever queueBuffer completes. Latch/composition conditions still apply, with enough time needed for GPU and SF/HWC work before the target presentation.

Offset ≠ 0

The original labels this example as a 4 ms phase difference:

The red rectangle measures the gap between scheduling markers, not App rendering duration, GPU completion, or total touch-to-display latency. Follow that frame’s submission, synchronization, and display result to establish whether it presents earlier.

Pros and Cons of Offset

Phase tuning trades input freshness against App work time and composition headroom. Neither a larger nor a smaller value is universally better.

  1. With a short Δ, the new Buffer may miss that composition. Old content can usually remain visible; the App does not disappear. In an idealized periodic model, the next SF time might be T + Δ after App work starts, but that is not a complete input-to-display latency formula.
  2. A long Δ can reduce SF/HWC and related GPU headroom enough to miss the target presentation. It may still help under another workload; it does not simply become ineffective.

Android 16 update: Scheduler works with components including VSyncPredictor, VSyncReactor, and VSyncDispatchTimerQueue. The timer queue uses target Vsync, workDuration, and readyDuration to calculate wakeup times and handles scheduling updates. Old DispSync constants or one fixed phase pair cannot explain every frame. See VSyncDispatchTimerQueue.cpp, android-16.0.0_r1.

Android 15 ARR also requires compatible display hardware and HAL support. ARR can decouple display Vsync cadence from refresh intervals; the official example uses roughly 240 Hz Vsync steps with a maximum 120 Hz refresh rate. A Vsync period is therefore not automatically the App frame rate. See Adaptive refresh rate.

HW_Vsync

Disabling HWC Vsync event callbacks does not stop panel refresh or GPU execution. Software prediction can continue scheduling App/SF events without receiving every hardware callback; resynchronization, display changes, and other conditions can request new samples.

The original 500 ms claim needs correction. This is the complete method from Android 9 SurfaceFlinger:

1
2
3
4
5
6
7
8
9
10
11
void SurfaceFlinger::resyncWithRateLimit() {
static constexpr nsecs_t kIgnoreDelay = ms2ns(500);

// No explicit locking is needed here since EventThread holds a lock while calling this method
static nsecs_t sLastResyncAttempted = 0;
const nsecs_t now = systemTime();
if (now - sLastResyncAttempted > kIgnoreDelay) {
resyncToHardwareVsync(false);
}
sLastResyncAttempted = now;
}

It compares the current call with the previous call to this rate-limiting method, updating sLastResyncAttempted every time. It does not compare with the last composition, and this is not the only way to enable hardware callbacks. Android 10 Scheduler has a similar 500 ms condition; the corresponding limit in Android 16 Scheduler::resync() is 750 ms. Neither is a public API guarantee.

Distinguish transitions on HW_VSYNC_0 from the enabled interval on HW_VSYNC_ON_0. The latter is not a panel power switch. Missing transitions can reflect disabled callbacks or capture coverage, not a stopped display.

Without HW_VSYNC

The old diagram shows an established software model continuing to deliver events, without every App request waiting for a new HWC callback.

With HW_VSYNC

This diagram adds resynchronization, hardware timestamp reception, and model updates. Its arrow directly to the App is simplified: legacy EventThread distributes events through Connection / BitTube, followed by native reception and Looper/Choreographer processing. The server does not directly invoke Java onVsync.

For sample counts, use Android 9 DispSync.h and DispSync.cpp:

  • The resynchronization ring stores up to 32 timestamps. updateModelLocked() re-estimates the model at 6 or more. Six timestamps provide five intervals; removing the minimum and maximum leaves three intervals for the period average. That is not a minimum of three hardware events.
  • mPeriod is the period; mPhase is the model phase, used with a reference time and listener-specific phases. Initialization supplies a display period, and the first resync sample updates the reference. Six samples are not a prerequisite for any software callback to exist.
  • Valid present-fence timestamps provide feedback. mError is a mean squared error in ns²; the version’s 160000000000 threshold is (400 µs)². Model validity and half-threshold hysteresis affect continued sampling, while ignoring present fences takes a different branch. Missing valid samples or an error reset to zero does not establish measured zero error.

These are version-specific details, not permanent Android “6/32” rules. Android 16’s VSyncPredictor has its own sample parameters and prediction logic. The original SW-VSYNC article remains additional historical reading.

Other Addresses for This Article

The follow-up on this blog is Perfetto 8: Vsync Mechanism and Performance Analysis.

References

  1. VSYNC
  2. Sync and Vsync
  3. Android Graphics Architecture
  4. Generation and Transmission of SW-VSYNC
  5. DispSync Implementation Details

About Me && Blog

Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”

  1. Blogger Intro: Includes personal WeChat and WeChat group links.
  2. Blog Content Navigation: A guide for my blog content.
  3. Curated Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome to recommend projects/articles.
  4. Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.

One walks faster alone, but a group walks further together.

Scan WeChat QR Code

CATALOG
  1. 1. Table of Contents
  • Series Article Index
  • Main Content
  • Android Graphics Data Flow
  • Graphic Data Flow in Systrace
  • Vsync Offset
    1. 1. Offset = 0
    2. 2. Offset ≠ 0
    3. 3. Pros and Cons of Offset
  • HW_Vsync
    1. 1. Without HW_VSYNC
    2. 2. With HW_VSYNC
  • Other Addresses for This Article
  • References
  • About Me && Blog