Skip to content

BGR 转换按 16 像素一块:SSSE3 / NEON,加标量回退 - #4

Merged
qiyinxi merged 1 commit into
ModAVD:mainfrom
qiyinxi:perf/bgr-convert
Nov 7, 2026
Merged

qiyinxi merged 1 commit into
ModAVD:mainfrom
qiyinxi:perf/bgr-convert

Conversation

@qiyinxi

@qiyinxi qiyinxi commented Nov 7, 2026

Copy link
Copy Markdown
Member

做的是 #1「随时可以做的」里的第一条:convert_row 的 BGR 分支提速。

Refs #1

改了什么

共享内存里是 BGRA 正向,BGR 只要丢掉 alpha。原来逐字节拷,比库自己的 RGBA 慢三倍多,也比旧伪装 DLL 的 RGBA 慢。现在按块处理:

  • x86 / x64:SSSE3 pshufb 把每 16 字节(4 像素)压成 12 字节,再用移位和或拼成 3 次 16 字节写出,一次 16 像素。GCC/Clang 用 __attribute__((target("ssse3"))),不改编译参数;MSVC 直接用内建函数。modavd_open 时用 CPUID 查一次,没有 SSSE3 就只走标量。
  • ARM64:NEON vld4q_u8 / vst3q_u8,一次 16 像素。
  • 标量回退(其他 CPU,以及向量块之后的尾巴):4 像素读 4 个 32 位字、写 3 个字,剩下 0–3 个逐像素。
  • 每行正好写 width * 3 字节,不越界;源和目标都不要求对齐。
  • 内部接口:convert_row 改名 mx_convert_row 并在 mx_internal.h 里声明(给单测直接调),加 mx_cpu_simd();struct modavd_instance 加一个 simd 字段,mx_copy_frame 多一个参数。对外接口 include/modavd_extras.h 没动,外壳的导出没动。RGBA、BGRA 分支没动。
  • 新增编译期检查:大端 CPU 直接报错(RGBA 分支原来就假定小端)。

参照的只有 Intel / ARM 公开的内建函数文档,没有参照别的项目的代码。

测试

改动和测试由 AI 编码助手完成,下面的都由它自己跑过。

  • Windows,MSVC(VS 2022 Build Tools):build.ps1,/W4 /WX 0 警告,test_core、test_shim 都过。
  • Windows,MinGW-w64 GCC 16.1:-std=c11 -Wall -Wextra -Werror,-O0、-O2、-O3、-O3 -mssse3 各编一次跑 test_core,都过。(MinGW 不是支持的目标,只为在本机多看一个编译器;core.c 原有的一行 %zu 在 MinGW 的 printf 检查下报错,和本 PR 无关,本地加了 -Wno-format。)
  • Linux GCC、macOS(arm64,走 NEON)看本 PR 的 CI。
  • 新增单测(tests/test_core.c):
    • test_convert_row:三种格式,宽度 0–67 加 255/256/257/719/720/1279/1280/1281/1919/1920/1921,源和目标各错开 0–3 字节,源是随机字节(alpha 不是 0xFF),目标两侧各 64 字节哨兵,和逐像素参考实现比;标量和向量(本机有的话)各跑一遍。
    • test_capture_odd_sizes:经 modavd_capture,假共享内存 1x1、1x5、3x4、15x3、17x5、33x2、1281x3,BGR / RGBA / BGRA,正向和倒向。
    • 接口没有调用方给的行距(stride 总是 width * 每像素字节数),所以「带填充的行」是在逐行测试里用错开和哨兵字节覆盖的。
    • 原有 expected_channel 的 BGR 分支改成 4 个元素的数组:GCC -O3 推不出 channel < 3,报 -Warray-bounds。
  • 故意改坏,确认测试会红(用 GCC 编改坏的副本):向量拼接的移位 8 改 7、标量第三个字的移位 8 改 0、标量尾巴少拷一个像素,都报像素不对;向量循环条件改成会越界写,测试崩在堆损坏上。

