ZZust

Mandelbrot Example

一个完整的 Zust GPU 数值示例

Mandelbrot 集适合展示 Zust 的几个关键能力:泛型结构体、BigFloat 数值类型、GPU kernel 形状、typed vector 输出,以及 SPIR-V、Metal、Vulkan 后端。

Zust BigFloat Mandelbrot 细节渲染
BigFloat Mandelbrot 细节
BigFloat 保存深层坐标后,像素偏移不会过早丢失,边界细节可以继续展开。
Mandelbrot 螺旋区域
提高迭代次数后,慢逃逸点会继续显露出旋涡、放射纹理和细碎边界。

参数结构

`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