vk_shader_macros_impl/
lib.rs

1extern crate proc_macro;
2
3use std::cell::RefCell;
4use std::path::Path;
5use std::{env, fs, mem, str};
6
7use proc_macro::TokenStream;
8use proc_macro_hack::proc_macro_hack;
9use quote::quote;
10use syn::parse::{Parse, ParseStream, Result};
11use syn::{parse_macro_input, Ident, LitInt, LitStr, Token};
12
13struct IncludeGlsl {
14    sources: Vec<String>,
15    spv: Vec<u32>,
16}
17
18impl Parse for IncludeGlsl {
19    fn parse(input: ParseStream) -> Result<Self> {
20        let path_lit = input.parse::<LitStr>()?;
21        let path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(&path_lit.value());
22        let path_str = path.to_string_lossy();
23
24        let sources = RefCell::new(vec![path_str.clone().into_owned()]);
25        let mut options = shaderc::CompileOptions::new().unwrap();
26        options.set_include_callback(|name, ty, src, _depth| {
27            let path = match ty {
28                shaderc::IncludeType::Relative => Path::new(src).parent().unwrap().join(name),
29                shaderc::IncludeType::Standard => {
30                    Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(name)
31                }
32            };
33            let path_str = path.to_str().ok_or("non-unicode path")?.to_owned();
34            sources.borrow_mut().push(path_str.clone());
35            Ok(shaderc::ResolvedInclude {
36                resolved_name: path_str,
37                content: fs::read_to_string(path).map_err(|x| x.to_string())?,
38            })
39        });
40
41        let mut kind = None;
42        let mut debug = !cfg!(feature = "strip");
43        let mut optimization = if cfg!(feature = "default-optimize-zero") {
44            shaderc::OptimizationLevel::Zero
45        } else {
46            shaderc::OptimizationLevel::Performance
47        };
48
49        let mut target_version = 1 << 22;
50
51        while !input.is_empty() {
52            input.parse::<Token![,]>()?;
53            let key = input.parse::<Ident>()?;
54            match &key.to_string()[..] {
55                "kind" => {
56                    input.parse::<Token![:]>()?;
57                    let value = input.parse::<Ident>()?;
58                    if let Some(x) = extension_kind(&value.to_string()) {
59                        kind = Some(x);
60                    } else {
61                        return Err(syn::Error::new(value.span(), "unknown shader kind"));
62                    }
63                }
64                "version" => {
65                    input.parse::<Token![:]>()?;
66                    let x = input.parse::<LitInt>()?;
67                    options.set_forced_version_profile(
68                        x.base10_parse::<u32>()?,
69                        shaderc::GlslProfile::None,
70                    );
71                }
72                "strip" => {
73                    debug = false;
74                }
75                "debug" => {
76                    debug = true;
77                }
78                "define" => {
79                    input.parse::<Token![:]>()?;
80                    let name = input.parse::<Ident>()?;
81                    let value = if input.peek(Token![,]) {
82                        None
83                    } else {
84                        Some(input.parse::<LitStr>()?.value())
85                    };
86
87                    options.add_macro_definition(&name.to_string(), value.as_ref().map(|x| &x[..]));
88                }
89                "optimize" => {
90                    input.parse::<Token![:]>()?;
91                    let value = input.parse::<Ident>()?;
92                    if let Some(x) = optimization_level(&value.to_string()) {
93                        optimization = x;
94                    } else {
95                        return Err(syn::Error::new(value.span(), "unknown optimization level"));
96                    }
97                }
98                "target" => {
99                    input.parse::<Token![:]>()?;
100                    let value = input.parse::<Ident>()?;
101                    if let Some(version) = target(&value.to_string()) {
102                        target_version = version;
103                    } else {
104                        return Err(syn::Error::new(value.span(), "unknown target"));
105                    }
106                }
107                _ => {
108                    return Err(syn::Error::new(key.span(), "unknown shader compile option"));
109                }
110            }
111        }
112
113        if debug {
114            options.set_generate_debug_info();
115        }
116
117        options.set_optimization_level(optimization);
118        options.set_target_env(shaderc::TargetEnv::Vulkan, target_version);
119
120        let kind = kind
121            .or_else(|| {
122                path.extension()
123                    .and_then(|x| x.to_str().and_then(|x| extension_kind(x)))
124            })
125            .unwrap_or(shaderc::ShaderKind::InferFromSource);
126        let src = fs::read_to_string(&path).map_err(|e| syn::Error::new(path_lit.span(), e))?;
127
128        let mut compiler = shaderc::Compiler::new().unwrap();
129        let out = compiler
130            .compile_into_spirv(&src, kind, &path_str, "main", Some(&options))
131            .map_err(|e| syn::Error::new(path_lit.span(), e))?;
132        if out.get_num_warnings() != 0 {
133            return Err(syn::Error::new(path_lit.span(), out.get_warning_messages()));
134        }
135        mem::drop(options);
136
137        Ok(Self {
138            sources: sources.into_inner(),
139            spv: out.as_binary().into(),
140        })
141    }
142}
143
144#[proc_macro_hack]
145pub fn include_glsl(tokens: TokenStream) -> TokenStream {
146    let IncludeGlsl { sources, spv } = parse_macro_input!(tokens as IncludeGlsl);
147    let expanded = quote! {
148        {
149            #({ const _FORCE_DEP: &[u8] = include_bytes!(#sources); })*
150            &[#(#spv),*]
151        }
152    };
153    TokenStream::from(expanded)
154}
155
156fn extension_kind(ext: &str) -> Option<shaderc::ShaderKind> {
157    use shaderc::ShaderKind::*;
158    Some(match ext {
159        "vert" => Vertex,
160        "frag" => Fragment,
161        "comp" => Compute,
162        "geom" => Geometry,
163        "tesc" => TessControl,
164        "tese" => TessEvaluation,
165        "spvasm" => SpirvAssembly,
166        "rgen" => RayGeneration,
167        "rahit" => AnyHit,
168        "rchit" => ClosestHit,
169        "rmiss" => Miss,
170        "rint" => Intersection,
171        "rcall" => Callable,
172        "task" => Task,
173        "mesh" => Mesh,
174        _ => {
175            return None;
176        }
177    })
178}
179
180fn optimization_level(level: &str) -> Option<shaderc::OptimizationLevel> {
181    match level {
182        "zero" => Some(shaderc::OptimizationLevel::Zero),
183        "size" => Some(shaderc::OptimizationLevel::Size),
184        "performance" => Some(shaderc::OptimizationLevel::Performance),
185        _ => None,
186    }
187}
188
189fn target(s: &str) -> Option<u32> {
190    Some(match s {
191        "vulkan" | "vulkan1_0" => 1 << 22,
192        "vulkan1_1" => 1 << 22 | 1 << 12,
193        "vulkan1_2" => 1 << 22 | 2 << 12,
194        _ => return None,
195    })
196}