Skip to content

Commit e336164

Browse files
authored
Merge pull request #52 from rust3ds/feature/indexed-drawing
feat: add indexed drawing
2 parents ea13d0d + 1d3d5d2 commit e336164

File tree

5 files changed

+357
-2
lines changed

5 files changed

+357
-2
lines changed

citro3d/examples/cube.rs

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
//! This example demonstrates drawing a coloured cube using indexed rendering.
2+
3+
#![feature(allocator_api)]
4+
5+
use citro3d::macros::include_shader;
6+
use citro3d::math::{
7+
AspectRatio, ClipPlanes, CoordinateOrientation, FVec3, Matrix4, Projection, StereoDisplacement,
8+
};
9+
use citro3d::render::ClearFlags;
10+
use citro3d::{attrib, buffer, render, shader, texenv};
11+
use ctru::prelude::*;
12+
use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D};
13+
14+
#[repr(C)]
15+
#[derive(Copy, Clone)]
16+
struct Vec3 {
17+
x: f32,
18+
y: f32,
19+
z: f32,
20+
}
21+
22+
impl Vec3 {
23+
const fn new(x: f32, y: f32, z: f32) -> Self {
24+
Self { x, y, z }
25+
}
26+
}
27+
28+
#[repr(C)]
29+
#[derive(Copy, Clone)]
30+
struct Vertex {
31+
pos: Vec3,
32+
color: Vec3,
33+
}
34+
35+
// borrowed from https://bevyengine.org/examples/3D%20Rendering/generate-custom-mesh/
36+
const VERTS: &[[f32; 3]] = &[
37+
// top (facing towards +y)
38+
[-0.5, 0.5, -0.5], // vertex with index 0
39+
[0.5, 0.5, -0.5], // vertex with index 1
40+
[0.5, 0.5, 0.5], // etc. until 23
41+
[-0.5, 0.5, 0.5],
42+
// bottom (-y)
43+
[-0.5, -0.5, -0.5],
44+
[0.5, -0.5, -0.5],
45+
[0.5, -0.5, 0.5],
46+
[-0.5, -0.5, 0.5],
47+
// right (+x)
48+
[0.5, -0.5, -0.5],
49+
[0.5, -0.5, 0.5],
50+
[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
51+
[0.5, 0.5, -0.5],
52+
// left (-x)
53+
[-0.5, -0.5, -0.5],
54+
[-0.5, -0.5, 0.5],
55+
[-0.5, 0.5, 0.5],
56+
[-0.5, 0.5, -0.5],
57+
// back (+z)
58+
[-0.5, -0.5, 0.5],
59+
[-0.5, 0.5, 0.5],
60+
[0.5, 0.5, 0.5],
61+
[0.5, -0.5, 0.5],
62+
// forward (-z)
63+
[-0.5, -0.5, -0.5],
64+
[-0.5, 0.5, -0.5],
65+
[0.5, 0.5, -0.5],
66+
[0.5, -0.5, -0.5],
67+
];
68+
69+
static SHADER_BYTES: &[u8] = include_shader!("assets/vshader.pica");
70+
const CLEAR_COLOR: u32 = 0x68_B0_D8_FF;
71+
72+
fn main() {
73+
let mut soc = Soc::new().expect("failed to get SOC");
74+
drop(soc.redirect_to_3dslink(true, true));
75+
76+
let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
77+
let mut hid = Hid::new().expect("Couldn't obtain HID controller");
78+
let apt = Apt::new().expect("Couldn't obtain APT controller");
79+
80+
let mut instance = citro3d::Instance::new().expect("failed to initialize Citro3D");
81+
82+
let top_screen = TopScreen3D::from(&gfx.top_screen);
83+
84+
let (mut top_left, mut top_right) = top_screen.split_mut();
85+
86+
let RawFrameBuffer { width, height, .. } = top_left.raw_framebuffer();
87+
let mut top_left_target = instance
88+
.render_target(width, height, top_left, None)
89+
.expect("failed to create render target");
90+
91+
let RawFrameBuffer { width, height, .. } = top_right.raw_framebuffer();
92+
let mut top_right_target = instance
93+
.render_target(width, height, top_right, None)
94+
.expect("failed to create render target");
95+
96+
let mut bottom_screen = gfx.bottom_screen.borrow_mut();
97+
let RawFrameBuffer { width, height, .. } = bottom_screen.raw_framebuffer();
98+
99+
let mut bottom_target = instance
100+
.render_target(width, height, bottom_screen, None)
101+
.expect("failed to create bottom screen render target");
102+
103+
let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap();
104+
let vertex_shader = shader.get(0).unwrap();
105+
106+
let program = shader::Program::new(vertex_shader).unwrap();
107+
instance.bind_program(&program);
108+
let mut vbo_data = Vec::with_capacity_in(VERTS.len(), ctru::linear::LinearAllocator);
109+
for vert in VERTS.iter().enumerate().map(|(i, v)| Vertex {
110+
pos: Vec3 {
111+
x: v[0],
112+
y: v[1],
113+
z: v[2],
114+
},
115+
color: {
116+
// Give each vertex a slightly different color just to highlight edges/corners
117+
let value = i as f32 / VERTS.len() as f32;
118+
Vec3::new(1.0, 0.7 * value, 0.5)
119+
},
120+
}) {
121+
vbo_data.push(vert);
122+
}
123+
124+
let attr_info = build_attrib_info();
125+
126+
let mut buf_info = buffer::Info::new();
127+
let vbo_slice = buf_info.add(&vbo_data, &attr_info).unwrap();
128+
129+
// Configure the first fragment shading substage to just pass through the vertex color
130+
// See https://www.opengl.org/sdk/docs/man2/xhtml/glTexEnv.xml for more insight
131+
let stage0 = texenv::Stage::new(0).unwrap();
132+
instance
133+
.texenv(stage0)
134+
.src(texenv::Mode::BOTH, texenv::Source::PrimaryColor, None, None)
135+
.func(texenv::Mode::BOTH, texenv::CombineFunc::Replace);
136+
137+
let projection_uniform_idx = program.get_uniform("projection").unwrap();
138+
let camera_transform = Matrix4::looking_at(
139+
FVec3::new(1.8, 1.8, 1.8),
140+
FVec3::new(0.0, 0.0, 0.0),
141+
FVec3::new(0.0, 1.0, 0.0),
142+
CoordinateOrientation::RightHanded,
143+
);
144+
let indices: &[u8] = &[
145+
0, 3, 1, 1, 3, 2, // triangles making up the top (+y) facing side.
146+
4, 5, 7, 5, 6, 7, // bottom (-y)
147+
8, 11, 9, 9, 11, 10, // right (+x)
148+
12, 13, 15, 13, 14, 15, // left (-x)
149+
16, 19, 17, 17, 19, 18, // back (+z)
150+
20, 21, 23, 21, 22, 23, // forward (-z)
151+
];
152+
let index_buffer = vbo_slice.index_buffer(indices).unwrap();
153+
154+
while apt.main_loop() {
155+
hid.scan_input();
156+
157+
if hid.keys_down().contains(KeyPad::START) {
158+
break;
159+
}
160+
161+
instance.render_frame_with(|instance| {
162+
let mut render_to = |target: &mut render::Target, projection| {
163+
target.clear(ClearFlags::ALL, CLEAR_COLOR, 0);
164+
165+
instance
166+
.select_render_target(target)
167+
.expect("failed to set render target");
168+
169+
instance.bind_vertex_uniform(projection_uniform_idx, projection * camera_transform);
170+
171+
instance.set_attr_info(&attr_info);
172+
unsafe {
173+
instance.draw_elements(buffer::Primitive::Triangles, vbo_slice, &index_buffer);
174+
}
175+
};
176+
177+
let Projections {
178+
left_eye,
179+
right_eye,
180+
center,
181+
} = calculate_projections();
182+
183+
render_to(&mut top_left_target, &left_eye);
184+
render_to(&mut top_right_target, &right_eye);
185+
render_to(&mut bottom_target, &center);
186+
});
187+
}
188+
}
189+
190+
fn build_attrib_info() -> attrib::Info {
191+
// Configure attributes for use with the vertex shader
192+
let mut attr_info = attrib::Info::new();
193+
194+
let reg0 = attrib::Register::new(0).unwrap();
195+
let reg1 = attrib::Register::new(1).unwrap();
196+
197+
attr_info
198+
.add_loader(reg0, attrib::Format::Float, 3)
199+
.unwrap();
200+
201+
attr_info
202+
.add_loader(reg1, attrib::Format::Float, 3)
203+
.unwrap();
204+
205+
attr_info
206+
}
207+
208+
struct Projections {
209+
left_eye: Matrix4,
210+
right_eye: Matrix4,
211+
center: Matrix4,
212+
}
213+
214+
fn calculate_projections() -> Projections {
215+
// TODO: it would be cool to allow playing around with these parameters on
216+
// the fly with D-pad, etc.
217+
let slider_val = ctru::os::current_3d_slider_state();
218+
let interocular_distance = slider_val / 2.0;
219+
220+
let vertical_fov = 40.0_f32.to_radians();
221+
let screen_depth = 2.0;
222+
223+
let clip_planes = ClipPlanes {
224+
near: 0.01,
225+
far: 100.0,
226+
};
227+
228+
let (left, right) = StereoDisplacement::new(interocular_distance, screen_depth);
229+
230+
let (left_eye, right_eye) =
231+
Projection::perspective(vertical_fov, AspectRatio::TopScreen, clip_planes)
232+
.stereo_matrices(left, right);
233+
234+
let center =
235+
Projection::perspective(vertical_fov, AspectRatio::BottomScreen, clip_planes).into();
236+
237+
Projections {
238+
left_eye,
239+
right_eye,
240+
center,
241+
}
242+
}

citro3d/src/buffer.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
66
use std::mem::MaybeUninit;
77

8+
use ctru::linear::LinearAllocator;
9+
10+
use crate::Error;
811
use crate::attrib;
912

1013
/// Vertex buffer info. This struct is used to describe the shape of the buffer
@@ -46,6 +49,68 @@ impl Slice<'_> {
4649
pub fn info(&self) -> &Info {
4750
self.buf_info
4851
}
52+
53+
/// Get an index buffer for this slice using the given indices.
54+
///
55+
/// # Errors
56+
///
57+
/// Returns an error if:
58+
/// - any of the given indices are out of bounds.
59+
/// - the given slice is too long for its length to fit in a `libc::c_int`.
60+
pub fn index_buffer<I>(&self, indices: &[I]) -> Result<Indices<'_, I>, Error>
61+
where
62+
I: Index + Copy + Into<libc::c_int>,
63+
{
64+
if libc::c_int::try_from(indices.len()).is_err() {
65+
return Err(Error::InvalidSize);
66+
}
67+
68+
for &idx in indices {
69+
let idx = idx.into();
70+
let len = self.len();
71+
if idx >= len {
72+
return Err(Error::IndexOutOfBounds { idx, len });
73+
}
74+
}
75+
76+
Ok(unsafe { self.index_buffer_unchecked(indices) })
77+
}
78+
79+
/// Get an index buffer for this slice using the given indices without
80+
/// bounds checking.
81+
///
82+
/// # Safety
83+
///
84+
/// If any indices are outside this buffer it can cause an invalid access by the GPU
85+
/// (this crashes citra).
86+
pub unsafe fn index_buffer_unchecked<I: Index + Clone>(&self, indices: &[I]) -> Indices<'_, I> {
87+
let mut buffer = Vec::with_capacity_in(indices.len(), LinearAllocator);
88+
buffer.extend_from_slice(indices);
89+
Indices {
90+
buffer,
91+
_slice: *self,
92+
}
93+
}
94+
}
95+
96+
/// An index buffer for indexed drawing. See [`Slice::index_buffer`] to obtain one.
97+
pub struct Indices<'buf, I> {
98+
pub(crate) buffer: Vec<I, LinearAllocator>,
99+
_slice: Slice<'buf>,
100+
}
101+
102+
/// A type that can be used as an index for indexed drawing.
103+
pub trait Index: crate::private::Sealed {
104+
/// The data type of the index, as used by [`citro3d_sys::C3D_DrawElements`]'s `type_` parameter.
105+
const TYPE: libc::c_int;
106+
}
107+
108+
impl Index for u8 {
109+
const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_BYTE as _;
110+
}
111+
112+
impl Index for u16 {
113+
const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_SHORT as _;
49114
}
50115

51116
/// The geometric primitive to draw (i.e. what shapes the buffer data describes).

citro3d/src/error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ pub enum Error {
3636
InvalidName,
3737
/// The requested resource could not be found.
3838
NotFound,
39+
/// Attempted to use an index that was out of bounds.
40+
IndexOutOfBounds {
41+
/// The index used.
42+
idx: libc::c_int,
43+
/// The length of the collection.
44+
len: libc::c_int,
45+
},
3946
}
4047

4148
impl From<TryFromIntError> for Error {

0 commit comments

Comments
 (0)