vk_shader_macros_impl/
lib.rs1extern 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}