[Android禪修之路] SurfaceFlinger 合成中的工作

語言: CN / TW / HK

theme: channing-cyan

SurfaceFlinger 合成中的工作

Android禪修之路

前言

在SurfaceFlinger 經歷完合成前的準備之後,接下來的就是具體的合成工作了,合成工作的入口程式碼就是 doComposition ,接下來看看 SurfaceFlinger 合成中又做了那些事情

一 doComposition

首先合成的方法都是在 doComposition 函式中呼叫的,這個函式中總共呼叫了

  1. doDisplayComposition
  2. display->getRenderSurface()->flip()
  3. postFramebuffer

```cpp void SurfaceFlinger::doComposition(const sp& displayDevice, bool repaintEverything) {

// 這幾個物件在合成前已經介紹過了,附錄1中也有詳細的介紹
auto display = displayDevice->getCompositionDisplay();
const auto& displayState = display->getState();

// 判斷 OutputCompositionState 是否為 true,如果 OutputCompositionState 的合成狀態不為 true ,
// 則不需要合成,這個變數中 DisplayDevice 建立的時候就設定為 true,只有電源關閉的時候才是 false
if (displayState.isEnabled) {
    // 將髒區域轉換為螢幕的座標
    const Region dirtyRegion = display->getDirtyRegion(repaintEverything);

    // 如果需要則重新繪製幀緩衝區
    doDisplayComposition(displayDevice, dirtyRegion);
// 清除髒區域
    display->editState().dirtyRegion.clear();
    // 通知 Surface
    display->getRenderSurface()->flip();
}
// 發生資料到對應的裝置
postFramebuffer(displayDevice);

}

```

二 doDisplayComposition

doDisplayComposition 首先會判斷是否需要合成顯示,只有兩種情況才需要合成顯示,其他情況都可以直接跳過合成

  1. 髒區域不為空
  2. 由 hardware composer(後續簡稱hwc)處理

```cpp void SurfaceFlinger::doDisplayComposition(const sp& displayDevice, const Region& inDirtyRegion) { auto display = displayDevice->getCompositionDisplay(); // 需要實際合成顯示的2種情況 // 1) 由 hwc 處理,它可能需要合成來保持其虛擬顯示的狀態同步 // 2) 髒區域不為空 // displayDevice->getId() 拿到的 Id 就是 hwc 的Id,如果它不為空,就說明需要 hwc 處理 // 也就是說只有 hwc 的 Id 為空,並且髒區域也為空,才會從這裡返回 if (!displayDevice->getId() && inDirtyRegion.isEmpty()) { return; }

// 定義一個準備合成的 Fence
base::unique_fd readyFence;
// 開始合成工作, 並把這個 Fence 傳遞進去
if (!doComposeSurfaces(displayDevice, Region::INVALID_REGION, &readyFence)) return;
// 交換緩衝區,這裡也攜帶了之前建立的 Fence 物件
display->getRenderSurface()->queueBuffer(std::move(readyFence));

} ``` doDisplayComposition 這個函式只做了兩件事 1. 呼叫 doComposeSurfaces 做合成工作。 2. 呼叫 queueBuffer 將處理完成的緩衝區入隊。 在這個過程中它還會傳遞一個 Fence,這個東西就是用來保證緩衝區同步機制的。

三 doComposeSurfaces

doComposeSurfaces - 合成Surface ,這個函式較長,主要分為以下幾個部分,我們還是分塊檢視

3.1 doComposeSurfaces 的第一部分

第一部分會獲得一些預先處理好的引數,這些都比較簡單,我們唯一需要注意的一點就是這裡拿到了一個 hasClientComposition 的變數,用來區分是否是客戶端處理

從這一步開始,SurfaceFlinger 就會根據圖層的不同處理型別,進行不同的合成邏輯,主要分為客戶端處理和硬體處理,其中客戶端處理也就是 OpenGl 處理(軟體處理),而硬體處理就是由 hwc 處理。

