Synchronization Framework

The synchronization framework explicitly describes dependencies between different asynchronous operations in the Android graphics system. The framework provides an API that enables components to indicate when buffers are released. The framework also allows synchronization primitives to be passed between drivers from the kernel to userspace and between userspace processes themselves.

For example, an application may queue up work to be performed in the GPU. The GPU starts drawing that image. Although the image hasn’t been drawn into memory yet, the buffer pointer is passed to the window compositor along with a fence that indicates when the GPU work will finish. The window compositor starts processing ahead of time and passes the work to the display controller. In a similar manner, the CPU work is done ahead of time. Once the GPU finishes, the display controller immediately displays the image.

The synchronization framework also lets implementers leverage synchronization resources in their own hardware components. Finally, the framework provides visibility into the graphics pipeline to help with debugging.

Explicit synchronization

Explicit synchronization enables producers and consumers of graphics buffers to signal when they're finished using a buffer. Explicit synchronization is implemented in kernel-space.

The benefits of explicit synchronization include:

  • Less behavior variation between devices
  • Better debugging support
  • Improved testing metrics

The sync framework has three object types:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline is a monotonically increasing timeline that vendors should implement for each driver instance, such as a GL context, display controller, or 2D blitter. sync_timeline counts jobs submitted to the kernel for a particular piece of hardware. sync_timeline provides guarantees about the order of operations and enables hardware-specific implementations.

One implementation of sync_timeline is offered as a CPU-only reference called sw_sync (software sync). If a vendor doesn't have a hardware-backed sync object resource, sw_sync should be used to save resources and avoid complexity.

Follow these guidelines when implementing sync_timeline:

  • Provide useful names for all drivers, timelines, and fences to simplify debugging.
  • Implement the timeline_value_str and pt_value_str operators in timelines to make debugging output more readable.
  • Implement the fill driver_data to give userspace libraries, such as the GL library, access to private timeline data, if desired. data_driver lets vendors pass information about the immutable sync_fence and sync_pts to build command lines based on them.
  • Don't allow userspace to explicitly create or signal a fence. Explicitly creating signals/fences results in a denial-of-service attack that halts pipeline functionality.
  • Don't access sync_timeline, sync_pt, or sync_fence elements explicitly. The API provides all required functions.

sync_pt

sync_pt is a single value or point on a sync_timeline. A point has three states: active, signaled, and error. Points start in the active state and transition to the signaled or error states. For example, when an image consumer no longer needs a buffer, a sync_pt is signaled so an image producer knows that it's okay to write into the buffer again.

sync_fence

sync_fence is a collection of sync_pt values that often have different sync_timeline parents (such as for the display controller and GPU). sync_fence, sync_pt, and sync_timeline are the main primitives that drivers and userspace use to communicate their dependencies. When a fence becomes signaled, all commands issued before the fence are guaranteed to be complete because the kernel driver or hardware block executes commands in order.

The sync framework allows multiple consumers or producers to signal when they're finished using a buffer, communicating the dependency information with one function parameter. Fences are backed by a file descriptor and are passed from kernel space to userspace. For example, a fence can contain two sync_pt values that signify when two separate image consumers are done reading a buffer. When the fence is signaled, the image producers know that both consumers are done consuming.

Fences, like sync_pt values, start active and change state based on the state of their points. If all sync_pt values become signaled, the sync_fence becomes signaled. If one sync_pt falls into an error state, the entire sync_fence has an error state.

Membership in a sync_fence is immutable after the fence is created. To get more than one point in a fence, a merge is conducted where points from two distinct fences are added to a third fence. If one of those points was signaled in the originating fence and the other wasn't, the third fence also won't be in a signaled state.

To implement explicit synchronization, provide the following:

  • A kernel-space subsystem that implements the sync framework for a particular hardware driver. Drivers that need to be fence-aware are generally anything that accesses or communicates with the Hardware Composer. Key files include:
    • Core implementation:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • sw_sync:
      • kernel/common/include/linux/sw_sync.h
      • kernel/common/drivers/base/sw_sync.c
    • Documentation at kernel/common/Documentation/sync.txt
    • Library to communicate with the kernel space in platform/system/core/libsync
  • The vendor must provide the appropriate synchronization fences as parameters to the validateDisplay() and presentDisplay() functions in the HAL.
  • Two fence-related GL extensions (EGL_ANDROID_native_fence_sync and EGL_ANDROID_wait_sync) and fence support in graphics driver.

Case study: Implementing a display driver

