Browse Source

Generate bindings with build.rs at compile time

Effectively:
- Move the previous bindgen script into build.rs
- In lib.rs, include the file generated at build time
- Compile inline statics with devkitARM toolchain
- Remove now-unneeded reimplementations of inline statics
- Use doxygen_rs to clean up the docs of the generated bindings.

Nice! This cleans up the repo a lot and also ensures we are using the
canonical implementations of those inline functions, which should help
prevent mistakes as well.
pull/26/head
Ian Chamberlain 1 year ago
parent
commit
b712e8a68f
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 7
      Cargo.toml
  2. 9
      bindgen-citro3d/Cargo.toml
  3. 92
      bindgen-citro3d/src/main.rs
  4. 5
      citro3d-sys/Cargo.toml
  5. 132
      citro3d-sys/build.rs
  6. 9
      citro3d-sys/src/base.rs
  7. 1740
      citro3d-sys/src/bindings.rs
  8. 3
      citro3d-sys/src/gx.rs
  9. 16
      citro3d-sys/src/lib.rs
  10. 13
      citro3d-sys/src/os.rs
  11. 18
      citro3d-sys/src/renderqueue.rs
  12. 100
      citro3d-sys/src/texenv.rs
  13. 50
      citro3d-sys/src/uniforms.rs
  14. 2
      citro3d/examples/triangle.rs

7
Cargo.toml

