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.
Follow these guidelines when implementing sync_timeline
:
- Provide useful names for all drivers, timelines, and fences to simplify debugging.
- Implement the
timeline_value_str
andpt_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 immutablesync_fence
andsync_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
, orsync_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
- Documentation at
kernel/common/Documentation/sync.txt
- Library to communicate with the kernel space in
platform/system/core/libsync
- Core implementation:
- The vendor must provide the appropriate synchronization
fences as parameters to the
validateDisplay()
andpresentDisplay()
functions in the HAL. - Two fence-related GL extensions (
EGL_ANDROID_native_fence_sync
andEGL_ANDROID_wait_sync
) and fence support in graphics driver.
Case study: Implement 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 inEGLSyncKHR
objects.EGL_ANDROID_wait_sync
allows GPU-side stalls rather than CPU-side, making the GPU wait forEGLSyncKHR
. TheEGL_ANDROID_wait_sync
extension is the same as theEGL_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
andsetClientTarget
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 thegetReleaseFences
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.