Compare commits
3 Commits
395e641250
...
7f7b5261d2
| Author | SHA1 | Date |
|---|---|---|
|
|
7f7b5261d2 | |
|
|
77755b3eb5 | |
|
|
5226578593 |
|
|
@ -8,4 +8,5 @@ proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = "2.0.90"
|
syn = "2.0.90"
|
||||||
|
naga = { version = "23.0.0", features = ["wgsl-in"] }
|
||||||
105
src/lib.rs
105
src/lib.rs
|
|
@ -1,25 +1,84 @@
|
||||||
#![feature(proc_macro_span)]
|
#![feature(proc_macro_span)]
|
||||||
|
|
||||||
use {
|
use {
|
||||||
|
naga::front::wgsl,
|
||||||
proc_macro::TokenStream,
|
proc_macro::TokenStream,
|
||||||
quote::quote,
|
quote::{format_ident, quote},
|
||||||
std::{fs, path::PathBuf},
|
|
||||||
syn::{parse, LitStr},
|
syn::{parse, LitStr},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn emit_create_shader_module(file_contents: &str) -> TokenStream {
|
mod source_reader;
|
||||||
quote! {
|
|
||||||
wgpu::ShaderModuleDescriptor {
|
struct ShaderModule {
|
||||||
label: None,
|
source_file: source_reader::SourceFile,
|
||||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(#file_contents)),
|
naga_module: naga::Module,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ShaderModule {
|
||||||
|
fn try_new(
|
||||||
|
source_dir: &std::path::Path,
|
||||||
|
source_filename: &std::path::Path,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let source_file = source_reader::read_source_file(source_dir, source_filename)?;
|
||||||
|
|
||||||
|
let naga_module = wgsl::parse_str(&source_file.full_text()).map_err(|err| {
|
||||||
|
if let Some(location) = err.location(&source_file.full_text()) {
|
||||||
|
let (inner_source_filename, inner_line_num) =
|
||||||
|
source_file.map_source_line_num(location.line_number as usize);
|
||||||
|
Error::Message(format!(
|
||||||
|
"Error in {} at line {}, column {}:\n{}",
|
||||||
|
&inner_source_filename.as_os_str().to_string_lossy(),
|
||||||
|
inner_line_num,
|
||||||
|
location.line_position,
|
||||||
|
err.message()
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Error::Message(format!(
|
||||||
|
"Error in {}:\n{}",
|
||||||
|
&source_file.root_filename().as_os_str().to_string_lossy(),
|
||||||
|
err.message()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
Ok(Self {
|
||||||
|
source_file,
|
||||||
|
naga_module,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_rust_code(&self) -> TokenStream {
|
||||||
|
// We include the text of all the source files into the generated code
|
||||||
|
// with include_bytes!() so that Cargo recognizes them as
|
||||||
|
// dependencies. It's not pretty but it works. Since the file contents
|
||||||
|
// are just stored in unused local variables, they should be removed by
|
||||||
|
// the optimizer and not affect the compiled code.
|
||||||
|
let source_text = &self.source_file.full_text();
|
||||||
|
let dependencies: Vec<_> = self
|
||||||
|
.source_file
|
||||||
|
.all_filenames()
|
||||||
|
.iter()
|
||||||
|
.map(|path| path.as_os_str().to_string_lossy())
|
||||||
|
.collect();
|
||||||
|
let dummy_var: Vec<_> = (0u32..)
|
||||||
|
.map(|n| format_ident!("_{}", n))
|
||||||
|
.take(dependencies.len())
|
||||||
|
.collect();
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
#(let #dummy_var = include_bytes!(#dependencies);)*
|
||||||
|
wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(#source_text)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WgslModuleMacroInput {
|
struct WgslModuleMacroInput {
|
||||||
source_dir: PathBuf,
|
source_dir: std::path::PathBuf,
|
||||||
filename: String,
|
filename: std::path::PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Error {
|
enum Error {
|
||||||
|
|
@ -48,7 +107,7 @@ fn parse_input(token_stream: TokenStream) -> Result<WgslModuleMacroInput, Error>
|
||||||
|
|
||||||
let source_dir = token.span().source_file().path().parent().unwrap().into();
|
let source_dir = token.span().source_file().path().parent().unwrap().into();
|
||||||
let literal: LitStr = parse(token_stream)?;
|
let literal: LitStr = parse(token_stream)?;
|
||||||
let filename = literal.value();
|
let filename = std::path::Path::new(&literal.value()).into();
|
||||||
Ok(WgslModuleMacroInput {
|
Ok(WgslModuleMacroInput {
|
||||||
source_dir,
|
source_dir,
|
||||||
filename,
|
filename,
|
||||||
|
|
@ -57,21 +116,17 @@ fn parse_input(token_stream: TokenStream) -> Result<WgslModuleMacroInput, Error>
|
||||||
|
|
||||||
fn wgsl_module_inner(input_token_stream: TokenStream) -> Result<TokenStream, Error> {
|
fn wgsl_module_inner(input_token_stream: TokenStream) -> Result<TokenStream, Error> {
|
||||||
let input = parse_input(input_token_stream)?;
|
let input = parse_input(input_token_stream)?;
|
||||||
let full_filename = input.source_dir.join(&input.filename);
|
let module = ShaderModule::try_new(&input.source_dir, &input.filename)?;
|
||||||
let contents = fs::read_to_string(&full_filename).map_err(|err| {
|
Ok(module.emit_rust_code())
|
||||||
Error::from_message(format!(
|
|
||||||
"Could not open \"{}\": {}",
|
|
||||||
&full_filename.as_os_str().to_string_lossy(),
|
|
||||||
err.to_string()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
Ok(emit_create_shader_module(&contents))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn wgsl_module(input: TokenStream) -> TokenStream {
|
pub fn wgsl_module(input: TokenStream) -> TokenStream {
|
||||||
wgsl_module_inner(input).unwrap_or_else(|err| match err {
|
wgsl_module_inner(input).unwrap_or_else(|err| {
|
||||||
Error::Message(message) => quote! { compile_error!(#message) },
|
match err {
|
||||||
Error::SynError(syn_err) => syn_err.into_compile_error(),
|
Error::Message(message) => quote! { compile_error!(#message) },
|
||||||
}.into())
|
Error::SynError(syn_err) => syn_err.into_compile_error(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
use {
|
||||||
|
crate::Error,
|
||||||
|
std::{
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SourceMap {
|
||||||
|
filename: PathBuf,
|
||||||
|
offset: usize,
|
||||||
|
total_lines: usize,
|
||||||
|
insertions: Vec<SourceMap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SourceFile {
|
||||||
|
source_text: String,
|
||||||
|
source_map: SourceMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceFile {
|
||||||
|
pub fn root_filename(&self) -> &Path {
|
||||||
|
&self.source_map.filename
|
||||||
|
}
|
||||||
|
pub fn full_text(&self) -> &str {
|
||||||
|
&self.source_text
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_source_line_num(&self, line_num: usize) -> (&Path, usize) {
|
||||||
|
(&self.source_map.filename, line_num)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all_filenames(&self) -> Vec<&Path> {
|
||||||
|
vec![&self.source_map.filename]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_source_file(source_dir: &Path, filename: &Path) -> Result<SourceFile, Error> {
|
||||||
|
let source_text = fs::read_to_string(&source_dir.join(filename)).map_err(|err| {
|
||||||
|
Error::from_message(format!(
|
||||||
|
"Could not open \"{}\": {}",
|
||||||
|
&filename.as_os_str().to_string_lossy(),
|
||||||
|
err
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
Ok(SourceFile {
|
||||||
|
source_text,
|
||||||
|
source_map: SourceMap {
|
||||||
|
filename: filename.into(),
|
||||||
|
offset: 0,
|
||||||
|
total_lines: 0,
|
||||||
|
insertions: vec![],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue