Skip to content

Commit 4752c65

Browse files
Rename to citro3d-macros
Export as pub mod macros, add tests, and refactor error handling a bit to make the logic simpler.
1 parent 7d5cd29 commit 4752c65

File tree

9 files changed

+91
-30
lines changed

9 files changed

+91
-30
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ Cargo.lock
1717
rust-toolchain
1818
rust-toolchain.toml
1919
.cargo/
20+
21+
# Pica200 output files
22+
*.shbin

Cargo.toml

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
[workspace]
2-
members = ["citro3d-sys", "citro3d", "bindgen-citro3d", "pica200"]
3-
default-members = ["citro3d", "citro3d-sys", "pica200"]
2+
members = [
3+
"bindgen-citro3d",
4+
"citro3d-macros",
5+
"citro3d-sys",
6+
"citro3d",
7+
]
8+
default-members = [
9+
"citro3d",
10+
"citro3d-sys",
11+
"citro3d-macros",
12+
]
413
resolver = "2"
514

615
[patch."https://github.com/rust3ds/citro3d-rs.git"]

pica200/Cargo.toml renamed to citro3d-macros/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "pica200"
2+
name = "citro3d-macros"
33
version = "0.1.0"
44
edition = "2021"
55
authors = ["Rust3DS Org"]
File renamed without changes.

pica200/src/lib.rs renamed to citro3d-macros/src/lib.rs

+41-25
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// we're already nightly-only so might as well use unstable proc macro APIs.
22
#![feature(proc_macro_span)]
33

4+
use std::error::Error;
5+
use std::fs::DirBuilder;
46
use std::path::PathBuf;
57
use std::{env, process};
68

79
use litrs::StringLit;
10+
use proc_macro::TokenStream;
811
use quote::quote;
912

