使用 Bazel 模块化集成 Skia:外部客户端构建指南
2026/9/23 14:00:26 网站建设 项目流程
  • 图形学

【免费下载链接】skia

Skia is a complete 2D graphic library for drawing Text, Geometries, and Images. See documentation for contribution instructions.

项目地址:https://gitcode.com/gh_mirrors/ski/skia
点击查看免费下载

导读

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_configcc_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_combinerpath_main.cppcorepathops路径布尔运算
png_decoderdecode_png_main.cppcorepng_decode_codec解码 PNG
decode_everythingdecode_everything.cppcore+ bmp/gif/ico/jpeg/jpegxl/png/wbmp/webp 全部解码 codec全格式解码
write_text_to_pngwrite_text_to_png.cppcorepng_encode_codec+ 平台字体模块绘制文本并编码 PNG
shape_textshape_text.cppcoreskparagraph_harfbuzz_skunicodeskunicode_icu文本整形排版
use_ganesh_glganesh_gl.cppcoreganesh_gl+ 平台 GL 工厂Ganesh + OpenGL
use_ganesh_vulkanganesh_vulkan.cppcoreganesh_vulkanGanesh + Vulkan
use_ganesh_metalganesh_metal.cppcoreganesh_metalGanesh + Metal(仅 Apple)
use_graphite_native_metalgraphite_native_metal.cppcoregraphite_native_metalGraphite + Metal
use_graphite_metal_capturegraphite_metal_capture.cppcoregraphite_native_metalGraphite + Metal 捕获
use_graphite_native_vulkangraphite_native_vulkan.cppcoregraphite_native_vulkan@vulkan_headersGraphite + Vulkan
use_log_testerlog_tester.cppcore日志通道验证(仅 Apple)
use_skresourcesuse_skresources.cppcoreskresources+ 编解码资源加载
svg_with_primitivesvg_renderer.cppcoresvg_rendererskshaper_core基础 SVG 渲染
svg_with_harfbuzz同上coresvg_rendererskshaper_harfbuzzskunicode_icuHarfBuzz 整形版 SVG
write_to_pdfwrite_to_pdf.cppcorepdf_writerpdf_jpeg_helpersPDF 输出
play_skottieplay_skottie.cppcoreskottiepng_encode_codecLottie 动画播放

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_factoryganesh_glx_factoryganesh_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_codecpng_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 光栅表面 → 按平台构造SkFontMgrSkFontMgr_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::VulkanBackendContextGrDirectContexts::MakeVulkan创建 Ganesh 上下文 →SkSurfaces::RenderTarget创建 GPU 表面。文件头注释特别说明“缺少完整 Vulkan 初始化代码,运行时不会成功,但用于验证构建系统可编译链接”,即这类目标是构建冒烟测试而非完整应用。

6.4 Lottie 动画(skottie 模块)

play_skottie.cpp 展示高层模块的易用性:skottie::Animation::Make(&input)解析 JSON → 按动画尺寸创建表面 → 逐帧animation->seek渲染并编码为 PNG,整个流程只依赖skottiepng_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.

项目地址:https://gitcode.com/gh_mirrors/ski/skia
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询