Pag实现原理

上一篇我们介绍了Pag的基本用法,这一篇开始我们来看一看Pag的核心实现原理。
由于都是利用AE制作动画,AE动画的基本元素也是各种Layer,Composition等。所以Pag本身的数据组成结构基本是和Lottie基本一样的,甚至源码实现上也借鉴了不少Lottie的实现。总的来说Pag最大的区别就是自己实现了一套自己的渲染系统。

环境搭建

我目前使用的编译环境是版本

  • OS:MaxOS 12.3.1
  • XCode:13.2.1
  • AndroidStudio:BumbleBee 2021.1.1
  • NDK:21.4.7075529
  • 源码Sha:5e10390e472e2fe269bf238dc057fa463585de51
    源码下载和环境搭建步骤如下:
  1. 从Github下载源码Pag的源码
  2. 进入libpag工程目录后,运行./sync_deps.sh。这个命令会去下载node cmake ninja yasm git-lfs emcc这几个编译需要用到的工具。还会使用depsync去安装vendor.json文件内的所有Pag使用到的依赖库
  3. 各平台IDE导入
    • libpag使用CMakeList组织编译结构,可以用CLion直接打开工程根目录,打开后如果sync报错The local md5 cache is out of date! Please run the 'update_baseline.sh' to regenerate the cache.,根据提示在终端运行update_baseline.sh后再次sync即可成功
    • MacOS和IOS可以使用XCode打开macosios目录的workspace项目文件即可
    • Android平台直接用AndroidStudio根目录下的android目录即可

由于我对Android平台最熟悉,这里就用Android平台来进行分析了。其他平台的核心逻辑是一模一样的,只是创建PagSurface的方式大同小异罢了。

准备知识

  • 从Pag的源码结构可以知道它是一个C++语言为核心的动画库,所以我们需要一定的C++知识储备
  • 从介绍里面可以知道Pag是通过图形硬件API为核心来实现一套动画库,需要对OpenGL之类的图形API有一定的了解
  • Pag的依赖里面有用一些多媒体相关的依赖库,如果对多媒体图像有一定了解更好

情景分析

我分析一个软件的原理比较喜欢从一个简单功能场景入手,抽丝剥茧似的从上到下分析。从上一篇…/pag_introduce/pag_introduce.md可以知道使用Pag的方法仅仅如下即可:

// 1. 加载Pag文件
PAGFile pagFile =  PAGFile.Load("/path/xxx.pag");
// 2. 创建Pag画布
PAGSurface pagSurface = PAGSurface.FromSurface(mEncoder.createInputSurface());
// 3. 创建Pag播放器
pagPlayer = new PAGPlayer();
// 4. 绑定播放渲染画布
pagPlayer.setSurface(pagSurface);
// 5. 设置Pag播放数据源
pagPlayer.setComposition(pagFile);
// 6. 在渲染线程循环播放Pag
pagPlayer.setProgress(progress);
pagPlayer.flush();

表示为时序图大概如下:

加载Pag文件

我们先看看PAGFile.Load()方法,里面有三种实现,提供了路径、assets、bytes三种加载方式。,都大同小异,我们就只分析加载本地文件路径的这个实现了。里面的基本流程追溯上比较简单我这里总结了一张调用时序图。里面的核心是文件的解析部分,基本也能猜得出来这里肯定就是把文件读入内存,分析出图层,可编辑文字,Composition等信息。

简单的来说Load方法核心就是填充PagFile内部的文件信息。PagFile我们可以理解为一个AE合成,可以直接用于渲染,这部分需要后续渲染的时候再具体分析。可以通过下面这个类图关系加深一些理解。

此外官方有在这里也给Pag的文件规范:pag-spec 通过这个规范我们能理解到Pag文件的数据组成,这里就不过多多文件解析做赘述。

创建Pag画布

和加载Pag过程十分类似,创建画布也有好几种方式:FromSurfaceTexture、FromSurface、FromTexture、FromTextureForAsyncThread。总结下其实就是两种:1. 传入Android Surface画布 2. 传入OpenGL纹理(需要在GL线程)。其中传入Surface的方式可以指定GLContext用于共享OpenGL上下文。几种方式流程大同小异,我们这里就直接分析FromSurface。这个流程很简单,我直接上图了:

里面其实最核心的一步是调用GPUDrawable::FromWindow创建了一个GPUDrawable对象。这个Drawable可不是Android里面的那个Drawable对象,一般在3D领域叫做Renderable。更接近于Android里面的Surface的概念,我觉得应该叫BeDrawable跟贴切。这里创建GPUDrawable的代码如下:

