Skip to main content

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}