OpenToonz 内置的 KISS FFT:基于混合基数算法的高效轻量 FFT 库使用指南
【免费下载链接】opentoonzOpenToonz - An open-source full-featured 2D animation creation software项目地址: https://gitcode.com/GitHub_Trending/op/opentoonz
导读
本文围绕 OpenToonz 仓库第三方面组件 KISS FFT(完整源码位于 thirdparty/kiss_fft)展开,系统讲解这一以"Keep It Simple, Stupid"为设计哲学的快速傅里叶变换库:从 1D 复数 FFT 的十分钟快速接入、Make/CMake 双构建系统的全部配置项,到混合基数算法、定点数缩放、SIMD 扩展等底层原理。读完本文,你将掌握如何把 KISS FFT 以 float / double / int16_t / int32_t 任意数据类型嵌入自己的 C 程序,并能理解 OpenToonz 的景深虚化(Bokeh)与眩光(Glare)特效为何以及如何调用该库完成频域卷积。
一、KISS FFT 是什么:为"够用且简单"而生的 FFT 库
KISS FFT(项目首页文档)是一个基于**混合基数(mixed-radix)**原理的快速傅里叶变换实现。它的自我定位非常克制:
There are many great fft libraries already around. Kiss FFT is not trying to be better than any of them. It only attempts to be a reasonably efficient, moderately useful FFT that can use fixed or floating data types and can be incorporated into someone's C program in a few minutes with trivial licensing.
即:市面上优秀的 FFT 库很多,KISS FFT 并不试图超越它们,它只追求三件事——合理的效率、够用的功能、以及数分钟之内即可嵌入任意 C 程序的极简集成体验,并附带宽松的 BSD 许可证。
从仓库目录结构看,核心实现极其精简:
| 文件 | 作用 |
|---|---|
| kiss_fft.h | 公共 API 声明与kiss_fft_scalar类型宏 |
| kiss_fft.c | 1D 复数 FFT 核心实现 |
| kiss_fftnd.c | 多维 FFT |
| kiss_fftr.c | 实数优化 FFT(只输出正半频谱) |
| kiss_fftndr.c | 多维实数 FFT |
| kfc.c | FFT 对象缓存工具 |
| kissfft.hh | C++ 模板封装 |
作者 Mark Borgerding 在 BACKGROUND 一节解释了创作动机:当时找不到不用汇编语言的定点 FFT,于是他先用浮点把理论走通,最终得到一段能通过重编译轻松切换short、float、double三种数据类型的精简代码。这与 kiss_fft.h 中的宏设计一一对应——通过FIXED_POINT和kiss_fft_scalar即可在编译期切换数据类型。
二、快速上手:1D 复数 FFT 的十分钟接入
KISS FFT 的 1D 复数 FFT 用法被刻意设计得极其直接。原文档给出的最小可运行流程如下:
#include "kiss_fft.h" kiss_fft_cfg cfg = kiss_fft_alloc( nfft ,is_inverse_fft ,0,0 ); while ... ... // put kth sample in cx_in[k].r and cx_in[k].i kiss_fft( cfg , cx_in , cx_out ); ... // transformed. DC is in cx_out[0].r and cx_out[0].i kiss_fft_free(cfg);核心调用只有三个函数:
kiss_fft_alloc(nfft, inverse_fft, mem, lenmem)—— 创建并初始化 FFT/IFFT 的配置缓冲区(plan)。nfft是变换长度;inverse_fft传0表示正变换、非0表示逆变换;后两个参数用于内存复用(见下文)。kiss_fft(cfg, fin, fout)—— 执行变换。输入输出均为复数数组kiss_fft_cpx,每个元素通过.r(实部)与.i(虚部)访问。kiss_fft_free(cfg)—— 释放配置缓冲区。
频域数据的布局约定
原文档特别强调频率域数据的排列顺序:从 DC 一路排到 2π。
cx_out[0]是 FFT 的DC(直流)bin;cx_out[nfft/2]是Nyquist(奈奎斯特)bin(当该 bin 存在时)。
即输出按DC, 正频率, ..., Nyquist, 负频率的顺序排列,正变换时fin应为f[0], f[1], ..., f[nfft-1],输出fout对应F[0], F[1], ..., F[nfft-1](见 kiss_fft.h)。
alloc 的内存复用机制(源码级细节)
kiss_fft.h 对kiss_fft_alloc的第四个参数mem/lenmem做了完整说明:
- 若
lenmem为NULL:内部用malloc分配 cfg 缓冲区,使用完毕后应free以避免内存泄漏; - 若
lenmem非NULL且mem非NULL、且*lenmem足够大:cfg 会放置到用户提供的缓冲区mem中,并把实际占用大小写回*lenmem,返回mem; - 若
lenmem非NULL但缓冲区不足:函数返回NULL,并把所需最小缓冲区大小写入*lenmem(可用作两次调用的探测模式)。
此外,kiss_fft.h 还提供了两个实用 API:
kiss_fft_stride(cfg, fin, fout, fin_stride)—— 每fin_stride个样本取一个输入的通用水印版本;kiss_fft_next_fast_size(n)—— 返回大于等于n的最小整数,且该整数只含有"快速"因子(2、3、5)。这一函数在 OpenToonz 中被大量用于将图像尺寸取整为 FFT 友好尺寸,见下文第六节。
超越 1D 的扩展功能
原文档指出,tools/目录(thirdparty/kiss_fft/tools)提供了一系列"很酷"的扩展:
- 多维 FFT(
kiss_fftnd/kiss_fftndr); - 实数优化 FFT(
kiss_fftr):只返回正半频谱,即(nfft/2+1)个复数频率 bin; - 快速卷积 FIR 滤波(
kiss_fastfir.c,注意不支持定点数); - 频谱图像生成(
psdpng.c,依赖 libpng); - 命令行 FFT 工具(
fftutil.c)。
核心 FFT 与绝大多数 tools 代码都能以 float、double、Q15 short 或 Q31 样本编译运行,默认数据类型是 float。
三、构建系统与全部配置项详解
KISS FFT 同时维护两套功能等价的构建系统(thirdparty/kiss_fft/CMakeLists.txt 与 thirdparty/kiss_fft/Makefile):
- Make:面向 Unix/Linux 的传统 Makefile;
- CMake(>= 3.6):Kitware 出品的现代构建系统。
支持的编译器环境包括 GCC、Clang、GNU Make,以及带 CMake 的 MSVC。构建与测试的可选依赖如下:
| 依赖 | 用途 |
|---|---|
| libpng | 编译psdpng频谱图工具 |
| libfftw3 | 验证 kissfft 结果正确性 |
| Python 2/3 + Numpy | 验证 kissfft 结果正确性 |
| OpenMP(GCC/Clang/MSVC 支持) | 多核 FFT 变换加速 |
原文档同时说明:面向 Windows 的 Cygwin、MinGW 环境也"很可能"可以构建(虽然尚未实测)。
3.1 核心配置参数一览
两个构建系统提供同名配置项(Make 用KEY=value,CMake 用-DKEY=value),原文档中的完整清单如下:
| 配置项 | Make 写法 | CMake 写法 | 说明 |
|---|---|---|---|
| 主数据类型 | KISSFFT_DATATYPE=<dt> | -DKISSFFT_DATATYPE=<dt> | float(默认)/double/int16_t/int32_t/simd(需目标 CPU 支持 SSE) |
| OpenMP 加速 | KISSFFT_OPENMP=1 | -DKISSFFT_OPENMP=ON | 默认关闭,需要编译器支持 |
| 静态库 | KISSFFT_STATIC=1 | -DKISSFFT_STATIC=ON | 生成.a(Unix/Linux)或.lib(Windows);默认生成共享库(.so/.dll/.dylib) |
| 关闭测试 | — | -DKISSFFT_TEST=OFF | Make 下测试由make testall/make testsingle单独触发 |
| 关闭工具 | KISSFFT_TOOLS=0 | -DKISSFFT_TOOLS=OFF | 不构建fastconv等命令行工具,默认构建 |
| 使用 alloca | KISSFFT_USE_ALLOCA=1 | -DKISSFFT_USE_ALLOCA=ON | 用alloca替代malloc/free |
| 安装前缀 | PREFIX=/path | -DCMAKE_INSTALL_PREFIX=/path | 指定安装目录前缀 |
3.2 配置项的底层实现
这些开关并非黑盒,均可在源码中直接验证:
- 数据类型:CMake 在 CMakeLists.txt 中把
float/double映射为编译宏kiss_fft_scalar=<type>,把int16_t/int32_t映射为FIXED_POINT=16/FIXED_POINT=32,把simd映射为USE_SIMD并自动追加-msse(GCC/Clang)或/arch:SSE(MSVC)。这些宏在 kiss_fft.h 中直接决定kiss_fft_scalar的真实类型。非法取值会被 CMake 直接FATAL_ERROR拒绝。 - 静态/共享:CMake 在 CMakeLists.txt 中依据
KISSFFT_STATIC自动设置BUILD_SHARED_LIBS;共享库构建时还会追加KISS_FFT_SHARED编译定义,配合 kiss_fft.h 的KISS_FFT_API实现 Windows DLL 的dllexport/dllimport符号导出。 - OpenMP:CMake 在 CMakeLists.txt 中按编译器追加
-fopenmp(GCC/Clang)或/openmp(MSVC),并将输出库名改为kissfft-<datatype>-openmp。 - alloca:追加
KISS_FFT_USE_ALLOCA编译定义,使内部临时缓冲区走栈上分配而非堆分配。
3.3 典型构建命令
以静态库 + int16_t 定点数据类型 + OpenMP为例,Make 构建:
make KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 all等价 CMake 构建:
mkdir build && cd build cmake -DKISSFFT_DATATYPE=int16_t -DKISSFFT_STATIC=ON -DKISSFFT_OPENMP=ON .. make all指定安装前缀/tmp/1234的安装命令(Make 版):
make PREFIX=/tmp/1234 KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 install(CMake 版):
mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/tmp/1234 -DKISSFFT_DATATYPE=int16_t -DKISSFFT_STATIC=ON -DKISSFFT_OPENMP=ON .. make all make install值得补充的是:仓库中 Makefile 通过语义化版本变量KFVER_MAJOR = 131、KFVER_MINOR = 1、KFVER_PATCH = 0管理 ABI 版本(当前目录名Lz4旁并列的 thirdparty/kiss_fft 对应 KISS FFT 131.1.0 版本),CMake 构建系统会在配置时自动从 Makefile 中正则提取这组版本号用于生成共享库的VERSION/SOVERSION。
四、测试与验证
构建完成后可用以下命令验证:
- Make 单配置验证(对应上面 int16_t 示例):
make KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 testsingle- CMake 验证:
make test- 全配置扩展测试套件:
sh test/kissfft-testsuite.sh该扩展测试套件覆盖所有可能的构建配置组合,耗时约20~40 分钟(取决于设备性能),适合报告 bug 或验证 pull request 时使用。测试基础设施位于 thirdparty/kiss_fft/test,其中benchkiss.c(性能基准)、benchfftw.c(对照 FFTW)、test_real.c(实数 FFT)、test_simd.c(SIMD 路径)、testcpp.cc(C++ 封装)以及testkiss.py(Numpy 对照)分别负责不同维度的验证;twotonetest.c用于两音叠加的数值正确性检查。
五、典型接入流程总结
综合原文档 USAGE 与 BUILDING 两节,把 KISS FFT 接入自有 C 工程的最短路径是:
- 将 kiss_fft.h 与 kiss_fft.c 加入工程(如需多维/实数/卷积再追加对应源文件);
- 按需定义
kiss_fft_scalar、FIXED_POINT等预处理宏(默认 float 则无需定义); - 调用
kiss_fft_alloc→kiss_fft→kiss_fft_free三步完成一次变换; - 注意所有参与编译的源文件必须使用一致的
FIXED_POINT与kiss_fft_scalar预处理定义(这正是原文档 FAQ 中"混编环境"错误的根源)。
六、在 OpenToonz 中的实战:Bokeh 与 Glare 特效的频域实现
KISS FFT 并非 OpenToonz 的孤立附件,而是被正式纳入特效渲染管线的第三方依赖。toonz/sources/stdfx/CMakeLists.txt 将kiss_fft.c与kiss_fftnd.c编译进 stdfx 库,并在第 332 行将thirdparty/kiss_fft加入头文件搜索路径。
6.1 基于 FFT 的快速卷积方案
光学模糊类特效(景深虚化、眩光)在空间域是逐像素的卷积运算,计算量与核尺寸成正比;KISS FFT 通过"空间域 → FFT 频域相乘 → IFFT"的卷积定理将其转化为逐元素乘法,复杂度与核尺寸解耦。这一思路在 iwa_bokeh_util.cpp 中体现得十分完整:
- 维度取整:在 iwa_bokehfx.cpp 中,用
kiss_fft_next_fast_size()将图像尺寸放大到"只含快速因子(2、3、5)"的 FFT 友好尺寸,避免混合基数退化到慢速因子。 - plan 分配:在 iwa_bokeh_util.cpp 中分别创建正向与反向两个 2D plan:
kiss_fftnd_alloc(dims, ndims, false, 0, 0)(正变换)与kiss_fftnd_alloc(dims, ndims, true, 0, 0)(逆变换)。 - 执行与复用:正向
kiss_fftnd(...)将图像与光瞳掩模(iris)变换到频域,频域相乘后(见multiplyFilter)再做逆向kiss_fftnd(...)还原空域;plan 用完立即kiss_fft_free释放。 - 资源管理:多处用
QList<kiss_fftnd_cfg>登记所有 plan,统一在特效结束时循环kiss_fft_free,防止泄漏(iwa_bokeh_util.cpp)。
6.2 眩光特效的同类实现
iwa_glarefx.cpp 采用完全相同的模式:先kiss_fft_next_fast_size取整光瞳尺寸,再kiss_fftnd_alloc建立正变换 plan,把光瞳(iris)变换到频域供后续多次复用。RGB 与 Alpha 通道各自维护独立的 fwd/bkwd plan(iwa_bokeh_util.cpp),通过"一次 FFT、频域逐元素相乘、一次 IFFT"完成整张图像与光瞳核的快速卷积。
从源码结构看,KISS FFT 之所以被选中,正是因为它"混合基数、可缩放、无依赖、可直接编译进工程"的特性与 OpenToonz 的插件化渲染框架高度契合。
七、底层原理(UNDER THE HOOD)
原文档对内部实现给出了精确描述,结合源码可梳理出以下要点:
7.1 算法形态:时域抽取的混合基数、异地(out-of-place)FFT
KISS FFT 使用时域抽取(time decimation)的混合基数算法,且默认异地执行——输入与输出使用不同缓冲区。若传入相同缓冲区,内部会自动创建一个临时缓冲区来承接数据。这一设计使调用方无需关心 in-place 与 out-of-place 的差异。
7.2 线程安全与无静态数据
核心例程不使用任何静态数据,因此是线程安全的(原文档特别注明 tools 目录下并非全部线程安全)。这使其天然适合 OpenToonz 这类多线程渲染环境:不同渲染线程可各自持有独立的 plan 与缓冲区并行执行 FFT。
7.3 缩放策略:浮点不缩放,定点双向缩放
- 浮点版本不做任何缩放(纯粹为了速度);
- 定点版本正反两个方向都做缩放(为了防止溢出)。
这一点也解释了原文档 FAQ 中常见的"输出与预期不符"问题——请先检查是否存在一个常量倍率差异(scaling factor)。
7.4 优化的蝶形单元
对因子2、3、4、5使用专门优化的蝶形(butterfly)实现,这也是kiss_fft_next_fast_size只认可 2、3、5 因子、以及偶数长度才能用实数优化的原因。
7.5 实数优化:偶数长度专用
实数(非复数)优化只对偶数长度有效:它并行执行两个半长度 FFT(打包进实部与虚部),再通过旋转因子(twiddling)合并,最终输出DC 到 Nyquist 的nfft/2+1个复数频率 bin(kiss_fft.h 中kiss_fftr_next_fast_size_real宏强制把尺寸取为偶数,正是为此)。
7.6 快速卷积:overlap-scrap 法
kiss_fastfir.c的快速卷积滤波采用overlap-scrap(重叠丢弃)方法,并做了轻微改动:把 scrap(废弃段)放在尾部而非头部。
八、SIMD 扩展:一次并行计算 4 路 FFT
仓库中的 README.simd 对KISSFFT_DATATYPE=simd模式给出专门说明。其基本思想是把 4 个 float 打包的__m128当作一个"标量元素"使用,从而一次 FFT 调用同时完成 A、B、C、D 四路独立信号的 FFT,在支持 SSE 的 Intel x86 机器上可换取2~3 倍加速。
- 复数数据交错布局:
rA0,rB0,rC0,rD0, iA0,iB0,iC0,iD0, rA1,rB1,rC1,rD1, iA1,iB1,iC1,iD1 ...(rA0为信号 A 第 0 个样本的实部); - 纯实数数据布局:
rA0,rB0,rC0,rD0, rA1,rB1,rC1,rD1, ...; - 推荐编译选项:
-O3 -mpreferred-stack-boundary=4 -DUSE_SIMD=1 -msse; - 对齐是最大的坑:SIMD 需要 16 字节对齐,栈上临时变量的地址必须落在 16 字节边界上,这是 SIMD 模式最常见的段错误来源。为此 kiss_fft.h 在
USE_SIMD下自动将KISS_FFT_MALLOC切换为_mm_malloc(nbytes, 16)、KISS_FFT_FREE切换为_mm_free,并在尺寸上做 16 字节向上取整。
README.simd 也坦诚地警告:该 API 不好用、文档不全,并且违背了 KISS 原则("Beyond here there be dragons!")。它附带的pack128/unpack128示例代码(来源为 Divide Concept 的 Robin,未经作者实测,风险自负)演示了如何在 4×N 与 N×4 转置之间格式化 SIMD 数据。
九、常见问题(FAQ)
原文档 FAQ 的权威回答如下:
Q:我能在使用 ___ 许可证的项目里用 kissfft 吗?A:可以,见下文的许可证说明。Revised BSD 许可证兼容面极广,从 GPL 到闭源商业软件都在其覆盖范围之内。
Q:为什么我得不到预期的输出?A:最常见原因有两个:
- 缩放:你得到的结果与预期之间是否只差一个常量倍率?
- 混编环境:所有代码必须用相同的
FIXED_POINT与kiss_fft_scalar预处理定义编译。
Q:你能帮我写/调试代码吗?A:作者表示除非付费否则大概率不会,但乐于回答具体而切题的问题。
十、性能画像与使用边界
原文档给出了一组朴素但直观的性能数据(Athlon XP 2100+,gcc 2.96,float 类型):
- 完成 10000 次 1024 点复数 FFT 耗时约0.63 秒CPU 时间;
- 处理同样数据量,
md5sum耗时约为其两倍; - 变换 5 分钟 CD 音质音频(nfft=1024)耗时不足 1 秒。
对照实验中,作者将 KISS FFT 与某个"备受尊敬且高度优化"的库(文中匿称为 FFT_BRANDX)对比,得到四个关键结论:
- FFT_BRANDX 有超过 10 万行代码,而 kiss_fft 的核心(1D 复数)约500 行;
- 作者花了很长时间才让 FFT_BRANDX 跑起来;
- 使用 FFT_BRANDX 的简单程序体积 522KB,而类似功能的 kiss_fft 程序仅18KB(且未做体积优化);
- FFT_BRANDX 在默认模式下大约比 KISS FFT快一倍。
因此作者给出的边界十分清醒——"DO NOT: 如果需要全世界最快的 FFT 就别用 KISS FFT;也别要求添加会让代码膨胀的功能",并留下一句耐人寻味的设计哲学:"Sometimes simpler is better, even if it's not better.(有时更简单更好,即使它并非更好。)"
十一、许可证与后续 TODO
- 许可证:Revised BSD License,详见 COPYING。概括为"可自由使用与修改,注明出处即可,不提供任何担保"。该许可证对 GPL 开源软件与闭源商业软件都兼容。kiss_fft.h 头部的版权声明(2003-2010, Mark Borgerding)与 SPDX 标识
BSD-3-Clause与此一致。 - TODO 清单(原文档记录,供贡献者参考):
- 为奇数长度 FFT 增加实数优化;
- 文档化/重新审视输入输出的缩放策略;
- 撰写
kiss_fastfir.c中 overlap(尾部 scrap)快速卷积滤波的说明文档; - 用定点数全面测试
tools/下代码(已知kiss_fastfir.c不工作,其余待测)。
结语
KISS FFT 以约 500 行核心代码同时交付了"混合基数 FFT、四类数据类型、线程安全、宽松许可证、分钟级集成"的组合,是嵌入式与桌面软件中"够用就好"路线的典型代表。在 OpenToonz 中,它被 stdfx 模块用于 Bokeh 景深虚化与 Glare 眩光特效的频域卷积,实现了核尺寸无关的高效模糊;如果你正在为自己的 C/C++ 工程寻找一个零依赖、可定点、可快速集成的 FFT 方案,thirdparty/kiss_fft 的这套源码与文档就是现成的参考答案。
【免费下载链接】opentoonzOpenToonz - An open-source full-featured 2D animation creation software项目地址: https://gitcode.com/GitHub_Trending/op/opentoonz
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考