Browse Source

Update render target API for auto framebuffer fmt

Transfer flags can be an implementation detail this way, although it
might need to be exposed eventually.
pull/18/head
Ian Chamberlain 3 years ago
parent
commit
184060c63d
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 28
      citro3d/examples/triangle.rs
  2. 2
      citro3d/src/error.rs
  3. 37
      citro3d/src/lib.rs
  4. 103
      citro3d/src/render.rs
  5. 57
      citro3d/src/render/transfer.rs
  6. 1
      citro3d/src/shader.rs
  7. 1
      citro3d/src/texture.rs
  8. 1
      citro3d/src/vbo.rs

28
citro3d/examples/triangle.rs

@ -2,12 +2,10 @@ use citro3d_sys::C3D_Mtx;
use citro3d_sys::{shaderProgram_s, DVLB_s}; use citro3d_sys::{shaderProgram_s, DVLB_s};
use ctru::gfx::{Gfx, Side}; use ctru::gfx::{Gfx, Side};
use ctru::services::apt::Apt; use ctru::services::apt::Apt;
use ctru::services::gspgpu::FramebufferFormat;
use ctru::services::hid::{Hid, KeyPad}; use ctru::services::hid::{Hid, KeyPad};
use ctru::services::soc::Soc; use ctru::services::soc::Soc;
use citro3d::render::{ClearFlags, DepthFormat, TransferFormat}; use citro3d::render::{ClearFlags, ColorFormat, DepthFormat};
use citro3d::C3DContext;
use std::ffi::CStr; use std::ffi::CStr;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
@ -59,20 +57,17 @@ fn main() {
let mut top_screen = gfx.top_screen.borrow_mut(); let mut top_screen = gfx.top_screen.borrow_mut();
let frame_buffer = top_screen.get_raw_framebuffer(Side::Left); let frame_buffer = top_screen.get_raw_framebuffer(Side::Left);
let ctx = C3DContext::new().expect("failed to initialize Citro3D"); let mut instance = citro3d::Instance::new().expect("failed to initialize Citro3D");
let mut render_target = ctx
let mut render_target = instance
.render_target_for_screen( .render_target_for_screen(
&frame_buffer, &frame_buffer,
// TODO: why doesn't getting this from the screen work? ColorFormat::RGBA8,
FramebufferFormat::Rgba8.into(),
DepthFormat::Depth24Stencil8, DepthFormat::Depth24Stencil8,
) )
.expect("failed to create render target"); .expect("failed to create render target");
// TODO: easier construction of flags, see macros in <3ds/gpu/gx.h> render_target.set_output(&*top_screen, Side::Left);
let transfer_flags = (TransferFormat::RGBA8 as u32) << 8 | (TransferFormat::RGB8 as u32) << 12;
render_target.set_output(&*top_screen, Side::Left, transfer_flags);
let (program, uloc_projection, projection, vbo_data, vshader_dvlb) = scene_init(); 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; let clear_color: u32 = 0x7F_7F_7F_FF;
render_target.clear(ClearFlags::ALL, clear_color, 0); 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); scene_render(uloc_projection.into(), &projection);
unsafe { unsafe {
citro3d_sys::C3D_FrameEnd(0); citro3d_sys::C3D_FrameEnd(0);
} }
} }
scene_exit(vbo_data, program, vshader_dvlb); scene_exit(vbo_data, program, vshader_dvlb);
unsafe {
citro3d_sys::C3D_Fini();
}
} }
static SHBIN_BYTES: &[u8] = static SHBIN_BYTES: &[u8] =

2
citro3d/src/error.rs

