diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index e7feff8..bce6779 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -2,12 +2,10 @@ use citro3d_sys::C3D_Mtx; use citro3d_sys::{shaderProgram_s, DVLB_s}; 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 citro3d::render::{ClearFlags, ColorFormat, DepthFormat}; use std::ffi::CStr; use std::mem::MaybeUninit; @@ -59,20 +57,17 @@ fn main() { let mut top_screen = gfx.top_screen.borrow_mut(); let frame_buffer = top_screen.get_raw_framebuffer(Side::Left); - let ctx = C3DContext::new().expect("failed to initialize Citro3D"); - let mut render_target = ctx + let mut instance = citro3d::Instance::new().expect("failed to initialize Citro3D"); + + let mut render_target = instance .render_target_for_screen( &frame_buffer, - // TODO: why doesn't getting this from the screen work? - FramebufferFormat::Rgba8.into(), + ColorFormat::RGBA8, DepthFormat::Depth24Stencil8, ) .expect("failed to create render target"); - // 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; - - render_target.set_output(&*top_screen, Side::Left, transfer_flags); + render_target.set_output(&*top_screen, Side::Left); let (program, uloc_projection, projection, vbo_data, vshader_dvlb) = scene_init(); @@ -91,22 +86,21 @@ fn main() { ); } - // 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(); + + instance + .select_render_target(&render_target) + .expect("failed to set render target"); scene_render(uloc_projection.into(), &projection); + unsafe { citro3d_sys::C3D_FrameEnd(0); } } scene_exit(vbo_data, program, vshader_dvlb); - - unsafe { - citro3d_sys::C3D_Fini(); - } } static SHBIN_BYTES: &[u8] = diff --git a/citro3d/src/error.rs b/citro3d/src/error.rs index bf77d05..b0f2c03 100644 --- a/citro3d/src/error.rs +++ b/citro3d/src/error.rs @@ -13,6 +13,8 @@ pub enum Error { 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, } impl From for Error { diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 1f01489..159d995 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -6,32 +6,34 @@ pub mod shader; pub mod texture; pub mod vbo; +use citro3d_sys::C3D_FrameDrawOn; use ctru::gfx::RawFrameBuffer; pub use error::{Error, Result}; use render::Target; -/// The base context for using `citro3d`. This type must be used for +/// The single instance for using `citro3d`. This is the base type that an application +/// should instantiate to use this library. #[non_exhaustive] #[derive(Debug)] -pub struct C3DContext; +pub struct Instance; -impl C3DContext { - /// Initialize the default context. +impl Instance { + /// Initialize the default `citro3d` instance. /// /// # Errors /// - /// Fails if the `citro3d` library cannot be initialized. + /// Fails if `citro3d` cannot be initialized. pub fn new() -> Result { - Self::with_command_buffer_size(citro3d_sys::C3D_DEFAULT_CMDBUF_SIZE) + Self::with_cmdbuf_size(citro3d_sys::C3D_DEFAULT_CMDBUF_SIZE) } - /// Initialize the context with a specified command buffer + /// Initialize the instance with a specified command buffer size. /// /// # Errors /// - /// Fails if the `citro3d` library cannot be initialized. - pub fn with_command_buffer_size(size: u32) -> Result { + /// Fails if `citro3d` cannot be initialized. + pub fn with_cmdbuf_size(size: u32) -> Result { if unsafe { citro3d_sys::C3D_Init(size) } { Ok(Self) } else { @@ -50,6 +52,7 @@ impl C3DContext { color_format: render::ColorFormat, depth_format: render::DepthFormat, ) -> Result { + let _ = self; Target::new( frame_buffer.width.into(), frame_buffer.height.into(), @@ -57,9 +60,23 @@ impl C3DContext { depth_format, ) } + + /// Select the given render target for drawing the frame. + /// + /// # Errors + /// + /// Fails if the given target cannot be used for drawing. + pub fn select_render_target(&mut self, target: &render::Target) -> Result<()> { + let _ = self; + if unsafe { C3D_FrameDrawOn(target.as_raw()) } { + Ok(()) + } else { + Err(Error::InvalidRenderTarget) + } + } } -impl Drop for C3DContext { +impl Drop for Instance { fn drop(&mut self) { unsafe { citro3d_sys::C3D_Fini(); diff --git a/citro3d/src/render.rs b/citro3d/src/render.rs index f737356..dcd7fa3 100644 --- a/citro3d/src/render.rs +++ b/citro3d/src/render.rs @@ -1,15 +1,30 @@ +//! This module provides render target types and options for controlling transfer +//! of data to the GPU, including the format of color and depth data to be rendered. + use citro3d_sys::{ - C3D_RenderTargetCreate, C3D_RenderTargetDelete, C3D_DEPTHTYPE, GPU_COLORBUF, GPU_DEPTHBUF, + C3D_RenderTarget, C3D_RenderTargetCreate, C3D_RenderTargetDelete, C3D_DEPTHTYPE, GPU_COLORBUF, + GPU_DEPTHBUF, }; use ctru::gfx; -use ctru::services::gspgpu; +use ctru::services::gspgpu::FramebufferFormat; use crate::{Error, Result}; -/// A render target for `citro3d`. This is the data structure which handles sending -/// data to the GPU +mod transfer; + +/// A render target for `citro3d`. Frame data will be written to this target +/// to be rendered on the GPU and displayed on the screen. pub struct Target { - tag: *mut citro3d_sys::C3D_RenderTarget_tag, + raw: *mut citro3d_sys::C3D_RenderTarget, + color_format: ColorFormat, +} + +impl Drop for Target { + fn drop(&mut self) { + unsafe { + C3D_RenderTargetDelete(self.raw); + } + } } impl Target { @@ -26,7 +41,7 @@ impl Target { color_format: ColorFormat, depth_format: DepthFormat, ) -> Result { - let tag = unsafe { + let raw = unsafe { C3D_RenderTargetCreate( width.try_into()?, height.try_into()?, @@ -35,58 +50,47 @@ impl Target { ) }; - if tag.is_null() { + if raw.is_null() { Err(Error::FailedToInitialize) } else { - Ok(Self { tag }) + Ok(Self { raw, color_format }) } } /// 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) { + pub fn set_output(&mut self, screen: &impl gfx::Screen, side: gfx::Side) { + let framebuf_format = screen.get_framebuffer_format(); + + let flags = transfer::Flags::default() + .in_format(self.color_format.into()) + .out_format(ColorFormat::from(framebuf_format).into()); + unsafe { citro3d_sys::C3D_RenderTargetSetOutput( - self.tag, + self.raw, screen.as_raw(), side.into(), - transfer_flags, + flags.bits(), ); } } - pub fn clear(&mut self, flags: ClearFlags, color: u32, depth: u32) { + /// Clear the render target with the given 32-bit RGBA color and depth buffer value. + /// Use `flags` to specify whether color and/or depth should be overwritten. + pub fn clear(&mut self, flags: ClearFlags, rgba_color: u32, depth: u32) { unsafe { - citro3d_sys::C3D_RenderTargetClear(self.tag, flags.bits(), color, depth); + citro3d_sys::C3D_RenderTargetClear(self.raw, flags.bits(), rgba_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; + /// Return the underlying `citro3d` render target for this target. + pub(crate) fn as_raw(&self) -> *mut C3D_RenderTarget { + self.raw } } bitflags::bitflags! { + /// Indicate whether color, depth buffer, or both values should be cleared. pub struct ClearFlags: u32 { const COLOR = citro3d_sys::C3D_CLEAR_COLOR; const DEPTH = citro3d_sys::C3D_CLEAR_DEPTH; @@ -94,14 +98,6 @@ bitflags::bitflags! { } } -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)] @@ -118,23 +114,28 @@ pub enum ColorFormat { RGBA4 = citro3d_sys::GPU_RB_RGBA4, } -impl From for ColorFormat { - fn from(format: gspgpu::FramebufferFormat) -> Self { +impl From for ColorFormat { + fn from(format: 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:?}"), + FramebufferFormat::Rgba8 => Self::RGBA8, + FramebufferFormat::Rgb565 => Self::RGB565, + FramebufferFormat::Rgb5A1 => Self::RGBA5551, + FramebufferFormat::Rgba4 => Self::RGBA4, + // this one seems unusual, but it appears to work fine: + FramebufferFormat::Bgr8 => Self::RGB8, } } } +/// The depth buffer format to use when rendering. #[repr(u32)] #[derive(Clone, Copy, Debug)] pub enum DepthFormat { + /// 16-bit depth. Depth16 = citro3d_sys::GPU_RB_DEPTH16, + /// 24-bit depth. Depth24 = citro3d_sys::GPU_RB_DEPTH24, + /// 24-bit depth + 8-bit Stencil. Depth24Stencil8 = citro3d_sys::GPU_RB_DEPTH24_STENCIL8, } diff --git a/citro3d/src/render/transfer.rs b/citro3d/src/render/transfer.rs new file mode 100644 index 0000000..60a5e58 --- /dev/null +++ b/citro3d/src/render/transfer.rs @@ -0,0 +1,57 @@ +use citro3d_sys::{GX_TRANSFER_FORMAT, GX_TRANSFER_IN_FORMAT, GX_TRANSFER_OUT_FORMAT}; + +use super::ColorFormat; + +/// Control flags for a GX data transfer. +#[derive(Default, Clone, Copy)] +pub struct Flags(u32); + +impl Flags { + /// Set the input format of the data transfer. + #[must_use] + pub fn in_format(self, fmt: Format) -> Self { + Self(self.0 | GX_TRANSFER_IN_FORMAT(fmt as GX_TRANSFER_FORMAT)) + } + + /// Set the output format of the data transfer. + #[must_use] + pub fn out_format(self, fmt: Format) -> Self { + Self(self.0 | GX_TRANSFER_OUT_FORMAT(fmt as GX_TRANSFER_FORMAT)) + } + + #[must_use] + pub fn bits(self) -> u32 { + self.0 + } +} + +/// The color format to use when transferring data to/from the GPU. +/// +/// NOTE: this a distinct type from [`ColorFormat`] because they are not implicitly +/// convertible to one another. Use [`From::from`] to get the [`Format`] corresponding +/// to a given [`ColorFormat`]. +#[repr(u32)] +pub enum Format { + /// 8-bit Red + 8-bit Green + 8-bit Blue + 8-bit Alpha. + RGBA8 = citro3d_sys::GX_TRANSFER_FMT_RGBA8, + /// 8-bit Red + 8-bit Green + 8-bit Blue. + RGB8 = citro3d_sys::GX_TRANSFER_FMT_RGB8, + /// 5-bit Red + 5-bit Green + 5-bit Blue + 1-bit Alpha. + RGB565 = citro3d_sys::GX_TRANSFER_FMT_RGB565, + /// 5-bit Red + 6-bit Green + 5-bit Blue. + RGB5A1 = citro3d_sys::GX_TRANSFER_FMT_RGB5A1, + /// 4-bit Red + 4-bit Green + 4-bit Blue + 4-bit Alpha. + RGBA4 = citro3d_sys::GX_TRANSFER_FMT_RGBA4, +} + +impl From for Format { + fn from(color_fmt: ColorFormat) -> Self { + match color_fmt { + ColorFormat::RGBA8 => Self::RGBA8, + ColorFormat::RGB8 => Self::RGB8, + ColorFormat::RGBA5551 => Self::RGB5A1, + ColorFormat::RGB565 => Self::RGB565, + ColorFormat::RGBA4 => Self::RGBA4, + } + } +} diff --git a/citro3d/src/shader.rs b/citro3d/src/shader.rs index e69de29..8b13789 100644 --- a/citro3d/src/shader.rs +++ b/citro3d/src/shader.rs @@ -0,0 +1 @@ + diff --git a/citro3d/src/texture.rs b/citro3d/src/texture.rs index e69de29..8b13789 100644 --- a/citro3d/src/texture.rs +++ b/citro3d/src/texture.rs @@ -0,0 +1 @@ + diff --git a/citro3d/src/vbo.rs b/citro3d/src/vbo.rs index e69de29..8b13789 100644 --- a/citro3d/src/vbo.rs +++ b/citro3d/src/vbo.rs @@ -0,0 +1 @@ +