- 图形学
【免费下载链接】skia
Skia is a complete 2D graphic library for drawing Text, Geometries, and Images. See documentation for contribution instructions.
导读
本文以 Skia 仓库中的 tools/skottie_ios_app/README.md 为主线,完整讲解如何将 Skottie(Skia 的 Lottie 动画渲染模块)示例应用skottie_example编译为 iOS.app包,并分别针对Metal、纯 CPU、OpenGL三种渲染后端给出可直接复制的 GN 参数配置与构建命令。读完本文,你将掌握args.gn中各关键开关(skia_use_metal、skia_enable_ganesh、skia_enable_pdf、skia_use_expat)的实际作用,理解示例应用内部的视图/上下文分层结构,以及三种后端在源码层面是如何被选择与实现的,从而能够根据自己的设备与性能需求快速搭建、定制 Skottie iOS 演示环境。
1. Skottie iOS 示例应用是什么
skottie_ios_app是 Skia 仓库中一个独立的 iOS 示例工程,其唯一产物是名为skottie_example的应用 bundle。它把 modules/skottie 模块包装成 Objective-C 接口,在一个可滚动的UIScrollView中依次加载并播放打包进 bundle 的多段 Lottie 动画 JSON(见 BUILD.gn 中data_sources列出的resources/skottie/下的动画资源)。
从源码结构看,该示例的核心设计是将渲染后端与动画控制逻辑解耦:
- SkiaContext.h 定义统一接口:
makeViewWithController:withFrame:负责为某个动画控制器创建对应后端的UIView,getViewController:用于反向取回控制器; - SkottieViewController.h 提供
loadAnimation:、seek:、togglePaused、currentTime、animationDurationSeconds等 Objective-C 接口,把modules/skottie/include/Skottie.h的skottie::Animation封装为可被 UIKit 直接调用的对象; - 具体后端实现在 SkiaMetalContext.mm、SkiaGLContext.mm、SkiaUIContext.mm 三个文件中。
在main.mm中,应用通过预编译宏选择后端:
#if defined(SK_GANESH) && defined(SK_METAL) [self setSkiaContext:MakeSkiaMetalContext()]; #elif defined(SK_GANESH) && defined(SK_GL) [self setSkiaContext:MakeSkiaGLContext()]; #else [self setSkiaContext:MakeSkiaUIContext()]; #endif也就是说,args.gn中的配置最终会决定SK_GANESH、SK_METAL、SK_GL这三个宏是否定义,进而决定运行时使用哪个渲染后端。这也解释了为什么同一个工程、三份构建配置可以分别产出 Metal、CPU、OpenGL 三种版本。
2. 构建前置条件与目录准备
三种后端的构建流程完全一致,都需要以下前置条件:
- Skia 源码根目录:下文用
$SKIA_ROOT_DIRECTORY指代(即克隆 Skia 仓库后所在的目录); - GN 与 Ninja:
bin/gn gen与ninja命令来自 Skia 内置的bin/工具链(bin/gn),首次使用时tools/git-sync-deps会同步第三方依赖; - Xcode 与 iOS SDK:编译
.appbundle 及后续安装到模拟器/真机都需要完整的 Xcode 环境;gn/ios.gni中定义了 iOS 应用 bundle 的打包逻辑(ios_app_bundle模板)。
每次构建都需要新建独立的输出目录并写入args.gn。下面按 Metal / CPU / OpenGL 三个后端分别给出完整的构建命令。三组命令均以cd $SKIA_ROOT_DIRECTORY开头,因此以下命令均需在仓库根目录下执行。
2.1 准备命令速查
| 步骤 | 命令 | 作用 |
|---|---|---|
| 进入仓库根目录 | cd $SKIA_ROOT_DIRECTORY | 定位源码根 |
| 创建输出目录 | mkdir -p out/ios_arm64_mtl | 隔离各后端产物 |
| 写入构建参数 | cat > out/ios_arm64_mtl/args.gn <<EOM ... EOM | 生成 GN 参数文件 |
| 同步依赖 | tools/git-sync-deps | 拉取/校验第三方依赖 |
| 生成构建文件 | bin/gn gen out/ios_arm64_mtl | 解析 args.gn 生成 ninja 文件 |
| 编译 | ninja -C out/ios_arm64_mtl skottie_example | 构建.appbundle |
注意:
bin/gn是 Skia 仓库自带的 GN 二进制,不要与系统安装的 GN 混淆;tools/git-sync-deps是仓库根目录下的 Python 脚本 tools/git-sync-deps,用于同步 DEPS 中声明的依赖。
3. 后端一:Metal(GPU 加速,真机推荐)
Metal 是 Apple 提供的低开销 GPU 图形 API,是 iOS 上性能最好的 Skia Ganesh 后端,适合在真机上运行。
cd $SKIA_ROOT_DIRECTORY mkdir -p out/ios_arm64_mtl cat > out/ios_arm64_mtl/args.gn <<EOM target_os="ios" target_cpu="arm64" skia_use_metal=true skia_use_expat=false skia_enable_pdf=false EOM tools/git-sync-deps bin/gn gen out/ios_arm64_mtl ninja -C out/ios_arm64_mtl skottie_example构建完成后,将out/ios_arm64_mtl/skottie_example.app安装到 iOS 设备即可(可通过 Xcode 的 Devices 面板或xcrun simctl安装)。
3.1 参数解读
target_os="ios":目标平台为 iOS,触发gn/ios.gni中的 iOS bundle 模板逻辑;target_cpu="arm64":64 位 ARM 架构,覆盖现代 iPhone/iPad 与 Apple Silicon 模拟器;skia_use_metal=true:启用 Metal 支持,编译时定义SK_METAL,Ganesh 使用 Metal 后端;skia_use_expat=false:禁用 expat(XML 解析库),Skottie 不需要它,可减小体积与构建时间;skia_enable_pdf=false:禁用 PDF 相关代码,进一步精简产物。
注意,此配置中未显式关闭 Ganesh,即skia_enable_ganesh保持默认开启(true)。这正是 BUILD.gn 中skia_enable_ganesh && skia_use_metal分支生效的条件:此时会额外编译SkMetalViewBridge.mm、SkiaMetalContext.mm,并链接Metal.framework与MetalKit.framework(见 BUILD.gn 第 60-64 行)。
3.2 源码实现:Metal 视图如何工作
Metal 后端由 SkiaMetalContext.mm 实现。初始化时创建MTLDevice(MTLCreateSystemDefaultDevice)、MTLCommandQueue,并用它们构造GrDirectContext:
GrMtlBackendContext backendContext = {}; backendContext.fDevice.reset((__bridge void*)[self metalDevice]); backendContext.fQueue.reset((__bridge void*)[self metalQueue]); fDContext = GrDirectContexts::MakeMetal(backendContext, GrContextOptions());渲染视图SkiaMtkView继承自MTKView,在drawRect:中通过SkMtkViewToSurface将MTKView的currentDrawable包装为SkSurface,调用[viewController draw:... toCanvas:...]完成 Skottie 帧渲染,随后:
fDContext->flushAndSubmit(surface.get()); id<MTLCommandBuffer> commandBuffer = [fQueue commandBuffer]; [commandBuffer presentDrawable:[self currentDrawable]]; [commandBuffer commit];值得注意的细节是帧率控制:视图创建时设置setPreferredFramesPerSecond:30(30 FPS);并且当动画处于暂停状态时,通过setEnableSetNeedsDisplay:paused与setPaused:paused让MTKView停止连续绘制,实现暂停即省电的效果。
4. 后端二:CPU(无 GPU 依赖,模拟器友好)
CPU 后端不依赖任何图形 API,使用 Skia 的 raster 路径把动画逐帧画到内存位图再上屏,适合在 iOS 模拟器或无 GPU 环境下运行,也便于调试。
cd $SKIA_ROOT_DIRECTORY mkdir -p out/ios_arm64_cpu cat > out/ios_arm64_cpu/args.gn <<EOM target_cpu="arm64" target_os="ios" skia_enable_ganesh=false skia_enable_pdf=false skia_use_expat=false EOM tools/git-sync-deps bin/gn gen out/ios_arm64_cpu ninja -C out/ios_arm64_cpu skottie_example构建完成后,将out/ios_arm64_cpu/skottie_example.app安装到设备或模拟器即可。
4.1 参数解读
skia_enable_ganesh=false:显式关闭 Ganesh GPU 渲染层,这是 CPU 后端与 Metal/OpenGL 后端的根本区别。此时SK_GANESH宏不定义,main.mm中的#if defined(SK_GANESH) && defined(SK_METAL)与#elif defined(SK_GANESH) && defined(SK_GL)均不成立,最终落入#else分支调用MakeSkiaUIContext();target_cpu="arm64"/target_os="ios":与 Metal 配置相同;skia_enable_pdf=false、skia_use_expat=false:精简依赖与产物。
从 BUILD.gn 的源码结构看,当skia_enable_ganesh为 false 时,两个 GPU 上下文文件都不会参与编译,而是仅编译SkiaUIContext.mm,并且不会链接任何 Metal/OpenGL 框架。
4.2 源码实现:CPU 视图如何工作
CPU 后端由 SkiaUIContext.mm 实现。SkiaUIView内部维护一块SkBitmap fBackBuffer作为后备缓冲:
drawRect:中按当前 view 尺寸重新分配位图(尺寸变化时才重建);- 构造
SkCanvas canvas(fBackBuffer),调用[viewController draw:... toCanvas:&canvas ...]让 Skottie 把动画帧画进位图; - 通过
SkCGDrawBitmap把位图交给 CoreGraphics 上屏; - 使用
NSTimer按kFrameRate = 1.0 / 30.0(30 FPS)调度下一次setNeedsDisplay;暂停状态下则不调度,从而停止重绘。
这段实现也印证了 README 中“CPU 后端”命名背后的机制:所有渲染都在 CPU 内存中完成,不涉及任何 GPU 命令队列。
5. 后端三:OpenGL(Ganesh + OpenGLES)
OpenGL 后端使用 Ganesh 的 OpenGL 实现(iOS 上即 OpenGLES),适合需要在 Ganesh 框架下对比 Metal/GL 渲染差异的场景。
cd $SKIA_ROOT_DIRECTORY mkdir -p out/ios_arm64_gl cat > out/ios_arm64_gl/args.gn <<EOM target_cpu="arm64" target_os="ios" skia_enable_ganesh=true skia_use_metal=false skia_enable_pdf=false skia_use_expat=false EOM tools/git-sync-deps bin/gn gen out/ios_arm64_gl ninja -C out/ios_arm64_gl skottie_example构建完成后,将out/ios_arm64_gl/skottie_example.app安装到设备或模拟器即可。
5.1 参数解读
skia_enable_ganesh=true:显式启用 Ganesh GPU 渲染层(与默认值一致,写出来更清晰);skia_use_metal=false:显式关闭 Metal,避免与 OpenGL 后端同时启用产生歧义;- 由于
skia_use_gl默认开启且本配置未修改,Ganesh 将采用 OpenGL 后端,SK_GL宏被定义; skia_enable_pdf=false、skia_use_expat=false:与前面两个配置一致的精简项。
对照 BUILD.gn 与第 65-69 行的框架列表:此时命中skia_enable_ganesh && skia_use_gl分支,仅追加编译SkiaGLContext.mm,并链接GLKit.framework与OpenGLES.framework。
5.2 源码实现:OpenGL 视图如何工作
OpenGL 后端由 SkiaGLContext.mm 实现。其核心步骤包括:
- 初始化时配置
GLKView的 drawable 格式:GLKViewDrawableColorFormatRGBA8888、24 位深度缓冲、8 位模板缓冲(见configure_glkview_for_skia,源码第 23-27 行); - 渲染时查询当前绑定帧缓冲(
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboid)),并通过GrBackendRenderTargets::MakeGL把GL_RGBA8帧缓冲包装为SkSurface,然后照常调用[viewController draw:... toCanvas:...]渲染 Skottie 帧; - 渲染完成后由 Ganesh 的 OpenGL 后端负责
flush到默认帧缓冲,GLKView负责上屏。
从代码结构可以推断,三个后端的差异被严格隔离在“创建SkiaContext/UIView”这一层,SkottieViewController的动画加载、seek、暂停/播放逻辑在三种后端间完全复用。
6. 三种后端配置对比与选型
| 配置项 | Metal | CPU | OpenGL |
|---|---|---|---|
target_os | "ios" | "ios" | "ios" |
target_cpu | "arm64" | "arm64" | "arm64" |
skia_enable_ganesh | 默认 true | false | true |
skia_use_metal | true | 不要求 | false |
skia_enable_pdf | false | false | false |
skia_use_expat | false | false | false |
| 运行时上下文 | MakeSkiaMetalContext() | MakeSkiaUIContext() | MakeSkiaGLContext() |
| 额外框架 | Metal、MetalKit | 无 | GLKit、OpenGLES |
| 适用场景 | 真机 GPU 渲染 | 模拟器/无 GPU | Ganesh + GL 对比 |
选型建议(基于仓库实现事实,非性能断言):
- 真机验证 Skottie 渲染效果:优先 Metal,它是 iOS 上 Skia 主推的 GPU 后端;
- 模拟器或 CI 无 GPU 环境:用 CPU 后端,零图形框架依赖,构建最轻;
- 需要 Ganesh 抽象层下的跨后端对比:同时构建 Metal 与 OpenGL 两套,观察同一动画在不同 GPU 后端的表现差异。
7. 运行示例应用:界面与交互
skottie_example的运行界面由 main.mm 构建:应用启动后,AppViewController读取 bundle 内data目录下所有.json动画资源,为每个可成功加载的动画创建一个SkottieViewController,并按动画原始宽高比计算高度,依次垂直排列进UIStackView,整体放入UIScrollView以支持滚动浏览多段动画。
交互方面,main.mm在根视图注册了UITapGestureRecognizer(handleTap:):点击屏幕会遍历所有SkiaViewController并调用togglePaused,即点击即可暂停/恢复所有动画,暂停状态会通过setNeedsDisplay立即反映到界面。
SkottieViewController的播放控制能力来自其内部的SkTimeKeeper与SkAnimationDraw(见 SkottieViewController.mm):
SkAnimationDraw负责用skottie::Animation::Builder从 JSON 数据构建动画,并在尺寸变化时计算居中缩放矩阵,然后fAnimation->render(canvas)渲染当前帧;SkTimeKeeper负责时间轴:基于SkTime::GetNSecs()的单调时钟计算播放位置,支持seek:(暂停时直接改播放时刻、运行时重置起点)、setStopAtEnd:(播到结尾自动暂停)以及togglePaused(暂停时记录当前时刻,恢复时校准时钟偏移)。
这两个类的实现恰好对应 SkottieViewController.h 中暴露的 Objective-C 接口(isPaused、togglePaused、seek:、currentTime、animationDurationSeconds、size等),如果你想把该示例改造为自己的播放器,直接复用这些接口即可。
8. 自定义与扩展建议
在保持仓库只读的前提下,你可以基于现有结构做如下扩展(仅需在本地副本中修改后重新构建):
- 更换/增加动画资源:参考 BUILD.gn 的
data_sources,把自己生成的 Lottie JSON 加入列表,重启应用即可在滚动列表中看到新动画; - 限制动画列表:
main.mm中遍历pathsForResourcesOfType:@"json" inDirectory:@"data",可以改为只加载指定文件名; - 调整帧率:Metal 视图的
setPreferredFramesPerSecond:30与 CPU 视图的kFrameRate = 1.0/30.0是两处可调的帧率参数,可按需改高或改低; - 改变交互:
handleTap:目前统一切换所有动画的暂停状态,可改为只切换被点击的那个视图对应的控制器(getViewController:接口已为此准备好); - 自定义渲染回调:
SkiaViewController draw:toCanvas:atSize:协议可被任意自定义控制器实现,不限于 Skottie,可作为在 iOS 上测试任意 Skia 绘制内容的宿主。
9. 常见问题排查
bin/gn gen报错找不到工具链:确认已安装 Xcode 并完成xcode-select -p配置;首次构建务必先执行tools/git-sync-deps;- 安装
.app失败(真机):检查设备是否信任开发者证书,.app需通过 Xcode 或xcrun devicectl安装并完成签名(示例产物默认面向开发调试场景); - CPU 后端动画不刷新:确认
skia_enable_ganesh=false已写入 args.gn;若宏选择逻辑生效,main.mm会调用MakeSkiaUIContext(),其NSTimer调度依赖isPaused状态,暂停时不会重绘,属于预期行为; - Metal 后端在模拟器上无画面:
MTLCreateSystemDefaultDevice在部分模拟器环境可能返回 nil(SkiaMetalContext.mm第 87-91 行会打印 "Metal is not supported on this device" 并返回 nil 上下文,main.mm检测到空上下文会abort),此时请改用 CPU 后端配置。
10. 延伸阅读
- 示例工程构建定义:tools/skottie_ios_app/BUILD.gn
- Skottie 渲染模块:
modules/skottie/include/Skottie.h(skottie::Animation的构建与渲染接口) - 后端上下文接口:tools/skottie_ios_app/SkiaContext.h
- 动画控制器封装:tools/skottie_ios_app/SkottieViewController.h 与 tools/skottie_ios_app/SkottieViewController.mm
- iOS 打包模板:
gn/ios.gni(ios_app_bundle) - 应用入口与交互逻辑:tools/skottie_ios_app/main.mm
- 示例动画资源:
resources/skottie/(BUILD.gndata_sources所列 JSON)
通过本文给出的三套args.gn与构建命令,你可以在同一份 Skia 源码上分别产出 Metal、CPU、OpenGL 三个版本的 Skottie iOS 示例应用,并借助源码理解每一份配置背后实际的渲染链路,为后续基于 Skottie 的 iOS 动画业务开发打好基础。
- 图形学
【免费下载链接】skia
Skia is a complete 2D graphic library for drawing Text, Geometries, and Images. See documentation for contribution instructions.
相关推荐
用 InstantDB 为 AI 编码应用构建实时后端:从包选择到完整示例的实战指南
用 InstantDB 为 AI 编码应用构建实时后端:从包选择到完整示例的实战指南 导读:本文基于仓库 client/sandbox/task tracker
后端数据库ESP-IDF安全启动终极指南:构建固若金汤的物联网设备保护系统
ESP IDF安全启动终极指南:构建固若金汤的物联网设备保护系统 ESP IDF安全启动流程是保护ESP32系列物联网设备免受恶意固件攻击的关键技术。作为Esp
物联网嵌入式Elm单页应用程序示例教程:构建现代化前端应用的完整指南
Elm单页应用程序示例教程:构建现代化前端应用的完整指南 前言:为什么选择Elm构建SPA? 还在为前端应用的复杂性而头疼吗?状态管理、路由跳转、异步请求、错误
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考