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 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 is a monotonically increasing timeline that
vendors should implement for each driver instance, such as a GL context,
display controller, or 2D blitter.
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.
Follow these guidelines when implementing
- Provide useful names for all drivers, timelines, and fences to simplify debugging.
- Implement the
pt_value_stroperators in timelines to make debugging output more readable.
- Implement the fill
driver_datato give userspace libraries, such as the GL library, access to private timeline data, if desired.
data_driverlets vendors pass information about the immutable
sync_ptsto 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_fenceelements explicitly. The API provides all required functions.
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 is a collection of
sync_timeline parents (such as for the display
controller and GPU).
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.
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
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:
- Documentation at
- Library to communicate with the kernel space in
- Core implementation:
- The vendor must provide the appropriate synchronization
fences as parameters to the
presentDisplay()functions in the HAL.
- Two fence-related GL extensions (
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
objects, put those buffers on the display, and block while the buffer was visible. For
/* * 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
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);
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.
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
sync_ptto 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
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 is fence aware.
cancelBuffer have fence parameters.
OpenGL ES integration
OpenGL ES sync integration relies on two EGL extensions:
EGL_ANDROID_native_fence_syncprovides a way to wrap or create native Android fence file descriptors in
EGL_ANDROID_wait_syncallows GPU-side stalls rather than CPU-side, making the GPU wait for
EGL_ANDROID_wait_syncextension is the same as the
To use these extensions independently, implement the
EGL_ANDROID_native_fence_sync extension along with the associated
kernel support. Next, enable the
extension in your driver. The
extension consists of a distinct native fence
type. As a result, extensions that apply to existing
object types don’t necessarily apply to
objects, avoiding unwanted interactions.
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
- -1 creates a native Android fence file descriptor from an
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
setClientTargetcalls. 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
getReleaseFencescall. 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,
presentDisplayreturns 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.