std::shared_ptr<GPUDrawable> GPUDrawable::FromWindow(ANativeWindow* nativeWindow,EGLContext sharedContext) {if (nativeWindow == nullptr) {LOGE("GPUDrawable.FromWindow() The nativeWindow is invalid.");return nullptr;}return std::shared_ptr<GPUDrawable>(new GPUDrawable(nativeWindow, sharedContext));
}GPUDrawable::GPUDrawable(ANativeWindow* nativeWindow, EGLContext eglContext): nativeWindow(nativeWindow), sharedContext(eglContext) {updateSize();
}
void GPUDrawable::updateSize() {if (nativeWindow != nullptr) {_width = ANativeWindow_getWidth(nativeWindow);_height = ANativeWindow_getHeight(nativeWindow);}
}

可以看到创建GPUDrawable真是十分简单,就是创建了一个对象,然后赋值ANativeWindow和EGLContext后更新了尺寸而已。从这里可以得知,我们可以在任意线程通过Surface来创建PAGSurface。这里顺便提一下另外两个通过纹理创建PAGSurface的方法:FromTexture、FromTextureForAsyncThread。这两个必须传入拥有这个纹理的上下文线程,最终会把Pag渲染到这个纹理上面。第二个方法会创建一个新共享当前线程的OpenGL上下文,这样就可以实现异步渲染的功能,比较适合视频预览+录制的场景使用。

创建播放器

创建播放器的过程十分简单,也都是实例化和赋值操作,如下图:

这里我们需要注意Native层创建了两个非常重要的对象:PAGStage和RenderCache。Stage是舞台的意思,舞台是干嘛的?肯定是用来实际做渲染的舞台对象,而之前提到的那些PagLayer应该就是上面的舞者了。RenderCache顾名思义是渲染缓存的意思,很明显就是用来防止多次计算的缓存系统。这两个之后看具体的渲染流程的时候再好好分析。

绑定播放渲染画布

这步同样比较简单,也就是一层一层调用到Native层的PagPlayer#setSurface()。我们直接看这个函数代码就可以了

void PAGPlayer::setSurfaceInternal(std::shared_ptr<PAGSurface> newSurface) {if (pagSurface == newSurface) {return;}if (newSurface && newSurface->pagPlayer != nullptr) {LOGE("PAGPlayer.setSurface(): The new surface is already set to another PAGPlayer!");return;}if (pagSurface) {pagSurface->pagPlayer = nullptr;pagSurface->rootLocker = std::make_shared<std::mutex>();}pagSurface = newSurface;if (pagSurface) {pagSurface->pagPlayer = this;pagSurface->contentVersion = 0;pagSurface->rootLocker = rootLocker;updateStageSize();} else {stage->setContentSizeInternal(0, 0);}
}

可以看到其实就是判断是否已经被设置了一下画布对象如果有的话就解绑后重新设置。

设置播放数据源

PagPlayer的数据源设置和设置PagSurface的过程十分类似:

void PAGPlayer::setComposition(std::shared_ptr<PAGComposition> newComposition) {LockGuard autoLock(rootLocker);auto pagComposition = stage->getRootComposition();if (pagComposition == newComposition) {return;}if (pagComposition) {auto index = stage->getLayerIndexInternal(pagComposition);if (index >= 0) {stage->doRemoveLayer(index);}delete reporter;reporter = nullptr;}pagComposition = newComposition;if (pagComposition) {stage->doAddLayer(pagComposition, 0);reporter = FileReporter::Make(pagComposition).release();updateScaleModeIfNeed();}
}

可以看到最核心的代码就是:stage->doAddLayer(pagComposition, 0);了。即把PagComposition设置到了stage的Layer的最底层。这个PAGComposition也就是前面我们创建的PagFile了,之前我们也介绍过,PAGComposition本质也是PagLayer。所以设置播放源其实也就是把PagFile设置到了PagPlayer的PagStage的最底层。

渲染线程播放

这个部分比较复杂,也是Pag的核心,libpag里面还内置了一套用于2D图形渲染的引擎叫做TGFX,根据官方介绍说是为了替换skia自研实现的。
我准备把渲染部分分为Pag渲染过程,Pag图层组织,TGFX渲染架构,TGFX 2D渲染实现等内容来讲。下一篇先讲最容易的Pag图层组织吧。

总结

通过对PagFile、PagComposition、PagLayer、PagPlayer、PagStage、Drawable等结构的组织,我们大概脑海里能有这样一张流程图:

简单的理解也就是一套配置 + 渲染的Pipeline模型。

