Skip to content

Instantly share code, notes, and snippets.

@hYdos
Created November 27, 2022 21:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hYdos/20943d62a19dfbb878f747ddc3a57704 to your computer and use it in GitHub Desktop.
Save hYdos/20943d62a19dfbb878f747ddc3a57704 to your computer and use it in GitHub Desktop.
use citro3d::include_aligned_bytes;
use citro3d_sys::{
shaderProgram_s, C3D_FVec, C3D_Mtx, C3D_Tex, Mtx_RotateX, Mtx_RotateY, Mtx_Scale,
Mtx_Translate, GPU_TEXTURE_FILTER_PARAM,
};
use ctru::gfx::{Gfx, Side};
use ctru::services::apt::Apt;
use ctru::services::hid::{Hid, KeyPad};
use citro3d::render::{ClearFlags, ColorFormat, DepthFormat};
use crate::new_utils::{load_texture_from_mem, Vec2, Vec3};
use std::ffi::CString;
use std::mem::MaybeUninit;
use ctru::console::Console;
use ctru::romfs::RomFS;
mod new_utils;
#[repr(C)]
struct Vertex {
position: Vec3,
texcoord: Vec2,
normal: Vec3,
}
const VERTICES: &[Vertex] = &[
// First face (PZ)
// First triangle
Vertex {
position: Vec3::new(-0.5, -0.5, 0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(0.0, 0.0, 1.0),
},
Vertex {
position: Vec3::new(0.5, -0.5, 0.5),
texcoord: Vec2::new(1.0, 0.0),
normal: Vec3::new(0.0, 0.0, 1.0),
},
Vertex {
position: Vec3::new(0.5, 0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(0.0, 0.0, 1.0),
},
// Second triangle
Vertex {
position: Vec3::new(0.5, 0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(0.0, 0.0, 1.0),
},
Vertex {
position: Vec3::new(-0.5, 0.5, 0.5),
texcoord: Vec2::new(0.0, 1.0),
normal: Vec3::new(0.0, 0.0, 1.0),
},
Vertex {
position: Vec3::new(-0.5, -0.5, 0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(0.0, 0.0, 1.0),
},
// Second face (MZ)
// First triangle
Vertex {
position: Vec3::new(-0.5, -0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(0.0, 0.0, -1.0),
},
Vertex {
position: Vec3::new(-0.5, 0.5, -0.5),
texcoord: Vec2::new(1.0, 0.0),
normal: Vec3::new(0.0, 0.0, -1.0),
},
Vertex {
position: Vec3::new(0.5, 0.5, -0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(0.0, 0.0, -1.0),
},
// Second triangle
Vertex {
position: Vec3::new(0.5, 0.5, -0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(0.0, 0.0, -1.0),
},
Vertex {
position: Vec3::new(0.5, -0.5, -0.5),
texcoord: Vec2::new(0.0, 1.0),
normal: Vec3::new(0.0, 0.0, -1.0),
},
Vertex {
position: Vec3::new(-0.5, -0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(0.0, 0.0, -1.0),
},
// Third face (PX)
// First triangle
Vertex {
position: Vec3::new(0.5, -0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(1.0, 0.0, 0.0),
},
Vertex {
position: Vec3::new(0.5, 0.5, -0.5),
texcoord: Vec2::new(1.0, 0.0),
normal: Vec3::new(1.0, 0.0, 0.0),
},
Vertex {
position: Vec3::new(0.5, 0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(1.0, 0.0, 0.0),
},
// Second triangle
Vertex {
position: Vec3::new(0.5, 0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(1.0, 0.0, 0.0),
},
Vertex {
position: Vec3::new(0.5, -0.5, 0.5),
texcoord: Vec2::new(0.0, 1.0),
normal: Vec3::new(1.0, 0.0, 0.0),
},
Vertex {
position: Vec3::new(0.5, -0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(1.0, 0.0, 0.0),
},
// Fourth face (MX)
// First triangle
Vertex {
position: Vec3::new(-0.5, -0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(-1.0, 0.0, 0.0),
},
Vertex {
position: Vec3::new(-0.5, -0.5, 0.5),
texcoord: Vec2::new(1.0, 0.0),
normal: Vec3::new(-1.0, 0.0, 0.0),
},
Vertex {
position: Vec3::new(-0.5, 0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(-1.0, 0.0, 0.0),
},
// Second triangle
Vertex {
position: Vec3::new(-0.5, 0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(-1.0, 0.0, 0.0),
},
Vertex {
position: Vec3::new(-0.5, 0.5, -0.5),
texcoord: Vec2::new(0.0, 1.0),
normal: Vec3::new(-1.0, 0.0, 0.0),
},
Vertex {
position: Vec3::new(-0.5, -0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(-1.0, 0.0, 0.0),
},
// Fifth face (PY)
// First triangle
Vertex {
position: Vec3::new(-0.5, 0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(0.0, 1.0, 0.0),
},
Vertex {
position: Vec3::new(-0.5, 0.5, 0.5),
texcoord: Vec2::new(1.0, 0.0),
normal: Vec3::new(0.0, 1.0, 0.0),
},
Vertex {
position: Vec3::new(0.5, 0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(0.0, 1.0, 0.0),
},
// Second triangle
Vertex {
position: Vec3::new(0.5, 0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(0.0, 1.0, 0.0),
},
Vertex {
position: Vec3::new(0.5, 0.5, -0.5),
texcoord: Vec2::new(0.0, 1.0),
normal: Vec3::new(0.0, 1.0, 0.0),
},
Vertex {
position: Vec3::new(-0.5, 0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(0.0, 1.0, 0.0),
},
// Sixth face (MY)
// First triangle
Vertex {
position: Vec3::new(-0.5, -0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(0.0, -1.0, 0.0),
},
Vertex {
position: Vec3::new(0.5, -0.5, -0.5),
texcoord: Vec2::new(1.0, 0.0),
normal: Vec3::new(0.0, -1.0, 0.0),
},
Vertex {
position: Vec3::new(0.5, -0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(0.0, -1.0, 0.0),
},
// Second triangle
Vertex {
position: Vec3::new(0.5, -0.5, 0.5),
texcoord: Vec2::new(1.0, 1.0),
normal: Vec3::new(0.0, -1.0, 0.0),
},
Vertex {
position: Vec3::new(-0.5, -0.5, 0.5),
texcoord: Vec2::new(0.0, 1.0),
normal: Vec3::new(0.0, -1.0, 0.0),
},
Vertex {
position: Vec3::new(-0.5, -0.5, -0.5),
texcoord: Vec2::new(0.0, 0.0),
normal: Vec3::new(0.0, -1.0, 0.0),
},
];
const SHADER_BYTES: &[u8] = include_aligned_bytes!("../resources/shader.v.shbin");
struct UniformLocations {
projection: i32,
model_view: i32,
}
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 _romfs = RomFS::init().unwrap();
std::env::set_var("RUST_BACKTRACE", "1");
let mut top_screen = gfx.top_screen.borrow_mut();
let frame_buffer = top_screen.get_raw_framebuffer(Side::Left);
let _console = Console::init(gfx.bottom_screen.borrow_mut());
let mut instance = citro3d::Instance::new().expect("failed to initialize Citro3D");
let mut render_target = instance
.render_target_for_screen(
&frame_buffer,
ColorFormat::RGBA8,
DepthFormat::Depth24Stencil8,
)
.expect("failed to create render target");
render_target.set_output(&*top_screen, Side::Left);
let shader = unsafe {
citro3d_sys::DVLB_ParseFile(
// SAFETY: we're trusting the parse implementation doesn't mutate
// the contents of the data. From a quick read it looks like that's
// correct and it should just take a const arg in the API.
SHADER_BYTES.as_ptr() as *mut _,
SHADER_BYTES.len() as u32,
)
};
let mut program = {
let mut program = unsafe {
let mut program = MaybeUninit::uninit();
let result = citro3d_sys::shaderProgramInit(program.as_mut_ptr());
if result != 0 {
panic!("{:?}", ctru::Error::from(result));
}
program.assume_init()
};
let result = unsafe { citro3d_sys::shaderProgramSetVsh(&mut program, (*shader).DVLE) };
if result != 0 {
panic!("{:?}", ctru::Error::from(result));
}
program
};
let (uloc_projection, vbo_data) = scene_init(&mut program);
let mut angle_x = 0.0;
let mut angle_y = 0.0;
while apt.main_loop() {
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
break;
}
#[inline(always)]
pub fn C3D_AngleFromDegrees(angle: f32) -> f32 {
angle * std::f32::consts::PI / 180.0
}
angle_x += C3D_AngleFromDegrees(1.0);
angle_y += C3D_AngleFromDegrees(0.5);
instance.render_frame_with(|instance| {
let clear_color: u32 = 0x7F_7F_7F_FF;
render_target.clear(ClearFlags::ALL, clear_color, 0);
instance
.select_render_target(&render_target)
.expect("failed to set render target");
let model_view = unsafe {
let mut model_view = C3D_Mtx {
r: [
C3D_FVec {
c: [1.0, 0.0, 0.0, 0.0],
},
C3D_FVec {
c: [0.0, 1.0, 0.0, 0.0],
},
C3D_FVec {
c: [0.0, 0.0, 1.0, 0.0],
},
C3D_FVec {
c: [0.0, 0.0, 0.0, 1.0],
},
],
};
Mtx_Translate(
&mut model_view,
0.0,
0.0,
-3.0, /*+ sinf(angleX)*/
true,
);
Mtx_RotateX(&mut model_view, angle_x, true);
Mtx_RotateY(&mut model_view, angle_y, true);
Mtx_Scale(&mut model_view, 1.5, 1.5, 1.5);
model_view
};
let projection = unsafe {
let mut projection = MaybeUninit::uninit();
citro3d_sys::Mtx_PerspStereoTilt(
projection.as_mut_ptr(),
C3D_AngleFromDegrees(40.0), /* TODO: 90 */
citro3d_sys::C3D_AspectRatioTop as f32,
0.01,
1000.0,
0.0,
2.0,
false,
);
projection.assume_init()
};
scene_render(&uloc_projection, &projection, &model_view);
});
}
scene_exit(vbo_data);
}
fn scene_init(program: &mut shaderProgram_s) -> (UniformLocations, *mut libc::c_void) {
// Load the vertex shader, create a shader program and bind it
unsafe {
citro3d_sys::C3D_BindProgram(program);
// Get the location of the uniforms
let projection_name = CString::new("projection").unwrap();
let uloc_projection = citro3d_sys::shaderInstanceGetUniformLocation(
program.vertexShader,
projection_name.as_ptr(),
);
let model_view_name = CString::new("model_view").unwrap();
let uloc_model_view = citro3d_sys::shaderInstanceGetUniformLocation(
program.vertexShader,
model_view_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, 2); // v1=texcoord
citro3d_sys::AttrInfo_AddLoader(attr_info, 2, citro3d_sys::GPU_FLOAT, 3); // v2=normal
// Create the vertex buffer object
let vbo_data: *mut Vertex = citro3d_sys::linearAlloc(std::mem::size_of_val(&VERTICES)).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 Vertex fits in u32"), // TODO: why not unwrap since we know it is always true?
3, // Each vertex has three attributes
0x210, // // 4 = 0x3210, 2 = 0x10 so assuming 3 = 0x210
);
// 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
if false {
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);
} else {
let mut diffuse_tex =
load_texture_from_mem("diffuse.rgb");
let mut normal_tex =
load_texture_from_mem("normal.rgb");
pub fn C3D_TexSetFilter(
tex: &mut C3D_Tex,
mag_filter: GPU_TEXTURE_FILTER_PARAM,
min_filter: GPU_TEXTURE_FILTER_PARAM,
) {
macro_rules! gpu_texture_mag_filter {
($v:expr) => {
((($v) & 0x1) << 1)
};
}
macro_rules! gpu_texture_min_filter {
($v:expr) => {
((($v) & 0x1) << 2)
};
}
tex.param &= !(gpu_texture_mag_filter!(citro3d_sys::GPU_LINEAR)
| gpu_texture_min_filter!(citro3d_sys::GPU_LINEAR));
tex.param |=
gpu_texture_mag_filter!(mag_filter) | gpu_texture_min_filter!(min_filter);
}
C3D_TexSetFilter(
&mut diffuse_tex,
citro3d_sys::GPU_LINEAR,
citro3d_sys::GPU_NEAREST,
);
C3D_TexSetFilter(
&mut normal_tex,
citro3d_sys::GPU_LINEAR,
citro3d_sys::GPU_NEAREST,
);
citro3d_sys::C3D_TexBind(0, &mut diffuse_tex);
citro3d_sys::C3D_TexBind(1, &mut normal_tex);
}
(
UniformLocations {
projection: uloc_projection as i32,
model_view: uloc_model_view as i32,
},
vbo_data.cast(),
)
}
}
fn scene_render(uniforms: &UniformLocations, projection: &C3D_Mtx, model_view: &C3D_Mtx) {
unsafe {
// Update the uniforms
citro3d_sys::C3D_FVUnifMtx4x4(
citro3d_sys::GPU_VERTEX_SHADER,
uniforms.projection,
projection,
);
citro3d_sys::C3D_FVUnifMtx4x4(
citro3d_sys::GPU_VERTEX_SHADER,
uniforms.model_view,
model_view,
);
// 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) {
unsafe {
citro3d_sys::linearFree(vbo_data);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment