From 92925b1411479a1ee1e547c42ddb3b6c0617db9d Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Mon, 5 Feb 2024 23:49:58 +0000 Subject: [PATCH 01/22] feat: lighting basics --- citro3d/examples/assets/frag-shader.pica | 73 ++++++++ citro3d/examples/fragment-light.rs | 218 +++++++++++++++++++++++ citro3d/src/lib.rs | 14 ++ citro3d/src/light.rs | 172 ++++++++++++++++++ citro3d/src/material.rs | 35 ++++ 5 files changed, 512 insertions(+) create mode 100644 citro3d/examples/assets/frag-shader.pica create mode 100644 citro3d/examples/fragment-light.rs create mode 100644 citro3d/src/light.rs create mode 100644 citro3d/src/material.rs diff --git a/citro3d/examples/assets/frag-shader.pica b/citro3d/examples/assets/frag-shader.pica new file mode 100644 index 0000000..80e61f3 --- /dev/null +++ b/citro3d/examples/assets/frag-shader.pica @@ -0,0 +1,73 @@ +; modified version of https://github.com/devkitPro/3ds-examples/blob/ea519187782397c279609da80310e0f8c7e80f09/graphics/gpu/fragment_light/source/vshader.v.pica +; Example PICA200 vertex shader + +; Uniforms +.fvec projection[4], modelView[4] + +; Constants +.constf myconst(0.0, 1.0, -1.0, 0.5) +.alias zeros myconst.xxxx ; Vector full of zeros +.alias ones myconst.yyyy ; Vector full of ones +.alias half myconst.wwww + +; Outputs +.out outpos position +.out outtc0 texcoord0 +.out outclr color +.out outview view +.out outnq normalquat + +; Inputs (defined as aliases for convenience) +.alias inpos v0 +.alias innrm v1 + +.proc main + ; Force the w component of inpos to be 1.0 + mov r0.xyz, inpos + mov r0.w, ones + + ; r1 = modelView * inpos + dp4 r1.x, modelView[0], r0 + dp4 r1.y, modelView[1], r0 + dp4 r1.z, modelView[2], r0 + dp4 r1.w, modelView[3], r0 + + ; outview = -r1 + mov outview, -r1 + + ; outpos = projection * r1 + dp4 outpos.x, projection[0], r1 + dp4 outpos.y, projection[1], r1 + dp4 outpos.z, projection[2], r1 + dp4 outpos.w, projection[3], r1 + + ; outtex = intex + ;mov outtc0, intex + + ; Transform the normal vector with the modelView matrix + ; TODO: use a separate normal matrix that is the transpose of the inverse of modelView + dp3 r14.x, modelView[0], innrm + dp3 r14.y, modelView[1], innrm + dp3 r14.z, modelView[2], innrm + dp3 r6.x, r14, r14 + rsq r6.x, r6.x + mul r14.xyz, r14.xyz, r6.x + + mov r0, myconst.yxxx + add r4, ones, r14.z + mul r4, half, r4 + cmp zeros, ge, ge, r4.x + rsq r4, r4.x + mul r5, half, r14 + jmpc cmp.x, degenerate + + rcp r0.z, r4.x + mul r0.xy, r5, r4 + +degenerate: + mov outnq, r0 + mov outclr, ones + + ; We're finished + end +.end \ No newline at end of file diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs new file mode 100644 index 0000000..d3f7548 --- /dev/null +++ b/citro3d/examples/fragment-light.rs @@ -0,0 +1,218 @@ +#![feature(allocator_api)] +use citro3d::{ + attrib, buffer, + light::{LightEnv, LightLutId, LutData, LutInput}, + material::{Color, Material}, + math::{AspectRatio, ClipPlanes, FVec3, Matrix4, Projection, StereoDisplacement}, + render::{self, ClearFlags}, + shader, texenv, +}; +use citro3d_macros::include_shader; +use ctru::services::{ + apt::Apt, + gfx::{Gfx, RawFrameBuffer, Screen, TopScreen3D}, + hid::{Hid, KeyPad}, + soc::Soc, +}; + +#[repr(C)] +#[derive(Copy, Clone)] +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)] +#[derive(Copy, Clone)] +struct Vertex { + pos: Vec3, + normal: Vec3, +} + +static VERTICES: &[Vertex] = &[ + Vertex { + pos: Vec3::new(0.0, 0.5, -3.0), + normal: Vec3::new(0.0, 0.0, -1.0), + }, + Vertex { + pos: Vec3::new(-0.5, -0.5, -3.0), + normal: Vec3::new(0.0, 1.0, -1.0), + }, + Vertex { + pos: Vec3::new(0.5, -0.5, -3.0), + normal: Vec3::new(0.0, 0.0, -1.0), + }, +]; +static SHADER_BYTES: &[u8] = include_shader!("assets/frag-shader.pica"); + +fn main() { + let mut soc = Soc::new().expect("failed to get SOC"); + drop(soc.redirect_to_3dslink(true, true)); + + let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); + let mut hid = Hid::new().expect("Couldn't obtain HID controller"); + let apt = Apt::new().expect("Couldn't obtain APT controller"); + + let mut instance = citro3d::Instance::new().expect("failed to initialize Citro3D"); + + let top_screen = TopScreen3D::from(&gfx.top_screen); + + let (mut top_left, mut top_right) = top_screen.split_mut(); + + let RawFrameBuffer { width, height, .. } = top_left.raw_framebuffer(); + let mut top_left_target = + render::Target::new(width, height, top_left, None).expect("failed to create render target"); + + let RawFrameBuffer { width, height, .. } = top_right.raw_framebuffer(); + let mut top_right_target = render::Target::new(width, height, top_right, None) + .expect("failed to create render target"); + + let mut bottom_screen = gfx.bottom_screen.borrow_mut(); + let RawFrameBuffer { width, height, .. } = bottom_screen.raw_framebuffer(); + + let mut bottom_target = render::Target::new(width, height, bottom_screen, None) + .expect("failed to create bottom screen render target"); + + let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap(); + let vertex_shader = shader.get(0).unwrap(); + + let program = shader::Program::new(vertex_shader).unwrap(); + instance.bind_program(&program); + + let mut vbo_data = Vec::with_capacity_in(VERTICES.len(), ctru::linear::LinearAllocator); + vbo_data.extend_from_slice(VERTICES); + + let mut buf_info = buffer::Info::new(); + let (attr_info, vbo_data) = prepare_vbos(&mut buf_info, &vbo_data); + let light_env = instance.light_env_mut(); + light_env.connect_lut(LightLutId::D0, LutInput::LightNormal, LutData::phong(30.0)); + light_env.set_material(Material { + ambient: Some(Color::new(0.2, 0.2, 0.2)), + diffuse: Some(Color::new(0.4, 0.4, 0.4)), + specular0: Some(Color::new(0.8, 0.8, 0.8)), + ..Default::default() + }); + let light = light_env.create_light().unwrap(); + let light = light_env.light_mut(light).unwrap(); + light.set_color(1.0, 1.0, 1.0); + light.set_position(FVec3::new(0.0, 0.0, -4.0)); + let mut c = Matrix4::identity(); + let model_idx = program.get_uniform("modelView").unwrap(); + c.translate(0.0, 0.0, -4.0); + instance.bind_vertex_uniform(model_idx, &c); + + // 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 stage0 = texenv::Stage::new(0).unwrap(); + instance + .texenv(stage0) + .src( + texenv::Mode::BOTH, + texenv::Source::FragmentPrimaryColor, + Some(texenv::Source::FragmentSecondaryColor), + None, + ) + .func(texenv::Mode::BOTH, texenv::CombineFunc::Add); + + let projection_uniform_idx = program.get_uniform("projection").unwrap(); + + while apt.main_loop() { + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::START) { + break; + } + + instance.render_frame_with(|instance| { + let mut render_to = |target: &mut render::Target, projection| { + target.clear(ClearFlags::ALL, 0, 0); + + instance + .select_render_target(target) + .expect("failed to set render target"); + + instance.bind_vertex_uniform(projection_uniform_idx, projection); + + instance.set_attr_info(&attr_info); + + instance.draw_arrays(buffer::Primitive::Triangles, vbo_data); + }; + + let Projections { + left_eye, + right_eye, + center, + } = calculate_projections(); + + render_to(&mut top_left_target, &left_eye); + render_to(&mut top_right_target, &right_eye); + render_to(&mut bottom_target, ¢er); + }); + } +} + +fn prepare_vbos<'a>( + buf_info: &'a mut buffer::Info, + vbo_data: &'a [Vertex], +) -> (attrib::Info, buffer::Slice<'a>) { + // Configure attributes for use with the vertex shader + let mut attr_info = attrib::Info::new(); + + let reg0 = attrib::Register::new(0).unwrap(); + let reg1 = attrib::Register::new(1).unwrap(); + + attr_info + .add_loader(reg0, attrib::Format::Float, 3) + .unwrap(); + + attr_info + .add_loader(reg1, attrib::Format::Float, 3) + .unwrap(); + + let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); + + (attr_info, buf_idx) +} + +struct Projections { + left_eye: Matrix4, + right_eye: Matrix4, + center: Matrix4, +} + +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 = ctru::os::current_3d_slider_state(); + let interocular_distance = slider_val / 2.0; + + let vertical_fov = 40.0_f32.to_radians(); + let screen_depth = 2.0; + + let clip_planes = ClipPlanes { + near: 0.01, + far: 100.0, + }; + + let (left, right) = StereoDisplacement::new(interocular_distance, screen_depth); + + let (left_eye, right_eye) = + Projection::perspective(vertical_fov, AspectRatio::TopScreen, clip_planes) + .stereo_matrices(left, right); + + let center = + Projection::perspective(vertical_fov, AspectRatio::BottomScreen, clip_planes).into(); + + Projections { + left_eye, + right_eye, + center, + } +} diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 452b89d..48a2115 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -19,6 +19,8 @@ pub mod attrib; pub mod buffer; pub mod error; +pub mod light; +pub mod material; pub mod math; pub mod render; pub mod shader; @@ -27,6 +29,7 @@ pub mod uniform; use std::cell::{OnceCell, RefMut}; use std::fmt; +use std::pin::Pin; use std::rc::Rc; use ctru::services::gfx::Screen; @@ -47,6 +50,7 @@ pub mod macros { pub struct Instance { texenvs: [OnceCell; texenv::TEXENV_COUNT], queue: Rc, + light_env: Pin>, } /// Representation of `citro3d`'s internal render queue. This is something that @@ -79,6 +83,12 @@ impl Instance { #[doc(alias = "C3D_Init")] pub fn with_cmdbuf_size(size: usize) -> Result { if unsafe { citro3d_sys::C3D_Init(size) } { + let mut light_env = Pin::new(Box::new(light::LightEnv::new())); + unsafe { + // setup the light env slot, since this is a pointer copy it will stick around even with we swap + // out light_env later + citro3d_sys::C3D_LightEnvBind(light_env.as_raw_mut() as *mut _); + } Ok(Self { texenvs: [ // thank goodness there's only six of them! @@ -90,6 +100,7 @@ impl Instance { OnceCell::new(), ], queue: Rc::new(RenderQueue), + light_env, }) } else { Err(Error::FailedToInitialize) @@ -208,6 +219,9 @@ impl Instance { citro3d_sys::C3D_BindProgram(program.as_raw().cast_mut()); } } + pub fn light_env_mut(&mut self) -> &mut light::LightEnv { + &mut self.light_env + } /// Bind a uniform to the given `index` in the vertex shader for the next draw call. /// diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs new file mode 100644 index 0000000..bcd5586 --- /dev/null +++ b/citro3d/src/light.rs @@ -0,0 +1,172 @@ +//! +//! +//! What does anything in this module mean? inspect this diagram: https://raw.githubusercontent.com/wwylele/misc-3ds-diagram/master/pica-pipeline.svg + +use std::{mem::MaybeUninit, pin::Pin}; + +use crate::{material::Material, math::FVec3}; + +#[derive(Default)] +struct LightEnvStorage { + lights: [Option; 8], + luts: [Option; 6], +} + +pub struct LightEnv { + raw: citro3d_sys::C3D_LightEnv, + /// The actual light data pointed to by the lights element of `raw` + /// + /// Note this is `Pin` as well as `Box` as `raw` means we are _actually_ self-referential which + /// is horrible but the best bad option in this case + store: Pin>, +} + +pub struct Light(citro3d_sys::C3D_Light); + +impl Default for LightEnv { + fn default() -> Self { + let raw = unsafe { + let mut env = MaybeUninit::uninit(); + citro3d_sys::C3D_LightEnvInit(env.as_mut_ptr()); + env.assume_init() + }; + Self { + raw, + store: Pin::new(Default::default()), + } + } +} +impl LightEnv { + pub fn new() -> Self { + Self::default() + } + pub fn set_material(&mut self, mat: Material) { + let raw = mat.to_raw(); + // Safety: This takes a pointer but it actually memcpy's it so this doesn't dangle + unsafe { + citro3d_sys::C3D_LightEnvMaterial(self.as_raw_mut() as *mut _, (&raw) as *const _); + } + } + + pub fn lights(&self) -> [Option<&Light>; 8] { + core::array::from_fn(|i| self.store.lights[i].as_ref()) + } + + pub fn light_mut(&mut self, idx: usize) -> Option<&mut Light> { + self.store.lights[idx].as_mut() + } + pub fn create_light(&mut self) -> Option { + let idx = self + .lights() + .iter() + .enumerate() + .find(|(_, n)| n.is_none()) + .map(|(n, _)| n)?; + let light = &mut self.store.lights[idx]; + let env = &mut self.raw; + let r = unsafe { citro3d_sys::C3D_LightInit(light as *mut _ as *mut _, env as *mut _) }; + assert!(r >= 0, "C3D_LightInit should only fail if there are no free light slots but we checked that already, how did this happen?"); + assert_eq!( + r as usize, idx, + "citro3d chose a different light to us? this shouldn't be possible" + ); + Some(idx) + } + /// + pub fn connect_lut(&mut self, id: LightLutId, input: LutInput, data: LutData) { + let idx = match id { + LightLutId::D0 => Some(0), + LightLutId::D1 => Some(1), + LightLutId::SpotLightAttenuation => None, + LightLutId::Fresnel => Some(2), + LightLutId::ReflectBlue => Some(3), + LightLutId::ReflectGreen => Some(4), + LightLutId::ReflectRed => Some(5), + LightLutId::DistanceAttenuation => None, + }; + let lut = idx.map(|i| self.store.luts[i].insert(data)); + let raw = &mut self.raw; + let lut = match lut { + Some(l) => (&mut l.0) as *mut _, + None => core::ptr::null_mut(), + }; + unsafe { + citro3d_sys::C3D_LightEnvLut(raw, id as u32, input as u32, false, lut); + } + } + + pub fn as_raw(&self) -> &citro3d_sys::C3D_LightEnv { + &self.raw + } + + pub fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_LightEnv { + &mut self.raw + } +} + +impl Light { + fn from_raw_ref(l: &citro3d_sys::C3D_Light) -> &Self { + unsafe { (l as *const _ as *const Self).as_ref().unwrap() } + } + fn from_raw_mut(l: &mut citro3d_sys::C3D_Light) -> &mut Self { + unsafe { (l as *mut _ as *mut Self).as_mut().unwrap() } + } + fn as_raw(&self) -> &citro3d_sys::C3D_Light { + &self.0 + } + + fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_Light { + &mut self.0 + } + pub fn set_position(&mut self, mut p: FVec3) { + unsafe { citro3d_sys::C3D_LightPosition(self.as_raw_mut(), (&mut p.0) as *mut _) } + } + pub fn set_color(&mut self, r: f32, g: f32, b: f32) { + unsafe { citro3d_sys::C3D_LightColor(self.as_raw_mut(), r, g, b) } + } +} + +#[repr(transparent)] +pub struct LutData(citro3d_sys::C3D_LightLut); + +extern "C" fn c_powf(a: f32, b: f32) -> f32 { + a.powf(b) +} + +impl LutData { + pub fn phong(shininess: f32) -> Self { + let lut = unsafe { + let mut lut = MaybeUninit::uninit(); + citro3d_sys::LightLut_FromFunc(lut.as_mut_ptr(), Some(c_powf), shininess, false); + lut.assume_init() + }; + Self(lut) + } +} + +#[repr(u32)] +pub enum LutInput { + CosPhi = ctru_sys::GPU_LUTINPUT_CP, + /// Light vector * normal + LightNormal = ctru_sys::GPU_LUTINPUT_LN, + /// normal * half vector + NormalHalf = ctru_sys::GPU_LUTINPUT_NH, + /// normal * view + NormalView = ctru_sys::GPU_LUTINPUT_NV, + /// light * spotlight + LightSpotLight = ctru_sys::GPU_LUTINPUT_SP, + /// view * half vector + ViewHalf = ctru_sys::GPU_LUTINPUT_VH, +} + +#[repr(u32)] +pub enum LightLutId { + D0 = ctru_sys::GPU_LUT_D0, + D1 = ctru_sys::GPU_LUT_D1, + SpotLightAttenuation = ctru_sys::GPU_LUT_SP, + Fresnel = ctru_sys::GPU_LUT_FR, + ReflectBlue = ctru_sys::GPU_LUT_RB, + ReflectGreen = ctru_sys::GPU_LUT_RG, + ReflectRed = ctru_sys::GPU_LUT_RR, + DistanceAttenuation = ctru_sys::GPU_LUT_DA, +} diff --git a/citro3d/src/material.rs b/citro3d/src/material.rs new file mode 100644 index 0000000..67fa32e --- /dev/null +++ b/citro3d/src/material.rs @@ -0,0 +1,35 @@ +#[derive(Debug, Default, Clone, Copy)] +pub struct Material { + pub ambient: Option, + pub diffuse: Option, + pub specular0: Option, + pub specular1: Option, + pub emission: Option, +} +impl Material { + pub fn to_raw(self) -> citro3d_sys::C3D_Material { + citro3d_sys::C3D_Material { + ambient: self.ambient.unwrap_or_default().to_parts(), + diffuse: self.diffuse.unwrap_or_default().to_parts(), + specular0: self.specular0.unwrap_or_default().to_parts(), + specular1: self.specular1.unwrap_or_default().to_parts(), + emission: self.emission.unwrap_or_default().to_parts(), + } + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct Color { + pub r: f32, + pub g: f32, + pub b: f32, +} + +impl Color { + pub fn new(r: f32, g: f32, b: f32) -> Self { + Self { r, g, b } + } + pub fn to_parts(self) -> [f32; 3] { + [self.r, self.g, self.b] + } +} From 8be02c289f97b17e91adfe11e15ac72aaa3765e3 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Fri, 9 Feb 2024 16:28:51 +0000 Subject: [PATCH 02/22] chore: trying to get lighting to work --- citro3d/examples/fragment-light.rs | 120 +++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 24 deletions(-) diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index d3f7548..95418d0 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -36,27 +36,80 @@ struct Vertex { normal: Vec3, } -static VERTICES: &[Vertex] = &[ - Vertex { - pos: Vec3::new(0.0, 0.5, -3.0), - normal: Vec3::new(0.0, 0.0, -1.0), - }, - Vertex { - pos: Vec3::new(-0.5, -0.5, -3.0), - normal: Vec3::new(0.0, 1.0, -1.0), - }, - Vertex { - pos: Vec3::new(0.5, -0.5, -3.0), - normal: Vec3::new(0.0, 0.0, -1.0), - }, -]; +impl Vertex { + const fn new(pos: Vec3, normal: Vec3) -> Self { + Self { pos, normal } + } +} + static SHADER_BYTES: &[u8] = include_shader!("assets/frag-shader.pica"); +const VERTICES: &[Vertex] = &[ + // First face (PZ) + // First triangle + Vertex::new(Vec3::new(-0.5, -0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), + Vertex::new(Vec3::new(0.5, -0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), + Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), + // Second triangle + Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), + Vertex::new(Vec3::new(-0.5, 0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), + Vertex::new(Vec3::new(-0.5, -0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), + // Second ace (MZ) + // First triangle + Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), + Vertex::new(Vec3::new(-0.5, 0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), + Vertex::new(Vec3::new(0.5, 0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), + // Second triangle + Vertex::new(Vec3::new(0.5, 0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), + Vertex::new(Vec3::new(0.5, -0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), + Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), + // Third ace (PX) + // First triangle + Vertex::new(Vec3::new(0.5, -0.5, -0.5), Vec3::new(1.0, 0.0, 0.0)), + Vertex::new(Vec3::new(0.5, 0.5, -0.5), Vec3::new(1.0, 0.0, 0.0)), + Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(1.0, 0.0, 0.0)), + // Second triangle + Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(1.0, 0.0, 0.0)), + Vertex::new(Vec3::new(0.5, -0.5, 0.5), Vec3::new(1.0, 0.0, 0.0)), + Vertex::new(Vec3::new(0.5, -0.5, -0.5), Vec3::new(1.0, 0.0, 0.0)), + // Fourth ace (MX) + // First triangle + Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(-1.0, 0.0, 0.0)), + Vertex::new(Vec3::new(-0.5, -0.5, 0.5), Vec3::new(-1.0, 0.0, 0.0)), + Vertex::new(Vec3::new(-0.5, 0.5, 0.5), Vec3::new(-1.0, 0.0, 0.0)), + // Second triangle + Vertex::new(Vec3::new(-0.5, 0.5, 0.5), Vec3::new(-1.0, 0.0, 0.0)), + Vertex::new(Vec3::new(-0.5, 0.5, -0.5), Vec3::new(-1.0, 0.0, 0.0)), + Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(-1.0, 0.0, 0.0)), + // Fith ace (PY) + // First triangle + Vertex::new(Vec3::new(-0.5, 0.5, -0.5), Vec3::new(0.0, 1.0, 0.0)), + Vertex::new(Vec3::new(-0.5, 0.5, 0.5), Vec3::new(0.0, 1.0, 0.0)), + Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.0, 1.0, 0.0)), + // Second triangle + Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.0, 1.0, 0.0)), + Vertex::new(Vec3::new(0.5, 0.5, -0.5), Vec3::new(0.0, 1.0, 0.0)), + Vertex::new(Vec3::new(-0.5, 0.5, -0.5), Vec3::new(0.0, 1.0, 0.0)), + // Sixth ace (MY) + // First triangle + Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(0.0, -1.0, 0.0)), + Vertex::new(Vec3::new(0.5, -0.5, -0.5), Vec3::new(0.0, -1.0, 0.0)), + Vertex::new(Vec3::new(0.5, -0.5, 0.5), Vec3::new(0.0, -1.0, 0.0)), + // Second triangle + Vertex::new(Vec3::new(0.5, -0.5, 0.5), Vec3::new(0.0, -1.0, 0.0)), + Vertex::new(Vec3::new(-0.5, -0.5, 0.5), Vec3::new(0.0, -1.0, 0.0)), + Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(0.0, -1.0, 0.0)), +]; + fn main() { let mut soc = Soc::new().expect("failed to get SOC"); drop(soc.redirect_to_3dslink(true, true)); - let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); + let gfx = Gfx::with_formats_shared( + ctru::services::gspgpu::FramebufferFormat::Rgba8, + ctru::services::gspgpu::FramebufferFormat::Rgba8, + ) + .expect("Couldn't obtain GFX controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let apt = Apt::new().expect("Couldn't obtain APT controller"); @@ -67,18 +120,33 @@ fn main() { let (mut top_left, mut top_right) = top_screen.split_mut(); let RawFrameBuffer { width, height, .. } = top_left.raw_framebuffer(); - let mut top_left_target = - render::Target::new(width, height, top_left, None).expect("failed to create render target"); + let mut top_left_target = render::Target::new( + width, + height, + top_left, + Some(render::DepthFormat::Depth24Stencil8), + ) + .expect("failed to create render target"); let RawFrameBuffer { width, height, .. } = top_right.raw_framebuffer(); - let mut top_right_target = render::Target::new(width, height, top_right, None) - .expect("failed to create render target"); + let mut top_right_target = render::Target::new( + width, + height, + top_right, + Some(render::DepthFormat::Depth24Stencil8), + ) + .expect("failed to create render target"); let mut bottom_screen = gfx.bottom_screen.borrow_mut(); let RawFrameBuffer { width, height, .. } = bottom_screen.raw_framebuffer(); - let mut bottom_target = render::Target::new(width, height, bottom_screen, None) - .expect("failed to create bottom screen render target"); + let mut bottom_target = render::Target::new( + width, + height, + bottom_screen, + Some(render::DepthFormat::Depth24Stencil8), + ) + .expect("failed to create bottom screen render target"); let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap(); let vertex_shader = shader.get(0).unwrap(); @@ -95,17 +163,17 @@ fn main() { light_env.connect_lut(LightLutId::D0, LutInput::LightNormal, LutData::phong(30.0)); light_env.set_material(Material { ambient: Some(Color::new(0.2, 0.2, 0.2)), - diffuse: Some(Color::new(0.4, 0.4, 0.4)), + diffuse: Some(Color::new(1.0, 0.4, 1.0)), specular0: Some(Color::new(0.8, 0.8, 0.8)), ..Default::default() }); let light = light_env.create_light().unwrap(); let light = light_env.light_mut(light).unwrap(); light.set_color(1.0, 1.0, 1.0); - light.set_position(FVec3::new(0.0, 0.0, -4.0)); + light.set_position(FVec3::new(0.0, 0.5, -3.0)); let mut c = Matrix4::identity(); let model_idx = program.get_uniform("modelView").unwrap(); - c.translate(0.0, 0.0, -4.0); + c.translate(0.0, 0.0, -2.0); instance.bind_vertex_uniform(model_idx, &c); // Configure the first fragment shading substage to just pass through the vertex color @@ -139,6 +207,7 @@ fn main() { .expect("failed to set render target"); instance.bind_vertex_uniform(projection_uniform_idx, projection); + instance.bind_vertex_uniform(model_idx, &c); instance.set_attr_info(&attr_info); @@ -155,6 +224,9 @@ fn main() { render_to(&mut top_right_target, &right_eye); render_to(&mut bottom_target, ¢er); }); + c.translate(0.0, 0.0, 2.0); + c.rotate_y(1.0f32.to_radians()); + c.translate(0.0, 0.0, -2.0); } } From bc25e2594451b4401ab38694a986bcca93c2d059 Mon Sep 17 00:00:00 2001 From: Jhynjhiruu Date: Mon, 12 Feb 2024 01:00:36 +0000 Subject: [PATCH 03/22] Fix lighting --- citro3d/examples/fragment-light.rs | 4 ++-- citro3d/src/light.rs | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index 95418d0..ab249b4 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -3,7 +3,7 @@ use citro3d::{ attrib, buffer, light::{LightEnv, LightLutId, LutData, LutInput}, material::{Color, Material}, - math::{AspectRatio, ClipPlanes, FVec3, Matrix4, Projection, StereoDisplacement}, + math::{AspectRatio, ClipPlanes, FVec4, Matrix4, Projection, StereoDisplacement}, render::{self, ClearFlags}, shader, texenv, }; @@ -170,7 +170,7 @@ fn main() { let light = light_env.create_light().unwrap(); let light = light_env.light_mut(light).unwrap(); light.set_color(1.0, 1.0, 1.0); - light.set_position(FVec3::new(0.0, 0.5, -3.0)); + light.set_position(FVec4::new(0.0, 0.0, -0.5, 1.0)); let mut c = Matrix4::identity(); let model_idx = program.get_uniform("modelView").unwrap(); c.translate(0.0, 0.0, -2.0); diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index bcd5586..54a1dcf 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -4,7 +4,7 @@ use std::{mem::MaybeUninit, pin::Pin}; -use crate::{material::Material, math::FVec3}; +use crate::{material::Material, math::FVec4}; #[derive(Default)] struct LightEnvStorage { @@ -62,9 +62,15 @@ impl LightEnv { .enumerate() .find(|(_, n)| n.is_none()) .map(|(n, _)| n)?; - let light = &mut self.store.lights[idx]; - let env = &mut self.raw; - let r = unsafe { citro3d_sys::C3D_LightInit(light as *mut _ as *mut _, env as *mut _) }; + + self.store.lights[idx] = Some(Light(unsafe { MaybeUninit::zeroed().assume_init() })); + + let r = unsafe { + citro3d_sys::C3D_LightInit( + self.store.lights[idx].as_mut().unwrap().as_raw_mut(), + self.as_raw_mut() as *mut _, + ) + }; assert!(r >= 0, "C3D_LightInit should only fail if there are no free light slots but we checked that already, how did this happen?"); assert_eq!( r as usize, idx, @@ -118,7 +124,7 @@ impl Light { fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_Light { &mut self.0 } - pub fn set_position(&mut self, mut p: FVec3) { + pub fn set_position(&mut self, mut p: FVec4) { unsafe { citro3d_sys::C3D_LightPosition(self.as_raw_mut(), (&mut p.0) as *mut _) } } pub fn set_color(&mut self, r: f32, g: f32, b: f32) { From c1b7cf2e2569e28a6be6f2aa258231b5b581bf90 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Mon, 12 Feb 2024 13:30:55 +0000 Subject: [PATCH 04/22] chore: switch set_position back to fvec3 --- citro3d/examples/fragment-light.rs | 4 ++-- citro3d/src/light.rs | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index ab249b4..b9787be 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -3,7 +3,7 @@ use citro3d::{ attrib, buffer, light::{LightEnv, LightLutId, LutData, LutInput}, material::{Color, Material}, - math::{AspectRatio, ClipPlanes, FVec4, Matrix4, Projection, StereoDisplacement}, + math::{AspectRatio, ClipPlanes, FVec3, FVec4, Matrix4, Projection, StereoDisplacement}, render::{self, ClearFlags}, shader, texenv, }; @@ -170,7 +170,7 @@ fn main() { let light = light_env.create_light().unwrap(); let light = light_env.light_mut(light).unwrap(); light.set_color(1.0, 1.0, 1.0); - light.set_position(FVec4::new(0.0, 0.0, -0.5, 1.0)); + light.set_position(FVec3::new(0.0, 0.0, -0.5)); let mut c = Matrix4::identity(); let model_idx = program.get_uniform("modelView").unwrap(); c.translate(0.0, 0.0, -2.0); diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 54a1dcf..8ea3b22 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -4,7 +4,10 @@ use std::{mem::MaybeUninit, pin::Pin}; -use crate::{material::Material, math::FVec4}; +use crate::{ + material::Material, + math::{FVec3, FVec4}, +}; #[derive(Default)] struct LightEnvStorage { @@ -124,8 +127,9 @@ impl Light { fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_Light { &mut self.0 } - pub fn set_position(&mut self, mut p: FVec4) { - unsafe { citro3d_sys::C3D_LightPosition(self.as_raw_mut(), (&mut p.0) as *mut _) } + pub fn set_position(&mut self, p: FVec3) { + let mut p = FVec4::new(p.x(), p.y(), p.z(), 1.0); + unsafe { citro3d_sys::C3D_LightPosition(self.as_raw_mut(), &mut p.0) } } pub fn set_color(&mut self, r: f32, g: f32, b: f32) { unsafe { citro3d_sys::C3D_LightColor(self.as_raw_mut(), r, g, b) } From 3153e7d071f9384ebac22e00997011bde013cbba Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Mon, 12 Feb 2024 13:42:47 +0000 Subject: [PATCH 05/22] chore: cleanup code --- citro3d/src/light.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 8ea3b22..588093a 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -1,4 +1,4 @@ -//! +//! Bindings for accessing the lighting part of the GPU pipeline //! //! What does anything in this module mean? inspect this diagram: https://raw.githubusercontent.com/wwylele/misc-3ds-diagram/master/pica-pipeline.svg @@ -9,9 +9,29 @@ use crate::{ math::{FVec3, FVec4}, }; +/// Index for one of the 8 hardware lights in the GPU pipeline +/// +/// Usually you don't want to construct one of these directly but use [`LightEnv::create_light`] +// Note we use a u8 here since usize is overkill and it saves a few bytes +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct LightIndex(u8); + +const NB_LIGHTS: usize = 8; + +impl LightIndex { + /// Manually create a `LightIndex` with a specific index + /// + /// # Panics + /// if `idx` out of range for the number of lights (>=8) + pub fn new(idx: usize) -> Self { + assert!(idx < NB_LIGHTS); + Self(idx as u8) + } +} + #[derive(Default)] struct LightEnvStorage { - lights: [Option; 8], + lights: [Option; NB_LIGHTS], luts: [Option; 6], } @@ -55,10 +75,10 @@ impl LightEnv { core::array::from_fn(|i| self.store.lights[i].as_ref()) } - pub fn light_mut(&mut self, idx: usize) -> Option<&mut Light> { - self.store.lights[idx].as_mut() + pub fn light_mut(&mut self, idx: LightIndex) -> Option<&mut Light> { + self.store.lights[idx.0 as usize].as_mut() } - pub fn create_light(&mut self) -> Option { + pub fn create_light(&mut self) -> Option { let idx = self .lights() .iter() @@ -79,7 +99,7 @@ impl LightEnv { r as usize, idx, "citro3d chose a different light to us? this shouldn't be possible" ); - Some(idx) + Some(LightIndex::new(idx)) } /// pub fn connect_lut(&mut self, id: LightLutId, input: LutInput, data: LutData) { From e04b730fb9a568f12a9fb9f6af58cea9faca0d1a Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Mon, 12 Feb 2024 13:48:39 +0000 Subject: [PATCH 06/22] fix: Light not send + sync --- citro3d/src/light.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 588093a..9407166 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -156,6 +156,11 @@ impl Light { } } +// Safety: I am 99% sure these are safe. That 1% is if citro3d does something weird I missed +// which is not impossible +unsafe impl Send for Light {} +unsafe impl Sync for Light {} + #[repr(transparent)] pub struct LutData(citro3d_sys::C3D_LightLut); From 1502f4158428d7347b4cff92157843677519e110 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Mon, 12 Feb 2024 13:52:13 +0000 Subject: [PATCH 07/22] fix: LightEnv not send + sync --- citro3d/src/light.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 9407166..5a8b3b4 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -161,6 +161,9 @@ impl Light { unsafe impl Send for Light {} unsafe impl Sync for Light {} +unsafe impl Send for LightEnv {} +unsafe impl Sync for LightEnv {} + #[repr(transparent)] pub struct LutData(citro3d_sys::C3D_LightLut); From 3d4ba6c6da46ccc594bfc9372bf5ff122cd6d3f8 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Mon, 12 Feb 2024 14:12:47 +0000 Subject: [PATCH 08/22] fix: no uvs in example --- citro3d/examples/assets/frag-shader.pica | 9 +- citro3d/examples/fragment-light.rs | 256 ++++++++++++++++++----- 2 files changed, 205 insertions(+), 60 deletions(-) diff --git a/citro3d/examples/assets/frag-shader.pica b/citro3d/examples/assets/frag-shader.pica index 80e61f3..79d357b 100644 --- a/citro3d/examples/assets/frag-shader.pica +++ b/citro3d/examples/assets/frag-shader.pica @@ -12,14 +12,15 @@ ; Outputs .out outpos position -.out outtc0 texcoord0 +.out outtex texcoord0 .out outclr color .out outview view .out outnq normalquat ; Inputs (defined as aliases for convenience) -.alias inpos v0 -.alias innrm v1 +.in inpos +.in innrm +.in intex .proc main ; Force the w component of inpos to be 1.0 @@ -42,7 +43,7 @@ dp4 outpos.w, projection[3], r1 ; outtex = intex - ;mov outtc0, intex + mov outtex, intex ; Transform the normal vector with the modelView matrix ; TODO: use a separate normal matrix that is the transpose of the inverse of modelView diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index b9787be..6e75d34 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -28,77 +28,216 @@ impl Vec3 { Self { x, y, z } } } +#[derive(Copy, Clone)] +#[repr(C)] +struct Vec2 { + x: f32, + y: f32, +} + +impl Vec2 { + const fn new(x: f32, y: f32) -> Self { + Self { x, y } + } +} #[repr(C)] #[derive(Copy, Clone)] struct Vertex { pos: Vec3, normal: Vec3, + uv: Vec2, } impl Vertex { - const fn new(pos: Vec3, normal: Vec3) -> Self { - Self { pos, normal } + const fn new(pos: Vec3, normal: Vec3, uv: Vec2) -> Self { + Self { pos, normal, uv } } } static SHADER_BYTES: &[u8] = include_shader!("assets/frag-shader.pica"); const VERTICES: &[Vertex] = &[ - // First face (PZ) - // First triangle - Vertex::new(Vec3::new(-0.5, -0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), - Vertex::new(Vec3::new(0.5, -0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), - Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), - // Second triangle - Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), - Vertex::new(Vec3::new(-0.5, 0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), - Vertex::new(Vec3::new(-0.5, -0.5, 0.5), Vec3::new(0.0, 0.0, 1.0)), - // Second ace (MZ) - // First triangle - Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), - Vertex::new(Vec3::new(-0.5, 0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), - Vertex::new(Vec3::new(0.5, 0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), - // Second triangle - Vertex::new(Vec3::new(0.5, 0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), - Vertex::new(Vec3::new(0.5, -0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), - Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(0.0, 0.0, -1.0)), - // Third ace (PX) - // First triangle - Vertex::new(Vec3::new(0.5, -0.5, -0.5), Vec3::new(1.0, 0.0, 0.0)), - Vertex::new(Vec3::new(0.5, 0.5, -0.5), Vec3::new(1.0, 0.0, 0.0)), - Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(1.0, 0.0, 0.0)), - // Second triangle - Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(1.0, 0.0, 0.0)), - Vertex::new(Vec3::new(0.5, -0.5, 0.5), Vec3::new(1.0, 0.0, 0.0)), - Vertex::new(Vec3::new(0.5, -0.5, -0.5), Vec3::new(1.0, 0.0, 0.0)), - // Fourth ace (MX) - // First triangle - Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(-1.0, 0.0, 0.0)), - Vertex::new(Vec3::new(-0.5, -0.5, 0.5), Vec3::new(-1.0, 0.0, 0.0)), - Vertex::new(Vec3::new(-0.5, 0.5, 0.5), Vec3::new(-1.0, 0.0, 0.0)), - // Second triangle - Vertex::new(Vec3::new(-0.5, 0.5, 0.5), Vec3::new(-1.0, 0.0, 0.0)), - Vertex::new(Vec3::new(-0.5, 0.5, -0.5), Vec3::new(-1.0, 0.0, 0.0)), - Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(-1.0, 0.0, 0.0)), - // Fith ace (PY) - // First triangle - Vertex::new(Vec3::new(-0.5, 0.5, -0.5), Vec3::new(0.0, 1.0, 0.0)), - Vertex::new(Vec3::new(-0.5, 0.5, 0.5), Vec3::new(0.0, 1.0, 0.0)), - Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.0, 1.0, 0.0)), - // Second triangle - Vertex::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.0, 1.0, 0.0)), - Vertex::new(Vec3::new(0.5, 0.5, -0.5), Vec3::new(0.0, 1.0, 0.0)), - Vertex::new(Vec3::new(-0.5, 0.5, -0.5), Vec3::new(0.0, 1.0, 0.0)), - // Sixth ace (MY) - // First triangle - Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(0.0, -1.0, 0.0)), - Vertex::new(Vec3::new(0.5, -0.5, -0.5), Vec3::new(0.0, -1.0, 0.0)), - Vertex::new(Vec3::new(0.5, -0.5, 0.5), Vec3::new(0.0, -1.0, 0.0)), - // Second triangle - Vertex::new(Vec3::new(0.5, -0.5, 0.5), Vec3::new(0.0, -1.0, 0.0)), - Vertex::new(Vec3::new(-0.5, -0.5, 0.5), Vec3::new(0.0, -1.0, 0.0)), - Vertex::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(0.0, -1.0, 0.0)), + Vertex::new( + Vec3::new(-0.5, -0.5, 0.5), + Vec3::new(0.0, 0.0, 1.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(0.5, -0.5, 0.5), + Vec3::new(0.0, 0.0, 1.0), + Vec2::new(1.0, 0.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, 0.5), + Vec3::new(0.0, 0.0, 1.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, 0.5), + Vec3::new(0.0, 0.0, 1.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(-0.5, 0.5, 0.5), + Vec3::new(0.0, 0.0, 1.0), + Vec2::new(0.0, 1.0), + ), + Vertex::new( + Vec3::new(-0.5, -0.5, 0.5), + Vec3::new(0.0, 0.0, 1.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(-0.5, -0.5, -0.5), + Vec3::new(0.0, 0.0, -1.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(-0.5, 0.5, -0.5), + Vec3::new(0.0, 0.0, -1.0), + Vec2::new(1.0, 0.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, -0.5), + Vec3::new(0.0, 0.0, -1.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, -0.5), + Vec3::new(0.0, 0.0, -1.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(0.5, -0.5, -0.5), + Vec3::new(0.0, 0.0, -1.0), + Vec2::new(0.0, 1.0), + ), + Vertex::new( + Vec3::new(-0.5, -0.5, -0.5), + Vec3::new(0.0, 0.0, -1.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(0.5, -0.5, -0.5), + Vec3::new(1.0, 0.0, 0.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, -0.5), + Vec3::new(1.0, 0.0, 0.0), + Vec2::new(1.0, 0.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, 0.5), + Vec3::new(1.0, 0.0, 0.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, 0.5), + Vec3::new(1.0, 0.0, 0.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(0.5, -0.5, 0.5), + Vec3::new(1.0, 0.0, 0.0), + Vec2::new(0.0, 1.0), + ), + Vertex::new( + Vec3::new(0.5, -0.5, -0.5), + Vec3::new(1.0, 0.0, 0.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(-0.5, -0.5, -0.5), + Vec3::new(-1.0, 0.0, 0.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(-0.5, -0.5, 0.5), + Vec3::new(-1.0, 0.0, 0.0), + Vec2::new(1.0, 0.0), + ), + Vertex::new( + Vec3::new(-0.5, 0.5, 0.5), + Vec3::new(-1.0, 0.0, 0.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(-0.5, 0.5, 0.5), + Vec3::new(-1.0, 0.0, 0.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(-0.5, 0.5, -0.5), + Vec3::new(-1.0, 0.0, 0.0), + Vec2::new(0.0, 1.0), + ), + Vertex::new( + Vec3::new(-0.5, -0.5, -0.5), + Vec3::new(-1.0, 0.0, 0.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(-0.5, 0.5, -0.5), + Vec3::new(0.0, 1.0, 0.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(-0.5, 0.5, 0.5), + Vec3::new(0.0, 1.0, 0.0), + Vec2::new(1.0, 0.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, 0.5), + Vec3::new(0.0, 1.0, 0.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, 0.5), + Vec3::new(0.0, 1.0, 0.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(0.5, 0.5, -0.5), + Vec3::new(0.0, 1.0, 0.0), + Vec2::new(0.0, 1.0), + ), + Vertex::new( + Vec3::new(-0.5, 0.5, -0.5), + Vec3::new(0.0, 1.0, 0.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(-0.5, -0.5, -0.5), + Vec3::new(0.0, -1.0, 0.0), + Vec2::new(0.0, 0.0), + ), + Vertex::new( + Vec3::new(0.5, -0.5, -0.5), + Vec3::new(0.0, -1.0, 0.0), + Vec2::new(1.0, 0.0), + ), + Vertex::new( + Vec3::new(0.5, -0.5, 0.5), + Vec3::new(0.0, -1.0, 0.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(0.5, -0.5, 0.5), + Vec3::new(0.0, -1.0, 0.0), + Vec2::new(1.0, 1.0), + ), + Vertex::new( + Vec3::new(-0.5, -0.5, 0.5), + Vec3::new(0.0, -1.0, 0.0), + Vec2::new(0.0, 1.0), + ), + Vertex::new( + Vec3::new(-0.5, -0.5, -0.5), + Vec3::new(0.0, -1.0, 0.0), + Vec2::new(0.0, 0.0), + ), ]; fn main() { @@ -239,6 +378,7 @@ fn prepare_vbos<'a>( let reg0 = attrib::Register::new(0).unwrap(); let reg1 = attrib::Register::new(1).unwrap(); + let reg2 = attrib::Register::new(2).unwrap(); attr_info .add_loader(reg0, attrib::Format::Float, 3) @@ -248,6 +388,10 @@ fn prepare_vbos<'a>( .add_loader(reg1, attrib::Format::Float, 3) .unwrap(); + attr_info + .add_loader(reg2, attrib::Format::Float, 2) + .unwrap(); + let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); (attr_info, buf_idx) From eeb3c963581f6b7caf90b1864ea36c271063376d Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Mon, 12 Feb 2024 17:36:26 +0000 Subject: [PATCH 09/22] feat: add couple more bindings for Light --- citro3d/src/light.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 5a8b3b4..1ba7a62 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -154,6 +154,14 @@ impl Light { pub fn set_color(&mut self, r: f32, g: f32, b: f32) { unsafe { citro3d_sys::C3D_LightColor(self.as_raw_mut(), r, g, b) } } + #[doc(alias = "C3D_LightEnable")] + pub fn set_enabled(&mut self, enabled: bool) { + unsafe { citro3d_sys::C3D_LightEnable(self.as_raw_mut(), enabled) } + } + #[doc(alias = "C3D_LightShadowEnable")] + pub fn set_shadow(&mut self, shadow: bool) { + unsafe { citro3d_sys::C3D_LightShadowEnable(self.as_raw_mut(), shadow) } + } } // Safety: I am 99% sure these are safe. That 1% is if citro3d does something weird I missed From 214312cd9b6cde46f876ab5568c9a2d558dde94e Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Mon, 12 Feb 2024 19:58:51 +0000 Subject: [PATCH 10/22] fix: soundness issues with pinned lights --- citro3d/Cargo.toml | 1 + citro3d/examples/fragment-light.rs | 16 ++-- citro3d/src/lib.rs | 8 +- citro3d/src/light.rs | 125 +++++++++++++++++++++-------- 4 files changed, 107 insertions(+), 43 deletions(-) diff --git a/citro3d/Cargo.toml b/citro3d/Cargo.toml index b96ca1e..e0772ec 100644 --- a/citro3d/Cargo.toml +++ b/citro3d/Cargo.toml @@ -16,6 +16,7 @@ ctru-rs = { git = "https://github.com/rust3ds/ctru-rs.git" } ctru-sys = { git = "https://github.com/rust3ds/ctru-rs.git" } document-features = "0.2.7" libc = "0.2.125" +pin_array = { version = "0.1.0" } [features] default = ["glam"] diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index 6e75d34..50349fb 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -298,18 +298,20 @@ fn main() { let mut buf_info = buffer::Info::new(); let (attr_info, vbo_data) = prepare_vbos(&mut buf_info, &vbo_data); - let light_env = instance.light_env_mut(); - light_env.connect_lut(LightLutId::D0, LutInput::LightNormal, LutData::phong(30.0)); - light_env.set_material(Material { + let mut light_env = instance.light_env_mut(); + light_env + .as_mut() + .connect_lut(LightLutId::D0, LutInput::LightNormal, LutData::phong(30.0)); + light_env.as_mut().set_material(Material { ambient: Some(Color::new(0.2, 0.2, 0.2)), diffuse: Some(Color::new(1.0, 0.4, 1.0)), specular0: Some(Color::new(0.8, 0.8, 0.8)), ..Default::default() }); - let light = light_env.create_light().unwrap(); - let light = light_env.light_mut(light).unwrap(); - light.set_color(1.0, 1.0, 1.0); - light.set_position(FVec3::new(0.0, 0.0, -0.5)); + let light = light_env.as_mut().create_light().unwrap(); + let mut light = light_env.as_mut().light_mut(light).unwrap(); + light.as_mut().set_color(1.0, 1.0, 1.0); + light.as_mut().set_position(FVec3::new(0.0, 0.0, -0.5)); let mut c = Matrix4::identity(); let model_idx = program.get_uniform("modelView").unwrap(); c.translate(0.0, 0.0, -2.0); diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 48a2115..0742ae6 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -83,11 +83,11 @@ impl Instance { #[doc(alias = "C3D_Init")] pub fn with_cmdbuf_size(size: usize) -> Result { if unsafe { citro3d_sys::C3D_Init(size) } { - let mut light_env = Pin::new(Box::new(light::LightEnv::new())); + let mut light_env = Box::pin(light::LightEnv::new()); unsafe { // setup the light env slot, since this is a pointer copy it will stick around even with we swap // out light_env later - citro3d_sys::C3D_LightEnvBind(light_env.as_raw_mut() as *mut _); + citro3d_sys::C3D_LightEnvBind(light_env.as_mut().as_raw_mut()); } Ok(Self { texenvs: [ @@ -219,8 +219,8 @@ impl Instance { citro3d_sys::C3D_BindProgram(program.as_raw().cast_mut()); } } - pub fn light_env_mut(&mut self) -> &mut light::LightEnv { - &mut self.light_env + pub fn light_env_mut(&mut self) -> Pin<&mut light::LightEnv> { + self.light_env.as_mut() } /// Bind a uniform to the given `index` in the vertex shader for the next draw call. diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 1ba7a62..8dcd5d0 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -2,7 +2,9 @@ //! //! What does anything in this module mean? inspect this diagram: https://raw.githubusercontent.com/wwylele/misc-3ds-diagram/master/pica-pipeline.svg -use std::{mem::MaybeUninit, pin::Pin}; +use std::{marker::PhantomPinned, mem::MaybeUninit, pin::Pin}; + +use pin_array::PinArray; use crate::{ material::Material, @@ -27,12 +29,29 @@ impl LightIndex { assert!(idx < NB_LIGHTS); Self(idx as u8) } + pub fn as_usize(self) -> usize { + self.0 as usize + } +} + +#[derive(Default)] +struct LightLutStorage { + spot: Option, + diffuse_atten: Option, + _pin: PhantomPinned, } #[derive(Default)] struct LightEnvStorage { lights: [Option; NB_LIGHTS], luts: [Option; 6], + _pin: PhantomPinned, +} + +impl LightEnvStorage { + fn lights_mut(self: Pin<&mut Self>) -> Pin<&mut [Option; NB_LIGHTS]> { + unsafe { Pin::map_unchecked_mut(self, |s| &mut s.lights) } + } } pub struct LightEnv { @@ -41,21 +60,30 @@ pub struct LightEnv { /// /// Note this is `Pin` as well as `Box` as `raw` means we are _actually_ self-referential which /// is horrible but the best bad option in this case - store: Pin>, + lights: LightArray, + luts: [Option; 6], + _pin: PhantomPinned, } -pub struct Light(citro3d_sys::C3D_Light); +pub struct Light { + raw: citro3d_sys::C3D_Light, + spot: Option, + diffuse_atten: Option, + _pin: PhantomPinned, +} impl Default for LightEnv { fn default() -> Self { let raw = unsafe { - let mut env = MaybeUninit::uninit(); + let mut env = MaybeUninit::zeroed(); citro3d_sys::C3D_LightEnvInit(env.as_mut_ptr()); env.assume_init() }; Self { raw, - store: Pin::new(Default::default()), + lights: Default::default(), + luts: Default::default(), + _pin: Default::default(), } } } @@ -63,7 +91,7 @@ impl LightEnv { pub fn new() -> Self { Self::default() } - pub fn set_material(&mut self, mat: Material) { + pub fn set_material(self: Pin<&mut Self>, mat: Material) { let raw = mat.to_raw(); // Safety: This takes a pointer but it actually memcpy's it so this doesn't dangle unsafe { @@ -71,14 +99,21 @@ impl LightEnv { } } - pub fn lights(&self) -> [Option<&Light>; 8] { - core::array::from_fn(|i| self.store.lights[i].as_ref()) + pub fn lights(&self) -> &LightArray { + &self.lights } - pub fn light_mut(&mut self, idx: LightIndex) -> Option<&mut Light> { - self.store.lights[idx.0 as usize].as_mut() + pub fn lights_mut(self: Pin<&mut Self>) -> Pin<&mut LightArray> { + unsafe { self.map_unchecked_mut(|s| &mut s.lights) } } - pub fn create_light(&mut self) -> Option { + + pub fn light_mut(self: Pin<&mut Self>, idx: LightIndex) -> Option> { + self.lights_mut() + .get_pin(idx.0 as usize) + .unwrap() + .as_pin_mut() + } + pub fn create_light(mut self: Pin<&mut Self>) -> Option { let idx = self .lights() .iter() @@ -86,14 +121,23 @@ impl LightEnv { .find(|(_, n)| n.is_none()) .map(|(n, _)| n)?; - self.store.lights[idx] = Some(Light(unsafe { MaybeUninit::zeroed().assume_init() })); + self.as_mut() + .lights_mut() + .get_pin(idx) + .unwrap() + .set(Some(Light::new(unsafe { + MaybeUninit::zeroed().assume_init() + }))); - let r = unsafe { - citro3d_sys::C3D_LightInit( - self.store.lights[idx].as_mut().unwrap().as_raw_mut(), - self.as_raw_mut() as *mut _, - ) + let target = unsafe { + self.as_mut() + .lights_mut() + .get_pin(idx) + .unwrap() + .map_unchecked_mut(|p| p.as_mut().unwrap()) }; + let r = + unsafe { citro3d_sys::C3D_LightInit(target.as_raw_mut(), self.as_raw_mut() as *mut _) }; assert!(r >= 0, "C3D_LightInit should only fail if there are no free light slots but we checked that already, how did this happen?"); assert_eq!( r as usize, idx, @@ -102,7 +146,7 @@ impl LightEnv { Some(LightIndex::new(idx)) } /// - pub fn connect_lut(&mut self, id: LightLutId, input: LutInput, data: LutData) { + pub fn connect_lut(mut self: Pin<&mut Self>, id: LightLutId, input: LutInput, data: LutData) { let idx = match id { LightLutId::D0 => Some(0), LightLutId::D1 => Some(1), @@ -113,11 +157,17 @@ impl LightEnv { LightLutId::ReflectRed => Some(5), LightLutId::DistanceAttenuation => None, }; - let lut = idx.map(|i| self.store.luts[i].insert(data)); - let raw = &mut self.raw; - let lut = match lut { - Some(l) => (&mut l.0) as *mut _, - None => core::ptr::null_mut(), + let (raw, lut) = unsafe { + // this is needed to do structural borrowing as otherwise + // the compiler rejects the reborrow needed with the pin + let me = self.as_mut().get_unchecked_mut(); + let lut = idx.map(|i| me.luts[i].insert(data)); + let raw = &mut me.raw; + let lut = match lut { + Some(l) => (&mut l.0) as *mut _, + None => core::ptr::null_mut(), + }; + (raw, lut) }; unsafe { citro3d_sys::C3D_LightEnvLut(raw, id as u32, input as u32, false, lut); @@ -128,12 +178,21 @@ impl LightEnv { &self.raw } - pub fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_LightEnv { - &mut self.raw + pub fn as_raw_mut(self: Pin<&mut Self>) -> &mut citro3d_sys::C3D_LightEnv { + unsafe { &mut self.get_unchecked_mut().raw } } } impl Light { + fn new(raw: citro3d_sys::C3D_Light) -> Self { + Self { + raw, + spot: Default::default(), + diffuse_atten: Default::default(), + _pin: Default::default(), + } + } + fn from_raw_ref(l: &citro3d_sys::C3D_Light) -> &Self { unsafe { (l as *const _ as *const Self).as_ref().unwrap() } } @@ -141,25 +200,25 @@ impl Light { unsafe { (l as *mut _ as *mut Self).as_mut().unwrap() } } fn as_raw(&self) -> &citro3d_sys::C3D_Light { - &self.0 + &self.raw } - fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_Light { - &mut self.0 + fn as_raw_mut(self: Pin<&mut Self>) -> &mut citro3d_sys::C3D_Light { + unsafe { &mut self.get_unchecked_mut().raw } } - pub fn set_position(&mut self, p: FVec3) { + pub fn set_position(self: Pin<&mut Self>, p: FVec3) { let mut p = FVec4::new(p.x(), p.y(), p.z(), 1.0); unsafe { citro3d_sys::C3D_LightPosition(self.as_raw_mut(), &mut p.0) } } - pub fn set_color(&mut self, r: f32, g: f32, b: f32) { + pub fn set_color(self: Pin<&mut Self>, r: f32, g: f32, b: f32) { unsafe { citro3d_sys::C3D_LightColor(self.as_raw_mut(), r, g, b) } } #[doc(alias = "C3D_LightEnable")] - pub fn set_enabled(&mut self, enabled: bool) { + pub fn set_enabled(self: Pin<&mut Self>, enabled: bool) { unsafe { citro3d_sys::C3D_LightEnable(self.as_raw_mut(), enabled) } } #[doc(alias = "C3D_LightShadowEnable")] - pub fn set_shadow(&mut self, shadow: bool) { + pub fn set_shadow(self: Pin<&mut Self>, shadow: bool) { unsafe { citro3d_sys::C3D_LightShadowEnable(self.as_raw_mut(), shadow) } } } @@ -216,3 +275,5 @@ pub enum LightLutId { ReflectRed = ctru_sys::GPU_LUT_RR, DistanceAttenuation = ctru_sys::GPU_LUT_DA, } + +type LightArray = PinArray, NB_LIGHTS>; From fa06debb7b582897ce22cb0dd7578578fb59fe3e Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Tue, 13 Feb 2024 00:29:17 +0000 Subject: [PATCH 11/22] feat: LutData from rust fn --- citro3d/examples/fragment-light.rs | 15 +++++-- citro3d/src/light.rs | 63 +++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index 50349fb..47ed5d0 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -241,6 +241,13 @@ const VERTICES: &[Vertex] = &[ ]; fn main() { + { + let prev = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + std::fs::write("panic.log", info.to_string()); + prev(info); + })); + } let mut soc = Soc::new().expect("failed to get SOC"); drop(soc.redirect_to_3dslink(true, true)); @@ -299,9 +306,11 @@ fn main() { let mut buf_info = buffer::Info::new(); let (attr_info, vbo_data) = prepare_vbos(&mut buf_info, &vbo_data); let mut light_env = instance.light_env_mut(); - light_env - .as_mut() - .connect_lut(LightLutId::D0, LutInput::LightNormal, LutData::phong(30.0)); + light_env.as_mut().connect_lut( + LightLutId::D0, + LutInput::LightNormal, + LutData::from_fn(|i| i.powf(30.0), false), + ); light_env.as_mut().set_material(Material { ambient: Some(Color::new(0.2, 0.2, 0.2)), diffuse: Some(Color::new(1.0, 0.4, 1.0)), diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 8dcd5d0..3013daa 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -231,15 +231,53 @@ unsafe impl Sync for Light {} unsafe impl Send for LightEnv {} unsafe impl Sync for LightEnv {} +#[derive(Clone, Copy, Debug)] #[repr(transparent)] pub struct LutData(citro3d_sys::C3D_LightLut); +impl PartialEq for LutData { + fn eq(&self, other: &Self) -> bool { + self.0.data == other.0.data + } +} +impl Eq for LutData {} + +#[cfg(test)] extern "C" fn c_powf(a: f32, b: f32) -> f32 { a.powf(b) } +const LUT_SZ: usize = 512; + impl LutData { - pub fn phong(shininess: f32) -> Self { + pub fn from_fn(mut f: impl FnMut(f32) -> f32, negative: bool) -> Self { + let base: i32 = 128; + let diff = if negative { 0 } else { base }; + let min = -128 + diff; + let max = base + diff; + assert_eq!(min.abs_diff(max), 2 * base as u32); + let mut data = [0.0f32; LUT_SZ]; + for i in min..=max { + let x = i as f32 / max as f32; + let v = f(x); + let idx = if negative { i & 0xFF } else { i } as usize; + if i < max { + data[idx] = v; + } + if i > min { + data[idx + 255] = v - data[idx - 1]; + } + } + let lut = unsafe { + let mut lut = MaybeUninit::zeroed(); + citro3d_sys::LightLut_FromArray(lut.as_mut_ptr(), data.as_mut_ptr()); + lut.assume_init() + }; + Self(lut) + } + + #[cfg(test)] + fn phong_citro3d(shininess: f32) -> Self { let lut = unsafe { let mut lut = MaybeUninit::uninit(); citro3d_sys::LightLut_FromFunc(lut.as_mut_ptr(), Some(c_powf), shininess, false); @@ -249,18 +287,19 @@ impl LutData { } } +/// This is used to decide what the input should be to a [`LutData`] #[repr(u32)] pub enum LutInput { CosPhi = ctru_sys::GPU_LUTINPUT_CP, - /// Light vector * normal + /// Dot product of the light and normal vectors LightNormal = ctru_sys::GPU_LUTINPUT_LN, - /// normal * half vector + /// Half the normal NormalHalf = ctru_sys::GPU_LUTINPUT_NH, - /// normal * view + /// Dot product of the view and normal NormalView = ctru_sys::GPU_LUTINPUT_NV, - /// light * spotlight + /// Dot product of the spotlight colour and light vector LightSpotLight = ctru_sys::GPU_LUTINPUT_SP, - /// view * half vector + /// Half the view vector ViewHalf = ctru_sys::GPU_LUTINPUT_VH, } @@ -277,3 +316,15 @@ pub enum LightLutId { } type LightArray = PinArray, NB_LIGHTS>; + +#[cfg(test)] +mod tests { + use super::LutData; + + #[test] + fn lut_data_phong_matches_for_own_and_citro3d() { + let c3d = LutData::phong_citro3d(30.0); + let rs = LutData::from_fn(|i| i.powf(30.0), false); + assert_eq!(c3d, rs); + } +} From dcb6a53633f3ea743f68015867eae8aa65a173cf Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Tue, 13 Feb 2024 01:32:21 +0000 Subject: [PATCH 12/22] feat: fresnel selector --- citro3d/examples/fragment-light.rs | 4 ++-- citro3d/src/light.rs | 33 +++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index 47ed5d0..8ec084c 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -1,7 +1,7 @@ #![feature(allocator_api)] use citro3d::{ attrib, buffer, - light::{LightEnv, LightLutId, LutData, LutInput}, + light::{FresnelSelector, LightEnv, LightLutId, LutData, LutInput}, material::{Color, Material}, math::{AspectRatio, ClipPlanes, FVec3, FVec4, Matrix4, Projection, StereoDisplacement}, render::{self, ClearFlags}, @@ -309,7 +309,7 @@ fn main() { light_env.as_mut().connect_lut( LightLutId::D0, LutInput::LightNormal, - LutData::from_fn(|i| i.powf(30.0), false), + LutData::from_fn(|v| v.powf(10.0), false), ); light_env.as_mut().set_material(Material { ambient: Some(Color::new(0.2, 0.2, 0.2)), diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 3013daa..46d3992 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -1,6 +1,23 @@ //! Bindings for accessing the lighting part of the GPU pipeline //! -//! What does anything in this module mean? inspect this diagram: https://raw.githubusercontent.com/wwylele/misc-3ds-diagram/master/pica-pipeline.svg +//! The hardware at play is shown in [this diagram][hardware], you should probably have +//! it open as a reference for the documentation in this module. +//! +//! # Hardware lights +//! There are 8 lights in the GPU's pipeline each of which have 4 colour fields and 1 spotlight colour, +//! you can set all of them at once with [`LightEnv::set_material`]. When rendering for example you call +//! `set_material` in your preparation code before the actual draw call. +//! +//! For things like specular lighting we need to go a bit deeper +//! +//! # LUTS +//! LUTS are lookup tables, in this case for the GPU. They are created ahead of time and stored in [`LutData`]'s, +//! [`LutData::from_fn`] essentially memoises the given function with the input changing depending on what +//! input it is bound to when setting it on the [`LightEnv`]. +//! +//! +//! +//! [hardware]: https://raw.githubusercontent.com/wwylele/misc-3ds-diagram/master/pica-pipeline.svg use std::{marker::PhantomPinned, mem::MaybeUninit, pin::Pin}; @@ -173,6 +190,9 @@ impl LightEnv { citro3d_sys::C3D_LightEnvLut(raw, id as u32, input as u32, false, lut); } } + pub fn set_fresnel(mut self: Pin<&mut Self>, sel: FresnelSelector) { + unsafe { citro3d_sys::C3D_LightEnvFresnel(self.as_raw_mut(), sel as _) } + } pub fn as_raw(&self) -> &citro3d_sys::C3D_LightEnv { &self.raw @@ -314,6 +334,17 @@ pub enum LightLutId { ReflectRed = ctru_sys::GPU_LUT_RR, DistanceAttenuation = ctru_sys::GPU_LUT_DA, } +#[repr(u32)] +pub enum FresnelSelector { + /// No fresnel selection + None = ctru_sys::GPU_NO_FRESNEL, + /// Use as selector for primary colour unit alpha + PrimaryAlpha = ctru_sys::GPU_PRI_ALPHA_FRESNEL, + /// Use as selector for secondary colour unit alpha + SecondaryAlpha = ctru_sys::GPU_SEC_ALPHA_FRESNEL, + /// Use as selector for both colour units + Both = ctru_sys::GPU_PRI_SEC_ALPHA_FRESNEL, +} type LightArray = PinArray, NB_LIGHTS>; From 085ab595ec9f23c178b726c61d0ed0c8b229820f Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 15 Feb 2024 12:35:06 +0000 Subject: [PATCH 13/22] fix: red and blue channels swapped in material rendering --- citro3d/src/material.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/citro3d/src/material.rs b/citro3d/src/material.rs index 67fa32e..08dbf79 100644 --- a/citro3d/src/material.rs +++ b/citro3d/src/material.rs @@ -9,15 +9,16 @@ pub struct Material { impl Material { pub fn to_raw(self) -> citro3d_sys::C3D_Material { citro3d_sys::C3D_Material { - ambient: self.ambient.unwrap_or_default().to_parts(), - diffuse: self.diffuse.unwrap_or_default().to_parts(), - specular0: self.specular0.unwrap_or_default().to_parts(), - specular1: self.specular1.unwrap_or_default().to_parts(), - emission: self.emission.unwrap_or_default().to_parts(), + ambient: self.ambient.unwrap_or_default().to_parts_bgr(), + diffuse: self.diffuse.unwrap_or_default().to_parts_bgr(), + specular0: self.specular0.unwrap_or_default().to_parts_bgr(), + specular1: self.specular1.unwrap_or_default().to_parts_bgr(), + emission: self.emission.unwrap_or_default().to_parts_bgr(), } } } +/// RGB color in linear space ([0, 1]) #[derive(Debug, Default, Clone, Copy)] pub struct Color { pub r: f32, @@ -29,7 +30,12 @@ impl Color { pub fn new(r: f32, g: f32, b: f32) -> Self { Self { r, g, b } } - pub fn to_parts(self) -> [f32; 3] { - [self.r, self.g, self.b] + /// Split into BGR ordered parts + /// + /// # Reason for existence + /// The C version of [`Material`] expects colours in BGR order (don't ask why it is beyond my comprehension) + /// so we have to reorder when converting + pub fn to_parts_bgr(self) -> [f32; 3] { + [self.b, self.g, self.r] } } From 1fcbb44d7812247ed0fbf7336d171eee730b639f Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 15 Feb 2024 12:50:22 +0000 Subject: [PATCH 14/22] feat: add disconnect lut --- citro3d/src/light.rs | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 46d3992..76cccf5 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -162,9 +162,8 @@ impl LightEnv { ); Some(LightIndex::new(idx)) } - /// - pub fn connect_lut(mut self: Pin<&mut Self>, id: LightLutId, input: LutInput, data: LutData) { - let idx = match id { + fn lut_id_to_index(id: LightLutId) -> Option { + match id { LightLutId::D0 => Some(0), LightLutId::D1 => Some(1), LightLutId::SpotLightAttenuation => None, @@ -173,7 +172,35 @@ impl LightEnv { LightLutId::ReflectGreen => Some(4), LightLutId::ReflectRed => Some(5), LightLutId::DistanceAttenuation => None, - }; + } + } + /// Attempt to disconnect a light lut + /// + /// # Note + /// This function will not panic if the lut does not exist for `id` and `input`, it will just return `None` + pub fn disconnect_lut( + mut self: Pin<&mut Self>, + id: LightLutId, + input: LutInput, + ) -> Option { + let idx = Self::lut_id_to_index(id); + let me = unsafe { self.as_mut().get_unchecked_mut() }; + let lut = idx.and_then(|i| me.luts[i].take()); + if let Some(lut) = lut { + unsafe { + citro3d_sys::C3D_LightEnvLut( + &mut me.raw, + id as u32, + input as u32, + false, + std::ptr::null_mut(), + ); + } + } + lut + } + pub fn connect_lut(mut self: Pin<&mut Self>, id: LightLutId, input: LutInput, data: LutData) { + let idx = Self::lut_id_to_index(id); let (raw, lut) = unsafe { // this is needed to do structural borrowing as otherwise // the compiler rejects the reborrow needed with the pin @@ -323,6 +350,7 @@ pub enum LutInput { ViewHalf = ctru_sys::GPU_LUTINPUT_VH, } +#[derive(Clone, Copy)] #[repr(u32)] pub enum LightLutId { D0 = ctru_sys::GPU_LUT_D0, From 54e8c4b7ddbb08bf2843ae85d6b2ae691b0c1f7b Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 15 Feb 2024 12:53:44 +0000 Subject: [PATCH 15/22] fix: missing basic derives for Lut types --- citro3d/src/light.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 76cccf5..5b11f3d 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -289,21 +289,28 @@ impl PartialEq for LutData { } impl Eq for LutData {} +impl std::hash::Hash for LutData { + fn hash(&self, state: &mut H) { + self.0.data.hash(state); + } +} + #[cfg(test)] extern "C" fn c_powf(a: f32, b: f32) -> f32 { a.powf(b) } -const LUT_SZ: usize = 512; +type LutArray = [u32; 256]; impl LutData { pub fn from_fn(mut f: impl FnMut(f32) -> f32, negative: bool) -> Self { + const LUT_BUF_SZ: usize = 512; let base: i32 = 128; let diff = if negative { 0 } else { base }; let min = -128 + diff; let max = base + diff; assert_eq!(min.abs_diff(max), 2 * base as u32); - let mut data = [0.0f32; LUT_SZ]; + let mut data = [0.0f32; LUT_BUF_SZ]; for i in min..=max { let x = i as f32 / max as f32; let v = f(x); @@ -323,6 +330,16 @@ impl LutData { Self(lut) } + /// Get a reference to the underlying data + pub fn data(&self) -> &LutArray { + &self.0.data + } + + /// Get a mutable reference to the underlying data + pub fn data_mut(&mut self) -> &mut LutArray { + &mut self.0.data + } + #[cfg(test)] fn phong_citro3d(shininess: f32) -> Self { let lut = unsafe { @@ -335,6 +352,7 @@ impl LutData { } /// This is used to decide what the input should be to a [`LutData`] +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] #[repr(u32)] pub enum LutInput { CosPhi = ctru_sys::GPU_LUTINPUT_CP, @@ -350,7 +368,7 @@ pub enum LutInput { ViewHalf = ctru_sys::GPU_LUTINPUT_VH, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] #[repr(u32)] pub enum LightLutId { D0 = ctru_sys::GPU_LUT_D0, @@ -362,6 +380,7 @@ pub enum LightLutId { ReflectRed = ctru_sys::GPU_LUT_RR, DistanceAttenuation = ctru_sys::GPU_LUT_DA, } +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] #[repr(u32)] pub enum FresnelSelector { /// No fresnel selection From 6c635f98678a970151ae4462657bbc1e768e6043 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 15 Feb 2024 12:54:23 +0000 Subject: [PATCH 16/22] chore: LutData -> LightLut --- citro3d/examples/fragment-light.rs | 4 ++-- citro3d/src/light.rs | 32 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index 8ec084c..26be967 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -1,7 +1,7 @@ #![feature(allocator_api)] use citro3d::{ attrib, buffer, - light::{FresnelSelector, LightEnv, LightLutId, LutData, LutInput}, + light::{FresnelSelector, LightEnv, LightLut, LightLutId, LutInput}, material::{Color, Material}, math::{AspectRatio, ClipPlanes, FVec3, FVec4, Matrix4, Projection, StereoDisplacement}, render::{self, ClearFlags}, @@ -309,7 +309,7 @@ fn main() { light_env.as_mut().connect_lut( LightLutId::D0, LutInput::LightNormal, - LutData::from_fn(|v| v.powf(10.0), false), + LightLut::from_fn(|v| v.powf(10.0), false), ); light_env.as_mut().set_material(Material { ambient: Some(Color::new(0.2, 0.2, 0.2)), diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 5b11f3d..942edf6 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -53,15 +53,15 @@ impl LightIndex { #[derive(Default)] struct LightLutStorage { - spot: Option, - diffuse_atten: Option, + spot: Option, + diffuse_atten: Option, _pin: PhantomPinned, } #[derive(Default)] struct LightEnvStorage { lights: [Option; NB_LIGHTS], - luts: [Option; 6], + luts: [Option; 6], _pin: PhantomPinned, } @@ -78,14 +78,14 @@ pub struct LightEnv { /// Note this is `Pin` as well as `Box` as `raw` means we are _actually_ self-referential which /// is horrible but the best bad option in this case lights: LightArray, - luts: [Option; 6], + luts: [Option; 6], _pin: PhantomPinned, } pub struct Light { raw: citro3d_sys::C3D_Light, - spot: Option, - diffuse_atten: Option, + spot: Option, + diffuse_atten: Option, _pin: PhantomPinned, } @@ -182,7 +182,7 @@ impl LightEnv { mut self: Pin<&mut Self>, id: LightLutId, input: LutInput, - ) -> Option { + ) -> Option { let idx = Self::lut_id_to_index(id); let me = unsafe { self.as_mut().get_unchecked_mut() }; let lut = idx.and_then(|i| me.luts[i].take()); @@ -199,7 +199,7 @@ impl LightEnv { } lut } - pub fn connect_lut(mut self: Pin<&mut Self>, id: LightLutId, input: LutInput, data: LutData) { + pub fn connect_lut(mut self: Pin<&mut Self>, id: LightLutId, input: LutInput, data: LightLut) { let idx = Self::lut_id_to_index(id); let (raw, lut) = unsafe { // this is needed to do structural borrowing as otherwise @@ -280,16 +280,16 @@ unsafe impl Sync for LightEnv {} #[derive(Clone, Copy, Debug)] #[repr(transparent)] -pub struct LutData(citro3d_sys::C3D_LightLut); +pub struct LightLut(citro3d_sys::C3D_LightLut); -impl PartialEq for LutData { +impl PartialEq for LightLut { fn eq(&self, other: &Self) -> bool { self.0.data == other.0.data } } -impl Eq for LutData {} +impl Eq for LightLut {} -impl std::hash::Hash for LutData { +impl std::hash::Hash for LightLut { fn hash(&self, state: &mut H) { self.0.data.hash(state); } @@ -302,7 +302,7 @@ extern "C" fn c_powf(a: f32, b: f32) -> f32 { type LutArray = [u32; 256]; -impl LutData { +impl LightLut { pub fn from_fn(mut f: impl FnMut(f32) -> f32, negative: bool) -> Self { const LUT_BUF_SZ: usize = 512; let base: i32 = 128; @@ -397,12 +397,12 @@ type LightArray = PinArray, NB_LIGHTS>; #[cfg(test)] mod tests { - use super::LutData; + use super::LightLut; #[test] fn lut_data_phong_matches_for_own_and_citro3d() { - let c3d = LutData::phong_citro3d(30.0); - let rs = LutData::from_fn(|i| i.powf(30.0), false); + let c3d = LightLut::phong_citro3d(30.0); + let rs = LightLut::from_fn(|i| i.powf(30.0), false); assert_eq!(c3d, rs); } } From d81514b6cb1fb09c8005541fb5390030e8a32148 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 15 Feb 2024 13:06:50 +0000 Subject: [PATCH 17/22] feat: improve docs on luts --- citro3d/src/light.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 942edf6..f2e3915 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -11,10 +11,29 @@ //! For things like specular lighting we need to go a bit deeper //! //! # LUTS -//! LUTS are lookup tables, in this case for the GPU. They are created ahead of time and stored in [`LutData`]'s, -//! [`LutData::from_fn`] essentially memoises the given function with the input changing depending on what +//! LUTS are lookup tables, in this case for the GPU. They are created ahead of time and stored in [`LightLut`]'s, +//! [`LightLut::from_fn`] essentially memoises the given function with the input changing depending on what //! input it is bound to when setting it on the [`LightEnv`]. //! +//! ## Example +//! Lets say we have this code +//! +//! ``` +//! # use citro3d::{Instance, light::{LightLutId, LightInput, LightLut}}; +//! let mut inst = Instance::new(); +//! let mut env = inst.light_env_mut(); +//! env.as_mut().connect_lut( +//! LutInputId::D0, +//! LutInput::NormalView, +//! LightLut::from_fn(|x| x.powf(10.0)), +//! ); +//! ``` +//! +//! This places the LUT in `D0` (refer to [the diagram][hardware]) and connects the input wire as the dot product +//! of the normal and view vectors. `x` is effectively the dot product of the normal and view for every vertex and +//! the return of the closure goes out on the corresponding wire +//! (which in the case of `D0` is used for specular lighting after being combined with with specular0) +//! //! //! //! [hardware]: https://raw.githubusercontent.com/wwylele/misc-3ds-diagram/master/pica-pipeline.svg @@ -278,6 +297,9 @@ unsafe impl Sync for Light {} unsafe impl Send for LightEnv {} unsafe impl Sync for LightEnv {} +/// Lookup table for light data +/// +/// For more refer to the module documentation #[derive(Clone, Copy, Debug)] #[repr(transparent)] pub struct LightLut(citro3d_sys::C3D_LightLut); @@ -303,6 +325,7 @@ extern "C" fn c_powf(a: f32, b: f32) -> f32 { type LutArray = [u32; 256]; impl LightLut { + /// Create a LUT by memoizing a function pub fn from_fn(mut f: impl FnMut(f32) -> f32, negative: bool) -> Self { const LUT_BUF_SZ: usize = 512; let base: i32 = 128; @@ -351,7 +374,7 @@ impl LightLut { } } -/// This is used to decide what the input should be to a [`LutData`] +/// This is used to decide what the input should be to a [`LightLut`] #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] #[repr(u32)] pub enum LutInput { From 5a7b44f55af497a1c30a15128b511a77c382d1a8 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 15 Feb 2024 17:48:42 +0000 Subject: [PATCH 18/22] feat: add distance attenuation --- citro3d/examples/fragment-light.rs | 9 +++++- citro3d/src/light.rs | 46 ++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index 26be967..771d957 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -1,7 +1,9 @@ #![feature(allocator_api)] +use std::f32::consts::PI; + use citro3d::{ attrib, buffer, - light::{FresnelSelector, LightEnv, LightLut, LightLutId, LutInput}, + light::{FresnelSelector, LightEnv, LightLut, LightLutDistAtten, LightLutId, LutInput}, material::{Color, Material}, math::{AspectRatio, ClipPlanes, FVec3, FVec4, Matrix4, Projection, StereoDisplacement}, render::{self, ClearFlags}, @@ -321,6 +323,11 @@ fn main() { let mut light = light_env.as_mut().light_mut(light).unwrap(); light.as_mut().set_color(1.0, 1.0, 1.0); light.as_mut().set_position(FVec3::new(0.0, 0.0, -0.5)); + light + .as_mut() + .set_distance_attenutation(Some(LightLutDistAtten::new(0.0..400.0, |d| { + (1.0 / (4.0 * PI * d * d)).min(1.0) + }))); let mut c = Matrix4::identity(); let model_idx = program.get_uniform("modelView").unwrap(); c.translate(0.0, 0.0, -2.0); diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index f2e3915..b1b12ed 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -38,7 +38,7 @@ //! //! [hardware]: https://raw.githubusercontent.com/wwylele/misc-3ds-diagram/master/pica-pipeline.svg -use std::{marker::PhantomPinned, mem::MaybeUninit, pin::Pin}; +use std::{marker::PhantomPinned, mem::MaybeUninit, ops::Range, pin::Pin}; use pin_array::PinArray; @@ -104,7 +104,7 @@ pub struct LightEnv { pub struct Light { raw: citro3d_sys::C3D_Light, spot: Option, - diffuse_atten: Option, + diffuse_atten: Option, _pin: PhantomPinned, } @@ -287,6 +287,30 @@ impl Light { pub fn set_shadow(self: Pin<&mut Self>, shadow: bool) { unsafe { citro3d_sys::C3D_LightShadowEnable(self.as_raw_mut(), shadow) } } + pub fn set_distance_attenutation(mut self: Pin<&mut Self>, lut: Option) { + { + let me = unsafe { self.as_mut().get_unchecked_mut() }; + me.diffuse_atten = lut; + } + // this is a bit of a mess because we need to be _reallly_ careful we don't trip aliasing rules + // reusing `me` here I think trips them because we have multiple live mutable references to + // the same region + let (raw, c_lut) = { + let me = unsafe { self.as_mut().get_unchecked_mut() }; + let raw = &mut me.raw; + let c_lut = me.diffuse_atten.as_mut().map(|d| &mut d.raw); + (raw, c_lut) + }; + unsafe { + citro3d_sys::C3D_LightDistAttn( + raw, + match c_lut { + Some(l) => l, + None => std::ptr::null_mut(), + }, + ); + } + } } // Safety: I am 99% sure these are safe. That 1% is if citro3d does something weird I missed @@ -323,11 +347,11 @@ extern "C" fn c_powf(a: f32, b: f32) -> f32 { } type LutArray = [u32; 256]; +const LUT_BUF_SZ: usize = 512; impl LightLut { /// Create a LUT by memoizing a function pub fn from_fn(mut f: impl FnMut(f32) -> f32, negative: bool) -> Self { - const LUT_BUF_SZ: usize = 512; let base: i32 = 128; let diff = if negative { 0 } else { base }; let min = -128 + diff; @@ -374,6 +398,22 @@ impl LightLut { } } +pub struct LightLutDistAtten { + raw: citro3d_sys::C3D_LightLutDA, +} + +impl LightLutDistAtten { + pub fn new(range: Range, mut f: impl FnMut(f32) -> f32) -> Self { + let mut raw: citro3d_sys::C3D_LightLutDA = unsafe { MaybeUninit::zeroed().assume_init() }; + let dist = range.end - range.start; + raw.scale = 1.0 / dist; + raw.bias = -range.start * raw.scale; + let lut = LightLut::from_fn(|x| f(range.start + dist * x), false); + raw.lut = citro3d_sys::C3D_LightLut { data: *lut.data() }; + Self { raw } + } +} + /// This is used to decide what the input should be to a [`LightLut`] #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] #[repr(u32)] From 64f6e3a223bc51b16abe81740b0f9de3e2072c8e Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Tue, 18 Jun 2024 21:22:59 +0100 Subject: [PATCH 19/22] fix: build with latest ctru-sys --- citro3d/src/light.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index b1b12ed..60c1f81 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -209,8 +209,8 @@ impl LightEnv { unsafe { citro3d_sys::C3D_LightEnvLut( &mut me.raw, - id as u32, - input as u32, + id as u8, + input as u8, false, std::ptr::null_mut(), ); @@ -233,7 +233,7 @@ impl LightEnv { (raw, lut) }; unsafe { - citro3d_sys::C3D_LightEnvLut(raw, id as u32, input as u32, false, lut); + citro3d_sys::C3D_LightEnvLut(raw, id as u8, input as u8, false, lut); } } pub fn set_fresnel(mut self: Pin<&mut Self>, sel: FresnelSelector) { @@ -416,7 +416,7 @@ impl LightLutDistAtten { /// This is used to decide what the input should be to a [`LightLut`] #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] -#[repr(u32)] +#[repr(u8)] pub enum LutInput { CosPhi = ctru_sys::GPU_LUTINPUT_CP, /// Dot product of the light and normal vectors @@ -432,7 +432,7 @@ pub enum LutInput { } #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] -#[repr(u32)] +#[repr(u8)] pub enum LightLutId { D0 = ctru_sys::GPU_LUT_D0, D1 = ctru_sys::GPU_LUT_D1, @@ -444,7 +444,7 @@ pub enum LightLutId { DistanceAttenuation = ctru_sys::GPU_LUT_DA, } #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] -#[repr(u32)] +#[repr(u8)] pub enum FresnelSelector { /// No fresnel selection None = ctru_sys::GPU_NO_FRESNEL, From 4e4620fdf2c4389cf00c401b13229f0e7f7f6cf2 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Tue, 18 Jun 2024 21:29:31 +0100 Subject: [PATCH 20/22] fix: fragment light example --- citro3d/examples/fragment-light.rs | 45 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index 771d957..4909250 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -268,33 +268,36 @@ fn main() { let (mut top_left, mut top_right) = top_screen.split_mut(); let RawFrameBuffer { width, height, .. } = top_left.raw_framebuffer(); - let mut top_left_target = render::Target::new( - width, - height, - top_left, - Some(render::DepthFormat::Depth24Stencil8), - ) - .expect("failed to create render target"); + let mut top_left_target = instance + .render_target( + width, + height, + top_left, + Some(render::DepthFormat::Depth24Stencil8), + ) + .expect("failed to create render target"); let RawFrameBuffer { width, height, .. } = top_right.raw_framebuffer(); - let mut top_right_target = render::Target::new( - width, - height, - top_right, - Some(render::DepthFormat::Depth24Stencil8), - ) - .expect("failed to create render target"); + let mut top_right_target = instance + .render_target( + width, + height, + top_right, + Some(render::DepthFormat::Depth24Stencil8), + ) + .expect("failed to create render target"); let mut bottom_screen = gfx.bottom_screen.borrow_mut(); let RawFrameBuffer { width, height, .. } = bottom_screen.raw_framebuffer(); - let mut bottom_target = render::Target::new( - width, - height, - bottom_screen, - Some(render::DepthFormat::Depth24Stencil8), - ) - .expect("failed to create bottom screen render target"); + let mut bottom_target = instance + .render_target( + width, + height, + bottom_screen, + Some(render::DepthFormat::Depth24Stencil8), + ) + .expect("failed to create bottom screen render target"); let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap(); let vertex_shader = shader.get(0).unwrap(); From 921a26788c2cb4ed9bb3595d5fcf2e3a1c293971 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Tue, 18 Jun 2024 21:31:27 +0100 Subject: [PATCH 21/22] chore: bump pin array min version --- citro3d/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/citro3d/Cargo.toml b/citro3d/Cargo.toml index e0772ec..2998a81 100644 --- a/citro3d/Cargo.toml +++ b/citro3d/Cargo.toml @@ -16,7 +16,7 @@ ctru-rs = { git = "https://github.com/rust3ds/ctru-rs.git" } ctru-sys = { git = "https://github.com/rust3ds/ctru-rs.git" } document-features = "0.2.7" libc = "0.2.125" -pin_array = { version = "0.1.0" } +pin_array = { version = "0.1.1" } [features] default = ["glam"] From c64460be6e1832d34e69740d3051c43c36723843 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Tue, 18 Jun 2024 21:49:08 +0100 Subject: [PATCH 22/22] chore: cleanup and improve API of Light --- citro3d/src/light.rs | 70 ++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/citro3d/src/light.rs b/citro3d/src/light.rs index 60c1f81..ab69fcb 100644 --- a/citro3d/src/light.rs +++ b/citro3d/src/light.rs @@ -70,32 +70,15 @@ impl LightIndex { } } -#[derive(Default)] -struct LightLutStorage { - spot: Option, - diffuse_atten: Option, - _pin: PhantomPinned, -} - -#[derive(Default)] -struct LightEnvStorage { - lights: [Option; NB_LIGHTS], - luts: [Option; 6], - _pin: PhantomPinned, -} - -impl LightEnvStorage { - fn lights_mut(self: Pin<&mut Self>) -> Pin<&mut [Option; NB_LIGHTS]> { - unsafe { Pin::map_unchecked_mut(self, |s| &mut s.lights) } - } -} +type LightArray = PinArray, NB_LIGHTS>; pub struct LightEnv { raw: citro3d_sys::C3D_LightEnv, /// The actual light data pointed to by the lights element of `raw` /// - /// Note this is `Pin` as well as `Box` as `raw` means we are _actually_ self-referential which - /// is horrible but the best bad option in this case + /// Note this is `Pin` as well, because `raw` means we are _actually_ self-referential which + /// is horrible but the best bad option in this case. Moving the one of these elements would + /// break the pointers in `raw` lights: LightArray, luts: [Option; 6], _pin: PhantomPinned, @@ -103,7 +86,8 @@ pub struct LightEnv { pub struct Light { raw: citro3d_sys::C3D_Light, - spot: Option, + // todo: implement spotlight support + _spot: Option, diffuse_atten: Option, _pin: PhantomPinned, } @@ -172,8 +156,12 @@ impl LightEnv { .unwrap() .map_unchecked_mut(|p| p.as_mut().unwrap()) }; - let r = - unsafe { citro3d_sys::C3D_LightInit(target.as_raw_mut(), self.as_raw_mut() as *mut _) }; + let r = unsafe { + citro3d_sys::C3D_LightInit( + target.get_unchecked_mut().as_raw_mut(), + self.as_raw_mut() as *mut _, + ) + }; assert!(r >= 0, "C3D_LightInit should only fail if there are no free light slots but we checked that already, how did this happen?"); assert_eq!( r as usize, idx, @@ -205,7 +193,7 @@ impl LightEnv { let idx = Self::lut_id_to_index(id); let me = unsafe { self.as_mut().get_unchecked_mut() }; let lut = idx.and_then(|i| me.luts[i].take()); - if let Some(lut) = lut { + if lut.is_some() { unsafe { citro3d_sys::C3D_LightEnvLut( &mut me.raw, @@ -236,7 +224,7 @@ impl LightEnv { citro3d_sys::C3D_LightEnvLut(raw, id as u8, input as u8, false, lut); } } - pub fn set_fresnel(mut self: Pin<&mut Self>, sel: FresnelSelector) { + pub fn set_fresnel(self: Pin<&mut Self>, sel: FresnelSelector) { unsafe { citro3d_sys::C3D_LightEnvFresnel(self.as_raw_mut(), sel as _) } } @@ -253,39 +241,39 @@ impl Light { fn new(raw: citro3d_sys::C3D_Light) -> Self { Self { raw, - spot: Default::default(), + _spot: Default::default(), diffuse_atten: Default::default(), _pin: Default::default(), } } - fn from_raw_ref(l: &citro3d_sys::C3D_Light) -> &Self { - unsafe { (l as *const _ as *const Self).as_ref().unwrap() } - } - fn from_raw_mut(l: &mut citro3d_sys::C3D_Light) -> &mut Self { - unsafe { (l as *mut _ as *mut Self).as_mut().unwrap() } - } - fn as_raw(&self) -> &citro3d_sys::C3D_Light { + /// Get a reference to the underlying raw `C3D_Light` + pub fn as_raw(&self) -> &citro3d_sys::C3D_Light { &self.raw } - fn as_raw_mut(self: Pin<&mut Self>) -> &mut citro3d_sys::C3D_Light { - unsafe { &mut self.get_unchecked_mut().raw } + /// Get a raw mut to the raw `C3D_Light` + /// + /// note: This does not take Pin<&mut Self>, if you need the raw from a pinned light you must use `unsafe` and ensure you uphold the pinning + /// restrictions of the original `Light` + pub fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_Light { + &mut self.raw } + pub fn set_position(self: Pin<&mut Self>, p: FVec3) { let mut p = FVec4::new(p.x(), p.y(), p.z(), 1.0); - unsafe { citro3d_sys::C3D_LightPosition(self.as_raw_mut(), &mut p.0) } + unsafe { citro3d_sys::C3D_LightPosition(self.get_unchecked_mut().as_raw_mut(), &mut p.0) } } pub fn set_color(self: Pin<&mut Self>, r: f32, g: f32, b: f32) { - unsafe { citro3d_sys::C3D_LightColor(self.as_raw_mut(), r, g, b) } + unsafe { citro3d_sys::C3D_LightColor(self.get_unchecked_mut().as_raw_mut(), r, g, b) } } #[doc(alias = "C3D_LightEnable")] pub fn set_enabled(self: Pin<&mut Self>, enabled: bool) { - unsafe { citro3d_sys::C3D_LightEnable(self.as_raw_mut(), enabled) } + unsafe { citro3d_sys::C3D_LightEnable(self.get_unchecked_mut().as_raw_mut(), enabled) } } #[doc(alias = "C3D_LightShadowEnable")] pub fn set_shadow(self: Pin<&mut Self>, shadow: bool) { - unsafe { citro3d_sys::C3D_LightShadowEnable(self.as_raw_mut(), shadow) } + unsafe { citro3d_sys::C3D_LightShadowEnable(self.get_unchecked_mut().as_raw_mut(), shadow) } } pub fn set_distance_attenutation(mut self: Pin<&mut Self>, lut: Option) { { @@ -456,8 +444,6 @@ pub enum FresnelSelector { Both = ctru_sys::GPU_PRI_SEC_ALPHA_FRESNEL, } -type LightArray = PinArray, NB_LIGHTS>; - #[cfg(test)] mod tests { use super::LightLut;