Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add indexed drawing #52

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 246 additions & 0 deletions citro3d/examples/cube.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
//! 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::macros::include_shader;
use citro3d::math::{
AspectRatio, ClipPlanes, CoordinateOrientation, FVec3, Matrix4, Projection, StereoDisplacement,
};
use citro3d::render::ClearFlags;
use citro3d::{attrib, buffer, render, shader, texenv};
use ctru::prelude::*;
use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D};

#[repr(C)]
#[derive(Copy, Clone)]
struct Vec3 {
x: f32,
y: f32,
z: f32,
}

impl Vec3 {
const fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
}

#[repr(C)]
#[derive(Copy, Clone)]
struct Vertex {
pos: Vec3,
color: Vec3,
}

// borrowed from https://bevyengine.org/examples/3D%20Rendering/generate-custom-mesh/
const VERTS: &[[f32; 3]] = &[
// top (facing towards +y)
[-0.5, 0.5, -0.5], // vertex with index 0
[0.5, 0.5, -0.5], // vertex with index 1
[0.5, 0.5, 0.5], // etc. until 23
[-0.5, 0.5, 0.5],
// bottom (-y)
[-0.5, -0.5, -0.5],
[0.5, -0.5, -0.5],
[0.5, -0.5, 0.5],
[-0.5, -0.5, 0.5],
// right (+x)
[0.5, -0.5, -0.5],
[0.5, -0.5, 0.5],
[0.5, 0.5, 0.5], // This vertex is at the same position as vertex with index 2, but they'll have different UV and normal
[0.5, 0.5, -0.5],
// left (-x)
[-0.5, -0.5, -0.5],
[-0.5, -0.5, 0.5],
[-0.5, 0.5, 0.5],
[-0.5, 0.5, -0.5],
// back (+z)
[-0.5, -0.5, 0.5],
[-0.5, 0.5, 0.5],
[0.5, 0.5, 0.5],
[0.5, -0.5, 0.5],
// forward (-z)
[-0.5, -0.5, -0.5],
[-0.5, 0.5, -0.5],
[0.5, 0.5, -0.5],
[0.5, -0.5, -0.5],
];

static SHADER_BYTES: &[u8] = include_shader!("assets/vshader.pica");
const CLEAR_COLOR: u32 = 0x68_B0_D8_FF;

fn main() {
let mut soc = Soc::new().expect("failed to get SOC");
drop(soc.redirect_to_3dslink(true, true));

let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller");

let mut instance = citro3d::Instance::new().expect("failed to initialize Citro3D");

let top_screen = TopScreen3D::from(&gfx.top_screen);

let (mut top_left, mut top_right) = top_screen.split_mut();

let RawFrameBuffer { width, height, .. } = top_left.raw_framebuffer();
let mut top_left_target = instance
.render_target(width, height, top_left, None)
.expect("failed to create render target");

let RawFrameBuffer { width, height, .. } = top_right.raw_framebuffer();
let mut top_right_target = instance
.render_target(width, height, top_right, None)
.expect("failed to create render target");

let mut bottom_screen = gfx.bottom_screen.borrow_mut();
let RawFrameBuffer { width, height, .. } = bottom_screen.raw_framebuffer();

let mut bottom_target = instance
.render_target(width, height, bottom_screen, None)
.expect("failed to create bottom screen render target");

let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap();
let vertex_shader = shader.get(0).unwrap();

let program = shader::Program::new(vertex_shader).unwrap();
instance.bind_program(&program);
let mut vbo_data = Vec::with_capacity_in(VERTS.len(), ctru::linear::LinearAllocator);
for vert in VERTS.iter().enumerate().map(|(i, v)| Vertex {
pos: Vec3 {
x: v[0],
y: v[1],
z: v[2],
},
color: {
// Give each vertex a slightly different color just to highlight edges/corners
let value = i as f32 / VERTS.len() as f32;
Vec3::new(1.0, 0.7 * value, 0.5)
},
}) {
vbo_data.push(vert);
}

let mut buf_info = buffer::Info::new();
let (attr_info, vbo_slice) = prepare_vbos(&mut buf_info, &vbo_data);

// Configure the first fragment shading substage to just pass through the vertex color
// See https://www.opengl.org/sdk/docs/man2/xhtml/glTexEnv.xml for more insight
let stage0 = texenv::Stage::new(0).unwrap();
instance
.texenv(stage0)
.src(texenv::Mode::BOTH, texenv::Source::PrimaryColor, None, None)
.func(texenv::Mode::BOTH, texenv::CombineFunc::Replace);

let projection_uniform_idx = program.get_uniform("projection").unwrap();
let camera_transform = Matrix4::looking_at(
FVec3::new(1.8, 1.8, 1.8),
FVec3::new(0.0, 0.0, 0.0),
FVec3::new(0.0, 1.0, 0.0),
CoordinateOrientation::RightHanded,
);
let indices: &[u8] = &[
0, 3, 1, 1, 3, 2, // triangles making up the top (+y) facing side.
4, 5, 7, 5, 6, 7, // bottom (-y)
8, 11, 9, 9, 11, 10, // right (+x)
12, 13, 15, 13, 14, 15, // left (-x)
16, 19, 17, 17, 19, 18, // back (+z)
20, 21, 23, 21, 22, 23, // forward (-z)
];
let indices = vbo_slice.index_buffer(indices).unwrap();

while apt.main_loop() {
hid.scan_input();

if hid.keys_down().contains(KeyPad::START) {
break;
}

instance.render_frame_with(|instance| {
let mut render_to = |target: &mut render::Target, projection| {
target.clear(ClearFlags::ALL, CLEAR_COLOR, 0);

instance
.select_render_target(target)
.expect("failed to set render target");

instance.bind_vertex_uniform(projection_uniform_idx, projection * camera_transform);

instance.set_attr_info(&attr_info);
unsafe {
instance.draw_elements(buffer::Primitive::Triangles, &buf_info, &indices);
}
};

let Projections {
left_eye,
right_eye,
center,
} = calculate_projections();

render_to(&mut top_left_target, &left_eye);
render_to(&mut top_right_target, &right_eye);
render_to(&mut bottom_target, &center);
});
}
}