```cpp bool SurfaceFlinger::doComposeSurfaces(const sp& displayDevice, const Region& debugRegion, base::unique_fd* readyFence) {

auto display = displayDevice->getCompositionDisplay();
const auto& displayState = display->getState();
const auto displayId = display->getId();
// 這裡 getRenderEngine 獲得的是 SurfaceFlinger 在 init 函式中, setRenderEngine 建立的 GLESRenderEngine
// 它提供了一些 OpenGL 的方法
auto& renderEngine = getRenderEngine();
// 是否是受保護的內容,與版權保護相關,此處不關注
const bool supportProtectedContent = renderEngine.supportsProtectedContent();

const Region bounds(displayState.bounds);
const DisplayRenderArea renderArea(displayDevice);

// 是否包含由GLES處理的圖層, GLES的處理圖層就是客戶端處理, 是否就是系統硬體處理
const bool hasClientComposition = getHwComposer().hasClientComposition(displayId);

bool applyColorMatrix = false;

renderengine::DisplaySettings clientCompositionDisplay;
std::vector<renderengine::LayerSettings> clientCompositionLayers;
sp<GraphicBuffer> buf;
base::unique_fd fd;

if (hasClientComposition) {
    // 如果需要客戶端處理,則執行下面這段邏輯
    if (displayDevice->isPrimary() && supportProtectedContent) {
        // 如果該圖層是受保護的圖層,則需要標記受保護的上下文
        // 數字版權相關,此處無需關注,詳細的可以自行研究 Android DRM 技術
        bool needsProtected = false;
        for (auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
            if (layer->isProtected()) {
                needsProtected = true;
                break;
            }
        }
        if (needsProtected != renderEngine.isProtected()) {
            renderEngine.useProtectedContext(needsProtected);
        }
        if (needsProtected != display->getRenderSurface()->isProtected() &&
            needsProtected == renderEngine.isProtected()) {
            display->getRenderSurface()->setProtected(needsProtected);
        }
    }

    // 1. 呼叫 RenderSurface 的 dequeueBuffer
    buf = display->getRenderSurface()->dequeueBuffer(&fd);
  ...

```

第一部分還是比較常規的引數處理,只是這裡值得注意的一點是,圖層的合成從這裡開始區分是由客戶端(軟體 OpenGl )處理,還是由硬體 hwc 處理,然後再呼叫 RenderSurface 的 dequeueBuffer 來獲取緩衝區,這裡會傳遞進去一個檔案描述符,它是 SurfaceFlinger 中用於同步的 Fence 機制,關於 Fence 機制檢視[同步機制 Fence ]

第一部分通過 displayDevice 開始獲取的一些物件就是封裝的顯示裝置的物件,具體的介紹可以檢視附錄1

最後就是從緩衝區佇列獲取一個緩衝區

3.2 doComposeSurfaces 的第二部分

這段程式碼的前半部分都是一些引數的設定,然後就是根據不同的合成方式,進行不同的處理

