From 4752c65f416bf3ec2edcccb63ea758ef5d610e34 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 23 Sep 2023 13:34:28 -0400 Subject: [PATCH] Rename to citro3d-macros Export as pub mod macros, add tests, and refactor error handling a bit to make the logic simpler. --- .gitignore | 3 ++ Cargo.toml | 13 ++++- {pica200 => citro3d-macros}/Cargo.toml | 2 +- {pica200 => citro3d-macros}/build.rs | 0 {pica200 => citro3d-macros}/src/lib.rs | 66 ++++++++++++++++---------- citro3d-macros/tests/integration.pica | 14 ++++++ citro3d-macros/tests/integration.rs | 15 ++++++ citro3d/Cargo.toml | 2 +- citro3d/src/lib.rs | 6 ++- 9 files changed, 91 insertions(+), 30 deletions(-) rename {pica200 => citro3d-macros}/Cargo.toml (89%) rename {pica200 => citro3d-macros}/build.rs (100%) rename {pica200 => citro3d-macros}/src/lib.rs (65%) create mode 100644 citro3d-macros/tests/integration.pica create mode 100644 citro3d-macros/tests/integration.rs diff --git a/.gitignore b/.gitignore index 4a87a85..f13002d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ Cargo.lock rust-toolchain rust-toolchain.toml .cargo/ + +# Pica200 output files +*.shbin diff --git a/Cargo.toml b/Cargo.toml index f8955b4..c2c3e84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,15 @@ [workspace] -members = ["citro3d-sys", "citro3d", "bindgen-citro3d", "pica200"] -default-members = ["citro3d", "citro3d-sys", "pica200"] +members = [ + "bindgen-citro3d", + "citro3d-macros", + "citro3d-sys", + "citro3d", +] +default-members = [ + "citro3d", + "citro3d-sys", + "citro3d-macros", +] resolver = "2" [patch."https://github.com/rust3ds/citro3d-rs.git"] diff --git a/pica200/Cargo.toml b/citro3d-macros/Cargo.toml similarity index 89% rename from pica200/Cargo.toml rename to citro3d-macros/Cargo.toml index bc7c0e3..968366e 100644 --- a/pica200/Cargo.toml +++ b/citro3d-macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pica200" +name = "citro3d-macros" version = "0.1.0" edition = "2021" authors = ["Rust3DS Org"] diff --git a/pica200/build.rs b/citro3d-macros/build.rs similarity index 100% rename from pica200/build.rs rename to citro3d-macros/build.rs diff --git a/pica200/src/lib.rs b/citro3d-macros/src/lib.rs similarity index 65% rename from pica200/src/lib.rs rename to citro3d-macros/src/lib.rs index 77f195e..6fbcc3a 100644 --- a/pica200/src/lib.rs +++ b/citro3d-macros/src/lib.rs @@ -1,10 +1,13 @@ // we're already nightly-only so might as well use unstable proc macro APIs. #![feature(proc_macro_span)] +use std::error::Error; +use std::fs::DirBuilder; use std::path::PathBuf; use std::{env, process}; use litrs::StringLit; +use proc_macro::TokenStream; use quote::quote; /// Compiles the given PICA200 shader using [`picasso`](https://github.com/devkitPro/picasso) @@ -23,22 +26,30 @@ use quote::quote; /// # Example /// /// ```no_run -/// # use pica200::include_shader; +/// # use citro3d_macros::include_shader; /// static SHADER_BYTES: &[u8] = include_shader!("assets/vshader.pica"); /// ``` #[proc_macro] pub fn include_shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + match include_shader_impl(input) { + Ok(tokens) => tokens, + Err(err) => { + let err_str = err.to_string(); + quote! { compile_error!( #err_str ) }.into() + } + } +} + +fn include_shader_impl(input: TokenStream) -> Result> { let tokens: Vec<_> = input.into_iter().collect(); if tokens.len() != 1 { - let msg = format!("expected exactly one input token, got {}", tokens.len()); - return quote! { compile_error!(#msg) }.into(); + return Err(format!("expected exactly one input token, got {}", tokens.len()).into()); } let string_lit = match StringLit::try_from(&tokens[0]) { - // Error if the token is not a string literal - Err(e) => return e.to_compile_error(), Ok(lit) => lit, + Err(err) => return Ok(err.to_compile_error()), }; // The cwd can change depending on whether this is running in a doctest or not: @@ -46,46 +57,51 @@ pub fn include_shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream // // But the span's `source_file()` seems to always be relative to the cwd. let cwd = env::current_dir().expect("unable to determine working directory"); - let invoking_source_file = tokens[0].span().source_file().path(); - let invoking_source_dir = invoking_source_file - .parent() - .expect("unable to find parent directory of invoking source file"); + let span = tokens[0].span(); + let invoking_source_file = span.source_file().path(); + let Some(invoking_source_dir) = invoking_source_file.parent() else { + return Err("unable to find parent directory of invoking source file".into()); + }; // By joining these three pieces, we arrive at approximately the same behavior as `include_bytes!` let shader_source_file = cwd.join(invoking_source_dir).join(string_lit.value()); - let shader_out_file = shader_source_file.with_extension("shbin"); - - let Some(shader_out_file) = shader_out_file.file_name() else { - let msg = format!("invalid input file name {shader_source_file:?}"); - return quote! { compile_error!(#msg) }.into(); - }; + let shader_out_file: PathBuf = shader_source_file.with_extension("shbin"); let out_dir = PathBuf::from(env!("OUT_DIR")); - let out_path = out_dir.join(shader_out_file); + // This might be overkill, but it ensures we get a unique path if different + // shaders with the same relative path are used within one program + let relative_out_path = shader_out_file.canonicalize()?; + + let out_path = out_dir.join( + relative_out_path + .strip_prefix("/") + .unwrap_or(&shader_out_file), + ); + + let parent_dir = out_path.parent().ok_or("invalid input filename")?; + DirBuilder::new().recursive(true).create(parent_dir)?; let devkitpro = PathBuf::from(env!("DEVKITPRO")); let output = process::Command::new(devkitpro.join("tools/bin/picasso")) .arg("--out") .args([&out_path, &shader_source_file]) - .output() - .unwrap(); + .output()?; match output.status.code() { Some(0) => {} code => { let code = code.map_or_else(|| String::from("unknown"), |c| c.to_string()); - let msg = format!( - "failed to compile shader {shader_source_file:?}: exit status {code}: {}", + return Err(format!( + "failed to compile shader: exit status from `picasso` was {code}: {}", String::from_utf8_lossy(&output.stderr), - ); - - return quote! { compile_error!(#msg) }.into(); + ) + .into()); } } - let bytes = std::fs::read(out_path).unwrap(); + let bytes = std::fs::read(out_path)?; let source_file_path = shader_source_file.to_string_lossy(); let result = quote! { @@ -111,5 +127,5 @@ pub fn include_shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream } }; - result.into() + Ok(result.into()) } diff --git a/citro3d-macros/tests/integration.pica b/citro3d-macros/tests/integration.pica new file mode 100644 index 0000000..a47bfde --- /dev/null +++ b/citro3d-macros/tests/integration.pica @@ -0,0 +1,14 @@ +; Trivial vertex shader + +.out outpos position +.out outclr color + +.alias inpos v1 +.alias inclr v0 + +.proc main + mov outpos, inpos + mov outclr, inclr + + end +.end diff --git a/citro3d-macros/tests/integration.rs b/citro3d-macros/tests/integration.rs new file mode 100644 index 0000000..c67a52f --- /dev/null +++ b/citro3d-macros/tests/integration.rs @@ -0,0 +1,15 @@ +use citro3d_macros::include_shader; + +#[test] +fn includes_shader_static() { + static SHADER_BYTES: &[u8] = include_shader!("test.pica"); + + assert_eq!(SHADER_BYTES.len() % 4, 0); +} + +#[test] +fn includes_shader_const() { + const SHADER_BYTES: &[u8] = include_shader!("test.pica"); + + assert_eq!(SHADER_BYTES.len() % 4, 0); +} diff --git a/citro3d/Cargo.toml b/citro3d/Cargo.toml index 8d94e9f..99b340c 100644 --- a/citro3d/Cargo.toml +++ b/citro3d/Cargo.toml @@ -6,7 +6,7 @@ version = "0.1.0" edition = "2021" [dependencies] -pica200 = { version = "0.1.0", path = "../pica200" } +citro3d-macros = { version = "0.1.0", path = "../citro3d-macros" } bitflags = "1.3.2" bytemuck = { version = "1.10.0", features = ["extern_crate_std"] } citro3d-sys = { git = "https://github.com/rust3ds/citro3d-rs.git" } diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index cab6a59..5176de6 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -8,7 +8,11 @@ pub mod shader; use citro3d_sys::C3D_FrameDrawOn; pub use error::{Error, Result}; -pub use pica200::include_shader; + +pub mod macros { + //! Helper macros for working with shaders. + pub use citro3d_macros::*; +} /// The single instance for using `citro3d`. This is the base type that an application /// should instantiate to use this library.