ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Skia 正确性测试工具 DM 完整指南:从构建、运行到回归比对

Skia 正确性测试工具 DM 完整指南:从构建、运行到回归比对 图形学【免费下载链接】skiaSkia 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 的官方文档 Correctness Testing 指出Skia 的正确性测试主要由一个名为DMDelta Master的工具承担。本文基于该文档并结合仓库源码完整讲解如何构建并运行 DM、如何解读它那铺天盖地的日志输出、如何理解 srcs/sinks/config 等核心概念以及如何用-r对比模式和skdiff做回归校验。读完本文你将能够独立搭建起一套 Skia 正确性测试流程并从源码层面理解 DM 的任务调度、哈希比对与结果落盘机制。DM 是什么Skia 正确性测试的发动机DM 不是普通的单元测试框架它是一个“源 × 汇”矩阵式的绘图验证工具。从仓库结构看DM 的全部实现集中在 dm/ 目录下dm/DM.cpp主程序负责命令行解析、任务收集、调度与结果汇总dm/DMSrcSink.h 与 dm/DMSrcSink.cpp定义了Src绘图源与Sink绘制目标/后端两类抽象以及大量具体实现dm/DMJsonWriter.cpp负责把每次运行的结果含 MD5 摘要写入dm.jsondm/DMGpuTestProcs.cppGPU 测试相关的辅助逻辑。在 BUILD.gn 中test_app(dm)目标由上述源文件组成因此构建产物是一个独立的可执行程序dm。DM 的工作模型可以概括为把一批“源”分别画进一批“汇”里逐像素记录结果摘要再与历史基线比对。这正是正确性测试的落脚点——不是简单地断言返回值而是验证每一种后端、每一种配置下绘制出的图像是否与预期一致。快速开始四步跑通 DM在仓库根目录依次执行以下命令即可完成构建并启动一次测试命令来自官方文档python3 tools/git-sync-deps bin/gn gen out/Debug ninja -C out/Debug dm out/Debug/dm -v -w dm_output各步骤的作用python3 tools/git-sync-deps同步第三方依赖。该脚本tools/git-sync-deps会解析仓库根目录的 DEPS 文件并逐一git checkout其中的依赖仓库为后续构建准备好完整的源码树。bin/gn gen out/Debug用 GN 生成构建文件到out/Debug目录。ninja -C out/Debug dm编译 DM 可执行程序。out/Debug/dm -v -w dm_output以 verbose 模式运行 DM并将输出产物PNG 图像与dm.json写入dm_output目录。注意-w--writePath是必填的输出参数而-v--verbose只是让 DM 以每任务一行的方式打印详细日志。此外首次运行时如果资源缺失DM 会提示Some resources are missing. Do you need to set --resourcePath?此时需要设置资源路径。运行行为为什么 CPU 先打满再回落当你运行 DM 时会观察到 CPU 先飙到 100% 一段时间然后逐渐回落到只有 12 个核在活跃。官方文档明确说明这是有意设计的DM 高度多线程化但部分工作尤其是 GPU 后端的工作仍被迫在单线程上执行。从源码看DM 的并行调度基于 Skia 自带的SkTaskGroup见 dm/DM.cpp并行任务通过parallel.add(...)提交到线程池每个可用的硬件线程同时跑一个任务标记为serial的源或汇以及全部 CPU 串行测试gCPUSerialTests在主线程上串行执行以避免与并行测试产生竞态线程池大小由--threads短选项-j控制其默认值是每个核一个额外线程见 dm/DM.cpp。如果机器的 CPU 相对内存比较富裕或者说内存相对紧张可以通过--threads N限制并发线程数out/Debug/dm -w dm_output --threads 4解读输出Skipping 行、任务总数与状态行启动期的 Skipping 行不必惊慌运行开始时你会看到大量类似下面的行Skipping nonrendering: Dont understand nonrendering. Skipping angle: Dont understand angle. Skipping nvprmsaa4: Could not create a surface.官方文档提示这些行是仅供参考FYI性质的提示。DM 支持非常多的测试配置但并非所有配置都适用于每台机器。出现Skipping xxx: Dont understand xxx.通常意味着该配置在当前构建中不可用如未编译对应后端而Could not create a surface.则意味着运行环境如 GPU 能力不满足该配置的要求。只有当某个你预期应该跑起来的配置被跳过时才需要认真排查。从源码看这些行来自gather_sinks()中对每个配置创建Sink失败时的打印dm/DM.cpp。另外如果出现skps: Couldnt read skps.也不必担心——默认构建并不自带.skp文件。如果需要测试 SKP 录制文件可以用 bin/fetch-skps 单独下载。任务总数srcs × sinks tests接下来 DM 会打印一行总览492 srcs * 3 sinks 382 tests 1858 tasks这行字对应源码 dm/DM.cpp 中的计算gPending gSrcs-size() * gSinks-size() testCount; info(%d srcs * %d sinks %d tests %d tasks\n, ...);其中srcs源可以被绘制的输入来自三类GM 集成测试代码位于 gm/ 目录每个 GM 用DEF_GM宏注册见 gm/gm.h图像文件来自--images默认目录是resources仓库中的 resources/images 就存放着大量测试用图例如文档示例中出现的 mandrill 系列.skp 文件来自--skps默认目录是skps。源类型由--src控制默认值是tests gm skp mskp lottie rive svg image colorImage见 dm/DM.cpp。sinks汇绘制目标即用什么后端、画到什么表面的配置组合由--config控制详见下文。tests单元测试通过DEF_TEST、DEF_SERIAL_TEST、DEF_GRAPHITE_TEST等宏注册见 tests/Test.h代码位于 tests/ 目录。单元测试不遵循 src-sink 模型因此单独计数。DM 总是把所有源画进所有汇除非被veto或--skip排除这就是492 × 3的来历。几千个任务是非常正常的数量。状态行一条日志的四个字段( 25MB 1857) 1.36ms 8888 image mandrill_132x132_12x12.astc-5-subsets [1] [2] [3] [4]逐字段解读官方文档原意 源码印证25MBDM 进程迄今为止用到的峰值内存高水位标记而非当前内存。它主要服务于 CI 构建机器人——有些机器离系统内存上限非常近需要跟踪峰值。源码中通过sk_tools::getMaxResidentSetSizeMB()获取dm/DM.cpp并会一并写入dm.json的max_rss_MB字段。1857尚未完成的任务数正在运行 等待运行。通常每个硬件线程同时跑一个任务所以典型笔记本上大约同时有 4 或 8 个任务在跑。文档特别说明启动初期计数看起来乱序是无害的不影响运行正确性。1.36ms该任务的实际耗时计时精度约 1 微秒。这个数字纯粹供参考主要用于发现慢测试。8888 image mandrill_132x132_12x12.astc-5-subsetsconfig srcType srcOptions name。这里就是把名为mandrill_132x132_12x12.astc-5-subsets的 image 源画进了8888汇。如果觉得日志太吵去掉-v后 DM 会把进度压缩到单行刷新配合--quiet/-q甚至可以完全静默。深入理解 config8888 与 gl 之外的世界文档指出Linux 上默认的--config是8888 gl nonrendering其中8888用软件后端software rasterizer绘制到 32 位 RGBA 位图gl用 OpenGL 后端GaneshSkia 的 GPU 渲染引擎绘制到 32 位 RGBA 位图nonrendering非渲染配置常因当前环境不支持而被跳过。从源码看默认配置定义在 tools/flags/CommonFlagsConfig.cppstatic const char defaultConfigs[] 8888 DEFAULT_GPU_CONFIG nonrendering #if SK_ANGLE defined(SK_BUILD_FOR_WIN) angle_d3d11_es2 #endif ;其中DEFAULT_GPU_CONFIG在 Android/iOS 上为gles其他平台为gl见同文件第 23-28 行——这印证了文档所说默认配置与操作系统相关。8888和gl只是冰山一角。同一个文件中的gPredefinedConfigs表tools/flags/CommonFlagsConfig.cpp预定义了上百种配置例如配置名底层后端说明gl/glesGPUOpenGL / OpenGL ESglf16/glsrgba/gl1010102GPU不同颜色格式半浮点 / sRGB / 10-10-10-2glmsaa4/glmsaa8GPU4x / 8x 多重采样抗锯齿glbetex/glbertGPU后端纹理 / 渲染目标表面变体gldmsaaGPU动态 MSAAangle_d3d11_es2等GPU通过 ANGLE 在 D3D11/D3D9/Metal 上跑 ES2/ES3glddlGPU使用 DDLDeferred Display ListSinkgltestpersistentcacheGPU测试持久化着色器缓存这些预定义配置用--config指定即可例如out/Debug/dm -w dm_output --config 8888 glmsaa4 glf16文档特别提醒DM 偶尔会把这些概念叫做 configs 或 sinks二者指的是同一回事日常关注最多的就是8888和gl。输出产物dm.json 与按目录组织的 PNG运行结束后dm_output目录里会有一个dm.json文件以及按config/srcType嵌套的图像目录$ ls dm_output 8888 dm.json gl $ find dm_output -name *.png dm_output/8888/gm/3x3bitmaprect.png dm_output/8888/gm/aaclip.png dm_output/8888/gm/aarectmodes.png ...目录结构为先按汇类型--config再按源类型--src最后以源名称命名图像文件。例如状态行中的任务8888 image mandrill_132x132_12x12.astc-5-subsets其输出位于dm_output/8888/image/mandrill_132x132_12x12.astc-5-subsets.png这一落盘逻辑可以在 dm/DM.cpp 的WriteToDisk()中看到它逐级创建writePath/config/srcType/[srcOptions/]name.ext。若启用--nameByHash则会改为按 MD5 内容寻址存储writePath/md5.ext且文件已存在时直接跳过内容寻址去重。dm.json是自动化测试系统的接口文件它记录了每次运行每个任务的结果摘要。从 dm/DMJsonWriter.cpp 可以看到其结构顶层properties、key用于标识 builder 的键值对来自--key/--properties、max_rss_MB、results数组每个 result 包含keyname测试名、config汇、source_type源类型、source_options源选项如存在optionsext文件扩展名、gamut色域如 sRGB/P3/Adobe、transfer_fn传递函数、color_type、alpha_type、color_depthmd5图像摘要。这些options字段在 dm/DM.cpp 中通过识别位图的色彩空间identify_gamut/identify_transfer_fn等生成方便在 GoldSkia 的自动图像比对服务中按色彩特征归类。Digest 细节哈希的不是 PNG 文件而是原始像素文档特意强调了一个枯燥但重要的技术细节dm.json中的 checksumMD5并不是对.png文件本身做哈希而是对生成该 PNG 的原始像素做哈希。这意味着两种配置可能生成字节完全相同的.png但它们的 checksum 却不同。源码印证在 dm/DM.cpp 中DM 用SkMD5对两种数据源之一做摘要如果data流有内容如 PDF、MSKP 等非位图产物直接对数据流做哈希否则通过HashAndEncode见 tools/HashAndEncode.h把SkBitmap的原始像素喂给哈希器再交给hashAndEncode-encodePNG(...)写 PNG。由于先算像素摘要、再编码 PNGchecksum 反映的是画出来的结果而非磁盘上的文件这正是回归比对能跨配置、跨机器生效的前提。单元测试通过时静默失败时全量汇报单元测试通过时通常只输出一条状态更新一旦失败DM 会在失败发生时立即打印断言失败信息并在全部任务结束后把所有失败再汇总打印一遍Failures:段落同时这些失败也会写入dm.json。源码逻辑DMReporter::reportFailed()调用全局的fail()dm/DM.cpp把错误压入gFailures数组并立即SkDebugf输出主流程结束时dm/DM.cpp再遍历gFailures汇总输出且只要存在任何失败DM 就以退出码 1 结束。测试本身通过DEF_TEST(name, reporter)等宏注册例如DEF_TEST(ExampleTest, reporter) { REPORTER_ASSERT(reporter, 1 1 2); }REPORTER_ASSERT宏tests/Test.h在断言失败时记录失败信息与TestRegistrytests/Test.h配合实现静态注册DM 启动时通过skiatest::TestRegistry::Range()收集全部测试dm/DM.cpp。回归比对-r 对比模式与 skdiff方式一-r直接对比上一次运行DM 内置了简单的历史结果比对能力ninja -C out/Debug dm out/Debug/dm -w good # 第一次运行产出基线 good/ # ... 修改或重构代码 ... ninja -C out/Debug dm out/Debug/dm -r good -w bad # 第二次运行与 good 对比后输出 bad/使用-r--readPath时只要某个测试没有产生与good运行完全相同的图像DM 就会报告失败。其实现机制gather_gold()dm/DM.cpp读取good/dm.json中所有(config, sourceType, sourceOptions, name, md5)五元组并存入gGold哈希集合运行中每个任务算出的 MD5 若不在gGold中则通过fail()报错dm/DM.cpp。这解释了为什么 checksum 必须基于原始像素——只有跨运行的稳定摘要才能作为可靠的比对键。方式二skdiff 做更精细的差异分析需要更复杂、可交互的差异分析时用skdiffninja -C out/Debug dm out/Debug/dm -w good # do some work ninja -C out/Debug dm out/Debug/dm -w bad ninja -C out/Debug skdiff mkdir diff out/Debug/skdiff good bad diff # open diff/index.html in your web browserskdiff是仓库中的一个独立工具源码位于 tools/skdiffGN 目标见 BUILD.gn。它在 skdiff_main.cpp 中的功能说明很清晰接收三个目录前两个分别视为基线图集与变体图集期望其中存在同名文件对每一对文件生成一张差异图diff image写入第三个目录在第三个目录生成index.html方便在浏览器中逐对对比不一致的图像默认递归遍历子目录可用--norecurse关闭若所有图像完全一致则返回退出码 0。相比-r的全有或全无skdiff能直观展示像素差异的位置与程度适合人工审视渲染回退。常用 Flags 速查DM 支持大量命令行开关最常用的整理如下源码定义见 dm/DM.cppout/Debug/dm --help # 打印所有 flag、默认值与简要说明 out/Debug/dm --src tests # 只运行单元测试 out/Debug/dm --nocpu # 只测试 GPU 后端工作 out/Debug/dm --nogpu # 只测试 CPU 后端工作 out/Debug/dm --match blur # 只运行名称包含 blur 的工作 out/Debug/dm --dryRun # 不真正执行仅打印将要运行的任务几个值得展开的开关--match短选项-m支持[~][^]substring[$]语法可空格分隔多个模式~表示反向匹配跳过^/$限定开头/结尾两者同时使用即精确匹配dm/DM.cpp。--skip以config/src/srcOptions/name四元组跳过特定任务_匹配任意值、~取反。例如--skip gpu skp _ _跳过所有画进 gpu 配置的 SKPdm/DM.cpp。--threads N短选项-j限制线程池大小详见上文运行行为一节。--shards N/--shard i把任务集分片适合多机并行测试dm/DM.cpp。--list收集并打印所有源与汇后退出方便确认配置是否生效[dm/DM.cpp](https://link.gitcode.com/i/087e038de69e3020bed20c7669692cf2#L169-L171, L1727-L1731)。--config覆盖默认汇配置--src覆盖默认源类型集合。--key/--properties向dm.json追加键值对用于标识 builder 或运行环境。--nameByHash按 MD5 内容寻址输出文件。小结DM 是 Skia 正确性测试的核心设施它以所有源 × 所有汇 单元测试的矩阵模型把每一种后端、每一种颜色配置下的绘制结果都沉淀为可复现的像素摘要dm.json PNG 目录并提供-r与skdiff两级回归比对手段。理解它的运行日志、配置体系与哈希细节是深入 Skia 开发、排查渲染回退的第一步。更多一手细节可以继续阅读 site/docs/dev/testing/testing.md 与 dm/DM.cpp 中的 flag 注释。赞分享图形学【免费下载链接】skiaSkia is a complete 2D graphic library for drawing Text, Geometries, and Images. See documentation for contribution instructions.项目地址https://gitcode.com/gh_mirrors/ski/skia点击查看免费下载相关推荐miniblink49 内置 Skia 的图形正确性测试DM 工具从构建到黄金图对比实战指南miniblink49 内置 Skia 的图形正确性测试DM 工具从构建到黄金图对比实战指南 miniblink49 作为基于 Blink 的轻量级浏览器内核前端桌面应用Skia GN 构建工作流完全指南从 dm 正确性测试到 nanobench 性能基准与 viewer 可视化Skia GN 构建工作流完全指南从 dm 正确性测试到 nanobench 性能基准与 viewer 可视化 Skia 是基于 GN 构建系统与 Ninja图形学Skia dm 工具使用指南GMs 图形测试与单元测试的正确性验证实战Skia dm 工具使用指南GMs 图形测试与单元测试的正确性验证实战 dm 是 Skia 主要的 正确性correctness测试工具 负责运行 GM图形学上一篇3大创新突破解决Android位置模拟难题FakeLocation技术原理解析与场景落地指南下一篇Pydantic AI 实时语音 Agent 的前端音频接入拓扑怎么选创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表