```cpp bool SurfaceFlinger::doComposeSurfaces(const sp& displayDevice, const Region& debugRegion, base::unique_fd* readyFence) { ...... // dequeueBuffer 是從緩衝區佇列中獲取一個緩衝區 buf = display->getRenderSurface()->dequeueBuffer(&fd); // 第二部分開始 // 首先就是判斷拿到的緩衝區是否為空 if (buf == nullptr) { return false; }

    clientCompositionDisplay.physicalDisplay = displayState.scissor;
    clientCompositionDisplay.clip = displayState.scissor;
    const ui::Transform& displayTransform = displayState.transform;
    clientCompositionDisplay.globalTransform = displayTransform.asMatrix4();
    clientCompositionDisplay.orientation = displayState.orientation;

    // DisplayColorProfile 封裝了有關如何為顯示器轉換顏色的所有狀態和功能
    const auto* profile = display->getDisplayColorProfile();
    Dataspace outputDataspace = Dataspace::UNKNOWN;
    if (profile->hasWideColorGamut()) {
        outputDataspace = displayState.dataspace;
    }
    clientCompositionDisplay.outputDataspace = outputDataspace;
    clientCompositionDisplay.maxLuminance =
            profile->getHdrCapabilities().getDesiredMaxLuminance();

    const bool hasDeviceComposition = getHwComposer().hasDeviceComposition(displayId);
    const bool skipClientColorTransform = getHwComposer().hasDisplayCapability(displayId,
        HWC2::DisplayCapability::SkipClientColorTransform);

    // 計算顏色變化矩陣
    applyColorMatrix = !hasDeviceComposition && !skipClientColorTransform;
    if (applyColorMatrix) {
        clientCompositionDisplay.colorTransform = displayState.colorTransformMat;
    }
}

/*
 * 然後,渲染針對幀緩衝區的圖層
 */
bool firstLayer = true;
Region clearRegion = Region::INVALID_REGION;
for (auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
    const Region viewportRegion(displayState.viewport);
    const Region clip(viewportRegion.intersect(layer->visibleRegion));

    if (!clip.isEmpty()) {
        // 根據不同的合成型別,通過呼叫 prepareClientLayer 將相關的引數設定到圖層Layer中,
        switch (layer->getCompositionType(displayDevice)) {
            case Hwc2::IComposerClient::Composition::CURSOR:
            case Hwc2::IComposerClient::Composition::DEVICE:
            case Hwc2::IComposerClient::Composition::SIDEBAND:
            case Hwc2::IComposerClient::Composition::SOLID_COLOR: {

                const Layer::State& state(layer->getDrawingState());
                if (layer->getClearClientTarget(displayDevice) && !firstLayer &&
                    layer->isOpaque(state) && (layer->getAlpha() == 1.0f) &&
                    layer->getRoundedCornerState().radius == 0.0f && hasClientComposition) {
                    //永遠不要清除第一個圖層,因為可以保證 FB 已經清除
                    renderengine::LayerSettings layerSettings;
                    Region dummyRegion;
                    // 2. 呼叫 Layer 的 prepareClientLayer
                    bool prepared =
                            layer->prepareClientLayer(renderArea, clip, dummyRegion,
                                                      supportProtectedContent, layerSettings);

                    if (prepared) {
                        layerSettings.source.buffer.buffer = nullptr;
                        layerSettings.source.solidColor = half3(0.0, 0.0, 0.0);
                        layerSettings.alpha = half(0.0);
                        layerSettings.disableBlending = true;
                        clientCompositionLayers.push_back(layerSettings);
                    }
                }
                break;
            }
            case Hwc2::IComposerClient::Composition::CLIENT: {
                renderengine::LayerSettings layerSettings;
                // 2. 呼叫 Layer 的 prepareClientLayer
                bool prepared =
                        layer->prepareClientLayer(renderArea, clip, clearRegion,
                                                  supportProtectedContent, layerSettings);
                if (prepared) {
                    clientCompositionLayers.push_back(layerSettings);
                }
                break;
            }
            default:
                break;
        }
    } else {
        ALOGV("  Skipping for empty clip");
    }
    firstLayer = false;
}

...

} ``` 第二部分主要呼叫的就是 Layer 的 prepareClientLayer,根據不同的型別,它會準備不同的引數傳遞到 Layer 中,這是為 Layer 設定一些合成前的引數。

3.3 doComposeSurfaces 的第三部分

第三部分的關鍵點是

```cpp bool SurfaceFlinger::doComposeSurfaces(const sp& displayDevice, const Region& debugRegion, base::unique_fd* readyFence) { // 第三部分開始,如果使用客戶端組合,則執行一些清理步驟 if (hasClientComposition) { clientCompositionDisplay.clearRegion = clearRegion;

    // 這裡先提高GPU的頻率用來處理色彩空間轉換,然後重置GPU的頻率來節省電量
    const bool expensiveRenderingExpected =
            clientCompositionDisplay.outputDataspace == Dataspace::DISPLAY_P3;
    if (expensiveRenderingExpected && displayId) {
        mPowerAdvisor.setExpensiveRenderingExpected(*displayId, true);
    }
    if (!debugRegion.isEmpty()) {
        ...
    }
    // 3. 如果是客戶端合成,則通過 GPU 合成為特定顯示器渲染圖層
    // 這個 renderEngine 之前已經說明了,它是 GLESRenderEngine ,它封裝了一些 OpenGL 的方法
    renderEngine.drawLayers(clientCompositionDisplay, clientCompositionLayers,
                            buf->getNativeBuffer(), /*useFramebufferCache=*/true, std::move(fd),
                            readyFence);
} else if (displayId) {
    // 如果不是客戶端合成,則重置GPU的頻率,因為一直使用高頻GPU會耗電
    mPowerAdvisor.setExpensiveRenderingExpected(*displayId, false);
}
return true;

} ```

3.4 doComposeSurfaces 的總結

