我们使用 rspirv 这个库生成 spirv 代码,使用 spirv 库 引入各种定义和基础函数
前面的章节中,我们已经描述了如何创建一个 type ID,然后用这个 type ID 创建一个变量
我们可以用下面的代码 创建一个函数 并给他一个名字
pub fn import_fn(&mut self, fn_def: FnDef)-> Result<()> {
for arg in fn_def.args {
self.add_arg(arg);
}
let ret_ty = self.get_type(Type::Void);
let func_type_id = self.builder.type_function(ret_ty, Vec::new()); //main 函数是没有参数的
let func_id = self.builder.begin_function(ret_ty, None, spirv::FunctionControl::empty(), func_type_id)?;
self.gen_block(&fn_def.code, None, None)?;
self.builder.name(func_id, fn_def.name);
self.builder.end_function()?;
Ok(())
}
复制代码
这段代码 申明了函数类型,包括函数参数类型,返回值类型,以及有他们决定的正割函数的类型,然后使用这个函数类型 创建了一个 spirv 函数 ,给了一个名字
中间的 self.gen_block(&fn_def.code, None, None)?; 代码 生成函数体的代码,我们接下来详细描述
pub fn assemble(self) -> Vec<u32> {
self.builder.module().assemble()
}
}
复制代码
这段代码 将 builder 创建的 spirv 模块 汇编为 最终的 spirv 模块
注意这里面的参数 是 self 而不是我们通常用的 可写的 &mut self 以及只读的 &self 引用
这是因为 assemble 函数会消耗掉 builder 本身 也就是 builder 被转化为 module 了
这种用法 会避免数据的大量拷贝 只要 SpirvBuilder 以后不再用了
我们就尽可能使用 self
assemble 的结果是一个 Vec<u32> 的数组,这个 spirv 可以直接加载到显卡执行,当然我们现在完成的代码肯定一大堆错误,有可能把显卡烧坏哦
所以我们需要 先检查 生成的 spirv 代码
下面的代码使用我们 编译的 Zeta 中间代码,去掉符号信息之后生成 module 然后生成 spirv 二进制文件,存储到 "demo.spv" 之后反汇编
let mut b = spirv::SpirvBuilder::default();
b.import_module(Module::from(compiler))?;
let spirv_code = b.assemble();
std::fs::write("demo.spv", bytemuck::cast_slice(&spirv_code))?;
let mut loader = rspirv::dr::Loader::new();
rspirv::binary::parse_words(&spirv_code, &mut loader).unwrap();
let parsed = loader.module();
println!("Disassembly:\n{}", parsed.disassemble());
复制代码
执行之后结果如下
完美,当然这个肯定有错,我们先用 Vulkan 的检查工具 spirv-val 检查一下,这玩意可以在 Vulkan 的官方 SDK 找到。
果然有错误
评论