腾讯Libpag动画库研究2(Pag实现原理)相关推荐

  1. 腾讯Libpag研究1(扫盲和介绍)

    Pag介绍 PAG是腾讯在好几年前发布的一种动画文件格式,但是以前一直没开源,用的人也很少,也就一直没有关注.不过他们终于在今年1月份开源了,我也是这个时候开始去研究PAG,发现里面有不少值得学习的东 ...

  2. 全面兼容各端的动画库PAG,对标Lottie

    感谢Tencent 开源了PAG库,终于有了国人自己的动画库了,该库兼容:移动端,桌面端,,WEB端,还有小程序端,真可谓用心了,而且PAG库相对其他库,使用的pag文件更小,更流畅,而且PAG还兼容 ...

  3. 前端必看!微信都在用的开源动效方案【PAG动效】

    1.PAG 是什么? 在 web 中实现一个动画, css animation 声明一下各个时间点的样式就好了, 写起来并不麻烦.但是当设计给的动画越来越复杂, 还原度要求越来越高的情况下, 单纯依赖 ...

  4. react动画库_React 2020动画库

    react动画库 Animations are important in instances like page transitions, scroll events, entering and ex ...

  5. ceph 数据库_Facebook打开了动画库,Ceph在Red Hat找到了新家,等等

    ceph 数据库 开源新闻让您阅读愉快. 2014年4月26日至5月2日 在本周的开源新闻摘要中,我们介绍了开源Facebook动画库Pop,Red Hat对Ceph的收购等等. 您在本周还阅读了哪些 ...

  6. 11 个非常受欢迎的 JavaScript 动画库

    我在网上寻找好用整洁的Javascript动画库时,我发现许多开发者推荐的一些动画库.经过我的研究,我收集了11个最好的动画库供你的使用学习,并且也可以应用到你的程序中. 1.Three.js 地址: ...

  7. tween.js 用户指南 - 与 Three.js 配合使用的补间动画库

    tween.js 用户指南 - 与 Three.js 配合使用的补间动画库 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途- ...

  8. 5个最佳React动画库

    英文 | https://betterprogramming.pub/the-5-best-animation-libraries-for-react-8dc5a8bc2abe 翻译 | 杨小二 用户 ...

  9. Lottie安卓开源动画库使用

    碉堡的Lottie Airbnb最近开源了一个名叫Lottie的动画库,它能够同时支持iOS,Android与ReactNative的开发.此消息一出,还在苦于探索自定义控件各种炫酷特效的我,兴奋地就 ...

最新文章

  1. 计算机编程书籍-Python金融大数据分析
  2. Hacking PostgreSQL
  3. 计算机实训课教案模板,CorelDRAW实训课教案(7周)
  4. C和指针之Eratosthenes-埃拉托斯特尼筛方法找质数
  5. Volley学习总结
  6. 2021快手奢侈品行业数据价值报告
  7. C# 在DbContext内通过DbSet名称来访问DbSet
  8. vs基于控制台应用程序的定时发送邮件_.NET Core 下收发邮件之 MailKit
  9. MLT-type渲染算法review
  10. C++ 输入多行以空格分隔的数将其变为数组,动态申请二维数组
  11. 队列元素逆置 数据结构 队列
  12. Kattis Doors
  13. outlook 签名_快速提示:轻松在Outlook 2007中的签名之间切换
  14. 后台接收前台传来的图片并保存在本地
  15. 【大脑】--如何让大脑快速记忆
  16. 怎么一键拼图?快速拼图这样做
  17. bzoj 5454: Subsequence
  18. 如何知道当前操作系统是centos的哪个版本和内核版本?
  19. 高精度结构光工业3D相机Mech-Eye PRO全面升级:可选蓝光/白光版本,适合中距离应用...
  20. 关于继电器开关带来的干扰

热门文章

  1. 写一个无尽的拉格朗日速本脚本
  2. 定义为“AI计算公司”,业绩大幅下滑的英伟达新变量在哪?
  3. 打开NVIDIA控制面板弹出“您当前未使用连接到nvidia gpu的显示器”
  4. iFunk超极本,超乎你的想象
  5. such as, for example, e.g., and so on, etc., et al, i.e. 用法简述
  6. Newifi路由器第三方固件玩机教程,这个路由比你想的更强大以及智能_Newifi y1刷机_smzdm
  7. 常用电线电缆型号的表示
  8. 高手新手都能用的140个电脑技巧
  9. js代码 父页面调用子页面中的js方法,子页面调用父页面中的js方法
  10. ChatGPT开始联网,最后的封印解除了