To use the API supporting the synchronization function, develop a display driver that has a display buffer function. Before the synchronization framework existed, this function would receive dma-buf objects, put those buffers on the display, and block while the buffer was visible. For example:

/*
 * assumes buffer is ready to be displayed.  returns when buffer is no longer on
 * screen.
 */
void display_buffer(struct dma_buf *buffer);

With the synchronization framework, the display_buffer function is more complex. While putting a buffer on display, the buffer is associated with a fence that indicates when the buffer will be ready. You can queue up and initiate the work after the fence clears.

Queuing and initiating work after the fence clears doesn't block anything. You immediately return your own fence, which guarantees when the buffer will be off of the display. As you queue up buffers, the kernel lists dependencies with the synchronization framework:

/*
 * displays buffer when fence is signaled.  returns immediately with a fence
 * that signals when buffer is no longer displayed.
 */
struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence
*fence);

Sync integration

This section explains how to integrate the kernel-space sync framework with userspace parts of the Android framework and the drivers that must communicate with one another. Kernel-space objects are represented as file descriptors in userspace.

Integration conventions

Follow the Android HAL interface conventions:

  • If the API provides a file descriptor that refers to a sync_pt, the vendor's driver or the HAL using the API must close the file descriptor.
  • If the vendor driver or the HAL passes a file descriptor that contains a sync_pt to an API function, the vendor driver or the HAL must not close the file descriptor.
  • To continue using the fence file descriptor, the vendor driver or the HAL must duplicate the descriptor.

A fence object is renamed every time it passes through BufferQueue. Kernel fence support allows fences to have strings for names, so the sync framework uses the window name and buffer index that's being queued to name the fence, such as SurfaceView:0. This is helpful in debugging to identify the source of a deadlock as the names appear in the output of /d/sync and bug reports.

ANativeWindow integration

ANativeWindow is fence aware. dequeueBuffer, queueBuffer, and cancelBuffer have fence parameters.

OpenGL ES integration

OpenGL ES sync integration relies on two EGL extensions:

  • EGL_ANDROID_native_fence_sync provides a way to wrap or create native Android fence file descriptors in EGLSyncKHR objects.
  • EGL_ANDROID_wait_sync allows GPU-side stalls rather than CPU-side, making the GPU wait for EGLSyncKHR. The EGL_ANDROID_wait_sync extension is the same as the EGL_KHR_wait_sync extension.

To use these extensions independently, implement the EGL_ANDROID_native_fence_sync extension along with the associated kernel support. Next, enable the EGL_ANDROID_wait_sync extension in your driver. The EGL_ANDROID_native_fence_sync extension consists of a distinct native fence EGLSyncKHR object type. As a result, extensions that apply to existing EGLSyncKHR object types don’t necessarily apply to EGL_ANDROID_native_fence objects, avoiding unwanted interactions.

The EGL_ANDROID_native_fence_sync extension employs a corresponding native fence file descriptor attribute that can be set only at creation time and can't be directly queried onward from an existing sync object. This attribute can be set to one of two modes:

  • A valid fence file descriptor wraps an existing native Android fence file descriptor in an EGLSyncKHR object.
  • -1 creates a native Android fence file descriptor from an EGLSyncKHR object.

Use the DupNativeFenceFD() function call to extract the EGLSyncKHR object from the native Android fence file descriptor. This has the same result as querying the set attribute, but adheres to the convention that the recipient closes the fence (hence the duplicate operation). Finally, destroying the EGLSyncKHR object closes the internal fence attribute.

Hardware Composer integration

The Hardware Composer handles three types of sync fences:

  • Acquire fences are passed along with input buffers to the setLayerBuffer and setClientTarget calls. These represent a pending write into the buffer and must signal before the SurfaceFlinger or the HWC attempts to read from the associated buffer to perform composition.
  • Release fences are retrieved after the call to presentDisplay using the getReleaseFences call. These represent a pending read from the previous buffer on the same layer. A release fence signals when the HWC is no longer using the previous buffer because the current buffer has replaced the previous buffer on the display. Release fences are passed back to the app along with the previous buffers that will be replaced during the current composition. The app must wait until a release fence signals before writing new contents into the buffer that was returned to them.
  • Present fences are returned, one per frame, as part of the call to presentDisplay. Present fences represent when the composition of this frame has completed, or alternately, when the composition result of the prior frame is no longer needed. For physical displays, presentDisplay returns present fences when the current frame appears on the screen. After present fences are returned, it's safe to write to the SurfaceFlinger target buffer again, if applicable. For virtual displays, present fences are returned when it's safe to read from the output buffer.