上机测量

条件:

  • 2026-11-07,Windows 11,7950X3D(32 线程)、64 GB。ModAVD 0.38.2 测试根里新建的测试实例,不登账号,画面停在桌面,无头开机。720p、1080p 各开机一次。
  • 测量时宿主 CPU 总占用 2–10%,没有别的模拟器在跑。
  • 交接:截图增强 DLL(ModAVDExtras)接下来的工作 #1「资料」里的 bench_capture.py:三种实现各在自己的子进程里载入,轮流跑 3 轮,每轮换先后顺序,每种每轮截 500 次。本分支编的库和 main(9e18ca9)编的库各跑一遍,旧伪装 DLL 用的是 交接:截图增强 DLL(ModAVDExtras)接下来的工作 #1 里那份(SHA-256 25f896447fd8eecab7ae287e74632d6d9e161daa61a7d9ebcc1a94aeaa6ae476)。

截图耗时(ms,3 轮的范围):

旧伪装 DLL,RGBA 新库,RGBA 正向 新库,BGR(本 PR) 新库,BGR(main)
720p p50 0.281–0.295 0.109–0.124 0.057–0.072 0.391–0.397
720p p95 0.296–0.364 0.132–0.171 0.061–0.091 0.417–0.460
1080p p50 0.675–0.698 0.263–0.300 0.130–0.134 0.881–0.898
1080p p95 0.788–0.861 0.316–0.408 0.152–0.202 0.925–1.069
  • 旧伪装 DLL 和新库 RGBA 取自本分支那一遍;main 那一遍里这两项在同一范围内(720p 新库 RGBA 第一轮有一次 0.282 的离群)。
  • BGR 比原来快 6–7 倍,比旧伪装 DLL 的 RGBA 快 4–5 倍,也比库自己的 RGBA 快一倍左右(写出的字节少四分之一,而且一次处理 16 像素)。
  • 新外壳(RGBA 倒向,没改):720p p50 0.114–0.122,1080p 0.284–0.306,和 main 一样。

对不对:

  • 测量脚本自己的比对:旧伪装 DLL 对新外壳、库对新外壳(翻回来)在 720p、1080p 都是 0 字节不同。
  • 另在 1080p 静止画面上,用本分支的库截 RGBA、BGR 正向、BGR 倒向(同一帧号),BGR 和 RGBA 换通道去 alpha 后逐字节相同,倒向的翻回来也相同。720p 没另做这一项(逐行单测覆盖了 1280 宽)。

没做的

  • Windows ARM64 没编过;NEON 只靠 CI 的 macOS arm64。
  • RGBA 分支没改成 SIMD(现在已经比旧外壳快 2.4 倍,不在这次范围里)。

共享内存是 BGRA 正向,BGR 只要丢掉 alpha。原来逐字节拷,比库自己的 RGBA 慢三倍多。

- x86:SSSE3 pshufb 把每 16 字节压成 12 字节,再拼成 3 次 16 字节写出。GCC/Clang 用
  target("ssse3") 属性,不改编译参数;开库时用 CPUID 查一次,没有 SSSE3 就走标量。
- ARM64:NEON vld4q_u8 / vst3q_u8。
- 标量:4 像素读 4 个 32 位字、写 3 个字,剩下 0–3 个逐像素。
- 每行正好写 width * 3 字节,不越界。

单测补了逐行转换对逐像素参考实现的比对(0–67 和常见屏宽、源和目标各错开 0–3 字节、
两侧哨兵字节、alpha 不是 0xFF,标量和向量各跑一遍),以及经 modavd_capture 的奇数尺寸
(1x1 到 1281x3)正向、倒向。

Refs ModAVD#1
@qiyinxi
qiyinxi added this pull request to the merge queue Nov 7, 2026
Merged via the queue into ModAVD:main with commit 566f823 Nov 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant