Android HardwareComposerのfenceメカニズム

7619 ワード

最近HardwareComposerと書いていますが、これの主な機能は簡単に言えば、SurfaceFlingerが用意した各layer dataを必要な場所に送り、aospのhardware/samsung_slsi/exynos 5/libhwcというhwcを例にとると、layer dataを/dev/graphics/fb 0に送って表示する.
しかし、いわゆる「sf準備のlayer」は、実際には必ずしも準備ができているとは限らない.
例えば、grallocが割り当てる1枚のbufferはapkとsfの間で共有され、apkはbufferのproducer、sfはbufferのconsumerとする.apkは必要なgl描画操作を実行すると、bufferの制御権をsfに渡し、sfはbufferに対応するhandleをhwcに渡してcomposeをします.このときapkのglのfuncはすでにreturnされていますが、本当の操作はgpuのqueueの中にあるかもしれません.bufferの内容はまだ更新されていません.hwcがbuffer情報をfb driverに送ると、fb driverがすぐにbufferの内容を画面に表示すると、エラーの画面が見える.逆に、画面の内容が表示されている状態で、apkが同じbuffer上に再描画する場合も同様の問題が発生する.したがって、producerとconsumerの間の同期メカニズム、すなわち本編で議論するfenceが必要である.
fenceがhwcでどのように運用されているかを見てみましょう.sfにhwcを表示する内容がある場合、sfはcall hwcのset関数(実際にはその前にcall prepareもありますが、prepareはfenceを処理しません).set関数returnの前にlayerの本当の準備を待つ必要はありません.fenceを正しく処理すれば、bufferはreadyの時にsignal関連のfenceをして、本当にbuffer dataを消費する人に知らせます.
hardware/libhardware/include/hardware/hwcomposer.h
    int (*set)(struct hwc_composer_device_1 *dev,
                size_t numDisplays, hwc_display_contents_1_t** displays);
hwc_display_contents_1_tは、fenceに関係のない部分を無視するように定義されています.
typedef struct hwc_display_contents_1 {
    int retireFenceFd;
    size_t numHwLayers;
    hwc_layer_1_t hwLayers[0];

} hwc_display_contents_1_t;

numHwLayersは今回表示する必要があるlayer個数を表し、hwLyaers[0]はサイズ0の配列でnumHwLayersの次のアドレスを指し、sfはhwc_を構築している.display_contents_1_tの場合numHwLayersによってsizeof(hwc_display_contents_1_t)+numHwLayers*sizeof(hwc_layer_1_t)の大きさのmemoryが割り当てられ、hwLyaersはnumHwLayers個のhwc_を本当に保存したことを指すlayer_1_tのアドレス
retireFenceFdは今回のcomposition retire後にsingalされる必要があり、physical displayにとってretireの概念は、今回の画面を次の画面に置き換えるときである.virtual displayにとってretireの概念は、コンテンツがoutbufferに完全に書き込まれ、readできる場合である.総じて言えば今回のcompositionの結果が必要とされなくなったとき、hwc_display_contents_1_tはretireとマークされています.
retireFenceFdはcomposition全体、つまりグループ全体のlayerを制御する.
またhwc_を見てみましょうlayer_1_t
typedef struct hwc_layer_1 {
    ...
    buffer_handle_t handle;
    int acquireFenceFd;
    int releaseFenceFd;
    ...
} hwc_layer_1_t;
handleは本layerに対応するbufferである.set関数がcallに到着するとbufferが本当にreadyかどうか分からないが、acquireFenceFdはbufferの内容が本当にreadyの時にsignalされ、hwcはclose acquireFenceFdを担当する.set関数returnの前にbufferの内容が使用済みであることを保証することはできないので、hwcはこのbufferのためにfenceを構築する必要があります.releaseFenceFdはset関数returnの前にsetされ、sfはreleaseFenceFdがsignalされるのを待ってからbufferを再利用します.sfはclose release FenceFdを担当します.
acquireFenceFdとreleaseFenceFdは単一layerを制御する.
まだhardware/samsung_slsi/exynos 5/libhwc/を例に挙げると、(簡略化codeはfenceに関連する部分のみを保持し、完全なcodeはaospを参照してください):
static int exynos5_set(struct hwc_composer_device_1 *dev,
        size_t numDisplays, hwc_display_contents_1_t** displays)
{
    if (!numDisplays || !displays)
        return 0;

    exynos5_hwc_composer_device_1_t *pdev =
            (exynos5_hwc_composer_device_1_t *)dev;
    hwc_display_contents_1_t *fimd_contents = displays[HWC_DISPLAY_PRIMARY];
    int fimd_err = 0;

    if (fimd_contents)
        fimd_err = exynos5_set_fimd(pdev, fimd_contents);
    return fimd_err;
}
set関数取得番号HWC_DISPLAY_PRIMARYのhwc_display_contents_1_t,すなわちandorid内蔵メインディスプレイに表示する内容,callからexynos 5_set_fimd.
static int exynos5_set_fimd(exynos5_hwc_composer_device_1_t *pdev,
        hwc_display_contents_1_t* contents)
{
    hwc_layer_1_t *fb_layer = NULL;
    int err = 0;

    int fence;
    if (!err) {
        fence = exynos5_post_fimd(pdev, contents);
        if (fence < 0)
            err = fence;
    }
    for (size_t i = 0; i < NUM_HW_WINDOWS; i++) {
	int dup_fd = dup(fence);
        layer.releaseFenceFd = dup_fd;
    }
    contents->retireFenceFd = fence;

    return err;
}
    exynos5_post_fimd returnはint fence、fenceは実はfdで、hwcはこのfdをn回dupして、layerのreleaseFeceFdごとに値を与えて、最後にfence自身をlayerグループ全体のretireFenceFdに値を与えます.すなわち、retireFenceFdとすべてのreleaseFenceFdはkernelに対応する同じfileである.これまでacqireFenceFdは出場しておらず、まだexynos 5_にいるに違いない.post_fimd中