@ -13,6 +13,8 @@ pub enum Error {
FailedToInitialize, FailedToInitialize,
/// A size parameter was specified that cannot be converted to the proper type. /// A size parameter was specified that cannot be converted to the proper type.
InvalidSize, InvalidSize,
/// Failed to select the given render target for drawing to.
InvalidRenderTarget,
} }
impl From<TryFromIntError> for Error { impl From<TryFromIntError> for Error {

37
citro3d/src/lib.rs

@ -6,32 +6,34 @@ pub mod shader;
pub mod texture; pub mod texture;
pub mod vbo; pub mod vbo;
use citro3d_sys::C3D_FrameDrawOn;
use ctru::gfx::RawFrameBuffer; use ctru::gfx::RawFrameBuffer;
pub use error::{Error, Result}; pub use error::{Error, Result};
use render::Target; 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] #[non_exhaustive]
#[derive(Debug)] #[derive(Debug)]
pub struct C3DContext; pub struct Instance;
impl C3DContext { impl Instance {
/// Initialize the default context. /// Initialize the default `citro3d` instance.
/// ///
/// # Errors /// # Errors
/// ///
/// Fails if the `citro3d` library cannot be initialized. /// Fails if `citro3d` cannot be initialized.
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
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 /// # Errors
/// ///
/// Fails if the `citro3d` library cannot be initialized. /// Fails if `citro3d` cannot be initialized.
pub fn with_command_buffer_size(size: u32) -> Result<Self> { pub fn with_cmdbuf_size(size: u32) -> Result<Self> {
if unsafe { citro3d_sys::C3D_Init(size) } { if unsafe { citro3d_sys::C3D_Init(size) } {
Ok(Self) Ok(Self)
} else { } else {
@ -50,6 +52,7 @@ impl C3DContext {
color_format: render::ColorFormat, color_format: render::ColorFormat,
depth_format: render::DepthFormat, depth_format: render::DepthFormat,
) -> Result<Target> { ) -> Result<Target> {
let _ = self;
Target::new( Target::new(
frame_buffer.width.into(), frame_buffer.width.into(),
frame_buffer.height.into(), frame_buffer.height.into(),
@ -57,9 +60,23 @@ impl C3DContext {
depth_format, 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) { fn drop(&mut self) {
unsafe { unsafe {
citro3d_sys::C3D_Fini(); citro3d_sys::C3D_Fini();

103
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::{ 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::gfx;
use ctru::services::gspgpu; use ctru::services::gspgpu::FramebufferFormat;
use crate::{Error, Result}; use crate::{Error, Result};
/// A render target for `citro3d`. This is the data structure which handles sending mod transfer;
/// data to the GPU
/// 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 { 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 { impl Target {
@ -26,7 +41,7 @@ impl Target {
color_format: ColorFormat, color_format: ColorFormat,
depth_format: DepthFormat, depth_format: DepthFormat,
) -> Result<Self> { ) -> Result<Self> {
let tag = unsafe { let raw = unsafe {
C3D_RenderTargetCreate( C3D_RenderTargetCreate(
width.try_into()?, width.try_into()?,
height.try_into()?, height.try_into()?,
@ -35,58 +50,47 @@ impl Target {
) )
}; };
if tag.is_null() { if raw.is_null() {
Err(Error::FailedToInitialize) Err(Error::FailedToInitialize)
} else { } else {
Ok(Self { tag }) Ok(Self { raw, color_format })
} }
} }
/// Sets the screen to actually display the output of this render target. /// 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 { unsafe {
citro3d_sys::C3D_RenderTargetSetOutput( citro3d_sys::C3D_RenderTargetSetOutput(
self.tag, self.raw,
screen.as_raw(), screen.as_raw(),
side.into(), 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 { 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? /// Return the underlying `citro3d` render target for this target.
pub fn set_for_draw(&mut self) { pub(crate) fn as_raw(&self) -> *mut C3D_RenderTarget {
unsafe { self.raw
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! { bitflags::bitflags! {
/// Indicate whether color, depth buffer, or both values should be cleared.
pub struct ClearFlags: u32 { pub struct ClearFlags: u32 {
const COLOR = citro3d_sys::C3D_CLEAR_COLOR; const COLOR = citro3d_sys::C3D_CLEAR_COLOR;
const DEPTH = citro3d_sys::C3D_CLEAR_DEPTH; 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. /// The color format to use when rendering on the GPU.
#[repr(u32)] #[repr(u32)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -118,23 +114,28 @@ pub enum ColorFormat {
RGBA4 = citro3d_sys::GPU_RB_RGBA4, RGBA4 = citro3d_sys::GPU_RB_RGBA4,
} }
impl From<gspgpu::FramebufferFormat> for ColorFormat { impl From<FramebufferFormat> for ColorFormat {
fn from(format: gspgpu::FramebufferFormat) -> Self { fn from(format: FramebufferFormat) -> Self {
match format { match format {
gspgpu::FramebufferFormat::Rgba8 => Self::RGBA8, FramebufferFormat::Rgba8 => Self::RGBA8,
gspgpu::FramebufferFormat::Rgb565 => Self::RGB565, FramebufferFormat::Rgb565 => Self::RGB565,
gspgpu::FramebufferFormat::Rgb5A1 => Self::RGBA5551, FramebufferFormat::Rgb5A1 => Self::RGBA5551,
gspgpu::FramebufferFormat::Rgba4 => Self::RGBA4, FramebufferFormat::Rgba4 => Self::RGBA4,
fmt => panic!("Unsupported frame buffer format {fmt:?}"), // this one seems unusual, but it appears to work fine:
FramebufferFormat::Bgr8 => Self::RGB8,
} }
} }
} }
/// The depth buffer format to use when rendering.
#[repr(u32)] #[repr(u32)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum DepthFormat { pub enum DepthFormat {
/// 16-bit depth.
Depth16 = citro3d_sys::GPU_RB_DEPTH16, Depth16 = citro3d_sys::GPU_RB_DEPTH16,
/// 24-bit depth.
Depth24 = citro3d_sys::GPU_RB_DEPTH24, Depth24 = citro3d_sys::GPU_RB_DEPTH24,
/// 24-bit depth + 8-bit Stencil.
Depth24Stencil8 = citro3d_sys::GPU_RB_DEPTH24_STENCIL8, Depth24Stencil8 = citro3d_sys::GPU_RB_DEPTH24_STENCIL8,
} }

57
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<ColorFormat> 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,
}
}
}

1
citro3d/src/shader.rs

@ -0,0 +1 @@

1
citro3d/src/texture.rs

@ -0,0 +1 @@

1
citro3d/src/vbo.rs

@ -0,0 +1 @@
Loading…
Cancel
Save