spirv_std_macros/
lib.rs

1// FIXME(eddyb) update/review these lints.
2//
3// BEGIN - Embark standard lints v0.4
4// do not change or add/remove here, but one can add exceptions after this section
5// for more info see: <https://github.com/EmbarkStudios/rust-ecosystem/issues/59>
6#![deny(unsafe_code)]
7#![warn(
8    clippy::all,
9    clippy::await_holding_lock,
10    clippy::char_lit_as_u8,
11    clippy::checked_conversions,
12    clippy::dbg_macro,
13    clippy::debug_assert_with_mut_call,
14    clippy::doc_markdown,
15    clippy::empty_enum,
16    clippy::enum_glob_use,
17    clippy::exit,
18    clippy::expl_impl_clone_on_copy,
19    clippy::explicit_deref_methods,
20    clippy::explicit_into_iter_loop,
21    clippy::fallible_impl_from,
22    clippy::filter_map_next,
23    clippy::float_cmp_const,
24    clippy::fn_params_excessive_bools,
25    clippy::if_let_mutex,
26    clippy::implicit_clone,
27    clippy::imprecise_flops,
28    clippy::inefficient_to_string,
29    clippy::invalid_upcast_comparisons,
30    clippy::large_types_passed_by_value,
31    clippy::let_unit_value,
32    clippy::linkedlist,
33    clippy::lossy_float_literal,
34    clippy::macro_use_imports,
35    clippy::manual_ok_or,
36    clippy::map_err_ignore,
37    clippy::map_flatten,
38    clippy::map_unwrap_or,
39    clippy::match_same_arms,
40    clippy::match_wildcard_for_single_variants,
41    clippy::mem_forget,
42    clippy::mut_mut,
43    clippy::mutex_integer,
44    clippy::needless_borrow,
45    clippy::needless_continue,
46    clippy::option_option,
47    clippy::path_buf_push_overwrite,
48    clippy::ptr_as_ptr,
49    clippy::ref_option_ref,
50    clippy::rest_pat_in_fully_bound_structs,
51    clippy::same_functions_in_if_condition,
52    clippy::semicolon_if_nothing_returned,
53    clippy::string_add_assign,
54    clippy::string_add,
55    clippy::string_lit_as_bytes,
56    clippy::string_to_string,
57    clippy::todo,
58    clippy::trait_duplication_in_bounds,
59    clippy::unimplemented,
60    clippy::unnested_or_patterns,
61    clippy::unused_self,
62    clippy::useless_transmute,
63    clippy::verbose_file_reads,
64    clippy::zero_sized_map_values,
65    future_incompatible,
66    nonstandard_style,
67    rust_2018_idioms
68)]
69// END - Embark standard lints v0.4
70// crate-specific exceptions:
71// #![allow()]
72#![doc = include_str!("../README.md")]
73
74mod debug_printf;
75mod image;
76mod sample_param_permutations;
77
78use crate::debug_printf::{DebugPrintfInput, debug_printf_inner};
79use proc_macro::TokenStream;
80use proc_macro2::{Delimiter, Group, Ident, TokenTree};
81use quote::{ToTokens, TokenStreamExt, format_ident, quote};
82use spirv_std_types::spirv_attr_version::spirv_attr_with_version;
83
84/// A macro for creating SPIR-V `OpTypeImage` types. Always produces a
85/// `spirv_std::image::Image<...>` type.
86///
87/// The grammar for the macro is as follows:
88///
89/// ```rust,ignore
90/// Image!(
91///     <dimensionality>,
92///     <type=...|format=...>,
93///     [sampled[=<true|false>],]
94///     [multisampled[=<true|false>],]
95///     [arrayed[=<true|false>],]
96///     [depth[=<true|false>],]
97/// )
98/// ```
99///
100/// `=true` can be omitted as shorthand - e.g. `sampled` is short for `sampled=true`.
101///
102/// A basic example looks like this:
103/// ```rust,ignore
104/// #[spirv(vertex)]
105/// fn main(#[spirv(descriptor_set = 0, binding = 0)] image: &Image!(2D, type=f32, sampled)) {}
106/// ```
107///
108/// ## Arguments
109///
110/// - `dimensionality` — Dimensionality of an image.
111///   Accepted values: `1D`, `2D`, `3D`, `rect`, `cube`, `subpass`.
112/// - `type` — The sampled type of an image, mutually exclusive with `format`,
113///   when set the image format is unknown.
114///   Accepted values: `f32`, `f64`, `u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`.
115/// - `format` — The image format of the image, mutually exclusive with `type`.
116///   Accepted values: Snake case versions of [`ImageFormat`] variants, e.g. `rgba32f`,
117///   `rgba8_snorm`.
118/// - `sampled` — Whether it is known that the image will be used with a sampler.
119///   Accepted values: `true` or `false`. Default: `unknown`.
120/// - `multisampled` — Whether the image contains multisampled content.
121///   Accepted values: `true` or `false`. Default: `false`.
122/// - `arrayed` — Whether the image contains arrayed content.
123///   Accepted values: `true` or `false`. Default: `false`.
124/// - `depth` — Whether it is known that the image is a depth image.
125///   Accepted values: `true` or `false`. Default: `unknown`.
126///
127/// [`ImageFormat`]: spirv_std_types::image_params::ImageFormat
128///
129/// Keep in mind that `sampled` here is a different concept than the `SampledImage` type:
130/// `sampled=true` means that this image requires a sampler to be able to access, while the
131/// `SampledImage` type bundles that sampler together with the image into a single type (e.g.
132/// `sampler2D` in GLSL, vs. `texture2D`).
133#[proc_macro]
134// The `Image` is supposed to be used in the type position, which
135// uses `PascalCase`.
136#[allow(nonstandard_style)]
137pub fn Image(item: TokenStream) -> TokenStream {
138    let output = syn::parse_macro_input!(item as image::ImageType).into_token_stream();
139
140    output.into()
141}
142
143/// Replaces all (nested) occurrences of the `#[spirv(..)]` attribute with
144/// `#[cfg_attr(target_arch="spirv", rust_gpu::spirv(..))]`.
145#[proc_macro_attribute]
146pub fn spirv(attr: TokenStream, item: TokenStream) -> TokenStream {
147    let spirv = format_ident!("{}", &spirv_attr_with_version());
148
149    // prepend with #[rust_gpu::spirv(..)]
150    let attr: proc_macro2::TokenStream = attr.into();
151    let mut tokens = quote! { #[cfg_attr(target_arch="spirv", rust_gpu::#spirv(#attr))] };
152
153    let item: proc_macro2::TokenStream = item.into();
154    for tt in item {
155        match tt {
156            TokenTree::Group(group) if group.delimiter() == Delimiter::Parenthesis => {
157                let mut group_tokens = proc_macro2::TokenStream::new();
158                let mut last_token_hashtag = false;
159                for tt in group.stream() {
160                    let is_token_hashtag =
161                        matches!(&tt, TokenTree::Punct(punct) if punct.as_char() == '#');
162                    match tt {
163                        TokenTree::Group(group)
164                            if group.delimiter() == Delimiter::Bracket
165                                && last_token_hashtag
166                                && matches!(group.stream().into_iter().next(), Some(TokenTree::Ident(ident)) if ident == "spirv") =>
167                        {
168                            // group matches [spirv ...]
169                            // group stream doesn't include the brackets
170                            let inner = group
171                                .stream()
172                                .into_iter()
173                                .skip(1)
174                                .collect::<proc_macro2::TokenStream>();
175                            group_tokens.extend(
176                                quote! { [cfg_attr(target_arch="spirv", rust_gpu::#spirv #inner)] },
177                            );
178                        }
179                        _ => group_tokens.append(tt),
180                    }
181                    last_token_hashtag = is_token_hashtag;
182                }
183                let mut out = Group::new(Delimiter::Parenthesis, group_tokens);
184                out.set_span(group.span());
185                tokens.append(out);
186            }
187            _ => tokens.append(tt),
188        }
189    }
190    tokens.into()
191}
192
193/// For testing only! Is not reexported in `spirv-std`, but reachable via
194/// `spirv_std::macros::spirv_recursive_for_testing`.
195///
196/// May be more expensive than plain `spirv`, since we're checking a lot more symbols. So I've opted to
197/// have this be a separate macro, instead of modifying the standard `spirv` one.
198#[proc_macro_attribute]
199pub fn spirv_recursive_for_testing(attr: TokenStream, item: TokenStream) -> TokenStream {
200    fn recurse(spirv: &Ident, stream: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
201        let mut last_token_hashtag = false;
202        stream.into_iter().map(|tt| {
203            let mut is_token_hashtag = false;
204            let out = match tt {
205                TokenTree::Group(group)
206                if group.delimiter() == Delimiter::Bracket
207                    && last_token_hashtag
208                    && matches!(group.stream().into_iter().next(), Some(TokenTree::Ident(ident)) if ident == "spirv") =>
209                    {
210                        // group matches [spirv ...]
211                        // group stream doesn't include the brackets
212                        let inner = group
213                            .stream()
214                            .into_iter()
215                            .skip(1)
216                            .collect::<proc_macro2::TokenStream>();
217                        quote! { [cfg_attr(target_arch="spirv", rust_gpu::#spirv #inner)] }
218                    },
219                TokenTree::Group(group) => {
220                    let mut out = Group::new(group.delimiter(), recurse(spirv, group.stream()));
221                    out.set_span(group.span());
222                    TokenTree::Group(out).into()
223                },
224                TokenTree::Punct(punct) => {
225                    is_token_hashtag = punct.as_char() == '#';
226                    TokenTree::Punct(punct).into()
227                }
228                tt => tt.into(),
229            };
230            last_token_hashtag = is_token_hashtag;
231            out
232        }).collect()
233    }
234
235    let attr: proc_macro2::TokenStream = attr.into();
236    let item: proc_macro2::TokenStream = item.into();
237
238    // prepend with #[rust_gpu::spirv(..)]
239    let spirv = format_ident!("{}", &spirv_attr_with_version());
240    let inner = recurse(&spirv, item);
241    quote! { #[cfg_attr(target_arch="spirv", rust_gpu::#spirv(#attr))] #inner }.into()
242}
243
244/// Marks a function as runnable only on the GPU, and will panic on
245/// CPU platforms.
246#[proc_macro_attribute]
247pub fn gpu_only(_attr: TokenStream, item: TokenStream) -> TokenStream {
248    let syn::ItemFn {
249        attrs,
250        vis,
251        sig,
252        block,
253    } = syn::parse_macro_input!(item as syn::ItemFn);
254
255    let fn_name = sig.ident.clone();
256
257    let sig_cpu = syn::Signature {
258        abi: None,
259        ..sig.clone()
260    };
261
262    let output = quote::quote! {
263        // Don't warn on unused arguments on the CPU side.
264        #[cfg(not(target_arch="spirv"))]
265        #[allow(unused_variables)]
266        #(#attrs)* #vis #sig_cpu {
267            unimplemented!(
268                concat!("`", stringify!(#fn_name), "` is only available on SPIR-V platforms.")
269            )
270        }
271
272        #[cfg(target_arch="spirv")]
273        #(#attrs)* #vis #sig {
274            #block
275        }
276    };
277
278    output.into()
279}
280
281/// Print a formatted string using the debug printf extension.
282///
283/// Examples:
284///
285/// ```rust,ignore
286/// debug_printf!("uv: %v2f\n", uv);
287/// debug_printf!("pos.x: %f, pos.z: %f, int: %i\n", pos.x, pos.z, int);
288/// ```
289///
290/// See <https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/debug_printf.md#debug-printf-format-string> for formatting rules.
291#[proc_macro]
292pub fn debug_printf(input: TokenStream) -> TokenStream {
293    debug_printf_inner(syn::parse_macro_input!(input as DebugPrintfInput))
294}
295
296/// Similar to `debug_printf` but appends a newline to the format string.
297#[proc_macro]
298pub fn debug_printfln(input: TokenStream) -> TokenStream {
299    let mut input = syn::parse_macro_input!(input as DebugPrintfInput);
300    input.format_string.push('\n');
301    debug_printf_inner(input)
302}
303
304/// Generates permutations of an `ImageWithMethods` implementation containing sampling functions
305/// that have asm instruction ending with a placeholder `$PARAMS` operand. The last parameter
306/// of each function must be named `params`, its type will be rewritten. Relevant generic
307/// arguments are added to the impl generics.
308/// See `SAMPLE_PARAM_GENERICS` for a list of names you cannot use as generic arguments.
309#[proc_macro_attribute]
310#[doc(hidden)]
311pub fn gen_sample_param_permutations(_attr: TokenStream, item: TokenStream) -> TokenStream {
312    sample_param_permutations::gen_sample_param_permutations(item)
313}