写点什么

曼德博洛特集合

作者:Miracle
  • 2025-09-30
    四川
  • 本文字数:2516 字

    阅读完需:约 8 分钟

Mandelbrot 集合是分形数学里面一个非常重要的概念,和科赫雪花等等将数学和艺术以一种奇妙的方式统一了起来,高质量的 Mandelbrot 几个的计算需要极其强大的算力,我们就不详细叙述概念了,下面是一个最简单的 逃逸次数计算代码,x, y 是复平面上的坐标,max_iter 是最大逃逸次数,

fn escape(x: f64, y: f64, max_iter: u32) {    let iter = 0;    let zx = 0.0;    let zy = 0.0;    while iter < max_iter {        let zx2 = zx * zx;        let zy2 = zy * zy;        if zx2 + zy2 > 4.0 {            break;        }        let tmp = zx2 - zy2 + x; // 实部        zy = 2.0 * zx * zy + y; // 虚部        zx = tmp;        iter += 1;    }    return iter;}
复制代码

我们简单的测试一下 一个典型的坐标

println!("{:?}", rt.run(0, vec![Dynamic::F64(-0.745f64), Dynamic::F64(0.11f64), Dynamic::U32(1200u32)]));
复制代码

结果如下

下面把 这段代码编译到 spirv


缺少的 while 循环处理需要加上

                Statement::While(cond, cond_var, code)=> {                    let header = self.builder.id();                    let merge = self.builder.id();                    let loop_continue: u32 = self.builder.id();                    let body = self.builder.id();                    let pre_id = self.block_id.unwrap();            //while 之前 有可能加上一个                    self.builder.branch(header)?;           //跳转到开始                    self.block_id = Some(self.builder.begin_block(Some(header))?);                    let phis = super::phi::get_phis(code);                    let symbols: Vec<(Symbol, Symbol)> = phis.iter().map(|p| (p.stmt.clone(), p.s.clone())).collect();                    let ids: Vec<(Symbol, u32)> = symbols                        .into_iter()                        .map(|(stmt, s)| {                            let r = self.get(&s).unwrap();                            let ty_id = self.get_type(r.ty.clone());                            let update = self.builder.id();                            let id = self.builder.phi(ty_id, None, [(r.id, pre_id), (update, loop_continue)]).unwrap();                            self.set(&s, Var{id, ty: r.ty});                            (stmt, update)                    }).collect();
self.gen_block(m, cond, None, None, &[])?; //生成条件代码 let cond_var = self.get(cond_var)?; //获取条件所在寄存器 self.builder.loop_merge(merge, loop_continue, spirv::LoopControl::NONE, [])?; self.builder.branch_conditional(cond_var.id, body, merge, None)?; self.block_id = Some(self.builder.begin_block(Some(body))?); self.gen_block(m,&code, Some(merge), Some(loop_continue), &ids)?;
self.builder.branch(loop_continue)?; self.block_id = Some(self.builder.begin_block(Some(loop_continue))?); self.builder.branch(header)?; // 回到循环头 self.block_id = Some(self.builder.begin_block(Some(merge))?); }
复制代码

这个代码比较难以理解 需要好好说明,所有 SSA 体系的 中间语言,基本原则是不去修改一个值而是为每次运算生成一个新的 值,也即是一个新 的 SSA ID 这样最大的好处是完全可以使用 寄存器窗口,不用考虑之前寄存器的复用问题。

但是就会面临循环这种最基础的情况,那么怎么处理循环呢,就需要在循环开始的时候 创建 所谓的 phi 变量,就是 求和变量

phi 变量每次根据来源 选择一个值 循环进入的时候,phi 选择的是初始值,循环中,phi 选择的是 修改后的值

let id = self.builder.phi(ty_id, None, [(r.id, pre_id), (update, loop_continue)]).unwrap();
复制代码

就是这条语句的作用了

然后在循环体里面 参与运算的是 phi 的统一标识 最后返回的 是也是这个统一标识

但是运算的结果 通过 phi 汇集的点再回到 统一标识里面,这样就避免了修改 SSA 变量

其实本质就是使用一个 可变的容器 来避免修改 SSA 的值

有点像 rust 里面的 RefCell 内部可变性


我们还是使用 32 x 32 的线程组

使用这个结构传递 计算的初始信息

struct Params {    x: f64,    y: f64,    step: f64,    max_iter: u32}
复制代码


main 函数是这样的

pub fn main(params: Params, buf: Vec<u32>) {    let wrk = spirv_group(); //当前工作组的 id 这个数量由 unsafe { builder.dispatch([32, 32, 1) }.unwrap(); 确定    let cx = wrk[0];    let cy = wrk[1];    let cent_x = params.x + ((cx + 0.5) * 32 - 512) * params.step;    let cent_y = params.y + (512 - (cy + 0.5) * 32) * params.step;    let id = spirv_local();    let x = cent_x + id[0] * params.step - 32 * params.step;    let y = cent_y + 32 * params.step - id[1] * params.step;    let esc = escape(x, y, params.max_iter);    let pos = (cy * 32 + id[1]) * 1024 + cx * 32 + id[0];    buf[pos] = esc as u32;}
复制代码

每个线程组 计算 64 x 64 的小块

总共启动 32 x 32 个线程组

一次计算 1024 x 1024 的图像

        let mut rt = spirv::Runtime::default();        let mut args = rt.get_args();        args.add_input(Params{x: -0.745f64, y: 0.11f64, step: 0.001f64, max_iter: 1200 });        let img = args.add_output_vec::<u32>(1024 * 1024);        rt.prepare(&spirv_code, args)?;        rt.run([32, 32, 1])?;        let buf = img.read()?;
save_mandelbrot_png(&buf, 1024, 1024, 1200, "mand.png")?;
复制代码

这是启动代码 和 初始点坐标

耗费的微秒数



生成的图像在这里


用户头像

Miracle

关注

三十年资深码农 2019-10-25 加入

还未添加个人简介

评论

发布
暂无评论
曼德博洛特集合_Miracle_InfoQ写作社区