drm_hwcomposer has moved to gitlab!
The following information may be out-of-date, please find our new home here.
drm_hwcomposer Overview
Overview
The drm_hwcomposer
(AKA drm_hwc, DRM HWComposer, DRM Hardware Composer, drm_hwc, etc...) is an implementation of the hardware composer (HWC) hardware abstraction layer (HAL) of the Android framework. The job of any HWC is to take a frame and make it appear on the screen. It takes the form of a shared object under the /system/lib64/hw folder in Android that is dynamically loaded by the SurfaceFlinger (SF) process. HWC and SF share address space and file descriptors because they are exactly the same process.
A bit of history: drm_hwcomposer
was started early 2015 by Sean Paul to allow Android to run on the Pixel C. Instead of using a HWC supplied by the vendor for their SoC like most HWCs, it was decided that it would be better to build one that would work on top of DRM. In theory, this would have made a HWC that would work on any Chrome OS device because they use DRM for display. The drm_hwcomposer
project started life as a C library, then C++ because the HWC interface is rather object oriented, and finally to C++11 because it has makes resource management a bit more consistent and easy.
At present day, drm_hwcomposer
is being change to support the second HWC interface (HWC2). This document doesn't cover those changes, as they are still very much work in progress (WIP).
Life of a Frame
Regardless of HWC implementation, frames consist of a two calls into the HWC module (hwc_composer_device_1
):
int (*prepare)(struct hwc_composer_device_1 *dev, size_t numDisplays, hwc_display_contents_1_t** displays);
int (*set)(struct hwc_composer_device_1 *dev, size_t numDisplays, hwc_display_contents_1_t** displays);
Prepare
The hwc_display_contents_1
structure contains the contents of one display in the form of an array of layers (hwc_layer_1
), a retire fence (int, file descriptor), and some flags. Each layer has a type, flags, and some layer type specific fields. The purpose of the prepare call of the HWC interface is to negotiate the types of each layer. For instance, a layer may get passed to prepare with type HWC_FRAMEBUFFER
, indicating that SF expects to composite that layer down into the framebuffer target, but HWC can decide to set the type to HWC_OVERLAY
indicating that HWC will handle putting that layer onto the screen. Traditionally that means the layer will be put into a real hardware overlay plane.
In the case of drm_hwcomposer
, we lie and say every layer is HWC_OVERLAY
regardless of if we have enough overlays for them all. We do this because SurfaceFlinger's GLES fallback wasn't well optimized and we thought we could do better. However, there are layers we can't composite ever, like the ones flagged HWC_SKIP_LAYER
. A non-obvious consequence of this is that HWC composite any layers sandwiched between HWC_SKIP_LAYER
flagged layers, as it's impossible to perform blending properly when the skip layers end up in the framebuffer target.
Set
On set, the HWC is expected to display all the layers that were marked NOT marked HWC_FRAMEBUFFER
. Those layers will have all be squashed into the layer with type HWC_FRAMEBUFFER_TARGET
. Traditionally the HWC has done things in a somewhat synchronous manner in that after set returns, SF wouldn't have to maintain any references passed into set. SF only promises not to reuse any buffers passed in until the buffer's corresponding release fence is triggered. In fact, SF seems to cheat a little bit and starts recycling buffers after about a second, but more on that later.
In the case of drm_hwcomposer
, we deviate from the synchronous set behavior and return from set well before the layers have been displayed. On set, we first wrap all the resources given to us by SF in RAII-style C++ wrappers that will ensure the release of all fences and buffers in the case of error. This is the primary way we prevent leakage in drm_hwcomposer
as resources work their way through our pipeline. The other thing our set does is make a concrete plan for how to display everything. This includes provisioning hardware planes to the various given layers and performing a DRM atomic check to ensure the driver will actually accept the plan we made. Assuming this is all successful, this plan is placed into a queue to be processed on another thread, release fences are placed into the hwc_display_contents_1
, and then set returns.
Planning
For drm_hwcomposer
, planning is somewhat complex. From a high-level view, the DrmCompositor, which is essentially a collection of per-display DrmDisplayCompositor's, is used to create a DrmComposition, which represents one set call's worth of composition. Like how the DrmCompositor is a collection of DrmDisplayCompositor's, the DrmComposition is made up of a collection of DrmDisplayComposition's. Because of all the similar sounding names, it's quite confusing. Refer to the following table for clarification.
All Displays Per-Display
Inter-Frame State DrmCompositor DrmDisplayCompositor
Frame State DrmComposition DrmDisplayComposition
During set, with fresh DrmComposition, SetLayers is called with a map of display index to vector of layers that belong to that display. Inside DrmComposition::SetLayers, the layer stacks are given out to each display's DrmDisplayComposition via SetLayers. The DrmDisplayComposition's now own all layer resources, but no planning has actually taken place. This is because the plan for composition depends on the exact order in which DrmComposition's get queued. Therefore, composition planning only happens on DrmCompositor::QueueComposition. In the same way that DrmComposition::SetLayers was a thin dispatcher around DrmDisplayComposition::SetLayers, DrmCompositor::QueueComposition is a thin dispatcher for DrmDisplayCompositor::QueueComposition. DrmCompositor::QueueComposition will also call DrmComposition::Plan, now that the order of frames is locked in.
DrmComposition::Plan, like the other dispatch methods, calls all the sub-DrmDisplayComposition::Plan methods, with some additional parameters: the current squash state and a vector of unused planes. The squash state is pulled down from the DrmDisplayCompositor and persists its state between frames. The vector of planes is made fresh from the DrmResources every composition. As each display plans its composition using planes, the vector of planes that gets passed down to the DrmDisplayComposition gets smaller. This usually has the effect that lower priority displays don't have any overlay planes to use for optimization, having only the primary plane specific to its connector. It is possible that a display could be started of all planes, if non of the hardware planes are specific to that display. After all displays have done their planning, DrmDisplayComposition::DisableUnusedPlanes flags any leftover unused planes to be disabled upon composition.