- 图形学
【免费下载链接】skia
Skia is a complete 2D graphic library for drawing Text, Geometries, and Images. See documentation for contribution instructions.
导读
Skia 不仅提供了功能完备的 2D 图形 API,还通过 Bazel 提供了一整套模块化构建规则,允许外部项目在自有 C++ 工具链下按需组装自己需要的组件——从最轻量的核心路径运算、图像编解码,到 Ganesh/Graphite 两种 GPU 后端,乃至 Skottie 动画、SVG 渲染与 PDF 输出。本文以仓库中 example/external_client 这一官方示例为骨架,逐步拆解外部客户端如何声明依赖、注入自定义用户配置、组装模块并最终构建出可运行的可执行文件,读完后你将能在自己的 Bazel 工程中像“搭积木”一样集成 Skia。
一、示例定位:外部客户端如何“依赖并构建”Skia
example/external_client/README.md 用三句话点明了整个示例的核心意图:
该目录演示了外部客户端如何使用自己的 C++ 工具链依赖并构建 Skia。先看
WORKSPACE.bazel了解 setup 部分(顺带快速浏览./custom_skia_config),再看BUILD.bazel了解真正使用 Skia 模块化构建规则组装特定任务所需组件的规则。
也就是说,这个示例不是一个独立存在的可执行程序,而是一份最小可复用的工程模板,它模拟了一个“把 Skia 当作外部依赖的第三方项目”的完整布局。值得注意的是,示例目前采用 Bazel 的 bzlmod 模式,依赖声明实际位于 example/external_client/MODULE.bazel,而custom_skia_config目录内保留了一个空的 example/external_client/custom_skia_config/WORKSPACE.bazel 以兼容传统工作区语义。整个示例目录结构如下:
example/external_client/ ├── MODULE.bazel # bzlmod 依赖声明(含 skia 与 skia_user_config 的本地覆盖) ├── MODULE.bazel.lock ├── BUILD.bazel # 各示例可执行文件的组装规则 ├── custom_skia_config/ # 客户端自定义 Skia 配置模块 │ ├── BUILD.bazel # user_config cc_library(SK_USE_BAZEL_CONFIG_HEADER) │ ├── SkUserConfig.h # 可自定义的构建期宏 │ ├── copts.bzl # DEFAULT_COPTS / DEFAULT_OBJC_COPTS │ ├── linkopts.bzl # DEFAULT_LINKOPTS │ └── WORKSPACE.bazel └── src/ # 各示例的 C++ 源码二、依赖声明:bzlmod 下的 Skia 接入
外部客户端与 Skia 的“连接点”全部集中在 example/external_client/MODULE.bazel,其声明逻辑可分为四层:
2.1 基础构建规则依赖
bazel_dep(name = "rules_cc", version = "0.1.1") bazel_dep(name = "platforms", version = "0.0.11")rules_cc提供cc_binary/cc_library等 C++ 规则,platforms提供@platforms//os:linux、@platforms//os:macos等平台约束,供后续select做平台差异化处理。
2.2 引入 Skia 本体(本地覆盖模拟真实拉取)
bazel_dep(name = "skia") local_path_override( module_name = "skia", path = "../..", )注释中明确说明:真实客户端应通过git_repository把 Skia 固定到某个 commit(文件中给出了99822dd8249dfe3773cbd7ca56bb1767bf92fa22的示例),而这里用local_path_override指向仓库根目录(../..,即 MODULE.bazel),从而能够在仓库内直接以“外部客户端”的身份测试最新代码。
2.3 注入客户端自己的配置模块
bazel_dep(name = "skia_user_config") local_path_override( module_name = "skia_user_config", path = "custom_skia_config", )这是 Skia Bazel 集成的关键机制:客户端必须提供一个名为skia_user_config的模块,其中包含一个名为user_config的cc_library目标。Skia 的各模块会依赖这个目标,从而把客户端定义的宏与编译选项“反向注入”到 Skia 自身的构建中。
2.4 第三方依赖传递(cpp_modules 扩展)
skia_deps = use_extension("@skia//bazel:cpp_modules.bzl", "cpp_modules") skia_deps.from_file(deps_json = "@skia//bazel:deps.json") use_repo( skia_deps, "dawn", "delaunator", "dng_sdk", "expat", "freetype", "harfbuzz", "icu", "icu4x", "imgui", "libavif", "libgav1", "libjpeg_turbo", "libjxl", "libpng", "libwebp", "libyuv", "perfetto", "piex", "spirv_cross", "spirv_headers", "spirv_tools", "vello", "vulkan_headers", "vulkan_tools", "vulkan_utility_libraries", "vulkanmemoryallocator", "wuffs", "zlib", )模块注释解释得直白:bzlmod 不允许传递依赖,因此一旦你的代码(或间接通过 Skia 头文件)直接包含这些第三方头文件,就必须自行声明它们。示例通过use_extension调用 Skia 的cpp_modules模块扩展(见 bazel/cpp_modules.bzl),从 bazel/deps.json 一次性拉取 freetype、harfbuzz、icu、libpng、libjpeg_turbo、vulkan_headers、wuffs 等全部依赖仓库,再用use_repo把需要的仓库句柄绑定到当前模块作用域。
三、注入配置:custom_skia_config 模块剖析
README 提醒的“quick detour”./custom_skia_config,是外部客户端定制 Skia 构建的唯一入口,其组成如下。
3.1 user_config 库:构建期宏的注入点
example/external_client/custom_skia_config/BUILD.bazel 定义了 Skia 要求的user_config目标:
config_setting( name = "debug_build", values = {"compilation_mode": "dbg"}, ) cc_library( name = "user_config", hdrs = ["SkUserConfig.h"], defines = [ "SK_USE_BAZEL_CONFIG_HEADER", ] + select({ ":debug_build": ["SK_DEBUG"], "//conditions:default": ["SK_RELEASE"], }), visibility = ["//visibility:public"], )这里有两个关键点:
SK_USE_BAZEL_CONFIG_HEADER宏是硬性要求。它告诉 Skia 的构建系统使用客户端提供的SkUserConfig.h作为配置头,而非 Skia 内置默认配置。从源码结构看,Skia 各源码文件在包含配置头时会依据此宏切换路径(详见 include/config/SkUserConfig.h 中罗列的可用宏清单)。SK_DEBUG/SK_RELEASE通过select+compilation_mode联动:使用bazel build -c dbg时自动定义SK_DEBUG,默认 fastbuild/opt 模式则定义SK_RELEASE,无需手工维护。
3.2 可选的编译/链接选项
- example/external_client/custom_skia_config/copts.bzl 提供
DEFAULT_COPTS = ["-std=c++20", "-Wno-psabi"]与DEFAULT_OBJC_COPTS = [],注释明确这些列表可以为空,也可以使用select按平台差异化设置; - example/external_client/custom_skia_config/linkopts.bzl 提供
DEFAULT_LINKOPTS = [],语义同上。
3.3 自定义配置头
example/external_client/custom_skia_config/SkUserConfig.h 目前是一个空模板,仅用 include guard 包裹,并注释“参见 include/config/SkUserConfig.h 了解可以在此处定义的宏”。外部客户端可以在此开启/关闭特定特性(如SK_ENABLE_SKSL、各类字体后端开关等),实现按需裁剪。
四、模块化组装:BUILD.bazel 中的 18 个示例目标
example/external_client/BUILD.bazel 是示例的主体,通过组合@skia//:下不同模块目标,演示了“不同任务只链接所需组件”的模块化哲学。全部目标归纳如下:
| 目标 | 源文件 | 依赖的 Skia 模块 | 用途 |
|---|---|---|---|
path_combiner | path_main.cpp | core、pathops | 路径布尔运算 |
png_decoder | decode_png_main.cpp | core、png_decode_codec | 解码 PNG |
decode_everything | decode_everything.cpp | core+ bmp/gif/ico/jpeg/jpegxl/png/wbmp/webp 全部解码 codec | 全格式解码 |
write_text_to_png | write_text_to_png.cpp | core、png_encode_codec+ 平台字体模块 | 绘制文本并编码 PNG |
shape_text | shape_text.cpp | core、skparagraph_harfbuzz_skunicode、skunicode_icu等 | 文本整形排版 |
use_ganesh_gl | ganesh_gl.cpp | core、ganesh_gl+ 平台 GL 工厂 | Ganesh + OpenGL |
use_ganesh_vulkan | ganesh_vulkan.cpp | core、ganesh_vulkan | Ganesh + Vulkan |
use_ganesh_metal | ganesh_metal.cpp | core、ganesh_metal | Ganesh + Metal(仅 Apple) |
use_graphite_native_metal | graphite_native_metal.cpp | core、graphite_native_metal | Graphite + Metal |
use_graphite_metal_capture | graphite_metal_capture.cpp | core、graphite_native_metal | Graphite + Metal 捕获 |
use_graphite_native_vulkan | graphite_native_vulkan.cpp | core、graphite_native_vulkan、@vulkan_headers | Graphite + Vulkan |
use_log_tester | log_tester.cpp | core | 日志通道验证(仅 Apple) |
use_skresources | use_skresources.cpp | core、skresources+ 编解码 | 资源加载 |
svg_with_primitive | svg_renderer.cpp | core、svg_renderer、skshaper_core | 基础 SVG 渲染 |
svg_with_harfbuzz | 同上 | core、svg_renderer、skshaper_harfbuzz、skunicode_icu | HarfBuzz 整形版 SVG |
write_to_pdf | write_to_pdf.cpp | core、pdf_writer、pdf_jpeg_helpers | PDF 输出 |
play_skottie | play_skottie.cpp | core、skottie、png_encode_codec | Lottie 动画播放 |
4.1 最小依赖示例:path_combiner
cc_library( name = "skia_core_and_pathops", deps = [ "@skia//:core", "@skia//:pathops", ], ) cc_binary( name = "path_combiner", srcs = ["src/path_main.cpp"], linkopts = ["-fuse-ld=lld", "-lpthread"], deps = [":skia_core_and_pathops"], )注释明确写道:“第一个示例只需要核心 Skia 功能与 pathops 模块”,因此客户端把core+pathops封装成本地cc_library再被可执行文件依赖——这是推荐的依赖组织方式。
4.2 平台差异化依赖:write_text_to_png
deps = [ "@skia//:core", "@skia//:png_encode_codec", ] + select({ "@platforms//os:linux": [ "@skia//:fontmgr_fontconfig", "@skia//:freetype_support", ], "@platforms//os:macos": ["@skia//:fontmgr_coretext"], "//conditions:default": ["@skia//:fontmgr_empty_freetype"], }),字体管理器因平台而异:Linux 用 fontconfig + FreeType,macOS 用 CoreText,其他平台回退到fontmgr_empty_freetype。对应源码 write_text_to_png.cpp 中也是用条件编译SK_FONTMGR_FONTCONFIG_AVAILABLE/SK_FONTMGR_CORETEXT_AVAILABLE来匹配这些模块提供的宏,构建期宏与源码宏一一对应。
4.3 GPU 后端与 Objective-C 辅助库
use_ganesh_gl展示了完整的多平台链路:Windows 链接gdi32/OpenGL32/user32,Linux 链接GL/X11,并分别选择ganesh_gl_win_factory、ganesh_glx_factory、ganesh_gl_mac_factory作为平台工厂;macOS 还需要一个objc_library辅助目标 gl_context_helper(引入OpenGLframework,并通过-DGL_SILENCE_DEPRECATION压制废弃告警)。Metal 类目标同理,ganesh_metal_context_helper明确要求依赖MetalKitframework,注释指出“不加入 MetalKit,[*device newCommandQueue]会失败”。
五、模块别名映射:从@skia//:xxx到真实源码
示例中所有@skia//:前缀目标,在仓库根 BUILD.bazel 中均有对应的alias映射,这是理解“模块化”落点的关键索引:
| 对外别名 | 实际指向 |
|---|---|
@skia//:core | //src/core:core |
@skia//:pathops | //src/pathops:pathops |
@skia//:ganesh_gl | //src/gpu/ganesh/gl:ganesh_gl |
@skia//:ganesh_metal | //src/gpu/ganesh/mtl:ganesh_metal |
@skia//:ganesh_vulkan | //src/gpu/ganesh/vk:ganesh_vulkan |
@skia//:graphite_native_metal | //src/gpu/graphite/mtl:graphite_native_metal |
@skia//:graphite_native_vulkan | //src/gpu/graphite/vk:graphite_native_vulkan |
@skia//:png_decode_codec | :png_decode_libpng→//src/codec:png_decode |
@skia//:png_encode_codec | :png_encode_libpng→//src/encode:png_encode |
@skia//:jpeg_decode_codec/jpeg_encode_codec | //src/codec:jpeg_decode///src/encode:jpeg_encode |
@skia//:bmp/gif/ico/jpegxl/wbmp/webp_decode_codec | //src/codec:*_decode |
@skia//:webp_encode_codec | //src/encode:webp_encode |
@skia//:fontmgr_fontconfig/freetype_support/fontmgr_coretext/fontmgr_empty_freetype | //src/ports:* |
注意一个细节:png_decode_codec、png_encode_codec这两个别名已带有deprecation提示,官方建议改用png_decode_libpng/png_encode_libpng(仓库同时提供png_decode_rust/png_encode_rust等 Rust 实现备选)。示例沿用旧别名保持了向后兼容。
六、示例源码走读:模块与 API 的对应关系
6.1 路径布尔运算(pathops 模块)
path_main.cpp 是pathops模块的最小用例:用SkPathBuilder构造两个重叠三角形路径,再调用Op(path1, path2, kIntersect_SkPathOp, &combined)求交集并dump()输出。这验证了@skia//:pathops仅需引入include/pathops/SkPathOps.h。
6.2 文本渲染 + PNG 编码
write_text_to_png.cpp 串起了core、编码模块与字体模块的完整调用链:SkSurfaces::Raster创建 100×50 光栅表面 → 按平台构造SkFontMgr(SkFontMgr_New_FontConfig/SkFontMgr_New_CoreText)→matchFamilyStyle("Roboto", ...)匹配字体 →canvas->drawString绘制 →SkPngEncoder::Encode写出。它同时是linkopts中-lpthread的用途样本(Skia 内部线程相关符号需要 pthread)。
6.3 GPU 后端上下文(ganesh_vulkan)
ganesh_vulkan.cpp 演示 GPU 路径的最小骨架:构造skgpu::VulkanBackendContext→GrDirectContexts::MakeVulkan创建 Ganesh 上下文 →SkSurfaces::RenderTarget创建 GPU 表面。文件头注释特别说明“缺少完整 Vulkan 初始化代码,运行时不会成功,但用于验证构建系统可编译链接”,即这类目标是构建冒烟测试而非完整应用。
6.4 Lottie 动画(skottie 模块)
play_skottie.cpp 展示高层模块的易用性:skottie::Animation::Make(&input)解析 JSON → 按动画尺寸创建表面 → 逐帧animation->seek渲染并编码为 PNG,整个流程只依赖skottie与png_encode_codec两个模块。
七、构建与运行方式
在example/external_client目录下(或在仓库根使用--package_path定位目标),按 Bazel 惯例即可构建任意示例:
bazel build //:path_combiner # 仅 core + pathops bazel build //:decode_everything # 全部解码器 bazel build //:write_to_png # 文本 → PNG bazel build //:use_ganesh_vulkan # GPU 后端冒烟测试 bazel build -c dbg //:path_combiner # 调试构建(自动注入 SK_DEBUG)运行产物位于bazel-bin/下,例如:
./bazel-bin/path_combiner # 打印路径交集结果 ./bazel-bin/write_text_to_png output.png # 生成 100×50 的 PNG ./bazel-bin/play_skottie anim.json 60 # 渲染 Lottie 前 60 帧几点实践提示:
- 示例普遍在
linkopts中加入-fuse-ld=lld(指定 lld 链接器)与-lpthread,前者可依工具链情况移除; - Metal 相关目标通过
target_compatible_with = select(...)声明仅兼容 macOS/iOS,在 Linux 上构建会直接报“incompatible”,这是 Bazel 平台约束的常规用法; - 若需要固定 Skia 版本,参照 example/external_client/MODULE.bazel 顶部注释的
git_repository方案替换local_path_override即可。
八、总结
example/external_client提供了一条清晰的“外部客户端集成 Skia”路径:用 bzlmod 声明依赖、用skia_user_config注入配置、用@skia//:模块别名按需组装、用select处理平台差异。其核心价值在于验证了 Skia 的 Bazel 构建真正做到了组件级裁剪——一个路径运算程序只需core+pathops两个模块,而完整的 GPU 后端、Skottie、SVG、PDF 能力都可以在需要时才引入。对于任何希望在自有 C++ 工具链中复用 Skia 的 Bazel 工程,这套模板都值得直接复制改造。
继续深入可阅读:BUILD.bazel(完整模块别名清单)、include/config/SkUserConfig.h(可自定义宏)、bazel/cpp_modules.bzl 与 bazel/deps.json(第三方依赖机制)。
- 图形学
【免费下载链接】skia
Skia is a complete 2D graphic library for drawing Text, Geometries, and Images. See documentation for contribution instructions.
相关推荐
Bazel 外部构建系统集成规则 —— rules_foreign_cc
Bazel 外部构建系统集成规则 —— rules_foreign_cc 项目基础介绍 rules_foreign_cc 是一个开源项目,旨在为 Bazel 提
RIOT OS 构建系统扩展指南:用 `EXTERNAL_MODULE_DIRS` 集成外部模块
RIOT OS 构建系统扩展指南:用 EXTERNAL_MODULE_DIRS 集成外部模块 RIOT 的构建系统默认只扫描仓库内 sys/ 、 drivers
物联网嵌入式操作系统实时系统CARLA C++ 客户端实战指南:构建示例、集成外部工程与底层 API 解析
CARLA C++ 客户端实战指南:构建示例、集成外部工程与底层 API 解析 本指南面向希望摆脱 Python 包装、直接用 C++ 驱动 CARLA 自动驾
自动驾驶科研仿真
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考