Browse Source

Rename to citro3d-macros

Export as pub mod macros, add tests, and refactor error handling a bit
to make the logic simpler.
pull/22/head
Ian Chamberlain 8 months ago
parent
commit
4752c65f41
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 3
      .gitignore
  2. 13
      Cargo.toml
  3. 2
      citro3d-macros/Cargo.toml
  4. 0
      citro3d-macros/build.rs
  5. 66
      citro3d-macros/src/lib.rs
  6. 14
      citro3d-macros/tests/integration.pica
  7. 15
      citro3d-macros/tests/integration.rs
  8. 2
      citro3d/Cargo.toml
  9. 6
      citro3d/src/lib.rs

3
.gitignore vendored

@ -17,3 +17,6 @@ Cargo.lock @@ -17,3 +17,6 @@ Cargo.lock
rust-toolchain
rust-toolchain.toml
.cargo/
# Pica200 output files
*.shbin

13
Cargo.toml

@ -1,6 +1,15 @@ @@ -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"]

2
pica200/Cargo.toml → citro3d-macros/Cargo.toml

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
[package]
name = "pica200"
name = "citro3d-macros"
version = "0.1.0"
edition = "2021"
authors = ["Rust3DS Org"]

0
pica200/build.rs → citro3d-macros/build.rs

66
pica200/src/lib.rs → citro3d-macros/src/lib.rs

@ -1,10 +1,13 @@ @@ -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; @@ -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<TokenStream, Box<dyn Error>> {
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 @@ -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 @@ -111,5 +127,5 @@ pub fn include_shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream
}
};
result.into()
Ok(result.into())
}

14
citro3d-macros/tests/integration.pica

@ -0,0 +1,14 @@ @@ -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

15
citro3d-macros/tests/integration.rs

@ -0,0 +1,15 @@ @@ -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);
}

2
citro3d/Cargo.toml

@ -6,7 +6,7 @@ version = "0.1.0" @@ -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" }

6
citro3d/src/lib.rs

@ -8,7 +8,11 @@ pub mod shader; @@ -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.

Loading…
Cancel
Save