From 704f6e58b7dcb4d7fee9448b94304ab6379dc1ba Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 25 Feb 2023 15:33:24 -0500 Subject: [PATCH 01/11] First pass of safe buffer info API --- Cargo.toml | 1 + citro3d-sys/src/bindings.rs | 287 +++++------------------------------ citro3d/examples/triangle.rs | 53 ++++--- citro3d/src/attrib.rs | 117 ++++++++++++++ citro3d/src/buffers.rs | 55 +++++++ citro3d/src/error.rs | 17 +++ citro3d/src/lib.rs | 5 +- citro3d/src/vbo.rs | 1 - 8 files changed, 272 insertions(+), 264 deletions(-) create mode 100644 citro3d/src/attrib.rs create mode 100644 citro3d/src/buffers.rs delete mode 100644 citro3d/src/vbo.rs diff --git a/Cargo.toml b/Cargo.toml index 248e09b..c8d1d04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = ["citro3d-sys", "citro3d", "bindgen-citro3d"] +default-members = ["citro3d-sys", "citro3d"] [patch."https://github.com/rust3ds/citro3d-rs.git"] citro3d-sys = { path = "citro3d-sys" } diff --git a/citro3d-sys/src/bindings.rs b/citro3d-sys/src/bindings.rs index 76ca7ad..ecda601 100644 --- a/citro3d-sys/src/bindings.rs +++ b/citro3d-sys/src/bindings.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.62.0 */ +/* automatically generated by rust-bindgen 0.64.0 */ use ctru_sys::*; @@ -96,10 +96,7 @@ pub type _off_t = __int64_t; pub type _fpos_t = __int64_t; pub type wint_t = ::libc::c_int; pub type C3D_IVec = u32_; -#[doc = " @struct C3D_FVec"] -#[doc = " @brief Float vector"] -#[doc = ""] -#[doc = " Matches PICA layout"] +#[doc = " @struct C3D_FVec\n @brief Float vector\n\n Matches PICA layout"] #[repr(C)] #[derive(Copy, Clone)] pub union C3D_FVec { @@ -134,13 +131,9 @@ pub struct C3D_FVec__bindgen_ty_2 { #[doc = "< I-component"] pub i: f32, } -#[doc = " @struct C3D_FVec"] -#[doc = " @brief Float vector"] -#[doc = ""] -#[doc = " Matches PICA layout"] +#[doc = " @struct C3D_FVec\n @brief Float vector\n\n Matches PICA layout"] pub type C3D_FQuat = C3D_FVec; -#[doc = " @struct C3D_Mtx"] -#[doc = " @brief Row-major 4x4 matrix"] +#[doc = " @struct C3D_Mtx\n @brief Row-major 4x4 matrix"] #[repr(C)] #[derive(Copy, Clone)] pub union C3D_Mtx { @@ -150,101 +143,55 @@ pub union C3D_Mtx { pub m: [f32; 16usize], } extern "C" { - #[doc = "@brief Transposes the matrix. Row => Column, and vice versa."] - #[doc = "@param[in,out] out Output matrix."] + #[doc = "@brief Transposes the matrix. Row => Column, and vice versa.\n@param[in,out] out Output matrix."] pub fn Mtx_Transpose(out: *mut C3D_Mtx); } extern "C" { - #[doc = " @brief Multiply two matrices"] - #[doc = " @param[out] out Output matrix"] - #[doc = " @param[in] a Multiplicand"] - #[doc = " @param[in] b Multiplier"] + #[doc = " @brief Multiply two matrices\n @param[out] out Output matrix\n @param[in] a Multiplicand\n @param[in] b Multiplier"] pub fn Mtx_Multiply(out: *mut C3D_Mtx, a: *const C3D_Mtx, b: *const C3D_Mtx); } extern "C" { - #[doc = " @brief Inverse a matrix"] - #[doc = " @param[in,out] out Matrix to inverse"] - #[doc = " @retval 0.0f Degenerate matrix (no inverse)"] - #[doc = " @return determinant"] + #[doc = " @brief Inverse a matrix\n @param[in,out] out Matrix to inverse\n @retval 0.0f Degenerate matrix (no inverse)\n @return determinant"] pub fn Mtx_Inverse(out: *mut C3D_Mtx) -> f32; } extern "C" { - #[doc = " @brief Multiply 3x3 matrix by a FVec3"] - #[doc = " @param[in] mtx Matrix"] - #[doc = " @param[in] v Vector"] - #[doc = " @return mtx*v (product)"] + #[doc = " @brief Multiply 3x3 matrix by a FVec3\n @param[in] mtx Matrix\n @param[in] v Vector\n @return mtx*v (product)"] pub fn Mtx_MultiplyFVec3(mtx: *const C3D_Mtx, v: C3D_FVec) -> C3D_FVec; } extern "C" { - #[doc = " @brief Multiply 4x4 matrix by a FVec4"] - #[doc = " @param[in] mtx Matrix"] - #[doc = " @param[in] v Vector"] - #[doc = " @return mtx*v (product)"] + #[doc = " @brief Multiply 4x4 matrix by a FVec4\n @param[in] mtx Matrix\n @param[in] v Vector\n @return mtx*v (product)"] pub fn Mtx_MultiplyFVec4(mtx: *const C3D_Mtx, v: C3D_FVec) -> C3D_FVec; } extern "C" { - #[doc = " @brief Get 4x4 matrix equivalent to Quaternion"] - #[doc = " @param[out] m Output matrix"] - #[doc = " @param[in] q Input Quaternion"] + #[doc = " @brief Get 4x4 matrix equivalent to Quaternion\n @param[out] m Output matrix\n @param[in] q Input Quaternion"] pub fn Mtx_FromQuat(m: *mut C3D_Mtx, q: C3D_FQuat); } extern "C" { - #[doc = " @brief 3D translation"] - #[doc = " @param[in,out] mtx Matrix to translate"] - #[doc = " @param[in] x X component to translate"] - #[doc = " @param[in] y Y component to translate"] - #[doc = " @param[in] z Z component to translate"] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] + #[doc = " @brief 3D translation\n @param[in,out] mtx Matrix to translate\n @param[in] x X component to translate\n @param[in] y Y component to translate\n @param[in] z Z component to translate\n @param[in] bRightSide Whether to transform from the right side"] pub fn Mtx_Translate(mtx: *mut C3D_Mtx, x: f32, y: f32, z: f32, bRightSide: bool); } extern "C" { - #[doc = " @brief 3D Scale"] - #[doc = " @param[in,out] mtx Matrix to scale"] - #[doc = " @param[in] x X component to scale"] - #[doc = " @param[in] y Y component to scale"] - #[doc = " @param[in] z Z component to scale"] + #[doc = " @brief 3D Scale\n @param[in,out] mtx Matrix to scale\n @param[in] x X component to scale\n @param[in] y Y component to scale\n @param[in] z Z component to scale"] pub fn Mtx_Scale(mtx: *mut C3D_Mtx, x: f32, y: f32, z: f32); } extern "C" { - #[doc = " @brief 3D Rotation"] - #[doc = " @param[in,out] mtx Matrix to rotate"] - #[doc = " @param[in] axis Axis about which to rotate"] - #[doc = " @param[in] angle Radians to rotate"] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] + #[doc = " @brief 3D Rotation\n @param[in,out] mtx Matrix to rotate\n @param[in] axis Axis about which to rotate\n @param[in] angle Radians to rotate\n @param[in] bRightSide Whether to transform from the right side"] pub fn Mtx_Rotate(mtx: *mut C3D_Mtx, axis: C3D_FVec, angle: f32, bRightSide: bool); } extern "C" { - #[doc = " @brief 3D Rotation about the X axis"] - #[doc = " @param[in,out] mtx Matrix to rotate"] - #[doc = " @param[in] angle Radians to rotate"] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] + #[doc = " @brief 3D Rotation about the X axis\n @param[in,out] mtx Matrix to rotate\n @param[in] angle Radians to rotate\n @param[in] bRightSide Whether to transform from the right side"] pub fn Mtx_RotateX(mtx: *mut C3D_Mtx, angle: f32, bRightSide: bool); } extern "C" { - #[doc = " @brief 3D Rotation about the Y axis"] - #[doc = " @param[in,out] mtx Matrix to rotate"] - #[doc = " @param[in] angle Radians to rotate"] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] + #[doc = " @brief 3D Rotation about the Y axis\n @param[in,out] mtx Matrix to rotate\n @param[in] angle Radians to rotate\n @param[in] bRightSide Whether to transform from the right side"] pub fn Mtx_RotateY(mtx: *mut C3D_Mtx, angle: f32, bRightSide: bool); } extern "C" { - #[doc = " @brief 3D Rotation about the Z axis"] - #[doc = " @param[in,out] mtx Matrix to rotate"] - #[doc = " @param[in] angle Radians to rotate"] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] + #[doc = " @brief 3D Rotation about the Z axis\n @param[in,out] mtx Matrix to rotate\n @param[in] angle Radians to rotate\n @param[in] bRightSide Whether to transform from the right side"] pub fn Mtx_RotateZ(mtx: *mut C3D_Mtx, angle: f32, bRightSide: bool); } extern "C" { - #[doc = " @brief Orthogonal projection"] - #[doc = " @param[out] mtx Output matrix"] - #[doc = " @param[in] left Left clip plane (X=left)"] - #[doc = " @param[in] right Right clip plane (X=right)"] - #[doc = " @param[in] bottom Bottom clip plane (Y=bottom)"] - #[doc = " @param[in] top Top clip plane (Y=top)"] - #[doc = " @param[in] near Near clip plane (Z=near)"] - #[doc = " @param[in] far Far clip plane (Z=far)"] - #[doc = " @param[in] isLeftHanded Whether to build a LH projection"] - #[doc = " @sa Mtx_OrthoTilt"] + #[doc = " @brief Orthogonal projection\n @param[out] mtx Output matrix\n @param[in] left Left clip plane (X=left)\n @param[in] right Right clip plane (X=right)\n @param[in] bottom Bottom clip plane (Y=bottom)\n @param[in] top Top clip plane (Y=top)\n @param[in] near Near clip plane (Z=near)\n @param[in] far Far clip plane (Z=far)\n @param[in] isLeftHanded Whether to build a LH projection\n @sa Mtx_OrthoTilt"] pub fn Mtx_Ortho( mtx: *mut C3D_Mtx, left: f32, @@ -257,16 +204,7 @@ extern "C" { ); } extern "C" { - #[doc = " @brief Perspective projection"] - #[doc = " @param[out] mtx Output matrix"] - #[doc = " @param[in] fovy Vertical field of view in radians"] - #[doc = " @param[in] aspect Aspect ration of projection plane (width/height)"] - #[doc = " @param[in] near Near clip plane (Z=near)"] - #[doc = " @param[in] far Far clip plane (Z=far)"] - #[doc = " @param[in] isLeftHanded Whether to build a LH projection"] - #[doc = " @sa Mtx_PerspTilt"] - #[doc = " @sa Mtx_PerspStereo"] - #[doc = " @sa Mtx_PerspStereoTilt"] + #[doc = " @brief Perspective projection\n @param[out] mtx Output matrix\n @param[in] fovy Vertical field of view in radians\n @param[in] aspect Aspect ration of projection plane (width/height)\n @param[in] near Near clip plane (Z=near)\n @param[in] far Far clip plane (Z=far)\n @param[in] isLeftHanded Whether to build a LH projection\n @sa Mtx_PerspTilt\n @sa Mtx_PerspStereo\n @sa Mtx_PerspStereoTilt"] pub fn Mtx_Persp( mtx: *mut C3D_Mtx, fovy: f32, @@ -277,24 +215,7 @@ extern "C" { ); } extern "C" { - #[doc = " @brief Stereo perspective projection"] - #[doc = " @note Typically you will use iod to mean the distance between the eyes. Plug"] - #[doc = " in -iod for the left eye and iod for the right eye."] - #[doc = " @note The focal length is defined by screen. If objects are further than this,"] - #[doc = " they will appear to be inside the screen. If objects are closer than this,"] - #[doc = " they will appear to pop out of the screen. Objects at this distance appear"] - #[doc = " to be at the screen."] - #[doc = " @param[out] mtx Output matrix"] - #[doc = " @param[in] fovy Vertical field of view in radians"] - #[doc = " @param[in] aspect Aspect ration of projection plane (width/height)"] - #[doc = " @param[in] near Near clip plane (Z=near)"] - #[doc = " @param[in] far Far clip plane (Z=far)"] - #[doc = " @param[in] iod Interocular distance"] - #[doc = " @param[in] screen Focal length"] - #[doc = " @param[in] isLeftHanded Whether to build a LH projection"] - #[doc = " @sa Mtx_Persp"] - #[doc = " @sa Mtx_PerspTilt"] - #[doc = " @sa Mtx_PerspStereoTilt"] + #[doc = " @brief Stereo perspective projection\n @note Typically you will use iod to mean the distance between the eyes. Plug\n in -iod for the left eye and iod for the right eye.\n @note The focal length is defined by screen. If objects are further than this,\n they will appear to be inside the screen. If objects are closer than this,\n they will appear to pop out of the screen. Objects at this distance appear\n to be at the screen.\n @param[out] mtx Output matrix\n @param[in] fovy Vertical field of view in radians\n @param[in] aspect Aspect ration of projection plane (width/height)\n @param[in] near Near clip plane (Z=near)\n @param[in] far Far clip plane (Z=far)\n @param[in] iod Interocular distance\n @param[in] screen Focal length\n @param[in] isLeftHanded Whether to build a LH projection\n @sa Mtx_Persp\n @sa Mtx_PerspTilt\n @sa Mtx_PerspStereoTilt"] pub fn Mtx_PerspStereo( mtx: *mut C3D_Mtx, fovy: f32, @@ -307,16 +228,7 @@ extern "C" { ); } extern "C" { - #[doc = " @brief Orthogonal projection, tilted to account for the 3DS screen rotation"] - #[doc = " @param[out] mtx Output matrix"] - #[doc = " @param[in] left Left clip plane (X=left)"] - #[doc = " @param[in] right Right clip plane (X=right)"] - #[doc = " @param[in] bottom Bottom clip plane (Y=bottom)"] - #[doc = " @param[in] top Top clip plane (Y=top)"] - #[doc = " @param[in] near Near clip plane (Z=near)"] - #[doc = " @param[in] far Far clip plane (Z=far)"] - #[doc = " @param[in] isLeftHanded Whether to build a LH projection"] - #[doc = " @sa Mtx_Ortho"] + #[doc = " @brief Orthogonal projection, tilted to account for the 3DS screen rotation\n @param[out] mtx Output matrix\n @param[in] left Left clip plane (X=left)\n @param[in] right Right clip plane (X=right)\n @param[in] bottom Bottom clip plane (Y=bottom)\n @param[in] top Top clip plane (Y=top)\n @param[in] near Near clip plane (Z=near)\n @param[in] far Far clip plane (Z=far)\n @param[in] isLeftHanded Whether to build a LH projection\n @sa Mtx_Ortho"] pub fn Mtx_OrthoTilt( mtx: *mut C3D_Mtx, left: f32, @@ -329,16 +241,7 @@ extern "C" { ); } extern "C" { - #[doc = " @brief Perspective projection, tilted to account for the 3DS screen rotation"] - #[doc = " @param[out] mtx Output matrix"] - #[doc = " @param[in] fovy Vertical field of view in radians"] - #[doc = " @param[in] aspect Aspect ration of projection plane (width/height)"] - #[doc = " @param[in] near Near clip plane (Z=near)"] - #[doc = " @param[in] far Far clip plane (Z=far)"] - #[doc = " @param[in] isLeftHanded Whether to build a LH projection"] - #[doc = " @sa Mtx_Persp"] - #[doc = " @sa Mtx_PerspStereo"] - #[doc = " @sa Mtx_PerspStereoTilt"] + #[doc = " @brief Perspective projection, tilted to account for the 3DS screen rotation\n @param[out] mtx Output matrix\n @param[in] fovy Vertical field of view in radians\n @param[in] aspect Aspect ration of projection plane (width/height)\n @param[in] near Near clip plane (Z=near)\n @param[in] far Far clip plane (Z=far)\n @param[in] isLeftHanded Whether to build a LH projection\n @sa Mtx_Persp\n @sa Mtx_PerspStereo\n @sa Mtx_PerspStereoTilt"] pub fn Mtx_PerspTilt( mtx: *mut C3D_Mtx, fovy: f32, @@ -349,19 +252,7 @@ extern "C" { ); } extern "C" { - #[doc = " @brief Stereo perspective projection, tilted to account for the 3DS screen rotation"] - #[doc = " @note See the notes for @ref Mtx_PerspStereo"] - #[doc = " @param[out] mtx Output matrix"] - #[doc = " @param[in] fovy Vertical field of view in radians"] - #[doc = " @param[in] aspect Aspect ration of projection plane (width/height)"] - #[doc = " @param[in] near Near clip plane (Z=near)"] - #[doc = " @param[in] far Far clip plane (Z=far)"] - #[doc = " @param[in] iod Interocular distance"] - #[doc = " @param[in] screen Focal length"] - #[doc = " @param[in] isLeftHanded Whether to build a LH projection"] - #[doc = " @sa Mtx_Persp"] - #[doc = " @sa Mtx_PerspTilt"] - #[doc = " @sa Mtx_PerspStereo"] + #[doc = " @brief Stereo perspective projection, tilted to account for the 3DS screen rotation\n @note See the notes for @ref Mtx_PerspStereo\n @param[out] mtx Output matrix\n @param[in] fovy Vertical field of view in radians\n @param[in] aspect Aspect ration of projection plane (width/height)\n @param[in] near Near clip plane (Z=near)\n @param[in] far Far clip plane (Z=far)\n @param[in] iod Interocular distance\n @param[in] screen Focal length\n @param[in] isLeftHanded Whether to build a LH projection\n @sa Mtx_Persp\n @sa Mtx_PerspTilt\n @sa Mtx_PerspStereo"] pub fn Mtx_PerspStereoTilt( mtx: *mut C3D_Mtx, fovy: f32, @@ -374,13 +265,7 @@ extern "C" { ); } extern "C" { - #[doc = " @brief Look-At matrix, based on DirectX implementation"] - #[doc = " @note See https://msdn.microsoft.com/en-us/library/windows/desktop/bb205342"] - #[doc = " @param[out] out Output matrix."] - #[doc = " @param[in] cameraPosition Position of the intended camera in 3D space."] - #[doc = " @param[in] cameraTarget Position of the intended target the camera is supposed to face in 3D space."] - #[doc = " @param[in] cameraUpVector The vector that points straight up depending on the camera's \"Up\" direction."] - #[doc = " @param[in] isLeftHanded Whether to build a LH projection"] + #[doc = " @brief Look-At matrix, based on DirectX implementation\n @note See https://msdn.microsoft.com/en-us/library/windows/desktop/bb205342\n @param[out] out Output matrix.\n @param[in] cameraPosition Position of the intended camera in 3D space.\n @param[in] cameraTarget Position of the intended target the camera is supposed to face in 3D space.\n @param[in] cameraUpVector The vector that points straight up depending on the camera's \"Up\" direction.\n @param[in] isLeftHanded Whether to build a LH projection"] pub fn Mtx_LookAt( out: *mut C3D_Mtx, cameraPosition: C3D_FVec, @@ -390,84 +275,43 @@ extern "C" { ); } extern "C" { - #[doc = " @brief Multiply two Quaternions"] - #[doc = " @param[in] lhs Multiplicand"] - #[doc = " @param[in] rhs Multiplier"] - #[doc = " @return lhs*rhs"] + #[doc = " @brief Multiply two Quaternions\n @param[in] lhs Multiplicand\n @param[in] rhs Multiplier\n @return lhs*rhs"] pub fn Quat_Multiply(lhs: C3D_FQuat, rhs: C3D_FQuat) -> C3D_FQuat; } extern "C" { - #[doc = " @brief Raise Quaternion to a power"] - #[doc = " @note If p is 0, this returns the identity Quaternion."] - #[doc = " If p is 1, this returns q."] - #[doc = " @param[in] q Base Quaternion"] - #[doc = " @param[in] p Power"] - #[doc = " @return qp"] + #[doc = " @brief Raise Quaternion to a power\n @note If p is 0, this returns the identity Quaternion.\n If p is 1, this returns q.\n @param[in] q Base Quaternion\n @param[in] p Power\n @return qp"] pub fn Quat_Pow(q: C3D_FQuat, p: f32) -> C3D_FQuat; } extern "C" { - #[doc = " @brief Cross product of Quaternion and FVec3"] - #[doc = " @param[in] q Base Quaternion"] - #[doc = " @param[in] v Vector to cross"] - #[doc = " @return q×v"] + #[doc = " @brief Cross product of Quaternion and FVec3\n @param[in] q Base Quaternion\n @param[in] v Vector to cross\n @return q×v"] pub fn Quat_CrossFVec3(q: C3D_FQuat, v: C3D_FVec) -> C3D_FVec; } extern "C" { - #[doc = " @brief 3D Rotation"] - #[doc = " @param[in] q Quaternion to rotate"] - #[doc = " @param[in] axis Axis about which to rotate"] - #[doc = " @param[in] r Radians to rotate"] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] - #[doc = " @return Rotated Quaternion"] + #[doc = " @brief 3D Rotation\n @param[in] q Quaternion to rotate\n @param[in] axis Axis about which to rotate\n @param[in] r Radians to rotate\n @param[in] bRightSide Whether to transform from the right side\n @return Rotated Quaternion"] pub fn Quat_Rotate(q: C3D_FQuat, axis: C3D_FVec, r: f32, bRightSide: bool) -> C3D_FQuat; } extern "C" { - #[doc = " @brief 3D Rotation about the X axis"] - #[doc = " @param[in] q Quaternion to rotate"] - #[doc = " @param[in] r Radians to rotate"] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] - #[doc = " @return Rotated Quaternion"] + #[doc = " @brief 3D Rotation about the X axis\n @param[in] q Quaternion to rotate\n @param[in] r Radians to rotate\n @param[in] bRightSide Whether to transform from the right side\n @return Rotated Quaternion"] pub fn Quat_RotateX(q: C3D_FQuat, r: f32, bRightSide: bool) -> C3D_FQuat; } extern "C" { - #[doc = " @brief 3D Rotation about the Y axis"] - #[doc = " @param[in] q Quaternion to rotate"] - #[doc = " @param[in] r Radians to rotate"] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] - #[doc = " @return Rotated Quaternion"] + #[doc = " @brief 3D Rotation about the Y axis\n @param[in] q Quaternion to rotate\n @param[in] r Radians to rotate\n @param[in] bRightSide Whether to transform from the right side\n @return Rotated Quaternion"] pub fn Quat_RotateY(q: C3D_FQuat, r: f32, bRightSide: bool) -> C3D_FQuat; } extern "C" { - #[doc = " @brief 3D Rotation about the Z axis"] - #[doc = " @param[in] q Quaternion to rotate"] - #[doc = " @param[in] r Radians to rotate"] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] - #[doc = " @return Rotated Quaternion"] + #[doc = " @brief 3D Rotation about the Z axis\n @param[in] q Quaternion to rotate\n @param[in] r Radians to rotate\n @param[in] bRightSide Whether to transform from the right side\n @return Rotated Quaternion"] pub fn Quat_RotateZ(q: C3D_FQuat, r: f32, bRightSide: bool) -> C3D_FQuat; } extern "C" { - #[doc = " @brief Get Quaternion equivalent to 4x4 matrix"] - #[doc = " @note If the matrix is orthogonal or special orthogonal, where determinant(matrix) = +1.0f, then the matrix can be converted."] - #[doc = " @param[in] m Input Matrix"] - #[doc = " @return Generated Quaternion"] + #[doc = " @brief Get Quaternion equivalent to 4x4 matrix\n @note If the matrix is orthogonal or special orthogonal, where determinant(matrix) = +1.0f, then the matrix can be converted.\n @param[in] m Input Matrix\n @return Generated Quaternion"] pub fn Quat_FromMtx(m: *const C3D_Mtx) -> C3D_FQuat; } extern "C" { - #[doc = " @brief Converting Pitch, Yaw, and Roll to Quaternion equivalent"] - #[doc = " @param[in] pitch The pitch angle in radians."] - #[doc = " @param[in] yaw The yaw angle in radians."] - #[doc = " @param[in] roll The roll angle in radians."] - #[doc = " @param[in] bRightSide Whether to transform from the right side"] - #[doc = " @return C3D_FQuat The Quaternion equivalent with the pitch, yaw, and roll (in that order) orientations applied."] + #[doc = " @brief Converting Pitch, Yaw, and Roll to Quaternion equivalent\n @param[in] pitch The pitch angle in radians.\n @param[in] yaw The yaw angle in radians.\n @param[in] roll The roll angle in radians.\n @param[in] bRightSide Whether to transform from the right side\n @return C3D_FQuat The Quaternion equivalent with the pitch, yaw, and roll (in that order) orientations applied."] pub fn Quat_FromPitchYawRoll(pitch: f32, yaw: f32, roll: f32, bRightSide: bool) -> C3D_FQuat; } extern "C" { - #[doc = " @brief Quaternion Look-At"] - #[doc = " @param[in] source C3D_FVec Starting position. Origin of rotation."] - #[doc = " @param[in] target C3D_FVec Target position to orient towards."] - #[doc = " @param[in] forwardVector C3D_FVec The Up vector."] - #[doc = " @param[in] upVector C3D_FVec The Up vector."] - #[doc = " @return Quaternion rotation."] + #[doc = " @brief Quaternion Look-At\n @param[in] source C3D_FVec Starting position. Origin of rotation.\n @param[in] target C3D_FVec Target position to orient towards.\n @param[in] forwardVector C3D_FVec The Up vector.\n @param[in] upVector C3D_FVec The Up vector.\n @return Quaternion rotation."] pub fn Quat_LookAt( source: C3D_FVec, target: C3D_FVec, @@ -476,10 +320,7 @@ extern "C" { ) -> C3D_FQuat; } extern "C" { - #[doc = " @brief Quaternion, created from a given axis and angle in radians."] - #[doc = " @param[in] axis C3D_FVec The axis to rotate around at."] - #[doc = " @param[in] angle float The angle to rotate. Unit: Radians"] - #[doc = " @return Quaternion rotation based on the axis and angle. Axis doesn't have to be orthogonal."] + #[doc = " @brief Quaternion, created from a given axis and angle in radians.\n @param[in] axis C3D_FVec The axis to rotate around at.\n @param[in] angle float The angle to rotate. Unit: Radians\n @return Quaternion rotation based on the axis and angle. Axis doesn't have to be orthogonal."] pub fn Quat_FromAxisAngle(axis: C3D_FVec, angle: f32) -> C3D_FQuat; } #[repr(C)] @@ -1823,8 +1664,7 @@ extern "C" { ); } pub type FILE = __FILE; -#[doc = " @brief Subtexture"] -#[doc = " @note If top > bottom, the subtexture is rotated 1/4 revolution counter-clockwise"] +#[doc = " @brief Subtexture\n @note If top > bottom, the subtexture is rotated 1/4 revolution counter-clockwise"] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct Tex3DS_SubTexture { @@ -1849,13 +1689,7 @@ pub struct Tex3DS_Texture_s { #[doc = " @brief Texture"] pub type Tex3DS_Texture = *mut Tex3DS_Texture_s; extern "C" { - #[doc = " @brief Import Tex3DS texture"] - #[doc = " @param[in] input Input data"] - #[doc = " @param[in] insize Size of the input data"] - #[doc = " @param[out] tex citro3d texture"] - #[doc = " @param[out] texcube citro3d texcube"] - #[doc = " @param[in] vram Whether to store textures in VRAM"] - #[doc = " @returns Tex3DS texture"] + #[doc = " @brief Import Tex3DS texture\n @param[in] input Input data\n @param[in] insize Size of the input data\n @param[out] tex citro3d texture\n @param[out] texcube citro3d texcube\n @param[in] vram Whether to store textures in VRAM\n @returns Tex3DS texture"] pub fn Tex3DS_TextureImport( input: *const ::libc::c_void, insize: usize, @@ -1865,18 +1699,7 @@ extern "C" { ) -> Tex3DS_Texture; } extern "C" { - #[doc = " @brief Import Tex3DS texture"] - #[doc = ""] - #[doc = " @description"] - #[doc = " For example, use this if you want to import from a large file without"] - #[doc = " pulling the entire file into memory."] - #[doc = ""] - #[doc = " @param[out] tex citro3d texture"] - #[doc = " @param[out] texcube citro3d texcube"] - #[doc = " @param[in] vram Whether to store textures in VRAM"] - #[doc = " @param[in] callback Data callback"] - #[doc = " @param[in] userdata User data passed to callback"] - #[doc = " @returns Tex3DS texture"] + #[doc = " @brief Import Tex3DS texture\n\n @description\n For example, use this if you want to import from a large file without\n pulling the entire file into memory.\n\n @param[out] tex citro3d texture\n @param[out] texcube citro3d texcube\n @param[in] vram Whether to store textures in VRAM\n @param[in] callback Data callback\n @param[in] userdata User data passed to callback\n @returns Tex3DS texture"] pub fn Tex3DS_TextureImportCallback( tex: *mut C3D_Tex, texcube: *mut C3D_TexCube, @@ -1886,17 +1709,7 @@ extern "C" { ) -> Tex3DS_Texture; } extern "C" { - #[doc = " @brief Import Tex3DS texture"] - #[doc = ""] - #[doc = " Starts reading at the current file descriptor's offset. The file"] - #[doc = " descriptor's position is left at the end of the decoded data. On error, the"] - #[doc = " file descriptor's position is indeterminate."] - #[doc = ""] - #[doc = " @param[in] fd Open file descriptor"] - #[doc = " @param[out] tex citro3d texture"] - #[doc = " @param[out] texcube citro3d texcube"] - #[doc = " @param[in] vram Whether to store textures in VRAM"] - #[doc = " @returns Tex3DS texture"] + #[doc = " @brief Import Tex3DS texture\n\n Starts reading at the current file descriptor's offset. The file\n descriptor's position is left at the end of the decoded data. On error, the\n file descriptor's position is indeterminate.\n\n @param[in] fd Open file descriptor\n @param[out] tex citro3d texture\n @param[out] texcube citro3d texcube\n @param[in] vram Whether to store textures in VRAM\n @returns Tex3DS texture"] pub fn Tex3DS_TextureImportFD( fd: ::libc::c_int, tex: *mut C3D_Tex, @@ -1905,17 +1718,7 @@ extern "C" { ) -> Tex3DS_Texture; } extern "C" { - #[doc = " @brief Import Tex3DS texture"] - #[doc = ""] - #[doc = " Starts reading at the current file stream's offset. The file stream's"] - #[doc = " position is left at the end of the decoded data. On error, the file"] - #[doc = " stream's position is indeterminate."] - #[doc = ""] - #[doc = " @param[in] fp Open file stream"] - #[doc = " @param[out] tex citro3d texture"] - #[doc = " @param[out] texcube citro3d texcube"] - #[doc = " @param[in] vram Whether to store textures in VRAM"] - #[doc = " @returns Tex3DS texture"] + #[doc = " @brief Import Tex3DS texture\n\n Starts reading at the current file stream's offset. The file stream's\n position is left at the end of the decoded data. On error, the file\n stream's position is indeterminate.\n\n @param[in] fp Open file stream\n @param[out] tex citro3d texture\n @param[out] texcube citro3d texcube\n @param[in] vram Whether to store textures in VRAM\n @returns Tex3DS texture"] pub fn Tex3DS_TextureImportStdio( fp: *mut FILE, tex: *mut C3D_Tex, @@ -1924,20 +1727,14 @@ extern "C" { ) -> Tex3DS_Texture; } extern "C" { - #[doc = " @brief Get number of subtextures"] - #[doc = " @param[in] texture Tex3DS texture"] - #[doc = " @returns Number of subtextures"] + #[doc = " @brief Get number of subtextures\n @param[in] texture Tex3DS texture\n @returns Number of subtextures"] pub fn Tex3DS_GetNumSubTextures(texture: Tex3DS_Texture) -> usize; } extern "C" { - #[doc = " @brief Get subtexture"] - #[doc = " @param[in] texture Tex3DS texture"] - #[doc = " @param[in] index Subtexture index"] - #[doc = " @returns Subtexture info"] + #[doc = " @brief Get subtexture\n @param[in] texture Tex3DS texture\n @param[in] index Subtexture index\n @returns Subtexture info"] pub fn Tex3DS_GetSubTexture(texture: Tex3DS_Texture, index: usize) -> *const Tex3DS_SubTexture; } extern "C" { - #[doc = " @brief Free Tex3DS texture"] - #[doc = " @param[in] texture Tex3DS texture to free"] + #[doc = " @brief Free Tex3DS texture\n @param[in] texture Tex3DS texture to free"] pub fn Tex3DS_TextureFree(texture: Tex3DS_Texture); } diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 50831e0..1b3a9a0 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -1,5 +1,7 @@ #![feature(allocator_api)] +use citro3d::attrib::{self, AttrInfo}; +use citro3d::buffers::BufInfo; use citro3d::render::{ClearFlags, Target}; use citro3d::{include_aligned_bytes, shader}; use citro3d_sys::C3D_Mtx; @@ -51,8 +53,6 @@ static SHADER_BYTES: &[u8] = include_aligned_bytes!(concat!(env!("OUT_DIR"), "/examples/assets/vshader.shbin")); fn main() { - ctru::init(); - let mut soc = Soc::init().expect("failed to get SOC"); drop(soc.redirect_to_3dslink(true, true)); @@ -121,10 +121,38 @@ fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mt ); // 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, ctru_sys::GPU_FLOAT, 3); // v0=position - citro3d_sys::AttrInfo_AddLoader(attr_info, 1, ctru_sys::GPU_FLOAT, 3); // v1=color + let mut attr_info = AttrInfo::get_mut().expect("failed to get global attr info"); + + let reg0 = attrib::Register::new(0).unwrap(); + let reg1 = attrib::Register::new(1).unwrap(); + + // The default permutation would actually already be what we want if we + // inserted position, then color, but just show that it's customizable + // by swapping the order then using `set_permutation`. + + let color_attr = attr_info + .add_loader(reg0, attrib::Format::Float, 3) + .unwrap(); + + let position_attr = attr_info + .add_loader(reg1, attrib::Format::Float, 3) + .unwrap(); + + eprintln!( + "count {} permutation {:#x}", + attr_info.count(), + attr_info.permutation() + ); + + attr_info + .set_permutation(&[position_attr, color_attr]) + .unwrap(); + + eprintln!( + "count {} permutation {:#x}", + attr_info.count(), + attr_info.permutation() + ); // Compute the projection matrix let projection = { @@ -144,17 +172,8 @@ fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mt }; // Configure buffers - let buf_info = citro3d_sys::C3D_GetBufInfo(); - citro3d_sys::BufInfo_Init(buf_info); - citro3d_sys::BufInfo_Add( - buf_info, - vbo_data.as_ptr().cast(), - std::mem::size_of::() - .try_into() - .expect("size of vec3 fits in u32"), - 2, // Each vertex has two attributes - 0x10, // v0 = position, v1 = color, in LSB->MSB nibble order - ); + let mut buf_info = BufInfo::get_mut().unwrap(); + buf_info.add(vbo_data, &attr_info).unwrap(); // 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 diff --git a/citro3d/src/attrib.rs b/citro3d/src/attrib.rs new file mode 100644 index 0000000..d4ee14b --- /dev/null +++ b/citro3d/src/attrib.rs @@ -0,0 +1,117 @@ +use std::ops::{Deref, DerefMut}; +use std::sync::{LazyLock, RwLock}; + +static ATTR_INFO: LazyLock> = LazyLock::new(|| { + let raw = unsafe { + // TODO: should we check is_null() here? + let info = citro3d_sys::C3D_GetAttrInfo(); + citro3d_sys::AttrInfo_Init(info); + info + }; + + RwLock::new(AttrInfo { raw }) +}); + +pub struct AttrInfo { + raw: *mut citro3d_sys::C3D_AttrInfo, +} + +pub struct Register(libc::c_int); + +impl Register { + pub fn new(n: u16) -> crate::Result { + // TODO proper validation for attributes? Or maybe just a next() function + // that gets atomically increasing indices or something? Or look at + // and define some consts + // or lookup functions + Ok(Self(n as _)) + } +} + +#[must_use] +pub struct Index(libc::c_int); + +#[repr(u32)] +pub enum Format { + Byte = ctru_sys::GPU_BYTE, + UnsignedByte = ctru_sys::GPU_UNSIGNED_BYTE, + Float = ctru_sys::GPU_FLOAT, + Short = ctru_sys::GPU_SHORT, +} + +// SAFETY: the RWLock ensures unique access when mutating the global struct, and +// we trust citro3d to Do The Right Thing™ and not mutate it otherwise. +unsafe impl Sync for AttrInfo {} +unsafe impl Send for AttrInfo {} + +impl AttrInfo { + /// Get a reference to the global attribute info. + pub fn get() -> crate::Result> { + Ok(ATTR_INFO.try_read()?) + } + + /// Get a mutable reference to the global attribute info. + pub fn get_mut() -> crate::Result> { + Ok(ATTR_INFO.try_write()?) + } + + /// Add an attribute loader to the attribute info. By default, the resulting + /// attribute index will be appended to the permutation + pub fn add_loader( + &mut self, + register: Register, + format: Format, + count: usize, + ) -> crate::Result { + let count = count.try_into()?; + + let idx = + unsafe { citro3d_sys::AttrInfo_AddLoader(self.raw, register.0, format as u32, count) }; + + Ok(Index(idx)) + } + + pub fn set_permutation(&mut self, indices: &[Index]) -> crate::Result<()> { + if indices.len() > 16 { + return Err(crate::Error::TooManyAttributes); + } + + let mut bytes: Vec = indices + .windows(2) + .map(|window| { + let [lo, hi] = match *window { + [Index(lo), Index(hi)] => [lo, hi], + [Index(lo)] => [lo, 0], // high nibble is just padding + _ => unreachable!(), // window size of 2 == always 1 or 2 elements + }; + // each value is a nibble, combine them into a byte + lo as u8 | (hi as u8) << 4 + }) + .collect(); + + // pad the remainder with zeros + bytes.extend(std::iter::repeat(0).take(8 - bytes.len())); + + let permutation = bytemuck::cast(<[u8; 8]>::try_from(bytes).unwrap()); + + unsafe { + (*self.raw).permutation = permutation; + (*self.raw).attrCount = indices.len() as _; + } + Ok(()) + } + + /// Get the current permutation of input register to vertex attributes mapping. + /// See [GPU/Internal Registers] for an explanation of how the bits are laid out + /// in the resulting value. + /// + /// [GPU/Internal Registers]: https://3dbrew.org/wiki/GPU/Internal_Registers#GPUREG_SH_ATTRIBUTES_PERMUTATION_LOW + pub fn permutation(&self) -> u64 { + unsafe { (*self.raw).permutation } + } + + /// Get the number of attributes in the current permutation. + pub fn count(&self) -> libc::c_int { + unsafe { (*self.raw).attrCount } + } +} diff --git a/citro3d/src/buffers.rs b/citro3d/src/buffers.rs new file mode 100644 index 0000000..6ad09f8 --- /dev/null +++ b/citro3d/src/buffers.rs @@ -0,0 +1,55 @@ +use std::ops::{Deref, DerefMut}; +use std::sync::{LazyLock, RwLock}; + +use crate::attrib; + +static BUF_INFO: LazyLock> = LazyLock::new(|| { + let raw = unsafe { + let info = citro3d_sys::C3D_GetBufInfo(); + citro3d_sys::BufInfo_Init(info); + info + }; + + RwLock::new(BufInfo { raw }) +}); + +pub struct BufInfo { + raw: *mut citro3d_sys::C3D_BufInfo, +} + +// SAFETY: the RWLock ensures unique access when mutating the global struct, and +// we trust citro3d to Do The Right Thing™ and not mutate it otherwise. +unsafe impl Sync for BufInfo {} +unsafe impl Send for BufInfo {} + +impl BufInfo { + /// Get a reference to the global buffer info. + pub fn get() -> crate::Result> { + Ok(BUF_INFO.try_read()?) + } + + /// Get a mutable reference to the global buffer info. + pub fn get_mut() -> crate::Result> { + Ok(BUF_INFO.try_write()?) + } + + pub fn add(&mut self, vbo_data: &[T], attrib_info: &attrib::AttrInfo) -> crate::Result<()> { + let stride = std::mem::size_of::().try_into()?; + let attrib_count = attrib_info.count(); + let permutation = attrib_info.permutation(); + + unsafe { + citro3d_sys::BufInfo_Add( + self.raw, + // TODO: figure out how the hell to encode the lifetime of this + // data so that we don't try to use it after it's destroyed... + vbo_data.as_ptr().cast(), + stride, + attrib_count, + permutation, + ); + } + + Ok(()) + } +} diff --git a/citro3d/src/error.rs b/citro3d/src/error.rs index b0f2c03..7451763 100644 --- a/citro3d/src/error.rs +++ b/citro3d/src/error.rs @@ -1,20 +1,31 @@ //! General-purpose error and result types returned by public APIs of this crate. use std::num::TryFromIntError; +use std::sync::TryLockError; /// The common result type returned by `citro3d` functions. pub type Result = std::result::Result; +// TODO probably want a similar type to ctru::Result to make it easier to convert +// nonzero result codes to errors. + /// The common error type that may be returned by `citro3d` functions. #[non_exhaustive] #[derive(Debug)] pub enum Error { + /// C3D error code. + System(libc::c_int), /// A C3D object or context could not be initialized. FailedToInitialize, /// A size parameter was specified that cannot be converted to the proper type. InvalidSize, /// Failed to select the given render target for drawing to. InvalidRenderTarget, + /// Indicates that a reference could not be obtained because a lock is already + /// held on the requested object. + LockHeld, + /// Indicates that too many vertex attributes were specified (max 16 supported). + TooManyAttributes, } impl From for Error { @@ -22,3 +33,9 @@ impl From for Error { Self::InvalidSize } } + +impl From> for Error { + fn from(_: TryLockError) -> Self { + Self::LockHeld + } +} diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index e544fb0..4e3bf09 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -1,10 +1,13 @@ +#![feature(once_cell)] + //! Safe Rust bindings to `citro3d`. +pub mod attrib; +pub mod buffers; pub mod error; pub mod render; pub mod shader; pub mod texture; -pub mod vbo; use citro3d_sys::C3D_FrameDrawOn; pub use error::{Error, Result}; diff --git a/citro3d/src/vbo.rs b/citro3d/src/vbo.rs deleted file mode 100644 index 8b13789..0000000 --- a/citro3d/src/vbo.rs +++ /dev/null @@ -1 +0,0 @@ - From ec91f7d2fe20878cfc5700f5c1721a4a3986d815 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 4 Mar 2023 11:58:17 -0500 Subject: [PATCH 02/11] Use the lifetime of the input VBO data We can return a "handle" that stores the index + size of the VBO data, as well as keeping a phantom borrow on the original data. By requiring this handle as input during the call to draw arrays, we ensure the VBO data lives long enough for the draw call. --- citro3d/examples/triangle.rs | 104 +++++++++++++++-------------------- citro3d/src/buffers.rs | 50 +++++++++++++++-- citro3d/src/render.rs | 12 +++- 3 files changed, 101 insertions(+), 65 deletions(-) diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 1b3a9a0..6309927 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -1,7 +1,7 @@ #![feature(allocator_api)] use citro3d::attrib::{self, AttrInfo}; -use citro3d::buffers::BufInfo; +use citro3d::buffers::{self, BufInfo}; use citro3d::render::{ClearFlags, Target}; use citro3d::{include_aligned_bytes, shader}; use citro3d_sys::C3D_Mtx; @@ -81,8 +81,9 @@ fn main() { let mut vbo_data = Vec::with_capacity_in(VERTICES.len(), ctru::linear::LinearAllocator); vbo_data.extend_from_slice(VERTICES); + let vbo_idx = prepare_vbos(&vbo_data); - let (uloc_projection, projection) = scene_init(&mut program, &vbo_data); + let (uloc_projection, projection) = scene_init(&mut program); while apt.main_loop() { hid.scan_input(); @@ -99,7 +100,17 @@ fn main() { let clear_color: u32 = 0x7F_7F_7F_FF; target.clear(ClearFlags::ALL, clear_color, 0); - scene_render(uloc_projection.into(), &projection); + + unsafe { + // Update the uniforms + citro3d_sys::C3D_FVUnifMtx4x4( + ctru_sys::GPU_VERTEX_SHADER, + uloc_projection.into(), + &projection, + ); + } + + target.draw_arrays(buffers::Primitive::Triangles, vbo_idx); }); }; @@ -108,50 +119,46 @@ fn main() { } } -fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mtx) { - // Load the vertex shader, create a shader program and bind it - unsafe { - citro3d_sys::C3D_BindProgram(program.as_raw()); +fn prepare_vbos(vbo_data: &[Vertex]) -> buffers::Index { + // Configure attributes for use with the vertex shader + let mut attr_info = AttrInfo::get_mut().expect("failed to get global attr info"); - // Get the location of the uniforms - let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap(); - let uloc_projection = ctru_sys::shaderInstanceGetUniformLocation( - (*program.as_raw()).vertexShader, - projection_name.as_ptr(), - ); + let reg0 = attrib::Register::new(0).unwrap(); + let reg1 = attrib::Register::new(1).unwrap(); - // Configure attributes for use with the vertex shader - let mut attr_info = AttrInfo::get_mut().expect("failed to get global attr info"); + // The default permutation would actually already be what we want if we + // inserted position, then color, but just show that it's customizable + // by swapping the order then using `set_permutation`. - let reg0 = attrib::Register::new(0).unwrap(); - let reg1 = attrib::Register::new(1).unwrap(); + let color_attr = attr_info + .add_loader(reg0, attrib::Format::Float, 3) + .unwrap(); - // The default permutation would actually already be what we want if we - // inserted position, then color, but just show that it's customizable - // by swapping the order then using `set_permutation`. + let position_attr = attr_info + .add_loader(reg1, attrib::Format::Float, 3) + .unwrap(); - let color_attr = attr_info - .add_loader(reg0, attrib::Format::Float, 3) - .unwrap(); + attr_info + .set_permutation(&[position_attr, color_attr]) + .unwrap(); - let position_attr = attr_info - .add_loader(reg1, attrib::Format::Float, 3) - .unwrap(); + // Configure buffers + let mut buf_info = BufInfo::get_mut().unwrap(); + let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); - eprintln!( - "count {} permutation {:#x}", - attr_info.count(), - attr_info.permutation() - ); + buf_idx +} - attr_info - .set_permutation(&[position_attr, color_attr]) - .unwrap(); +fn scene_init(program: &mut shader::Program) -> (i8, C3D_Mtx) { + // Load the vertex shader, create a shader program and bind it + unsafe { + citro3d_sys::C3D_BindProgram(program.as_raw()); - eprintln!( - "count {} permutation {:#x}", - attr_info.count(), - attr_info.permutation() + // Get the location of the uniforms + let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap(); + let uloc_projection = ctru_sys::shaderInstanceGetUniformLocation( + (*program.as_raw()).vertexShader, + projection_name.as_ptr(), ); // Compute the projection matrix @@ -171,10 +178,6 @@ fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mt projection.assume_init() }; - // Configure buffers - let mut buf_info = BufInfo::get_mut().unwrap(); - buf_info.add(vbo_data, &attr_info).unwrap(); - // Configure the first fragment shading substage to just pass through the vertex color // See https://www.opengl.org/sdk/docs/man2/xhtml/glTexEnv.xml for more insight let env = citro3d_sys::C3D_GetTexEnv(0); @@ -191,20 +194,3 @@ fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mt (uloc_projection, projection) } } - -fn scene_render(uloc_projection: i32, projection: &C3D_Mtx) { - unsafe { - // Update the uniforms - citro3d_sys::C3D_FVUnifMtx4x4(ctru_sys::GPU_VERTEX_SHADER, uloc_projection, projection); - - // Draw the VBO - citro3d_sys::C3D_DrawArrays( - ctru_sys::GPU_TRIANGLES, - 0, - VERTICES - .len() - .try_into() - .expect("VERTICES.len() fits in i32"), - ); - } -} diff --git a/citro3d/src/buffers.rs b/citro3d/src/buffers.rs index 6ad09f8..9191d3f 100644 --- a/citro3d/src/buffers.rs +++ b/citro3d/src/buffers.rs @@ -1,3 +1,4 @@ +use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::sync::{LazyLock, RwLock}; @@ -22,6 +23,33 @@ pub struct BufInfo { unsafe impl Sync for BufInfo {} unsafe impl Send for BufInfo {} +// TODO: is this a good name? It's more like a "handle" to the VBO data, or a slice. +#[derive(Debug, Clone, Copy)] +pub struct Index<'vbo> { + index: libc::c_int, + size: libc::c_int, + _data: PhantomData<&'vbo ()>, +} + +impl Index<'_> { + pub fn as_raw(&self) -> libc::c_int { + self.index + } + + pub fn size(&self) -> libc::c_int { + self.size + } +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy)] +pub enum Primitive { + Triangles = ctru_sys::GPU_TRIANGLES, + TriangleStrip = ctru_sys::GPU_TRIANGLE_STRIP, + TriangleFan = ctru_sys::GPU_TRIANGLE_FAN, + GeometryPrim = ctru_sys::GPU_GEOMETRY_PRIM, +} + impl BufInfo { /// Get a reference to the global buffer info. pub fn get() -> crate::Result> { @@ -33,12 +61,16 @@ impl BufInfo { Ok(BUF_INFO.try_write()?) } - pub fn add(&mut self, vbo_data: &[T], attrib_info: &attrib::AttrInfo) -> crate::Result<()> { + pub fn add<'vbo, T>( + &mut self, + vbo_data: &'vbo [T], + attrib_info: &attrib::AttrInfo, + ) -> crate::Result> { let stride = std::mem::size_of::().try_into()?; let attrib_count = attrib_info.count(); let permutation = attrib_info.permutation(); - unsafe { + let res = unsafe { citro3d_sys::BufInfo_Add( self.raw, // TODO: figure out how the hell to encode the lifetime of this @@ -47,9 +79,17 @@ impl BufInfo { stride, attrib_count, permutation, - ); - } + ) + }; - Ok(()) + if res < 0 { + Err(crate::Error::System(res)) + } else { + Ok(Index { + index: res, + size: vbo_data.len().try_into()?, + _data: PhantomData, + }) + } } } diff --git a/citro3d/src/render.rs b/citro3d/src/render.rs index 0a7a563..08a232f 100644 --- a/citro3d/src/render.rs +++ b/citro3d/src/render.rs @@ -10,7 +10,7 @@ use ctru::gfx::Screen; use ctru::services::gspgpu::FramebufferFormat; use ctru_sys::{GPU_COLORBUF, GPU_DEPTHBUF}; -use crate::{Error, Result}; +use crate::{buffers, Error, Result}; mod transfer; @@ -91,6 +91,16 @@ impl<'screen> Target<'screen> { pub(crate) fn as_raw(&self) -> *mut C3D_RenderTarget { self.raw } + + pub fn draw_arrays(&mut self, primitive: buffers::Primitive, index: buffers::Index) { + unsafe { + citro3d_sys::C3D_DrawArrays( + primitive as ctru_sys::GPU_Primitive_t, + index.as_raw(), + index.size(), + ); + } + } } bitflags::bitflags! { From a645ba14df87716e73833fa0b1a3a1a661b85110 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 4 Mar 2023 12:10:25 -0500 Subject: [PATCH 03/11] Move draw call to Instance Also add some notes about error handling for adding VBO data --- citro3d/examples/triangle.rs | 2 +- citro3d/src/buffers.rs | 6 ++++++ citro3d/src/lib.rs | 10 ++++++++++ citro3d/src/render.rs | 12 +----------- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 6309927..6a1e0cc 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -110,7 +110,7 @@ fn main() { ); } - target.draw_arrays(buffers::Primitive::Triangles, vbo_idx); + instance.draw_arrays(buffers::Primitive::Triangles, vbo_idx); }); }; diff --git a/citro3d/src/buffers.rs b/citro3d/src/buffers.rs index 9191d3f..9459c9f 100644 --- a/citro3d/src/buffers.rs +++ b/citro3d/src/buffers.rs @@ -83,6 +83,12 @@ impl BufInfo { }; if res < 0 { + // TODO: should we convert to a more specific error if this fails? + // It looks like the common cases are + // - too many buffers already added (max 12) + // - physical memory address in the wrong place (this can be seen by + // using default allocator instead of LinearAllocator) + // Err(crate::Error::System(res)) } else { Ok(Index { diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 4e3bf09..2ce2ec5 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -73,6 +73,16 @@ impl Instance { citro3d_sys::C3D_FrameEnd(0); } } + + pub fn draw_arrays(&mut self, primitive: buffers::Primitive, index: buffers::Index) { + unsafe { + citro3d_sys::C3D_DrawArrays( + primitive as ctru_sys::GPU_Primitive_t, + index.as_raw(), + index.size(), + ); + } + } } impl Drop for Instance { diff --git a/citro3d/src/render.rs b/citro3d/src/render.rs index 08a232f..0a7a563 100644 --- a/citro3d/src/render.rs +++ b/citro3d/src/render.rs @@ -10,7 +10,7 @@ use ctru::gfx::Screen; use ctru::services::gspgpu::FramebufferFormat; use ctru_sys::{GPU_COLORBUF, GPU_DEPTHBUF}; -use crate::{buffers, Error, Result}; +use crate::{Error, Result}; mod transfer; @@ -91,16 +91,6 @@ impl<'screen> Target<'screen> { pub(crate) fn as_raw(&self) -> *mut C3D_RenderTarget { self.raw } - - pub fn draw_arrays(&mut self, primitive: buffers::Primitive, index: buffers::Index) { - unsafe { - citro3d_sys::C3D_DrawArrays( - primitive as ctru_sys::GPU_Primitive_t, - index.as_raw(), - index.size(), - ); - } - } } bitflags::bitflags! { From ceacc3387dc7fbb247deed0f7c0741045c4d4855 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Tue, 21 Mar 2023 17:40:48 -0400 Subject: [PATCH 04/11] Minor naming cleanups --- citro3d/examples/triangle.rs | 15 ++++++++----- citro3d/src/attrib.rs | 32 ++++++++++++++++----------- citro3d/src/{buffers.rs => buffer.rs} | 18 +++++++-------- citro3d/src/shader.rs | 6 ++--- 4 files changed, 40 insertions(+), 31 deletions(-) rename citro3d/src/{buffers.rs => buffer.rs} (86%) diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 6a1e0cc..7e51ace 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -1,7 +1,10 @@ +//! This example demonstrates the most basic usage of `citro3d`: rendering a simple +//! RGB triangle (sometimes called a "Hello triangle") to the 3DS screen. + #![feature(allocator_api)] -use citro3d::attrib::{self, AttrInfo}; -use citro3d::buffers::{self, BufInfo}; +use citro3d::attrib::{self}; +use citro3d::buffer::{self}; use citro3d::render::{ClearFlags, Target}; use citro3d::{include_aligned_bytes, shader}; use citro3d_sys::C3D_Mtx; @@ -110,7 +113,7 @@ fn main() { ); } - instance.draw_arrays(buffers::Primitive::Triangles, vbo_idx); + instance.draw_arrays(buffer::Primitive::Triangles, vbo_idx); }); }; @@ -119,9 +122,9 @@ fn main() { } } -fn prepare_vbos(vbo_data: &[Vertex]) -> buffers::Index { +fn prepare_vbos(vbo_data: &[Vertex]) -> buffer::Index { // Configure attributes for use with the vertex shader - let mut attr_info = AttrInfo::get_mut().expect("failed to get global attr info"); + let mut attr_info = attrib::Info::get_mut().expect("failed to get global attr info"); let reg0 = attrib::Register::new(0).unwrap(); let reg1 = attrib::Register::new(1).unwrap(); @@ -143,7 +146,7 @@ fn prepare_vbos(vbo_data: &[Vertex]) -> buffers::Index { .unwrap(); // Configure buffers - let mut buf_info = BufInfo::get_mut().unwrap(); + let mut buf_info = buffer::Info::get_mut().unwrap(); let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); buf_idx diff --git a/citro3d/src/attrib.rs b/citro3d/src/attrib.rs index d4ee14b..5b7d354 100644 --- a/citro3d/src/attrib.rs +++ b/citro3d/src/attrib.rs @@ -1,7 +1,7 @@ use std::ops::{Deref, DerefMut}; use std::sync::{LazyLock, RwLock}; -static ATTR_INFO: LazyLock> = LazyLock::new(|| { +static INFO: LazyLock> = LazyLock::new(|| { let raw = unsafe { // TODO: should we check is_null() here? let info = citro3d_sys::C3D_GetAttrInfo(); @@ -9,13 +9,14 @@ static ATTR_INFO: LazyLock> = LazyLock::new(|| { info }; - RwLock::new(AttrInfo { raw }) + RwLock::new(Info { raw }) }); -pub struct AttrInfo { +pub struct Info { raw: *mut citro3d_sys::C3D_AttrInfo, } +#[derive(Debug, Clone, Copy)] pub struct Register(libc::c_int); impl Register { @@ -24,12 +25,12 @@ impl Register { // that gets atomically increasing indices or something? Or look at // and define some consts // or lookup functions - Ok(Self(n as _)) + Ok(Self(n.into())) } } #[must_use] -pub struct Index(libc::c_int); +pub struct Index(u8); #[repr(u32)] pub enum Format { @@ -41,18 +42,18 @@ pub enum Format { // SAFETY: the RWLock ensures unique access when mutating the global struct, and // we trust citro3d to Do The Right Thing™ and not mutate it otherwise. -unsafe impl Sync for AttrInfo {} -unsafe impl Send for AttrInfo {} +unsafe impl Sync for Info {} +unsafe impl Send for Info {} -impl AttrInfo { +impl Info { /// Get a reference to the global attribute info. pub fn get() -> crate::Result> { - Ok(ATTR_INFO.try_read()?) + Ok(INFO.try_read()?) } /// Get a mutable reference to the global attribute info. pub fn get_mut() -> crate::Result> { - Ok(ATTR_INFO.try_write()?) + Ok(INFO.try_write()?) } /// Add an attribute loader to the attribute info. By default, the resulting @@ -65,9 +66,13 @@ impl AttrInfo { ) -> crate::Result { let count = count.try_into()?; - let idx = + let ret = unsafe { citro3d_sys::AttrInfo_AddLoader(self.raw, register.0, format as u32, count) }; + let Ok(idx) = ret.try_into() else { + return Err(crate::Error::FailedToInitialize) + }; + Ok(Index(idx)) } @@ -75,6 +80,7 @@ impl AttrInfo { if indices.len() > 16 { return Err(crate::Error::TooManyAttributes); } + let attr_count: libc::c_int = indices.len().try_into().unwrap(); let mut bytes: Vec = indices .windows(2) @@ -85,7 +91,7 @@ impl AttrInfo { _ => unreachable!(), // window size of 2 == always 1 or 2 elements }; // each value is a nibble, combine them into a byte - lo as u8 | (hi as u8) << 4 + lo | (hi << 4) }) .collect(); @@ -96,7 +102,7 @@ impl AttrInfo { unsafe { (*self.raw).permutation = permutation; - (*self.raw).attrCount = indices.len() as _; + (*self.raw).attrCount = attr_count; } Ok(()) } diff --git a/citro3d/src/buffers.rs b/citro3d/src/buffer.rs similarity index 86% rename from citro3d/src/buffers.rs rename to citro3d/src/buffer.rs index 9459c9f..a41527a 100644 --- a/citro3d/src/buffers.rs +++ b/citro3d/src/buffer.rs @@ -1,27 +1,29 @@ use std::marker::PhantomData; +use std::mem::MaybeUninit; use std::ops::{Deref, DerefMut}; use std::sync::{LazyLock, RwLock}; use crate::attrib; -static BUF_INFO: LazyLock> = LazyLock::new(|| { +static BUF_INFO: LazyLock> = LazyLock::new(|| { let raw = unsafe { let info = citro3d_sys::C3D_GetBufInfo(); citro3d_sys::BufInfo_Init(info); info }; - RwLock::new(BufInfo { raw }) + RwLock::new(Info { raw }) }); -pub struct BufInfo { +/// Vertex attribute info. This struct can be used to +pub struct Info { raw: *mut citro3d_sys::C3D_BufInfo, } // SAFETY: the RWLock ensures unique access when mutating the global struct, and // we trust citro3d to Do The Right Thing™ and not mutate it otherwise. -unsafe impl Sync for BufInfo {} -unsafe impl Send for BufInfo {} +unsafe impl Sync for Info {} +unsafe impl Send for Info {} // TODO: is this a good name? It's more like a "handle" to the VBO data, or a slice. #[derive(Debug, Clone, Copy)] @@ -50,7 +52,7 @@ pub enum Primitive { GeometryPrim = ctru_sys::GPU_GEOMETRY_PRIM, } -impl BufInfo { +impl Info { /// Get a reference to the global buffer info. pub fn get() -> crate::Result> { Ok(BUF_INFO.try_read()?) @@ -64,7 +66,7 @@ impl BufInfo { pub fn add<'vbo, T>( &mut self, vbo_data: &'vbo [T], - attrib_info: &attrib::AttrInfo, + attrib_info: &attrib::Info, ) -> crate::Result> { let stride = std::mem::size_of::().try_into()?; let attrib_count = attrib_info.count(); @@ -73,8 +75,6 @@ impl BufInfo { let res = unsafe { citro3d_sys::BufInfo_Add( self.raw, - // TODO: figure out how the hell to encode the lifetime of this - // data so that we don't try to use it after it's destroyed... vbo_data.as_ptr().cast(), stride, attrib_count, diff --git a/citro3d/src/shader.rs b/citro3d/src/shader.rs index 91142ee..e962532 100644 --- a/citro3d/src/shader.rs +++ b/citro3d/src/shader.rs @@ -11,8 +11,8 @@ pub mod macros; /// A PICA200 shader program. It may have one or both of: /// -/// * A vertex [shader instance](Instance) -/// * A geometry [shader instance](Instance) +/// * A vertex shader [`Library`] +/// * A geometry shader [`Library`] /// /// The PICA200 does not support user-programmable fragment shaders. pub struct Program { @@ -86,7 +86,7 @@ impl Drop for Program { /// one or more [`Entrypoint`]s, but most commonly has one vertex shader and an /// optional geometry shader. /// -/// This is the result of parsing a shader binary (shbin), and the resulting +/// This is the result of parsing a shader binary (`.shbin`), and the resulting /// [`Entrypoint`]s can be used as part of a [`Program`]. pub struct Library(*mut ctru_sys::DVLB_s); From c4e1287d3667e65d7efceb271d78bb0fafe5eae9 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Tue, 21 Mar 2023 18:48:28 -0400 Subject: [PATCH 05/11] Try to rework buffer info to be less error-prone --- bindgen-citro3d/Cargo.toml | 1 + citro3d/examples/triangle.rs | 22 ++++++++--- citro3d/src/attrib.rs | 62 +++++++++++++++---------------- citro3d/src/buffer.rs | 71 +++++++++++++++++++----------------- citro3d/src/lib.rs | 38 ++++++++++++++++++- 5 files changed, 120 insertions(+), 74 deletions(-) diff --git a/bindgen-citro3d/Cargo.toml b/bindgen-citro3d/Cargo.toml index 86ac6e5..28137fb 100644 --- a/bindgen-citro3d/Cargo.toml +++ b/bindgen-citro3d/Cargo.toml @@ -3,6 +3,7 @@ name = "bindgen-citro3d" version = "0.1.0" edition = "2021" description = "Helper tool to generate citro3d-sys bindings." +publish = false [dependencies] bindgen = "0.62.0" diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 7e51ace..c91c8d7 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -84,7 +84,9 @@ fn main() { let mut vbo_data = Vec::with_capacity_in(VERTICES.len(), ctru::linear::LinearAllocator); vbo_data.extend_from_slice(VERTICES); - let vbo_idx = prepare_vbos(&vbo_data); + + let mut buf_info = buffer::Info::new(); + let (attr_info, vbo_idx) = prepare_vbos(&mut buf_info, &vbo_data); let (uloc_projection, projection) = scene_init(&mut program); @@ -113,6 +115,8 @@ fn main() { ); } + instance.set_attr_info(&attr_info); + instance.draw_arrays(buffer::Primitive::Triangles, vbo_idx); }); }; @@ -122,9 +126,17 @@ fn main() { } } -fn prepare_vbos(vbo_data: &[Vertex]) -> buffer::Index { +// sheeeesh, this sucks to type: +fn prepare_vbos<'buf, 'info, 'vbo>( + buf_info: &'info mut buffer::Info, + vbo_data: &'vbo [Vertex], +) -> (attrib::Info, buffer::Index<'buf>) +where + 'info: 'buf, + 'vbo: 'buf, +{ // Configure attributes for use with the vertex shader - let mut attr_info = attrib::Info::get_mut().expect("failed to get global attr info"); + let mut attr_info = attrib::Info::new(); let reg0 = attrib::Register::new(0).unwrap(); let reg1 = attrib::Register::new(1).unwrap(); @@ -145,11 +157,9 @@ fn prepare_vbos(vbo_data: &[Vertex]) -> buffer::Index { .set_permutation(&[position_attr, color_attr]) .unwrap(); - // Configure buffers - let mut buf_info = buffer::Info::get_mut().unwrap(); let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); - buf_idx + (attr_info, buf_idx) } fn scene_init(program: &mut shader::Program) -> (i8, C3D_Mtx) { diff --git a/citro3d/src/attrib.rs b/citro3d/src/attrib.rs index 5b7d354..67a75e1 100644 --- a/citro3d/src/attrib.rs +++ b/citro3d/src/attrib.rs @@ -1,22 +1,9 @@ -use std::ops::{Deref, DerefMut}; -use std::sync::{LazyLock, RwLock}; - -static INFO: LazyLock> = LazyLock::new(|| { - let raw = unsafe { - // TODO: should we check is_null() here? - let info = citro3d_sys::C3D_GetAttrInfo(); - citro3d_sys::AttrInfo_Init(info); - info - }; - - RwLock::new(Info { raw }) -}); - -pub struct Info { - raw: *mut citro3d_sys::C3D_AttrInfo, -} +use std::mem::MaybeUninit; + +#[derive(Debug)] +pub struct Info(pub(crate) citro3d_sys::C3D_AttrInfo); -#[derive(Debug, Clone, Copy)] +#[derive(Debug)] pub struct Register(libc::c_int); impl Register { @@ -46,14 +33,23 @@ unsafe impl Sync for Info {} unsafe impl Send for Info {} impl Info { - /// Get a reference to the global attribute info. - pub fn get() -> crate::Result> { - Ok(INFO.try_read()?) + pub fn new() -> Self { + let mut raw = MaybeUninit::zeroed(); + let raw = unsafe { + citro3d_sys::AttrInfo_Init(raw.as_mut_ptr()); + raw.assume_init() + }; + Self(raw) } - /// Get a mutable reference to the global attribute info. - pub fn get_mut() -> crate::Result> { - Ok(INFO.try_write()?) + pub(crate) fn copy_from(raw: *const citro3d_sys::C3D_AttrInfo) -> Option { + if raw.is_null() { + None + } else { + // This is less efficient than returning a pointer or something, but it's + // safer since we don't know the lifetime of the pointee + Some(Self(unsafe { *raw })) + } } /// Add an attribute loader to the attribute info. By default, the resulting @@ -66,8 +62,11 @@ impl Info { ) -> crate::Result { let count = count.try_into()?; - let ret = - unsafe { citro3d_sys::AttrInfo_AddLoader(self.raw, register.0, format as u32, count) }; + // SAFETY: the &mut self.0 reference is only used to access fields in + // the attribute info, not stored somewhere for later use + let ret = unsafe { + citro3d_sys::AttrInfo_AddLoader(&mut self.0, register.0, format as u32, count) + }; let Ok(idx) = ret.try_into() else { return Err(crate::Error::FailedToInitialize) @@ -100,10 +99,9 @@ impl Info { let permutation = bytemuck::cast(<[u8; 8]>::try_from(bytes).unwrap()); - unsafe { - (*self.raw).permutation = permutation; - (*self.raw).attrCount = attr_count; - } + self.0.permutation = permutation; + self.0.attrCount = attr_count; + Ok(()) } @@ -113,11 +111,11 @@ impl Info { /// /// [GPU/Internal Registers]: https://3dbrew.org/wiki/GPU/Internal_Registers#GPUREG_SH_ATTRIBUTES_PERMUTATION_LOW pub fn permutation(&self) -> u64 { - unsafe { (*self.raw).permutation } + self.0.permutation } /// Get the number of attributes in the current permutation. pub fn count(&self) -> libc::c_int { - unsafe { (*self.raw).attrCount } + self.0.attrCount } } diff --git a/citro3d/src/buffer.rs b/citro3d/src/buffer.rs index a41527a..86e0600 100644 --- a/citro3d/src/buffer.rs +++ b/citro3d/src/buffer.rs @@ -1,36 +1,18 @@ use std::marker::PhantomData; use std::mem::MaybeUninit; -use std::ops::{Deref, DerefMut}; -use std::sync::{LazyLock, RwLock}; use crate::attrib; -static BUF_INFO: LazyLock> = LazyLock::new(|| { - let raw = unsafe { - let info = citro3d_sys::C3D_GetBufInfo(); - citro3d_sys::BufInfo_Init(info); - info - }; - - RwLock::new(Info { raw }) -}); - -/// Vertex attribute info. This struct can be used to -pub struct Info { - raw: *mut citro3d_sys::C3D_BufInfo, -} - -// SAFETY: the RWLock ensures unique access when mutating the global struct, and -// we trust citro3d to Do The Right Thing™ and not mutate it otherwise. -unsafe impl Sync for Info {} -unsafe impl Send for Info {} +#[derive(Debug)] +pub struct Info(pub(crate) citro3d_sys::C3D_BufInfo); // TODO: is this a good name? It's more like a "handle" to the VBO data, or a slice. #[derive(Debug, Clone, Copy)] -pub struct Index<'vbo> { +pub struct Index<'buf> { index: libc::c_int, size: libc::c_int, - _data: PhantomData<&'vbo ()>, + _vbo_data: PhantomData<&'buf ()>, + buf_info: &'buf Info, } impl Index<'_> { @@ -41,6 +23,10 @@ impl Index<'_> { pub fn size(&self) -> libc::c_int { self.size } + + pub fn info(&self) -> &Info { + self.buf_info + } } #[repr(u32)] @@ -53,28 +39,44 @@ pub enum Primitive { } impl Info { - /// Get a reference to the global buffer info. - pub fn get() -> crate::Result> { - Ok(BUF_INFO.try_read()?) + pub fn new() -> Self { + let mut info = MaybeUninit::zeroed(); + let info = unsafe { + citro3d_sys::BufInfo_Init(info.as_mut_ptr()); + info.assume_init() + }; + Self(info) } - /// Get a mutable reference to the global buffer info. - pub fn get_mut() -> crate::Result> { - Ok(BUF_INFO.try_write()?) + pub(crate) fn copy_from(raw: *const citro3d_sys::C3D_BufInfo) -> Option { + if raw.is_null() { + None + } else { + // This is less efficient than returning a pointer or something, but it's + // safer since we don't know the lifetime of the pointee + Some(Self(unsafe { *raw })) + } } - pub fn add<'vbo, T>( - &mut self, + pub fn add<'this, 'vbo, 'idx, T>( + &'this mut self, vbo_data: &'vbo [T], attrib_info: &attrib::Info, - ) -> crate::Result> { + ) -> crate::Result> + where + 'this: 'idx, + 'vbo: 'idx, + { let stride = std::mem::size_of::().try_into()?; let attrib_count = attrib_info.count(); let permutation = attrib_info.permutation(); + // SAFETY: the lifetime of the VBO data is encapsulated in the return value's + // 'vbo lifetime, and the pointer to &mut self.0 is used to access values + // in the BufInfo, not copied to be used later. let res = unsafe { citro3d_sys::BufInfo_Add( - self.raw, + &mut self.0, vbo_data.as_ptr().cast(), stride, attrib_count, @@ -94,7 +96,8 @@ impl Info { Ok(Index { index: res, size: vbo_data.len().try_into()?, - _data: PhantomData, + _vbo_data: PhantomData, + buf_info: self, }) } } diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 2ce2ec5..0f3f9c0 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -3,7 +3,7 @@ //! Safe Rust bindings to `citro3d`. pub mod attrib; -pub mod buffers; +pub mod buffer; pub mod error; pub mod render; pub mod shader; @@ -15,6 +15,7 @@ pub use error::{Error, Result}; /// The single instance for using `citro3d`. This is the base type that an application /// should instantiate to use this library. #[non_exhaustive] +#[must_use] #[derive(Debug)] pub struct Instance; @@ -74,7 +75,40 @@ impl Instance { } } - pub fn draw_arrays(&mut self, primitive: buffers::Primitive, index: buffers::Index) { + /// Get the buffer info being used, if it exists. Note that the resulting + /// [`buffer::Info`] is copied from the one currently in use. + pub fn buffer_info(&self) -> Option { + let raw = unsafe { citro3d_sys::C3D_GetBufInfo() }; + buffer::Info::copy_from(raw) + } + + /// Set the buffer info to use for any following draw calls. + pub fn set_buffer_info(&mut self, buffer_info: &buffer::Info) { + let raw: *const _ = &buffer_info.0; + // SAFETY: C3D_SetBufInfo actually copies the pointee instead of mutating it. + unsafe { citro3d_sys::C3D_SetBufInfo(raw.cast_mut()) }; + } + + /// Get the attribute info being used, if it exists. Note that the resulting + /// [`attrib::Info`] is copied from the one currently in use. + pub fn attr_info(&self) -> Option { + let raw = unsafe { citro3d_sys::C3D_GetAttrInfo() }; + attrib::Info::copy_from(raw) + } + + /// Set the attribute info to use for any following draw calls. + pub fn set_attr_info(&mut self, attr_info: &attrib::Info) { + let raw: *const _ = &attr_info.0; + // SAFETY: C3D_SetAttrInfo actually copies the pointee instead of mutating it. + unsafe { citro3d_sys::C3D_SetAttrInfo(raw.cast_mut()) }; + } + + /// Draw the specified primitivearrays. The + pub fn draw_arrays(&mut self, primitive: buffer::Primitive, index: buffer::Index) { + self.set_buffer_info(index.info()); + + // TODO: should we also require the attrib info directly here? + unsafe { citro3d_sys::C3D_DrawArrays( primitive as ctru_sys::GPU_Primitive_t, From 4fa59ada84fcf4ba16de3a87594298a17b5875ff Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Fri, 14 Apr 2023 11:50:29 -0400 Subject: [PATCH 06/11] Update example for 3D and new gfx APIs --- Cargo.toml | 1 + citro3d/examples/triangle.rs | 69 ++++++++++++++++++++++-------------- citro3d/src/render.rs | 12 +++---- 3 files changed, 49 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 248e09b..1ceb75b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = ["citro3d-sys", "citro3d", "bindgen-citro3d"] +default-members = ["citro3d", "citro3d-sys"] [patch."https://github.com/rust3ds/citro3d-rs.git"] citro3d-sys = { path = "citro3d-sys" } diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 50831e0..b1154ea 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -3,11 +3,10 @@ use citro3d::render::{ClearFlags, Target}; use citro3d::{include_aligned_bytes, shader}; use citro3d_sys::C3D_Mtx; -use ctru::gfx::{Gfx, RawFrameBuffer, Screen}; -use ctru::services::apt::Apt; -use ctru::services::hid::{Hid, KeyPad}; -use ctru::services::soc::Soc; +use ctru::prelude::*; +use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D}; +use std::f32::consts::PI; use std::ffi::CStr; use std::mem::MaybeUninit; @@ -51,25 +50,31 @@ static SHADER_BYTES: &[u8] = include_aligned_bytes!(concat!(env!("OUT_DIR"), "/examples/assets/vshader.shbin")); fn main() { - ctru::init(); + ctru::use_panic_handler(); - let mut soc = Soc::init().expect("failed to get SOC"); + let mut soc = Soc::new().expect("failed to get SOC"); drop(soc.redirect_to_3dslink(true, true)); - let gfx = Gfx::init().expect("Couldn't obtain GFX controller"); - let hid = Hid::init().expect("Couldn't obtain HID controller"); - let apt = Apt::init().expect("Couldn't obtain APT controller"); - - let mut top_screen = gfx.top_screen.borrow_mut(); - let RawFrameBuffer { width, height, .. } = top_screen.get_raw_framebuffer(); + 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 mut top_target = citro3d::render::Target::new(width, height, top_screen, None) + 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 = citro3d::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 = citro3d::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.get_raw_framebuffer(); + let RawFrameBuffer { width, height, .. } = bottom_screen.raw_framebuffer(); let mut bottom_target = citro3d::render::Target::new(width, height, bottom_screen, None) .expect("failed to create bottom screen render target"); @@ -82,29 +87,35 @@ fn main() { let mut vbo_data = Vec::with_capacity_in(VERTICES.len(), ctru::linear::LinearAllocator); vbo_data.extend_from_slice(VERTICES); - let (uloc_projection, projection) = scene_init(&mut program, &vbo_data); + let (projection_uniform_idx, mut projection) = scene_init(&mut program, &vbo_data); + + unsafe { citro3d_sys::Mtx_RotateY(&mut projection, -PI / 12.0, true) }; + + let mut right_eye_projection = projection; + unsafe { citro3d_sys::Mtx_RotateY(&mut right_eye_projection, 2.0 * PI / 12.0, true) }; while apt.main_loop() { hid.scan_input(); - if hid.keys_down().contains(KeyPad::KEY_START) { + if hid.keys_down().contains(KeyPad::START) { break; } - let mut render_to = |target: &mut Target| { - instance.render_frame_with(|instance| { + instance.render_frame_with(|instance| { + let mut render_to = |target: &mut Target, projection| { instance .select_render_target(target) .expect("failed to set render target"); let clear_color: u32 = 0x7F_7F_7F_FF; target.clear(ClearFlags::ALL, clear_color, 0); - scene_render(uloc_projection.into(), &projection); - }); - }; + scene_render(projection_uniform_idx.into(), projection); + }; - render_to(&mut top_target); - render_to(&mut bottom_target); + render_to(&mut top_left_target, &projection); + render_to(&mut top_right_target, &right_eye_projection); + render_to(&mut bottom_target, &projection); + }); } } @@ -115,7 +126,7 @@ fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mt // Get the location of the uniforms let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap(); - let uloc_projection = ctru_sys::shaderInstanceGetUniformLocation( + let projection_uniform_idx = ctru_sys::shaderInstanceGetUniformLocation( (*program.as_raw()).vertexShader, projection_name.as_ptr(), ); @@ -169,14 +180,18 @@ fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mt ); citro3d_sys::C3D_TexEnvFunc(env, citro3d_sys::C3D_Both, ctru_sys::GPU_REPLACE); - (uloc_projection, projection) + (projection_uniform_idx, projection) } } -fn scene_render(uloc_projection: i32, projection: &C3D_Mtx) { +fn scene_render(projection_uniform_idx: i32, projection: &C3D_Mtx) { unsafe { // Update the uniforms - citro3d_sys::C3D_FVUnifMtx4x4(ctru_sys::GPU_VERTEX_SHADER, uloc_projection, projection); + citro3d_sys::C3D_FVUnifMtx4x4( + ctru_sys::GPU_VERTEX_SHADER, + projection_uniform_idx, + projection, + ); // Draw the VBO citro3d_sys::C3D_DrawArrays( diff --git a/citro3d/src/render.rs b/citro3d/src/render.rs index 0a7a563..16bd48b 100644 --- a/citro3d/src/render.rs +++ b/citro3d/src/render.rs @@ -6,7 +6,7 @@ use std::cell::RefMut; use citro3d_sys::{ C3D_RenderTarget, C3D_RenderTargetCreate, C3D_RenderTargetDelete, C3D_DEPTHTYPE, }; -use ctru::gfx::Screen; +use ctru::services::gfx::Screen; use ctru::services::gspgpu::FramebufferFormat; use ctru_sys::{GPU_COLORBUF, GPU_DEPTHBUF}; @@ -39,17 +39,17 @@ impl<'screen> Target<'screen> { /// /// Fails if the target could not be created. pub fn new( - width: u16, - height: u16, + width: usize, + height: usize, screen: RefMut<'screen, dyn Screen>, depth_format: Option, ) -> Result { - let color_format: ColorFormat = screen.get_framebuffer_format().into(); + let color_format: ColorFormat = screen.framebuffer_format().into(); let raw = unsafe { C3D_RenderTargetCreate( - width.into(), - height.into(), + width.try_into()?, + height.try_into()?, color_format as GPU_COLORBUF, depth_format.map_or(C3D_DEPTHTYPE { __i: -1 }, DepthFormat::as_raw), ) From 009b7eb94f714511882552818932d206acef0061 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Fri, 2 Jun 2023 09:42:43 -0400 Subject: [PATCH 07/11] Update to 2021 resolver by default --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 1ceb75b..d59cf00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = ["citro3d-sys", "citro3d", "bindgen-citro3d"] default-members = ["citro3d", "citro3d-sys"] +resolver = "2" [patch."https://github.com/rust3ds/citro3d-rs.git"] citro3d-sys = { path = "citro3d-sys" } From b05a1e3ff31e248e63b6cc158fde4104e092f395 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Fri, 2 Jun 2023 10:00:10 -0400 Subject: [PATCH 08/11] Fix up a few remaining clippy errors --- citro3d/src/attrib.rs | 10 ++++++++-- citro3d/src/buffer.rs | 10 ++++++++-- citro3d/src/lib.rs | 2 -- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/citro3d/src/attrib.rs b/citro3d/src/attrib.rs index 67a75e1..ea1f76a 100644 --- a/citro3d/src/attrib.rs +++ b/citro3d/src/attrib.rs @@ -32,8 +32,8 @@ pub enum Format { unsafe impl Sync for Info {} unsafe impl Send for Info {} -impl Info { - pub fn new() -> Self { +impl Default for Info { + fn default() -> Self { let mut raw = MaybeUninit::zeroed(); let raw = unsafe { citro3d_sys::AttrInfo_Init(raw.as_mut_ptr()); @@ -41,6 +41,12 @@ impl Info { }; Self(raw) } +} + +impl Info { + pub fn new() -> Self { + Self::default() + } pub(crate) fn copy_from(raw: *const citro3d_sys::C3D_AttrInfo) -> Option { if raw.is_null() { diff --git a/citro3d/src/buffer.rs b/citro3d/src/buffer.rs index 86e0600..3bc0b66 100644 --- a/citro3d/src/buffer.rs +++ b/citro3d/src/buffer.rs @@ -38,8 +38,8 @@ pub enum Primitive { GeometryPrim = ctru_sys::GPU_GEOMETRY_PRIM, } -impl Info { - pub fn new() -> Self { +impl Default for Info { + fn default() -> Self { let mut info = MaybeUninit::zeroed(); let info = unsafe { citro3d_sys::BufInfo_Init(info.as_mut_ptr()); @@ -47,6 +47,12 @@ impl Info { }; Self(info) } +} + +impl Info { + pub fn new() -> Self { + Self::default() + } pub(crate) fn copy_from(raw: *const citro3d_sys::C3D_BufInfo) -> Option { if raw.is_null() { diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 0f3f9c0..976b21a 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(once_cell)] - //! Safe Rust bindings to `citro3d`. pub mod attrib; From f65da50b683bdc832806318e8270cdb685586076 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Fri, 2 Jun 2023 11:37:43 -0400 Subject: [PATCH 09/11] Add attrib docstrings and update some names --- citro3d/examples/triangle.rs | 14 ++--- citro3d/src/attrib.rs | 102 +++++++++++++++++------------------ citro3d/src/buffer.rs | 15 +++--- citro3d/src/error.rs | 2 +- citro3d/src/lib.rs | 2 +- 5 files changed, 62 insertions(+), 73 deletions(-) diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 19947e1..ac6ea8b 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -143,7 +143,7 @@ fn main() { fn prepare_vbos<'buf, 'info, 'vbo>( buf_info: &'info mut buffer::Info, vbo_data: &'vbo [Vertex], -) -> (attrib::Info, buffer::Index<'buf>) +) -> (attrib::Info, buffer::Slice<'buf>) where 'info: 'buf, 'vbo: 'buf, @@ -154,20 +154,12 @@ where let reg0 = attrib::Register::new(0).unwrap(); let reg1 = attrib::Register::new(1).unwrap(); - // The default permutation would actually already be what we want if we - // inserted position, then color, but just show that it's customizable - // by swapping the order then using `set_permutation`. - - let color_attr = attr_info + attr_info .add_loader(reg0, attrib::Format::Float, 3) .unwrap(); - let position_attr = attr_info - .add_loader(reg1, attrib::Format::Float, 3) - .unwrap(); - attr_info - .set_permutation(&[position_attr, color_attr]) + .add_loader(reg1, attrib::Format::Float, 5) .unwrap(); let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); diff --git a/citro3d/src/attrib.rs b/citro3d/src/attrib.rs index ea1f76a..176e46a 100644 --- a/citro3d/src/attrib.rs +++ b/citro3d/src/attrib.rs @@ -1,29 +1,49 @@ use std::mem::MaybeUninit; +/// Vertex attribute info. This struct is used to describe how vertex buffers are +/// used (i.e. the shape of the vertex data). #[derive(Debug)] pub struct Info(pub(crate) citro3d_sys::C3D_AttrInfo); -#[derive(Debug)] +/// A shader input register, usually corresponding to a single vertex attribute +/// (e.g. position or color). These are called `v0`, `v1`, ... `v15` in the +/// [picasso](https://github.com/devkitPro/picasso/blob/master/Manual.md) +/// shader language. +#[derive(Debug, Clone, Copy)] pub struct Register(libc::c_int); impl Register { + /// Get a register corresponding to the given index. + /// + /// # Errors + /// + /// Returns an error for `n >= 16`. pub fn new(n: u16) -> crate::Result { - // TODO proper validation for attributes? Or maybe just a next() function - // that gets atomically increasing indices or something? Or look at - // and define some consts - // or lookup functions - Ok(Self(n.into())) + if n < 16 { + Ok(Self(n.into())) + } else { + Err(crate::Error::TooManyAttributes) + } } } -#[must_use] +/// An attribute index. This is the attribute's actual index in the input buffer, +/// and may correspond to any [`Register`] (or multiple) as input in the shader +/// program. +#[derive(Debug, Clone, Copy)] pub struct Index(u8); +/// The data format of an attribute. #[repr(u32)] +#[derive(Debug, Clone, Copy)] pub enum Format { + /// A signed byte, i.e. [`i8`]. Byte = ctru_sys::GPU_BYTE, + /// An unsigned byte, i.e. [`u8`]. UnsignedByte = ctru_sys::GPU_UNSIGNED_BYTE, + /// A float, i.e. [`f32`]. Float = ctru_sys::GPU_FLOAT, + /// A short integer, i.e. [`i16`]. Short = ctru_sys::GPU_SHORT, } @@ -44,6 +64,7 @@ impl Default for Info { } impl Info { + /// Construct a new attribute info structure with no attributes. pub fn new() -> Self { Self::default() } @@ -58,70 +79,49 @@ impl Info { } } - /// Add an attribute loader to the attribute info. By default, the resulting - /// attribute index will be appended to the permutation + /// Add an attribute loader to the attribute info. The resulting attribute index + /// indicates the registration order of the attributes. + /// + /// ## Parameters + /// + /// * `register`: the shader program input register for this attribute. + /// * `format`: the data format of this attribute. + /// * `count`: the number of elements in each attribute (up to 4, corresponding + /// to `xyzw` / `rgba` / `stpq`). + /// + /// ## Errors + /// + /// * If `count > 4` + /// * If this attribute info already has the maximum number of attributes. pub fn add_loader( &mut self, register: Register, format: Format, - count: usize, + count: u8, ) -> crate::Result { - let count = count.try_into()?; + if count > 4 { + return Err(crate::Error::InvalidSize); + } // SAFETY: the &mut self.0 reference is only used to access fields in // the attribute info, not stored somewhere for later use let ret = unsafe { - citro3d_sys::AttrInfo_AddLoader(&mut self.0, register.0, format as u32, count) + citro3d_sys::AttrInfo_AddLoader(&mut self.0, register.0, format as u32, count.into()) }; let Ok(idx) = ret.try_into() else { - return Err(crate::Error::FailedToInitialize) + return Err(crate::Error::TooManyAttributes) }; Ok(Index(idx)) } - pub fn set_permutation(&mut self, indices: &[Index]) -> crate::Result<()> { - if indices.len() > 16 { - return Err(crate::Error::TooManyAttributes); - } - let attr_count: libc::c_int = indices.len().try_into().unwrap(); - - let mut bytes: Vec = indices - .windows(2) - .map(|window| { - let [lo, hi] = match *window { - [Index(lo), Index(hi)] => [lo, hi], - [Index(lo)] => [lo, 0], // high nibble is just padding - _ => unreachable!(), // window size of 2 == always 1 or 2 elements - }; - // each value is a nibble, combine them into a byte - lo | (hi << 4) - }) - .collect(); - - // pad the remainder with zeros - bytes.extend(std::iter::repeat(0).take(8 - bytes.len())); - - let permutation = bytemuck::cast(<[u8; 8]>::try_from(bytes).unwrap()); - - self.0.permutation = permutation; - self.0.attrCount = attr_count; - - Ok(()) - } - - /// Get the current permutation of input register to vertex attributes mapping. - /// See [GPU/Internal Registers] for an explanation of how the bits are laid out - /// in the resulting value. - /// - /// [GPU/Internal Registers]: https://3dbrew.org/wiki/GPU/Internal_Registers#GPUREG_SH_ATTRIBUTES_PERMUTATION_LOW - pub fn permutation(&self) -> u64 { + pub(crate) fn permutation(&self) -> u64 { self.0.permutation } - /// Get the number of attributes in the current permutation. - pub fn count(&self) -> libc::c_int { + /// Get the number of registered attributes. + pub fn attr_count(&self) -> libc::c_int { self.0.attrCount } } diff --git a/citro3d/src/buffer.rs b/citro3d/src/buffer.rs index 3bc0b66..0fd294d 100644 --- a/citro3d/src/buffer.rs +++ b/citro3d/src/buffer.rs @@ -6,16 +6,15 @@ use crate::attrib; #[derive(Debug)] pub struct Info(pub(crate) citro3d_sys::C3D_BufInfo); -// TODO: is this a good name? It's more like a "handle" to the VBO data, or a slice. #[derive(Debug, Clone, Copy)] -pub struct Index<'buf> { +pub struct Slice<'buf> { index: libc::c_int, size: libc::c_int, _vbo_data: PhantomData<&'buf ()>, buf_info: &'buf Info, } -impl Index<'_> { +impl Slice<'_> { pub fn as_raw(&self) -> libc::c_int { self.index } @@ -68,14 +67,12 @@ impl Info { &'this mut self, vbo_data: &'vbo [T], attrib_info: &attrib::Info, - ) -> crate::Result> + ) -> crate::Result> where 'this: 'idx, 'vbo: 'idx, { let stride = std::mem::size_of::().try_into()?; - let attrib_count = attrib_info.count(); - let permutation = attrib_info.permutation(); // SAFETY: the lifetime of the VBO data is encapsulated in the return value's // 'vbo lifetime, and the pointer to &mut self.0 is used to access values @@ -85,8 +82,8 @@ impl Info { &mut self.0, vbo_data.as_ptr().cast(), stride, - attrib_count, - permutation, + attrib_info.attr_count(), + attrib_info.permutation(), ) }; @@ -99,7 +96,7 @@ impl Info { // Err(crate::Error::System(res)) } else { - Ok(Index { + Ok(Slice { index: res, size: vbo_data.len().try_into()?, _vbo_data: PhantomData, diff --git a/citro3d/src/error.rs b/citro3d/src/error.rs index 7451763..0f3791f 100644 --- a/citro3d/src/error.rs +++ b/citro3d/src/error.rs @@ -24,7 +24,7 @@ pub enum Error { /// Indicates that a reference could not be obtained because a lock is already /// held on the requested object. LockHeld, - /// Indicates that too many vertex attributes were specified (max 16 supported). + /// Indicates that too many vertex attributes were specified (max 12 supported). TooManyAttributes, } diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 976b21a..77cc425 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -102,7 +102,7 @@ impl Instance { } /// Draw the specified primitivearrays. The - pub fn draw_arrays(&mut self, primitive: buffer::Primitive, index: buffer::Index) { + pub fn draw_arrays(&mut self, primitive: buffer::Primitive, index: buffer::Slice) { self.set_buffer_info(index.info()); // TODO: should we also require the attrib info directly here? From 5eaab0760a3df9daa29bd11062a944b8bc285ae7 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Fri, 2 Jun 2023 12:47:50 -0400 Subject: [PATCH 10/11] Add a ton more docs where missing --- citro3d/src/attrib.rs | 15 ++++++--- citro3d/src/buffer.rs | 65 +++++++++++++++++++++++++++--------- citro3d/src/error.rs | 7 +++- citro3d/src/lib.rs | 5 ++- citro3d/src/render.rs | 3 ++ citro3d/src/shader.rs | 16 ++++++--- citro3d/src/shader/macros.rs | 15 +++++---- citro3d/src/texture.rs | 1 - 8 files changed, 91 insertions(+), 36 deletions(-) delete mode 100644 citro3d/src/texture.rs diff --git a/citro3d/src/attrib.rs b/citro3d/src/attrib.rs index 176e46a..babdbbb 100644 --- a/citro3d/src/attrib.rs +++ b/citro3d/src/attrib.rs @@ -1,7 +1,14 @@ +//! Configure vertex attributes. +//! +//! This module has types and helpers for describing the shape/structure of vertex +//! data to be sent to the GPU. +//! +//! See the [`buffer`](crate::buffer) module to use the vertex data itself. + use std::mem::MaybeUninit; -/// Vertex attribute info. This struct is used to describe how vertex buffers are -/// used (i.e. the shape of the vertex data). +/// Vertex attribute info. This struct describes how vertex buffers are +/// layed out and used (i.e. the shape of the vertex data). #[derive(Debug)] pub struct Info(pub(crate) citro3d_sys::C3D_AttrInfo); @@ -82,14 +89,14 @@ impl Info { /// Add an attribute loader to the attribute info. The resulting attribute index /// indicates the registration order of the attributes. /// - /// ## Parameters + /// # Parameters /// /// * `register`: the shader program input register for this attribute. /// * `format`: the data format of this attribute. /// * `count`: the number of elements in each attribute (up to 4, corresponding /// to `xyzw` / `rgba` / `stpq`). /// - /// ## Errors + /// # Errors /// /// * If `count > 4` /// * If this attribute info already has the maximum number of attributes. diff --git a/citro3d/src/buffer.rs b/citro3d/src/buffer.rs index 0fd294d..8af071d 100644 --- a/citro3d/src/buffer.rs +++ b/citro3d/src/buffer.rs @@ -1,39 +1,64 @@ -use std::marker::PhantomData; +//! Configure vertex buffer objects to be sent to the GPU for rendering. +//! +//! See the [`attrib`] module for details on how to describe the shape and type +//! of the VBO data. + use std::mem::MaybeUninit; use crate::attrib; +/// Vertex buffer info. This struct is used to describe the shape of the buffer +/// data to be sent to the GPU for rendering. #[derive(Debug)] pub struct Info(pub(crate) citro3d_sys::C3D_BufInfo); +/// A slice of buffer data. This borrows the buffer data and can be thought of +/// as similar to `&[T]` obtained by slicing a `Vec`. #[derive(Debug, Clone, Copy)] pub struct Slice<'buf> { index: libc::c_int, size: libc::c_int, - _vbo_data: PhantomData<&'buf ()>, buf_info: &'buf Info, + // TODO: should we encapsulate the primitive here too, and require it when the + // slice is registered? Could there ever be a use case to draw different primitives + // using the same backing data??? } impl Slice<'_> { - pub fn as_raw(&self) -> libc::c_int { + /// Get the index into the buffer for this slice. + pub fn index(&self) -> libc::c_int { self.index } - pub fn size(&self) -> libc::c_int { + /// Get the length of the slice. + #[must_use] + pub fn len(&self) -> libc::c_int { self.size } + /// Return whether or not the slice has any elements. + pub fn is_empty(&self) -> bool { + self.len() <= 0 + } + + /// Get the buffer info this slice is associated with. pub fn info(&self) -> &Info { self.buf_info } } +/// The geometric primitive to draw (i.e. what shapes the buffer data describes). #[repr(u32)] #[derive(Debug, Clone, Copy)] pub enum Primitive { + /// Draw triangles (3 vertices per triangle). Triangles = ctru_sys::GPU_TRIANGLES, + /// Draw a triangle strip (each vertex shared by 1-3 triangles). TriangleStrip = ctru_sys::GPU_TRIANGLE_STRIP, + /// Draw a triangle fan (first vertex shared by all triangles). TriangleFan = ctru_sys::GPU_TRIANGLE_FAN, + /// Geometry primitive. Can be used for more complex use cases like geometry + /// shaders that output custom primitives. GeometryPrim = ctru_sys::GPU_GEOMETRY_PRIM, } @@ -49,6 +74,7 @@ impl Default for Info { } impl Info { + /// Construct buffer info without any registered data. pub fn new() -> Self { Self::default() } @@ -63,6 +89,18 @@ impl Info { } } + /// Register vertex buffer object data. The resulting [`Slice`] will have its + /// lifetime tied to both this [`Info`] and the passed-in VBO. `vbo_data` is + /// assumed to use one `T` per drawn primitive, and its layout is assumed to + /// match the given `attrib_info` + /// + /// # Errors + /// + /// Registering VBO data may fail: + /// + /// * if `vbo_data` is not allocated with the [`ctru::linear`] allocator + /// * if the maximum number (12) of VBOs are already registered + /// pub fn add<'this, 'vbo, 'idx, T>( &'this mut self, vbo_data: &'vbo [T], @@ -87,21 +125,16 @@ impl Info { ) }; - if res < 0 { - // TODO: should we convert to a more specific error if this fails? - // It looks like the common cases are - // - too many buffers already added (max 12) - // - physical memory address in the wrong place (this can be seen by - // using default allocator instead of LinearAllocator) - // - Err(crate::Error::System(res)) - } else { - Ok(Slice { + // Error codes from + match res { + -2 => Err(crate::Error::InvalidMemoryLocation), + -1 => Err(crate::Error::TooManyBuffers), + ..=0 => Err(crate::Error::System(res)), + _ => Ok(Slice { index: res, size: vbo_data.len().try_into()?, - _vbo_data: PhantomData, buf_info: self, - }) + }), } } } diff --git a/citro3d/src/error.rs b/citro3d/src/error.rs index 0f3791f..29acd73 100644 --- a/citro3d/src/error.rs +++ b/citro3d/src/error.rs @@ -24,8 +24,13 @@ pub enum Error { /// Indicates that a reference could not be obtained because a lock is already /// held on the requested object. LockHeld, - /// Indicates that too many vertex attributes were specified (max 12 supported). + /// Indicates that too many vertex attributes were registered (max 12 supported). TooManyAttributes, + /// Indicates that too many vertex buffer objects were registered (max 12 supported). + TooManyBuffers, + /// The given memory could not be converted to a physical address for sharing + /// with the GPU. Data should be allocated with [`ctru::linear`]. + InvalidMemoryLocation, } impl From for Error { diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 77cc425..f3f79fd 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -5,7 +5,6 @@ pub mod buffer; pub mod error; pub mod render; pub mod shader; -pub mod texture; use citro3d_sys::C3D_FrameDrawOn; pub use error::{Error, Result}; @@ -110,8 +109,8 @@ impl Instance { unsafe { citro3d_sys::C3D_DrawArrays( primitive as ctru_sys::GPU_Primitive_t, - index.as_raw(), - index.size(), + index.index(), + index.len(), ); } } diff --git a/citro3d/src/render.rs b/citro3d/src/render.rs index 16bd48b..517f12f 100644 --- a/citro3d/src/render.rs +++ b/citro3d/src/render.rs @@ -96,8 +96,11 @@ impl<'screen> Target<'screen> { bitflags::bitflags! { /// Indicate whether color, depth buffer, or both values should be cleared. pub struct ClearFlags: u32 { + /// Clear the color of the render target. const COLOR = citro3d_sys::C3D_CLEAR_COLOR; + /// Clear the depth buffer value of the render target. const DEPTH = citro3d_sys::C3D_CLEAR_DEPTH; + /// Clear both color and depth buffer values of the render target. const ALL = citro3d_sys::C3D_CLEAR_ALL; } } diff --git a/citro3d/src/shader.rs b/citro3d/src/shader.rs index e962532..a1bd264 100644 --- a/citro3d/src/shader.rs +++ b/citro3d/src/shader.rs @@ -7,6 +7,9 @@ use std::error::Error; use std::mem::MaybeUninit; +// Macros get exported at the crate root, so no reason to document this module. +// It still needs to be `pub` for the helper struct it exports. +#[doc(hidden)] pub mod macros; /// A PICA200 shader program. It may have one or both of: @@ -110,11 +113,19 @@ impl Library { })) } + /// Get the number of [`Entrypoint`]s in this shader library. #[must_use] pub fn len(&self) -> usize { unsafe { (*self.0).numDVLE as usize } } + /// Whether the library has any [`Entrypoint`]s or not. + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get the [`Entrypoint`] at the given index, if present. #[must_use] pub fn get(&self, index: usize) -> Option { if index < self.len() { @@ -127,11 +138,6 @@ impl Library { } } - #[must_use] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - fn as_raw(&mut self) -> *mut ctru_sys::DVLB_s { self.0 } diff --git a/citro3d/src/shader/macros.rs b/citro3d/src/shader/macros.rs index 5a6fc2e..fb61eb5 100644 --- a/citro3d/src/shader/macros.rs +++ b/citro3d/src/shader/macros.rs @@ -1,9 +1,4 @@ -/// Helper struct to [`include_bytes`] aligned as a specific type. -#[repr(C)] // guarantee 'bytes' comes after '_align' -pub struct AlignedAs { - pub _align: [Align; 0], - pub bytes: Bytes, -} +//! Helper macros for working with shader data. /// Helper macro for including a file as bytes that are correctly aligned for /// use as a [`Library`](super::Library). @@ -22,3 +17,11 @@ macro_rules! include_aligned_bytes { &ALIGNED.bytes }}; } + +/// Helper struct to [`include_bytes`] aligned as a specific type. +#[repr(C)] // guarantee 'bytes' comes after '_align' +#[doc(hidden)] +pub struct AlignedAs { + pub _align: [Align; 0], + pub bytes: Bytes, +} diff --git a/citro3d/src/texture.rs b/citro3d/src/texture.rs deleted file mode 100644 index 8b13789..0000000 --- a/citro3d/src/texture.rs +++ /dev/null @@ -1 +0,0 @@ - From e47c4140699ea29152d63de90cbdcff1a3582c58 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 17 Jun 2023 13:10:09 -0400 Subject: [PATCH 11/11] Use proper stereo perspective projection Read + use the OS 3D slider value when projecting left+right eyes, and use a mono perspective projection for the bottom screen. --- citro3d-sys/src/lib.rs | 2 + citro3d-sys/src/os.rs | 13 ++++ citro3d/examples/triangle.rs | 123 +++++++++++++++++++++++------------ citro3d/src/buffer.rs | 2 +- 4 files changed, 99 insertions(+), 41 deletions(-) create mode 100644 citro3d-sys/src/os.rs diff --git a/citro3d-sys/src/lib.rs b/citro3d-sys/src/lib.rs index 3426e83..48a46ec 100644 --- a/citro3d-sys/src/lib.rs +++ b/citro3d-sys/src/lib.rs @@ -7,6 +7,7 @@ pub mod base; pub mod gx; +pub mod os; pub mod renderqueue; pub mod texenv; pub mod uniforms; @@ -16,6 +17,7 @@ mod bindings; pub use base::*; pub use bindings::*; pub use gx::*; +pub use os::*; pub use renderqueue::*; pub use texenv::*; pub use uniforms::*; diff --git a/citro3d-sys/src/os.rs b/citro3d-sys/src/os.rs new file mode 100644 index 0000000..ed04411 --- /dev/null +++ b/citro3d-sys/src/os.rs @@ -0,0 +1,13 @@ +// TODO: move this to ctru-sys, maybe? +// would probably be auto-generated via https://github.com/rust3ds/ctru-rs/issues/123 + +use ctru_sys::{osSharedConfig_s, OS_SHAREDCFG_VADDR}; + +fn OS_SharedConfig() -> *mut osSharedConfig_s { + OS_SHAREDCFG_VADDR as _ +} + +/// Gets the state of the 3D slider as a value from 0.0 to 1.0 +pub unsafe fn osGet3DSliderState() -> f32 { + (*OS_SharedConfig()).slider_3d +} diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index ac6ea8b..6bfc1fa 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -11,7 +11,6 @@ use citro3d_sys::C3D_Mtx; use ctru::prelude::*; use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D}; -use std::f32::consts::PI; use std::ffi::CStr; use std::mem::MaybeUninit; @@ -38,15 +37,15 @@ struct Vertex { static VERTICES: &[Vertex] = &[ Vertex { - pos: Vec3::new(0.0, 0.5, 0.5), + pos: Vec3::new(0.0, 0.5, 3.0), color: Vec3::new(1.0, 0.0, 0.0), }, Vertex { - pos: Vec3::new(-0.5, -0.5, 0.5), + pos: Vec3::new(-0.5, -0.5, 3.0), color: Vec3::new(0.0, 1.0, 0.0), }, Vertex { - pos: Vec3::new(0.5, -0.5, 0.5), + pos: Vec3::new(0.5, -0.5, 3.0), color: Vec3::new(0.0, 0.0, 1.0), }, ]; @@ -95,13 +94,7 @@ fn main() { let mut buf_info = buffer::Info::new(); let (attr_info, vbo_idx) = prepare_vbos(&mut buf_info, &vbo_data); - let (projection_uniform_idx, mut projection) = scene_init(&mut program); - - unsafe { citro3d_sys::Mtx_RotateY(&mut projection, -PI / 12.0, true) }; - - let mut right_eye_projection = projection; - unsafe { citro3d_sys::Mtx_RotateY(&mut right_eye_projection, 2.0 * PI / 12.0, true) }; - + let projection_uniform_idx = scene_init(&mut program); while apt.main_loop() { hid.scan_input(); @@ -132,9 +125,15 @@ fn main() { instance.draw_arrays(buffer::Primitive::Triangles, vbo_idx); }; - render_to(&mut top_left_target, &projection); - render_to(&mut top_right_target, &right_eye_projection); - render_to(&mut bottom_target, &projection); + let Projections { + left, + right, + center, + } = calculate_projections(); + + render_to(&mut top_left_target, &left); + render_to(&mut top_right_target, &right); + render_to(&mut bottom_target, ¢er); }); } } @@ -159,7 +158,7 @@ where .unwrap(); attr_info - .add_loader(reg1, attrib::Format::Float, 5) + .add_loader(reg1, attrib::Format::Float, 3) .unwrap(); let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); @@ -167,34 +166,71 @@ where (attr_info, buf_idx) } -fn scene_init(program: &mut shader::Program) -> (i8, C3D_Mtx) { - // Load the vertex shader, create a shader program and bind it +struct Projections { + left: C3D_Mtx, + right: C3D_Mtx, + center: C3D_Mtx, +} + +fn calculate_projections() -> Projections { + let mut left_eye = MaybeUninit::uninit(); + let mut right_eye = MaybeUninit::uninit(); + let mut center = MaybeUninit::uninit(); + + // TODO: it would be cool to allow playing around with these parameters on + // the fly with D-pad, etc. + let slider_val = unsafe { citro3d_sys::osGet3DSliderState() }; + let iod = slider_val / 4.0; + + let near = 0.01; + let far = 100.0; + let fovy = 40.0_f32.to_radians(); + let screen = 2.0; + unsafe { - citro3d_sys::C3D_BindProgram(program.as_raw()); + citro3d_sys::Mtx_PerspStereoTilt( + left_eye.as_mut_ptr(), + fovy, + citro3d_sys::C3D_AspectRatioTop as f32, + near, + far, + -iod, + screen, + true, + ); - // Get the location of the uniforms - let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap(); - let projection_uniform_idx = ctru_sys::shaderInstanceGetUniformLocation( - (*program.as_raw()).vertexShader, - projection_name.as_ptr(), + citro3d_sys::Mtx_PerspStereoTilt( + right_eye.as_mut_ptr(), + fovy, + citro3d_sys::C3D_AspectRatioTop as f32, + near, + far, + iod, + screen, + true, ); - // Compute the projection matrix - let projection = { - let mut projection = MaybeUninit::uninit(); - citro3d_sys::Mtx_OrthoTilt( - projection.as_mut_ptr(), - // The 3ds top screen is a 5:3 ratio - -1.66, - 1.66, - -1.0, - 1.0, - 0.0, - 1.0, - true, - ); - projection.assume_init() - }; + citro3d_sys::Mtx_PerspTilt( + center.as_mut_ptr(), + fovy, + citro3d_sys::C3D_AspectRatioBot as f32, + near, + far, + true, + ); + + Projections { + left: left_eye.assume_init(), + right: right_eye.assume_init(), + center: center.assume_init(), + } + } +} + +fn scene_init(program: &mut shader::Program) -> i8 { + // Load the vertex shader, create a shader program and bind it + unsafe { + citro3d_sys::C3D_BindProgram(program.as_raw()); // 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 @@ -209,6 +245,13 @@ fn scene_init(program: &mut shader::Program) -> (i8, C3D_Mtx) { ); citro3d_sys::C3D_TexEnvFunc(env, citro3d_sys::C3D_Both, ctru_sys::GPU_REPLACE); - (projection_uniform_idx, projection) + // Get the location of the uniforms + let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap(); + let projection_uniform_idx = ctru_sys::shaderInstanceGetUniformLocation( + (*program.as_raw()).vertexShader, + projection_name.as_ptr(), + ); + + projection_uniform_idx } } diff --git a/citro3d/src/buffer.rs b/citro3d/src/buffer.rs index 8af071d..534a487 100644 --- a/citro3d/src/buffer.rs +++ b/citro3d/src/buffer.rs @@ -127,9 +127,9 @@ impl Info { // Error codes from match res { + ..=-3 => Err(crate::Error::System(res)), -2 => Err(crate::Error::InvalidMemoryLocation), -1 => Err(crate::Error::TooManyBuffers), - ..=0 => Err(crate::Error::System(res)), _ => Ok(Slice { index: res, size: vbo_data.len().try_into()?,