OpenHarmony 源码解析之图形子系统 (一) 原创 精华
作者:姜怀修
1 简介
本文基于OpenHarmony 2.0
为基础,讲解Graphic子系统。Graphic子系统主要包括UI组件、布局、动画、字体、输入事件、窗口管理、渲染绘制等模块,构建基于轻量OS应用框架满足硬件资源较小的物联网设备或者构建基于标准OS的应用框架满足富设备(如平板和轻智能机等)的OpenHarmony系统应用开发。
1.1 OpenHarmony 架构图
2 基础知识
2.1 标准系统的图形子系统
提供了图形接口能力和窗口管理接口能力,
支持应用程序框架子系统和ACE等子系统使用。支持所有运行标准系统的设备使用。
其主要的结构如下图所示:
Wayland Client Adapter
是OpenHarmony
新增的窗口系统的Client端,通过AAFWK
的ability
为应用和UI
框架提供Window
、Surface
渲染和Vsync
帧同步信号。
-
Surface:图形缓冲区管理接口,负责管理图形缓冲区和高效便捷的轮转缓冲区;
-
WindowManager:窗口管理器接口,负责创建和管理窗口;
Wayland Server
使用开源的三方库提供窗口管理、合成和输入事件的管理。Wayland详细介绍
-
Compositor:合成器,负责合成各个图层;
-
Renderer:合成器的后端渲染模块;
-
Shell:提供多窗口能力;
-
Vsync:垂直同步信号管理接口,负责管理所有垂直同步信号注册和响应;
-
Input Manger:多模输入模块,负责接收事件输入;
-
Wayland protocols:Wayland 进程间通信协议。
2.2 图形子系统相关代码目录
foundation/graphic/standard/
├── frameworks # 框架代码目录
│ ├── bootanimation # 开机动画目录
│ ├── surface # Surface代码
│ ├── vsync # Vsync代码
│ └── wm # WindowManager代码
├── interfaces # 对外接口存放目录
│ ├── innerkits # native接口存放目录
│ └── kits # js/napi接口存放目录
└── utils # 小部件存放目录
ace_ace_engine # JS UI框架
aafwk_standard #元能力子系统
third_party_flutter #依赖的三方库
flutter third_party_wayland-ivi-extension #依赖的三方库
wayland third_party_wayland_standard #依赖的三方库
wayland third_party_wayland-protocols_standard #依赖的三方库
wayland third_party_weston #wayland的参考实现weston
2.3 图形子系统重要类全景
3 图形子系统环境初始化
从graphic.rc
可以看出,启动了wms_service
和bootanimation
,这两个服务是显示启动。
foundation\graphic\standard\graphic.rc
service wms_service /system/bin/wms_service
class weston
disabled
seclabel u:r:wms_service:s0
service bootanimation /system/bin/bootanimation
class weston
disabled
oneshot
on weston_start
start wms_service
start bootanimation
上面这两个服务的启动是分别进入了各自的入口函数main
来完成的。
wms_service
的启动,通过startServer
来完成,同时垂直同步信号管理Vsync
初始化也在main
完成,通过VsyncModule::GetInstance()-\>Start()
方法注册。
3.1 wms_service初始化
从StartServer
可以看出WindowManagerServer
的实例通过AddSystemAbility
接口注册到SystemAbilityManager
,初始化完成。
int main()
{
int ret = StartServer();
printf("StartServer: %d\n", ret);
ret = VsyncModule::GetInstance()->Start();
printf("VsyncModule->Start return %d\n", ret);
if (ret < 0) {
return ret;
}
IPCSkeleton::JoinWorkThread();
}
int StartServer()
{
WMLOG_I("wms start_server");
int result = 0;
while (1) {
auto sm = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
if (sm != nullptr) {
// new WindowManagerServer的实例
sptr<IRemoteObject> service = new WindowManagerServer();
// 通过AddSystemAbility接口注册到SystemAbilityManager
result = sm->AddSystemAbility(WINDOW_MANAGER_ID, service);
break;
}
WMLOG_I("wms start_server SystemAbilityManager is nullptr");
usleep(SLEEP_TIME);
}
return result;
}
3.2 Vsync初始化
VsyncModuleImpl
继承自VsyncModule
,VsyncModule::GetInstance()->Start()
,实际调用VsyncModuleImpl::Start()
,从InitSA()
的调用关系可以看出,最终将VsyncManager
的实例注册到SystemAbilityManager
。
VsyncError VsyncModuleImpl::Start()
{
VsyncError ret = InitSA();
if (ret != VSYNC_ERROR_OK) {
return ret;
}
drmFd_ = drmOpen(DRM_MODULE_NAME, nullptr);
if (drmFd_ < 0) {
VLOG_ERROR_API(errno, drmOpen);
return VSYNC_ERROR_API_FAILED;
}
VLOGD("drmOpen fd is %{public}d", drmFd_);
vsyncThreadRunning_ = true;
vsyncThread_ = std::make_unique<std::thread>([this]()->void {
VsyncMainThread();
});
return VSYNC_ERROR_OK;
}
VsyncError VsyncModuleImpl::InitSA()
{
return InitSA(VSYNC_MANAGER_ID);
}
VsyncError VsyncModuleImpl::InitSA(int32_t vsyncSystemAbilityId)
{
vsyncSystemAbilityId_ = vsyncSystemAbilityId;
int tryCount = 0;
while (!RegisterSystemAbility()) {
if (tryCount++ >= RETRY_TIMES) {
VLOGE("RegisterSystemAbility failed after %{public}d tries!!!", RETRY_TIMES);
return VSYNC_ERROR_SERVICE_NOT_FOUND;
} else {
VLOGE("RegisterSystemAbility failed, try again:%{public}d", tryCount);
usleep(USLEEP_TIME);
}
}
return VSYNC_ERROR_OK;
}
bool VsyncModuleImpl::RegisterSystemAbility()
{
if (isRegisterSA_) {
return true;
}
auto sm = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
if (sm) {
sm->AddSystemAbility(vsyncSystemAbilityId_, vsyncManager_);
isRegisterSA_ = true;
}
return isRegisterSA_;
}
我们再来看下VsyncMainThread
,调用WaitNextVBlank
获取到一个时间间隔,即VBlank
。
VBlank的定义:
在将光信号转换为电信号的扫描过程中,扫描总是从图像的左上角开始,水平向前行进,同时扫描点也以较慢的速率向下移动。当扫描点到达图像右侧边缘时,扫描点快速返回左侧,重新开始在第1行的起点下面进行第2行扫描,行与行之间的返回过程称为水平消隐。一幅完整的图像扫描信号,由水平消隐间隔分开的行信号序列构成,称为一帧。扫描点扫描完一帧后,要从图像的右下角返回到图像的左上角,开始新一帧的扫描,这一时间间隔,叫做垂直消隐,也称场消隐(VBlank)。
void VsyncModuleImpl::VsyncMainThread()
{
while (vsyncThreadRunning_) {
vsyncManager_->CheckVsyncRequest();
if (!vsyncThreadRunning_) {
break;
}
int64_t timestamp = WaitNextVBlank();
if (timestamp < 0) {
continue;
}
vsyncManager_->Callback(timestamp);
}
}
vsyncThreadRunning_
值为true
,DRM
每隔timestamp
发出VSYNC
信号,触发对UI
进行渲染,至此Vsync
初始化完成。
3.3 bootanimation启动过程
bootanimation
的启动,主要是解析bootanimation.raw
文件,然后使用OpenHarmony
处理线程间通信的机制EventHandler
来执行Main函数。
int main(int argc, const char *argv[])
{
int64_t start = GetNowTime();
std::string filename = "/system/etc/bootanimation.raw";
// 解析bootanimation.raw
if (RawParser::GetInstance()->Parse(filename)) {
return -1;
}
LOG("time: %{public}lld", GetNowTime() - start);
// 创建新线程执行Main函数
auto runner = AppExecFwk::EventRunner::Create(false);
auto handler = std::make_shared<AppExecFwk::EventHandler>(runner);
handler->PostTask(Main);
runner->Run();
return 0;
}
我们先看bootanimation.raw
的解析,再看Main
函数的执行,是如何创建window
之后画出来的。
-
bootanimation.raw的解析
对
raw
文件的读取,分成若干帧数据,每次读取一块数据,后面的Draw
会取出这块数据copy
到SurfaceBuffer
的虚拟地址用来显示,后面会说到。int32_t RawParser::Parse(std::string \&filename) { // 读取bootanimation.raw文件,一次读取到&compressed[0] int32_t ret = ReadFile(filename); if (ret) { LOG("ReadFile failed"); return ret; } auto magic = reinterpret_cast<char *>(&compressed[0]); if (strstr(magic, "RAW.diff") == nullptr) { constexpr uint32_t magicHeaderStringLength = magicHeaderLength + 2; char fileMagic[magicHeaderStringLength]; memcpy_s(fileMagic, sizeof(fileMagic) - 1, magic, magicHeaderLength); LOG("file magic is wrong, %{public}s", fileMagic); return -1; } struct HeaderInfo { uint32_t type; uint32_t offset; uint32_t length; uint32_t clen; uint8_t mem[0]; }; // 一次读取magicHeaderLength=8位,ipos向后移动 struct HeaderInfo *info = reinterpret_cast<struct HeaderInfo *>(&compressed[magicHeaderLength]); uint32_t ipos = reinterpret_cast<uint8_t *>(info) - reinterpret_cast<uint8_t *>(magic); while (ipos < clength) { …… if (info->clen < 0) { LOG("info->clen less then 0"); return -1; } // 构建Zlibinfo并加入到infos中,用于后续解压 struct ZlibInfo zi = { .type = info->type, .offset = info->offset, .length = info->length, .clen = info->clen, .mem = info->mem, }; infos.push_back(zi); // for BUS_ADRALN constexpr uint32_t memalign = 4; uint32_t align = info->clen - info->clen / memalign * memalign; if (align) { align = memalign - align; } info = reinterpret_cast<struct HeaderInfo *>(info->mem + info->clen + align); ipos = reinterpret_cast<uint8_t *>(info) - reinterpret_cast<uint8_t*>(magic); } if (infos.empty()) { LOG("file have no zip"); return -1; } return 0; }
-
window的创建并显示
void Main()
{
// 获取Window的宽,高和其他配置信息
const int32_t width = WindowManager::GetInstance()->GetMaxWidth();
const int32_t height = WindowManager::GetInstance()->GetMaxHeight();
WindowConfig config = {
.width = width,
.height = height,
.pos_x = 0,
.pos_y = 0,
.format = PIXEL_FMT_RGBA_8888,
.type = WINDOW_TYPE_NORMAL,
};
// 通过WindowManager的CreateWindow创建一个Window
Static std::unique_ptr<Window> window = WindowManager::GetInstance()->CreateWindow(&config);
if (window == nullptr) {
LOG("window is nullptr");
} else {
// Window Position(0,0),计算宽和高,并置顶显示。
window->Move(0, 0);
window->ReSize(width, height);
window->SwitchTop();
// 注册Window创建的回调函数
auto onWindowCreate = [](uint32_t pid) {
constexpr uint32_t stringLength = 32;
char filename[stringLength];
(void)snprintf_s(filename,
sizeof(filename), sizeof(filename) - 1, "/proc/%d/cmdline", pid);
auto fp = fopen(filename, "r");
if (fp == nullptr) {
return;
}
char cmdline[stringLength] = {};
fread(cmdline, sizeof(char), stringLength, fp);
fclose(fp);
if (strcmp(cmdline, "com.ohos.systemui") == 0 ||
strcmp(cmdline, "com.ohos.launcher") == 0) {
LOG("exiting");
exit(0);
}
};
window->RegistOnWindowCreateCb(onWindowCreate);
// 同步窗口数据,每隔一次执行Draw
DoubleSync(0, window.get());
}
PostTask([]() { LOG(""); exit(0); }, PRETIME + POSTTIME);
}
void DoubleSync(int64_t time, void *data)
{
static int32_t count = 0;
if (count % 2 == 0) {
// 根据window的data开始画窗口
Draw(reinterpret_cast<Window *>(data));
}
count++;
RequestSync(DoubleSync, data);
}
void Draw(Window *window)
{
// 根据window的信息获取到surface对象,它是个producer
sptr<Surface> surface = window->GetSurface();
if (surface == nullptr) {
LOG("surface is nullptr");
return;
}
do {
sptr<SurfaceBuffer> buffer;
int32_t releaseFence;
BufferRequestConfig config;
window->GetRequestConfig(config);
// 请求一个待生产的SurfaceBuffer对象
SurfaceError ret = surface->RequestBuffer(buffer, releaseFence, config);
if (ret == SURFACE_ERROR_NO_BUFFER) {
break;
}
if (ret) {
LOG("RequestBuffer failed: %{public}s", SurfaceErrorStr(ret).c_str());
break;
}
if (buffer == nullptr) {
break;
}
static uint32_t count = 0;
// 获得SurfaceBuffer的虚拟地址
auto addr = static_cast<uint8_t *>(buffer->GetVirAddr());
while (true) {
// 将bootanimation.raw的数据copy到addr,给SurfaceBuffer带上自定义数据,也就是生产者。
int32_t drawRet = DoDraw(addr, buffer->GetWidth(), buffer->GetHeight(),count);
if (drawRet && count == 0) {
exit(1);
}
if (drawRet) {
count--;
continue;
}
break;
}
BufferFlushConfig flushConfig = {
.damage = {
.w = buffer->GetWidth(),
.h = buffer->GetHeight(),
},
};
// 归还生产好的SurfaceBuffer并携带宽高信息
ret = surface->FlushBuffer(buffer, -1, flushConfig);
LOG("Sync %{public}d %{public}s", count, SurfaceErrorStr(ret).c_str());
fflush(stdout);
count++;
} while (false);
}
int32_t DoDraw(uint8_t *addr, uint32_t width, uint32_t height, uint32_t count)
{
constexpr uint32_t stride = 4;
int32_t addrSize = width * height * stride;
static auto frame = std::make_unique<uint8_t[]>(addrSize);
static uint32_t last = -1;
int64_t start = GetNowTime();
uint8_t *data = nullptr;
uint32_t length;
uint32_t offset;
// 读取一次bootanimation数据
if (RawParser::GetInstance()->GetData(count, data, offset, length)) {
return -1;
}
if (last != count && length > 0) {
memcpy_s(frame.get() + offset, addrSize - offset, data, length);
}
// copy一次bootanimation.raw数据到addr。
memcpy_s(addr, addrSize, frame.get(), addrSize);
last = count;
LOG("GetData time: %{public}lld, data: %{public}p, length: %{public}d",
GetNowTime() - start, data, length);
return 0;
}
4 总结
以上内容首先对OpenHarmony
系统启动时图形子系统的环境初始化,然后对开机动画的显示过程做了源码分析。后续内容会持续发布。另外,我们根据开机动画的解析方式,写了一个工具可以生成bootanimation.raw
,即定制开机动画,见附件。
更多原创内容请关注:开鸿 HarmonyOS 学院
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
流弊,膜拜大佬~
写的真不错,收藏了
能把问题分析地如此透彻的作者一定很帅吧~~~
马上上手更换一个开机动画,走你
好文,排版优美~