OpenHarmony 源码解析之图形子系统 (一) 原创 精华

中软国际AIoT开发者社区
发布于 2021-9-24 11:19
浏览
12收藏

作者:姜怀修

1 简介

本文基于OpenHarmony 2.0为基础,讲解Graphic子系统。Graphic子系统主要包括UI组件、布局、动画、字体、输入事件、窗口管理、渲染绘制等模块,构建基于轻量OS应用框架满足硬件资源较小的物联网设备或者构建基于标准OS的应用框架满足富设备(如平板和轻智能机等)的OpenHarmony系统应用开发。

1.1 OpenHarmony 架构图

OpenHarmony 源码解析之图形子系统 (一)-鸿蒙开发者社区

2 基础知识

2.1 标准系统的图形子系统

提供了图形接口能力和窗口管理接口能力,
支持应用程序框架子系统和ACE等子系统使用。支持所有运行标准系统的设备使用。

其主要的结构如下图所示:

OpenHarmony 源码解析之图形子系统 (一)-鸿蒙开发者社区

Wayland Client Adapter

OpenHarmony新增的窗口系统的Client端,通过AAFWKability为应用和UI框架提供WindowSurface渲染和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 图形子系统重要类全景

OpenHarmony 源码解析之图形子系统 (一)-鸿蒙开发者社区

3 图形子系统环境初始化

graphic.rc可以看出,启动了wms_servicebootanimation,这两个服务是显示启动。

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继承自VsyncModuleVsyncModule::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_值为trueDRM每隔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之后画出来的。

  1. bootanimation.raw的解析

    raw文件的读取,分成若干帧数据,每次读取一块数据,后面的Draw会取出这块数据copySurfaceBuffer的虚拟地址用来显示,后面会说到。

    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;
    }
    
  2. 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开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
bootanimationTool.rar 2.87K 117次下载
已于2021-12-13 17:10:55修改
14
收藏 12
回复
举报
5条回复
按时间正序
/
按时间倒序
萨瓦迪迪
萨瓦迪迪

流弊,膜拜大佬~

回复
2021-9-24 11:34:03
强哥1999
强哥1999

写的真不错,收藏了

回复
2021-9-24 11:34:37
wx60b7765540463
wx60b7765540463

能把问题分析地如此透彻的作者一定很帅吧~~~

回复
2021-9-24 11:35:00
mb6096018f96945
mb6096018f96945

马上上手更换一个开机动画,走你

回复
2021-9-24 13:41:01
爱吃土豆丝的打工人
爱吃土豆丝的打工人

好文,排版优美~

1
回复
2021-9-26 09:20:50
回复
    相关推荐