Rust bindings and safe wrappers for citro3d
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
6.9 KiB

use citro3d_sys::C3D_Mtx;
use citro3d_sys::{shaderProgram_s, DVLB_s};
use ctru::gfx::{Gfx, Screen, Side};
use ctru::services::apt::Apt;
use ctru::services::hid::{Hid, KeyPad};
use ctru::services::soc::Soc;
use std::ffi::CStr;
use std::mem::MaybeUninit;
#[repr(C)]
struct Vec3 {
x: f32,
y: f32,
z: f32,
}
impl Vec3 {
const fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
}
#[repr(C)]
struct Vertex {
pos: Vec3,
color: Vec3,
}
const VERTICES: [Vertex; 3] = [
Vertex {
pos: Vec3::new(0.0, 0.5, 0.5),
color: Vec3::new(1.0, 0.0, 0.0),
},
Vertex {
pos: Vec3::new(-0.5, -0.5, 0.5),
color: Vec3::new(0.0, 1.0, 0.0),
},
Vertex {
pos: Vec3::new(0.5, -0.5, 0.5),
color: Vec3::new(0.0, 0.0, 1.0),
},
];
fn main() {
ctru::init();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller");
let mut soc = Soc::init().expect("failed to get SOC");
drop(soc.redirect_to_3dslink(true, true));
let top_screen = gfx.top_screen.borrow_mut();
let target = unsafe {
citro3d_sys::C3D_Init(citro3d_sys::C3D_DEFAULT_CMDBUF_SIZE);
let depth_fmt = citro3d_sys::C3D_DEPTHTYPE {
__e: citro3d_sys::GPU_RB_DEPTH24_STENCIL8,
};
let target =
citro3d_sys::C3D_RenderTargetCreate(240, 400, citro3d_sys::GPU_RB_RGBA8, depth_fmt);
// TODO: easier construction of flags
let transfer_flags =
citro3d_sys::GX_TRANSFER_FMT_RGBA8 << 8 | citro3d_sys::GX_TRANSFER_FMT_RGB8 << 12;
citro3d_sys::C3D_RenderTargetSetOutput(
target,
top_screen.as_raw(),
Side::Left.into(),
transfer_flags,
);
target
};
let (program, uloc_projection, projection, vbo_data, vshader_dvlb) = scene_init();
while apt.main_loop() {
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
break;
}
let clear_color: u32 = 0x7F_7F_7F_FF;
unsafe {
citro3d_sys::C3D_FrameBegin(
citro3d_sys::C3D_FRAME_SYNCDRAW
.try_into()
.expect("const is valid u8"),
);
citro3d_sys::C3D_RenderTargetClear(target, citro3d_sys::C3D_CLEAR_ALL, clear_color, 0);
citro3d_sys::C3D_FrameDrawOn(target);
}
scene_render(uloc_projection.into(), &projection);
unsafe {
citro3d_sys::C3D_FrameEnd(0);
}
}
scene_exit(vbo_data, program, vshader_dvlb);
unsafe {
citro3d_sys::C3D_Fini();
}
}
static SHBIN_BYTES: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/examples/assets/vshader.shbin"));
fn scene_init() -> (shaderProgram_s, i8, C3D_Mtx, *mut libc::c_void, *mut DVLB_s) {
// Load the vertex shader, create a shader program and bind it
unsafe {
let mut shader_bytes = SHBIN_BYTES.to_owned();
// Assume the data is aligned properly...
let vshader_dvlb = citro3d_sys::DVLB_ParseFile(
shader_bytes.as_mut_ptr().cast(),
(shader_bytes.len() / 4)
.try_into()
.expect("shader len fits in a u32"),
);
let mut program = {
let mut program = MaybeUninit::uninit();
citro3d_sys::shaderProgramInit(program.as_mut_ptr());
program.assume_init()
};
citro3d_sys::shaderProgramSetVsh(&mut program, (*vshader_dvlb).DVLE);
citro3d_sys::C3D_BindProgram(&mut program);
// Get the location of the uniforms
let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap();
let uloc_projection = citro3d_sys::shaderInstanceGetUniformLocation(
program.vertexShader,
projection_name.as_ptr(),
);
// Configure attributes for use with the vertex shader
let attr_info = citro3d_sys::C3D_GetAttrInfo();
citro3d_sys::AttrInfo_Init(attr_info);
citro3d_sys::AttrInfo_AddLoader(attr_info, 0, citro3d_sys::GPU_FLOAT, 3); // v0=position
citro3d_sys::AttrInfo_AddLoader(attr_info, 1, citro3d_sys::GPU_FLOAT, 3); // v1=color
// Compute the projection matrix
let projection = {
let mut projection = MaybeUninit::uninit();
citro3d_sys::Mtx_OrthoTilt(
projection.as_mut_ptr(),
// The 3ds top screen is a 5:3 ratio
-1.66,
1.66,
-1.0,
1.0,
0.0,
1.0,
true,
);
projection.assume_init()
};
// Create the vertex buffer object
let vbo_data: *mut Vertex = citro3d_sys::linearAlloc(
std::mem::size_of_val(&VERTICES)
.try_into()
.expect("size fits in u32"),
)
.cast();
vbo_data.copy_from(VERTICES.as_ptr(), VERTICES.len());
// Configure buffers
let buf_info = citro3d_sys::C3D_GetBufInfo();
citro3d_sys::BufInfo_Init(buf_info);
citro3d_sys::BufInfo_Add(
buf_info,
vbo_data.cast(),
std::mem::size_of::<Vertex>()
.try_into()
.expect("size of vec3 fits in u32"),
2, // Each vertex has two attributes
0x10, // v0 = position, v1 = color, in LSB->MSB nibble order
);
// Configure the first fragment shading substage to just pass through the vertex color
// See https://www.opengl.org/sdk/docs/man2/xhtml/glTexEnv.xml for more insight
let env = citro3d_sys::C3D_GetTexEnv(0);
citro3d_sys::C3D_TexEnvInit(env);
citro3d_sys::C3D_TexEnvSrc(
env,
citro3d_sys::C3D_Both,
citro3d_sys::GPU_PRIMARY_COLOR,
0,
0,
);
citro3d_sys::C3D_TexEnvFunc(env, citro3d_sys::C3D_Both, citro3d_sys::GPU_REPLACE);
(
program,
uloc_projection,
projection,
vbo_data.cast(),
vshader_dvlb,
)
}
}
fn scene_render(uloc_projection: i32, projection: &C3D_Mtx) {
unsafe {
// Update the uniforms
citro3d_sys::C3D_FVUnifMtx4x4(citro3d_sys::GPU_VERTEX_SHADER, uloc_projection, projection);
// Draw the VBO
citro3d_sys::C3D_DrawArrays(
citro3d_sys::GPU_TRIANGLES,
0,
VERTICES
.len()
.try_into()
.expect("VERTICES.len() fits in i32"),
);
}
}
fn scene_exit(
vbo_data: *mut libc::c_void,
mut program: shaderProgram_s,
vshader_dvlb: *mut DVLB_s,
) {
unsafe {
citro3d_sys::linearFree(vbo_data);
citro3d_sys::shaderProgramFree(&mut program);
citro3d_sys::DVLB_Free(vshader_dvlb);
}
}