dev->fd = open("/dev/graphics/fb0", O_RDWR);
static int exynos5_post_fimd(exynos5_hwc_composer_device_1_t *pdev,
        hwc_display_contents_1_t* contents)
{
    exynos5_hwc_post_data_t *pdata = &pdev->bufs;
    struct s3c_fb_win_config_data win_data;
    struct s3c_fb_win_config *config = win_data.config;

    memset(config, 0, sizeof(win_data.config));
    for (size_t i = 0; i < NUM_HW_WINDOWS; i++)
        config[i].fence_fd = -1;

    for (size_t i = 0; i < NUM_HW_WINDOWS; i++) {
        int layer_idx = pdata->overlay_map[i];
        if (layer_idx != -1) {
            hwc_layer_1_t &layer = contents->hwLayers[layer_idx];
            private_handle_t *handle =
                    private_handle_t::dynamicCast(layer.handle);

            exynos5_config_overlay(&layer, config[i], pdev);
        }
    }
	
    int ret = ioctl(pdev->fd, S3CFB_WIN_CONFIG, &win_data);
    for (size_t i = 0; i < NUM_HW_WINDOWS; i++)
        if (config[i].fence_fd != -1)
            close(config[i].fence_fd);
    return win_data.fence;
}

各layerは1つのconfigに対応し、すべてのconfigはwinを構成する.data,exynos5_config_overlayは各layerからconfigの必要な情報を抽出してcofnigに記入し、最後にwin全体を記入します.dataをパラメータとしてS 3 CFB_WIN_CONFIG ioctlはfb 0に送りました、exynos 5_post_fimd returnのfenceは実はfb driver処理後のwin_data.fence.
acqireFenceFdはまだ現れていませんが、ioctlの前に各configのfence_fdは-1,ioctlに設定された後、再びfence_を判断するfd-1でなければcloseですconfig.fence_fdはacqireFenceFdと関係があるに違いない!でもここでclose fence_fdは少し早すぎるのではないでしょうか.このfenceはもう必要ありませんか?set関数が戻るまでacquireFenceFdがsignalされるのを待つ必要はないのではないでしょうか.さらにexynos 5に入りますconfig_見てみろ
static void exynos5_config_overlay(hwc_layer_1_t *layer, s3c_fb_win_config &cfg,
        exynos5_hwc_composer_device_1_t *pdev)
{
    private_handle_t *handle = private_handle_t::dynamicCast(layer->handle);
    exynos5_config_handle(handle, layer->sourceCrop, layer->displayFrame,
            layer->blending, layer->acquireFenceFd, cfg, pdev);
}
static void exynos5_config_handle(private_handle_t *handle,
        hwc_rect_t &sourceCrop, hwc_rect_t &displayFrame,
        int32_t blending, int fence_fd, s3c_fb_win_config &cfg,
        exynos5_hwc_composer_device_1_t *pdev)
{
    uint32_t x, y;
    uint32_t w = WIDTH(displayFrame);
    uint32_t h = HEIGHT(displayFrame);
    uint8_t bpp = exynos5_format_to_bpp(handle->format);
    uint32_t offset = (sourceCrop.top * handle->stride + sourceCrop.left) * bpp / 8;

    cfg.state = cfg.S3C_FB_WIN_STATE_BUFFER;
    cfg.fd = handle->fd;
    cfg.x = x;
    cfg.y = y;
    cfg.w = w;
    cfg.h = h;
    cfg.format = exynos5_format_to_s3c_format(handle->format);
    cfg.offset = offset;
    cfg.stride = handle->stride * bpp / 8;
    cfg.blending = exynos5_blending_to_s3c_blending(blending);
    cfg.fence_fd = fence_fd;
}
acquireFenceFdは、layer毎に対応するconfigのcfgとしてついに現れる.fence_fd、S 3 CFB_経由WIN_CONFIG ioctrlはfb driverに送った.fb driverを送った以上、fenceの生死はdriverが責任を負う.hwcはもう気にしないでください.だから上のexynos 5_post_各layerのconfigをfence_fd、つまりlayer->acquireFenceFdはcloseに落ちた.
これでhwcにおけるfenceに対する処理フローは終了し,以下をまとめる.
    1. hwcはすべてのlayer->acquireFenceFdをfb driverに一緒に送った後、layer->acquireFenceFd closeをfb driverに渡し、fenceの管理権をfb driverに渡す.
    2. ioctl rentrunの場合、win_data.fenceはdriverによって新しいfdとして記入され、このfdはdupによってn回与えられ、各layerのlayer->releaseFenceFdに与えられる.
    3. win_data.fenceはhwc全体に値を付与するdisplay_contents_1_t、つまりすべてのlayerの組み合わせのretireFenceFd.
    4.その後releaseFenceFdとretaireFenceFdの生死はsfに任せた.
次の議論はfd driverに入ります.Android fb driverのfenceメカニズムを参照してください.