fn prepare_vbos<'a>(
buf_info: &'a mut buffer::Info,
vbo_data: &'a [Vertex],
) -> (attrib::Info, buffer::Slice<'a>) {
// Configure attributes for use with the vertex shader
let mut attr_info = attrib::Info::new();

let reg0 = attrib::Register::new(0).unwrap();
let reg1 = attrib::Register::new(1).unwrap();

attr_info
.add_loader(reg0, attrib::Format::Float, 3)
.unwrap();

attr_info
.add_loader(reg1, attrib::Format::Float, 3)
.unwrap();

let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap();

(attr_info, buf_idx)
}

struct Projections {
left_eye: Matrix4,
right_eye: Matrix4,
center: Matrix4,
}

fn calculate_projections() -> Projections {
// TODO: it would be cool to allow playing around with these parameters on
// the fly with D-pad, etc.
let slider_val = ctru::os::current_3d_slider_state();
let interocular_distance = slider_val / 2.0;

let vertical_fov = 40.0_f32.to_radians();
let screen_depth = 2.0;

let clip_planes = ClipPlanes {
near: 0.01,
far: 100.0,
};

let (left, right) = StereoDisplacement::new(interocular_distance, screen_depth);

let (left_eye, right_eye) =
Projection::perspective(vertical_fov, AspectRatio::TopScreen, clip_planes)
.stereo_matrices(left, right);

let center =
Projection::perspective(vertical_fov, AspectRatio::BottomScreen, clip_planes).into();

Projections {
left_eye,
right_eye,
center,
}
}
59 changes: 59 additions & 0 deletions citro3d/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

use std::mem::MaybeUninit;

use ctru::linear::LinearAllocator;

use crate::attrib;
use crate::Error;

/// Vertex buffer info. This struct is used to describe the shape of the buffer
/// data to be sent to the GPU for rendering.
Expand Down Expand Up @@ -46,6 +49,62 @@ impl Slice<'_> {
pub fn info(&self) -> &Info {
self.buf_info
}

/// Get an index buffer for this slice using the given indices.
///
/// # Errors
///
/// Returns an error if:
/// - any of the given indices are out of bounds.
/// - the given slice is too long for its length to fit in a `libc::c_int`.
pub fn index_buffer<I>(&self, indices: &[I]) -> Result<Vec<I, LinearAllocator>, Error>
ian-h-chamberlain marked this conversation as resolved.
Show resolved Hide resolved
where
I: Index + Copy + Into<libc::c_int>,
{
if libc::c_int::try_from(indices.len()).is_err() {
return Err(Error::InvalidSize);
}

for &idx in indices {
let idx = idx.into();
let len = self.len();
if idx >= len {
return Err(Error::IndexOutOfBounds { idx, len });
}
}

Ok(unsafe { self.index_buffer_unchecked(indices) })
}

/// Get an index buffer for this slice using the given indices without
/// bounds checking.
///
/// # Safety
///
/// If any indices are outside this buffer it can cause an invalid access by the GPU
/// (this crashes citra).
pub unsafe fn index_buffer_unchecked<I: Index + Clone>(
&self,
indices: &[I],
) -> Vec<I, LinearAllocator> {
let mut buf = Vec::with_capacity_in(indices.len(), LinearAllocator);
buf.extend_from_slice(indices);
buf
}
}

/// A type that can be used as an index for indexed drawing.
pub trait Index: crate::private::Sealed {
/// The data type of the index, as used by [`citro3d_sys::C3D_DrawElements`]'s `type_` parameter.
const TYPE: libc::c_int;
}

impl Index for u8 {
const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_BYTE as _;
}

impl Index for u16 {
const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_SHORT as _;
}

/// The geometric primitive to draw (i.e. what shapes the buffer data describes).
Expand Down
8 changes: 8 additions & 0 deletions citro3d/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! General-purpose error and result types returned by public APIs of this crate.

use core::fmt;

Check warning on line 3 in citro3d/src/error.rs

View workflow job for this annotation

GitHub Actions / lint (nightly-2024-02-18)

unused import: `core::fmt`
use std::ffi::NulError;
use std::num::TryFromIntError;
use std::sync::TryLockError;
Expand Down Expand Up @@ -36,6 +37,13 @@
InvalidName,
/// The requested resource could not be found.
NotFound,
/// Attempted to use an index that was out of bounds.
IndexOutOfBounds {
/// The index used.
idx: libc::c_int,
/// The length of the collection.
len: libc::c_int,
},
}

impl From<TryFromIntError> for Error {
Expand Down
Loading
Loading