1extern crate proc_macro;
4
5use darling::util::PathList;
6use darling::{FromDeriveInput, FromField};
7use proc_macro::TokenStream;
8use quote::{quote, ToTokens};
9use syn::{Data, DataStruct, Path};
10
11#[derive(FromDeriveInput, Clone)]
12#[darling(attributes(shader))]
13struct DeriveShadersArgs {
14 #[darling(default)]
15 pub derive: PathList,
16 #[darling(default)]
17 pub composable: Option<bool>,
18 pub src: String,
19 #[darling(default)]
20 pub src_fn: Option<Path>,
21 #[darling(default)]
22 pub shader_defs: Option<Path>,
23}
24
25#[derive(FromField, Clone)]
26#[darling(attributes(shader))]
27struct DeriveShadersFieldArgs {
28 #[darling(default)]
29 pub kernel: Option<String>,
30}
31
32#[proc_macro_derive(Shader, attributes(shader))]
33pub fn derive_shader(item: TokenStream) -> TokenStream {
34 let input = syn::parse_macro_input!(item as syn::DeriveInput);
35 let struct_identifier = &input.ident;
36
37 let derive_shaders = match DeriveShadersArgs::from_derive_input(&input) {
38 Ok(v) => v,
39 Err(e) => {
40 return e.write_errors().into();
41 }
42 };
43
44 match &input.data {
45 Data::Struct(DataStruct { fields, .. }) => {
46 let mut kernels_to_build = vec![];
50 let src_path = derive_shaders.src;
51
52 for field in fields.iter() {
53 let field_args = match DeriveShadersFieldArgs::from_field(field) {
54 Ok(v) => v,
55 Err(e) => {
56 return e.write_errors().into();
57 }
58 };
59 let ident = field.ident.as_ref().expect("unnamed fields not supported").into_token_stream();
60 let kernel_name = field_args.kernel.map(|k| quote! { #k }).unwrap_or_else(|| quote! { stringify!(#ident) });
61
62 if fields.len() == 1 {
63 kernels_to_build.push(quote! {
65 #ident: wgcore::utils::load_module(device, #kernel_name, module),
66 });
67 } else {
68 kernels_to_build.push(quote! {
69 #ident: wgcore::utils::load_module(device, #kernel_name, module.clone()),
70 });
71 }
72 }
73
74 let shader_defs = derive_shaders.shader_defs.map(|defs| quote! { #defs() })
75 .unwrap_or_else(|| quote! { Default::default() });
76 let raw_src = quote! {
77 if let Some(path) = Self::absolute_path() {
81 std::fs::read_to_string(path).unwrap()
83 } else {
84 include_str!(#src_path).to_string()
85 }
86 };
87
88 let src = derive_shaders.src_fn.map(|f| quote! { #f(&#raw_src) })
89 .unwrap_or_else(|| quote! { #raw_src });
90 let naga_module = quote! {
91 Self::composer().and_then(|mut c|
92 c.make_naga_module(wgcore::re_exports::naga_oil::compose::NagaModuleDescriptor {
93 source: &Self::src(),
94 file_path: Self::FILE_PATH,
95 shader_defs: #shader_defs,
96 ..Default::default()
97 })
98 )
99 };
100
101 let from_device = if !kernels_to_build.is_empty() {
102 quote! {
103 let module = #naga_module?;
104 Ok(Self {
105 #(
106 #kernels_to_build
107 )*
108 })
109 }
110 } else {
111 quote ! {
112 Ok(Self)
113 }
114 };
115
116 let to_derive: Vec<_> = derive_shaders
120 .derive
121 .iter()
122 .map(|p| p.into_token_stream())
123 .collect();
124 let composable = derive_shaders.composable.unwrap_or(true);
125 quote! {
126 #[automatically_derived]
127 impl wgcore::shader::Shader for #struct_identifier {
128 const FILE_PATH: &'static str = #src_path;
129
130 fn from_device(device: &wgcore::re_exports::Device) -> Result<Self, wgcore::re_exports::ComposerError> {
131 #from_device
132 }
133
134 fn src() -> String {
135 #src
136 }
137
138 fn naga_module() -> Result<wgcore::re_exports::wgpu::naga::Module, wgcore::re_exports::ComposerError> {
139 #naga_module
140 }
141
142 fn absolute_path() -> Option<std::path::PathBuf> {
143 if let Some(path) = wgcore::ShaderRegistry::get().get_path::<#struct_identifier>() {
144 Some(path.clone())
145 } else {
146 std::path::Path::new(file!())
151 .parent()?
152 .join(Self::FILE_PATH)
153 .canonicalize().ok()
154 }
155 }
156
157 fn compose(composer: &mut wgcore::re_exports::Composer) -> Result<(), wgcore::re_exports::ComposerError> {
158 use wgcore::composer::ComposerExt;
159 #(
160 #to_derive::compose(composer)?;
161 )*
162
163 if #composable {
164 composer
165 .add_composable_module_once(wgcore::re_exports::ComposableModuleDescriptor {
166 source: &Self::src(),
167 file_path: Self::FILE_PATH,
168 shader_defs: #shader_defs,
169 ..Default::default()
170 })?;
171 }
172
173 Ok(())
174 }
175
176 fn watch_sources(state: &mut wgcore::hot_reloading::HotReloadState) -> wgcore::re_exports::notify::Result<()> {
180 #(
181 #to_derive::watch_sources(state)?;
182 )*
183
184 if let Some(path) = Self::absolute_path() {
185 state.watch_file(&path)?;
186 }
187
188 Ok(())
189 }
190
191 fn needs_reload(state: &wgcore::hot_reloading::HotReloadState) -> bool {
192 #(
193 if #to_derive::needs_reload(state) {
194 return true;
195 }
196 )*
197
198 Self::absolute_path()
199 .map(|path| state.file_changed(&path))
200 .unwrap_or_default()
201 }
202 }
203 }
204 }
205 _ => unimplemented!(),
206 }
207 .into()
208}