Skip to main content

naga_to_tokenstream/
lib.rs

1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use std::collections::HashSet;
5
6/// Methods for converting sets of `naga::Constant`s to token streams.
7pub mod constants;
8/// Methods for converting sets of `naga::EntryPoint`s to token streams.
9pub mod entry_points;
10/// Methods for converting sets of `naga::GlobalVariable`s to token streams.
11pub mod globals;
12/// Methods for converting sets of `naga::Type`s to token streams.
13pub mod types;
14
15fn collect_tokenstream<I: quote::ToTokens>(
16    items: impl IntoIterator<Item = I>,
17) -> proc_macro2::TokenStream {
18    let mut tokens = proc_macro2::TokenStream::new();
19    for item in items {
20        item.to_tokens(&mut tokens);
21    }
22
23    tokens
24}
25
26fn module_to_source(module: &naga::Module, retain_entry_point: Option<String>) -> Option<String> {
27    // Clone since we sometimes modify things
28    #[allow(unused_mut)]
29    let mut module = module.clone();
30
31    // We allow only a single entry point, for specialised source strings per entry point.
32    if let Some(retain_entry_point) = retain_entry_point {
33        entry_points::filter_entry_points(&mut module, retain_entry_point);
34    }
35
36    // If we minify, do the first pass before writing out
37    #[cfg(feature = "minify")]
38    {
39        wgsl_minifier::minify_module(&mut module);
40    }
41
42    // Mini validation to get module info
43    let info = naga::valid::Validator::new(
44        naga::valid::ValidationFlags::empty(),
45        naga::valid::Capabilities::all(),
46    )
47    .validate(&module);
48
49    // Write to wgsl
50    let info = info.ok()?;
51    let src =
52        naga::back::wgsl::write_string(&module, &info, naga::back::wgsl::WriterFlags::empty())
53            .ok()?;
54
55    // Remove whitespace if minifying
56    #[cfg(feature = "minify")]
57    let src = wgsl_minifier::minify_wgsl_source(&src);
58
59    Some(src)
60}
61
62/// The configuration required to create a token stream describing a module.
63#[derive(Default)]
64pub struct ModuleToTokensConfig {
65    /// A filter on the structs to expose. This is useful specifically when using the `encase` feature,
66    /// since many structs can't be encoded or decoded. It is therefore the using crate's responsibility
67    /// to expose this in some way, for example by having structs that should be exported to Rust require
68    /// an attribute.
69    pub structs_filter: Option<HashSet<String>>,
70    /// Generate `glam` types.
71    pub gen_glam: bool,
72    /// Generate `encase` types.
73    pub gen_encase: bool,
74    /// Generate `naga` types.
75    pub gen_naga: bool,
76}
77
78mod sealed {
79    pub trait SealedModule {}
80    impl SealedModule for naga::Module {}
81}
82
83/// An extension trait for `naga::Module` which exposes the functionality of this crate.
84///
85/// # Usage
86///
87/// ```
88/// use naga_to_tokenstream::{ModuleToTokens, ModuleToTokensConfig};
89///
90/// let my_module = naga::Module::default();
91/// let token_representation = my_module.to_tokens(ModuleToTokensConfig::default());
92/// ```
93pub trait ModuleToTokens: sealed::SealedModule {
94    /// Converts a module to a set of `syn` module items, representing the module.
95    fn to_items(&self, cfg: ModuleToTokensConfig) -> Vec<syn::Item>;
96    /// Convenience method which calls `to_items` and then flattens the items to a single tokenstream.
97    fn to_tokens(&self, cfg: ModuleToTokensConfig) -> proc_macro2::TokenStream {
98        collect_tokenstream(self.to_items(cfg))
99    }
100}
101impl ModuleToTokens for naga::Module {
102    fn to_items(&self, cfg: ModuleToTokensConfig) -> Vec<syn::Item> {
103        let mut items = Vec::new();
104        let mut types = types::TypesDefinitions::new(self, cfg.structs_filter.clone(), &cfg);
105
106        // Globals
107        let globals = collect_tokenstream(globals::make_globals(self, &mut types, &cfg));
108        let globals: syn::File = syn::parse2(globals).unwrap();
109        let globals_str = format!("```rust\n{}\n```", prettyplease::unparse(&globals));
110        let globals_doc: proc_macro2::TokenStream = quote::quote! { #[doc = #globals_str] };
111        items.push(syn::parse_quote! {
112            #[allow(unused)]
113            #[doc = "Information about the globals within the module, exposed as constants and functions."]
114            #globals_doc
115            pub mod globals {
116                #[allow(unused)]
117                use super::*;
118
119                #globals
120            }
121        });
122
123        // Constants
124        let constants = collect_tokenstream(constants::make_constants(self, &mut types, &cfg));
125        let constants: syn::File = syn::parse2(constants).unwrap();
126        let constants_str = format!("```rust\n{}\n```", prettyplease::unparse(&constants));
127        let constants_doc: proc_macro2::TokenStream = quote::quote! { #[doc = #constants_str] };
128        items.push(syn::parse_quote! {
129            #[allow(unused)]
130            #[doc = "Information about the constants within the module, exposed as constants and functions."]
131            #constants_doc
132            pub mod constants {
133                #[allow(unused)]
134                use super::*;
135
136                #constants
137            }
138        });
139
140        // Entry Points
141        let entry_points =
142            collect_tokenstream(entry_points::make_entry_points(self, &mut types, &cfg));
143        let entry_points: syn::File = syn::parse2(entry_points).unwrap();
144        let entry_points_str = format!("```rust\n{}\n```", prettyplease::unparse(&entry_points));
145        let entry_points_doc: proc_macro2::TokenStream =
146            quote::quote! { #[doc = #entry_points_str] };
147        items.push(syn::parse_quote! {
148            #[allow(unused)]
149            #[doc = "Information about the entry points within the module, exposed as constants and functions."]
150            #entry_points_doc
151            pub mod entry_points {
152                #[allow(unused)]
153                use super::*;
154
155                #entry_points
156            }
157        });
158
159        // Types
160        let types = collect_tokenstream(types.definitions());
161        let types: syn::File = syn::parse2(types).unwrap();
162        let types_str = format!("```rust\n{}\n```", prettyplease::unparse(&types));
163        let types_doc: proc_macro2::TokenStream = quote::quote! { #[doc = #types_str] };
164        items.push(syn::parse_quote! {
165          #[allow(unused)]
166          #[doc = "Equivalent Rust definitions of the types defined in this module."]
167          #types_doc
168          pub mod types {
169            #types
170          }
171        });
172        // We use all the types from the types mod in other modules.
173        items.push(syn::parse_quote! {
174            #[allow(unused)]
175            use types::*;
176        });
177
178        // Source string
179        if let Some(src) = module_to_source(self, None) {
180            items.push(syn::parse_quote! {
181                #[doc = "The sourcecode for the shader, as a constant string."]
182                pub const SOURCE: &'static str = #src;
183            });
184        }
185
186        items
187    }
188}