Trick Cargo into adding shader files as dependencies

Add the context of the shader sources with include_bytes!() so that
Cargo will recompile the rust source if the shader source changes.
This commit is contained in:
Matthew Gordon 2024-12-09 22:37:09 -04:00
parent 77755b3eb5
commit 7f7b5261d2
2 changed files with 42 additions and 21 deletions

View File

@ -3,26 +3,26 @@
use { use {
naga::front::wgsl, naga::front::wgsl,
proc_macro::TokenStream, proc_macro::TokenStream,
quote::quote, quote::{format_ident, quote},
std::fs,
syn::{parse, LitStr}, syn::{parse, LitStr},
}; };
mod source_reader; mod source_reader;
struct ShaderModule { struct ShaderModule {
source_text: String, source_file: source_reader::SourceFile,
naga_module: naga::Module, naga_module: naga::Module,
} }
impl ShaderModule { impl ShaderModule {
fn try_new(source_file: impl Into<std::path::PathBuf>) -> Result<Self, Error> { fn try_new(
let source_filename = source_file.into(); source_dir: &std::path::Path,
let source_file = source_reader::read_source_file(source_filename)?; source_filename: &std::path::Path,
let source_text = source_file.full_text().to_string(); ) -> Result<Self, Error> {
let source_file = source_reader::read_source_file(source_dir, source_filename)?;
let naga_module = wgsl::parse_str(&source_text).map_err(|err| { let naga_module = wgsl::parse_str(&source_file.full_text()).map_err(|err| {
if let Some(location) = err.location(&source_text) { if let Some(location) = err.location(&source_file.full_text()) {
let (inner_source_filename, inner_line_num) = let (inner_source_filename, inner_line_num) =
source_file.map_source_line_num(location.line_number as usize); source_file.map_source_line_num(location.line_number as usize);
Error::Message(format!( Error::Message(format!(
@ -41,26 +41,44 @@ impl ShaderModule {
} }
})?; })?;
Ok(Self { Ok(Self {
source_text, source_file,
naga_module, naga_module,
}) })
} }
fn emit_rust_code(&self) -> TokenStream { fn emit_rust_code(&self) -> TokenStream {
let source_text = &self.source_text; // 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! { quote! {
{
#(let #dummy_var = include_bytes!(#dependencies);)*
wgpu::ShaderModuleDescriptor { wgpu::ShaderModuleDescriptor {
label: None, label: None,
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(#source_text)), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(#source_text)),
} }
} }
}
.into() .into()
} }
} }
struct WgslModuleMacroInput { struct WgslModuleMacroInput {
source_dir: std::path::PathBuf, source_dir: std::path::PathBuf,
filename: String, filename: std::path::PathBuf,
} }
enum Error { enum Error {
@ -89,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,
@ -98,8 +116,7 @@ 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 module = ShaderModule::try_new(full_filename)?;
Ok(module.emit_rust_code()) Ok(module.emit_rust_code())
} }

View File

@ -29,10 +29,14 @@ impl SourceFile {
pub fn map_source_line_num(&self, line_num: usize) -> (&Path, usize) { pub fn map_source_line_num(&self, line_num: usize) -> (&Path, usize) {
(&self.source_map.filename, line_num) (&self.source_map.filename, line_num)
} }
pub fn all_filenames(&self) -> Vec<&Path> {
vec![&self.source_map.filename]
}
} }
pub fn read_source_file(filename: PathBuf) -> Result<SourceFile, Error> { pub fn read_source_file(source_dir: &Path, filename: &Path) -> Result<SourceFile, Error> {
let source_text = fs::read_to_string(&filename).map_err(|err| { let source_text = fs::read_to_string(&source_dir.join(filename)).map_err(|err| {
Error::from_message(format!( Error::from_message(format!(
"Could not open \"{}\": {}", "Could not open \"{}\": {}",
&filename.as_os_str().to_string_lossy(), &filename.as_os_str().to_string_lossy(),
@ -42,7 +46,7 @@ pub fn read_source_file(filename: PathBuf) -> Result<SourceFile, Error> {
Ok(SourceFile { Ok(SourceFile {
source_text, source_text,
source_map: SourceMap { source_map: SourceMap {
filename, filename: filename.into(),
offset: 0, offset: 0,
total_lines: 0, total_lines: 0,
insertions: vec![], insertions: vec![],