作者:曹璀
1 簡介
标準系統提供了圖形接口能力和視窗管理接口能力, 支援應用程式架構子系統和ACE等子系統使用。故可以根據不同硬體系統選擇編譯不同子產品,選擇适配輕量系統或者标準系統。圖形子系統主要包括UI元件、布局、動畫、字型、輸入事件、視窗管理、渲染繪制等子產品。
以下主要分析标準系統能力。代碼版本是OpenHarmony3.1版本。
開機動畫是鴻蒙系統啟動後,運作的第一個和圖形渲染相關的程序,相關依賴相對獨立便于分析,是分析圖形子系統比較好的切入點。圖形子系統主要依賴視窗、surface、render service。
目錄
./foundation/graphic
|-- standard
| |-- figures
| |-- frameworks # 架構代碼目錄
| | |-- animation_server
| | |-- bootanimation # 開機動畫
| | |-- dumper
| | |-- fence
| | |-- surface # 渲染表面
| | |-- vsync
| | |-- wm
| | |-- wmserver
| | |-- wmservice
| |-- interfaces
| | |-- innerkits
| | `-- kits
| |-- rosen
| | |-- build
| | |-- doc
| | |-- include
| | |-- lib
| | |-- modules
| | | |-- 2d_graphics # 2維圖形
| | | |-- animation # 動畫
| | | |-- composer # 渲染合成器
| | | |-- effect
| | | |-- render_service # 渲染服務端
| | | |-- render_service_base # 渲染基礎能力
| | | |-- render_service_client # 渲染用戶端
| | | `-- utils
| | `-- tools
| `-- utils
|-- surface
|-- ui
Graphic子系統 提供了圖形接口能力和視窗管理接口能力, 支援應用程式架構子系統和ACE等子系統使用。支援所有運作标準系統的裝置使用。
其主要的結構如下圖所示:
-
Surface
圖形緩沖區管理接口,負責管理圖形緩沖區和高效便捷的輪轉緩沖區。依賴Display driver開辟buffer及buffer管理。
-
Vsync Client
垂直同步信号管理接口,負責管理所有垂直同步信号注冊和響應。
-
WindowManager
視窗管理器接口,負責建立和管理視窗。
-
IPC/RPC通信
程序間通信協定,支援渲染用戶端和服務端建立連接配接、申請buffer、重新整理buffer等
-
Render Service ohos
render service适配ohos的部分,屬于render service的基礎能力,其中适配了ohos的render service client及IPC代理能力
-
Compositor
圖像合成送顯,依賴Display driver和Surface,管理buffer及送顯。
-
Input Manager
多模輸入子產品,負責接收事件輸入
-
Skia
第三方渲染接口,支援CPU和GPU渲染情況下的畫布繪制
-
Render Service Base
render service的基礎能力,包含Render Service ohos
主要流程
CPU渲染
申請buffer
建立畫布
說明:GPU渲染時,擷取buffer沒有經過render service server,而是在client端用skia完成,在用egl做完顯示視窗的初始化動作後,開始繪制圖像。可參考
OpenGLES 與 EGL 基礎概念 - 知乎 (zhihu.com)
源碼分析
啟動
服務啟動配置graphic.cfg在foundation/graphic/standard/graphic.cfg目錄,分别啟動了bootanimation和render_service程序。
{
"jobs" : [{
"name" : "post-fs-data",
"cmds" : [
"start render_service",
"start bootanimation"
]
}, {
"name" : "init",
"cmds" : [
"chmod 666 /dev/mali0",
"chown system graphics /dev/mali0"
]
}
],
"services" : [{
"name" : "render_service", # 渲染服務端
"path" : ["/system/bin/render_service"],
"uid" : "root",
"gid" : ["system", "shell", "uhid", "root"]
}, {
"name" : "bootanimation", # 開機啟動程序
"path" : ["/system/bin/bootanimation"],
"once" : 1,
"uid" : "root",
"gid" : ["system", "shell", "uhid", "root"]
}
]
}
初始化
void BootAnimation::Init(int32_t width, int32_t height)
{
windowWidth_ = width;
windowHeight_ = height;
InitBootWindow(); // 建立啟動視窗
InitRsSurface(); // 初始化surface
InitPicCoordinates();
std::vector<uint32_t> freqs;
VsyncHelper::Current()->GetSupportedVsyncFrequencys(freqs);
if (freqs.size() >= 0x2) {
freq_ = freqs[1];
}
UnzipFile(BOOT_PIC_ZIP, DST_FILE_PATH); // 解壓動畫壓縮包
CountPicNum(DST_FILE_PATH.c_str(), maxPicNum_); // 計算圖檔數量
Draw(); // 開始繪制
PostTask(std::bind(&BootAnimation::CheckExitAnimation, this), EXIT_TIME);
}
說明:
- Init函數會初始化surface。
- InitBootWindow建立啟動視窗,通過WindowScene調用WindowImpl建立RSSurfaceNode對象。
- RSSurfaceNode對象可在InitRsSurface中建立surface。
- UnzipFile輸入參數都是固定的,分别為zip包和輸出目錄。
- CountPicNum會對輸出目錄下的圖檔進行統計。
- Draw對解壓出來的開機圖檔進行繪制渲染。
- PostTask設定開機動畫結束退出回調。
void BootAnimation::InitRsSurface()
{
rsSurface_ = OHOS::Rosen::RSSurfaceExtractor::ExtractRSSurface(window_->GetSurfaceNode());
if (rsSurface_ == nullptr) {
LOG("rsSurface is nullptr");
return;
}
#ifdef ACE_ENABLE_GL
rc_ = OHOS::Rosen::RenderContextFactory::GetInstance().CreateEngine();
rc_->InitializeEglContext();
rsSurface_->SetRenderContext(rc_);
#endif
}
說明:
- ExtractRSSurface則是在InitBootWindow獲得的RSSurfaceNode的基礎上擷取surface。
繪制流程
1.擷取RSSurface,當是CPU渲染的時候擷取的是個RSSurfaceOhosRaster對象,GPU渲染時是個RSSurfaceOhosGl對象,這個地方擷取RSRenderServiceConnectionProxy就是IPC機制的應用。
bool RSSurfaceNode::CreateNodeAndSurface(const RSSurfaceRenderNodeConfig& config)
{
// RSIRenderClient::CreateRenderServiceClient()擷取了一個RSRenderServiceClient對象
surface_ = std::static_pointer_cast<RSRenderServiceClient>(RSIRenderClient::CreateRenderServiceClient())
->CreateNodeAndSurface(config);
return (surface_ != nullptr);
}
std::shared_ptr<RSSurface> RSRenderServiceClient::CreateNodeAndSurface(const RSSurfaceRenderNodeConfig& config)
{
// 得到RSRenderServiceConnectionProxy
auto renderService = RSRenderServiceConnectHub::GetRenderService();
if (renderService == nullptr) {
return nullptr;
}
// 用得到的RSRenderServiceConnectionProxy建立ProducerSurface對象
sptr<Surface> surface = renderService->CreateNodeAndSurface(config);
#ifdef ACE_ENABLE_GL
// GPU render
std::shared_ptr<RSSurface> producer = std::make_shared<RSSurfaceOhosGl>(surface);
#else
// CPU render
std::shared_ptr<RSSurface> producer = std::make_shared<RSSurfaceOhosRaster>(surface);
#endif
return producer;
}
2.繪制開機圖檔,逐個圖檔加載渲染,渲染時flush的過程參考擷取buffer的過程,flush會把buffer發送到render service server端合成送顯。
void BootAnimation::OnDraw(SkCanvas* canvas)
{
std::string imgPath = BOOT_PIC_DIR + std::to_string(bootPicCurNo_) + ".jpg";
// pic is named from 0
if (bootPicCurNo_ != (maxPicNum_ - 1)) {
bootPicCurNo_ = bootPicCurNo_ + 1;
}
std::unique_ptr<FILE, decltype(&fclose)> file(fopen(imgPath.c_str(), "rb"), fclose);
auto skData = SkData::MakeFromFILE(file.get());
auto codec = SkCodec::MakeFromData(skData);
sk_sp<SkImage> image = SkImage::MakeFromEncoded(skData); // 通過skia轉換圖像資料
// 在畫布上繪制
SkPaint backPaint;
backPaint.setColor(SK_ColorBLACK);
canvas->drawRect(SkRect::MakeXYWH(0.0, 0.0, windowWidth_, windowHeight_), backPaint);
SkPaint paint;
SkRect rect;
rect.setXYWH(pointX_, pointY_, realWidth_, realHeight_);
canvas->drawImageRect(image.get(), rect, &paint);
// 把畫布資料發送到render service server端,并在server端送顯
rsSurface_->FlushFrame(framePtr_);
}
說明:
- MakeFromFILE加載圖檔。
- MakeFromEncoded對加載圖檔資料進行轉化。
- SkCanvas通過drawRect和drawImageRect繪制圖像。
- FlushFrame把畫布資料送顯,經過IPC通信會把buffer資訊傳到server端BufferQueue。總體過程參考申請buffer的過程。
GSError BufferQueue::FlushBuffer(int32_t sequence, const BufferExtraData &bedata,
int32_t fence, const BufferFlushConfig &config)
{
ScopedBytrace bufferIPCSend("BufferIPCSend");
sret = DoFlushBuffer(sequence, bedata, fence, config);
if (sret == GSERROR_OK) {
if (listener_ != nullptr) {
ScopedBytrace bufferIPCSend("OnBufferAvailable");
listener_->OnBufferAvailable();
} else if (listenerClazz_ != nullptr) {
ScopedBytrace bufferIPCSend("OnBufferAvailable");
listenerClazz_->OnBufferAvailable();
}
}
return sret;
}
說明:
- DoFlushBuffer會調用display驅動FlushCache。
- OnBufferAvailable調用的是RSRenderServiceListener::OnBufferAvailable,進行可用buffer計量,同時會通知Vsync可以同步。
3.遞歸重新整理圖檔,把BootAnimation::Draw注冊成回調函數,在DispatchMain中循環調用,達到逐個圖檔渲染的效果。
void BootAnimation::RequestNextVsync()
{
if (needCheckExit) {
CheckExitAnimation();
}
struct FrameCallback cb = {
.frequency_ = freq_,
.timestamp_ = 0,
.userdata_ = nullptr,
.callback_ = std::bind(&BootAnimation::Draw, this),
};
// 注冊回調
GSError ret = VsyncHelper::Current()->RequestFrameCallback(cb);
}
說明:
- 會通過VsyncHelper注冊回調,定時調用Draw。
總結
開機動畫的CPU渲染過程是從render_service擷取buffer,在client端用buffer+skia建立canvas,進行繪制。逐個圖檔flush到render service server端,在server端完成送顯。
更多原創内容請關注:深開鴻技術團隊
入門到精通、技巧到案例,系統化分享HarmonyOS開發技術,歡迎投稿和訂閱,讓我們一起攜手前行共建鴻蒙生态。
想了解更多關于鴻蒙的内容,請通路:
51CTO和華為官方合作共建的鴻蒙技術社群
https://ost.51cto.com/#bkwz