現在看完了 doComposeSurfaces 的三個部分,這裡先做一下小小的總結

  1. 先是處理一些引數,然後呼叫 RenderSurface 的 dequeueBuffer ,分配緩衝區
  2. 處理 client composition 的相關引數,並計算顏色變換矩陣
  3. 渲染幀緩衝區的圖層,首先填充幀緩衝區的圖層 Layer prepareClientLayer, 設定一些引數
  4. 渲染圖層 Layer,如果是使用客戶端合成,就直接呼叫 GLESRenderEngine 的 drawLayers

補充說明:

緩衝區的分配,在 SurfaceFlinger 的合成過程中,涉及到一個生產者和消費者模型,並且這個模型貫穿了整個流程,詳情可見[解讀SurfaceFlinger中的生產者和消費者]

關於圖層的合成和一些處理操作,因為不同的圖層具體的操作也不相同,所以具體的操作參考[Layer詳解]

renderEngine 是 SurfaceFlinger 初始化時建立的一個 GLESRenderEngine 物件,所以最後它呼叫 GLESRenderEngine 的 drawLayers 函式,其實也是通過 OpenGL 完成的。

四 生產者和消費者簡介

生產者消費者模型

之前已經說明了在合成中的緩衝區分配,有一個生產者和消費者,這裡我們可以簡單介紹一下這個模型執行的過程,如上圖所示,具體的流程如下

  1. 生產者從 BufferQueue 中申請緩衝區
  2. BufferQueue 從底層拿到一塊緩衝區,然後將緩衝區出隊,提供給生產者
  3. 生產者將資料寫入緩衝區後,將緩衝區入隊,然後緩衝區在 BufferQueue 中,等待消費者讀取
  4. 消費者讀取完緩衝區後,又回到步驟1

現在的 queueBuffer 其實就是步驟3,生產者將緩衝區入隊,然後緩衝區在 BufferQueue 中等待消費者讀取

緩衝區的入隊和緩衝區的出隊涉及到的邏輯一樣非常多,詳情可見解讀SurfaceFlinger中的生產者和消費者

五 postFramebuffer

在緩衝區入隊之後,還做了一個清除髒資料的操作和一個 flip 函式翻頁的操作,不過這個兩個函式的邏輯比較簡單,這裡就不展開了。接下來再來看看 postFramebuffer 函式

```cpp

void SurfaceFlinger::postFramebuffer(const sp& displayDevice) {

auto display = displayDevice->getCompositionDisplay();
const auto& displayState = display->getState();
const auto displayId = display->getId();

if (displayState.isEnabled) {
    if (displayId) {
        // Fence 同步
        getHwComposer().presentAndGetReleaseFences(*displayId);
    }
    // DisplaySurface 的 onFrameCommitted
    display->getRenderSurface()->onPresentDisplayCompleted();
    for (auto& layer : display->getOutputLayersOrderedByZ()) {
        // 按照 Z 軸遍歷圖層
        sp<Fence> releaseFence = Fence::NO_FENCE;
        bool usedClientComposition = true;

        // 只有當來自該幀(如果有)的 release Fence 訊號發出時,HWC才會釋放來自前一幀(如果有)的層緩衝區。
        // 始終首先從HWC處獲得釋放圍欄。
        if (layer->getState().hwc) {
            const auto& hwcState = *layer->getState().hwc;
            releaseFence =
                    getHwComposer().getLayerReleaseFence(*displayId, hwcState.hwcLayer.get());
            usedClientComposition =
                    hwcState.hwcCompositionType == Hwc2::IComposerClient::Composition::CLIENT;
        }

        // 如果前一幀使用的是軟體合成,則這一幀需要與前一幀合併
        if (usedClientComposition) {
            releaseFence =
                    Fence::merge("LayerRelease", releaseFence,
                                 display->getRenderSurface()->getClientTargetAcquireFence());
        }
        // 拿到圖層,然後呼叫 onLayerDisplayed ,傳遞 Fence 訊號進行同步
        layer->getLayerFE().onLayerDisplayed(releaseFence);
    }

    // 我們有一個圖層列表需要 fence , 這些圖層中 Z 軸數不相交的, 
    // 所以我們能做的最好方式就是為它們提供當前的 fence
    if (!displayDevice->getLayersNeedingFences().isEmpty()) {
        // 如果圖層中需要 Fence 訊號的佇列不為空,則在合成結束後,都傳遞一遍 Fence 訊號
        sp<Fence> presentFence =
                displayId ? getHwComposer().getPresentFence(*displayId) : Fence::NO_FENCE;
        for (auto& layer : displayDevice->getLayersNeedingFences()) {
            layer->getCompositionLayer()->getLayerFE()->onLayerDisplayed(presentFence);
        }
    }

    if (displayId) {
        // 然後清除 hwc 的 Fence 訊號
        getHwComposer().clearReleaseFences(*displayId);
    }
}

}

void RenderSurface::onPresentDisplayCompleted() { // 呼叫 DisplaySurface 的 onFrameCommitted mDisplaySurface->onFrameCommitted(); } ``` postFramebuffer 主要就是傳遞一些 Fence 訊號,然後提交資料。

