diff --git a/citro3d/Cargo.toml b/citro3d/Cargo.toml index a6e316e..63630ff 100644 --- a/citro3d/Cargo.toml +++ b/citro3d/Cargo.toml @@ -39,3 +39,6 @@ all-features = true default-target = "armv6k-nintendo-3ds" targs = [] cargo-args = ["-Z", "build-std"] + +[package.metadata.cargo-3ds] +romfs_dir = "examples/assets/romfs" diff --git a/citro3d/examples/assets/gshader_geo.pica b/citro3d/examples/assets/gshader_geo.pica new file mode 100644 index 0000000..0ccd7b1 --- /dev/null +++ b/citro3d/examples/assets/gshader_geo.pica @@ -0,0 +1,93 @@ +; Example PICA200 geometry shader +.gsh point c0 + +; Uniforms +.fvec projection[4] + +; Constants +.constf myconst(0.0, 1.0, -1.0, 0.5) +.alias zeros myconst.xxxx ; Vector full of zeros +.alias ones myconst.yyyy ; Vector full of ones +.alias half myconst.wwww + +; Outputs - this time the type *is* used +.out outpos position +.out outclr color + +; Inputs: we will receive the following inputs: +; v0-v1: position/color of the first vertex +; v2-v3: position/color of the second vertex +; v4-v5: position/color of the third vertex + +.entry gmain +.proc gmain + ; Calculate the midpoints of the vertices + mov r4, v0 + add r4, v2, r4 + mul r4, half, r4 + mov r5, v2 + add r5, v4, r5 + mul r5, half, r5 + mov r6, v4 + add r6, v0, r6 + mul r6, half, r6 + + ; Emit the first triangle + mov r0, v0 + mov r1, r4 + mov r2, r6 + call emit_triangle + + ; Emit the second triangle + mov r0, r4 + mov r1, v2 + mov r2, r5 + call emit_triangle + + ; Emit the third triangle + mov r0, r6 + mov r1, r5 + mov r2, v4 + call emit_triangle + + ; We're finished + end +.end + +.proc emit_triangle + ; Emit the first vertex + setemit 0 + mov r8, r0 + mov r9, v1 + call process_vertex + emit + + ; Emit the second vertex + setemit 1 + mov r8, r1 + mov r9, v3 + call process_vertex + emit + + ; Emit the third vertex and finish the primitive + setemit 2, prim + mov r8, r2 + mov r9, v5 + call process_vertex + emit +.end + +; Subroutine +; Inputs: +; r8: vertex position +; r9: vertex color +.proc process_vertex + ; outpos = projectionMatrix * r8 + dp4 outpos.x, projection[0], r8 + dp4 outpos.y, projection[1], r8 + dp4 outpos.z, projection[2], r8 + dp4 outpos.w, projection[3], r8 + + ; outclr = r9 + mov outclr, r9 +.end diff --git a/citro3d/examples/assets/vshader_geo.pica b/citro3d/examples/assets/vshader_geo.pica new file mode 100644 index 0000000..e0ae137 --- /dev/null +++ b/citro3d/examples/assets/vshader_geo.pica @@ -0,0 +1,26 @@ +; Example PICA200 vertex shader + +; Constants +.constf myconst(0.0, 1.0, -1.0, -0.5) +.alias zeros myconst.xxxx ; Vector full of zeros +.alias ones myconst.yyyy ; Vector full of ones + +; Outputs - since we are also using a geoshader the output type isn't really used +.out outpos position +.out outclr color + +; Inputs (defined as aliases for convenience) +.alias inpos v0 +.alias inclr v1 + +.entry vmain +.proc vmain + ; Pass through both inputs to the geoshader + mov outpos.xyz, inpos + mov outpos.w, ones + mov outclr.xyz, inclr + mov outclr.w, ones + + ; We're finished + end +.end diff --git a/citro3d/examples/cube.rs b/citro3d/examples/cube.rs index c27b6c6..e0f7c36 100644 --- a/citro3d/examples/cube.rs +++ b/citro3d/examples/cube.rs @@ -128,7 +128,7 @@ fn main() { let mut buf_info = buffer::Info::new(); buf_info.add(vbo_data, attr_info.permutation()).unwrap(); - let projection_uniform_idx = program.get_uniform("projection").unwrap(); + let projection_uniform_idx = program.get_vertex_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), diff --git a/citro3d/examples/dynamic-shader.rs b/citro3d/examples/dynamic-shader.rs new file mode 100644 index 0000000..ca5214e --- /dev/null +++ b/citro3d/examples/dynamic-shader.rs @@ -0,0 +1,208 @@ +// This examples demonstrates loading a shader into memory at runtime + +#![feature(allocator_api)] + +use citro3d::math::{AspectRatio, ClipPlanes, Matrix4, Projection, StereoDisplacement}; +use citro3d::render::{ClearFlags, Frame, ScreenTarget, Target}; +use citro3d::texenv; +use citro3d::{attrib, buffer, shader}; +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, +} + +static VERTICES: &[Vertex] = &[ + Vertex { + pos: Vec3::new(0.0, 0.5, -3.0), + color: Vec3::new(1.0, 0.0, 0.0), + }, + Vertex { + pos: Vec3::new(-0.5, -0.5, -3.0), + color: Vec3::new(0.0, 1.0, 0.0), + }, + Vertex { + pos: Vec3::new(0.5, -0.5, -3.0), + color: Vec3::new(0.0, 0.0, 1.0), + }, +]; + +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 _romfs = ctru::services::romfs::RomFS::new().unwrap(); + + let shader = { + let shader_bytes = std::fs::read("romfs:/vshader.shbin").unwrap(); + shader::Library::from_bytes(shader_bytes).unwrap() + }; + + let vertex_shader = shader.get(0).unwrap(); + + let program = shader::Program::new(vertex_shader).unwrap(); + let projection_uniform_idx = program.get_vertex_uniform("projection").unwrap(); + + let vbo_data = buffer::Buffer::new(VERTICES); + + let mut buf_info = buffer::Info::new(); + let attr_info = prepare_vbos(&mut buf_info, vbo_data); + + let stage0 = texenv::TexEnv::new() + .src(texenv::Mode::BOTH, texenv::Source::PrimaryColor, None, None) + .func(texenv::Mode::BOTH, texenv::CombineFunc::Replace); + + while apt.main_loop() { + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::START) { + break; + } + + instance.render_frame_with(|mut frame| { + // Sadly closures can't have lifetime specifiers, + // so we wrap `render_to` in this function to force the borrow checker rules. + fn cast_lifetime_to_closure<'frame, T>(x: T) -> T + where + T: Fn(&mut Frame<'frame>, &'frame mut ScreenTarget<'_>, &Matrix4), + { + x + } + + let render_to = cast_lifetime_to_closure(|frame, target, projection| { + target.clear(ClearFlags::ALL, CLEAR_COLOR, 0); + + frame + .select_render_target(target) + .expect("failed to set render target"); + frame.bind_vertex_uniform(projection_uniform_idx, projection); + + frame.set_texenvs(&[stage0]); + + frame.set_attr_info(&attr_info); + + frame + .draw_arrays(buffer::Primitive::Triangles, &buf_info, None) + .unwrap(); + }); + + // We bind the vertex shader. + frame.bind_program(&program); + + // 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 Projections { + left_eye, + right_eye, + center, + } = calculate_projections(); + + render_to(&mut frame, &mut top_left_target, &left_eye); + render_to(&mut frame, &mut top_right_target, &right_eye); + render_to(&mut frame, &mut bottom_target, ¢er); + + frame + }); + } +} + +fn prepare_vbos(buf_info: &mut buffer::Info, vbo_data: buffer::Buffer) -> attrib::Info { + // Configure attributes for use with the vertex shader + let mut attr_info = attrib::Info::new(); + + attr_info + .add_loader(attrib::Register::V0, attrib::Format::Float, 3) + .unwrap(); + + attr_info + .add_loader(attrib::Register::V1, attrib::Format::Float, 3) + .unwrap(); + + buf_info.add(vbo_data, attr_info.permutation()).unwrap(); + + attr_info +} + +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, + } +} diff --git a/citro3d/examples/fragment-light.rs b/citro3d/examples/fragment-light.rs index 54606da..9aee8c3 100644 --- a/citro3d/examples/fragment-light.rs +++ b/citro3d/examples/fragment-light.rs @@ -334,10 +334,10 @@ fn main() { // Setup the rotating view of the cube let mut view = Matrix4::identity(); - let model_idx = program.get_uniform("modelView").unwrap(); + let model_idx = program.get_vertex_uniform("modelView").unwrap(); view.translate(0.0, 0.0, -2.0); - let projection_uniform_idx = program.get_uniform("projection").unwrap(); + let projection_uniform_idx = program.get_vertex_uniform("projection").unwrap(); let stage0 = texenv::TexEnv::new() .src( diff --git a/citro3d/examples/geometry.rs b/citro3d/examples/geometry.rs new file mode 100644 index 0000000..d58aa29 --- /dev/null +++ b/citro3d/examples/geometry.rs @@ -0,0 +1,165 @@ +#![feature(allocator_api)] + +use citro3d::macros::include_shader; +use citro3d::math::{ClipPlanes, Matrix4, Projection}; +use citro3d::render::{ClearFlags, Frame, ScreenTarget, Target}; +use citro3d::texenv; +use citro3d::{attrib, buffer, shader}; +use ctru::prelude::*; +use ctru::services::gfx::{RawFrameBuffer, Screen}; + +#[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, +} + +static VERTICES: &[Vertex] = &[ + Vertex { + pos: Vec3::new(200.0, 200.0, -0.5), + color: Vec3::new(1.0, 0.0, 0.0), + }, + Vertex { + pos: Vec3::new(100.0, 40.0, -0.5), + color: Vec3::new(0.0, 1.0, 0.0), + }, + Vertex { + pos: Vec3::new(300.0, 40.0, -0.5), + color: Vec3::new(0.0, 0.0, 1.0), + }, +]; + +static VERTEX_SHADER: &[u8] = include_shader!("assets/vshader_geo.pica"); +static GEOMETRY_SHADER: &[u8] = include_shader!("assets/gshader_geo.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)); + + println!("soc initialized"); + + 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 mut top_screen = gfx.top_screen.borrow_mut(); + let RawFrameBuffer { width, height, .. } = top_screen.raw_framebuffer(); + + let mut top_target = instance + .render_target(width, height, top_screen, 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 vertex_library = shader::Library::from_bytes(VERTEX_SHADER).unwrap(); + let vertex_shader = vertex_library.get(0).unwrap(); + + let geometry_library = shader::Library::from_bytes(GEOMETRY_SHADER).unwrap(); + let geometry_shader = geometry_library.get(0).unwrap(); + + let mut program = shader::Program::new(vertex_shader).unwrap(); + program.set_geometry_shader(geometry_shader, 6).unwrap(); + + let projection_uniform_idx = program.get_geometry_uniform("projection").unwrap(); + + let vbo_data = buffer::Buffer::new(VERTICES); + + let mut buf_info = buffer::Info::new(); + let attr_info = prepare_vbos(&mut buf_info, vbo_data); + + let stage0 = texenv::TexEnv::new() + .src(texenv::Mode::BOTH, texenv::Source::PrimaryColor, None, None) + .func(texenv::Mode::BOTH, texenv::CombineFunc::Replace); + + let projection = Projection::orthographic( + 0.0..240.0, + 0.0..400.0, + ClipPlanes { + near: 0.0, + far: 1.0, + }, + ).into(); + + while apt.main_loop() { + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::START) { + break; + } + + instance.render_frame_with(|mut frame| { + fn cast_lifetime_to_closure<'frame, T>(x: T) -> T + where + T: Fn(&mut Frame<'frame>, &'frame mut ScreenTarget<'_>, &Matrix4), + { + x + } + + let render_to = cast_lifetime_to_closure(|frame, target, projection| { + target.clear(ClearFlags::ALL, CLEAR_COLOR, 0); + + frame + .select_render_target(target) + .expect("failed to set render target"); + frame.bind_geometry_uniform(projection_uniform_idx, projection); + + frame.set_texenvs(&[stage0]); + + frame.set_attr_info(&attr_info); + + frame + .draw_arrays(buffer::Primitive::GeometryPrim, &buf_info, None) + .unwrap(); + }); + + // We bind the vertex and geometry shaders. + frame.bind_program(&program); + + render_to(&mut frame, &mut top_target, &projection); + render_to(&mut frame, &mut bottom_target, &projection); + + frame + }); + } +} + +fn prepare_vbos(buf_info: &mut buffer::Info, vbo_data: buffer::Buffer) -> attrib::Info { + // Configure attributes for use with the vertex shader + let mut attr_info = attrib::Info::new(); + + attr_info + .add_loader(attrib::Register::V0, attrib::Format::Float, 3) + .unwrap(); + + attr_info + .add_loader(attrib::Register::V1, attrib::Format::Float, 3) + .unwrap(); + + buf_info.add(vbo_data, attr_info.permutation()).unwrap(); + + attr_info +} diff --git a/citro3d/examples/multiple-buffers.rs b/citro3d/examples/multiple-buffers.rs index d2cfca9..38054da 100644 --- a/citro3d/examples/multiple-buffers.rs +++ b/citro3d/examples/multiple-buffers.rs @@ -75,7 +75,7 @@ fn main() { let vertex_shader = shader.get(0).unwrap(); let program = shader::Program::new(vertex_shader).unwrap(); - let projection_uniform_idx = program.get_uniform("projection").unwrap(); + let projection_uniform_idx = program.get_vertex_uniform("projection").unwrap(); let vbo_pos = buffer::Buffer::new(VERTEX_POSITIONS); let vbo_col = buffer::Buffer::new(VERTEX_COLS); diff --git a/citro3d/examples/render-to-texture.rs b/citro3d/examples/render-to-texture.rs index bb258d8..52da4b1 100644 --- a/citro3d/examples/render-to-texture.rs +++ b/citro3d/examples/render-to-texture.rs @@ -113,7 +113,7 @@ fn main() { let vertex_shader = shader.get(0).unwrap(); let program = shader::Program::new(vertex_shader).unwrap(); - let projection_uniform_idx = program.get_uniform("projection").unwrap(); + let projection_uniform_idx = program.get_vertex_uniform("projection").unwrap(); let vbo_data = buffer::Buffer::new(VERTICES); diff --git a/citro3d/examples/shared-library.rs b/citro3d/examples/shared-library.rs new file mode 100644 index 0000000..8eec22f --- /dev/null +++ b/citro3d/examples/shared-library.rs @@ -0,0 +1,163 @@ +#![feature(allocator_api)] + +use std::rc::Rc; + +use citro3d::math::{ClipPlanes, Matrix4, Projection}; +use citro3d::render::{ClearFlags, Frame, ScreenTarget, Target}; +use citro3d::texenv; +use citro3d::{attrib, buffer, shader}; +use ctru::prelude::*; +use ctru::services::gfx::{RawFrameBuffer, Screen}; + +#[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, +} + +static VERTICES: &[Vertex] = &[ + Vertex { + pos: Vec3::new(200.0, 200.0, -0.5), + color: Vec3::new(1.0, 0.0, 0.0), + }, + Vertex { + pos: Vec3::new(100.0, 40.0, -0.5), + color: Vec3::new(0.0, 1.0, 0.0), + }, + Vertex { + pos: Vec3::new(300.0, 40.0, -0.5), + color: Vec3::new(0.0, 0.0, 1.0), + }, +]; + +static SHADER_BYTES: &[u8] = include_bytes!("assets/shader.shbin"); + +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)); + + println!("soc initialized"); + + 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 mut top_screen = gfx.top_screen.borrow_mut(); + let RawFrameBuffer { width, height, .. } = top_screen.raw_framebuffer(); + + let mut top_target = instance + .render_target(width, height, top_screen, 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 = Rc::new(shader::Library::from_bytes(SHADER_BYTES).unwrap()); + let vertex_shader = shader.clone().get_shared(0).unwrap(); + let geometry_shader = shader.get_shared(1).unwrap(); + + let mut program = shader::Program::new(vertex_shader).unwrap(); + program.set_geometry_shader(geometry_shader, 6).unwrap(); + + let projection_uniform_idx = program.get_geometry_uniform("projection").unwrap(); + + let vbo_data = buffer::Buffer::new(VERTICES); + + let mut buf_info = buffer::Info::new(); + let attr_info = prepare_vbos(&mut buf_info, vbo_data); + + let stage0 = texenv::TexEnv::new() + .src(texenv::Mode::BOTH, texenv::Source::PrimaryColor, None, None) + .func(texenv::Mode::BOTH, texenv::CombineFunc::Replace); + + let projection = Projection::orthographic( + 0.0..240.0, + 0.0..400.0, + ClipPlanes { + near: 0.0, + far: 1.0, + }, + ).into(); + + while apt.main_loop() { + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::START) { + break; + } + + instance.render_frame_with(|mut frame| { + fn cast_lifetime_to_closure<'frame, T>(x: T) -> T + where + T: Fn(&mut Frame<'frame>, &'frame mut ScreenTarget<'_>, &Matrix4), + { + x + } + + let render_to = cast_lifetime_to_closure(|frame, target, projection| { + target.clear(ClearFlags::ALL, CLEAR_COLOR, 0); + + frame + .select_render_target(target) + .expect("failed to set render target"); + frame.bind_geometry_uniform(projection_uniform_idx, projection); + + frame.set_texenvs(&[stage0]); + + frame.set_attr_info(&attr_info); + + frame + .draw_arrays(buffer::Primitive::GeometryPrim, &buf_info, None) + .unwrap(); + }); + + // We bind the vertex and geometry shaders. + frame.bind_program(&program); + + render_to(&mut frame, &mut top_target, &projection); + render_to(&mut frame, &mut bottom_target, &projection); + + frame + }); + } +} + +fn prepare_vbos(buf_info: &mut buffer::Info, vbo_data: buffer::Buffer) -> attrib::Info { + // Configure attributes for use with the vertex shader + let mut attr_info = attrib::Info::new(); + + attr_info + .add_loader(attrib::Register::V0, attrib::Format::Float, 3) + .unwrap(); + + attr_info + .add_loader(attrib::Register::V1, attrib::Format::Float, 3) + .unwrap(); + + buf_info.add(vbo_data, attr_info.permutation()).unwrap(); + + attr_info +} diff --git a/citro3d/examples/skybox.rs b/citro3d/examples/skybox.rs index 88dc9b6..9f6ea4b 100644 --- a/citro3d/examples/skybox.rs +++ b/citro3d/examples/skybox.rs @@ -132,8 +132,8 @@ fn main() { let vertex_shader = shader.get(0).unwrap(); let program = shader::Program::new(vertex_shader).unwrap(); - let projection_uniform_idx = program.get_uniform("projection").unwrap(); - let model_view_uniform_idx = program.get_uniform("modelView").unwrap(); + let projection_uniform_idx = program.get_vertex_uniform("projection").unwrap(); + let model_view_uniform_idx = program.get_vertex_uniform("modelView").unwrap(); let vbo_data = buffer::Buffer::new(VERTICES); let mut buf_info = buffer::Info::new(); diff --git a/citro3d/examples/textured.rs b/citro3d/examples/textured.rs index 7d2acf4..e88eadb 100644 --- a/citro3d/examples/textured.rs +++ b/citro3d/examples/textured.rs @@ -108,7 +108,7 @@ fn main() { let vertex_shader = shader.get(0).unwrap(); let program = shader::Program::new(vertex_shader).unwrap(); - let projection_uniform_idx = program.get_uniform("projection").unwrap(); + let projection_uniform_idx = program.get_vertex_uniform("projection").unwrap(); let vbo_data = buffer::Buffer::new(VERTICES); diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 0edc41d..7dcd00c 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -85,7 +85,7 @@ fn main() { let vertex_shader = shader.get(0).unwrap(); let program = shader::Program::new(vertex_shader).unwrap(); - let projection_uniform_idx = program.get_uniform("projection").unwrap(); + let projection_uniform_idx = program.get_vertex_uniform("projection").unwrap(); let vbo_data = buffer::Buffer::new(VERTICES); @@ -104,8 +104,6 @@ fn main() { } instance.render_frame_with(|mut frame| { - // Sadly closures can't have lifetime specifiers, - // so we wrap `render_to` in this function to force the borrow checker rules. fn cast_lifetime_to_closure<'frame, T>(x: T) -> T where T: Fn(&mut Frame<'frame>, &'frame mut ScreenTarget<'_>, &Matrix4), diff --git a/citro3d/src/shader.rs b/citro3d/src/shader.rs index 6b8cc42..5f6248e 100644 --- a/citro3d/src/shader.rs +++ b/citro3d/src/shader.rs @@ -4,9 +4,11 @@ //! For more details about the PICA200 compiler / shader language, see //! documentation for . +use std::borrow::Cow; use std::error::Error; use std::ffi::CString; use std::mem::MaybeUninit; +use std::rc::Rc; use crate::uniform; @@ -20,6 +22,8 @@ use crate::uniform; #[must_use] pub struct Program { program: ctru_sys::shaderProgram_s, + _vsh: Entrypoint, + gsh: Option, } impl Program { @@ -32,7 +36,7 @@ impl Program { /// * the input shader is not a vertex shader or is otherwise invalid #[doc(alias = "shaderProgramInit")] #[doc(alias = "shaderProgramSetVsh")] - pub fn new(vertex_shader: Entrypoint) -> Result { + pub fn new(mut vertex_shader: Entrypoint) -> Result { let mut program = unsafe { let mut program = MaybeUninit::uninit(); let result = ctru_sys::shaderProgramInit(program.as_mut_ptr()); @@ -45,7 +49,11 @@ impl Program { let ret = unsafe { ctru_sys::shaderProgramSetVsh(&mut program, vertex_shader.as_raw()) }; if ret == 0 { - Ok(Self { program }) + Ok(Self { + program, + _vsh: vertex_shader, + gsh: None, + }) } else { Err(ctru::Error::from(ret)) } @@ -60,7 +68,7 @@ impl Program { #[doc(alias = "shaderProgramSetGsh")] pub fn set_geometry_shader( &mut self, - geometry_shader: Entrypoint, + mut geometry_shader: Entrypoint, stride: u8, ) -> Result<(), ctru::Error> { let ret = unsafe { @@ -68,20 +76,21 @@ impl Program { }; if ret == 0 { + self.gsh = Some(geometry_shader); Ok(()) } else { Err(ctru::Error::from(ret)) } } - /// Get the index of a uniform by name. + /// Get the index of a uniform in the vertex shader by name. /// /// # Errors /// /// * If the given `name` contains a null byte /// * If a uniform with the given `name` could not be found #[doc(alias = "shaderInstanceGetUniformLocation")] - pub fn get_uniform(&self, name: &str) -> crate::Result { + pub fn get_vertex_uniform(&self, name: &str) -> crate::Result { let vertex_instance = unsafe { (*self.as_raw()).vertexShader }; assert!( !vertex_instance.is_null(), @@ -100,6 +109,33 @@ impl Program { } } + /// Get the index of a uniform in the geometry shader by name. + /// + /// # Errors + /// + /// * If a geometry shader has not been set + /// * If the given `name` contains a null byte + /// * If a uniform with the given `name` could not be found + #[doc(alias = "shaderInstanceGetUniformLocation")] + pub fn get_geometry_uniform(&self, name: &str) -> crate::Result { + if self.gsh.is_none() { + return Err(crate::Error::MissingProgram); + } + + let geometry_instance = unsafe { (*self.as_raw()).geometryShader }; + + let name = CString::new(name)?; + + let idx = + unsafe { ctru_sys::shaderInstanceGetUniformLocation(geometry_instance, name.as_ptr()) }; + + if idx < 0 { + Err(crate::Error::NotFound) + } else { + Ok((idx as u8).into()) + } + } + pub(crate) fn as_raw(&self) -> *const ctru_sys::shaderProgram_s { &self.program } @@ -137,7 +173,10 @@ impl From for u8 { /// This is the result of parsing a shader binary (`.shbin`), and the resulting /// [`Entrypoint`]s can be used as part of a [`Program`]. #[doc(alias = "DVLB_s")] -pub struct Library(*mut ctru_sys::DVLB_s); +pub struct Library { + dvlb: *mut ctru_sys::DVLB_s, + _bytes: Cow<'static, [u8]>, +} impl Library { /// Parse a new shader library from input bytes. @@ -147,9 +186,11 @@ impl Library { /// An error is returned if the input data does not have an alignment of 4 /// (cannot be safely converted to `&[u32]`). #[doc(alias = "DVLB_ParseFile")] - pub fn from_bytes(bytes: &[u8]) -> Result> { - let aligned: &[u32] = bytemuck::try_cast_slice(bytes)?; - Ok(Self(unsafe { + pub fn from_bytes>>(bytes: B) -> Result> { + let bytes = bytes.into(); + + let aligned: &[u32] = bytemuck::try_cast_slice(&bytes)?; + let dvlb = unsafe { ctru_sys::DVLB_ParseFile( // SAFETY: we're trusting the parse implementation doesn't mutate // the contents of the data. From a quick read it looks like that's @@ -157,14 +198,19 @@ impl Library { aligned.as_ptr().cast_mut(), aligned.len().try_into()?, ) - })) + }; + + Ok(Self { + dvlb, + _bytes: bytes, + }) } /// Get the number of [`Entrypoint`]s in this shader library. #[must_use] #[doc(alias = "numDVLE")] pub fn len(&self) -> usize { - unsafe { (*self.0).numDVLE as usize } + unsafe { (*self.dvlb).numDVLE as usize } } /// Whether the library has any [`Entrypoint`]s or not. @@ -175,11 +221,27 @@ impl Library { /// Get the [`Entrypoint`] at the given index, if present. #[must_use] - pub fn get(&self, index: usize) -> Option> { + pub fn get(self, index: usize) -> Option { if index < self.len() { Some(Entrypoint { - ptr: unsafe { (*self.0).DVLE.add(index) }, - _library: self, + ptr: unsafe { (*self.dvlb).DVLE.add(index) }, + _library: MaybeRc::Owned(self), + }) + } else { + None + } + } + + #[must_use] + /// Get the [`Entrypoint`] at the given index, if present. + /// + /// Like [`Library::get`], except takes `Rc` instead of `Self`, to allow the same library + /// to have multiple entrypoints + pub fn get_shared(self: Rc, index: usize) -> Option { + if index < self.len() { + Some(Entrypoint { + ptr: unsafe { (*self.dvlb).DVLE.add(index) }, + _library: MaybeRc::Shared(self), }) } else { None @@ -187,7 +249,7 @@ impl Library { } fn as_raw(&mut self) -> *mut ctru_sys::DVLB_s { - self.0 + self.dvlb } } @@ -200,16 +262,21 @@ impl Drop for Library { } } +#[allow(dead_code)] +enum MaybeRc { + Owned(T), + Shared(Rc), +} + /// A shader library entrypoint (also called DVLE). This represents either a /// vertex or a geometry shader. -#[derive(Clone, Copy)] -pub struct Entrypoint<'lib> { +pub struct Entrypoint { ptr: *mut ctru_sys::DVLE_s, - _library: &'lib Library, + _library: MaybeRc, } -impl<'lib> Entrypoint<'lib> { - fn as_raw(self) -> *mut ctru_sys::DVLE_s { +impl Entrypoint { + fn as_raw(&mut self) -> *mut ctru_sys::DVLE_s { self.ptr } }