@ -1,9 +1,8 @@ @@ -1,9 +1,8 @@
[workspace]
members = [
"bindgen-citro3d",
"citro3d-macros",
"citro3d-sys",
"citro3d",
"citro3d-sys",
"citro3d-macros",
]
default-members = [
"citro3d",
@ -13,4 +12,6 @@ default-members = [ @@ -13,4 +12,6 @@ default-members = [
resolver = "2"
[patch."https://github.com/rust3ds/citro3d-rs.git"]
citro3d = { path = "citro3d" }
citro3d-sys = { path = "citro3d-sys" }
citro3d-macros = { path = "citro3d-macros" }

9
bindgen-citro3d/Cargo.toml

@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
[package]
name = "bindgen-citro3d"
version = "0.1.0"
edition = "2021"
description = "Helper tool to generate citro3d-sys bindings."
publish = false
[dependencies]
bindgen = "0.62.0"

92
bindgen-citro3d/src/main.rs

@ -1,92 +0,0 @@ @@ -1,92 +0,0 @@
//! This is meant to be run as a "script" to generate bindings to `citro3d`.
//! We use this instead of `bindgen-cli` to enable the use of [`CustomCallbacks`]
//! with [`bindgen`] as a library for finer grained control of the bindings.
use std::iter::FromIterator;
use std::path::PathBuf;
use bindgen::callbacks::{DeriveTrait, ImplementsTrait, ParseCallbacks};
use bindgen::{Builder, RustTarget};
fn main() {
let devkitpro = std::env::var("DEVKITPRO").expect("DEVKITPRO not set in environment");
let devkitarm = std::env::var("DEVKITARM").expect("DEVKITARM not set in environment");
let include_path = PathBuf::from_iter([devkitpro.as_str(), "libctru", "include"]);
let header = include_path.join("tex3ds.h");
let sysroot = PathBuf::from(devkitarm).join("arm-none-eabi");
let system_include = sysroot.join("include");
let bindings = Builder::default()
.header(header.to_str().unwrap())
.rust_target(RustTarget::Nightly)
.use_core()
.trust_clang_mangling(false)
.layout_tests(false)
.ctypes_prefix("::libc")
.prepend_enum_name(false)
.fit_macro_constants(true)
.raw_line("use ctru_sys::*;")
.must_use_type("Result")
.blocklist_type("u(8|16|32|64)")
.opaque_type("(GPU|GFX)_.*")
.opaque_type("float24Uniform_s")
.allowlist_file(".*/c3d/.*[.]h")
.allowlist_file(".*/tex3ds[.]h")
.blocklist_file(".*/3ds/.*[.]h")
.blocklist_file(".*/sys/.*[.]h")
.clang_args([
"--target=arm-none-eabi",
"--sysroot",
sysroot.to_str().unwrap(),
"-isystem",
system_include.to_str().unwrap(),
"-I",
include_path.to_str().unwrap(),
"-mfloat-abi=hard",
"-march=armv6k",
"-mtune=mpcore",
"-mfpu=vfp",
"-DARM11 ",
"-D_3DS ",
"-D__3DS__ ",
])
.parse_callbacks(Box::new(CustomCallbacks))
.generate()
.expect("Unable to generate bindings");
bindings
.write(Box::new(std::io::stdout()))
.expect("failed to write bindings");
}
/// Custom callback struct to allow us to mark some "known good types" as
/// [`Copy`], which in turn allows using Rust `union` instead of bindgen union
/// types. See
/// <https://rust-lang.github.io/rust-bindgen/using-unions.html#which-union-type-will-bindgen-generate>
/// for more info.
///
/// We do the same for [`Debug`] just for the convenience of derived Debug impls
/// on some `citro3d` types.
#[derive(Debug)]
struct CustomCallbacks;
impl ParseCallbacks for CustomCallbacks {
fn blocklisted_type_implements_trait(
&self,
name: &str,
derive_trait: DeriveTrait,
) -> Option<ImplementsTrait> {
if let DeriveTrait::Copy | DeriveTrait::Debug = derive_trait {
match name {
"u64_" | "u32_" | "u16_" | "u8_" | "u64" | "u32" | "u16" | "u8" | "gfxScreen_t"
| "gfx3dSide_t" => Some(ImplementsTrait::Yes),
_ if name.starts_with("GPU_") => Some(ImplementsTrait::Yes),
_ => None,
}
} else {
None
}
}
}

5
citro3d-sys/Cargo.toml

@ -8,3 +8,8 @@ license = "Zlib" @@ -8,3 +8,8 @@ license = "Zlib"
[dependencies]
libc = "0.2.116"
ctru-sys = { git = "https://github.com/rust3ds/ctru-rs.git" }
[build-dependencies]
bindgen = { version = "0.68.1", features = ["experimental"] }
cc = "1.0.83"
doxygen-rs = "0.4.2"

132
citro3d-sys/build.rs

@ -1,19 +1,143 @@ @@ -1,19 +1,143 @@
//! This build script generates bindings from `citro3d` on the fly at compilation
//! time into `OUT_DIR`, from which they can be included into `lib.rs`.
use std::env;
use std::iter::FromIterator;
use std::path::{Path, PathBuf};
use bindgen::callbacks::{DeriveTrait, ImplementsTrait, ParseCallbacks};
use bindgen::{Builder, RustTarget};
fn main() {
let dkp_path = env::var("DEVKITPRO").unwrap();
let devkitpro = env::var("DEVKITPRO").expect("DEVKITPRO not set in environment");
println!("cargo:rerun-if-env-changed=DEVKITPRO");
let devkitarm = std::env::var("DEVKITARM").expect("DEVKITARM not set in environment");
println!("cargo:rerun-if-env-changed=DEVKITARM");
let debug_symbols = env::var("DEBUG").unwrap();
println!("cargo:rerun-if-env-changed=DEBUG");
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
println!("cargo:rerun-if-env-changed=OUT_DIR");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=DEVKITPRO");
println!("cargo:rustc-link-search=native={dkp_path}/libctru/lib");
println!("cargo:rustc-link-search=native={devkitpro}/libctru/lib");
println!(
"cargo:rustc-link-lib=static={}",
match debug_symbols.as_str() {
// Based on valid values described in
// https://doc.rust-lang.org/cargo/reference/profiles.html#debug
"0" | "false" => "citro3d",
"0" | "false" | "none" => "citro3d",
_ => "citro3dd",
}
);
let include_path = PathBuf::from_iter([devkitpro.as_str(), "libctru", "include"]);
let tex3ds_h = include_path.join("tex3ds.h");
let citro3d_h = include_path.join("citro3d.h");
let three_ds_h = include_path.join("3ds.h");
let sysroot = Path::new(devkitarm.as_str()).join("arm-none-eabi");
let system_include = sysroot.join("include");
let static_fns_path = Path::new("citro3d_statics_wrapper");
let bindings = Builder::default()
.header(three_ds_h.to_str().unwrap())
.header(citro3d_h.to_str().unwrap())
.header(tex3ds_h.to_str().unwrap())
.rust_target(RustTarget::Nightly)
.use_core()
.trust_clang_mangling(false)
.layout_tests(false)
.ctypes_prefix("::libc")
.prepend_enum_name(false)
.fit_macro_constants(true)
.raw_line("use ctru_sys::*;")
.must_use_type("Result")
.blocklist_type("u(8|16|32|64)")
.opaque_type("(GPU|GFX)_.*")
.opaque_type("float24Uniform_s")
.allowlist_file(".*/c3d/.*[.]h")
.allowlist_file(".*/tex3ds[.]h")
.blocklist_file(".*/3ds/.*[.]h")
.blocklist_file(".*/sys/.*[.]h")
.wrap_static_fns(true)
.wrap_static_fns_path(out_dir.join(static_fns_path))
.clang_args([
"--target=arm-none-eabi",
"--sysroot",
sysroot.to_str().unwrap(),
"-isystem",
system_include.to_str().unwrap(),
"-I",
include_path.to_str().unwrap(),
"-mfloat-abi=hard",
"-march=armv6k",
"-mtune=mpcore",
"-mfpu=vfp",
"-DARM11 ",
"-D_3DS ",
"-D__3DS__ ",
])
.parse_callbacks(Box::new(CustomCallbacks))
.generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("failed to write bindings");
// Compile static inline fns wrapper
let cc = Path::new(devkitarm.as_str()).join("bin/arm-none-eabi-gcc");
let ar = Path::new(devkitarm.as_str()).join("bin/arm-none-eabi-ar");
cc::Build::new()
.compiler(cc)
.archiver(ar)
.include(&include_path)
.file(out_dir.join(static_fns_path.with_extension("c")))
.flag("-march=armv6k")
.flag("-mtune=mpcore")
.flag("-mfloat-abi=hard")
.flag("-mfpu=vfp")
.flag("-mtp=soft")
.flag("-Wno-deprecated-declarations")
.compile("citro3d_statics_wrapper");
}
/// Custom callback struct to allow us to mark some "known good types" as
/// [`Copy`], which in turn allows using Rust `union` instead of bindgen union types. See
/// <https://rust-lang.github.io/rust-bindgen/using-unions.html#which-union-type-will-bindgen-generate>
/// for more info.
///
/// We do the same for [`Debug`] just for the convenience of derived Debug impls
/// on some `citro3d` types.
///
/// Finally, we use [`doxygen_rs`] to transform the doc comments into something
/// easier to read in the generated documentation / hover documentation.
#[derive(Debug)]
struct CustomCallbacks;
impl ParseCallbacks for CustomCallbacks {
fn process_comment(&self, comment: &str) -> Option<String> {
Some(doxygen_rs::transform(comment))
}
fn blocklisted_type_implements_trait(
&self,
name: &str,
derive_trait: DeriveTrait,
) -> Option<ImplementsTrait> {
if let DeriveTrait::Copy | DeriveTrait::Debug = derive_trait {
match name {
"u64_" | "u32_" | "u16_" | "u8_" | "u64" | "u32" | "u16" | "u8" | "gfxScreen_t"
| "gfx3dSide_t" => Some(ImplementsTrait::Yes),
_ if name.starts_with("GPU_") => Some(ImplementsTrait::Yes),
_ => None,
}
} else {
None
}
}
}

9
citro3d-sys/src/base.rs

@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
//! Definitions from `<c3d/base.h>`
use crate::C3D_FixedAttribGetWritePtr;
#[inline]
pub unsafe fn C3D_FixedAttribSet(id: libc::c_int, x: f32, y: f32, z: f32, w: f32) {
let ptr = C3D_FixedAttribGetWritePtr(id);
(*ptr).c.copy_from_slice(&[x, y, z, w]);
}

1740
citro3d-sys/src/bindings.rs

File diff suppressed because it is too large Load Diff

3
citro3d-sys/src/gx.rs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
//! Helper functions based on `<3ds/gpu/gx.h>`.
//! Helper functions based on `<3ds/gpu/gx.h>`. Bindgen doesn't work on these
//! function-like macros so we just reimplement them as `#[inline]` here.
use ctru_sys::{GX_TRANSFER_FORMAT, GX_TRANSFER_SCALE};

16
citro3d-sys/src/lib.rs

@ -5,19 +5,7 @@ @@ -5,19 +5,7 @@
#![allow(non_camel_case_types)]
#![allow(clippy::all)]
pub mod base;
pub mod gx;
pub mod os;
pub mod renderqueue;
pub mod texenv;
pub mod uniforms;
mod bindings;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
pub use base::*;
pub use bindings::*;
pub mod gx;
pub use gx::*;
pub use os::*;
pub use renderqueue::*;
pub use texenv::*;
pub use uniforms::*;

13
citro3d-sys/src/os.rs

@ -1,13 +0,0 @@ @@ -1,13 +0,0 @@
// TODO: move this to ctru-sys, maybe?
// would probably be auto-generated via https://github.com/rust3ds/ctru-rs/issues/123
use ctru_sys::{osSharedConfig_s, OS_SHAREDCFG_VADDR};
fn OS_SharedConfig() -> *mut osSharedConfig_s {
OS_SHAREDCFG_VADDR as _
}
/// Gets the state of the 3D slider as a value from 0.0 to 1.0
pub unsafe fn osGet3DSliderState() -> f32 {
(*OS_SharedConfig()).slider_3d
}

18
citro3d-sys/src/renderqueue.rs

@ -1,18 +0,0 @@ @@ -1,18 +0,0 @@
//! Definitions from `<c3d/renderqueue.h>`
use crate::*;
#[inline]
pub unsafe fn C3D_RenderTargetDetachOutput(target: *mut C3D_RenderTarget) {
C3D_RenderTargetSetOutput(core::ptr::null_mut(), (*target).screen, (*target).side, 0);
}
#[inline]
pub unsafe fn C3D_RenderTargetClear(
target: *mut C3D_RenderTarget,
clearBits: C3D_ClearBits,
clearColor: u32,
clearDepth: u32,
) {
C3D_FrameBufClear(&mut (*target).frameBuf, clearBits, clearColor, clearDepth);
}

100
citro3d-sys/src/texenv.rs

@ -1,100 +0,0 @@ @@ -1,100 +0,0 @@
//! Definitions from `<c3d/texenv.h>`.
//!
//! Most of these functions are `static inline` so they don't get generated by `bindgen`.
//! See <https://github.com/rust-lang/rust-bindgen/issues/1090>.
use core::ops::{BitOr, Shl};
use ctru_sys::{GPU_COMBINEFUNC, GPU_PREVIOUS, GPU_REPLACE, GPU_TEVSCALE_1, GPU_TEVSRC};
use libc::c_int;
use super::*;
// TODO: why are these two different macros in C?
/// Creates a texture combiner source parameter from three sources.
#[inline]
fn GPU_TEVSOURCES<T>(a: T, b: T, c: T) -> T
where
T: BitOr<Output = T> + Shl<u8, Output = T>,
{
a | b << 4 | c << 8
}
/// Creates a texture combiner operand parameter from three operands.
#[inline]
fn GPU_TEVOPERANDS<T>(a: T, b: T, c: T) -> T
where
T: BitOr<Output = T> + Shl<u8, Output = T>,
{
a | b << 4 | c << 8
}
#[inline]
pub unsafe fn C3D_TexEnvInit(env: *mut C3D_TexEnv) {
(*env).srcRgb = GPU_TEVSOURCES(GPU_PREVIOUS, 0, 0) as u16;
(*env).srcAlpha = (*env).srcRgb;
(*env).__bindgen_anon_1.opAll = 0;
(*env).funcRgb = GPU_REPLACE as u16;
(*env).funcAlpha = (*env).funcRgb;
(*env).color = 0xFFFFFFFF;
(*env).scaleRgb = GPU_TEVSCALE_1 as u16;
(*env).scaleAlpha = GPU_TEVSCALE_1 as u16;
}
#[inline]
pub unsafe fn C3D_TexEnvSrc(
env: *mut C3D_TexEnv,
mode: C3D_TexEnvMode,
s1: GPU_TEVSRC,
s2: GPU_TEVSRC, // default GPU_PRIMARY_COLOR
s3: GPU_TEVSRC, // default GPU_PRIMARY_COLOR
) {
let param = GPU_TEVSOURCES(s1, s2, s3);
if mode & C3D_RGB != 0 {
(*env).srcRgb = param as u16;
}
if mode & C3D_Alpha != 0 {
(*env).srcAlpha = param as u16;
}
}
// TODO: match API of texenv.h
#[inline]
pub unsafe fn C3D_TexEnvOp(
env: *mut C3D_TexEnv,
mode: C3D_TexEnvMode,
o1: c_int,
o2: c_int,
o3: c_int,
) {
let param = GPU_TEVOPERANDS(o1, o2, o3);
if mode & C3D_RGB != 0 {
// (*env).opRgb = param as u16;
(*env)
.__bindgen_anon_1
.__bindgen_anon_1
.set_opRgb(param as u32);
}
if mode & C3D_Alpha != 0 {
(*env)
.__bindgen_anon_1
.__bindgen_anon_1
.set_opAlpha(param as u32);
}
}
#[inline]
pub unsafe fn C3D_TexEnvFunc(env: *mut C3D_TexEnv, mode: C3D_TexEnvMode, param: GPU_COMBINEFUNC) {
if mode & C3D_RGB != 0 {
(*env).funcRgb = param as u16;
}
if mode & C3D_Alpha != 0 {
(*env).funcAlpha = param as u16;
}
}

50
citro3d-sys/src/uniforms.rs

@ -1,50 +0,0 @@ @@ -1,50 +0,0 @@
//! Definitions from`<c3d/uniforms.h>`
use ctru_sys::GPU_SHADER_TYPE;
use super::{C3D_FVUnif, C3D_FVUnifDirty, C3D_FVec, C3D_Mtx};
#[inline]
pub unsafe fn C3D_FVUnifWritePtr(
type_: GPU_SHADER_TYPE,
id: libc::c_int,
size: libc::c_int,
) -> *mut C3D_FVec {
for i in 0..size {
C3D_FVUnifDirty[type_ as usize][(id + i) as usize] = true;
}
return &mut C3D_FVUnif[type_ as usize][id as usize];
}
#[inline]
pub unsafe fn C3D_FVUnifMtxNx4(
type_: GPU_SHADER_TYPE,
id: libc::c_int,
mtx: *const C3D_Mtx,
num: libc::c_int,
) {
let ptr = C3D_FVUnifWritePtr(type_, id, num);
for i in 0..num {
*ptr.offset(i as isize) = (*mtx).r.as_ref()[i as usize];
}
}
#[inline]
pub unsafe fn C3D_FVUnifMtx4x4(type_: GPU_SHADER_TYPE, id: libc::c_int, mtx: *const C3D_Mtx) {
C3D_FVUnifMtxNx4(type_, id, mtx, 4);
}
#[inline]
pub unsafe fn C3D_FVUnifSet(
type_: GPU_SHADER_TYPE,
id: libc::c_int,
x: f32,
y: f32,
z: f32,
w: f32,
) {
let ptr = C3D_FVUnifWritePtr(type_, id, 1);
(*ptr).c.copy_from_slice(&[x, y, z, w]);
}

2
citro3d/examples/triangle.rs

@ -177,7 +177,7 @@ fn calculate_projections() -> Projections { @@ -177,7 +177,7 @@ fn calculate_projections() -> Projections {
// TODO: it would be cool to allow playing around with these parameters on
// the fly with D-pad, etc.
let slider_val = unsafe { citro3d_sys::osGet3DSliderState() };
let slider_val = unsafe { ctru_sys::osGet3DSliderState() };
let iod = slider_val / 4.0;
let near = 0.01;

Loading…
Cancel
Save