Alpha. The public API, CLI, reflection schema, and emitted SPIR-V may change during the
0.xseries. The project is suitable for experimentation and integration pilots, but consumers should version their cached outputs and expect upgrades to require integration work.
metal2vulkan translates Metal AIR—LLVM bitcode or sanitized LLVM IR—into Vulkan 1.2 SPIR-V
with a native Rust emitter and retained-SPIR-V passes.
- Vertex, post-tessellation vertex, fragment, compute-kernel, and generated passthrough interfaces
- A Rust library and an
in → out.spvcommand-line interface - Structurally selected construction with one final validation pass; unsupported inputs fail visibly instead of returning known-invalid SPIR-V
- Consumer reflection for descriptors, stage interfaces, function constants, argument buffers, and conservative buffer byte footprints
- An optional unpublished validation workspace for authored semantic cases, GPU evidence, and exact translator A/B
- Rust 1.87 or newer
llvm-diswhen the input is AIR bitcodespirv-valfrom SPIRV-Tools for product translation
Tool paths are found through the usual search path. Override an individual tool with
METAL2VULKAN_LLVM_DIS, METAL2VULKAN_SPIRV_VAL, or the corresponding
METAL2VULKAN_<TOOL> variable.
# CLI without reflection JSON
cargo install metal2vulkan
# CLI with --emit-meta JSON support
cargo install metal2vulkan --features serdeFor a library dependency:
[dependencies]
metal2vulkan = { version = "0.1", features = ["serde"] } # serde is optionalThe CLI auto-detects vertex, fragment, or kernel stage metadata by default:
metal2vulkan input.air output.spv
metal2vulkan input.air output.spv --emit-meta reflection.jsonUse an explicit stage when the input does not carry stage metadata or when generating a passthrough vertex shader:
metal2vulkan input.ll output.spv --stage kernel
metal2vulkan input.ll output.spv --stage passthrough--emit-meta requires a build with the serde feature and is unavailable for passthrough output,
which has no Metal stage metadata. On success the CLI writes validated SPIR-V, prints PASS, and
exits 0. On failure it prints FALLBACK, exits nonzero, and writes a reproducer under
$TMPDIR/metal2vulkan-repros by default. Set METAL2VULKAN_REPRO_DIR to choose another location.
Fragment AIR that calls air.get_num_samples.i32 also needs the graphics pipeline's exact sample
count because Vulkan has no equivalent shader-side query:
metal2vulkan fragment.air fragment.spv --raster-samples 4Accepted values are 1, 2, 4, 8, 16, 32, and 64. Library callers set
TransformOptions::raster_sample_count; translation fails visibly when the query is present and
the value is unknown.
Metal dispatchThreads can make boundary workgroups smaller. Exact-thread kernels therefore expose
specializable local sizes and a reflected KernelDispatch::plan: create a compute pipeline for
each distinct region local_size, specializing KERNEL_LOCAL_SIZE_SPEC_IDS, then dispatch every
region with its 48-byte logical-grid payload. This produces at most eight rectangular dispatches,
with no surplus invocations and uniform workgroup barriers.
For a fixed grid, the CLI can instead bake the exact bounds into a pipeline variant:
metal2vulkan kernel.air kernel.spv --local 8,8,1 --threads-per-grid 57,9,1--threads-per-grid-push-constant OFFSET moves the default dynamic dispatch payload.
Library callers select an override with TransformOptions::kernel_dispatch; reflected translation
returns the effective contract in ShaderReflection::kernel_dispatch. Callers that can prove every
launch covers complete workgroups may explicitly select KernelDispatch::Workgroups or pass
--whole-workgroups to retain a single fixed-local-size dispatch.
Library translation takes caller-owned scratch space. Use a unique directory for concurrent calls and remove it when the operation finishes:
use metal2vulkan::passes::Stage;
use std::path::Path;
fn translate_kernel(sanitized_ll: &str, scratch: &Path) -> Result<Vec<u8>, String> {
metal2vulkan::translate_sanitized_native(sanitized_ll, Stage::Kernel, scratch)
}For a complete executable that auto-detects the stage, writes SPIR-V and reflection JSON, and cleans up its scratch directory on every return path:
cargo run --features serde --example translate_reflected -- \
input.air output.spv reflection.json| Document | Start here when… |
|---|---|
| How to translate and integrate a shader | You want an end-to-end CLI or Rust integration recipe |
| Shader reflection | You need the complete reflection schema and binding contract |
| Architecture | You are changing the emitter, retained module, passes, or retries |
| Validation playbook | You are checking byte stability or authored semantic behavior |
| Contributing | You need the repository layout and required development gates |
| Validation crate | You are operating the unpublished corpus tooling |
| Changelog | You need unreleased API and behavior changes |
The generated Rust API documentation is available from cargo doc --all-features --open.
Grammar and lowering behavior is locked with owned synthetic Rust tests. This repository does not ship third-party captured shaders. Optional private system-metallib harvest, authored manifests, and dependency-exact evidence stay in the validation workspace and are described in the validation playbook.
Passing spirv-val proves structural validity, not agreement with Metal or correct pixels. Use the
authored validation ladder for semantic claims.
Licensed under the GNU Lesser General Public License v3.0 or later
(LGPL-3.0-or-later).
Metal is a trademark of Apple Inc. metal2vulkan is an independent project and is not affiliated with, sponsored by, or endorsed by Apple Inc.