需要注意的一點就是,在 SurfaceFlinger 的工作過程中,應用會將自己的畫面提供給 SurfaceFlinger,然後 SurfaceFlinger 再將這些資料所在的緩衝區提交到硬體。但是緩衝區提交給了硬體,並不代表這些資料就已經是準備好可以使用的,這時在提交資料的同時,還會附帶的提交一個 Fence 訊號標誌,具體什麼時候可以用,就需要等待這個訊號。所以這裡進行了一系列的訊號傳遞。

六 總結

關於合成中的操作,到這就簡單的介紹完了,還是進行一個簡單的總結

  1. 合成中的函式主要有兩個
  2. doDisplayComposition:重繪部分需要重繪的緩衝區
  3. 傳送緩衝區資料到顯示裝置
  4. doDisplayComposition 處理顯示的合成邏輯,包含兩步
  5. doComposeSurfaces:根據圖層的不同型別(Layer 和它的各種子類)還有合成方式(客戶端合成還是 hwc 合成)進行不同的處理
  6. queueBuffer:將處理完的緩衝區入隊
  7. postFramebuffer 發生緩衝區資料到顯示裝置,此處會有 Fence 同步

到此,SurfaceFlinger 合成中的工作流程,算是簡單的總結完了,但是對於其中 hwc 和 Layer 的細節,我們依舊還是不清楚,雖然瞭解了 SurfaceFlinger 合成的一些流程,但是又似乎並沒有完全弄懂,關於這些內容,後續還是需要單獨深入學習。

SurfaceFlinger 的合成工作比合成之前的準備工作要複雜許多,特別是裡面涉及到的一些其他知識點,如果不弄清這些知識點,又很難弄懂 SurfaceFlinger 合成時的具體邏輯,所以我將 SurfaceFlinger 合成時涉及到的其他知識點都單獨拿出來,然後和 SurfaceFlinger 合成的具體流程進行對照,通過這樣拆分和組合的方式,也更容易弄懂 SurfaceFlinger 的合成原理

  • 首先是 display->getRenderSurface()->dequeueBuffer 和 display->getRenderSurface()->queueBuffer 這兩個函式,這裡面涉及到了 SurfaceFlinger 的生產者和消費者模型,以及 GraphicBuffer 的建立過程,以及 Android 系統中,圖形系統的 ION 機制,這三點的難度逐漸增加,需要在學習的時候舉一反三,不斷的回頭讀程式碼驗證
  • [SurfaceFlinger 中的生產者和消費者]
  • [GraphicBuffer 的建立過程]
  • [Android 圖形系統的 ION 機制]

  • 然後是在不同圖層的處理,例如 prepareClientLayer 函式不同的 Layer 都做了些什麼,[Layer詳解]

  • 軟體合成和硬體合成,HWC 的工作原理
  • 接下來則是 GLESRenderEngine 的 drawLayers ,它涉及到 OpenGL 在 Android 圖形系統,不過對於 OpenGL 目前並沒有深入瞭解的打算,一是學這東西有門檻,另外則是對於應用工程師,學這個花費了精力,也不如專業弄這個的遊戲領域的人,所以 OpenGL 簡單瞭解即可。
  • 最後則是 postFramebuffer 函式,它裡面又涉及到 Android 圖形系統中比較重要的一塊, [Fence 同步機制]

到此,合成中的邏輯已經整理清楚了,不過一下子出來這麼多東西,也不知道該慶幸還是不幸,不過 Android 圖形系統中,重要的內容這裡幾乎佔一半,接下來再看 SurfaceFlinger 合成後的處理