From 83b613684a5c2d460e6bd2442676b399b94e158e Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 15 May 2022 11:20:10 -0400 Subject: [PATCH] First pass at some safe wrappers for render target --- citro3d/Cargo.toml | 3 +- citro3d/examples/assets/.gitignore | 1 - citro3d/examples/assets/vshader.pica | 5 +- citro3d/examples/triangle.rs | 60 ++++++----- citro3d/src/error.rs | 22 ++++ citro3d/src/lib.rs | 72 +++++++++++-- citro3d/src/render.rs | 147 +++++++++++++++++++++++++++ citro3d/src/shader.rs | 0 citro3d/src/texture.rs | 0 citro3d/src/vbo.rs | 0 10 files changed, 268 insertions(+), 42 deletions(-) delete mode 100644 citro3d/examples/assets/.gitignore create mode 100644 citro3d/src/error.rs create mode 100644 citro3d/src/render.rs create mode 100644 citro3d/src/shader.rs create mode 100644 citro3d/src/texture.rs create mode 100644 citro3d/src/vbo.rs diff --git a/citro3d/Cargo.toml b/citro3d/Cargo.toml index 6cf4742..fd1189d 100644 --- a/citro3d/Cargo.toml +++ b/citro3d/Cargo.toml @@ -5,8 +5,9 @@ edition = "2021" authors = [""] [dependencies] +bitflags = "1.3.2" citro3d-sys = { git = "https://github.com/ian-h-chamberlain/citro3d-rs.git" } +ctru-rs = { git = "https://github.com/Meziu/ctru-rs.git" } libc = "0.2.125" [dev-dependencies] -ctru-rs = { git = "https://github.com/Meziu/ctru-rs.git" } diff --git a/citro3d/examples/assets/.gitignore b/citro3d/examples/assets/.gitignore deleted file mode 100644 index 7852bdf..0000000 --- a/citro3d/examples/assets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.shbin diff --git a/citro3d/examples/assets/vshader.pica b/citro3d/examples/assets/vshader.pica index 27ce6bf..b428a8e 100644 --- a/citro3d/examples/assets/vshader.pica +++ b/citro3d/examples/assets/vshader.pica @@ -1,11 +1,10 @@ -; Example PICA200 vertex shader +; Basic PICA200 vertex shader ; Uniforms .fvec projection[4] ; Constants -.constf myconst(0.0, 1.0, -1.0, 0.1) -.alias ones myconst.yyyy ; Vector full of ones +.constf ones(1.0, 1.0, 1.0, 1.0) ; Outputs .out outpos position diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 24caa4d..e7feff8 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -1,10 +1,14 @@ use citro3d_sys::C3D_Mtx; use citro3d_sys::{shaderProgram_s, DVLB_s}; -use ctru::gfx::{Gfx, Screen, Side}; +use ctru::gfx::{Gfx, Side}; use ctru::services::apt::Apt; +use ctru::services::gspgpu::FramebufferFormat; use ctru::services::hid::{Hid, KeyPad}; use ctru::services::soc::Soc; +use citro3d::render::{ClearFlags, DepthFormat, TransferFormat}; +use citro3d::C3DContext; + use std::ffi::CStr; use std::mem::MaybeUninit; @@ -27,7 +31,7 @@ struct Vertex { color: Vec3, } -const VERTICES: [Vertex; 3] = [ +const VERTICES: &[Vertex] = &[ Vertex { pos: Vec3::new(0.0, 0.5, 0.5), color: Vec3::new(1.0, 0.0, 0.0), @@ -45,37 +49,30 @@ const VERTICES: [Vertex; 3] = [ fn main() { ctru::init(); - let gfx = Gfx::init().expect("Couldn't obtain GFX controller"); - let hid = Hid::init().expect("Couldn't obtain HID controller"); - let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut soc = Soc::init().expect("failed to get SOC"); drop(soc.redirect_to_3dslink(true, true)); - let top_screen = gfx.top_screen.borrow_mut(); - - let target = unsafe { - citro3d_sys::C3D_Init(citro3d_sys::C3D_DEFAULT_CMDBUF_SIZE); - - let depth_fmt = citro3d_sys::C3D_DEPTHTYPE { - __e: citro3d_sys::GPU_RB_DEPTH24_STENCIL8, - }; + let 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 target = - citro3d_sys::C3D_RenderTargetCreate(240, 400, citro3d_sys::GPU_RB_RGBA8, depth_fmt); + let mut top_screen = gfx.top_screen.borrow_mut(); + let frame_buffer = top_screen.get_raw_framebuffer(Side::Left); - // TODO: easier construction of flags - let transfer_flags = - citro3d_sys::GX_TRANSFER_FMT_RGBA8 << 8 | citro3d_sys::GX_TRANSFER_FMT_RGB8 << 12; + let ctx = C3DContext::new().expect("failed to initialize Citro3D"); + let mut render_target = ctx + .render_target_for_screen( + &frame_buffer, + // TODO: why doesn't getting this from the screen work? + FramebufferFormat::Rgba8.into(), + DepthFormat::Depth24Stencil8, + ) + .expect("failed to create render target"); - citro3d_sys::C3D_RenderTargetSetOutput( - target, - top_screen.as_raw(), - Side::Left.into(), - transfer_flags, - ); + // TODO: easier construction of flags, see macros in <3ds/gpu/gx.h> + let transfer_flags = (TransferFormat::RGBA8 as u32) << 8 | (TransferFormat::RGB8 as u32) << 12; - target - }; + render_target.set_output(&*top_screen, Side::Left, transfer_flags); let (program, uloc_projection, projection, vbo_data, vshader_dvlb) = scene_init(); @@ -86,18 +83,19 @@ fn main() { break; } - let clear_color: u32 = 0x7F_7F_7F_FF; - unsafe { citro3d_sys::C3D_FrameBegin( citro3d_sys::C3D_FRAME_SYNCDRAW .try_into() .expect("const is valid u8"), ); - - citro3d_sys::C3D_RenderTargetClear(target, citro3d_sys::C3D_CLEAR_ALL, clear_color, 0); - citro3d_sys::C3D_FrameDrawOn(target); } + + // Is this format-dependent? because we used RGBA8 for transfer? + let clear_color: u32 = 0x7F_7F_7F_FF; + render_target.clear(ClearFlags::ALL, clear_color, 0); + render_target.set_for_draw(); + scene_render(uloc_projection.into(), &projection); unsafe { citro3d_sys::C3D_FrameEnd(0); diff --git a/citro3d/src/error.rs b/citro3d/src/error.rs new file mode 100644 index 0000000..bf77d05 --- /dev/null +++ b/citro3d/src/error.rs @@ -0,0 +1,22 @@ +//! General-purpose error and result types returned by public APIs of this crate. + +use std::num::TryFromIntError; + +/// The common result type returned by `citro3d` functions. +pub type Result = std::result::Result; + +/// The common error type that may be returned by `citro3d` functions. +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + /// A C3D object or context could not be initialized. + FailedToInitialize, + /// A size parameter was specified that cannot be converted to the proper type. + InvalidSize, +} + +impl From for Error { + fn from(_: TryFromIntError) -> Self { + Self::InvalidSize + } +} diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 1b4a90c..1f01489 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -1,8 +1,68 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); +//! Safe Rust bindings to `citro3d`. + +pub mod error; +pub mod render; +pub mod shader; +pub mod texture; +pub mod vbo; + +use ctru::gfx::RawFrameBuffer; +pub use error::{Error, Result}; + +use render::Target; + +/// The base context for using `citro3d`. This type must be used for +#[non_exhaustive] +#[derive(Debug)] +pub struct C3DContext; + +impl C3DContext { + /// Initialize the default context. + /// + /// # Errors + /// + /// Fails if the `citro3d` library cannot be initialized. + pub fn new() -> Result { + Self::with_command_buffer_size(citro3d_sys::C3D_DEFAULT_CMDBUF_SIZE) + } + + /// Initialize the context with a specified command buffer + /// + /// # Errors + /// + /// Fails if the `citro3d` library cannot be initialized. + pub fn with_command_buffer_size(size: u32) -> Result { + if unsafe { citro3d_sys::C3D_Init(size) } { + Ok(Self) + } else { + Err(Error::FailedToInitialize) + } + } + + /// Create a default render target for the given screen. + /// + /// # Errors + /// + /// Fails if the render target could not be created. + pub fn render_target_for_screen( + &self, + frame_buffer: &RawFrameBuffer, + color_format: render::ColorFormat, + depth_format: render::DepthFormat, + ) -> Result { + Target::new( + frame_buffer.width.into(), + frame_buffer.height.into(), + color_format, + depth_format, + ) + } +} + +impl Drop for C3DContext { + fn drop(&mut self) { + unsafe { + citro3d_sys::C3D_Fini(); + } } } diff --git a/citro3d/src/render.rs b/citro3d/src/render.rs new file mode 100644 index 0000000..f737356 --- /dev/null +++ b/citro3d/src/render.rs @@ -0,0 +1,147 @@ +use citro3d_sys::{ + C3D_RenderTargetCreate, C3D_RenderTargetDelete, C3D_DEPTHTYPE, GPU_COLORBUF, GPU_DEPTHBUF, +}; +use ctru::gfx; +use ctru::services::gspgpu; + +use crate::{Error, Result}; + +/// A render target for `citro3d`. This is the data structure which handles sending +/// data to the GPU +pub struct Target { + tag: *mut citro3d_sys::C3D_RenderTarget_tag, +} + +impl Target { + /// Create a new render target with the specified size, color format, + /// and depth format. + /// + /// # Errors + /// + /// Fails if the specified sizes are invalid, or the target could not be + /// created. + pub fn new( + width: u32, + height: u32, + color_format: ColorFormat, + depth_format: DepthFormat, + ) -> Result { + let tag = unsafe { + C3D_RenderTargetCreate( + width.try_into()?, + height.try_into()?, + color_format as GPU_COLORBUF, + depth_format.as_raw(), + ) + }; + + if tag.is_null() { + Err(Error::FailedToInitialize) + } else { + Ok(Self { tag }) + } + } + + /// Sets the screen to actually display the output of this render target. + pub fn set_output(&mut self, screen: &impl gfx::Screen, side: gfx::Side, transfer_flags: u32) { + unsafe { + citro3d_sys::C3D_RenderTargetSetOutput( + self.tag, + screen.as_raw(), + side.into(), + transfer_flags, + ); + } + } + + pub fn clear(&mut self, flags: ClearFlags, color: u32, depth: u32) { + unsafe { + citro3d_sys::C3D_RenderTargetClear(self.tag, flags.bits(), color, depth); + } + } + + // TODO: this should maybe be a method on C3DContext instead? + pub fn set_for_draw(&mut self) { + unsafe { + citro3d_sys::C3D_FrameDrawOn(self.tag); + } + } +} + +#[repr(u32)] +pub enum TransferFormat { + RGBA8 = citro3d_sys::GX_TRANSFER_FMT_RGBA8, + RGB8 = citro3d_sys::GX_TRANSFER_FMT_RGB8, + RGB565 = citro3d_sys::GX_TRANSFER_FMT_RGB565, + RGB5A1 = citro3d_sys::GX_TRANSFER_FMT_RGB5A1, + RGBA4 = citro3d_sys::GX_TRANSFER_FMT_RGBA4, +} + +// TODO: more flags +bitflags::bitflags! { + pub struct TransferFlags: u32 { + const SCALE_NO = citro3d_sys::GX_TRANSFER_SCALE_NO; + const SCALE_X = citro3d_sys::GX_TRANSFER_SCALE_X; + const SCALE_XY = citro3d_sys::GX_TRANSFER_SCALE_XY; + } +} + +bitflags::bitflags! { + pub struct ClearFlags: u32 { + const COLOR = citro3d_sys::C3D_CLEAR_COLOR; + const DEPTH = citro3d_sys::C3D_CLEAR_DEPTH; + const ALL = citro3d_sys::C3D_CLEAR_ALL; + } +} + +impl Drop for Target { + fn drop(&mut self) { + unsafe { + C3D_RenderTargetDelete(self.tag); + } + } +} + +/// The color format to use when rendering on the GPU. +#[repr(u32)] +#[derive(Clone, Copy, Debug)] +pub enum ColorFormat { + /// 8-bit Red + 8-bit Green + 8-bit Blue + 8-bit Alpha. + RGBA8 = citro3d_sys::GPU_RB_RGBA8, + /// 8-bit Red + 8-bit Green + 8-bit Blue. + RGB8 = citro3d_sys::GPU_RB_RGB8, + /// 5-bit Red + 5-bit Green + 5-bit Blue + 1-bit Alpha. + RGBA5551 = citro3d_sys::GPU_RB_RGBA5551, + /// 5-bit Red + 6-bit Green + 5-bit Blue. + RGB565 = citro3d_sys::GPU_RB_RGB565, + /// 4-bit Red + 4-bit Green + 4-bit Blue + 4-bit Alpha. + RGBA4 = citro3d_sys::GPU_RB_RGBA4, +} + +impl From for ColorFormat { + fn from(format: gspgpu::FramebufferFormat) -> Self { + match format { + gspgpu::FramebufferFormat::Rgba8 => Self::RGBA8, + gspgpu::FramebufferFormat::Rgb565 => Self::RGB565, + gspgpu::FramebufferFormat::Rgb5A1 => Self::RGBA5551, + gspgpu::FramebufferFormat::Rgba4 => Self::RGBA4, + fmt => panic!("Unsupported frame buffer format {fmt:?}"), + } + } +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug)] +pub enum DepthFormat { + Depth16 = citro3d_sys::GPU_RB_DEPTH16, + Depth24 = citro3d_sys::GPU_RB_DEPTH24, + Depth24Stencil8 = citro3d_sys::GPU_RB_DEPTH24_STENCIL8, +} + +impl DepthFormat { + fn as_raw(self) -> C3D_DEPTHTYPE { + C3D_DEPTHTYPE { + __e: self as GPU_DEPTHBUF, + } + } +} diff --git a/citro3d/src/shader.rs b/citro3d/src/shader.rs new file mode 100644 index 0000000..e69de29 diff --git a/citro3d/src/texture.rs b/citro3d/src/texture.rs new file mode 100644 index 0000000..e69de29 diff --git a/citro3d/src/vbo.rs b/citro3d/src/vbo.rs new file mode 100644 index 0000000..e69de29