naga_rust_back/lib.rs
1//! [`naga`] backend allowing you to translate shader code in any language supported by Naga
2//! to Rust code.
3//!
4//! The generated code requires the [`naga_rust_rt`] library.
5//! Alternatively, you can use [`naga_rust_embed`], which combines this library with
6//! [`naga_rust_rt`] and provides convenient macros for embedding translated WGSL in your Rust code.
7//!
8//! This library is in an early stage of development and many features do not work yet.
9//! Expect compilation failures, incorrect behaviors, and to have to tweak your code to fit,
10//! if you wish to use it. Broadly:
11//!
12//! * Simple mathematical functions will work.
13//! * Code involving pointers is likely to fail to compile.
14//! * Textures are supported but texture filtering is not.
15//! * Atomics, derivatives, and workgroup operations are not supported.
16//! * Pipelines involving multiple shaders (e.g. passing data from vertex to fragment)
17//! are not automatically executed but you can build that yourself.
18//!
19//! [`naga_rust_rt`]: https://docs.rs/naga-rust-rt
20//! [`naga_rust_embed`]: https://docs.rs/naga-rust-embed
21
22#![no_std]
23
24extern crate alloc;
25
26use alloc::format;
27use alloc::string::String;
28use alloc::vec::Vec;
29use core::fmt;
30
31use naga::valid::Capabilities;
32
33use crate::ra::PrintAst as _;
34
35// -------------------------------------------------------------------------------------------------
36
37mod config;
38mod conv;
39mod ra;
40mod util;
41mod writer;
42
43pub use config::Config;
44pub use writer::Writer;
45
46/// The version of Naga we are compatible with.
47pub use naga;
48
49// -------------------------------------------------------------------------------------------------
50
51/// The [`Capabilities`] supported by our Rust runtime library.
52///
53/// Pass this to [`naga::valid::Validator`] when validating a module that is to be translated to
54/// Rust.
55// TODO: There are probably some additional capabilities which should be enabled here
56// either because we can support them or they don’t affect us.
57pub const CAPABILITIES: Capabilities = Capabilities::FLOAT64;
58
59/// Errors returned by the Rust-generating backend.
60#[derive(Debug)]
61#[non_exhaustive]
62pub enum Error {
63 /// The provided [`fmt::Write`] implementation returned an error.
64 FmtError(fmt::Error),
65
66 /// The Rust backend currently does not support this particular shader functionality.
67 // TODO: this should not be a thing when finished; everything should be either supported
68 // or fall into a well-defined category of unsupportedness.
69 Unimplemented(String),
70
71 /// To use a shader with private global variables, [`Config::global_struct()`] must be set.
72 #[non_exhaustive]
73 GlobalVariablesNotEnabled {
74 /// The name of one of the prohibited global variables.
75 example: String,
76 },
77
78 /// To use a shader with resources, [`Config::resource_struct()`] must be set.
79 #[non_exhaustive]
80 ResourcesNotEnabled {
81 /// The name of one of the prohibited resources.
82 example: String,
83 },
84}
85
86impl From<fmt::Error> for Error {
87 fn from(value: fmt::Error) -> Self {
88 Self::FmtError(value)
89 }
90}
91
92impl core::error::Error for Error {}
93impl fmt::Display for Error {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 Error::FmtError(fmt::Error) => write!(f, "formatting cancelled"),
97 Error::Unimplemented(msg) => write!(f, "not yet implemented for Rust: {msg}"),
98 Error::GlobalVariablesNotEnabled { example } => write!(
99 f,
100 "global variable `{example}` found in shader, but `global_struct` is not configured"
101 ),
102 Error::ResourcesNotEnabled { example } => write!(
103 f,
104 "resource `{example}` found in shader, but `resource_struct` is not configured"
105 ),
106 }
107 }
108}
109
110/// Converts `module` to a string of Rust code.
111///
112/// This is a convenience wrapper around [`Writer::write()`].
113///
114/// # Errors
115///
116/// Returns an error if the module cannot be represented as Rust.
117pub fn write_string(
118 module: &naga::Module,
119 info: &naga::valid::ModuleInfo,
120 config: Config,
121) -> Result<String, Error> {
122 let mut w = Writer::new(config);
123 let mut output = String::new();
124 w.write(&mut output, module, info)?;
125 Ok(output)
126}
127
128/// Converts `module` to Rust code, then throws away everything but the body of the single function.
129///
130/// This function is used to help test the translation of individual statements and expressions
131/// without having to reiterate the rest of the generated code.
132#[doc(hidden)] // test helper
133#[mutants::skip] // test helper, not code under test
134pub fn translate_function_body_only_for_testing(
135 module: &naga::Module,
136 info: &naga::valid::ModuleInfo,
137 config: &Config,
138) -> Result<String, Error> {
139 let mut w = Writer::new(config.clone());
140 let items = w.translate_module(module, info)?;
141
142 let functions: Vec<ra::FunctionItem> = items
143 .into_iter()
144 .filter_map(|item| {
145 let ra::Item::Function(fn_item) = item else {
146 return None;
147 };
148 // Look for the internal/vectorized function rather than the wrapper function.
149 if !fn_item.name.starts_with("v_") {
150 return None;
151 }
152 Some(fn_item)
153 })
154 .collect();
155
156 if functions.len() != 1 {
157 return Err(Error::Unimplemented(format!(
158 "expected exactly one function; found {items:?}",
159 items = functions.into_iter().map(|f| f.name).collect::<Vec<_>>()
160 )));
161 }
162 let function = functions.into_iter().next().unwrap();
163
164 let mut output = String::new();
165 function.body.write(
166 &mut output,
167 ra::PrintCtx {
168 config,
169 indent: naga::back::Level(0),
170 },
171 )?;
172 Ok(output)
173}