1013
/// Compiles the given PICA200 shader using [`picasso`](https://github.com/devkitPro/picasso)
@@ -23,69 +26,82 @@ use quote::quote;
2326
/// # Example
2427
///
2528
/// ```no_run
26-
/// # use pica200::include_shader;
29+
/// # use citro3d_macros::include_shader;
2730
/// static SHADER_BYTES: &[u8] = include_shader!("assets/vshader.pica");
2831
/// ```
2932
#[proc_macro]
3033
pub fn include_shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
34+
match include_shader_impl(input) {
35+
Ok(tokens) => tokens,
36+
Err(err) => {
37+
let err_str = err.to_string();
38+
quote! { compile_error!( #err_str ) }.into()
39+
}
40+
}
41+
}
42+
43+
fn include_shader_impl(input: TokenStream) -> Result<TokenStream, Box<dyn Error>> {
3144
let tokens: Vec<_> = input.into_iter().collect();
3245

3346
if tokens.len() != 1 {
34-
let msg = format!("expected exactly one input token, got {}", tokens.len());
35-
return quote! { compile_error!(#msg) }.into();
47+
return Err(format!("expected exactly one input token, got {}", tokens.len()).into());
3648
}
3749

3850
let string_lit = match StringLit::try_from(&tokens[0]) {
39-
// Error if the token is not a string literal
40-
Err(e) => return e.to_compile_error(),
4151
Ok(lit) => lit,
52+
Err(err) => return Ok(err.to_compile_error()),
4253
};
4354

4455
// The cwd can change depending on whether this is running in a doctest or not:
4556
// https://users.rust-lang.org/t/which-directory-does-a-proc-macro-run-from/71917
4657
//
4758
// But the span's `source_file()` seems to always be relative to the cwd.
4859
let cwd = env::current_dir().expect("unable to determine working directory");
49-
let invoking_source_file = tokens[0].span().source_file().path();
50-
let invoking_source_dir = invoking_source_file
51-
.parent()
52-
.expect("unable to find parent directory of invoking source file");
60+
let span = tokens[0].span();
61+
let invoking_source_file = span.source_file().path();
62+
let Some(invoking_source_dir) = invoking_source_file.parent() else {
63+
return Err("unable to find parent directory of invoking source file".into());
64+
};
5365

5466
// By joining these three pieces, we arrive at approximately the same behavior as `include_bytes!`
5567
let shader_source_file = cwd.join(invoking_source_dir).join(string_lit.value());
56-
let shader_out_file = shader_source_file.with_extension("shbin");
57-
58-
let Some(shader_out_file) = shader_out_file.file_name() else {
59-
let msg = format!("invalid input file name {shader_source_file:?}");
60-
return quote! { compile_error!(#msg) }.into();
61-
};
68+
let shader_out_file: PathBuf = shader_source_file.with_extension("shbin");
6269

6370
let out_dir = PathBuf::from(env!("OUT_DIR"));
64-
let out_path = out_dir.join(shader_out_file);
71+
// This might be overkill, but it ensures we get a unique path if different
72+
// shaders with the same relative path are used within one program
73+
let relative_out_path = shader_out_file.canonicalize()?;
74+
75+
let out_path = out_dir.join(
76+
relative_out_path
77+
.strip_prefix("/")
78+
.unwrap_or(&shader_out_file),
79+
);
80+
81+
let parent_dir = out_path.parent().ok_or("invalid input filename")?;
82+
DirBuilder::new().recursive(true).create(parent_dir)?;
6583

6684
let devkitpro = PathBuf::from(env!("DEVKITPRO"));
6785

6886
let output = process::Command::new(devkitpro.join("tools/bin/picasso"))
6987
.arg("--out")
7088
.args([&out_path, &shader_source_file])
71-
.output()
72-
.unwrap();
89+
.output()?;
7390

7491
match output.status.code() {
7592
Some(0) => {}
7693
code => {
7794
let code = code.map_or_else(|| String::from("unknown"), |c| c.to_string());
7895

79-
let msg = format!(
80-
"failed to compile shader {shader_source_file:?}: exit status {code}: {}",
96+
return Err(format!(
97+
"failed to compile shader: exit status from `picasso` was {code}: {}",
8198
String::from_utf8_lossy(&output.stderr),
82-
);
83-
84-
return quote! { compile_error!(#msg) }.into();
99+
)
100+
.into());
85101
}
86102
}
87103

88-
let bytes = std::fs::read(out_path).unwrap();
104+
let bytes = std::fs::read(out_path)?;
89105
let source_file_path = shader_source_file.to_string_lossy();
90106

91107
let result = quote! {
@@ -111,5 +127,5 @@ pub fn include_shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream
111127
}
112128
};
113129

114-
result.into()
130+
Ok(result.into())
115131
}

citro3d-macros/tests/integration.pica

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
; Trivial vertex shader
2+
3+
.out outpos position
4+
.out outclr color
5+
6+
.alias inpos v1
7+
.alias inclr v0
8+
9+
.proc main
10+
mov outpos, inpos
11+
mov outclr, inclr
12+
13+
end
14+
.end

citro3d-macros/tests/integration.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use citro3d_macros::include_shader;
2+
3+
#[test]
4+
fn includes_shader_static() {
5+
static SHADER_BYTES: &[u8] = include_shader!("test.pica");
6+
7+
assert_eq!(SHADER_BYTES.len() % 4, 0);
8+
}
9+
10+
#[test]
11+
fn includes_shader_const() {
12+
const SHADER_BYTES: &[u8] = include_shader!("test.pica");
13+
14+
assert_eq!(SHADER_BYTES.len() % 4, 0);
15+
}

citro3d/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ version = "0.1.0"
66
edition = "2021"
77

88
[dependencies]
9-
pica200 = { version = "0.1.0", path = "../pica200" }
9+
citro3d-macros = { version = "0.1.0", path = "../citro3d-macros" }
1010
bitflags = "1.3.2"
1111
bytemuck = { version = "1.10.0", features = ["extern_crate_std"] }
1212
citro3d-sys = { git = "https://github.com/rust3ds/citro3d-rs.git" }

citro3d/src/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ pub mod shader;
88

99
use citro3d_sys::C3D_FrameDrawOn;
1010
pub use error::{Error, Result};
11-
pub use pica200::include_shader;
11+
12+
pub mod macros {
13+
//! Helper macros for working with shaders.
14+
pub use citro3d_macros::*;
15+
}
1216

1317
/// The single instance for using `citro3d`. This is the base type that an application
1418
/// should instantiate to use this library.

0 commit comments

Comments
 (0)