参数结构
`Params<N>` 里的 `N` 是 BigFloat limb 数量。它让同一个 kernel 可以用不同精度重新实例化:浅层用较少 limb,深 zoom 用更多 limb。
import("bigfloat", "../bigfloat.zs");
pub struct Params<N> {
x: bigfloat::BigFloat<N>,
y: bigfloat::BigFloat<N>,
step: bigfloat::BigFloat<N>,
max_iter: u32,
}
像素映射到复平面
GPU 线程通过 workgroup id 和 local id 找到采样点,再把像素偏移转换成 BigFloat,最后叠加到高精度中心坐标上。
let group = spirv::group_id();
let local = spirv::local_id();
let px = group[0] * 16u32 + local[0];
let py = group[1] * 16u32 + local[1];
let x_offset = bigfloat::BigFloat<N>::from_f32(
((px as f32) * 0.5f32) - 512.5f32
);
let y_offset = bigfloat::BigFloat<N>::from_f32(
512.5f32 - ((py as f32) * 0.5f32)
);
let x_bf = params.x.add(x_offset.mul(params.step));
let y_bf = params.y.add(y_offset.mul(params.step));
逃逸迭代
Mandelbrot 的核心公式是 `z = z² + c`。当 `zx² + zy² > 4` 时,该点逃逸;没有逃逸的点通常被视为集合内部或边界附近。
while iter < params.max_iter {
let zx2 = zx.mul(zx);
let zy2 = zy.mul(zy);
let radius2 = zx2.add(zy2);
if radius2.gt(escape_radius2) {
break;
}
let tmp = zx2.sub(zy2).add(x_bf);
let next_zy = two_bf.mul(zx).mul(zy).add(y_bf);
zy = next_zy;
zx = tmp;
iter += 1u32;
}
为什么需要 BigFloat
深度放大时,屏幕上相邻像素对应的复平面距离可能远小于普通 `f32` 能表达的有效间隔。此时画面会变成平滑色块,不是因为没有细节,而是像素偏移被精度吞掉了。
- `mandelbrot_f32.zs` 适合快速验证浅层图像。
- `mandelbrot_bigfloat2.zs` 能覆盖接近双精度的场景。
- `mandelbrot_bigfloat4.zs` 和 `mandelbrot_bigfloat8.zs` 用于更深层探索。
运行命令
仓库保留了编译和运行示例。只检查 SPIR-V 不需要真实 Vulkan dispatch;运行 Vulkan 或 Metal 示例则需要对应平台环境。
cargo run -p vm-spirv --example mandelbrot
MANDEL_ZS=zusts/gpu/mandelbrot_bigfloat8.zs \
MANDEL_MODULE=mandelbrot_bigfloat8 \
cargo run -p vulkan --example run_mandel